diff --git a/.github/workflows/build-framework-cli.yml b/.github/workflows/build-framework-cli.yml index f8d72dd9..df6ccce6 100644 --- a/.github/workflows/build-framework-cli.yml +++ b/.github/workflows/build-framework-cli.yml @@ -19,19 +19,20 @@ jobs: run: make build-framework - name: Build and Install CLI run: | - make print-debug-info | grep "Mockingbird rpath: /var/tmp/mockingbird/$(make get-version)/libs" PREFIX=$(pwd) HERMETIC=1 make print-debug-info PREFIX=$(pwd) HERMETIC=1 make install - name: Set Up Caching Target run: | - ./bin/mockingbird install \ - --target MockingbirdTests \ - --source MockingbirdTestsHost \ + ./bin/mockingbird configure MockingbirdTests --verbose -- \ + --targets \ + MockingbirdTestsHost \ + MockingbirdShadowedTestsHost \ --support Sources/MockingbirdSupport \ - --output Tests/MockingbirdTests/Mocks/MockingbirdTestsHostMocks.generated.swift \ + --outputs \ + Tests/MockingbirdTests/Mocks/MockingbirdTestsHostMocks.generated.swift \ + Tests/MockingbirdTests/Mocks/MockingbirdShadowedTestsHostMocks.generated.swift \ --header '// Header line 1' '// Header line 2' \ --diagnostics all \ - --loglevel verbose \ --prune stub \ --verbose - name: Test @@ -40,70 +41,36 @@ jobs: run: make test - name: Set Up Non-Caching Target run: | - ./bin/mockingbird install \ - --target MockingbirdTests \ - --source MockingbirdTestsHost \ + ./bin/mockingbird configure MockingbirdTests --verbose -- \ + --targets \ + MockingbirdTestsHost \ + MockingbirdShadowedTestsHost \ --support Sources/MockingbirdSupport \ - --output Tests/MockingbirdTests/Mocks/MockingbirdTestsHostMocks.generated.swift \ + --outputs \ + Tests/MockingbirdTests/Mocks/MockingbirdTestsHostMocks.generated.swift \ + Tests/MockingbirdTests/Mocks/MockingbirdShadowedTestsHostMocks.generated.swift \ --header '// Header line 1' '// Header line 2' \ --disable-cache \ --diagnostics all \ - --loglevel verbose \ --prune stub \ --verbose - name: Test Flakiness run: make test-flaky - name: Set Up Non-Pruning Target run: | - ./bin/mockingbird install \ - --target MockingbirdTests \ - --source MockingbirdTestsHost \ + ./bin/mockingbird configure MockingbirdTests --verbose -- \ + --targets \ + MockingbirdTestsHost \ + MockingbirdShadowedTestsHost \ --support Sources/MockingbirdSupport \ - --output Tests/MockingbirdTests/Mocks/MockingbirdTestsHostMocks.generated.swift \ + --outputs \ + Tests/MockingbirdTests/Mocks/MockingbirdTestsHostMocks.generated.swift \ + Tests/MockingbirdTests/Mocks/MockingbirdShadowedTestsHostMocks.generated.swift \ --header '// Header line 1' '// Header line 2' \ --prune disable \ --disable-cache \ --diagnostics all \ - --loglevel verbose \ --prune disable \ --verbose - name: Test All Thunks run: make clean-test - - build-xcode-12: - name: Xcode 12 toolchain - runs-on: macOS-latest - - steps: - - uses: actions/checkout@v2 - - name: Set Up Environment - run: sudo xcode-select -s /Applications/Xcode_12.app/Contents/Developer - - name: Print Debug Info - run: make print-debug-info - - name: Set Up Project - run: make setup-project - - name: Clean - run: make clean - - name: Build Framework - run: make build-framework - - name: Build and Install CLI - run: | - make print-debug-info | grep "Mockingbird rpath: /var/tmp/mockingbird/$(make get-version)/libs" - PREFIX=$(pwd) HERMETIC=1 make print-debug-info - PREFIX=$(pwd) HERMETIC=1 make install - - name: Set Up Target - run: | - ./bin/mockingbird install \ - --target MockingbirdTests \ - --source MockingbirdTestsHost \ - --support Sources/MockingbirdSupport \ - --output Tests/MockingbirdTests/Mocks/MockingbirdTestsHostMocks.generated.swift \ - --header '// Header line 1' '// Header line 2' \ - --diagnostics all \ - --loglevel verbose \ - --prune stub \ - --verbose - - name: Test - run: make clean-test - - name: Cached Test - run: make test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f5ffaa88..4d5b2137 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,11 +41,6 @@ jobs: with: name: Mockingbird.zip path: Mockingbird.zip - - name: Upload Pkg - uses: actions/upload-artifact@v2 - with: - name: Mockingbird.pkg - path: Mockingbird.pkg - name: Upload Starter Pack uses: actions/upload-artifact@v2 with: @@ -57,8 +52,8 @@ jobs: name: MockingbirdCli.dr path: Codesigning/MockingbirdCli.dr - build-cisafe: - name: Build CI-Safe Signed Artifacts + build-installable: + name: Build Installable Signed Artifacts runs-on: macOS-latest steps: - uses: actions/checkout@v2 @@ -77,7 +72,7 @@ jobs: env: AC_USERNAME: ${{ secrets.AC_USERNAME }} AC_PASSWORD: ${{ secrets.AC_PASSWORD }} - HERMETIC: 1 + MKB_INSTALLABLE: 1 run: make signed-release - name: Document SHAs run: | @@ -88,10 +83,10 @@ jobs: - name: Upload Zip uses: actions/upload-artifact@v2 with: - name: Mockingbird-cisafe.zip + name: Mockingbird-installable.zip path: Mockingbird.zip - name: Upload Pkg uses: actions/upload-artifact@v2 with: - name: Mockingbird-cisafe.pkg + name: Mockingbird.pkg path: Mockingbird.pkg diff --git a/.xcode/xcconfigs/MockingbirdCli.xcconfig b/.xcode/xcconfigs/MockingbirdCli.xcconfig index 53aaceae..66969939 100644 --- a/.xcode/xcconfigs/MockingbirdCli.xcconfig +++ b/.xcode/xcconfigs/MockingbirdCli.xcconfig @@ -1,4 +1,4 @@ INFOPLIST_FILE = $(SRCROOT)/Sources/MockingbirdCli/Info.plist FRAMEWORK_SEARCH_PATHS = $(PLATFORM_DIR)/Developer/Library/Frameworks -MACOSX_DEPLOYMENT_TARGET = 10.14 +MACOSX_DEPLOYMENT_TARGET = 10.15 SUPPORTED_PLATFORMS = macosx diff --git a/Makefile b/Makefile index f51e5cb5..9fda4f32 100644 --- a/Makefile +++ b/Makefile @@ -1,33 +1,36 @@ TEMPORARY_FOLDER_ROOT?=/tmp -HERMETIC?=0 PREFIX?=/usr/local +BIN_DIR?=$(PREFIX)/bin BUILD_TOOL?=xcodebuild -REPO_URL?=https://github.com/birdrides/mockingbird -ARTIFACTS_URL?=$(REPO_URL)/releases/download VERIFY_SIGNATURES?=1 AC_USERNAME?= AC_PASSWORD?= PKG_IDENTITY?=Developer ID Installer: Bird Rides, Inc. (P2T4T6R4SL) BIN_IDENTITY?=Developer ID Application: Bird Rides, Inc. (P2T4T6R4SL) +MKB_INSTALLABLE?=0 +MKB_VERSION?=$(shell /usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$(CLI_BUNDLE_PLIST)") +REPO_URL?=https://github.com/birdrides/mockingbird +ARTIFACTS_URL?=$(REPO_URL)/releases/download +ZIP_RELEASE_URL?=$(ARTIFACTS_URL)/$(MKB_VERSION)/$(ZIP_FILENAME) -# Prevent bad things from happening when cleaning the temporary folder. +# The `DSTROOT` must be kept seperate when running xcodebuild outside of Xcode. TEMPORARY_FOLDER=$(TEMPORARY_FOLDER_ROOT)/Mockingbird.make.dst TEMPORARY_INSTALLER_FOLDER=$(TEMPORARY_FOLDER)/install XCODEBUILD_DERIVED_DATA=$(TEMPORARY_FOLDER)/xcodebuild/DerivedData/MockingbirdFramework XCODE_PATH=$(shell xcode-select --print-path) -CLI_BUNDLE_PLIST=Sources/MockingbirdCli/Info.plist -MKB_VERSION?=$(shell /usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$(CLI_BUNDLE_PLIST)") -VERSION_STRING=$(MKB_VERSION) - -# Needs to be kept in sync with `LoadDylib.swift` and `build-framework-cli.yml`. -$(eval RELATIVE_RPATH_FLAG = $(shell [[ $(HERMETIC) -eq 1 ]] && echo '-Xswiftc -DRELATIVE_RPATH' || echo '')) -$(eval MOCKINGBIRD_RPATH = $(shell [[ $(HERMETIC) -eq 1 ]] && echo '@executable_path' || echo '/var/tmp/mockingbird/$(VERSION_STRING)/libs')) +DEFAULT_XCODE_RPATH=$(XCODE_PATH)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx SIMULATOR_NAME=iphone11-mockingbird SIMULATOR_DEVICE_TYPE=com.apple.CoreSimulator.SimDeviceType.iPhone-11 SIMULATOR_RUNTIME=$(shell xcrun simctl list runtimes | pcregrep -o1 '(com\.apple\.CoreSimulator\.SimRuntime\.iOS\-.*)') -SWIFT_BUILD_FLAGS=--configuration release -Xlinker -weak-l_InternalSwiftSyntaxParser $(RELATIVE_RPATH_FLAG) +# Needs to be kept in sync with `LoadDylib.swift` and `build-framework-cli.yml`. +$(eval MKB_INSTALLABLE_FLAG = $(shell [[ $(MKB_INSTALLABLE) -eq 1 ]] && echo '-Xswiftc -DMKB_INSTALLABLE' || echo '')) + +SWIFT_BUILD_ENV=MKB_BUILD_CLI=1 +SWIFT_BUILD_FLAGS=--configuration release -Xlinker -weak-l_InternalSwiftSyntaxParser $(MKB_INSTALLABLE_FLAG) + +# CLI build configuration. XCODEBUILD_FLAGS=-project 'Mockingbird.xcodeproj' DSTROOT=$(TEMPORARY_FOLDER) XCODEBUILD_MACOS_FLAGS=$(XCODEBUILD_FLAGS) -destination 'platform=OS X' XCODEBUILD_FRAMEWORK_FLAGS=$(XCODEBUILD_FLAGS) \ @@ -37,6 +40,7 @@ XCODEBUILD_FRAMEWORK_FLAGS=$(XCODEBUILD_FLAGS) \ STRIP_INSTALLED_PRODUCT=NO \ SKIP_INSTALL=YES +# Example project build configuration. EXAMPLE_XCODEBUILD_FLAGS=DSTROOT=$(TEMPORARY_FOLDER) EXAMPLE_COCOAPODS_XCODEBUILD_FLAGS=$(EXAMPLE_XCODEBUILD_FLAGS) \ -workspace 'Examples/iOSMockingbirdExample-CocoaPods/iOSMockingbirdExample-CocoaPods.xcworkspace' \ @@ -48,23 +52,20 @@ EXAMPLE_SPM_XCODEBUILD_FLAGS=$(EXAMPLE_XCODEBUILD_FLAGS) \ -project 'Examples/iOSMockingbirdExample-SPM/iOSMockingbirdExample-SPM.xcodeproj' \ -scheme 'iOSMockingbirdExample-SPM' -FRAMEWORKS_FOLDER=/Library/Frameworks -BINARIES_FOLDER=$(PREFIX)/bin -DEFAULT_XCODE_RPATH=$(XCODE_PATH)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx - PKG_BUNDLE_IDENTIFIER=co.bird.mockingbird CLI_DESIGNATED_REQUIREMENT=Codesigning/MockingbirdCli.dr CLI_FILENAME=mockingbird -$(eval CLI_PATH = $(shell [[ $(HERMETIC) -eq 1 ]] && echo "bin/$(VERSION_STRING)" || echo "bin/$(VERSION_STRING)-portable")) -$(eval ZIP_FILENAME = $(shell [[ $(HERMETIC) -eq 1 ]] && echo 'Mockingbird-cisafe.zip' || echo 'Mockingbird.zip')) +CLI_BUNDLE_PLIST=Sources/MockingbirdCli/Info.plist -FRAMEWORK_FILENAME=Mockingbird.framework +# Needs to be kept in sync with the launcher. +$(eval ZIP_FILENAME = $(shell [[ $(MKB_INSTALLABLE) -eq 1 ]] && echo 'Mockingbird-installable.zip' || echo 'Mockingbird.zip')) +FRAMEWORK_FILENAME=Mockingbird.framework MACOS_FRAMEWORK_FILENAME=Mockingbird-macOS.framework IPHONESIMULATOR_FRAMEWORK_FILENAME=Mockingbird-iOS.framework APPLETVSIMULATOR_FRAMEWORK_FILENAME=Mockingbird-tvOS.framework -EXECUTABLE_PATH=$(shell cd Sources && swift build $(SWIFT_BUILD_FLAGS) --show-bin-path)/mockingbird +EXECUTABLE_PATH=$(shell $(SWIFT_BUILD_ENV) swift build $(SWIFT_BUILD_FLAGS) --show-bin-path)/mockingbird MACOS_FRAMEWORK_FOLDER=$(XCODEBUILD_DERIVED_DATA)/Build/Products/Release MACOS_FRAMEWORK_PATH=$(MACOS_FRAMEWORK_FOLDER)/$(FRAMEWORK_FILENAME) @@ -89,13 +90,12 @@ STARTER_PACK_FOLDER=MockingbirdSupport OUTPUT_PACKAGE=Mockingbird.pkg OUTPUT_ZIP=Mockingbird.zip OUTPUT_STARTER_PACK_ZIP=MockingbirdSupport.zip -OUTPUT_DOCS_FOLDER=docs/$(VERSION_STRING) +OUTPUT_DOCS_FOLDER=docs/$(MKB_VERSION) -ZIP_RELEASE_URL?=$(ARTIFACTS_URL)/$(VERSION_STRING)/$(ZIP_FILENAME) SUCCESS_MSG=✅ Verified the CLI binary code signature ERROR_MSG=❌ The CLI binary is not signed with the expected code signature! (Set VERIFY_SIGNATURES=0 to ignore this error.) -REDIRECT_DOCS_PAGE= +REDIRECT_DOCS_PAGE= .PHONY: all all: build @@ -116,7 +116,7 @@ clean-xcode: clean-temporary-files .PHONY: clean-swift clean-swift: - (cd Sources && swift package clean) + swift package clean .PHONY: clean-installables clean-installables: @@ -126,7 +126,6 @@ clean-installables: .PHONY: clean-dylibs clean-dylibs: rm -f Sources/MockingbirdCli/Libraries/*.generated.swift - rm -rf "$(MOCKINGBIRD_RPATH)" .PHONY: clean clean: clean-mocks clean-xcode clean-swift clean-installables clean-dylibs @@ -144,10 +143,13 @@ save-xcschemes: .PHONY: print-debug-info print-debug-info: - @echo "Mockingbird version: $(VERSION_STRING)" + @echo "Mockingbird version: $(MKB_VERSION)" + @echo "Installable build variant: $(MKB_INSTALLABLE)" @echo "Installation prefix: $(PREFIX)" + @echo "Binary directory: $(BIN_DIR)" @echo "Temporary folder: $(TEMPORARY_FOLDER)" - @echo "Mockingbird rpath: $(MOCKINGBIRD_RPATH)" + @echo "Mockingbird rpath: /tmp/mockingbird/$(MKB_VERSION)/libs" + @echo "Mockingbird rpath: /var/tmp/mockingbird/$(MKB_VERSION)/libs" @echo "Build tool: $(BUILD_TOOL)" $(eval XCODE_PATH_VAR = $(XCODE_PATH)) @echo "Xcode path: $(XCODE_PATH_VAR)" @@ -159,7 +161,9 @@ print-debug-info: $(eval XCODEBUILD_VERSION = $(shell xcodebuild -version)) @echo "Xcodebuild version: $(XCODEBUILD_VERSION)" @echo "Swift build flags: $(SWIFT_BUILD_FLAGS)" + @echo "Swift build env: $(SWIFT_BUILD_ENV)" @echo "Simulator runtime: $(SIMULATOR_RUNTIME)" + @echo "Zip release URL: $(ZIP_RELEASE_URL)" .PHONY: generate-embedded-dylibs generate-embedded-dylibs: @@ -170,11 +174,12 @@ generate-embedded-dylibs: .PHONY: build-cli build-cli: generate-embedded-dylibs - (cd Sources && swift build $(SWIFT_BUILD_FLAGS) --product mockingbird) - # Inject custom rpath into binary. + $(SWIFT_BUILD_ENV) swift build $(SWIFT_BUILD_FLAGS) --product mockingbird $(eval RPATH = $(DEFAULT_XCODE_RPATH)) install_name_tool -delete_rpath "$(RPATH)" "$(EXECUTABLE_PATH)" - install_name_tool -add_rpath "$(MOCKINGBIRD_RPATH)" "$(EXECUTABLE_PATH)" + install_name_tool -add_rpath '@executable_path' "$(EXECUTABLE_PATH)" + install_name_tool -add_rpath "/var/tmp/mockingbird/$(MKB_VERSION)/libs" "$(EXECUTABLE_PATH)" + install_name_tool -add_rpath "/tmp/mockingbird/$(MKB_VERSION)/libs" "$(EXECUTABLE_PATH)" .PHONY: build-framework-macos build-framework-macos: @@ -215,8 +220,6 @@ test-cocoapods: setup-cocoapods $(eval DEVICE_UUID = $(shell xcrun simctl create "$(SIMULATOR_NAME)" "$(SIMULATOR_DEVICE_TYPE)" "$(SIMULATOR_RUNTIME)")) $(BUILD_TOOL) -destination "platform=iOS Simulator,id=$(DEVICE_UUID)" $(EXAMPLE_COCOAPODS_XCODEBUILD_FLAGS) test xcrun simctl delete "$(DEVICE_UUID)" - # Ensure the pinned prebuilt binary for CocoaPods exists. - [[ -f Examples/iOSMockingbirdExample-CocoaPods/Pods/MockingbirdFramework/mockingbird ]] .PHONY: test-carthage test-carthage: setup-carthage @@ -301,69 +304,55 @@ docs: clean-docs setup-swiftdoc docs/index.html docs/latest/index.html /usr/local/bin/swift-doc generate \ Sources/MockingbirdFramework \ --module-name Mockingbird \ - --version "$(VERSION_STRING)" \ + --version "$(MKB_VERSION)" \ --output "$(OUTPUT_DOCS_FOLDER)" \ --format html \ - --base-url "/mockingbird/$(VERSION_STRING)" + --base-url "/mockingbird/$(MKB_VERSION)" cp -f docs/swift-doc/Resources/all.min.css "$(OUTPUT_DOCS_FOLDER)/all.css" .PHONY: download download: $(eval CURL_AUTH_HEADER = $(shell [[ -z "${GH_ACCESS_TOKEN}" ]] || echo '-H "Authorization: token' ${GH_ACCESS_TOKEN}'"')) curl $(CURL_AUTH_HEADER) --progress-bar -Lo "$(ZIP_FILENAME)" "$(ZIP_RELEASE_URL)" - mkdir -p "$(CLI_PATH)" - unzip -o "$(ZIP_FILENAME)" "$(CLI_FILENAME)" -d "$(CLI_PATH)" - chmod +x "$(CLI_PATH)/$(CLI_FILENAME)" + mkdir -p "$(BIN_DIR)" + unzip -o "$(ZIP_FILENAME)" "$(CLI_FILENAME)" -d "$(BIN_DIR)" + chmod +x "$(BIN_DIR)/$(CLI_FILENAME)" @if [[ $(VERIFY_SIGNATURES) -eq 1 ]]; then $(MAKE) verify; fi .PHONY: verify verify: - @codesign -v -R "$(CLI_DESIGNATED_REQUIREMENT)" "$(CLI_PATH)/$(CLI_FILENAME)" \ + @codesign -v -R "$(CLI_DESIGNATED_REQUIREMENT)" "$(BIN_DIR)/$(CLI_FILENAME)" \ && echo "$(SUCCESS_MSG)" \ || $$(echo "$(ERROR_MSG)" >&2; exit 1) .PHONY: install install: build-cli - install -d "$(BINARIES_FOLDER)" - install "$(EXECUTABLE_PATH)" "$(BINARIES_FOLDER)" + install -d "$(BIN_DIR)" + install "$(EXECUTABLE_PATH)" "$(BIN_DIR)" .PHONY: install-prebuilt install-prebuilt: download - install -d "$(BINARIES_FOLDER)" - install "$(CLI_PATH)/$(CLI_FILENAME)" "$(BINARIES_FOLDER)" + install -d "$(BIN_DIR)" + install "$(BIN_DIR)/$(CLI_FILENAME)" "$(BIN_DIR)" .PHONY: uninstall uninstall: - rm -f "$(BINARIES_FOLDER)/$(CLI_FILENAME)" - rm -rf "$(FRAMEWORKS_FOLDER)/$(MACOS_FRAMEWORK_FILENAME)" - rm -rf "$(FRAMEWORKS_FOLDER)/$(IPHONESIMULATOR_FRAMEWORK_PATH)" - rm -rf "$(FRAMEWORKS_FOLDER)/$(APPLETVSIMULATOR_FRAMEWORK_PATH)" + rm -f "$(BIN_DIR)/$(CLI_FILENAME)" .PHONY: installables installables: build - install -d "$(TEMPORARY_INSTALLER_FOLDER)$(BINARIES_FOLDER)" - install -d "$(TEMPORARY_INSTALLER_FOLDER)$(FRAMEWORKS_FOLDER)" - - install "$(EXECUTABLE_PATH)" "$(TEMPORARY_INSTALLER_FOLDER)$(BINARIES_FOLDER)" - - cp -rf "$(MACOS_FRAMEWORK_PATH)" "$(TEMPORARY_INSTALLER_FOLDER)$(FRAMEWORKS_FOLDER)/$(MACOS_FRAMEWORK_FILENAME)" - cp -rf "$(IPHONESIMULATOR_FRAMEWORK_PATH)" "$(TEMPORARY_INSTALLER_FOLDER)$(FRAMEWORKS_FOLDER)/$(IPHONESIMULATOR_FRAMEWORK_FILENAME)" - cp -rf "$(APPLETVSIMULATOR_FRAMEWORK_PATH)" "$(TEMPORARY_INSTALLER_FOLDER)$(FRAMEWORKS_FOLDER)/$(APPLETVSIMULATOR_FRAMEWORK_FILENAME)" + install -d "$(TEMPORARY_INSTALLER_FOLDER)$(BIN_DIR)" + install "$(EXECUTABLE_PATH)" "$(TEMPORARY_INSTALLER_FOLDER)$(BIN_DIR)" .PHONY: bundle-artifacts bundle-artifacts: - mkdir -p "$(TEMPORARY_INSTALLER_FOLDER)$(BINARIES_FOLDER)" - cp -f "$(EXECUTABLE_PATH)" "$(TEMPORARY_INSTALLER_FOLDER)$(BINARIES_FOLDER)" - - mkdir -p "$(TEMPORARY_INSTALLER_FOLDER)$(FRAMEWORKS_FOLDER)" - cp -rf "$(MACOS_FRAMEWORK_PATH)" "$(TEMPORARY_INSTALLER_FOLDER)$(FRAMEWORKS_FOLDER)/$(MACOS_FRAMEWORK_FILENAME)" - cp -rf "$(IPHONESIMULATOR_FRAMEWORK_PATH)" "$(TEMPORARY_INSTALLER_FOLDER)$(FRAMEWORKS_FOLDER)/$(IPHONESIMULATOR_FRAMEWORK_FILENAME)" - cp -rf "$(APPLETVSIMULATOR_FRAMEWORK_PATH)" "$(TEMPORARY_INSTALLER_FOLDER)$(FRAMEWORKS_FOLDER)/$(APPLETVSIMULATOR_FRAMEWORK_FILENAME)" + mkdir -p "$(TEMPORARY_INSTALLER_FOLDER)$(BIN_DIR)" + cp -f "$(EXECUTABLE_PATH)" "$(TEMPORARY_INSTALLER_FOLDER)$(BIN_DIR)" .PHONY: signed-installables signed-installables: build bundle-artifacts codesign --sign "$(BIN_IDENTITY)" -v --timestamp --options runtime \ - "$(TEMPORARY_INSTALLER_FOLDER)$(BINARIES_FOLDER)/$(CLI_FILENAME)" + "$(TEMPORARY_INSTALLER_FOLDER)$(BIN_DIR)/$(CLI_FILENAME)" .PHONY: package package: installables @@ -371,7 +360,7 @@ package: installables --identifier "$(PKG_BUNDLE_IDENTIFIER)" \ --install-location "/" \ --root "$(TEMPORARY_INSTALLER_FOLDER)" \ - --version "$(VERSION_STRING)" \ + --version "$(MKB_VERSION)" \ "$(OUTPUT_PACKAGE)" .PHONY: signed-package @@ -380,7 +369,7 @@ signed-package: signed-installables --identifier "$(PKG_BUNDLE_IDENTIFIER)" \ --install-location "/" \ --root "$(TEMPORARY_INSTALLER_FOLDER)" \ - --version "$(VERSION_STRING)" \ + --version "$(MKB_VERSION)" \ --sign "$(PKG_IDENTITY)" \ "$(OUTPUT_PACKAGE)" @[[ -z "$(AC_USERNAME)" ]] || xcrun altool \ @@ -396,10 +385,7 @@ stapled-package: .PHONY: prepare-zip prepare-zip: - cp -f "$(TEMPORARY_INSTALLER_FOLDER)$(BINARIES_FOLDER)/$(CLI_FILENAME)" "$(TEMPORARY_INSTALLER_FOLDER)" - cp -rf "$(TEMPORARY_INSTALLER_FOLDER)$(FRAMEWORKS_FOLDER)/$(MACOS_FRAMEWORK_FILENAME)" "$(TEMPORARY_INSTALLER_FOLDER)" - cp -rf "$(TEMPORARY_INSTALLER_FOLDER)$(FRAMEWORKS_FOLDER)/$(IPHONESIMULATOR_FRAMEWORK_FILENAME)" "$(TEMPORARY_INSTALLER_FOLDER)" - cp -rf "$(TEMPORARY_INSTALLER_FOLDER)$(FRAMEWORKS_FOLDER)/$(APPLETVSIMULATOR_FRAMEWORK_FILENAME)" "$(TEMPORARY_INSTALLER_FOLDER)" + cp -f "$(TEMPORARY_INSTALLER_FOLDER)$(BIN_DIR)/$(CLI_FILENAME)" "$(TEMPORARY_INSTALLER_FOLDER)" cp -f "$(LICENSE_PATH)" "$(TEMPORARY_INSTALLER_FOLDER)" .PHONY: archive @@ -434,7 +420,7 @@ signed-release: signed-package signed-zip starter-pack-zip .PHONY: get-version get-version: - @echo $(VERSION_STRING) + @echo $(MKB_VERSION) .PHONY: get-zip-sha256 get-zip-sha256: diff --git a/Mockingbird.xcodeproj/project.pbxproj b/Mockingbird.xcodeproj/project.pbxproj index 003a9002..f3a0e09c 100644 --- a/Mockingbird.xcodeproj/project.pbxproj +++ b/Mockingbird.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -24,9 +24,11 @@ 281B6246251DC7220084EBED /* MockingbirdShadowedTestsHost.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 281B6226251DC5AD0084EBED /* MockingbirdShadowedTestsHost.framework */; }; 281B6286251DC7FC0084EBED /* ModuleNameShadowing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 281B61F1251DBBDE0084EBED /* ModuleNameShadowing.swift */; }; 281B62FF251DCCFC0084EBED /* MockingbirdShadowedTestsHostMocks.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 281B62FE251DCCFC0084EBED /* MockingbirdShadowedTestsHostMocks.generated.swift */; }; + 2838FD2127756314007A1CB4 /* Path+Abbreviate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2838FD2027756314007A1CB4 /* Path+Abbreviate.swift */; }; 284C3B4026AAC9DA00F076E2 /* NominalTypeDefinitionTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284C3B3F26AAC9DA00F076E2 /* NominalTypeDefinitionTemplate.swift */; }; 284C3B4326AACD5700F076E2 /* BlockTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284C3B4226AACD5700F076E2 /* BlockTemplate.swift */; }; 284C3B4826AAD05C00F076E2 /* GenericStaticMockContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284C3B4726AAD05C00F076E2 /* GenericStaticMockContext.swift */; }; + 2852643F2775882B000298B3 /* SwiftFilePath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2852643E2775882B000298B3 /* SwiftFilePath.swift */; }; 28571A7C26A6548B0063AB83 /* MKBObjectInvocationHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 28571A7A26A6548B0063AB83 /* MKBObjectInvocationHandler.h */; }; 28571A7D26A6548B0063AB83 /* MKBObjectInvocationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 28571A7B26A6548B0063AB83 /* MKBObjectInvocationHandler.m */; }; 28571A8026A65A490063AB83 /* MKBComparator.h in Headers */ = {isa = PBXBuildFile; fileRef = 28571A7E26A65A490063AB83 /* MKBComparator.h */; }; @@ -69,6 +71,8 @@ 28571ACC26A668530063AB83 /* MKBInvocationHandlerChain.h in Headers */ = {isa = PBXBuildFile; fileRef = 28571ACA26A668530063AB83 /* MKBInvocationHandlerChain.h */; }; 28571ACD26A668530063AB83 /* MKBInvocationHandlerChain.m in Sources */ = {isa = PBXBuildFile; fileRef = 28571ACB26A668530063AB83 /* MKBInvocationHandlerChain.m */; }; 28571ACF26A670E50063AB83 /* MKBComparator.m in Sources */ = {isa = PBXBuildFile; fileRef = 28571ACE26A670E50063AB83 /* MKBComparator.m */; }; + 286467DB26BFADE2005CDD67 /* DirectoryPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286467DA26BFADE2005CDD67 /* DirectoryPath.swift */; }; + 286467DD26BFB087005CDD67 /* SupportingSourcesPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286467DC26BFB087005CDD67 /* SupportingSourcesPath.swift */; }; 28719AF026B21CB100C38C2C /* MKBProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = 28719AEE26B21CB100C38C2C /* MKBProperty.h */; }; 28719AF126B21CB100C38C2C /* MKBProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = 28719AEF26B21CB100C38C2C /* MKBProperty.m */; }; 28719AF326B22D1300C38C2C /* Stubbing+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28719AF226B22D1300C38C2C /* Stubbing+ObjC.swift */; }; @@ -89,7 +93,6 @@ 287C4F6426A3D21D00A7E0D9 /* MonotonicIncreasingIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287C4F6326A3D21D00A7E0D9 /* MonotonicIncreasingIndex.swift */; }; 287C4F6626A3E00800A7E0D9 /* NonEscapingType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287C4F6526A3E00800A7E0D9 /* NonEscapingType.swift */; }; 287C4F6C26A4AAA900A7E0D9 /* InvocationRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287C4F6B26A4AAA900A7E0D9 /* InvocationRecorder.swift */; }; - 287F851F25194DE4007D135D /* SPMUtility in Frameworks */ = {isa = PBXBuildFile; productRef = 287F851E25194DE4007D135D /* SPMUtility */; }; 287F852225194E16007D135D /* SwiftSyntax in Frameworks */ = {isa = PBXBuildFile; productRef = 287F852125194E16007D135D /* SwiftSyntax */; }; 287F852525194EF5007D135D /* SourceKittenFramework in Frameworks */ = {isa = PBXBuildFile; productRef = 287F852425194EF5007D135D /* SourceKittenFramework */; }; 287F852825194F2A007D135D /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 287F852725194F2A007D135D /* ZIPFoundation */; }; @@ -98,6 +101,11 @@ 28843B2726AE710400AFB8DF /* MKBTestUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 28843B2526AE710400AFB8DF /* MKBTestUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; 28843B2826AE710400AFB8DF /* MKBTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 28843B2626AE710400AFB8DF /* MKBTestUtils.m */; }; 28843B2A26AE77E800AFB8DF /* InlinePropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28843B2926AE77E800AFB8DF /* InlinePropertyTests.swift */; }; + 28874F8B26BF12DD00097529 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 28874F8A26BF12DD00097529 /* ArgumentParser */; }; + 28874F8D26BF7AB000097529 /* Configure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28874F8C26BF7AB000097529 /* Configure.swift */; }; + 28874F8F26BF7C3C00097529 /* XcodeProjPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28874F8E26BF7C3C00097529 /* XcodeProjPath.swift */; }; + 28874F9126BF7FA400097529 /* ValidatableArgument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28874F9026BF7FA400097529 /* ValidatableArgument.swift */; }; + 28874F9326BF828800097529 /* InferableArgument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28874F9226BF828800097529 /* InferableArgument.swift */; }; 2894622626A2AF6F00044839 /* Mockingbird.h in Headers */ = {isa = PBXBuildFile; fileRef = 2894622526A2AF6F00044839 /* Mockingbird.h */; settings = {ATTRIBUTES = (Public, ); }; }; 28950E2D251C4C82008EEE29 /* ProjectDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28950E2C251C4C82008EEE29 /* ProjectDescription.swift */; }; 2896E51826A6E93A00124D02 /* StubbingContext+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2896E51726A6E93A00124D02 /* StubbingContext+ObjC.swift */; }; @@ -118,6 +126,7 @@ 2896E54126AA52A400124D02 /* KeyedDecodingContainer+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2896E54026AA52A400124D02 /* KeyedDecodingContainer+Array.swift */; }; 2896E54626AA5A3E00124D02 /* spm-project-description.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2896E54326AA59ED00124D02 /* spm-project-description.json */; }; 2896E54726AA5A3F00124D02 /* generic-project-description.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2896E54426AA5A1900124D02 /* generic-project-description.json */; }; + 289FD00226D5D8D6009786A3 /* MockingbirdBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289FD00126D5D8D6009786A3 /* MockingbirdBridge.swift */; }; 28A1F3B226AC857B002F282D /* ThunkTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A1F3B126AC857B002F282D /* ThunkTemplate.swift */; }; 28A1F3B426AC85C8002F282D /* IfStatementTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A1F3B326AC85C8002F282D /* IfStatementTemplate.swift */; }; 28A1F3B626AC9007002F282D /* SwitchStatementTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A1F3B526AC9007002F282D /* SwitchStatementTemplate.swift */; }; @@ -128,9 +137,22 @@ 28A1F3C026ADA2A8002F282D /* PartialMockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A1F3BF26ADA2A8002F282D /* PartialMockTests.swift */; }; 28A1F3C226ADC9DA002F282D /* PropertyProviders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A1F3C126ADC9DA002F282D /* PropertyProviders.swift */; }; 28A1F3C426ADD57C002F282D /* MinimalTestTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A1F3C326ADD57C002F282D /* MinimalTestTypes.swift */; }; + 28B127E526C667C600BC8B85 /* TestBundleName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B127E426C667C600BC8B85 /* TestBundleName.swift */; }; 28B4F6E226B3C9C7005C0049 /* ObjCTypeEncodings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B4F6E126B3C9C7005C0049 /* ObjCTypeEncodings.swift */; }; + 28C28E7726C0EA4E00729617 /* BinaryPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C28E7626C0EA4E00729617 /* BinaryPath.swift */; }; + 28C28E7926C0EE4D00729617 /* Path+Symlink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C28E7826C0EE4D00729617 /* Path+Symlink.swift */; }; + 28C28E7B26C0FB2700729617 /* TimeUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C28E7A26C0FB2700729617 /* TimeUnit.swift */; }; + 28C28E7D26C0FF4800729617 /* Generate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C28E7C26C0FF4800729617 /* Generate.swift */; }; + 28C28E7F26C1009900729617 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C28E7E26C1009900729617 /* Downloader.swift */; }; + 28C2EE3D2771952B003CD0D5 /* ArgumentsEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C2EE3C2771952B003CD0D5 /* ArgumentsEncoder.swift */; }; + 28C2EE3F277195B0003CD0D5 /* OptionArgumentEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C2EE3E277195B0003CD0D5 /* OptionArgumentEncoding.swift */; }; + 28C2EE41277195F5003CD0D5 /* FlagArgumentEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C2EE40277195F5003CD0D5 /* FlagArgumentEncoding.swift */; }; + 28C2EE482771A45F003CD0D5 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C2EE472771A45F003CD0D5 /* Version.swift */; }; + 28C2EE4A2771AA41003CD0D5 /* ExtendedGeneratorTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C2EE492771AA41003CD0D5 /* ExtendedGeneratorTypes.swift */; }; + 28C2EE4E27728C4F003CD0D5 /* URLArgument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C2EE4D27728C4F003CD0D5 /* URLArgument.swift */; }; 28C8E5DB26A64D6C00C68A1D /* MKBInvocationHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 28C8E5D926A64D6C00C68A1D /* MKBInvocationHandler.h */; }; 28C8E5DC26A64D6C00C68A1D /* MKBInvocationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C8E5DA26A64D6C00C68A1D /* MKBInvocationHandler.m */; }; + 28D08CD62775338100AE7C39 /* OptionGroupArgumentEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D08CD52775338100AE7C39 /* OptionGroupArgumentEncoding.swift */; }; 28DAD96E251BDD66001A0B3F /* Project.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DAD96D251BDD66001A0B3F /* Project.swift */; }; 28DDDFC126B8571D002556C7 /* DynamicCast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DDDFC026B8571D002556C7 /* DynamicCast.swift */; }; 8356225C26A94CBE005CD5C5 /* TargetDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8356225B26A94CBE005CD5C5 /* TargetDescriptionTests.swift */; }; @@ -292,17 +314,9 @@ OBJ_1178 /* Variables.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_207 /* Variables.swift */; }; OBJ_1179 /* VariadicParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_208 /* VariadicParameters.swift */; }; OBJ_1181 /* MockingbirdModuleTestsHost.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "Mockingbird::MockingbirdModuleTestsHost::Product" /* MockingbirdModuleTestsHost.framework */; }; - OBJ_807 /* ArgumentParser+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* ArgumentParser+Extensions.swift */; }; - OBJ_808 /* DownloadCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* DownloadCommand.swift */; }; - OBJ_809 /* GenerateCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* GenerateCommand.swift */; }; - OBJ_810 /* InstallCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* InstallCommand.swift */; }; - OBJ_811 /* TestbedCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* TestbedCommand.swift */; }; - OBJ_812 /* UninstallCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* UninstallCommand.swift */; }; - OBJ_813 /* VersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* VersionCommand.swift */; }; OBJ_814 /* Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* Generator.swift */; }; OBJ_815 /* Installer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* Installer.swift */; }; - OBJ_816 /* LocalizedError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* LocalizedError+Extensions.swift */; }; - OBJ_817 /* Program.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* Program.swift */; }; + OBJ_817 /* Mockingbird.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* Mockingbird.swift */; }; OBJ_818 /* LoadDylib.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* LoadDylib.swift */; }; OBJ_819 /* Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* Resource.swift */; }; OBJ_820 /* SwiftSyntaxParserDylib.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* SwiftSyntaxParserDylib.generated.swift */; }; @@ -484,9 +498,11 @@ 281B6226251DC5AD0084EBED /* MockingbirdShadowedTestsHost.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MockingbirdShadowedTestsHost.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 281B62CF251DCAB90084EBED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 281B62FE251DCCFC0084EBED /* MockingbirdShadowedTestsHostMocks.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MockingbirdShadowedTestsHostMocks.generated.swift; path = Tests/MockingbirdTests/Mocks/MockingbirdShadowedTestsHostMocks.generated.swift; sourceTree = ""; }; + 2838FD2027756314007A1CB4 /* Path+Abbreviate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Path+Abbreviate.swift"; sourceTree = ""; }; 284C3B3F26AAC9DA00F076E2 /* NominalTypeDefinitionTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NominalTypeDefinitionTemplate.swift; sourceTree = ""; }; 284C3B4226AACD5700F076E2 /* BlockTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockTemplate.swift; sourceTree = ""; }; 284C3B4726AAD05C00F076E2 /* GenericStaticMockContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericStaticMockContext.swift; sourceTree = ""; }; + 2852643E2775882B000298B3 /* SwiftFilePath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftFilePath.swift; sourceTree = ""; }; 28571A7A26A6548B0063AB83 /* MKBObjectInvocationHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MKBObjectInvocationHandler.h; sourceTree = ""; }; 28571A7B26A6548B0063AB83 /* MKBObjectInvocationHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MKBObjectInvocationHandler.m; sourceTree = ""; }; 28571A7E26A65A490063AB83 /* MKBComparator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MKBComparator.h; sourceTree = ""; }; @@ -529,6 +545,8 @@ 28571ACA26A668530063AB83 /* MKBInvocationHandlerChain.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MKBInvocationHandlerChain.h; sourceTree = ""; }; 28571ACB26A668530063AB83 /* MKBInvocationHandlerChain.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MKBInvocationHandlerChain.m; sourceTree = ""; }; 28571ACE26A670E50063AB83 /* MKBComparator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MKBComparator.m; sourceTree = ""; }; + 286467DA26BFADE2005CDD67 /* DirectoryPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryPath.swift; sourceTree = ""; }; + 286467DC26BFB087005CDD67 /* SupportingSourcesPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportingSourcesPath.swift; sourceTree = ""; }; 28719AEE26B21CB100C38C2C /* MKBProperty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MKBProperty.h; sourceTree = ""; }; 28719AEF26B21CB100C38C2C /* MKBProperty.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MKBProperty.m; sourceTree = ""; }; 28719AF226B22D1300C38C2C /* Stubbing+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stubbing+ObjC.swift"; sourceTree = ""; }; @@ -549,10 +567,13 @@ 287C4F6326A3D21D00A7E0D9 /* MonotonicIncreasingIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonotonicIncreasingIndex.swift; sourceTree = ""; }; 287C4F6526A3E00800A7E0D9 /* NonEscapingType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonEscapingType.swift; sourceTree = ""; }; 287C4F6B26A4AAA900A7E0D9 /* InvocationRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvocationRecorder.swift; sourceTree = ""; }; - 287F853D25196ACE007D135D /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Package.swift; path = Sources/Package.swift; sourceTree = ""; }; 28843B2526AE710400AFB8DF /* MKBTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MKBTestUtils.h; sourceTree = ""; }; 28843B2626AE710400AFB8DF /* MKBTestUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MKBTestUtils.m; sourceTree = ""; }; 28843B2926AE77E800AFB8DF /* InlinePropertyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinePropertyTests.swift; sourceTree = ""; }; + 28874F8C26BF7AB000097529 /* Configure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configure.swift; sourceTree = ""; }; + 28874F8E26BF7C3C00097529 /* XcodeProjPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeProjPath.swift; sourceTree = ""; }; + 28874F9026BF7FA400097529 /* ValidatableArgument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidatableArgument.swift; sourceTree = ""; }; + 28874F9226BF828800097529 /* InferableArgument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferableArgument.swift; sourceTree = ""; }; 2894622526A2AF6F00044839 /* Mockingbird.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Mockingbird.h; sourceTree = ""; }; 28950E2C251C4C82008EEE29 /* ProjectDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectDescription.swift; sourceTree = ""; }; 2896E51726A6E93A00124D02 /* StubbingContext+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StubbingContext+ObjC.swift"; sourceTree = ""; }; @@ -573,6 +594,7 @@ 2896E54026AA52A400124D02 /* KeyedDecodingContainer+Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyedDecodingContainer+Array.swift"; sourceTree = ""; }; 2896E54326AA59ED00124D02 /* spm-project-description.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "spm-project-description.json"; sourceTree = ""; }; 2896E54426AA5A1900124D02 /* generic-project-description.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "generic-project-description.json"; sourceTree = ""; }; + 289FD00126D5D8D6009786A3 /* MockingbirdBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockingbirdBridge.swift; sourceTree = ""; }; 28A1F3B126AC857B002F282D /* ThunkTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThunkTemplate.swift; sourceTree = ""; }; 28A1F3B326AC85C8002F282D /* IfStatementTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IfStatementTemplate.swift; sourceTree = ""; }; 28A1F3B526AC9007002F282D /* SwitchStatementTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchStatementTemplate.swift; sourceTree = ""; }; @@ -583,9 +605,22 @@ 28A1F3BF26ADA2A8002F282D /* PartialMockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialMockTests.swift; sourceTree = ""; }; 28A1F3C126ADC9DA002F282D /* PropertyProviders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyProviders.swift; sourceTree = ""; }; 28A1F3C326ADD57C002F282D /* MinimalTestTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimalTestTypes.swift; sourceTree = ""; }; + 28B127E426C667C600BC8B85 /* TestBundleName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestBundleName.swift; sourceTree = ""; }; 28B4F6E126B3C9C7005C0049 /* ObjCTypeEncodings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjCTypeEncodings.swift; sourceTree = ""; }; + 28C28E7626C0EA4E00729617 /* BinaryPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryPath.swift; sourceTree = ""; }; + 28C28E7826C0EE4D00729617 /* Path+Symlink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Path+Symlink.swift"; sourceTree = ""; }; + 28C28E7A26C0FB2700729617 /* TimeUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeUnit.swift; sourceTree = ""; }; + 28C28E7C26C0FF4800729617 /* Generate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generate.swift; sourceTree = ""; }; + 28C28E7E26C1009900729617 /* Downloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloader.swift; sourceTree = ""; }; + 28C2EE3C2771952B003CD0D5 /* ArgumentsEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArgumentsEncoder.swift; sourceTree = ""; }; + 28C2EE3E277195B0003CD0D5 /* OptionArgumentEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionArgumentEncoding.swift; sourceTree = ""; }; + 28C2EE40277195F5003CD0D5 /* FlagArgumentEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagArgumentEncoding.swift; sourceTree = ""; }; + 28C2EE472771A45F003CD0D5 /* Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; }; + 28C2EE492771AA41003CD0D5 /* ExtendedGeneratorTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedGeneratorTypes.swift; sourceTree = ""; }; + 28C2EE4D27728C4F003CD0D5 /* URLArgument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLArgument.swift; sourceTree = ""; }; 28C8E5D926A64D6C00C68A1D /* MKBInvocationHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MKBInvocationHandler.h; sourceTree = ""; }; 28C8E5DA26A64D6C00C68A1D /* MKBInvocationHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MKBInvocationHandler.m; sourceTree = ""; }; + 28D08CD52775338100AE7C39 /* OptionGroupArgumentEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionGroupArgumentEncoding.swift; sourceTree = ""; }; 28DAD96D251BDD66001A0B3F /* Project.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Project.swift; sourceTree = ""; }; 28DDDFC026B8571D002556C7 /* DynamicCast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicCast.swift; sourceTree = ""; }; 8356225B26A94CBE005CD5C5 /* TargetDescriptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetDescriptionTests.swift; sourceTree = ""; }; @@ -644,7 +679,6 @@ OBJ_106 /* SourceFileAuxiliaryParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceFileAuxiliaryParser.swift; sourceTree = ""; }; OBJ_108 /* CodableTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableTarget.swift; sourceTree = ""; }; OBJ_109 /* PBXTarget+Target.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PBXTarget+Target.swift"; sourceTree = ""; }; - OBJ_11 /* ArgumentParser+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ArgumentParser+Extensions.swift"; sourceTree = ""; }; OBJ_110 /* SubstitutionStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubstitutionStyle.swift; sourceTree = ""; }; OBJ_111 /* Target.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Target.swift; sourceTree = ""; }; OBJ_113 /* InferType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InferType.swift; sourceTree = ""; }; @@ -661,7 +695,6 @@ OBJ_127 /* String+ParserUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+ParserUtils.swift"; sourceTree = ""; }; OBJ_128 /* String+SHA1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+SHA1.swift"; sourceTree = ""; }; OBJ_129 /* Synchronized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Synchronized.swift; sourceTree = ""; }; - OBJ_13 /* DownloadCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadCommand.swift; sourceTree = ""; }; OBJ_130 /* Timing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timing.swift; sourceTree = ""; }; OBJ_131 /* Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; }; OBJ_133 /* ClassScopedTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassScopedTypes.swift; sourceTree = ""; }; @@ -670,7 +703,6 @@ OBJ_136 /* ObjectiveC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectiveC.swift; sourceTree = ""; }; OBJ_137 /* Typealiasing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Typealiasing.swift; sourceTree = ""; }; OBJ_139 /* Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Empty.swift; sourceTree = ""; }; - OBJ_14 /* GenerateCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateCommand.swift; sourceTree = ""; }; OBJ_141 /* MockingbirdTestsHost.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockingbirdTestsHost.h; sourceTree = ""; }; OBJ_143 /* ArgumentMatching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArgumentMatching.swift; sourceTree = ""; }; OBJ_144 /* Child.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Child.swift; sourceTree = ""; }; @@ -679,7 +711,6 @@ OBJ_147 /* ClassOnlyProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassOnlyProtocols.swift; sourceTree = ""; }; OBJ_148 /* ClassScopedTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassScopedTypes.swift; sourceTree = ""; }; OBJ_149 /* ClosureParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosureParameters.swift; sourceTree = ""; }; - OBJ_15 /* InstallCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallCommand.swift; sourceTree = ""; }; OBJ_150 /* Collections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collections.swift; sourceTree = ""; }; OBJ_151 /* CompilationDirectives.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompilationDirectives.swift; sourceTree = ""; }; OBJ_152 /* CompoundTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompoundTypes.swift; sourceTree = ""; }; @@ -690,7 +721,6 @@ OBJ_157 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; OBJ_158 /* ExternalModuleClassScopedTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalModuleClassScopedTypes.swift; sourceTree = ""; }; OBJ_159 /* ExternalModuleImplicitlyImportedTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalModuleImplicitlyImportedTypes.swift; sourceTree = ""; }; - OBJ_16 /* TestbedCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestbedCommand.swift; sourceTree = ""; }; OBJ_160 /* ExternalModuleTypealiasing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalModuleTypealiasing.swift; sourceTree = ""; }; OBJ_161 /* ExternalModuleTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalModuleTypes.swift; sourceTree = ""; }; OBJ_162 /* FakeableTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeableTypes.swift; sourceTree = ""; }; @@ -701,14 +731,12 @@ OBJ_167 /* Initializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Initializers.swift; sourceTree = ""; }; OBJ_168 /* InoutParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InoutParameters.swift; sourceTree = ""; }; OBJ_169 /* KeywordArgumentNames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeywordArgumentNames.swift; sourceTree = ""; }; - OBJ_17 /* UninstallCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UninstallCommand.swift; sourceTree = ""; }; OBJ_171 /* !EscapedNegationPrefixIgnoredSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "!EscapedNegationPrefixIgnoredSource.swift"; sourceTree = ""; }; OBJ_172 /* #EscapedCommentPrefixIgnoredSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "#EscapedCommentPrefixIgnoredSource.swift"; sourceTree = ""; }; OBJ_173 /* CascadingExcludedSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CascadingExcludedSource.swift; sourceTree = ""; }; OBJ_174 /* CascadingIncludedSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CascadingIncludedSource.swift; sourceTree = ""; }; OBJ_175 /* RelativeTopLevelFileIgnoredSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelativeTopLevelFileIgnoredSource.swift; sourceTree = ""; }; OBJ_178 /* DirectoryIgnoredSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryIgnoredSource.swift; sourceTree = ""; }; - OBJ_18 /* VersionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionCommand.swift; sourceTree = ""; }; OBJ_180 /* EnclosingDirectoryOverriddenIgnoredSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnclosingDirectoryOverriddenIgnoredSource.swift; sourceTree = ""; }; OBJ_181 /* OverriddenIncludedSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverriddenIncludedSource.swift; sourceTree = ""; }; OBJ_182 /* WildcardFileIgnoredSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WildcardFileIgnoredSource.swift; sourceTree = ""; }; @@ -735,7 +763,6 @@ OBJ_206 /* UndefinedArgumentLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UndefinedArgumentLabels.swift; sourceTree = ""; }; OBJ_207 /* Variables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Variables.swift; sourceTree = ""; }; OBJ_208 /* VariadicParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariadicParameters.swift; sourceTree = ""; }; - OBJ_21 /* LocalizedError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalizedError+Extensions.swift"; sourceTree = ""; }; OBJ_212 /* ChildMockMockableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChildMockMockableTests.swift; sourceTree = ""; }; OBJ_213 /* ChildMockStubbableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChildMockStubbableTests.swift; sourceTree = ""; }; OBJ_214 /* ChildProtocolMockMockableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChildProtocolMockMockableTests.swift; sourceTree = ""; }; @@ -744,7 +771,7 @@ OBJ_217 /* ClassScopedTypesStubbableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassScopedTypesStubbableTests.swift; sourceTree = ""; }; OBJ_218 /* ClosureParametersMockableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosureParametersMockableTests.swift; sourceTree = ""; }; OBJ_219 /* ClosureParametersStubbableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosureParametersStubbableTests.swift; sourceTree = ""; }; - OBJ_22 /* Program.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Program.swift; sourceTree = ""; }; + OBJ_22 /* Mockingbird.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mockingbird.swift; sourceTree = ""; }; OBJ_220 /* DefaultArgumentValuesMockableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultArgumentValuesMockableTests.swift; sourceTree = ""; }; OBJ_221 /* DefaultArgumentValuesStubbableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultArgumentValuesStubbableTests.swift; sourceTree = ""; }; OBJ_222 /* EmptyTypesMockableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTypesMockableTests.swift; sourceTree = ""; }; @@ -909,7 +936,7 @@ buildActionMask = 0; files = ( 287F85322519659B007D135D /* MockingbirdGenerator.framework in Frameworks */, - 287F851F25194DE4007D135D /* SPMUtility in Frameworks */, + 28874F8B26BF12DD00097529 /* ArgumentParser in Frameworks */, 287F852825194F2A007D135D /* ZIPFoundation in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -978,21 +1005,15 @@ 2894622026A28F3C00044839 /* Objective-C */ = { isa = PBXGroup; children = ( - 2894622526A2AF6F00044839 /* Mockingbird.h */, 287C4F3926A3547000A7E0D9 /* MKBClassMock.h */, 287C4F3A26A3547000A7E0D9 /* MKBClassMock.m */, 287C4F5526A3710600A7E0D9 /* MKBConcreteMock.h */, 287C4F5626A3710600A7E0D9 /* MKBConcreteMock.m */, - 287C4F4126A3688100A7E0D9 /* MKBMocking.h */, - 287C4F4226A3688100A7E0D9 /* MKBMocking.m */, - 287C4F3D26A3547F00A7E0D9 /* MKBProtocolMock.h */, - 287C4F3E26A3547F00A7E0D9 /* MKBProtocolMock.m */, - 28843B2526AE710400AFB8DF /* MKBTestUtils.h */, - 28843B2626AE710400AFB8DF /* MKBTestUtils.m */, - 287C4F4D26A36DC000A7E0D9 /* MKBTypeFacade.h */, - 287C4F4E26A36DC000A7E0D9 /* MKBTypeFacade.m */, 28719AEE26B21CB100C38C2C /* MKBProperty.h */, 28719AEF26B21CB100C38C2C /* MKBProperty.m */, + 287C4F3D26A3547F00A7E0D9 /* MKBProtocolMock.h */, + 287C4F3E26A3547F00A7E0D9 /* MKBProtocolMock.m */, + 289FCFF826D5C4E0009786A3 /* Bridge */, 28C8E5D826A64D1600C68A1D /* InvocationHandlers */, ); path = "Objective-C"; @@ -1007,6 +1028,76 @@ path = Resources; sourceTree = ""; }; + 289FCFF826D5C4E0009786A3 /* Bridge */ = { + isa = PBXGroup; + children = ( + 289FCFFA26D5CD2A009786A3 /* include */, + 289FCFFB26D5CD43009786A3 /* sources */, + ); + path = Bridge; + sourceTree = ""; + }; + 289FCFFA26D5CD2A009786A3 /* include */ = { + isa = PBXGroup; + children = ( + 2894622526A2AF6F00044839 /* Mockingbird.h */, + 287C4F4126A3688100A7E0D9 /* MKBMocking.h */, + 28843B2526AE710400AFB8DF /* MKBTestUtils.h */, + 287C4F4D26A36DC000A7E0D9 /* MKBTypeFacade.h */, + ); + path = include; + sourceTree = ""; + }; + 289FCFFB26D5CD43009786A3 /* sources */ = { + isa = PBXGroup; + children = ( + 287C4F4226A3688100A7E0D9 /* MKBMocking.m */, + 28843B2626AE710400AFB8DF /* MKBTestUtils.m */, + 287C4F4E26A36DC000A7E0D9 /* MKBTypeFacade.m */, + ); + path = sources; + sourceTree = ""; + }; + 28C2EE4227719624003CD0D5 /* Encoding */ = { + isa = PBXGroup; + children = ( + 28C2EE3C2771952B003CD0D5 /* ArgumentsEncoder.swift */, + 28C2EE40277195F5003CD0D5 /* FlagArgumentEncoding.swift */, + 28C2EE3E277195B0003CD0D5 /* OptionArgumentEncoding.swift */, + 28D08CD52775338100AE7C39 /* OptionGroupArgumentEncoding.swift */, + ); + path = Encoding; + sourceTree = ""; + }; + 28C2EE44277196CB003CD0D5 /* Arguments */ = { + isa = PBXGroup; + children = ( + 28C28E7626C0EA4E00729617 /* BinaryPath.swift */, + 286467DA26BFADE2005CDD67 /* DirectoryPath.swift */, + 28C2EE492771AA41003CD0D5 /* ExtendedGeneratorTypes.swift */, + 28874F9226BF828800097529 /* InferableArgument.swift */, + 286467DC26BFB087005CDD67 /* SupportingSourcesPath.swift */, + 2852643E2775882B000298B3 /* SwiftFilePath.swift */, + 28B127E426C667C600BC8B85 /* TestBundleName.swift */, + 28C2EE4D27728C4F003CD0D5 /* URLArgument.swift */, + 28874F9026BF7FA400097529 /* ValidatableArgument.swift */, + 28874F8E26BF7C3C00097529 /* XcodeProjPath.swift */, + ); + path = Arguments; + sourceTree = ""; + }; + 28C2EE4527719737003CD0D5 /* Handlers */ = { + isa = PBXGroup; + children = ( + 28C28E7E26C1009900729617 /* Downloader.swift */, + OBJ_19 /* Generator.swift */, + D3B3D4C7248D652500FEEDA0 /* Generator+Pipeline.swift */, + D3DC37942492140D001E02A5 /* Generator+PruningPipeline.swift */, + OBJ_20 /* Installer.swift */, + ); + path = Handlers; + sourceTree = ""; + }; 28C8E5D826A64D1600C68A1D /* InvocationHandlers */ = { isa = PBXGroup; children = ( @@ -1069,13 +1160,15 @@ name = "Generated Mocks"; sourceTree = ""; }; - D36A3DC924922D2D007964DC /* Extensions */ = { + D36A3DC924922D2D007964DC /* Utils */ = { isa = PBXGroup; children = ( - OBJ_21 /* LocalizedError+Extensions.swift */, D36A3DCA24922D6B007964DC /* Encodable+SHA1.swift */, + 2838FD2027756314007A1CB4 /* Path+Abbreviate.swift */, + 28C28E7826C0EE4D00729617 /* Path+Symlink.swift */, + 28C28E7A26C0FB2700729617 /* TimeUnit.swift */, ); - path = Extensions; + path = Utils; sourceTree = ""; }; D372A58F245586DD0000E80A /* Xcode Configs */ = { @@ -1105,13 +1198,10 @@ OBJ_10 /* Interface */ = { isa = PBXGroup; children = ( - OBJ_11 /* ArgumentParser+Extensions.swift */, + 28C2EE44277196CB003CD0D5 /* Arguments */, OBJ_12 /* Commands */, - OBJ_19 /* Generator.swift */, - D3B3D4C7248D652500FEEDA0 /* Generator+Pipeline.swift */, - D3DC37942492140D001E02A5 /* Generator+PruningPipeline.swift */, - OBJ_20 /* Installer.swift */, - OBJ_22 /* Program.swift */, + 28C2EE4527719737003CD0D5 /* Handlers */, + OBJ_22 /* Mockingbird.swift */, ); path = Interface; sourceTree = ""; @@ -1170,12 +1260,10 @@ OBJ_12 /* Commands */ = { isa = PBXGroup; children = ( - OBJ_13 /* DownloadCommand.swift */, - OBJ_14 /* GenerateCommand.swift */, - OBJ_15 /* InstallCommand.swift */, - OBJ_16 /* TestbedCommand.swift */, - OBJ_17 /* UninstallCommand.swift */, - OBJ_18 /* VersionCommand.swift */, + 28874F8C26BF7AB000097529 /* Configure.swift */, + 28C28E7C26C0FF4800729617 /* Generate.swift */, + 28C2EE472771A45F003CD0D5 /* Version.swift */, + 28C2EE4227719624003CD0D5 /* Encoding */, ); path = Commands; sourceTree = ""; @@ -1553,6 +1641,7 @@ OBJ_49 /* Array+Extensions.swift */, D3B3D4C1248C9D1600FEEDA0 /* CwlDemangle.swift */, 28DDDFC026B8571D002556C7 /* DynamicCast.swift */, + 289FD00126D5D8D6009786A3 /* MockingbirdBridge.swift */, 28B4F6E126B3C9C7005C0049 /* ObjCTypeEncodings.swift */, D3E6F67B24844C5B000D1971 /* SourceLocation.swift */, D3B3D4C5248CC15E00FEEDA0 /* StackTrace.swift */, @@ -1656,7 +1745,6 @@ OBJ_7 /* Sources */ = { isa = PBXGroup; children = ( - 287F853D25196ACE007D135D /* Package.swift */, OBJ_8 /* MockingbirdCli */, OBJ_29 /* MockingbirdFramework */, OBJ_62 /* MockingbirdGenerator */, @@ -1700,7 +1788,7 @@ OBJ_23 /* Launcher */, OBJ_26 /* Libraries */, D3D647BB24819CCD007B5B49 /* Scripts */, - D36A3DC924922D2D007964DC /* Extensions */, + D36A3DC924922D2D007964DC /* Utils */, OBJ_28 /* main.swift */, ); name = MockingbirdCli; @@ -1832,8 +1920,8 @@ ); name = MockingbirdCli; packageProductDependencies = ( - 287F851E25194DE4007D135D /* SPMUtility */, 287F852725194F2A007D135D /* ZIPFoundation */, + 28874F8A26BF12DD00097529 /* ArgumentParser */, ); productName = MockingbirdCli; productReference = "Mockingbird::MockingbirdCli::Product" /* MockingbirdCli */; @@ -1913,7 +2001,6 @@ isa = PBXNativeTarget; buildConfigurationList = OBJ_1015 /* Build configuration list for PBXNativeTarget "MockingbirdTests" */; buildPhases = ( - E0C37AF4D09E1A81AA94E903 /* Clean Mockingbird Mocks */, 02321D380F2F1A659EC139B7 /* Generate Mockingbird Mocks */, OBJ_1018 /* Sources */, OBJ_1083 /* Frameworks */, @@ -1960,7 +2047,7 @@ isa = PBXProject; attributes = { LastSwiftMigration = 9999; - LastUpgradeCheck = 1200; + LastUpgradeCheck = 1250; TargetAttributes = { 287F85372519698B007D135D = { CreatedOnToolsVersion = 12.0; @@ -1983,11 +2070,11 @@ ); mainGroup = OBJ_5; packageReferences = ( - 287F851D25194DE4007D135D /* XCRemoteSwiftPackageReference "swift-package-manager" */, 287F852025194E16007D135D /* XCRemoteSwiftPackageReference "swift-syntax" */, 287F852325194EF5007D135D /* XCRemoteSwiftPackageReference "SourceKitten" */, 287F852625194F2A007D135D /* XCRemoteSwiftPackageReference "ZIPFoundation" */, 287F852925194FEA007D135D /* XCRemoteSwiftPackageReference "XcodeProj" */, + 28874F8926BF12DD00097529 /* XCRemoteSwiftPackageReference "swift-argument-parser" */, ); productRefGroup = OBJ_647 /* Products */; projectDirPath = ""; @@ -2009,11 +2096,11 @@ /* Begin PBXShellScriptBuildPhase section */ 02321D380F2F1A659EC139B7 /* Generate Mockingbird Mocks */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "/tmp/Mockingbird-16BC195B-65B7-4763-B2C9-AEA49F30A43A", ); name = "Generate Mockingbird Mocks"; outputPaths = ( @@ -2022,10 +2109,11 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\n\n# Ensure mocks are generated prior to running Compile Sources\nrm -f '/tmp/Mockingbird-16BC195B-65B7-4763-B2C9-AEA49F30A43A'\n\n${BUILT_PRODUCTS_DIR}/MockingbirdCli generate \\\n --targets 'MockingbirdTestsHost' 'MockingbirdShadowedTestsHost' \\\n --outputs \"${SRCROOT}/Tests/MockingbirdTests/Mocks/MockingbirdTestsHostMocks.generated.swift\" \"${SRCROOT}/Tests/MockingbirdTests/Mocks/MockingbirdShadowedTestsHostMocks.generated.swift\" \\\n --support \"${SRCROOT}/Sources/MockingbirdSupport\" \\\n --diagnostics all \\\n --disable-cache \\\n --prune stub \\\n --verbose\n"; + shellScript = "set -e\n\n${BUILT_PRODUCTS_DIR}/MockingbirdCli generate \\\n --targets 'MockingbirdTestsHost' 'MockingbirdShadowedTestsHost' \\\n --outputs \"${SRCROOT}/Tests/MockingbirdTests/Mocks/MockingbirdTestsHostMocks.generated.swift\" \"${SRCROOT}/Tests/MockingbirdTests/Mocks/MockingbirdShadowedTestsHostMocks.generated.swift\" \\\n --support \"${SRCROOT}/Sources/MockingbirdSupport\" \\\n --diagnostics all \\\n --disable-cache \\\n --prune stub \\\n --verbose\n"; }; D372A59C2455878B0000E80A /* Generate Embedded Dylibs */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -2042,22 +2130,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Sources/MockingbirdCli/Scripts/generate-resource-file.sh\" \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\" 'swiftSyntaxParserDylib'\n"; - }; - E0C37AF4D09E1A81AA94E903 /* Clean Mockingbird Mocks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Clean Mockingbird Mocks"; - outputPaths = ( - "/tmp/Mockingbird-16BC195B-65B7-4763-B2C9-AEA49F30A43A", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo $RANDOM > '/tmp/Mockingbird-16BC195B-65B7-4763-B2C9-AEA49F30A43A'\n"; + shellScript = "set -eu\n\n# Prevent Xcode 13 from running this script while indexing.\nif [[ \"${ACTION}\" == \"indexbuild\" ]]; then\n exit 0\nfi\n\n\"${SRCROOT}/Sources/MockingbirdCli/Scripts/generate-resource-file.sh\" \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\" 'swiftSyntaxParserDylib'\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -2233,23 +2306,36 @@ isa = PBXSourcesBuildPhase; buildActionMask = 0; files = ( - OBJ_807 /* ArgumentParser+Extensions.swift in Sources */, - OBJ_808 /* DownloadCommand.swift in Sources */, + 28C28E7F26C1009900729617 /* Downloader.swift in Sources */, + 28C2EE482771A45F003CD0D5 /* Version.swift in Sources */, + 28D08CD62775338100AE7C39 /* OptionGroupArgumentEncoding.swift in Sources */, + 286467DB26BFADE2005CDD67 /* DirectoryPath.swift in Sources */, + 28C2EE41277195F5003CD0D5 /* FlagArgumentEncoding.swift in Sources */, D36A3DCB24922D6B007964DC /* Encodable+SHA1.swift in Sources */, - OBJ_809 /* GenerateCommand.swift in Sources */, - OBJ_810 /* InstallCommand.swift in Sources */, - OBJ_811 /* TestbedCommand.swift in Sources */, - OBJ_812 /* UninstallCommand.swift in Sources */, - OBJ_813 /* VersionCommand.swift in Sources */, + 28C28E7D26C0FF4800729617 /* Generate.swift in Sources */, D3DC37952492140D001E02A5 /* Generator+PruningPipeline.swift in Sources */, + 2852643F2775882B000298B3 /* SwiftFilePath.swift in Sources */, + 28C28E7B26C0FB2700729617 /* TimeUnit.swift in Sources */, + 28C2EE4A2771AA41003CD0D5 /* ExtendedGeneratorTypes.swift in Sources */, OBJ_814 /* Generator.swift in Sources */, OBJ_815 /* Installer.swift in Sources */, - OBJ_816 /* LocalizedError+Extensions.swift in Sources */, - OBJ_817 /* Program.swift in Sources */, + 28874F8F26BF7C3C00097529 /* XcodeProjPath.swift in Sources */, + 28C28E7926C0EE4D00729617 /* Path+Symlink.swift in Sources */, + 28C28E7726C0EA4E00729617 /* BinaryPath.swift in Sources */, + OBJ_817 /* Mockingbird.swift in Sources */, OBJ_818 /* LoadDylib.swift in Sources */, OBJ_819 /* Resource.swift in Sources */, OBJ_820 /* SwiftSyntaxParserDylib.generated.swift in Sources */, + 286467DD26BFB087005CDD67 /* SupportingSourcesPath.swift in Sources */, + 28C2EE3D2771952B003CD0D5 /* ArgumentsEncoder.swift in Sources */, + 28B127E526C667C600BC8B85 /* TestBundleName.swift in Sources */, + 28C2EE4E27728C4F003CD0D5 /* URLArgument.swift in Sources */, + 28874F9326BF828800097529 /* InferableArgument.swift in Sources */, + 28874F8D26BF7AB000097529 /* Configure.swift in Sources */, OBJ_821 /* main.swift in Sources */, + 28874F9126BF7FA400097529 /* ValidatableArgument.swift in Sources */, + 28C2EE3F277195B0003CD0D5 /* OptionArgumentEncoding.swift in Sources */, + 2838FD2127756314007A1CB4 /* Path+Abbreviate.swift in Sources */, D3B3D4C8248D652500FEEDA0 /* Generator+Pipeline.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2304,6 +2390,7 @@ OBJ_883 /* TestKiller.swift in Sources */, 28A1F3C226ADC9DA002F282D /* PropertyProviders.swift in Sources */, OBJ_884 /* ValueProvider+Collections.swift in Sources */, + 289FD00226D5D8D6009786A3 /* MockingbirdBridge.swift in Sources */, 28DDDFC126B8571D002556C7 /* DynamicCast.swift in Sources */, OBJ_885 /* ValueProvider+Foundation.swift in Sources */, OBJ_886 /* ValueProvider+Tuples.swift in Sources */, @@ -2496,6 +2583,280 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 28106AF727716704003D9F6D /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "SWIFT_PACKAGE=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + USE_HEADERMAP = NO; + }; + name = Profile; + }; + 28106AF827716704003D9F6D /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D372A597245586DD0000E80A /* MockingbirdCli-Profile.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + HEADER_SEARCH_PATHS = "$(inherited)"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", + "@executable_path", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + OTHER_CFLAGS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_FORCE_DYNAMIC_LINK_STDLIB = YES; + SWIFT_FORCE_STATIC_LINK_STDLIB = NO; + SWIFT_VERSION = 5.0; + TARGET_NAME = MockingbirdCli; + TVOS_DEPLOYMENT_TARGET = 12.0; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Profile; + }; + 28106AF927716704003D9F6D /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D372A592245586DD0000E80A /* MockingbirdFramework.xcconfig */; + buildSettings = { + }; + name = Profile; + }; + 28106AFA27716704003D9F6D /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D372A593245586DD0000E80A /* MockingbirdGenerator-Profile.xcconfig */; + buildSettings = { + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + HEADER_SEARCH_PATHS = "$(inherited)"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + OTHER_CFLAGS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_VERSION = 5.0; + TARGET_NAME = MockingbirdGenerator; + TVOS_DEPLOYMENT_TARGET = 12.0; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Profile; + }; + 28106AFB27716704003D9F6D /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D372A590245586DD0000E80A /* MockingbirdTests.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + HEADER_SEARCH_PATHS = "$(inherited)"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@loader_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + OTHER_CFLAGS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -Xfrontend -debug-time-function-bodies"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_VERSION = 5.0; + TARGET_NAME = MockingbirdTests; + TVOS_DEPLOYMENT_TARGET = 12.0; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Profile; + }; + 28106AFC27716704003D9F6D /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D372A595245586DD0000E80A /* MockingbirdTestsHost.xcconfig */; + buildSettings = { + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + HEADER_SEARCH_PATHS = "$(inherited)"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + OTHER_CFLAGS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_VERSION = 5.0; + TARGET_NAME = MockingbirdTestsHost; + TVOS_DEPLOYMENT_TARGET = 12.0; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Profile; + }; + 28106AFD27716704003D9F6D /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D372A591245586DD0000E80A /* MockingbirdModuleTestsHost.xcconfig */; + buildSettings = { + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + HEADER_SEARCH_PATHS = "$(inherited)"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + OTHER_CFLAGS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_VERSION = 5.0; + TARGET_NAME = MockingbirdModuleTestsHost; + TVOS_DEPLOYMENT_TARGET = 12.0; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Profile; + }; + 28106AFE27716704003D9F6D /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D372A595245586DD0000E80A /* MockingbirdTestsHost.xcconfig */; + buildSettings = { + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + HEADER_SEARCH_PATHS = "$(inherited)"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + OTHER_CFLAGS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_VERSION = 5.0; + TARGET_NAME = MockingbirdShadowedTestsHost; + TVOS_DEPLOYMENT_TARGET = 12.0; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Profile; + }; + 28106AFF27716704003D9F6D /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D372A595245586DD0000E80A /* MockingbirdTestsHost.xcconfig */; + buildSettings = { + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + HEADER_SEARCH_PATHS = "$(inherited)"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + OTHER_CFLAGS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_VERSION = 5.0; + TARGET_NAME = MockingbirdPerformanceTestsHost; + TVOS_DEPLOYMENT_TARGET = 12.0; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Profile; + }; + 28106B0027716704003D9F6D /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; 281B6224251DC5AD0084EBED /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = D372A595245586DD0000E80A /* MockingbirdTestsHost.xcconfig */; @@ -2886,7 +3247,7 @@ }; OBJ_805 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D372A597245586DD0000E80A /* MockingbirdCli-Profile.xcconfig */; + baseConfigurationReference = D372A596245586DD0000E80A /* MockingbirdCli.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = "-"; FRAMEWORK_SEARCH_PATHS = ( @@ -2960,7 +3321,7 @@ }; OBJ_903 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D372A593245586DD0000E80A /* MockingbirdGenerator-Profile.xcconfig */; + baseConfigurationReference = D372A594245586DD0000E80A /* MockingbirdGenerator.xcconfig */; buildSettings = { ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -3056,6 +3417,7 @@ buildConfigurations = ( 281B6224251DC5AD0084EBED /* Debug */, 281B6225251DC5AD0084EBED /* Release */, + 28106AFE27716704003D9F6D /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3065,6 +3427,7 @@ buildConfigurations = ( 287F85382519698B007D135D /* Debug */, 287F85392519698B007D135D /* Release */, + 28106B0027716704003D9F6D /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3074,6 +3437,7 @@ buildConfigurations = ( OBJ_1010 /* Debug */, OBJ_1011 /* Release */, + 28106AFF27716704003D9F6D /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3083,6 +3447,7 @@ buildConfigurations = ( OBJ_1016 /* Debug */, OBJ_1017 /* Release */, + 28106AFB27716704003D9F6D /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3092,6 +3457,7 @@ buildConfigurations = ( OBJ_1118 /* Debug */, OBJ_1119 /* Release */, + 28106AFC27716704003D9F6D /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3101,6 +3467,7 @@ buildConfigurations = ( OBJ_3 /* Debug */, OBJ_4 /* Release */, + 28106AF727716704003D9F6D /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3110,6 +3477,7 @@ buildConfigurations = ( OBJ_804 /* Debug */, OBJ_805 /* Release */, + 28106AF827716704003D9F6D /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3119,6 +3487,7 @@ buildConfigurations = ( OBJ_871 /* Debug */, OBJ_872 /* Release */, + 28106AF927716704003D9F6D /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3128,6 +3497,7 @@ buildConfigurations = ( OBJ_902 /* Debug */, OBJ_903 /* Release */, + 28106AFA27716704003D9F6D /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3137,6 +3507,7 @@ buildConfigurations = ( OBJ_987 /* Debug */, OBJ_988 /* Release */, + 28106AFD27716704003D9F6D /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3144,20 +3515,12 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 287F851D25194DE4007D135D /* XCRemoteSwiftPackageReference "swift-package-manager" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/apple/swift-package-manager.git"; - requirement = { - kind = exactVersion; - version = 0.4.0; - }; - }; 287F852025194E16007D135D /* XCRemoteSwiftPackageReference "swift-syntax" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/apple/swift-syntax.git"; requirement = { kind = exactVersion; - version = 0.50400.0; + version = 0.50500.0; }; }; 287F852325194EF5007D135D /* XCRemoteSwiftPackageReference "SourceKitten" */ = { @@ -3181,17 +3544,20 @@ repositoryURL = "https://github.com/tuist/XcodeProj.git"; requirement = { kind = exactVersion; - version = 7.14.0; + version = 8.7.1; + }; + }; + 28874F8926BF12DD00097529 /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-argument-parser.git"; + requirement = { + kind = exactVersion; + version = 0.4.4; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 287F851E25194DE4007D135D /* SPMUtility */ = { - isa = XCSwiftPackageProductDependency; - package = 287F851D25194DE4007D135D /* XCRemoteSwiftPackageReference "swift-package-manager" */; - productName = SPMUtility; - }; 287F852125194E16007D135D /* SwiftSyntax */ = { isa = XCSwiftPackageProductDependency; package = 287F852025194E16007D135D /* XCRemoteSwiftPackageReference "swift-syntax" */; @@ -3212,6 +3578,11 @@ package = 287F852925194FEA007D135D /* XCRemoteSwiftPackageReference "XcodeProj" */; productName = XcodeProj; }; + 28874F8A26BF12DD00097529 /* ArgumentParser */ = { + isa = XCSwiftPackageProductDependency; + package = 28874F8926BF12DD00097529 /* XCRemoteSwiftPackageReference "swift-argument-parser" */; + productName = ArgumentParser; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = OBJ_1 /* Project object */; diff --git a/Mockingbird.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mockingbird.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f2f62f17..7e3e9d01 100644 --- a/Mockingbird.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mockingbird.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -3,11 +3,11 @@ "pins": [ { "package": "AEXML", - "repositoryURL": "https://github.com/tadija/AEXML", + "repositoryURL": "https://github.com/tadija/AEXML.git", "state": { "branch": null, - "revision": "e4d517844dd03dac557e35d77a8e9ab438de91a6", - "version": "4.4.0" + "revision": "38f7d00b23ecd891e1ee656fa6aeebd6ba04ecc3", + "version": "4.6.1" } }, { @@ -30,11 +30,11 @@ }, { "package": "PathKit", - "repositoryURL": "https://github.com/kylef/PathKit", + "repositoryURL": "https://github.com/kylef/PathKit.git", "state": { "branch": null, - "revision": "73f8e9dca9b7a3078cb79128217dc8f2e585a511", - "version": "1.0.0" + "revision": "3bfd2737b700b9a36565a8c94f4ad2b050a5e574", + "version": "1.0.1" } }, { @@ -60,26 +60,17 @@ "repositoryURL": "https://github.com/kylef/Spectre.git", "state": { "branch": null, - "revision": "f717bbce0e19f0129fc001b2b6bed43b70fd8b87", - "version": "0.9.1" + "revision": "26cc5e9ae0947092c7139ef7ba612e34646086c7", + "version": "0.10.1" } }, { - "package": "llbuild", - "repositoryURL": "https://github.com/apple/swift-llbuild.git", + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser.git", "state": { "branch": null, - "revision": "f1c9ad9a253cdf1aa89a7f5c99c30b4513b06ddb", - "version": "0.1.1" - } - }, - { - "package": "SwiftPM", - "repositoryURL": "https://github.com/apple/swift-package-manager.git", - "state": { - "branch": null, - "revision": "8656a25cb906c1896339f950ac960ee1b4fe8034", - "version": "0.4.0" + "revision": "83b23d940471b313427da226196661856f6ba3e0", + "version": "0.4.4" } }, { @@ -87,8 +78,8 @@ "repositoryURL": "https://github.com/apple/swift-syntax.git", "state": { "branch": null, - "revision": "2fff9fc25cdc059379b6bd309377cfab45d8520c", - "version": "0.50400.0" + "revision": "75e60475d9d8fd5bbc16a12e0eaa2cb01b0c322e", + "version": "0.50500.0" } }, { @@ -96,8 +87,8 @@ "repositoryURL": "https://github.com/drmohundro/SWXMLHash.git", "state": { "branch": null, - "revision": "a4931e5c3bafbedeb1601d3bb76bbe835c6d475a", - "version": "5.0.1" + "revision": "9183170d20857753d4f331b0ca63f73c60764bf3", + "version": "5.0.2" } }, { @@ -105,17 +96,8 @@ "repositoryURL": "https://github.com/tuist/XcodeProj.git", "state": { "branch": null, - "revision": "81bb2bb333eafa68f8ecd8187a4bb56d51e78e97", - "version": "7.14.0" - } - }, - { - "package": "XcodeProjCExt", - "repositoryURL": "https://github.com/tuist/XcodeProjCExt", - "state": { - "branch": null, - "revision": "21a510c225ff2bc83d5920a21d902af4b1e7e218", - "version": "0.1.0" + "revision": "c75c3acc25460195cfd203a04dde165395bf00e0", + "version": "8.7.1" } }, { @@ -123,8 +105,8 @@ "repositoryURL": "https://github.com/jpsim/Yams.git", "state": { "branch": null, - "revision": "88caa2e6fffdbef2e91c2022d038576062042907", - "version": "4.0.0" + "revision": "9ff1cc9327586db4e0c8f46f064b6a82ec1566fa", + "version": "4.0.6" } }, { diff --git a/Mockingbird.xcodeproj/xcshareddata/xcschemes/MockingbirdFramework.xcscheme b/Mockingbird.xcodeproj/xcshareddata/xcschemes/MockingbirdFramework.xcscheme index f05f8fe0..874cf658 100644 --- a/Mockingbird.xcodeproj/xcshareddata/xcschemes/MockingbirdFramework.xcscheme +++ b/Mockingbird.xcodeproj/xcshareddata/xcschemes/MockingbirdFramework.xcscheme @@ -1,6 +1,6 @@

- Package managers + Package managers License - Slack channel + Slack channel

Mockingbird lets you mock, stub, and verify objects written in either Swift or Objective-C. The syntax takes inspiration from (OC)Mockito but was designed to be “Swifty” in terms of type safety and expressiveness. @@ -35,7 +35,7 @@ See a detailed [feature comparison table](https://github.com/birdrides/mockingbi ### Who Uses Mockingbird? -Mockingbird powers thousands of tests at companies including [Facebook](https://facebook.com), [Amazon](https://amazon.com), [Twilio](https://twilio.com), and [Bird](https://bird.co). Using Mockingbird to improve your testing workflow? Consider dropping us a line on the [#mockingbird Slack channel](https://slofile.com/slack/birdopensource). +Mockingbird powers thousands of tests at companies including [Meta](https://meta.com), [Amazon](https://amazon.com), [Twilio](https://twilio.com), and [Bird](https://bird.co). Using Mockingbird to improve your testing workflow? Consider dropping us a line on the [#mockingbird Slack channel](https://join.slack.com/t/birdopensource/shared_invite/zt-wogxij50-3ZM7F8ZxFXvPkE0j8xTtmw). ### An Example @@ -69,7 +69,7 @@ Person().release(bird) verify(bird.fly()).wasCalled() ``` -## Installation +## Quick Start Select your preferred dependency manager below to get started. @@ -90,10 +90,12 @@ In your project directory, initialize the pod. $ pod install ``` -Finally, configure a test target to generate mocks for each listed source module. This adds a build phase to the test target which calls [`mockingbird generate`](#generate). For advanced usages, modify the installed build phase or [set up targets manually](https://github.com/birdrides/mockingbird/wiki/Manual-Setup). +Finally, configure the test target to generate mocks for specific modules or libraries. + +> The configurator adds a build phase to the test target which automatically calls [`mockingbird generate`](#generate). You can pass additional arguments to the generator after the double-dash (`--`) or [manually set up targets](https://github.com/birdrides/mockingbird/wiki/Manual-Setup). ```console -$ Pods/MockingbirdFramework/mockingbird install --target MyAppTests --sources MyApp MyLibrary1 MyLibrary2 +$ Pods/MockingbirdFramework/mockingbird configure MyAppTests -- --targets MyApp MyLibrary1 MyLibrary2 ``` Optional but recommended: @@ -103,7 +105,7 @@ Optional but recommended: Have questions or issues? -- [Join the Slack channel](https://slofile.com/slack/birdopensource) +- [Join the Slack channel](https://join.slack.com/t/birdopensource/shared_invite/zt-wogxij50-3ZM7F8ZxFXvPkE0j8xTtmw) - [Search the troubleshooting guide](https://github.com/birdrides/mockingbird/wiki/Troubleshooting) - [Check out the CocoaPods example project](/Examples/iOSMockingbirdExample-CocoaPods) @@ -123,10 +125,12 @@ In your project directory, build the framework and [link it to your test target] $ carthage update --use-xcframeworks ``` -Finally, configure a test target to generate mocks for each listed source module. This adds a build phase to the test target which calls [`mockingbird generate`](#generate). For advanced usages, modify the installed build phase or [set up targets manually](https://github.com/birdrides/mockingbird/wiki/Manual-Setup). +Finally, configure the test target to generate mocks for specific modules or libraries. + +> The configurator adds a build phase to the test target which automatically calls [`mockingbird generate`](#generate). You can pass additional arguments to the generator after the double-dash (`--`) or [manually set up targets](https://github.com/birdrides/mockingbird/wiki/Manual-Setup). ```console -$ mockingbird install --target MyAppTests --sources MyApp MyLibrary1 MyLibrary2 +$ Carthage/Checkouts/mockingbird/mockingbird configure MyAppTests -- --targets MyApp MyLibrary1 MyLibrary2 ``` Optional but recommended: @@ -136,13 +140,49 @@ Optional but recommended: Have questions or issues? -- [Join the Slack channel](https://slofile.com/slack/birdopensource) +- [Join the Slack channel](https://join.slack.com/t/birdopensource/shared_invite/zt-wogxij50-3ZM7F8ZxFXvPkE0j8xTtmw) - [Search the troubleshooting guide](https://github.com/birdrides/mockingbird/wiki/Troubleshooting) - [Check out the Carthage example project](/Examples/iOSMockingbirdExample-Carthage) -
Swift Package Manager +
Swift Package Manager - Xcode Project + +Add the framework to your project: + +1. Navigate to **File › Add Packages…** and enter `https://github.com/birdrides/mockingbird` +2. Change **Dependency Rule** to “Up to Next Minor Version” and enter `0.19.0` +3. Click **Add Package** +4. Select your test target and click **Add Package** + +In your project directory, resolve the derived data path. This can take a few moments. + +```console +$ DERIVED_DATA="$(xcodebuild -showBuildSettings | pcregrep -o1 'OBJROOT = (/.*)/Build')" +``` + +Finally, configure the test target to generate mocks for specific modules or libraries. + +> The configurator adds a build phase to the test target which automatically calls [`mockingbird generate`](#generate). You can pass additional arguments to the generator after the double-dash (`--`) or [manually set up targets](https://github.com/birdrides/mockingbird/wiki/Manual-Setup). + +```console +$ "${DERIVED_DATA}/SourcePackages/checkouts/mockingbird/mockingbird" configure MyPackageTests -- --targets MyPackage MyLibrary1 MyLibrary2 +``` + +Optional but recommended: + +- [Exclude generated files from source control](https://github.com/birdrides/mockingbird/wiki/Integration-Tips#source-control-exclusion) +- [Add supporting source files for compatibility with external dependencies](https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files) + +Have questions or issues? + +- [Join the Slack channel](https://join.slack.com/t/birdopensource/shared_invite/zt-wogxij50-3ZM7F8ZxFXvPkE0j8xTtmw) +- [Search the troubleshooting guide](https://github.com/birdrides/mockingbird/wiki/Troubleshooting) +- [Check out the Swift Package Manager example project](/Examples/iOSMockingbirdExample-SPM) + +
+ +
Swift Package Manager - Package Manifest Add Mockingbird as a package and test target dependency in your `Package.swift` manifest. @@ -158,22 +198,47 @@ let package = Package( ) ``` -In your project directory, initialize the package dependency. - -> Parsing the `DERIVED_DATA` path can take a minute. +In your project directory, initialize the package. ```console $ xcodebuild -resolvePackageDependencies -$ DERIVED_DATA="$(xcodebuild -showBuildSettings | pcregrep -o1 'OBJROOT = (/.*)/Build')" -$ REPO_PATH="${DERIVED_DATA}/SourcePackages/checkouts/mockingbird" ``` -Finally, configure a test target to generate mocks for each listed source module. This adds a build phase to the test target which calls [`mockingbird generate`](#generate). For advanced usages, modify the installed build phase or [set up targets manually](https://github.com/birdrides/mockingbird/wiki/Manual-Setup). +Next, save Bash script below in the same directory as your package manifest. Change the lines marked with `FIXME`. + +```bash +#!/bin/bash +set -eu +cd "$(dirname "$0")" +readonly derivedData="$(xcodebuild -showBuildSettings | pcregrep -o1 'OBJROOT = (/.*)/Build')" +swift package describe --type json > project.json +"${derivedData}/SourcePackages/checkouts/mockingbird/mockingbird" generate --project project.json \ + --testbundle MyPackageTests \ # FIXME: The name of your test target. + --targets MyPackage MyLibrary1 MyLibrary2 # FIXME: Specific modules or libraries that should be mocked. +``` -> Not using an Xcode project? Generate mocks from the command line by calling [`mockingbird generate`](#generate). +Ensure that the script runs and generates mock files. ```console -$ "${REPO_PATH}/mockingbird" install --target MyPackageTests --sources MyPackage MyLibrary1 MyLibrary2 +$ chmod u+x generate-mocks.sh +$ ./generate-mocks.sh +Generated file to MockingbirdMocks/MyPackageTests-MyPackage.generated.swift +Generated file to MockingbirdMocks/MyPackageTests-MyLibrary1.generated.swift +Generated file to MockingbirdMocks/MyPackageTests-MyLibrary2.generated.swift +``` + +Finally, add each generated mock file to your test target sources. + +```swift +.testTarget( + name: "MyPackageTests", + dependencies: ["Mockingbird"], + sources: [ + "Tests/MyPackageTests", + "MockingbirdMocks/MyPackageTests-MyPackage.generated.swift", + "MockingbirdMocks/MyPackageTests-MyLibrary1.generated.swift", + "MockingbirdMocks/MyPackageTests-MyLibrary2.generated.swift", + ]), ``` Optional but recommended: @@ -183,7 +248,7 @@ Optional but recommended: Have questions or issues? -- [Join the Slack channel](https://slofile.com/slack/birdopensource) +- [Join the Slack channel](https://join.slack.com/t/birdopensource/shared_invite/zt-wogxij50-3ZM7F8ZxFXvPkE0j8xTtmw) - [Search the troubleshooting guide](https://github.com/birdrides/mockingbird/wiki/Troubleshooting) - [Check out the Swift Package Manager example project](/Examples/iOSMockingbirdExample-SPM) @@ -650,7 +715,7 @@ To improve compilation times for large projects, Mockingbird only generates mock Usage is determined by statically analyzing test target sources for calls to `mock(SomeType.self)`, which may not work out of the box for projects that indirectly synthesize types such as through Objective-C based dependency injection. - **Option 1:** Explicitly reference each indirectly synthesized type in your tests, e.g. `_ = mock(SomeType.self)`. References can be placed anywhere in the test target sources, such as in the `setUp` method of a test case or in a single file. -- **Option 2:** Disable pruning entirely by setting the prune level with `--prunelevel disable`. Note that this may increase compilation times for large projects. +- **Option 2:** Disable pruning entirely by setting the prune level with `--prune disable`. Note that this may increase compilation times for large projects. ## Mockingbird CLI @@ -662,79 +727,45 @@ Generate mocks for a set of targets in a project. | Option | Default Value | Description | | --- | --- | --- | -| `--targets` | *(required)* | List of target names to generate mocks for. | -| `--project` | [`(inferred)`](#--project) | Path to an `.xcodeproj` file or a [JSON project description](https://github.com/birdrides/mockingbird/wiki/Manual-Setup#generating-mocks-for-non-xcode-projects). | +| `-t, --targets` | *(required)* | List of target names to generate mocks for. | +| `-o, --outputs` | [`(inferred)`](#--outputs) | List of mock output file paths for each target. | +| `-p, --project` | [`(inferred)`](#--project) | Path to an Xcode project or a [JSON project description](https://github.com/birdrides/mockingbird/wiki/Manual-Setup#generating-mocks-for-non-xcode-projects). | | `--srcroot` | [`(inferred)`](#--srcroot) | The directory containing your project’s source files. | -| `--outputs` | [`(inferred)`](#--outputs) | List of mock output file paths for each target. | | `--support` | [`(inferred)`](#--support) | The directory containing [supporting source files](https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files). | | `--testbundle` | [`(inferred)`](#--testbundle) | The name of the test bundle using the mocks. | | `--header` | `(none)` | Content to add at the beginning of each generated mock file. | | `--condition` | `(none)` | [Compilation condition](https://docs.swift.org/swift-book/ReferenceManual/Statements.html#ID538) to wrap all generated mocks in, e.g. `DEBUG`. | | `--diagnostics` | `(none)` | List of [diagnostic generator warnings](https://github.com/birdrides/mockingbird/wiki/Diagnostic-Warnings-and-Errors) to enable. | -| `--prune` | `stub` | The [pruning method](#thunk-pruning) to use on unreferenced types. | +| `--prune` | `omit` | The [pruning method](#thunk-pruning) to use on unreferenced types. | | Flag | Description | | --- | --- | | `--only-protocols` | Only generate mocks for protocols. | -| `--disable-module-import` | Omit `@testable import ` from generated mocks. | | `--disable-swiftlint` | Disable all SwiftLint rules in generated mocks. | | `--disable-cache` | Ignore cached mock information stored on disk. | | `--disable-relaxed-linking` | Only search explicitly imported modules. | -### Install +### Configure -Configure a test target to use mocks. +Configure a test target to generate mocks. -`mockingbird install` - -| Option | Default Value | Description | -| --- | --- | --- | -| `--target` | *(required)* | The name of a test target to configure. | -| `--sources` | *(required)* | List of target names to generate mocks for. | -| `--project` | [`(inferred)`](#--project) | Path to an `.xcodeproj` file or a [JSON project description](https://github.com/birdrides/mockingbird/wiki/Manual-Setup#generating-mocks-for-non-xcode-projects). | -| `--srcroot` | [`(inferred)`](#--srcroot) | The directory containing your project’s source files. | -| `--outputs` | [`(inferred)`](#--outputs) | List of mock output file paths for each target. | -| `--support` | [`(inferred)`](#--support) | The directory containing [supporting source files](https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files). | -| `--header` | `(none)` | Content to add at the beginning of each generated mock file. | -| `--condition` | `(none)` | [Compilation condition](https://docs.swift.org/swift-book/ReferenceManual/Statements.html#ID538) to wrap all generated mocks in, e.g. `DEBUG`. | -| `--diagnostics` | `(none)` | List of [diagnostic generator warnings](https://github.com/birdrides/mockingbird/wiki/Diagnostic-Warnings-and-Errors) to enable. | -| `--loglevel` | `(none)` | The log level to use when generating mocks, `quiet` or `verbose`. | -| `--prune` | `omit` | The [pruning method](#thunk-pruning) to use on unreferenced types. | +`mockingbird configure -- ` -| Flag | Description | +| Argument | Description | | --- | --- | -| `--preserve-existing` | Don’t overwrite previously installed configurations. | -| `--asynchronous` | Generate mocks asynchronously in the background when building. | -| `--only-protocols` | Only generate mocks for protocols. | -| `--disable-swiftlint` | Disable all SwiftLint rules in generated mocks. | -| `--disable-cache` | Ignore cached mock information stored on disk. | -| `--disable-relaxed-linking` | Only search explicitly imported modules. | - -### Uninstall - -Remove Mockingbird from a test target. - -`mockingbird uninstall` +| `test-target` | The name of a test target to configure. | +| `generator-options` | Arguments to use when running the generator. See the 'generate' command for all options. | | Option | Default Value | Description | | --- | --- | --- | -| `--targets` | *(required)* | List of target names to uninstall the Run Script Phase. | -| `--project` | [`(inferred)`](#--project) | Your project’s `.xcodeproj` file. | -| `--srcroot` | [`(inferred)`](#--srcroot) | The directory containing your project’s source files. | - -### Download - -Download and unpack a compatible asset bundle. Bundles will never overwrite existing files on disk. +| `-p, --project` | [`(inferred)`](#--project) | Path to an Xcode project. | +| `--srcproject` | [`(inferred)`](#--project) | Path to the Xcode project with source modules, if separate from tests. | +| `--generator` | [`(inferred)`](#--generator) | Path to the Mockingbird generator executable. | +| `--url` | [`(inferred)`](#--url) | The base URL hosting downloadable asset bundles. | -`mockingbird download ` - -| Asset | Description | +| Flag | Description | | --- | --- | -| `starter-pack` | Starter [supporting source files](https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files). | - -| Option | Default Value | Description | -| --- | --- | --- | -| `--url` | `https://github.com/birdrides/mockingbird/releases/download` | The base URL containing downloadable asset bundles. | +| `--preserve-existing` | Keep previously added Mockingbird build phases. | ### Global Options @@ -742,8 +773,10 @@ Download and unpack a compatible asset bundle. Bundles will never overwrite exis | --- | --- | | `--verbose` | Log all errors, warnings, and debug messages. | | `--quiet` | Only log error messages. | +| `--version` | Show the version. | +| `-h, --help` | Show help information. | -### Inferred Paths +### Default Inferred Values #### `--project` @@ -755,7 +788,7 @@ Mockingbird checks the environment variables `SRCROOT` and `SOURCE_ROOT` set by #### `--outputs` -By Mockingbird generates mocks into the directory `$(SRCROOT)/MockingbirdMocks` with the file name `$(PRODUCT_MODULE_NAME)Mocks.generated.swift`. +Mockingbird generates mocks into the directory `$(SRCROOT)/MockingbirdMocks` with the file name `$(PRODUCT_MODULE_NAME)Mocks.generated.swift`. #### `--support` @@ -765,6 +798,10 @@ Mockingbird recursively looks for [supporting source files](https://github.com/b Mockingbird checks the environment variables `TARGET_NAME` and `TARGETNAME` set by the Xcode build context and verifies that it refers to a valid Swift unit test target. The test bundle option must be set when using [JSON project descriptions](https://github.com/birdrides/mockingbird/wiki/Manual-Setup#generating-mocks-for-non-xcode-projects) in order to enable thunk stubs. +### `--url` + +Mockingbird uses the GitHub release artifacts located at `https://github.com/birdrides/mockingbird/releases/download`. Note that asset bundles are versioned by release. + ## Additional Resources ### Examples and Tutorials @@ -776,6 +813,6 @@ Mockingbird checks the environment variables `TARGET_NAME` and `TARGETNAME` set ### Help and Documentation - [API reference](https://birdrides.github.io/mockingbird/latest/) -- [Slack channel](https://slofile.com/slack/birdopensource) +- [Slack channel](https://join.slack.com/t/birdopensource/shared_invite/zt-wogxij50-3ZM7F8ZxFXvPkE0j8xTtmw) - [Troubleshooting guide](https://github.com/birdrides/mockingbird/wiki/Troubleshooting) - [Mockingbird wiki](https://github.com/birdrides/mockingbird/wiki/) diff --git a/README.md b/README.md index 1d45da64..8b867f79 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

Package managers License - Slack channel + Slack channel

Mockingbird lets you mock, stub, and verify objects written in either Swift or Objective-C. The syntax takes inspiration from (OC)Mockito but was designed to be “Swifty” in terms of type safety and expressiveness. @@ -35,7 +35,7 @@ See a detailed [feature comparison table](https://github.com/birdrides/mockingbi ### Who Uses Mockingbird? -Mockingbird powers thousands of tests at companies including [Facebook](https://facebook.com), [Amazon](https://amazon.com), [Twilio](https://twilio.com), and [Bird](https://bird.co). Using Mockingbird to improve your testing workflow? Consider dropping us a line on the [#mockingbird Slack channel](https://slofile.com/slack/birdopensource). +Mockingbird powers thousands of tests at companies including [Meta](https://meta.com), [Amazon](https://amazon.com), [Twilio](https://twilio.com), and [Bird](https://bird.co). Using Mockingbird to improve your testing workflow? Consider dropping us a line on the [#mockingbird Slack channel](https://join.slack.com/t/birdopensource/shared_invite/zt-wogxij50-3ZM7F8ZxFXvPkE0j8xTtmw). ### An Example @@ -103,7 +103,7 @@ Optional but recommended: Have questions or issues? -- [Join the Slack channel](https://slofile.com/slack/birdopensource) +- [Join the Slack channel](https://join.slack.com/t/birdopensource/shared_invite/zt-wogxij50-3ZM7F8ZxFXvPkE0j8xTtmw) - [Search the troubleshooting guide](https://github.com/birdrides/mockingbird/wiki/Troubleshooting) - [Check out the CocoaPods example project](/Examples/iOSMockingbirdExample-CocoaPods) @@ -136,7 +136,7 @@ Optional but recommended: Have questions or issues? -- [Join the Slack channel](https://slofile.com/slack/birdopensource) +- [Join the Slack channel](https://join.slack.com/t/birdopensource/shared_invite/zt-wogxij50-3ZM7F8ZxFXvPkE0j8xTtmw) - [Search the troubleshooting guide](https://github.com/birdrides/mockingbird/wiki/Troubleshooting) - [Check out the Carthage example project](/Examples/iOSMockingbirdExample-Carthage) @@ -183,7 +183,7 @@ Optional but recommended: Have questions or issues? -- [Join the Slack channel](https://slofile.com/slack/birdopensource) +- [Join the Slack channel](https://join.slack.com/t/birdopensource/shared_invite/zt-wogxij50-3ZM7F8ZxFXvPkE0j8xTtmw) - [Search the troubleshooting guide](https://github.com/birdrides/mockingbird/wiki/Troubleshooting) - [Check out the Swift Package Manager example project](/Examples/iOSMockingbirdExample-SPM) @@ -650,7 +650,7 @@ To improve compilation times for large projects, Mockingbird only generates mock Usage is determined by statically analyzing test target sources for calls to `mock(SomeType.self)`, which may not work out of the box for projects that indirectly synthesize types such as through Objective-C based dependency injection. - **Option 1:** Explicitly reference each indirectly synthesized type in your tests, e.g. `_ = mock(SomeType.self)`. References can be placed anywhere in the test target sources, such as in the `setUp` method of a test case or in a single file. -- **Option 2:** Disable pruning entirely by setting the prune level with `--prunelevel disable`. Note that this may increase compilation times for large projects. +- **Option 2:** Disable pruning entirely by setting the prune level with `--prune disable`. Note that this may increase compilation times for large projects. ## Mockingbird CLI @@ -671,7 +671,7 @@ Generate mocks for a set of targets in a project. | `--header` | `(none)` | Content to add at the beginning of each generated mock file. | | `--condition` | `(none)` | [Compilation condition](https://docs.swift.org/swift-book/ReferenceManual/Statements.html#ID538) to wrap all generated mocks in, e.g. `DEBUG`. | | `--diagnostics` | `(none)` | List of [diagnostic generator warnings](https://github.com/birdrides/mockingbird/wiki/Diagnostic-Warnings-and-Errors) to enable. | -| `--prune` | `stub` | The [pruning method](#thunk-pruning) to use on unreferenced types. | +| `--prune` | `omit` | The [pruning method](#thunk-pruning) to use on unreferenced types. | | Flag | Description | | --- | --- | @@ -776,6 +776,6 @@ Mockingbird checks the environment variables `TARGET_NAME` and `TARGETNAME` set ### Help and Documentation - [API reference](https://birdrides.github.io/mockingbird/latest/) -- [Slack channel](https://slofile.com/slack/birdopensource) +- [Slack channel](https://join.slack.com/t/birdopensource/shared_invite/zt-wogxij50-3ZM7F8ZxFXvPkE0j8xTtmw) - [Troubleshooting guide](https://github.com/birdrides/mockingbird/wiki/Troubleshooting) - [Mockingbird wiki](https://github.com/birdrides/mockingbird/wiki/) diff --git a/Sources/MockingbirdCli/Extensions/LocalizedError+Extensions.swift b/Sources/MockingbirdCli/Extensions/LocalizedError+Extensions.swift deleted file mode 100644 index 41e448aa..00000000 --- a/Sources/MockingbirdCli/Extensions/LocalizedError+Extensions.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// LocalizedError+Extensions.swift -// MockingbirdCli -// -// Created by Sterling Hackley on 10/27/19. -// - -import Foundation -import SPMUtility - -extension ArgumentParserError: LocalizedError {} -extension ArgumentConversionError: LocalizedError {} -extension Generator.MalformedConfiguration: LocalizedError {} - -public extension LocalizedError where Self: CustomStringConvertible { - var errorDescription: String? { - return description - } -} diff --git a/Sources/MockingbirdCli/Interface/ArgumentParser+Extensions.swift b/Sources/MockingbirdCli/Interface/ArgumentParser+Extensions.swift deleted file mode 100644 index 3600953f..00000000 --- a/Sources/MockingbirdCli/Interface/ArgumentParser+Extensions.swift +++ /dev/null @@ -1,446 +0,0 @@ -// -// ArgumentParser+Extensions.swift -// MockingbirdCli -// -// Created by Andrew Chang on 8/23/19. -// - -import Foundation -import MockingbirdGenerator -import PathKit -import SPMUtility - -extension ArgumentParser { - // MARK: Options - - func addProjectPath() -> OptionArgument { - return add(option: "--project", - kind: PathArgument.self, - usage: "Path to an '.xcodeproj' file or a JSON project description.", - completion: .filename) - } - - func addSourceRoot() -> OptionArgument { - return add(option: "--srcroot", - kind: PathArgument.self, - usage: "The directory containing your project's source files.", - completion: .filename) - } - - func addTargets() -> OptionArgument<[String]> { - return add(option: "--targets", - kind: [String].self, - usage: "List of target names to generate mocks for.") - } - - /// Convenience for `--targets`. Accepts multiple targets. - func addTarget() -> OptionArgument<[String]> { - return add(option: "--target", - kind: [String].self, - usage: "A target name to generate mocks for.") - } - - func addSourceTargets() -> OptionArgument<[String]> { - return add(option: "--sources", - kind: [String].self, - usage: "List of target names to generate mocks for.") - } - - /// Convenience for source `--targets`. Accepts multiple targets. - func addSourceTarget() -> OptionArgument<[String]> { - return add(option: "--source", - kind: [String].self, - usage: "A target name to generate mocks for.") - } - - func addDestinationTarget() -> OptionArgument { - return add(option: "--target", - kind: String.self, - usage: "The name of a test target to configure.") - } - - func addOutputs() -> OptionArgument<[PathArgument]> { - return add(option: "--outputs", - kind: [PathArgument].self, - usage: "List of mock output file paths for each target.", - completion: .filename) - } - - /// Convenience for `--outputs`. Accepts multiple outputs. - func addOutput() -> OptionArgument<[PathArgument]> { - return add(option: "--output", - kind: [PathArgument].self, - usage: "Mock output file path.", - completion: .filename) - } - - /// For installation, only accepts a single output. - func addInstallationOutput() -> OptionArgument { - return add(option: "--output", - kind: PathArgument.self, - usage: "Mock output file path.", - completion: .filename) - } - - func addSupportPath() -> OptionArgument { - return add(option: "--support", - kind: PathArgument.self, - usage: "The directory containing supporting source files.", - completion: .filename) - } - - func addTestBundle() -> OptionArgument { - return add(option: "--testbundle", - kind: String.self, - usage: "The name of the test bundle using the mocks.") - } - - func addHeader() -> OptionArgument<[String]> { - return add(option: "--header", - kind: [String].self, - usage: "Content to add at the beginning of each generated mock file.") - } - - func addCompilationCondition() -> OptionArgument { - return add(option: "--condition", - kind: String.self, - usage: "Compilation condition to wrap all generated mocks in, e.g. 'DEBUG'.", - completion: .values([ - (value: "DEBUG", description: "Debug build configuration"), - (value: "RELEASE", description: "Release build configuration"), - (value: "TEST", description: "Test build configuration")])) - } - - func addInstallerLogLevel() -> OptionArgument { - return add(option: "--loglevel", - kind: LogLevel.self, - usage: "The log level to use when generating mocks.") - } - - func addPruningMethod() -> OptionArgument { - return add(option: "--prune", - kind: PruningMethod.self, - usage: "The pruning method to use on unreferenced types.") - } - - func addMetagenerateOutput() -> OptionArgument { - return add(option: "--output", - kind: PathArgument.self, - usage: "Output directory to generate source files.", - completion: .filename) - } - - func addMetagenerateCount() -> OptionArgument { - return add(option: "--count", - kind: Int.self, - usage: "Number of source files to generate.") - } - - func addDiagnostics() -> OptionArgument<[DiagnosticType]> { - return add(option: "--diagnostics", - kind: [DiagnosticType].self, - usage: "List of diagnostic generator warnings to enable.") - } - - func addBaseUrl() -> OptionArgument { - return add(option: "--url", - kind: String.self, - usage: "The base URL containing downloadable asset bundles.") - } - - // MARK: Global Options - - func addVerboseLogLevel() -> OptionArgument { - return add(option: "--verbose", - kind: Bool.self, - usage: "Log all errors, warnings, and debug messages.") - } - - func addQuietLogLevel() -> OptionArgument { - return add(option: "--quiet", - kind: Bool.self, - usage: "Only log error messages.") - } - - // MARK: Flags - - func addOnlyProtocols() -> OptionArgument { - return add(option: "--only-protocols", - kind: Bool.self, - usage: "Only generate mocks for protocols.") - } - - func addDisableModuleImport() -> OptionArgument { - return add(option: "--disable-module-import", - kind: Bool.self, - usage: "Omit '@testable import ' from generated mocks.") - } - - func addIgnoreExistingRunScript() -> OptionArgument { - return add(option: "--preserve-existing", - kind: Bool.self, - usage: "Don’t overwrite previously installed configurations.") - } - - func addAynchronousGeneration() -> OptionArgument { - return add(option: "--asynchronous", - kind: Bool.self, - usage: "Generate mocks asynchronously in the background when building.") - } - - func addDisableSwiftlint() -> OptionArgument { - return add(option: "--disable-swiftlint", - kind: Bool.self, - usage: "Disable all SwiftLint rules in generated mocks.") - } - - func addDisableCache() -> OptionArgument { - return add(option: "--disable-cache", - kind: Bool.self, - usage: "Ignore cached mock information stored on disk.") - } - - func addDisableRelaxedLinking() -> OptionArgument { - return add(option: "--disable-relaxed-linking", - kind: Bool.self, - usage: "Only search explicitly imported modules.") - } - - // MARK: - Positional - - func addAssetBundleType() -> PositionalArgument { - return add(positional: "asset", - kind: AssetBundleType.self, - usage: "An asset bundle to download and unpack.", - completion: AssetBundleType.completion) - } -} - -extension ArgumentParser.Result { - func getProjectPath(using argument: OptionArgument, - environment: [String: String], - workingPath: Path) throws -> Path { - let projectPath: Path - if let rawProjectPath = get(argument)?.path.pathString ?? environment["PROJECT_FILE_PATH"] { - projectPath = Path(rawProjectPath) - } else { - let inferredXcodeProjects = try workingPath.containedXcodeProjects() - if let firstProject = inferredXcodeProjects.first, inferredXcodeProjects.count == 1 { - log("Using inferred Xcode project at \(firstProject.absolute())") - projectPath = firstProject - } else { - if inferredXcodeProjects.count > 1 { - logWarning("Unable to infer Xcode project because there are multiple '.xcodeproj' files in \(workingPath.absolute())") - } - throw ArgumentParserError.expectedValue(option: "--project ") - } - } - return projectPath - } - - func getSourceRoot(using argument: OptionArgument, - environment: [String: String], - projectPath: Path) -> Path { - if let rawSourceRoot = get(argument)?.path.pathString ?? - environment["SRCROOT"] ?? environment["SOURCE_ROOT"] { - return Path(rawSourceRoot) - } else { - return projectPath.parent() - } - } - - func getTargets(using argument: OptionArgument<[String]>, - convenienceArgument: OptionArgument<[String]>, - environment: [String: String]) throws -> [String] { - if let targets = get(argument) ?? get(convenienceArgument) { - return targets - } else if let target = environment["TARGET_NAME"] { - return [target] - } else { - throw ArgumentParserError.expectedValue(option: "--targets ") - } - } - - func getOutputs(using argument: OptionArgument<[PathArgument]>, - convenienceArgument: OptionArgument<[PathArgument]>) -> [Path]? { - if let rawOutputs = (get(argument) ?? get(convenienceArgument))?.map({ $0.path.pathString }) { - return rawOutputs.map({ Path($0) }) - } - return nil - } - - func getSupportPath(using argument: OptionArgument, - sourceRoot: Path) throws -> Path? { - guard let rawSupportPath = get(argument)?.path.pathString else { - let defaultSupportPath = sourceRoot + "MockingbirdSupport" - guard defaultSupportPath.isDirectory else { - logWarning("Unable to infer support path because no directory exists at \(defaultSupportPath)") - return nil - } - log("Using inferred support path at \(defaultSupportPath)") - return defaultSupportPath - } - let supportPath = Path(rawSupportPath) - guard supportPath.isDirectory else { - throw ArgumentParserError.invalidValue(argument: "--support \(supportPath.absolute())", - error: .custom("Not a valid directory")) - } - return supportPath - } - - func getSourceTargets(using argument: OptionArgument<[String]>, - convenienceArgument: OptionArgument<[String]>) throws -> [String] { - if let targets = get(argument) ?? get(convenienceArgument) { - return targets - } else { - throw ArgumentParserError.expectedValue(option: "--sources ") - } - } - - func getDestinationTarget(using argument: OptionArgument) throws -> String { - if let target = get(argument) { - return target - } else { - throw ArgumentParserError.expectedValue(option: "--target ") - } - } - - func getOutputDirectory(using argument: OptionArgument) throws -> Path { - if let rawOutput = get(argument)?.path.pathString { - let path = Path(rawOutput) - guard path.isDirectory else { - throw ArgumentParserError.invalidValue(argument: "--output \(path.absolute())", - error: .custom("Not a valid directory")) - } - return path - } - throw ArgumentParserError.expectedValue(option: "--output ") - } - - func getCount(using argument: OptionArgument) throws -> Int? { - if let count = get(argument) { - guard count > 0 else { - throw ArgumentParserError.invalidValue(argument: "--count \(count)", - error: .custom("Not a positive number")) - } - return count - } - return nil - } - - func getLogLevel(verboseOption: OptionArgument, - quietOption: OptionArgument) throws -> LogLevel { - let isVerbose = get(verboseOption) == true - let isQuiet = get(quietOption) == true - guard !isVerbose || !isQuiet else { - let error = ArgumentConversionError.custom("Cannot specify both --verbose and --quiet") - throw ArgumentParserError.invalidValue(argument: "--verbose --quiet", - error: error) - } - if isVerbose { - return .verbose - } else if isQuiet { - return .quiet - } else { - return .normal - } - } -} - -extension LogLevel: ArgumentKind, CustomStringConvertible { - public init(argument: String) throws { - guard LogLevel(rawValue: argument) != nil else { - let allOptions = LogLevel.allCases.map({ $0.rawValue }).joined(separator: ", ") - throw ArgumentParserError.invalidValue( - argument: "--loglevel \(argument)", - error: .custom("Not a valid log level, expected: \(allOptions)") - ) - } - self.init(rawValue: argument)! - } - - public static var completion: ShellCompletion { - return .values(LogLevel.allCases.map({ - (value: $0.rawValue, description: "\($0)") - })) - } - - public var description: String { - switch self { - case .quiet: - return "Only log error messages." - case .normal: - return "Log errors and warnings." - case .verbose: - return "Log all errors, warnings, and debug messages." - } - } -} - -extension DiagnosticType: ArgumentKind, CustomStringConvertible { - public init(argument: String) throws { - guard DiagnosticType(rawValue: argument) != nil else { - let allOptions = DiagnosticType.allCases.map({ $0.rawValue }).joined(separator: ", ") - throw ArgumentParserError.invalidValue( - argument: "--diagnostics \(argument)", - error: .custom("Not a valid diagnostic type, expected: \(allOptions)") - ) - } - self.init(rawValue: argument)! - } - - public static var completion: ShellCompletion { - return .values(DiagnosticType.allCases.map({ - (value: $0.rawValue, description: "\($0)") - })) - } - - public var description: String { - switch self { - case .all: - return "Emit all diagnostic warnings." - case .notMockable: - return "Warn when skipping declarations that cannot be mocked." - case .undefinedType: - return "Warn on external types not defined in a supporting source file." - case .typeInference: - return "Warn when skipping complex property assignments in class mocks." - } - } -} - -extension PruningMethod: ArgumentKind, CustomStringConvertible { - public init(argument: String) throws { - guard PruningMethod(rawValue: argument) != nil else { - let allOptions = PruningMethod.allCases.map({ $0.rawValue }).joined(separator: ", ") - throw ArgumentParserError.invalidValue( - argument: "--prune \(argument)", - error: .custom("Not a valid pruning method, expected: \(allOptions)") - ) - } - self.init(rawValue: argument)! - } - - public static var completion: ShellCompletion { - return .values(PruningMethod.allCases.map({ - (value: $0.rawValue, description: "\($0)") - })) - } - - public var description: String { - switch self { - case .disable: - return "Always generate full thunks regardless of usage in tests." - case .stub: - return "Generate partial definitions filled with 'fatalError'." - case .omit: - return "Don’t generate any definitions for unused types." - } - } -} - -private extension Path { - func containedXcodeProjects() throws -> [Path] { - return try children().filter({ $0.isDirectory && $0.extension == "xcodeproj" }) - } -} diff --git a/Sources/MockingbirdCli/Interface/Arguments/BinaryPath.swift b/Sources/MockingbirdCli/Interface/Arguments/BinaryPath.swift new file mode 100644 index 00000000..83aa99a7 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Arguments/BinaryPath.swift @@ -0,0 +1,44 @@ +// +// BinaryPath.swift +// MockingbirdCli +// +// Created by typealias on 8/8/21. +// + +import ArgumentParser +import Foundation +import MockingbirdGenerator +import PathKit + +struct BinaryPath: ExpressibleByArgument { + var path: Path + var defaultValueDescription: String { path.abbreviate().string } + static var defaultCompletionKind: CompletionKind = .file() + + init?(argument: String) { + self.path = Path(argument) + } +} + +extension BinaryPath: Encodable { + func encode(to encoder: Encoder) throws { + try OptionArgumentEncoding.encode(path, with: encoder) + } +} + +extension BinaryPath: InferableArgument { + init?(context: ArgumentContext) throws { + let launcherPath = context.environment["MKB_LAUNCHER"] + let realBinaryPath = context.arguments[0] + self.path = Path(launcherPath ?? realBinaryPath) + } +} + +extension BinaryPath: ValidatableArgument { + func validate(name: String) throws { + let realPath = try path.followRecursively() + guard realPath.isExecutable else { + throw ValidationError("'\(name)' must be executable") + } + } +} diff --git a/Sources/MockingbirdCli/Interface/Arguments/DirectoryPath.swift b/Sources/MockingbirdCli/Interface/Arguments/DirectoryPath.swift new file mode 100644 index 00000000..67d13453 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Arguments/DirectoryPath.swift @@ -0,0 +1,40 @@ +// +// DirectoryPath.swift +// MockingbirdCli +// +// Created by typealias on 8/7/21. +// + +import ArgumentParser +import Foundation +import PathKit +import MockingbirdGenerator + +class DirectoryPath: ExpressibleByArgument { + var path: Path + var defaultValueDescription: String { path.abbreviate().string } + static var defaultCompletionKind: CompletionKind = .directory + + required init?(argument: String) { + self.path = Path(argument) + } + + init?(path: Path?) { + guard let path = path else { return nil } + self.path = path + } +} + +extension DirectoryPath: Encodable { + func encode(to encoder: Encoder) throws { + try OptionArgumentEncoding.encode(path, with: encoder) + } +} + +extension DirectoryPath: ValidatableArgument { + func validate(name: String) throws { + guard path.exists, path.isDirectory else { + throw ValidationError("'\(name)' must be an existing directory") + } + } +} diff --git a/Sources/MockingbirdCli/Interface/Arguments/ExtendedGeneratorTypes.swift b/Sources/MockingbirdCli/Interface/Arguments/ExtendedGeneratorTypes.swift new file mode 100644 index 00000000..1e76a4eb --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Arguments/ExtendedGeneratorTypes.swift @@ -0,0 +1,12 @@ +// +// ExtendedGeneratorTypes.swift +// MockingbirdCli +// +// Created by typealias on 12/20/21. +// + +import ArgumentParser +import MockingbirdGenerator + +extension PruningMethod: ExpressibleByArgument {} +extension DiagnosticType: ExpressibleByArgument {} diff --git a/Sources/MockingbirdCli/Interface/Arguments/InferableArgument.swift b/Sources/MockingbirdCli/Interface/Arguments/InferableArgument.swift new file mode 100644 index 00000000..114cc460 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Arguments/InferableArgument.swift @@ -0,0 +1,33 @@ +// +// InferableArgument.swift +// MockingbirdCli +// +// Created by typealias on 8/7/21. +// + +import Foundation +import PathKit + +struct ArgumentContext: Codable { + let workingPath: Path + let environment: [String: String] + let arguments: [String] + + static var shared = ArgumentContext( + workingPath: Path(FileManager.default.currentDirectoryPath), + environment: ProcessInfo.processInfo.environment, + arguments: CommandLine.arguments + ) +} + +protocol InferableArgument { + init?(context: ArgumentContext) throws +} + +func inferArgument(_ argument: T?, + in context: ArgumentContext = .shared) throws -> T? { + guard argument == nil else { + return argument + } + return try T(context: context) +} diff --git a/Sources/MockingbirdCli/Interface/Arguments/SupportingSourcesPath.swift b/Sources/MockingbirdCli/Interface/Arguments/SupportingSourcesPath.swift new file mode 100644 index 00000000..07aac734 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Arguments/SupportingSourcesPath.swift @@ -0,0 +1,28 @@ +// +// SupportingSourcesPath.swift +// MockingbirdCli +// +// Created by typealias on 8/7/21. +// + +import ArgumentParser +import Foundation +import MockingbirdGenerator +import PathKit + +final class SupportingSourcesPath: DirectoryPath {} + +extension SupportingSourcesPath: InferableArgument { + convenience init?(context: ArgumentContext) throws { + let defaultSupportPath = Self.genDefaultPath(workingPath: context.workingPath) + guard defaultSupportPath.exists, defaultSupportPath.isDirectory else { + return nil + } + log("Using inferred support path at \(defaultSupportPath.absolute())") + self.init(path: defaultSupportPath) + } + + static func genDefaultPath(workingPath: Path) -> Path { + return workingPath + "MockingbirdSupport" + } +} diff --git a/Sources/MockingbirdCli/Interface/Arguments/SwiftFilePath.swift b/Sources/MockingbirdCli/Interface/Arguments/SwiftFilePath.swift new file mode 100644 index 00000000..781a5870 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Arguments/SwiftFilePath.swift @@ -0,0 +1,27 @@ +// +// SwiftFilePath.swift +// MockingbirdCli +// +// Created by typealias on 12/23/21. +// + +import ArgumentParser +import Foundation +import PathKit +import MockingbirdGenerator + +struct SwiftFilePath: ExpressibleByArgument { + var path: Path + var defaultValueDescription: String { path.abbreviate().string } + static var defaultCompletionKind: CompletionKind = .file(extensions: ["swift"]) + + init?(argument: String) { + self.path = Path(argument) + } +} + +extension SwiftFilePath: Encodable { + func encode(to encoder: Encoder) throws { + try OptionArgumentEncoding.encode(path, with: encoder) + } +} diff --git a/Sources/MockingbirdCli/Interface/Arguments/TestBundleName.swift b/Sources/MockingbirdCli/Interface/Arguments/TestBundleName.swift new file mode 100644 index 00000000..65082e80 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Arguments/TestBundleName.swift @@ -0,0 +1,40 @@ +// +// TestBundleName.swift +// MockingbirdCli +// +// Created by typealias on 8/13/21. +// + +import ArgumentParser +import Foundation +import MockingbirdGenerator +import PathKit + +struct TestBundleName: ExpressibleByArgument { + var name: String + var defaultValueDescription: String { name } + + init?(argument: String) { + self.name = argument + } +} + +extension TestBundleName: Encodable { + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(name) + } +} + +extension TestBundleName: InferableArgument { + init?(context: ArgumentContext) throws { + guard let targetName = + context.environment["TARGET_NAME"] ?? + context.environment["TARGETNAME"] else { + return nil + } + + log("Using inferred test bundle name \(singleQuoted: targetName)") + self.init(argument: targetName) + } +} diff --git a/Sources/MockingbirdCli/Interface/Arguments/URLArgument.swift b/Sources/MockingbirdCli/Interface/Arguments/URLArgument.swift new file mode 100644 index 00000000..16d68bc5 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Arguments/URLArgument.swift @@ -0,0 +1,31 @@ +// +// URLArgument.swift +// MockingbirdCli +// +// Created by typealias on 12/21/21. +// + +import ArgumentParser +import Foundation +import MockingbirdGenerator + +class URLArgument: ExpressibleByArgument { + var url: URL + var defaultValueDescription: String { url.absoluteString } + + required init?(argument: String) { + guard let url = URL(string: argument) else { return nil } + self.url = url + } + + init(_ url: URL) { + self.url = url + } +} + +extension URLArgument: Encodable { + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(url.absoluteString) + } +} diff --git a/Sources/MockingbirdCli/Interface/Arguments/ValidatableArgument.swift b/Sources/MockingbirdCli/Interface/Arguments/ValidatableArgument.swift new file mode 100644 index 00000000..7152df64 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Arguments/ValidatableArgument.swift @@ -0,0 +1,34 @@ +// +// ValidatableArgument.swift +// MockingbirdCli +// +// Created by typealias on 8/7/21. +// + +import ArgumentParser +import Foundation + +protocol ValidatableArgument { + func validate(name: String) throws +} + +@discardableResult +func validateRequiredArgument(_ argument: T?, name: String) throws -> T { + guard let validatedArgument = try argument ?? + validateOptionalArgument(argument, name: name) else { + if argument is InferableArgument { + throw ValidationError("Unable to infer a value for '\(name)'") + } else { + throw ValidationError("Missing required value for '\(name)'") + } + } + return validatedArgument +} + +@discardableResult +func validateOptionalArgument(_ argument: T?, name: String) throws -> T? { + if let validatableArgument = argument as? ValidatableArgument { + try validatableArgument.validate(name: name) + } + return argument +} diff --git a/Sources/MockingbirdCli/Interface/Arguments/XcodeProjPath.swift b/Sources/MockingbirdCli/Interface/Arguments/XcodeProjPath.swift new file mode 100644 index 00000000..f7df8e0d --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Arguments/XcodeProjPath.swift @@ -0,0 +1,71 @@ +// +// Path+ExpressibleByArgument.swift +// MockingbirdCli +// +// Created by typealias on 8/7/21. +// + +import ArgumentParser +import Foundation +import PathKit +import MockingbirdGenerator + +struct XcodeProjPath: ExpressibleByArgument { + var path: Path + var defaultValueDescription: String { path.abbreviate().string } + static var defaultCompletionKind: CompletionKind = .file(extensions: ["xcodeproj"]) + + init?(argument: String) { + let path = Path(argument) + if let containedXcodeProjects = try? path.findContainedXcodeProjects(), + let firstXcodeProject = containedXcodeProjects.first { + // The user provided the directory containing the Xcode project instead of the `.xcodeproj`. + if containedXcodeProjects.count > 1 { + logWarning("Found multiple Xcode projects in \(path.absolute())") + } + self.path = firstXcodeProject + } else { + self.path = path + } + } +} + +extension XcodeProjPath: Encodable { + func encode(to encoder: Encoder) throws { + try OptionArgumentEncoding.encode(path, with: encoder) + } +} + +extension XcodeProjPath: InferableArgument { + init?(context: ArgumentContext) throws { + if let xcodebuildProjectPath = context.environment["PROJECT_FILE_PATH"] { + path = Path(xcodebuildProjectPath) + return + } + + let containedXcodeProjects = try context.workingPath.findContainedXcodeProjects().sorted() + if let firstXcodeProject = containedXcodeProjects.first { + if containedXcodeProjects.count > 1 { + logWarning("Found multiple Xcode projects in \(context.workingPath.absolute())") + } + log("Using inferred Xcode project at \(firstXcodeProject.absolute())") + path = firstXcodeProject + } else { + return nil + } + } +} + +extension XcodeProjPath: ValidatableArgument { + func validate(name: String) throws { + guard path.extension == "xcodeproj" else { + throw ValidationError("'\(name)' must be an Xcode project or JSON project description") + } + } +} + +private extension Path { + func findContainedXcodeProjects() throws -> [Path] { + return try children().filter({ $0.isDirectory && $0.extension == "xcodeproj" }) + } +} diff --git a/Sources/MockingbirdCli/Interface/Commands/Configure.swift b/Sources/MockingbirdCli/Interface/Commands/Configure.swift new file mode 100644 index 00000000..78c9c2ae --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Commands/Configure.swift @@ -0,0 +1,146 @@ +// +// Configure.swift +// MockingbirdCli +// +// Created by typealias on 8/7/21. +// + +import ArgumentParser +import Foundation +import PathKit +import MockingbirdGenerator + +extension Mockingbird { + struct Configure: ParsableCommand { + static var configuration = CommandConfiguration( + abstract: "Configure a test target to generate mocks." + ) + + /// Inherited from parent command. + @OptionGroup() var globalOptions: Options + + @Argument(help: "The name of a test target to configure.") + var testTarget: String + + @Option(name: [.customLong("project"), + .customShort("p")], + help: "Path to an Xcode project.") + var project: XcodeProjPath? + + @Option(name: [.customLong("srcproject")], + help: "Path to the Xcode project with source modules, if separate from tests.") + var sourceProject: XcodeProjPath? + + @Option(help: "Path to the Mockingbird generator executable.") + var generator: BinaryPath? + + @Option(name: [.customLong("url")], + help: "The base URL hosting downloadable asset bundles.") + var baseURL: URLArgument = URLArgument(URL(string: "https://github.com/birdrides/mockingbird/releases/download")!) + + // MARK: Flags + + @Flag(help: "Keep previously added Mockingbird build phases.") + var preserveExisting: Bool = false + + // MARK: Generator + + @Argument(help: "Arguments to use when running the generator. See the 'generate' command for all options.") + var generatorOptions: [String] = [] + + struct Arguments { + let testTarget: String + let project: Path + let sourceProject: Path + let generator: Path + let baseURL: URL + let preserveExisting: Bool + let generatorOptions: [String] + let generateCommand: Generate + } + + func validate() throws { + let arguments = try infer() + guard arguments.project.extension == "xcodeproj" else { + throw ValidationError("'--project' must be a valid Xcode project") + } + } + + @discardableResult + nonmutating func infer() throws -> Arguments { + let validProject = try validateRequiredArgument(inferArgument(project), name: "project") + let validGenerator = try validateRequiredArgument(inferArgument(generator), name: "generator") + let sourceProject = sourceProject?.path ?? validProject.path + + let generateCommand: Generate + do { + // Common options that should be forwarded to the generate command. + var forwardedOptions: [String] = [] + // Unnecessarily specifying the project path makes it brittle to refactoring. + if sourceProject != validProject.path { + forwardedOptions.append(contentsOf: ["--project", sourceProject.string]) + } + generateCommand = try Generate.parse(generatorOptions + forwardedOptions) + } catch { + // Need to rethrow `CommandError` objects thrown when manually parsing. + throw ValidationError(Generate.message(for: error)) + } + + return Arguments( + testTarget: testTarget, + project: validProject.path, + sourceProject: sourceProject, + generator: validGenerator.path, + baseURL: baseURL.url, + preserveExisting: preserveExisting, + generatorOptions: generatorOptions, + generateCommand: generateCommand) + } + + mutating func run() throws { + let start = Date() + let parsedConfigureArguments = try infer() + let parsedGenerateArguments = try parsedConfigureArguments.generateCommand.infer() + + logInfo("🛠 Project: \(parsedConfigureArguments.project.abbreviate())") + logInfo("🎯 Test Target: \(parsedConfigureArguments.testTarget)") + logInfo("🧰 Supporting sources: \(parsedGenerateArguments.support.abbreviate())") + + let downloaderConfig = Downloader.Configuration( + assetBundleType: .starterPack, + outputPath: parsedGenerateArguments.support.parent(), + baseURL: parsedConfigureArguments.baseURL) + let downloader = Downloader(config: downloaderConfig) + try downloader.download() + logInfo("✅ Downloaded supporting source files") + + // Ensure consistency between the build phase and the generator frontend while also performing + // path transformations to make the installation relative to the project source root. + let encoder = ArgumentsEncoder() + encoder.sourceRoot = parsedConfigureArguments.project.parent() + + let installerConfig = Installer.Configuration( + projectPath: parsedConfigureArguments.project, + sourceProjectPath: parsedConfigureArguments.sourceProject, + testTargetName: parsedConfigureArguments.testTarget, + cliPath: parsedConfigureArguments.generator, + sourceRoot: parsedGenerateArguments.srcroot, + sourceTargetNames: parsedGenerateArguments.targets, + outputPaths: parsedGenerateArguments.outputs, + generatorOptions: try encoder.encode(parsedConfigureArguments.generateCommand), + overwrite: !parsedConfigureArguments.preserveExisting) + let installer = try Installer(config: installerConfig) + try installer.install() + logInfo("✅ Added build phase \(singleQuoted: Installer.Constants.buildPhaseName)") + + let wallTime = TimeUnit(Date().timeIntervalSince(start)) + logInfo("🎉 Successfully configured \(parsedConfigureArguments.testTarget) in \(wallTime)") + logInfo(""" + 🚀 Usage: + 1. Initialize a mock in your test with `mock(SomeType.self)` + 2. Build \(singleQuoted: parsedConfigureArguments.testTarget) (⇧⌘U) to generate mocks + 3. Write some Swifty tests! + """) + } + } +} diff --git a/Sources/MockingbirdCli/Interface/Commands/DownloadCommand.swift b/Sources/MockingbirdCli/Interface/Commands/DownloadCommand.swift deleted file mode 100644 index 7dffcb87..00000000 --- a/Sources/MockingbirdCli/Interface/Commands/DownloadCommand.swift +++ /dev/null @@ -1,146 +0,0 @@ -// -// DownloadCommand.swift -// MockingbirdCli -// -// Created by Andrew Chang on 4/26/20. -// - -import Foundation -import MockingbirdGenerator -import PathKit -import SPMUtility -import ZIPFoundation - -enum AssetBundleType: String, ArgumentKind, CaseIterable, CustomStringConvertible { - case starterPack = "starter-pack" - - init(argument: String) throws { - guard AssetBundleType(rawValue: argument) != nil else { - let allOptions = AssetBundleType.allCases.map({ $0.rawValue }).joined(separator: ", ") - throw ArgumentParserError.invalidValue( - argument: "asset", - error: .custom("\(argument.singleQuoted) is not a valid download type, expected: \(allOptions)") - ) - } - self.init(rawValue: argument)! - } - - static var completion: ShellCompletion { - return .values(AssetBundleType.allCases.map({ - (value: $0.rawValue, description: "\($0)") - })) - } - - var description: String { - switch self { - case .starterPack: return "Starter supporting source files." - } - } - - func getUrl(with baseUrl: String) -> Foundation.URL { - let fileName: String - switch self { - case .starterPack: fileName = "MockingbirdSupport.zip" - } - return Foundation.URL(string: - "\(baseUrl)/\(mockingbirdVersion)/\(fileName)" - )! - } -} - -final class DownloadCommand: BaseCommand { - private enum Constants { - static let name = "download" - static let overview = "Download and unpack a compatible asset bundle." - - static let excludedAssetRootDirectories: Set = [ - "__MACOSX", - ] - static let excludedAssetFileNames: Set = [ - ".DS_Store", - ] - - static let defaultBaseUrl = "https://github.com/birdrides/mockingbird/releases/download" - } - override var name: String { return Constants.name } - override var overview: String { return Constants.overview } - - private let assetBundleTypeArgument: PositionalArgument - private let projectPathArgument: OptionArgument - private let baseUrlArgument: OptionArgument - - required init(parser: ArgumentParser) { - let subparser = parser.add(subparser: Constants.name, overview: Constants.overview) - self.assetBundleTypeArgument = subparser.addAssetBundleType() - self.projectPathArgument = subparser.addProjectPath() - self.baseUrlArgument = subparser.addBaseUrl() - super.init(parser: subparser) - } - - override func run(with arguments: ArgumentParser.Result, - environment: [String: String], - workingPath: Path) throws { - let projectPath = try arguments.getProjectPath(using: projectPathArgument, - environment: environment, - workingPath: workingPath) - let inferredRootPath = projectPath.parent() - let baseUrl = arguments.get(baseUrlArgument) ?? Constants.defaultBaseUrl - - try super.run(with: arguments, environment: environment, workingPath: workingPath) - guard let type = arguments.get(assetBundleTypeArgument) else { return } - - let downloadUrl = type.getUrl(with: baseUrl) - logInfo("Downloading asset bundle from \(downloadUrl)") - guard let fileUrl = downloadAssetBundle(downloadUrl) else { - log("Unable to download asset bundle \(type.rawValue.singleQuoted)", type: .error) - exit(1) - } - - logInfo("Temporary asset bundle data stored at \(fileUrl)") - logInfo("Extracting downloaded asset bundle to \(Path().absolute())") - guard let archive = Archive(url: fileUrl, accessMode: .read) else { - log("The downloaded asset bundle is corrupted", type: .error) - exit(1) - } - - try self.extractAssetBundle(archive, to: inferredRootPath) - logInfo("Successfully loaded asset bundle \(type.rawValue.singleQuoted) into \(inferredRootPath)") - } - - private func downloadAssetBundle(_ url: Foundation.URL) -> Foundation.URL? { - let semaphore = DispatchSemaphore(value: 0) - var fileUrl: Foundation.URL? - URLSession.shared.downloadTask(with: url) { (url, _, error) in - if let error = error { log(error) } - fileUrl = url - semaphore.signal() - }.resume() - semaphore.wait() - return fileUrl - } - - private func extractAssetBundle(_ archive: Archive, to path: Path) throws { - let basePath = path.absolute() - for entry in archive { - let entryPath = Path(entry.path) - guard - let firstComponent = entryPath.components.first, - !Constants.excludedAssetRootDirectories.contains(firstComponent) - else { - log("Skipping excluded asset bundle entry based on root directory at \(entryPath)") - continue - } - guard !Constants.excludedAssetFileNames.contains(entryPath.lastComponent) else { - log("Skipping excluded asset bundle entry based on file name at \(entryPath)") - continue - } - - let destinationPath = basePath + entryPath - guard !destinationPath.exists else { - logWarning("Skipping existing asset bundle contents at \(entryPath)") - continue - } - _ = try archive.extract(entry, to: destinationPath.url) - } - } -} diff --git a/Sources/MockingbirdCli/Interface/Commands/Encoding/ArgumentsEncoder.swift b/Sources/MockingbirdCli/Interface/Commands/Encoding/ArgumentsEncoder.swift new file mode 100644 index 00000000..3b47b544 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Commands/Encoding/ArgumentsEncoder.swift @@ -0,0 +1,58 @@ +// +// ArgumentsEncoder.swift +// MockingbirdCli +// +// Created by typealias on 12/20/21. +// + +import Foundation +import MockingbirdGenerator +import PathKit + +protocol EncodableArguments: Encodable { + func encodeOptions(to encoder: Encoder) throws + func encodeFlags(to encoder: Encoder) throws + func encodeOptionGroups(to encoder: Encoder) throws +} + +/// Encodes an object into an array of command line option and flag arguments. +class ArgumentsEncoder { + var sourceRoot: Path? + var substitutionStyle: SubstitutionStyle = .bash + + func encode(_ value: T) throws -> [String] { + var pathConfig: OptionArgumentEncoding.PathConfiguration? + if let sourceRoot = sourceRoot { + pathConfig = OptionArgumentEncoding.PathConfiguration( + sourceRoot: sourceRoot, + substitutionStyle: substitutionStyle + ) + } + + let optionEncoding = OptionArgumentEncoding(pathConfig: pathConfig) + try value.encodeOptions(to: optionEncoding) + + let flagEncoding = FlagArgumentEncoding() + try value.encodeFlags(to: flagEncoding) + + let optionGroupEncoding = OptionGroupArgumentEncoding() + try value.encodeOptionGroups(to: optionGroupEncoding) + + return optionEncoding.data.arguments + + flagEncoding.data.arguments + + optionGroupEncoding.data.arguments + } +} + +extension CodingKey { + var longArgumentName: String { + let hyphenatedName = stringValue.unicodeScalars.reduce(into: "") { (name, character) in + if CharacterSet.uppercaseLetters.contains(character) { + name += "-" + String(character).localizedLowercase + } else { + name += String(character) + } + } + return "--\(hyphenatedName)" + } +} diff --git a/Sources/MockingbirdCli/Interface/Commands/Encoding/FlagArgumentEncoding.swift b/Sources/MockingbirdCli/Interface/Commands/Encoding/FlagArgumentEncoding.swift new file mode 100644 index 00000000..ae899ec4 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Commands/Encoding/FlagArgumentEncoding.swift @@ -0,0 +1,214 @@ +// +// FlagArgumentEncoding.swift +// MockingbirdCli +// +// Created by typealias on 12/20/21. +// + +import Foundation + +struct FlagArgumentEncoding: Encoder { + final class EncodedArguments { + private(set) var arguments: [String] = [] + func append(_ name: CodingKey?) { + if let name = name?.longArgumentName { + arguments.append(name) + } + } + } + + var codingPath: [CodingKey] = [] + var userInfo: [CodingUserInfoKey: Any] = [:] + let data: EncodedArguments + + init(arguments: EncodedArguments = EncodedArguments()) { + self.data = arguments + } + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { + var container = FlagArgumentKeyedEncoding(arguments: data) + container.codingPath = codingPath + return KeyedEncodingContainer(container) + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + fatalError("Unsupported encoding container type") + } + + func singleValueContainer() -> SingleValueEncodingContainer { + var container = FlagArgumentSingleValueEncoding(arguments: data) + container.codingPath = codingPath + return container + } +} + +struct FlagArgumentKeyedEncoding: KeyedEncodingContainerProtocol { + + var codingPath: [CodingKey] = [] + private let data: FlagArgumentEncoding.EncodedArguments + + init(arguments: FlagArgumentEncoding.EncodedArguments) { + self.data = arguments + } + + mutating func encodeNil(forKey key: Key) throws { + // No-op + } + + mutating func encode(_ value: Bool, forKey key: Key) throws { + if value { + data.append(key) + } + } + + mutating func encode(_ value: String, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Double, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Float, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Int, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Int8, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Int16, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Int32, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Int64, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: UInt, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: UInt8, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: UInt16, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: UInt32, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: UInt64, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: T, forKey key: Key) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func nestedContainer( + keyedBy keyType: NestedKey.Type, + forKey key: Key + ) -> KeyedEncodingContainer { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func superEncoder() -> Encoder { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func superEncoder(forKey key: Key) -> Encoder { + fatalError("Flag arguments must be a 'Bool' type") + } +} + +struct FlagArgumentSingleValueEncoding: SingleValueEncodingContainer { + + var codingPath: [CodingKey] = [] + let data: FlagArgumentEncoding.EncodedArguments + + init(arguments: FlagArgumentEncoding.EncodedArguments) { + self.data = arguments + } + + mutating func encodeNil() throws { + // No-op + } + + mutating func encode(_ value: Bool) throws { + if value { + data.append(codingPath.last) + } + } + + mutating func encode(_ value: String) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Double) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Float) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Int) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Int8) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Int16) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Int32) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: Int64) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: UInt) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: UInt8) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: UInt16) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: UInt32) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: UInt64) throws { + fatalError("Flag arguments must be a 'Bool' type") + } + + mutating func encode(_ value: T) throws { + fatalError("Flag arguments must be a 'Bool' type") + } +} diff --git a/Sources/MockingbirdCli/Interface/Commands/Encoding/OptionArgumentEncoding.swift b/Sources/MockingbirdCli/Interface/Commands/Encoding/OptionArgumentEncoding.swift new file mode 100644 index 00000000..d49fa7d0 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Commands/Encoding/OptionArgumentEncoding.swift @@ -0,0 +1,374 @@ +// +// OptionArgumentEncoding.swift +// MockingbirdCli +// +// Created by typealias on 12/20/21. +// + +import Foundation +import MockingbirdGenerator +import PathKit + +/// Encodes key-value options, normalizing values based on the configuration. +struct OptionArgumentEncoding: Encoder { + class EncodedArguments { + private(set) var arguments: [String] = [] + + func append(_ argument: String?) { + if let argument = argument { + arguments.append(argument) + } + } + + func append(_ name: CodingKey?, _ argument: String?) { + append(name?.longArgumentName) + append(argument) + } + } + + struct PathConfiguration { + let sourceRoot: Path + let substitutionStyle: SubstitutionStyle + } + + var codingPath: [CodingKey] = [] + var userInfo: [CodingUserInfoKey: Any] = [:] + let data: EncodedArguments + let pathConfig: PathConfiguration? + + init(arguments: EncodedArguments = EncodedArguments(), + pathConfig: PathConfiguration? = nil) { + self.data = arguments + self.pathConfig = pathConfig + self.userInfo[Self.pathConfigUserInfoKey] = pathConfig + } + + static var pathConfigUserInfoKey: CodingUserInfoKey { + CodingUserInfoKey(rawValue: "pathConfig")! + } + static func encode(_ path: Path, with encoder: Encoder) throws { + var container = encoder.singleValueContainer() + guard let config = encoder.userInfo[Self.pathConfigUserInfoKey] as? PathConfiguration else { + return try container.encode(path.abbreviated()) + } + let relativePath = path.abbreviated(root: config.sourceRoot, + variable: "SRCROOT", + style: config.substitutionStyle) + try container.encode(relativePath) + } + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { + var container = OptionArgumentKeyedEncoding(arguments: data, pathConfig: pathConfig) + container.codingPath = codingPath + return KeyedEncodingContainer(container) + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + var container = OptionArgumentUnkeyedEncoding(arguments: data, pathConfig: pathConfig) + container.codingPath = codingPath + return container + } + + func singleValueContainer() -> SingleValueEncodingContainer { + var container = OptionArgumentSingleValueEncoding(arguments: data, pathConfig: pathConfig) + container.codingPath = codingPath + return container + } +} + +struct OptionArgumentKeyedEncoding: KeyedEncodingContainerProtocol { + + var codingPath: [CodingKey] = [] + private let data: OptionArgumentEncoding.EncodedArguments + private let pathConfig: OptionArgumentEncoding.PathConfiguration? + + init(arguments: OptionArgumentEncoding.EncodedArguments, + pathConfig: OptionArgumentEncoding.PathConfiguration?) { + self.data = arguments + self.pathConfig = pathConfig + } + + mutating func encodeNil(forKey key: Key) throws { + // No-op + } + + mutating func encode(_ value: Bool, forKey key: Key) throws { + data.append(key, value ? "1" : "0") + } + + mutating func encode(_ value: String, forKey key: Key) throws { + data.append(key, value.doubleQuoted) + } + + mutating func encode(_ value: Double, forKey key: Key) throws { + data.append(key, String(describing: value)) + } + + mutating func encode(_ value: Float, forKey key: Key) throws { + data.append(key, String(describing: value)) + } + + mutating func encode(_ value: Int, forKey key: Key) throws { + data.append(key, String(describing: value)) + } + + mutating func encode(_ value: Int8, forKey key: Key) throws { + data.append(key, String(describing: value)) + } + + mutating func encode(_ value: Int16, forKey key: Key) throws { + data.append(key, String(describing: value)) + } + + mutating func encode(_ value: Int32, forKey key: Key) throws { + data.append(key, String(describing: value)) + } + + mutating func encode(_ value: Int64, forKey key: Key) throws { + data.append(key, String(describing: value)) + } + + mutating func encode(_ value: UInt, forKey key: Key) throws { + data.append(key, String(describing: value)) + } + + mutating func encode(_ value: UInt8, forKey key: Key) throws { + data.append(key, String(describing: value)) + } + + mutating func encode(_ value: UInt16, forKey key: Key) throws { + data.append(key, String(describing: value)) + } + + mutating func encode(_ value: UInt32, forKey key: Key) throws { + data.append(key, String(describing: value)) + } + + mutating func encode(_ value: UInt64, forKey key: Key) throws { + data.append(key, String(describing: value)) + } + + mutating func encode(_ value: T, forKey key: Key) throws { + var encoding = OptionArgumentEncoding(arguments: data, pathConfig: pathConfig) + encoding.codingPath = codingPath + [key] + try value.encode(to: encoding) + } + + mutating func nestedContainer( + keyedBy keyType: NestedKey.Type, + forKey key: Key + ) -> KeyedEncodingContainer { + var container = OptionArgumentKeyedEncoding(arguments: data, pathConfig: pathConfig) + container.codingPath = codingPath + [key] + return KeyedEncodingContainer(container) + } + + mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + var container = OptionArgumentUnkeyedEncoding(arguments: data, pathConfig: pathConfig) + container.codingPath = codingPath + [key] + return container + } + + mutating func superEncoder() -> Encoder { + let superKey = Key(stringValue: "super")! + return superEncoder(forKey: superKey) + } + + mutating func superEncoder(forKey key: Key) -> Encoder { + var encoding = OptionArgumentEncoding(arguments: data, pathConfig: pathConfig) + encoding.codingPath = codingPath + [key] + return encoding + } +} + +struct OptionArgumentUnkeyedEncoding: UnkeyedEncodingContainer { + class UnkeyedEncodedArguments: OptionArgumentEncoding.EncodedArguments { + override func append(_ name: CodingKey?, _ argument: String?) { + append(argument) + } + } + + var codingPath: [CodingKey] = [] + var count = 0 + + let data: OptionArgumentEncoding.EncodedArguments + let pathConfig: OptionArgumentEncoding.PathConfiguration? + + private mutating func appendArgument(_ argument: String) { + count += 1 + // Handle the current argument parsing strategy for arrays: `--option item1 item2 item3`. + data.append(count == 1 ? codingPath.last : nil, argument) + } + + init(arguments: OptionArgumentEncoding.EncodedArguments, + pathConfig: OptionArgumentEncoding.PathConfiguration?) { + self.data = arguments + self.pathConfig = pathConfig + } + + mutating func encodeNil() throws { + // No-op + } + + mutating func encode(_ value: Bool) throws { + appendArgument(value ? "1" : "0") + } + + mutating func encode(_ value: String) throws { + appendArgument(value.doubleQuoted) + } + + mutating func encode(_ value: Double) throws { + appendArgument(String(describing: value)) + } + + mutating func encode(_ value: Float) throws { + appendArgument(String(describing: value)) + } + + mutating func encode(_ value: Int) throws { + appendArgument(String(describing: value)) + } + + mutating func encode(_ value: Int8) throws { + appendArgument(String(describing: value)) + } + + mutating func encode(_ value: Int16) throws { + appendArgument(String(describing: value)) + } + + mutating func encode(_ value: Int32) throws { + appendArgument(String(describing: value)) + } + + mutating func encode(_ value: Int64) throws { + appendArgument(String(describing: value)) + } + + mutating func encode(_ value: UInt) throws { + appendArgument(String(describing: value)) + } + + mutating func encode(_ value: UInt8) throws { + appendArgument(String(describing: value)) + } + + mutating func encode(_ value: UInt16) throws { + appendArgument(String(describing: value)) + } + + mutating func encode(_ value: UInt32) throws { + appendArgument(String(describing: value)) + } + + mutating func encode(_ value: UInt64) throws { + appendArgument(String(describing: value)) + } + + mutating func encode(_ value: T) throws { + let subdata = UnkeyedEncodedArguments() + var encoding = OptionArgumentEncoding(arguments: subdata, pathConfig: pathConfig) + encoding.codingPath = codingPath + try value.encode(to: encoding) + subdata.arguments.forEach({ appendArgument($0) }) + } + + mutating func nestedContainer( + keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer { + var container = OptionArgumentKeyedEncoding(arguments: data, pathConfig: pathConfig) + container.codingPath = codingPath + return KeyedEncodingContainer(container) + } + + mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { + var container = OptionArgumentUnkeyedEncoding(arguments: data, pathConfig: pathConfig) + container.codingPath = codingPath + return container + } + + mutating func superEncoder() -> Encoder { + var encoding = OptionArgumentEncoding(arguments: data, pathConfig: pathConfig) + encoding.codingPath = codingPath + return encoding + } +} + +struct OptionArgumentSingleValueEncoding: SingleValueEncodingContainer { + + var codingPath: [CodingKey] = [] + let data: OptionArgumentEncoding.EncodedArguments + let pathConfig: OptionArgumentEncoding.PathConfiguration? + + init(arguments: OptionArgumentEncoding.EncodedArguments, + pathConfig: OptionArgumentEncoding.PathConfiguration?) { + self.data = arguments + self.pathConfig = pathConfig + } + + mutating func encodeNil() throws { + // No-op + } + + mutating func encode(_ value: Bool) throws { + data.append(codingPath.last, value ? "1" : "0") + } + + mutating func encode(_ value: String) throws { + data.append(codingPath.last, value.doubleQuoted) + } + + mutating func encode(_ value: Double) throws { + data.append(codingPath.last, String(describing: value)) + } + + mutating func encode(_ value: Float) throws { + data.append(codingPath.last, String(describing: value)) + } + + mutating func encode(_ value: Int) throws { + data.append(codingPath.last, String(describing: value)) + } + + mutating func encode(_ value: Int8) throws { + data.append(codingPath.last, String(describing: value)) + } + + mutating func encode(_ value: Int16) throws { + data.append(codingPath.last, String(describing: value)) + } + + mutating func encode(_ value: Int32) throws { + data.append(codingPath.last, String(describing: value)) + } + + mutating func encode(_ value: Int64) throws { + data.append(codingPath.last, String(describing: value)) + } + + mutating func encode(_ value: UInt) throws { + data.append(codingPath.last, String(describing: value)) + } + + mutating func encode(_ value: UInt8) throws { + data.append(codingPath.last, String(describing: value)) + } + + mutating func encode(_ value: UInt16) throws { + data.append(codingPath.last, String(describing: value)) + } + + mutating func encode(_ value: UInt32) throws { + data.append(codingPath.last, String(describing: value)) + } + + mutating func encode(_ value: UInt64) throws { + data.append(codingPath.last, String(describing: value)) + } + + mutating func encode(_ value: T) throws { + var encoding = OptionArgumentEncoding(arguments: data, pathConfig: pathConfig) + encoding.codingPath = codingPath + try value.encode(to: encoding) + } +} diff --git a/Sources/MockingbirdCli/Interface/Commands/Encoding/OptionGroupArgumentEncoding.swift b/Sources/MockingbirdCli/Interface/Commands/Encoding/OptionGroupArgumentEncoding.swift new file mode 100644 index 00000000..8d21f827 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Commands/Encoding/OptionGroupArgumentEncoding.swift @@ -0,0 +1,126 @@ +// +// OptionGroupArgumentEncoding.swift +// MockingbirdCli +// +// Created by typealias on 12/23/21. +// + +import Foundation +import MockingbirdGenerator +import PathKit + +struct OptionGroupArgumentEncoding: Encoder { + final class EncodedArguments { + private(set) var arguments: [String] = [] + func append(_ values: [String]) { + arguments.append(contentsOf: values) + } + } + + var codingPath: [CodingKey] = [] + var userInfo: [CodingUserInfoKey: Any] = [:] + let data: EncodedArguments + let pathConfig: OptionArgumentEncoding.PathConfiguration? + + init(arguments: EncodedArguments = EncodedArguments(), + pathConfig: OptionArgumentEncoding.PathConfiguration? = nil) { + self.data = arguments + self.pathConfig = pathConfig + } + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { + var container = OptionGroupArgumentKeyedEncoding(arguments: data, pathConfig: pathConfig) + container.codingPath = codingPath + return KeyedEncodingContainer(container) + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + fatalError("Unsupported encoding container type") + } + + func singleValueContainer() -> SingleValueEncodingContainer { + fatalError("Unsupported encoding container type") + } +} + +struct OptionGroupArgumentKeyedEncoding: KeyedEncodingContainerProtocol { + + var codingPath: [CodingKey] = [] + private let data: OptionGroupArgumentEncoding.EncodedArguments + private let pathConfig: OptionArgumentEncoding.PathConfiguration? + + init(arguments: OptionGroupArgumentEncoding.EncodedArguments, + pathConfig: OptionArgumentEncoding.PathConfiguration?) { + self.data = arguments + self.pathConfig = pathConfig + } + + mutating func encodeNil(forKey key: Key) throws {} + + mutating func encode(_ value: Bool, forKey key: Key) throws {} + + mutating func encode(_ value: String, forKey key: Key) throws {} + + mutating func encode(_ value: Double, forKey key: Key) throws {} + + mutating func encode(_ value: Float, forKey key: Key) throws {} + + mutating func encode(_ value: Int, forKey key: Key) throws {} + + mutating func encode(_ value: Int8, forKey key: Key) throws {} + + mutating func encode(_ value: Int16, forKey key: Key) throws {} + + mutating func encode(_ value: Int32, forKey key: Key) throws {} + + mutating func encode(_ value: Int64, forKey key: Key) throws {} + + mutating func encode(_ value: UInt, forKey key: Key) throws {} + + mutating func encode(_ value: UInt8, forKey key: Key) throws {} + + mutating func encode(_ value: UInt16, forKey key: Key) throws {} + + mutating func encode(_ value: UInt32, forKey key: Key) throws {} + + mutating func encode(_ value: UInt64, forKey key: Key) throws {} + + mutating func encode(_ value: T, forKey key: Key) throws { + guard let argumentsEncoder = value as? EncodableArguments else { + fatalError("Unexpected value type in option group encoder") + } + + var optionEncoding = OptionArgumentEncoding(pathConfig: pathConfig) + optionEncoding.codingPath = codingPath + [key] + try argumentsEncoder.encodeOptions(to: optionEncoding) + + var flagEncoding = FlagArgumentEncoding() + flagEncoding.codingPath = codingPath + [key] + try argumentsEncoder.encodeFlags(to: flagEncoding) + + data.append(optionEncoding.data.arguments) + data.append(flagEncoding.data.arguments) + } + + mutating func nestedContainer( + keyedBy keyType: NestedKey.Type, + forKey key: Key + ) -> KeyedEncodingContainer { + fatalError("Nested option groups are not supported") + } + + mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + fatalError("Unsupported encoding container type") + } + + mutating func superEncoder() -> Encoder { + let superKey = Key(stringValue: "super")! + return superEncoder(forKey: superKey) + } + + mutating func superEncoder(forKey key: Key) -> Encoder { + var encoding = OptionGroupArgumentEncoding(arguments: data) + encoding.codingPath = codingPath + [key] + return encoding + } +} diff --git a/Sources/MockingbirdCli/Interface/Commands/Generate.swift b/Sources/MockingbirdCli/Interface/Commands/Generate.swift new file mode 100644 index 00000000..b24aa1b9 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Commands/Generate.swift @@ -0,0 +1,234 @@ +// +// Generate.swift +// MockingbirdCli +// +// Created by typealias on 8/8/21. +// + +import ArgumentParser +import Foundation +import MockingbirdGenerator +import PathKit + +extension Mockingbird { + struct Generate: ParsableCommand { + static var configuration = CommandConfiguration( + abstract: "Generate mocks for a set of targets in a project." + ) + + /// Inherited from parent command. + @OptionGroup() var globalOptions: Options + + @Option(name: [.customLong("targets"), + .customLong("target"), + .customShort("t")], + parsing: .upToNextOption, + help: "List of target names to generate mocks for.") + var targets: [String] // TODO: This will be optional in generator v2 + + @Option(name: [.customLong("project"), + .customShort("p")], + help: "Path to an Xcode project or a JSON project description.") + var project: XcodeProjPath? + + @Option(help: "The directory containing your project’s source files.") + var srcroot: DirectoryPath? // TODO: This will be deprecated in generator v2 + + @Option(name: [.customLong("outputs"), + .customLong("output"), + .customShort("o")], + parsing: .upToNextOption, + help: "List of mock output file paths for each target.") + var outputs: [SwiftFilePath] = [] // TODO: This will be optional in generator v2 + + @Option(help: "The directory containing supporting source files.") + var support: DirectoryPath? + + @Option(help: "The name of the test bundle using the mocks.") + var testbundle: TestBundleName? + + @Option(parsing: .upToNextOption, + help: "Content to add at the beginning of each generated mock file.") + var header: [String] = [] + + @Option(help: "Compilation condition to wrap all generated mocks in, e.g. 'DEBUG'.") + var condition: String? + + @Option(parsing: .upToNextOption, + help: "List of diagnostic generator warnings to enable.") + var diagnostics: [DiagnosticType] = [] + + @Option(help: "The pruning method to use on unreferenced types.") + var prune: PruningMethod? + + // MARK: Flags + + @Flag(help: "Only generate mocks for protocols.") + var onlyProtocols: Bool = false + + @Flag(help: "Disable all SwiftLint rules in generated mocks.") + var disableSwiftlint: Bool = false + + @Flag(help: "Ignore cached mock information stored on disk.") + var disableCache: Bool = false + + @Flag(help: "Only search explicitly imported modules.") + var disableRelaxedLinking: Bool = false + + struct Arguments { + let targets: [String] + let project: Path + let srcroot: Path + let outputs: [Path] + let support: Path + let testbundle: String? + let header: [String] + let condition: String? + let diagnostics: [DiagnosticType] + let prune: PruningMethod? + + let environmentProjectFilePath: Path? + let environmentSourceRoot: Path? + let environmentTargetName: String? + + let onlyProtocols: Bool + let disableSwiftlint: Bool + let disableCache: Bool + let disableRelaxedLinking: Bool + } + + func validate() throws { + try infer() + } + + @discardableResult + nonmutating func infer() throws -> Arguments { + let validProject = try validateRequiredArgument(inferArgument(project), name: "project") + let validSrcroot = try validateRequiredArgument( + srcroot ?? DirectoryPath(path: validProject.path.parent()), name: "srcroot") + let validTestBundle = try validateOptionalArgument(inferArgument(testbundle), + name: "testbundle") + let validSupportPath = support?.path ?? (validSrcroot.path + "MockingbirdSupport") + + let environment = ArgumentContext.shared.environment + let environmentProjectFilePath: Path? = { + guard validProject.path.extension == "xcodeproj" else { return validProject.path } + guard let filePath = environment["PROJECT_FILE_PATH"] else { return nil } + let path = Path(filePath) + guard path.extension == "xcodeproj" else { return nil } + return path + }() + let environmentSourceRoot: Path? = { + guard validProject.path.extension == "xcodeproj" else { return validProject.path.parent() } + guard let sourceRoot = environment["SRCROOT"] ?? environment["SOURCE_ROOT"] else { + return nil + } + return Path(sourceRoot) + }() + let environmentTargetName: String? = validTestBundle?.name + ?? environment["TARGET_NAME"] + ?? environment["TARGETNAME"] + + return Arguments( + targets: targets, + project: validProject.path, + srcroot: validSrcroot.path, + outputs: outputs.map({ $0.path }), + support: validSupportPath, + testbundle: validTestBundle?.name, + header: header, + condition: condition, + diagnostics: diagnostics, + prune: prune, + + environmentProjectFilePath: environmentProjectFilePath, + environmentSourceRoot: environmentSourceRoot, + environmentTargetName: environmentTargetName, + + onlyProtocols: onlyProtocols, + disableSwiftlint: disableSwiftlint, + disableCache: disableCache, + disableRelaxedLinking: disableRelaxedLinking + ) + } + + func run() throws { + let arguments = try infer() + let config = Generator.Configuration( + projectPath: arguments.project, + sourceRoot: arguments.srcroot, + inputTargetNames: arguments.targets, + environmentProjectFilePath: arguments.environmentProjectFilePath, + environmentSourceRoot: arguments.environmentSourceRoot, + environmentTargetName: arguments.environmentTargetName, + outputPaths: arguments.outputs, + supportPath: arguments.support, + header: arguments.header, + compilationCondition: arguments.condition, + pruningMethod: arguments.prune ?? .omit, + onlyMockProtocols: arguments.onlyProtocols, + disableSwiftlint: arguments.disableSwiftlint, + disableCache: arguments.disableCache, + disableRelaxedLinking: arguments.disableRelaxedLinking + ) + try Generator(config).generate() + } + } +} + +extension Mockingbird.Generate: EncodableArguments { + // Keep in sync with the options and flags declared in `Mockingbird.Generate`. + enum CodingKeys: String, CodingKey { + // Options + case globalOptions + case targets + case project + case srcroot + case outputs + case support + case testbundle + case header + case condition + case diagnostics + case prune + + // Flags + case onlyProtocols + case disableSwiftlint + case disableCache + case disableRelaxedLinking + } + + func encode(to encoder: Encoder) throws { + try encodeOptions(to: encoder) + try encodeFlags(to: encoder) + try encodeOptionGroups(to: encoder) + } + + func encodeOptions(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(targets, forKey: .targets) + try container.encode(project, forKey: .project) + try container.encode(srcroot, forKey: .srcroot) + try container.encode(outputs, forKey: .outputs) + try container.encode(support, forKey: .support) + try container.encode(testbundle, forKey: .testbundle) + try container.encode(header, forKey: .header) + try container.encode(condition, forKey: .condition) + try container.encode(diagnostics, forKey: .diagnostics) + try container.encode(prune, forKey: .prune) + } + + func encodeFlags(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(onlyProtocols, forKey: .onlyProtocols) + try container.encode(disableSwiftlint, forKey: .disableSwiftlint) + try container.encode(disableCache, forKey: .disableCache) + try container.encode(disableRelaxedLinking, forKey: .disableRelaxedLinking) + } + + func encodeOptionGroups(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(globalOptions, forKey: .globalOptions) + } +} diff --git a/Sources/MockingbirdCli/Interface/Commands/GenerateCommand.swift b/Sources/MockingbirdCli/Interface/Commands/GenerateCommand.swift deleted file mode 100644 index f5d59243..00000000 --- a/Sources/MockingbirdCli/Interface/Commands/GenerateCommand.swift +++ /dev/null @@ -1,123 +0,0 @@ -// -// GenerateCommand.swift -// MockingbirdCli -// -// Created by Andrew Chang on 8/23/19. -// - -import Foundation -import MockingbirdGenerator -import PathKit -import SPMUtility - -final class GenerateCommand: BaseCommand { - private enum Constants { - static var name = "generate" - static var overview = "Generate mocks for a set of targets in a project." - } - override var name: String { return Constants.name } - override var overview: String { return Constants.overview } - - private let projectPathArgument: OptionArgument - private let targetsArgument: OptionArgument<[String]> - private let targetArgument: OptionArgument<[String]> - private let sourceRootArgument: OptionArgument - private let outputsArgument: OptionArgument<[PathArgument]> - private let outputArgument: OptionArgument<[PathArgument]> - private let supportPathArgument: OptionArgument - private let testBundleArgument: OptionArgument - private let diagnosticsArgument: OptionArgument<[DiagnosticType]> - private let headerArgument: OptionArgument<[String]> - private let compilationConditionArgument: OptionArgument - private let pruningMethod: OptionArgument - - private let disableModuleImportArgument: OptionArgument - private let onlyMockProtocolsArgument: OptionArgument - private let disableSwiftlintArgument: OptionArgument - private let disableCacheArgument: OptionArgument - private let disableRelaxedLinking: OptionArgument - - required init(parser: ArgumentParser) { - let subparser = parser.add(subparser: Constants.name, overview: Constants.overview) - - self.projectPathArgument = subparser.addProjectPath() - self.targetsArgument = subparser.addTargets() - self.targetArgument = subparser.addTarget() - self.sourceRootArgument = subparser.addSourceRoot() - self.outputsArgument = subparser.addOutputs() - self.outputArgument = subparser.addOutput() - self.supportPathArgument = subparser.addSupportPath() - self.testBundleArgument = subparser.addTestBundle() - self.diagnosticsArgument = subparser.addDiagnostics() - self.headerArgument = subparser.addHeader() - self.compilationConditionArgument = subparser.addCompilationCondition() - self.pruningMethod = subparser.addPruningMethod() - - self.disableModuleImportArgument = subparser.addDisableModuleImport() - self.onlyMockProtocolsArgument = subparser.addOnlyProtocols() - self.disableSwiftlintArgument = subparser.addDisableSwiftlint() - self.disableCacheArgument = subparser.addDisableCache() - self.disableRelaxedLinking = subparser.addDisableRelaxedLinking() - - super.init(parser: subparser) - } - - override func run(with arguments: ArgumentParser.Result, - environment: [String: String], - workingPath: Path) throws { - try super.run(with: arguments, environment: environment, workingPath: workingPath) - DiagnosticType.enabled.value = Set(arguments.get(diagnosticsArgument) ?? []) - - let projectPath = try arguments.getProjectPath(using: projectPathArgument, - environment: environment, - workingPath: workingPath) - let sourceRoot = arguments.getSourceRoot(using: sourceRootArgument, - environment: environment, - projectPath: projectPath) - let targets = try arguments.getTargets(using: targetsArgument, - convenienceArgument: targetArgument, - environment: environment) - let outputs = arguments.getOutputs(using: outputsArgument, - convenienceArgument: outputArgument) - let supportPath = try arguments.getSupportPath(using: supportPathArgument, - sourceRoot: sourceRoot) - - var environmentProjectFilePath: Path? { - guard projectPath.extension == "xcodeproj" else { return projectPath } - guard let filePath = environment["PROJECT_FILE_PATH"] else { return nil } - let path = Path(filePath) - guard path.extension == "xcodeproj" else { return nil } - return path - } - var environmentSourceRoot: Path? { - guard projectPath.extension == "xcodeproj" else { return projectPath.parent() } - guard let sourceRoot = environment["SRCROOT"] ?? environment["SOURCE_ROOT"] else { - return nil - } - let path = Path(sourceRoot) - return path - } - let environmentTargetName = arguments.get(testBundleArgument) - ?? environment["TARGET_NAME"] ?? environment["TARGETNAME"] - - let config = Generator.Configuration( - projectPath: projectPath, - sourceRoot: sourceRoot, - inputTargetNames: targets, - environmentProjectFilePath: environmentProjectFilePath, - environmentSourceRoot: environmentSourceRoot, - environmentTargetName: environmentTargetName, - outputPaths: outputs, - supportPath: supportPath, - header: arguments.get(headerArgument), - compilationCondition: arguments.get(compilationConditionArgument), - pruningMethod: arguments.get(pruningMethod) ?? .omit, - shouldImportModule: arguments.get(disableModuleImportArgument) != true, - onlyMockProtocols: arguments.get(onlyMockProtocolsArgument) == true, - disableSwiftlint: arguments.get(disableSwiftlintArgument) == true, - disableCache: arguments.get(disableCacheArgument) == true, - disableRelaxedLinking: arguments.get(disableRelaxedLinking) == true - ) - try Generator(config).generate() - } -} diff --git a/Sources/MockingbirdCli/Interface/Commands/InstallCommand.swift b/Sources/MockingbirdCli/Interface/Commands/InstallCommand.swift deleted file mode 100644 index d8b9a96d..00000000 --- a/Sources/MockingbirdCli/Interface/Commands/InstallCommand.swift +++ /dev/null @@ -1,145 +0,0 @@ -// -// InstallCommand.swift -// MockingbirdCli -// -// Created by Andrew Chang on 8/23/19. -// - -import Foundation -import MockingbirdGenerator -import PathKit -import SPMUtility - -class ConfigureCommand: InstallCommand { - private enum Constants { - static let name = "configure" - static let overview = "Configure a test target to use mocks (alias of 'install')." - } - override var name: String { return Constants.name } - override var overview: String { return Constants.overview } - - required init(parser: ArgumentParser) { - super.init(parser: parser, name: Constants.name, overview: Constants.overview) - } - - required init(parser: ArgumentParser, name: String, overview: String) { - super.init(parser: parser, name: name, overview: overview) - } -} - -class InstallCommand: BaseCommand, AliasableCommand { - private enum Constants { - static let name = "install" - static let overview = "Configure a test target to use mocks." - - static let launcherEnvironmentKey = "MKB_LAUNCHER" - } - override var name: String { return Constants.name } - override var overview: String { return Constants.overview } - - private let projectPathArgument: OptionArgument - private let sourceTargetsArgument: OptionArgument<[String]> - private let sourceTargetArgument: OptionArgument<[String]> - private let destinationTargetArgument: OptionArgument - private let sourceRootArgument: OptionArgument - private let outputsArgument: OptionArgument<[PathArgument]> - private let outputArgument: OptionArgument<[PathArgument]> - private let supportPathArgument: OptionArgument - private let headerArgument: OptionArgument<[String]> - private let compilationConditionArgument: OptionArgument - private let diagnosticsArgument: OptionArgument<[DiagnosticType]> - private let logLevelArgument: OptionArgument - private let pruningMethod: OptionArgument - - private let ignoreExistingRunScriptArgument: OptionArgument - private let asynchronousGenerationArgument: OptionArgument - private let onlyMockProtocolsArgument: OptionArgument - private let disableSwiftlintArgument: OptionArgument - private let disableCacheArgument: OptionArgument - private let disableRelaxedLinking: OptionArgument - - required convenience init(parser: ArgumentParser) { - self.init(parser: parser, name: Constants.name, overview: Constants.overview) - } - - required init(parser: ArgumentParser, name: String, overview: String) { - let subparser = parser.add(subparser: name, overview: overview) - - self.projectPathArgument = subparser.addProjectPath() - self.sourceTargetsArgument = subparser.addSourceTargets() - self.sourceTargetArgument = subparser.addSourceTarget() - self.destinationTargetArgument = subparser.addDestinationTarget() - self.sourceRootArgument = subparser.addSourceRoot() - self.outputsArgument = subparser.addOutputs() - self.outputArgument = subparser.addOutput() - self.supportPathArgument = subparser.addSupportPath() - self.headerArgument = subparser.addHeader() - self.compilationConditionArgument = subparser.addCompilationCondition() - self.diagnosticsArgument = subparser.addDiagnostics() - self.logLevelArgument = subparser.addInstallerLogLevel() - self.pruningMethod = subparser.addPruningMethod() - - self.ignoreExistingRunScriptArgument = subparser.addIgnoreExistingRunScript() - self.asynchronousGenerationArgument = subparser.addAynchronousGeneration() - self.onlyMockProtocolsArgument = subparser.addOnlyProtocols() - self.disableSwiftlintArgument = subparser.addDisableSwiftlint() - self.disableCacheArgument = subparser.addDisableCache() - self.disableRelaxedLinking = subparser.addDisableRelaxedLinking() - - super.init(parser: subparser) - } - - override func run(with arguments: ArgumentParser.Result, - environment: [String: String], - workingPath: Path) throws { - try super.run(with: arguments, environment: environment, workingPath: workingPath) - - let projectPath = try arguments.getProjectPath(using: projectPathArgument, - environment: environment, - workingPath: workingPath) - let sourceRoot = arguments.getSourceRoot(using: sourceRootArgument, - environment: environment, - projectPath: projectPath) - let sourceTargets = try arguments.getSourceTargets(using: sourceTargetsArgument, - convenienceArgument: sourceTargetArgument) - let destinationTarget = try arguments.getDestinationTarget(using: destinationTargetArgument) - let outputs = arguments.getOutputs(using: outputsArgument, convenienceArgument: outputArgument) - let supportPath = try arguments.getSupportPath(using: supportPathArgument, - sourceRoot: sourceRoot) - - let launcherPath = environment[Constants.launcherEnvironmentKey] - let realBinaryPath = CommandLine.arguments[0] - let cliPath = Path(launcherPath ?? realBinaryPath) - - let config = Installer.InstallConfiguration( - projectPath: projectPath, - sourceRoot: sourceRoot, - sourceTargetNames: sourceTargets, - destinationTargetName: destinationTarget, - outputPaths: outputs, - supportPath: supportPath, - cliPath: cliPath, - header: arguments.get(headerArgument), - compilationCondition: arguments.get(compilationConditionArgument), - diagnostics: arguments.get(diagnosticsArgument), - logLevel: arguments.get(logLevelArgument), - pruningMethod: arguments.get(pruningMethod), - ignoreExisting: arguments.get(ignoreExistingRunScriptArgument) == true, - asynchronousGeneration: arguments.get(asynchronousGenerationArgument) == true, - onlyMockProtocols: arguments.get(onlyMockProtocolsArgument) == true, - disableSwiftlint: arguments.get(disableSwiftlintArgument) == true, - disableCache: arguments.get(disableCacheArgument) == true, - disableRelaxedLinking: arguments.get(disableRelaxedLinking) == true - ) - try Installer.install(using: config) - logInfo("Installed Mockingbird to \(destinationTarget.singleQuoted) in \(projectPath)") - - // Warn users that haven't added supporting source files. - guard supportPath == nil else { return } - logInfo(""" - Please add starter supporting source files for basic compatibility with system frameworks. - $ mockingbird download starter-pack - See https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files for more information. - """) - } -} diff --git a/Sources/MockingbirdCli/Interface/Commands/TestbedCommand.swift b/Sources/MockingbirdCli/Interface/Commands/TestbedCommand.swift deleted file mode 100644 index 858d6d7a..00000000 --- a/Sources/MockingbirdCli/Interface/Commands/TestbedCommand.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// TestbedCommand.swift -// MockingbirdCli -// -// Created by Andrew Chang on 9/7/19. -// - -import Foundation -import MockingbirdGenerator -import PathKit -import SPMUtility - -final class TestbedCommand: BaseCommand { - private enum Constants { - static let name = "testbed" - static let overview = "Generate source files for benchmarking Mockingbird." - } - override var name: String { return Constants.name } - override var overview: String { return Constants.overview } - - private let outputArgument: OptionArgument - private let countArgument: OptionArgument - - required init(parser: ArgumentParser) { - let subparser = parser.add(subparser: Constants.name, overview: Constants.overview) - self.outputArgument = subparser.addMetagenerateOutput() - self.countArgument = subparser.addMetagenerateCount() - super.init(parser: subparser) - } - - override func run(with arguments: ArgumentParser.Result, - environment: [String: String], - workingPath: Path) throws { - try super.run(with: arguments, environment: environment, workingPath: workingPath) - - let outputDirectory = try arguments.getOutputDirectory(using: outputArgument) - let count = try arguments.getCount(using: countArgument) ?? 1000 - for i in 0.. 1 ? "s" : "") to \(outputDirectory.absolute())") - } - - func generateSourceFile(to directory: Path, index: Int) throws { - let contents = """ - import Foundation - - protocol GeneratedGrandparentProtocol\(index) { - var grandparentReadOnlyInstanceVariable: Bool { get } - var grandparentInstanceVariable: Bool { get set } - func grandparentTrivialInstanceMethod() - func grandparentParameterizedInstanceMethod(param1: Bool, _ param2: Int) - func grandparentParameterizedReturningInstanceMethod(param1: Bool, _ param2: Int) -> String - } - - protocol GeneratedParentProtocol\(index): GeneratedGrandparentProtocol\(index) { - var parentReadOnlyInstanceVariable: Bool { get } - var parentInstanceVariable: Bool { get set } - func parentTrivialInstanceMethod() - func parentParameterizedInstanceMethod(param1: Bool, _ param2: Int) - func parentParameterizedReturningInstanceMethod(param1: Bool, _ param2: Int) -> String - } - - protocol GeneratedChildProtocol\(index): GeneratedParentProtocol\(index) { - var childReadOnlyInstanceVariable: Bool { get } - var childInstanceVariable: Bool { get set } - func childTrivialInstanceMethod() - func childParameterizedInstanceMethod(param1: Bool, _ param2: Int) - func childParameterizedReturningInstanceMethod(param1: Bool, _ param2: Int) -> String - } - - """ - - let filePath = directory + "GeneratedProtocols\(index).generated.swift" - try filePath.write(contents, encoding: .utf8) - } -} diff --git a/Sources/MockingbirdCli/Interface/Commands/UninstallCommand.swift b/Sources/MockingbirdCli/Interface/Commands/UninstallCommand.swift deleted file mode 100644 index 35028f8d..00000000 --- a/Sources/MockingbirdCli/Interface/Commands/UninstallCommand.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// UninstallCommand.swift -// MockingbirdCli -// -// Created by Andrew Chang on 8/23/19. -// - -import Foundation -import MockingbirdGenerator -import PathKit -import SPMUtility - -final class UninstallCommand: BaseCommand { - private enum Constants { - static let name = "uninstall" - static let overview = "Remove Mockingbird from a test target." - } - override var name: String { return Constants.name } - override var overview: String { return Constants.overview } - - private let projectPathArgument: OptionArgument - private let targetsArgument: OptionArgument<[String]> - private let targetArgument: OptionArgument<[String]> - private let sourceRootArgument: OptionArgument - - required init(parser: ArgumentParser) { - let subparser = parser.add(subparser: Constants.name, overview: Constants.overview) - - self.projectPathArgument = subparser.addProjectPath() - self.targetsArgument = subparser.addTargets() - self.targetArgument = subparser.addTarget() - self.sourceRootArgument = subparser.addSourceRoot() - - super.init(parser: subparser) - } - - override func run(with arguments: ArgumentParser.Result, - environment: [String: String], - workingPath: Path) throws { - try super.run(with: arguments, environment: environment, workingPath: workingPath) - - let projectPath = try arguments.getProjectPath(using: projectPathArgument, - environment: environment, - workingPath: workingPath) - let sourceRoot = arguments.getSourceRoot(using: sourceRootArgument, - environment: environment, - projectPath: projectPath) - let targets = try arguments.getTargets(using: targetsArgument, - convenienceArgument: targetArgument, - environment: environment) - - let config = Installer.UninstallConfiguration( - projectPath: projectPath, - sourceRoot: sourceRoot, - targetNames: targets - ) - try Installer.uninstall(using: config) - logInfo("Uninstalled Mockingbird from \(targets.map({ "`\($0)`" }).joined(separator: ", ")) in \(projectPath)") - } -} diff --git a/Sources/MockingbirdCli/Interface/Commands/Version.swift b/Sources/MockingbirdCli/Interface/Commands/Version.swift new file mode 100644 index 00000000..0d2e2b6b --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Commands/Version.swift @@ -0,0 +1,24 @@ +// +// Version.swift +// MockingbirdCli +// +// Created by typealias on 12/20/21. +// + +import ArgumentParser +import Foundation +import MockingbirdGenerator +import PathKit + +extension Mockingbird { + struct Version: ParsableCommand { + static var configuration = CommandConfiguration( + abstract: "Show the version.", + shouldDisplay: false + ) + + func run() throws { + logInfo("\(mockingbirdVersion)") + } + } +} diff --git a/Sources/MockingbirdCli/Interface/Commands/VersionCommand.swift b/Sources/MockingbirdCli/Interface/Commands/VersionCommand.swift deleted file mode 100644 index c21b5a27..00000000 --- a/Sources/MockingbirdCli/Interface/Commands/VersionCommand.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// VersionCommand.swift -// MockingbirdCli -// -// Created by Andrew Chang on 9/2/19. -// - -import Foundation -import MockingbirdGenerator -import PathKit -import SPMUtility - -final class VersionCommand: BaseCommand { - private enum Constants { - static let name = "version" - static let overview = "Returns the current CLI generator version." - } - override var name: String { return Constants.name } - override var overview: String { return Constants.overview } - - required init(parser: ArgumentParser) { - let subparser = parser.add(subparser: Constants.name, overview: Constants.overview) - super.init(parser: subparser) - } - - override func run(with arguments: ArgumentParser.Result, - environment: [String: String], - workingPath: Path) throws { - try super.run(with: arguments, environment: environment, workingPath: workingPath) - logInfo("\(mockingbirdVersion)") - } -} diff --git a/Sources/MockingbirdCli/Interface/Handlers/Downloader.swift b/Sources/MockingbirdCli/Interface/Handlers/Downloader.swift new file mode 100644 index 00000000..01864102 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Handlers/Downloader.swift @@ -0,0 +1,146 @@ +// +// Downloader.swift +// MockingbirdCli +// +// Created by typealias on 8/8/21. +// + +import ArgumentParser +import Foundation +import MockingbirdGenerator +import PathKit +import ZIPFoundation + +enum AssetBundleType: String, ExpressibleByArgument, CustomStringConvertible { + case starterPack = "starter-pack" + + var description: String { + switch self { + case .starterPack: return "Starter supporting source files." + } + } + + var fileName: String { + switch self { + case .starterPack: return "MockingbirdSupport.zip" + } + } + + func getURL(baseURL: URL) -> URL? { + return URL(string: "\(baseURL.absoluteString)/\(mockingbirdVersion)/\(fileName)") + } +} + +struct Downloader { + struct Configuration { + let assetBundleType: AssetBundleType + let outputPath: Path + let baseURL: URL + let overwrite: Bool + let urlSession: URLSession + + init(assetBundleType: AssetBundleType, + outputPath: Path, + baseURL: URL, + overwrite: Bool = false, + urlSession: URLSession = URLSession(configuration: .ephemeral)) { + self.assetBundleType = assetBundleType + self.outputPath = outputPath + self.baseURL = baseURL + self.overwrite = overwrite + self.urlSession = urlSession + } + } + + enum Error: LocalizedError { + case validationFailed(_ message: String) + case downloadFailed(_ message: String) + case corruptBundle(_ message: String) + + var errorDescription: String? { + switch self { + case .validationFailed(let message), + .downloadFailed(let message), + .corruptBundle(let message): + return message + } + } + } + + enum Constants { + static let excludedAssetRootDirectories: Set = [ + "__MACOSX", + ] + static let excludedAssetFileNames: Set = [ + ".DS_Store", + ] + } + + let config: Configuration + + init(config: Configuration) { + self.config = config + } + + func download() throws { + guard let downloadURL = config.assetBundleType.getURL(baseURL: config.baseURL) else { + throw Error.validationFailed("Invalid base URL \(config.baseURL)") + } + switch downloadAssetBundle(at: downloadURL) { + case .success(let fileURL): + guard let archive = Archive(url: fileURL, accessMode: .read) else { + throw Error.corruptBundle("Downloaded asset bundle is corrupt") + } + try extractAssetBundle(archive, to: config.outputPath) + case .failure(let error): + throw error + } + } + + private func downloadAssetBundle(at url: URL) -> Result { + let semaphore = DispatchSemaphore(value: 0) + var result: Result? + config.urlSession.downloadTask(with: url) { (url, _, error) in + if let fileURL = url { + result = .success(fileURL) + } else if let error = error { + result = .failure(.downloadFailed(error.localizedDescription)) + } else { + result = .failure(.downloadFailed("Missing path to downloaded asset bundle")) + } + semaphore.signal() + }.resume() + semaphore.wait() + return result! + } + + private func extractAssetBundle(_ archive: Archive, to path: Path) throws { + let basePath = path.absolute() + for entry in archive { + let entryPath = Path(entry.path) + guard + let firstComponent = entryPath.components.first, + !Constants.excludedAssetRootDirectories.contains(firstComponent) + else { + log("Skipping asset bundle entry due to excluded root directory at \(entryPath)") + continue + } + guard !Constants.excludedAssetFileNames.contains(entryPath.lastComponent) else { + log("Skipping asset bundle entry due to excluded file name at \(entryPath)") + continue + } + + let destinationPath = basePath + entryPath + if destinationPath.exists { + if !config.overwrite { + log("Not overwriting existing contents at \(entryPath)") + continue + } else { + log("Overwriting existing contents at \(entryPath)") + } + } + + _ = try archive.extract(entry, to: destinationPath.url) + } + } +} diff --git a/Sources/MockingbirdCli/Interface/Generator+Pipeline.swift b/Sources/MockingbirdCli/Interface/Handlers/Generator+Pipeline.swift similarity index 99% rename from Sources/MockingbirdCli/Interface/Generator+Pipeline.swift rename to Sources/MockingbirdCli/Interface/Handlers/Generator+Pipeline.swift index b7bce55b..9d610c82 100644 --- a/Sources/MockingbirdCli/Interface/Generator+Pipeline.swift +++ b/Sources/MockingbirdCli/Interface/Handlers/Generator+Pipeline.swift @@ -92,7 +92,6 @@ extension Generator { outputPath: outputPath, header: config.header, compilationCondition: config.compilationCondition, - shouldImportModule: config.shouldImportModule, onlyMockProtocols: config.onlyMockProtocols, disableSwiftlint: config.disableSwiftlint, pruningMethod: config.pruningMethod diff --git a/Sources/MockingbirdCli/Interface/Generator+PruningPipeline.swift b/Sources/MockingbirdCli/Interface/Handlers/Generator+PruningPipeline.swift similarity index 100% rename from Sources/MockingbirdCli/Interface/Generator+PruningPipeline.swift rename to Sources/MockingbirdCli/Interface/Handlers/Generator+PruningPipeline.swift diff --git a/Sources/MockingbirdCli/Interface/Generator.swift b/Sources/MockingbirdCli/Interface/Handlers/Generator.swift similarity index 92% rename from Sources/MockingbirdCli/Interface/Generator.swift rename to Sources/MockingbirdCli/Interface/Handlers/Generator.swift index 15c1fb67..889f5b10 100644 --- a/Sources/MockingbirdCli/Interface/Generator.swift +++ b/Sources/MockingbirdCli/Interface/Handlers/Generator.swift @@ -9,7 +9,6 @@ import Foundation import MockingbirdGenerator import PathKit -import SPMUtility import XcodeProj import os.log @@ -23,18 +22,30 @@ class Generator { let environmentTargetName: String? let outputPaths: [Path]? let supportPath: Path? - let header: [String]? + let header: [String] let compilationCondition: String? let pruningMethod: PruningMethod - let shouldImportModule: Bool let onlyMockProtocols: Bool let disableSwiftlint: Bool let disableCache: Bool let disableRelaxedLinking: Bool } - struct MalformedConfiguration: Error, CustomStringConvertible { - let description: String + enum Error: LocalizedError { + case mismatchedInputsAndOutputs(inputCount: Int, outputCount: Int) + case invalidOutputPath(path: Path) + case invalidInputTarget(name: String) + + var errorDescription: String? { + switch self { + case let .mismatchedInputsAndOutputs(inputCount, outputCount): + return "Mismatched number of input targets (\(inputCount)) and output file paths (\(outputCount))" + case let .invalidOutputPath(path): + return "Invalid output file path \(path)" + case let .invalidInputTarget(name): + return "The target '\(name)' does not exist in the project" + } + } } enum Constants { @@ -137,9 +148,8 @@ class Generator { func generate() throws { guard config.outputPaths == nil || config.inputTargetNames.count == config.outputPaths?.count else { - throw MalformedConfiguration( - description: "Number of input targets does not match the number of output file paths" - ) + throw Error.mismatchedInputsAndOutputs(inputCount: config.inputTargetNames.count, + outputCount: config.outputPaths?.count ?? 0) } if config.supportPath == nil { @@ -197,9 +207,7 @@ class Generator { var pipelines = [Pipeline]() for (target, outputPath) in zip(targets, outputPaths) { guard !outputPath.isDirectory else { - throw MalformedConfiguration( - description: "Output file path points to a directory: \(outputPath)" - ) + throw Error.invalidOutputPath(path: outputPath) } try pipelines.append(Pipeline(inputTarget: target, outputPath: outputPath, @@ -287,9 +295,7 @@ class Generator { } guard let target = targets.first else { - throw MalformedConfiguration( - description: "Unable to find target named \(targetName.singleQuoted)" - ) + throw Error.invalidInputTarget(name: targetName) } return target } diff --git a/Sources/MockingbirdCli/Interface/Handlers/Installer.swift b/Sources/MockingbirdCli/Interface/Handlers/Installer.swift new file mode 100644 index 00000000..851ff884 --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Handlers/Installer.swift @@ -0,0 +1,281 @@ +// +// Installer.swift +// MockingbirdCli +// +// Created by Andrew Chang on 8/10/19. +// Copyright © 2019 Bird Rides, Inc. All rights reserved. +// + +import Foundation +import MockingbirdGenerator +import PathKit +import XcodeProj + +struct Installer { + struct Configuration { + let projectPath: Path + let sourceProjectPath: Path + let testTargetName: String + let cliPath: Path + let sourceRoot: Path + let sourceTargetNames: [String] + let outputPaths: [Path] + let generatorOptions: [String] + let overwrite: Bool + } + + enum Failure: LocalizedError { + case validationError(_ message: String) + case invalidProjectConfiguration(_ message: String) + case invalidTargetConfiguration(_ message: String) + case unableToModifyProject(_ message: String) + + var errorDescription: String? { + switch self { + case .validationError(let message), + .invalidProjectConfiguration(let message), + .invalidTargetConfiguration(let message), + .unableToModifyProject(let message): + return message + } + } + } + + enum Constants { + /// Name of the primary build phase for generating mocks. + static let buildPhaseName = "Generate Mockingbird Mocks" + /// Name of the legacy build phase for working around Xcode build caching. + static let cleanMocksBuildPhaseName = "Clean Mockingbird Mocks" + /// Name of the Xcode project group containing generated files. + static let sourceGroupName = "Generated Mocks" + } + + let config: Configuration + + init(config: Configuration) throws { + self.config = config + } + + // MARK: - Install + + func install() throws { + let testProject = try XcodeProj(path: config.projectPath) + let sourceProject: XcodeProj = try { + guard config.sourceProjectPath != config.projectPath else { return testProject } + return try XcodeProj(path: config.sourceProjectPath) + }() + + guard let rootGroup = try testProject.pbxproj.rootGroup() else { + throw Failure.invalidProjectConfiguration( + "Missing root group in Xcode project at \(config.projectPath.abbreviate())") + } + + let testTarget = try findTarget(name: config.testTargetName, + project: testProject, + testTarget: true) + let sourceTargets = try config.sourceTargetNames.map({ targetName in + try findTarget(name: targetName, project: sourceProject, testTarget: false) + }) + + if config.overwrite { + try uninstall(from: testProject, target: testTarget, rootGroup: rootGroup) + } + + let sourceGroup = try createSourceGroup(name: Constants.sourceGroupName, rootGroup: rootGroup) + + guard let sourcesBuildPhase = try testTarget.sourcesBuildPhase(), + let buildPhaseIndex = testTarget.buildPhases.firstIndex(of: sourcesBuildPhase) else { + throw Failure.invalidTargetConfiguration( + "Target \(singleQuoted: config.testTargetName) has no Compile Sources build phase") + } + + // TODO: Migrate to a single output file with generator v2. + let outputPaths = !config.outputPaths.isEmpty ? config.outputPaths : + sourceTargets.map({ sourceTarget in + Generator.defaultOutputPath(for: .pbxTarget(sourceTarget), + testTarget: .pbxTarget(testTarget), + sourceRoot: config.sourceRoot, + environment: { testProject.implicitBuildEnvironment }) + }) + guard outputPaths.count == sourceTargets.count else { + throw Failure.validationError( + "The number of output paths does not equal the number of targets") + } + + // Add build phase to target and project. + let buildPhase = createBuildPhase(outputPaths: outputPaths) + testTarget.buildPhases.insert(buildPhase, at: buildPhaseIndex) + testProject.pbxproj.add(object: buildPhase) + + // Track the generated mock files. + for outputPath in outputPaths { + try addSourceFilePath(outputPath, + target: testTarget, + sourceGroup: sourceGroup, + xcodeproj: testProject) + } + + try testProject.writePBXProj(path: config.projectPath, outputSettings: PBXOutputSettings()) + } + + private func findTarget(name: String, + project: XcodeProj, + testTarget: Bool) throws -> PBXTarget { + let targets = project.pbxproj.targets(named: name) + if targets.count > 1 { + logWarning("Found multiple targets named \(singleQuoted: config.testTargetName)") + } + + let validTargets = { + project.pbxproj.nativeTargets.compactMap({ target -> String? in + guard let productType = target.productType, + testTarget ? productType.isSwiftUnitTestBundle : !productType.isTestBundle + else { return nil } + return target.name + }) + } + + guard let target = targets.first else { + let targetType = testTarget ? "test target" : "target" + throw Failure.validationError( + "Cannot find the \(targetType) \(singleQuoted: name). " + + "Valid targets: \(separated: validTargets())") + } + + guard let productType = target.productType else { + throw Failure.invalidTargetConfiguration( + "Target \(singleQuoted: name) has an unknown product type. " + + "Valid targets: \(separated: validTargets())") + } + + if testTarget && !productType.isSwiftUnitTestBundle { + throw Failure.validationError( + "Expected a test target but \(singleQuoted: name) is not a unit test bundle. " + + "Valid targets: \(separated: validTargets())") + } + + if !testTarget && productType.isTestBundle { + throw Failure.validationError( + "Expected a source target but \(singleQuoted: name) is a unit test bundle. " + + "Valid targets: \(separated: validTargets())") + } + + return target + } + + private func createSourceGroup(name: String, rootGroup: PBXGroup) throws -> PBXGroup { + if let previousGroup = rootGroup.group(named: name) { + return previousGroup + } + + if let newGroup = try rootGroup.addGroup(named: Constants.sourceGroupName, + options: [.withoutFolder]).first { + return newGroup + } + + throw Failure.unableToModifyProject( + "Cannot create \(singleQuoted: Constants.sourceGroupName) Xcode project group") + } + + private func addSourceFilePath(_ outputPath: Path, + target: PBXTarget, + sourceGroup: PBXGroup, + xcodeproj: XcodeProj) throws { + guard try target.sourcesBuildPhase()?.files?.contains(where: { buildFile in + try buildFile.file?.fullPath(sourceRoot: config.sourceRoot) == outputPath + }) != true else { + log("Target \(singleQuoted: target.name) already compiles \(outputPath.absolute())") + return + } + + let fileReference: PBXFileReference = try { + // Need to check if the file already exists, or XcodeProj will throw an invalidGroupPath error + // when re-running the installer. + if let existingReference = sourceGroup.file(named: outputPath.lastComponent), + (try? existingReference.fullPath(sourceRoot: config.sourceRoot)) == outputPath { + log("Using existing output mock file reference at \(outputPath)") + return existingReference + } + + log("Creating a new output mock file reference at \(outputPath)") + return try sourceGroup.addFile(at: outputPath, + sourceRoot: config.sourceRoot, + override: false, + validatePresence: false) + }() + + _ = try target.sourcesBuildPhase()?.add(file: fileReference) + xcodeproj.pbxproj.add(object: fileReference) + } + + private func createBuildPhase(outputPaths: [Path]) -> PBXShellScriptBuildPhase { + let cliPath = config.cliPath.abbreviated(root: config.sourceRoot, variable: "SRCROOT") + var options = config.generatorOptions + // TODO: Remove this for generator v2. Only needed for backwards compatibility. + if config.outputPaths.isEmpty { + options += ["--outputs"] + outputPaths.map({ path in + path.abbreviated(root: config.sourceRoot, variable: "SRCROOT").doubleQuoted + }) + } + return PBXShellScriptBuildPhase( + name: Constants.buildPhaseName, + outputPaths: outputPaths.map({ path in + path.abbreviated(root: config.sourceRoot, variable: "SRCROOT", style: .make) + }), + shellScript: """ + set -eu + + # Prevent Xcode 13 from running this script while indexing. + if [[ "${ACTION}" == "indexbuild" ]]; then + exit 0 + fi + + \(cliPath) generate \(options.joined(separator: " ")) + """, + alwaysOutOfDate: true) + } + + // MARK: - Uninstall + + private func uninstall(from xcodeproj: XcodeProj, target: PBXTarget, rootGroup: PBXGroup) throws { + let sourceGroup = rootGroup.group(named: Constants.sourceGroupName) + + for buildPhase in target.buildPhases { + guard let shellScriptBuildPhase = buildPhase as? PBXShellScriptBuildPhase else { + continue + } + + guard let name = buildPhase.name(), + name == Constants.buildPhaseName || + name == Constants.cleanMocksBuildPhaseName else { + continue + } + + log("Removing \(singleQuoted: name) build phase from \(singleQuoted: target.name)") + + // Build phase must be removed from both the target and the project. + xcodeproj.pbxproj.delete(object: buildPhase) + target.buildPhases.removeAll(where: { $0 === buildPhase }) + + // Remove generated output files from the Compile Sources build phase and the project. + let generatedFilePaths = Set(shellScriptBuildPhase.outputPaths.map({ path in + path.replacingOccurrences(of: SubstitutionStyle.make.wrap("SRCROOT"), + with: config.sourceRoot.absolute().string) + })) + + try target.sourcesBuildPhase()?.files?.removeAll(where: { buildFile in + guard let fullPath = try buildFile.file?.fullPath(sourceRoot: config.sourceRoot) else { + return false + } + return generatedFilePaths.contains(fullPath.string) + }) + + try sourceGroup?.children.removeAll(where: { child in + guard let fullPath = try child.fullPath(sourceRoot: config.sourceRoot) else { + return false + } + return generatedFilePaths.contains(fullPath.string) + }) + } + } +} diff --git a/Sources/MockingbirdCli/Interface/Installer.swift b/Sources/MockingbirdCli/Interface/Installer.swift deleted file mode 100644 index 54473972..00000000 --- a/Sources/MockingbirdCli/Interface/Installer.swift +++ /dev/null @@ -1,396 +0,0 @@ -// -// Installer.swift -// MockingbirdCli -// -// Created by Andrew Chang on 8/10/19. -// Copyright © 2019 Bird Rides, Inc. All rights reserved. -// - -// swiftlint:disable leading_whitespace - -import Foundation -import MockingbirdGenerator -import PathKit -import XcodeProj - -class Installer { - struct InstallConfiguration { - let projectPath: Path - let sourceRoot: Path - let sourceTargetNames: [String] - let destinationTargetName: String - let outputPaths: [Path]? - let supportPath: Path? - let cliPath: Path - let header: [String]? - let compilationCondition: String? - let diagnostics: [DiagnosticType]? - let logLevel: LogLevel? - let pruningMethod: PruningMethod? - let ignoreExisting: Bool - let asynchronousGeneration: Bool - let onlyMockProtocols: Bool - let disableSwiftlint: Bool - let disableCache: Bool - let disableRelaxedLinking: Bool - } - - struct UninstallConfiguration { - let projectPath: Path - let sourceRoot: Path - let targetNames: [String] - } - - enum Failure: LocalizedError { - case malformedConfiguration(description: String) - case invalidXcodeProjectConfiguration(description: String) - - var errorDescription: String? { - switch self { - case .malformedConfiguration(let description): - return "Malformed configuration - \(description)" - case .invalidXcodeProjectConfiguration(let description): - return "Invalid Xcode project configuration - \(description)" - } - } - } - - private enum Constants { - /// The name of the build phase for generating mocks. - static let buildPhaseName = "Generate Mockingbird Mocks" - /// The name of the build phase for forcing Xcode to generate mocks before each build. - static let cleanBuildPhaseName = "Clean Mockingbird Mocks" - /// The name of the Xcode project file group containing all generated mock file references. - static let sourceGroupName = "Generated Mocks" - } - - - // MARK: - Install - - static func install(using config: InstallConfiguration) throws { - guard config.outputPaths == nil || config.sourceTargetNames.count == config.outputPaths?.count else { - throw Failure.malformedConfiguration(description: "Number source targets does not match the number of output file paths") - } - - let xcodeproj = try XcodeProj(path: config.projectPath) - - guard let rootGroup = try? xcodeproj.pbxproj.rootGroup() else { - throw Failure.invalidXcodeProjectConfiguration( - description: "Xcode project does not have a root file group" - ) - } - - // Validate destination target. - let targetName = config.destinationTargetName - let destinationTargets = xcodeproj.pbxproj.targets(named: targetName) - if destinationTargets.count > 1 { - logWarning("Found multiple targets named \(targetName.singleQuoted), using the first one") - } - guard let target = destinationTargets.first else { - throw Failure.malformedConfiguration( - description: "Unable to find a target named \(targetName.singleQuoted)" - ) - } - if target.productType?.isTestBundle != true { - logWarning("Installing to target `\(targetName)` which is not a test target") - } - - // Validate source targets. - let sourceTargets = try getSourceTargets(for: config, xcodeproj: xcodeproj) - - // Create a "Generated Mocks" source group to store all mocks under. - let sourceGroup: PBXGroup - if let existingSourceGroup = rootGroup.group(named: Constants.sourceGroupName) { - sourceGroup = existingSourceGroup - } else { - guard let addedGroup = try xcodeproj.pbxproj - .rootGroup()? - .addGroup(named: Constants.sourceGroupName, options: [.withoutFolder]).first else { - throw Failure.malformedConfiguration( - description: "Unable to create top-level 'Generated Mocks' Xcode project file group" - ) - } - sourceGroup = addedGroup - } - - // Check if there's an existing installation that should be overridden or if we should abort. - if !config.ignoreExisting { // Cleanup past installations. - try uninstall(from: xcodeproj, target: target, sourceRoot: config.sourceRoot) - } else { - guard !target.buildPhases.contains(where: { $0.name() == Constants.buildPhaseName }) else { - // Build phase is already added. - log("Ignoring existing Mockingbird build phase in target \(target.name.singleQuoted)") - return - } - } - - // Validate that the destination target has a compile sources phase. - guard let sourcesBuildPhase = try target.sourcesBuildPhase(), - let buildPhaseIndex = target.buildPhases.firstIndex(of: sourcesBuildPhase) else { - throw Failure.malformedConfiguration( - description: "Target \(targetName.singleQuoted) does not have a compile sources phase" - ) - } - - // Create fixed output paths for each source target. - let getBuildEnvironment = { return xcodeproj.implicitBuildEnvironment } - let outputPaths = config.outputPaths ?? sourceTargets.map({ - Generator.defaultOutputPath(for: .pbxTarget($0), - testTarget: .pbxTarget(target), - sourceRoot: config.sourceRoot, - environment: getBuildEnvironment) - }) - - // Add build phase reference to project. - let cacheBreakerPath = Path("/tmp/Mockingbird-\(UUID().uuidString)") - let buildPhase = createGenerateMocksBuildPhase(outputPaths: outputPaths, - cacheBreakerPath: cacheBreakerPath, - config: config) - let cleanBuildPhase = createCleanMocksBuildPhase(cacheBreakerPath: cacheBreakerPath) - xcodeproj.pbxproj.add(object: buildPhase) - xcodeproj.pbxproj.add(object: cleanBuildPhase) - - // Add build phase to target before the first compile sources phase. - target.buildPhases.insert(buildPhase, at: buildPhaseIndex) - target.buildPhases.insert(cleanBuildPhase, at: buildPhaseIndex) - - // Add the generated source target mock files to the destination target compile sources phase. - for outputPath in outputPaths { - try addSourceFilePath(outputPath, - target: target, - sourceGroup: sourceGroup, - xcodeproj: xcodeproj, - config: config) - } - - try xcodeproj.writePBXProj(path: config.projectPath, outputSettings: PBXOutputSettings()) - } - - private static func getSourceTargets(for config: InstallConfiguration, - xcodeproj: XcodeProj) throws -> [PBXTarget] { - return try config.sourceTargetNames.map({ targetName throws -> PBXTarget in - let sourceTargets = xcodeproj.pbxproj.targets(named: targetName).filter({ target in - guard target.productType?.isTestBundle != true else { - logWarning("Ignoring source target \(targetName.singleQuoted) because it is a test target") - return false - } - return true - }) - if sourceTargets.count > 1 { - logWarning("Found multiple source targets named \(targetName.singleQuoted), using the first one") - } - guard let sourceTarget = sourceTargets.first else { - throw Failure.malformedConfiguration( - description: "Unable to find source target named \(targetName.singleQuoted)" - ) - } - return sourceTarget - }) - } - - private static func addSourceFilePath(_ outputPath: Path, - target: PBXTarget, - sourceGroup: PBXGroup, - xcodeproj: XcodeProj, - config: InstallConfiguration) throws { - guard try target.sourcesBuildPhase()?.files?.contains(where: { - try $0.file?.fullPath(sourceRoot: config.sourceRoot) == outputPath - }) != true else { - // De-dup already-added sources. - log("Target \(target.name.singleQuoted) already references the output mock file at \(outputPath)") - return - } - - // Add the generated source file reference to the destination target groups. - let fileReference: PBXFileReference - if let existingReference = sourceGroup.file(named: outputPath.lastComponent), - (try? existingReference.fullPath(sourceRoot: config.sourceRoot)) == outputPath { - fileReference = existingReference - log("Using existing output mock file reference at \(outputPath)") - } else { - fileReference = try sourceGroup.addFile(at: outputPath, - sourceRoot: config.sourceRoot, - override: false, - validatePresence: false) - log("Creating new output mock file reference at \(outputPath)") - } - - // Add the generated source file reference to the Xcode project. - xcodeproj.pbxproj.add(object: fileReference) - - _ = try target.sourcesBuildPhase()?.add(file: fileReference) - } - - - // MARK: - Uninstall - - static func uninstall(using config: UninstallConfiguration) throws { - let xcodeproj = try XcodeProj(path: config.projectPath) - try config.targetNames.forEach({ targetName throws in - guard let target = xcodeproj.pbxproj.targets(named: targetName).first else { - throw Failure.malformedConfiguration( - description: "Unable to find target named \(targetName.singleQuoted)" - ) - } - try uninstall(from: xcodeproj, target: target, sourceRoot: config.sourceRoot) - }) - - try xcodeproj.writePBXProj(path: config.projectPath, outputSettings: PBXOutputSettings()) - } - - private static func uninstall(from xcodeproj: XcodeProj, - target: PBXTarget, - sourceRoot: Path) throws { - try uninstallGeneratePhase(from: xcodeproj, target: target, sourceRoot: sourceRoot) - try uninstallCleanPhase(from: xcodeproj, target: target, sourceRoot: sourceRoot) - } - - private static func uninstallGeneratePhase(from xcodeproj: XcodeProj, - target: PBXTarget, - sourceRoot: Path) throws { - guard let buildPhase = target.buildPhases - .first(where: { - $0 is PBXShellScriptBuildPhase && $0.name() == Constants.buildPhaseName - }) as? PBXShellScriptBuildPhase - else { return } - - log("Uninstalling existing 'Generate Mockingbird Mocks' build phase from target \(target.name.singleQuoted)") - - // Remove build phase reference from project. - xcodeproj.pbxproj.delete(object: buildPhase) - - // Remove all associated generated output files. - let outputFilesPaths = Set(buildPhase.outputPaths.map({ - $0.replacingOccurrences(of: "$(SRCROOT)", with: "\(sourceRoot.absolute())") - })) - try target.sourcesBuildPhase()?.files?.removeAll(where: { - guard let fullPath = try $0.file?.fullPath(sourceRoot: sourceRoot) else { return false } - return outputFilesPaths.contains("\(fullPath)") - }) - - // Remove from target build phases. - target.buildPhases.removeAll(where: { $0 === buildPhase }) - } - - private static func uninstallCleanPhase(from xcodeproj: XcodeProj, - target: PBXTarget, - sourceRoot: Path) throws { - guard let buildPhase = target.buildPhases - .first(where: { - $0 is PBXShellScriptBuildPhase && $0.name() == Constants.cleanBuildPhaseName - }) as? PBXShellScriptBuildPhase - else { return } - - log("Uninstalling existing 'Clean Mockingbird Mocks' build phase from target \(target.name.singleQuoted)") - - // Remove build phase reference from project. - xcodeproj.pbxproj.delete(object: buildPhase) - - // Remove from target build phases. - target.buildPhases.removeAll(where: { $0 === buildPhase }) - } - - - // MARK: - Create build phases - - private static func createGenerateMocksBuildPhase(outputPaths: [Path], - cacheBreakerPath: Path, - config: InstallConfiguration) - -> PBXShellScriptBuildPhase { - let targets = config.sourceTargetNames.map({ $0.singleQuoted }).joined(separator: " ") - let outputs = outputPaths.map({ - $0.getRelativePath(to: config.sourceRoot, style: .bash).doubleQuoted - }).joined(separator: " ") - var options = [ - "--targets \(targets)", - "--outputs \(outputs)", - ] - if let supportPath = config.supportPath { - let relativeSupportPath = supportPath.getRelativePath(to: config.sourceRoot, style: .bash) - options.append("--support \(relativeSupportPath.doubleQuoted)") - } - if let expression = config.compilationCondition { - options.append("--condition \(expression.singleQuoted)") - } - if let header = config.header { - options.append("--header \(header.map({ "'\($0)'" }).joined(separator: " "))") - } - if let diagnostics = config.diagnostics { - let allDiagnostics = Set(diagnostics) - .map({ $0.rawValue.singleQuoted }) - .sorted() - .joined(separator: " ") - options.append("--diagnostics \(allDiagnostics)") - } - if let method = config.pruningMethod { - options.append("--prune \(method.rawValue.singleQuoted)") - } - if config.onlyMockProtocols { - options.append("--only-protocols") - } - if config.disableSwiftlint { - options.append("--disable-swiftlint") - } - if config.disableCache { - options.append("--disable-cache") - } - if config.disableRelaxedLinking { - options.append("--disable-relaxed-linking") - } - if let logLevel = config.logLevel { - switch logLevel { - case .quiet: options.append("--quiet") - case .normal: break - case .verbose: options.append("--verbose") - } - } - if config.asynchronousGeneration { - options.append("&") - } - let cliPath = config.cliPath.getRelativePath(to: config.sourceRoot, - style: .bash, - shouldNormalize: false) - let shellScript = """ - set -eu - - # Ensure mocks are generated prior to running Compile Sources - rm -f '\(cacheBreakerPath.absolute())' - - \(cliPath) generate \\ - \(options.joined(separator: " \\\n ")) - """ - return PBXShellScriptBuildPhase(name: Constants.buildPhaseName, - inputPaths: ["\(cacheBreakerPath.absolute())"], - outputPaths: outputPaths.map({ - $0.getRelativePath(to: config.sourceRoot, style: .make) - }), - shellScript: shellScript) - } - - /// Xcode constructs a dependency graph from the input and output file lists of build phases and - /// uses that to parallelize operations. The Generate Mocks phase lists each generated mock as an - /// output file in order to ensure that it runs prior to the Compile Source phase. - /// - /// However, Xcode also caches Run Script phase results based on the presence and modification of - /// input and output files. In order to not specify all input source files at installation time - /// (which would be brittle), it's necessary to use a secondary build phase as a trigger for the - /// primary Generate Mocks phase. - private static func createCleanMocksBuildPhase(cacheBreakerPath: Path) - -> PBXShellScriptBuildPhase { - let shellScript = "echo $RANDOM > '\(cacheBreakerPath.absolute())'\n" - return PBXShellScriptBuildPhase(name: Constants.cleanBuildPhaseName, - outputPaths: ["\(cacheBreakerPath.absolute())"], - shellScript: shellScript) - } -} - -extension Path { - func getRelativePath(to sourceRoot: Path, - style: SubstitutionStyle, - shouldNormalize: Bool = true) -> String { - let sourceRootPath = "\(sourceRoot.absolute())" - let absolutePath = shouldNormalize ? "\(absolute())" : "\(abbreviate())" - guard absolutePath.hasPrefix(sourceRootPath) else { return absolutePath } - return style.wrap("SRCROOT") + absolutePath.dropFirst(sourceRootPath.count) - } -} diff --git a/Sources/MockingbirdCli/Interface/Mockingbird.swift b/Sources/MockingbirdCli/Interface/Mockingbird.swift new file mode 100644 index 00000000..986daf0e --- /dev/null +++ b/Sources/MockingbirdCli/Interface/Mockingbird.swift @@ -0,0 +1,65 @@ +// +// Mockingbird.swift +// MockingbirdCli +// +// Created by Andrew Chang on 8/23/19. +// + +import ArgumentParser +import Foundation +import MockingbirdGenerator +import PathKit + +/// Represents a CLI that can parse arguments and run the appropriate `Command`. +struct Mockingbird: ParsableCommand { + static var configuration = CommandConfiguration( + abstract: "A convenient Swift mocking framework.", + version: "\(mockingbirdVersion)", + subcommands: [ + Configure.self, + Generate.self, + Version.self, + ] + ) + + struct Options: ParsableArguments { + @Flag(help: "Log all errors, warnings, and debug messages.") + var verbose = false + + @Flag(help: "Only log error messages.") + var quiet = false + } + + @OptionGroup() var globalOptions: Options + + func validate() throws { + if globalOptions.verbose { + LogLevel.default.value = .verbose + } else if globalOptions.quiet { + LogLevel.default.value = .quiet + } + } +} + +extension Mockingbird.Options: EncodableArguments { + enum CodingKeys: String, CodingKey { + case verbose + case quiet + } + + func encode(to encoder: Encoder) throws { + try encodeOptions(to: encoder) + try encodeFlags(to: encoder) + try encodeOptionGroups(to: encoder) + } + + func encodeOptions(to encoder: Encoder) throws {} + + func encodeFlags(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(verbose, forKey: .verbose) + try container.encode(quiet, forKey: .quiet) + } + + func encodeOptionGroups(to encoder: Encoder) throws {} +} diff --git a/Sources/MockingbirdCli/Interface/Program.swift b/Sources/MockingbirdCli/Interface/Program.swift deleted file mode 100644 index bdde7ee6..00000000 --- a/Sources/MockingbirdCli/Interface/Program.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// Program.swift -// MockingbirdCli -// -// Created by Andrew Chang on 8/23/19. -// - -import Basic -import Foundation -import MockingbirdGenerator -import PathKit -import SPMUtility -import os.log - -protocol Command { - var name: String { get } - var overview: String { get } - var subparser: ArgumentParser { get } - init(parser: ArgumentParser) - func run(with arguments: ArgumentParser.Result, - environment: [String: String], - workingPath: Path) throws -} - -protocol AliasableCommand: BaseCommand { - init(parser subparser: ArgumentParser, name: String, overview: String) -} - -class BaseCommand: Command { - var name: String { fatalError() } - var overview: String { fatalError() } - let subparser: ArgumentParser - - let verboseOption: OptionArgument - let quietOption: OptionArgument - - required init(parser subparser: ArgumentParser) { - self.subparser = subparser - self.verboseOption = subparser.addVerboseLogLevel() - self.quietOption = subparser.addQuietLogLevel() - } - - func run(with arguments: ArgumentParser.Result, - environment: [String: String], - workingPath: Path) throws { - let logLevel = try arguments.getLogLevel(verboseOption: verboseOption, quietOption: quietOption) - LogLevel.default.value = logLevel - } -} - -/// Represents a CLI that can parse arguments and run the appropriate `Command`. -struct Program { - private let parser: ArgumentParser - private let commands: [Command] - private let environment: [String: String] - private let fileManager: FileManager - - init(usage: String, - overview: String, - commands: [Command.Type], - environment: [String: String] = ProcessInfo.processInfo.environment, - fileManager: FileManager = FileManager.default) { - let parser = ArgumentParser(usage: usage, overview: overview) - self.parser = parser - self.commands = commands.map({ $0.init(parser: parser) }) - self.environment = environment - self.fileManager = fileManager - } - - func run(with arguments: [String]) -> Int32 { - var exitStatus: Int32 = 0 - time(.runProgram) { - do { - var parsedArguments: ArgumentParser.Result! - try time(.parseArguments) { - let arguments = Array(arguments.dropFirst()) - parsedArguments = try parser.parse(arguments) - } - try process(arguments: parsedArguments) - } - catch let error { - log(error) - exitStatus = 1 - } - } - return exitStatus - } - - private func process(arguments: ArgumentParser.Result) throws { - guard let subparser = arguments.subparser(parser), - let command = commands.last(where: { $0.name == subparser }) else { - parser.printUsage(on: stdoutStream) - return - } - try command.run(with: arguments, - environment: environment, - workingPath: Path(fileManager.currentDirectoryPath)) - - } -} - -func exit(_ exitStatus: Int32) -> Never { - flushLogs() - Darwin.exit(exitStatus) -} diff --git a/Sources/MockingbirdCli/Launcher/LoadDylib.swift b/Sources/MockingbirdCli/Launcher/LoadDylib.swift index f228db41..8a4fe4ba 100644 --- a/Sources/MockingbirdCli/Launcher/LoadDylib.swift +++ b/Sources/MockingbirdCli/Launcher/LoadDylib.swift @@ -45,19 +45,20 @@ func loadDylibs(_ dylibs: [Resource], onLoad block: () -> Void) { let environment = processInfo.environment - // Global world-writable place to output dylibs, must be kept in sync with `Makefile`. - #if RELATIVE_RPATH // Use relative paths for sandboxed CI builds. + #if !(MKB_INSTALLABLE) + // Use relative paths for sandboxed CI builds. let mockingbirdPath = Path(processInfo.arguments.first ?? "./mockingbird").absolute() var globalLibraryDirectory = mockingbirdPath.parent() if mockingbirdPath.isSymlink { do { - globalLibraryDirectory = try mockingbirdPath.symlinkDestination().absolute().parent() + globalLibraryDirectory = try mockingbirdPath.followRecursively().absolute().parent() } catch { logWarning("Mockingbird was run from a symbolic link, but the symbolic link destination " + "could not be resolved. Dylibs may be extracted to the wrong location.") } } #else + // Global world-writable place to output dylibs, must be kept in sync with `Makefile`. let globalLibraryDirectory = Path("/var/tmp/mockingbird/\(mockingbirdVersion)/libs/") #endif diff --git a/Sources/MockingbirdCli/Libraries/lib_InternalSwiftSyntaxParser.dylib b/Sources/MockingbirdCli/Libraries/lib_InternalSwiftSyntaxParser.dylib index 2e0feac7..7029e04c 100755 Binary files a/Sources/MockingbirdCli/Libraries/lib_InternalSwiftSyntaxParser.dylib and b/Sources/MockingbirdCli/Libraries/lib_InternalSwiftSyntaxParser.dylib differ diff --git a/Sources/MockingbirdCli/Extensions/Encodable+SHA1.swift b/Sources/MockingbirdCli/Utils/Encodable+SHA1.swift similarity index 100% rename from Sources/MockingbirdCli/Extensions/Encodable+SHA1.swift rename to Sources/MockingbirdCli/Utils/Encodable+SHA1.swift diff --git a/Sources/MockingbirdCli/Utils/Path+Abbreviate.swift b/Sources/MockingbirdCli/Utils/Path+Abbreviate.swift new file mode 100644 index 00000000..4ec083ba --- /dev/null +++ b/Sources/MockingbirdCli/Utils/Path+Abbreviate.swift @@ -0,0 +1,30 @@ +// +// Path+Abbreviate.swift +// MockingbirdCli +// +// Created by typealias on 12/23/21. +// + +import Foundation +import PathKit +import MockingbirdGenerator + +extension Path { + func abbreviated(style: SubstitutionStyle = .bash) -> String { + let abbreviated = absolute().abbreviate().string + guard abbreviated.starts(with: "~") else { return abbreviated } + return style.wrap("HOME") + abbreviated.dropFirst() + } + + func abbreviated(root: Path, + variable: String, + style: SubstitutionStyle = .bash) -> String { + let absoluteSelfPath = absolute().string + let absoluteRootPath = root.absolute().string + let relativePath = abbreviated() + guard absoluteSelfPath.starts(with: absoluteRootPath) else { + return relativePath + } + return style.wrap(variable) + absoluteSelfPath.dropFirst(absoluteRootPath.count) + } +} diff --git a/Sources/MockingbirdCli/Utils/Path+Symlink.swift b/Sources/MockingbirdCli/Utils/Path+Symlink.swift new file mode 100644 index 00000000..d371724b --- /dev/null +++ b/Sources/MockingbirdCli/Utils/Path+Symlink.swift @@ -0,0 +1,19 @@ +// +// Path+Symlink.swift +// MockingbirdCli +// +// Created by typealias on 8/8/21. +// + +import Foundation +import PathKit + +extension Path { + func followRecursively() throws -> Path { + guard isSymlink else { + return self + } + // POSIX should detect circular symlinks for us. + return try symlinkDestination().followRecursively() + } +} diff --git a/Sources/MockingbirdCli/Utils/TimeUnit.swift b/Sources/MockingbirdCli/Utils/TimeUnit.swift new file mode 100644 index 00000000..34692709 --- /dev/null +++ b/Sources/MockingbirdCli/Utils/TimeUnit.swift @@ -0,0 +1,41 @@ +// +// TimeInterval+Normalize.swift +// MockingbirdCli +// +// Created by typealias on 8/8/21. +// + +import Foundation + +enum TimeUnit: CustomStringConvertible { + case millisecond(Double), second(Double), minute(Double) + + init(_ delta: TimeInterval) { + if delta < 1 { + self = .millisecond(delta*1000) + } else if delta < 60 { + self = .second(delta) + } else { + self = .minute(delta/60) + } + } + + var description: String { + format(to: 0) + } + + func format(to digits: Int) -> String { + let normalize = { (value: Double) -> String in + guard digits > 0 else { + return String(Int(value)) + } + let precision = pow(10, Double(digits)) + return String(round(value * precision) / precision) + } + switch self { + case .millisecond(let value): return "\(normalize(value))ms" + case .second(let value): return "\(normalize(value))s" + case .minute(let value): return "\(normalize(value))m" + } + } +} diff --git a/Sources/MockingbirdCli/main.swift b/Sources/MockingbirdCli/main.swift index 18aa8785..ce9919ee 100644 --- a/Sources/MockingbirdCli/main.swift +++ b/Sources/MockingbirdCli/main.swift @@ -7,20 +7,21 @@ // import Foundation - -func main(arguments: [String]) -> Int32 { - let program = Program(usage: "", - overview: "Mockingbird mock generator", - commands: [GenerateCommand.self, - ConfigureCommand.self, - InstallCommand.self, - UninstallCommand.self, - DownloadCommand.self, - TestbedCommand.self, - VersionCommand.self]) - return program.run(with: arguments) -} +import MockingbirdGenerator +import PathKit loadDylibs([swiftSyntaxParserDylib]) { - exit(main(arguments: ProcessInfo.processInfo.arguments)) + do { + var command = try Mockingbird.parseAsRoot() + switch command { + case var subcommand as Mockingbird.Configure: + try subcommand.run() + default: + try command.run() + } + flushLogs() + } catch { + flushLogs() + Mockingbird.exit(withError: error) + } } diff --git a/Sources/MockingbirdFramework/Objective-C/MKBMocking.h b/Sources/MockingbirdFramework/Objective-C/Bridge/include/MKBMocking.h similarity index 100% rename from Sources/MockingbirdFramework/Objective-C/MKBMocking.h rename to Sources/MockingbirdFramework/Objective-C/Bridge/include/MKBMocking.h diff --git a/Sources/MockingbirdFramework/Objective-C/MKBTestUtils.h b/Sources/MockingbirdFramework/Objective-C/Bridge/include/MKBTestUtils.h similarity index 100% rename from Sources/MockingbirdFramework/Objective-C/MKBTestUtils.h rename to Sources/MockingbirdFramework/Objective-C/Bridge/include/MKBTestUtils.h diff --git a/Sources/MockingbirdFramework/Objective-C/MKBTypeFacade.h b/Sources/MockingbirdFramework/Objective-C/Bridge/include/MKBTypeFacade.h similarity index 100% rename from Sources/MockingbirdFramework/Objective-C/MKBTypeFacade.h rename to Sources/MockingbirdFramework/Objective-C/Bridge/include/MKBTypeFacade.h diff --git a/Sources/MockingbirdFramework/Objective-C/Mockingbird.h b/Sources/MockingbirdFramework/Objective-C/Bridge/include/Mockingbird.h similarity index 100% rename from Sources/MockingbirdFramework/Objective-C/Mockingbird.h rename to Sources/MockingbirdFramework/Objective-C/Bridge/include/Mockingbird.h diff --git a/Sources/MockingbirdFramework/Objective-C/Bridge/sources/MKBMocking.m b/Sources/MockingbirdFramework/Objective-C/Bridge/sources/MKBMocking.m new file mode 100644 index 00000000..ded6b62b --- /dev/null +++ b/Sources/MockingbirdFramework/Objective-C/Bridge/sources/MKBMocking.m @@ -0,0 +1,34 @@ +// +// MKBMocking.m +// MockingbirdFramework +// +// Created by typealias on 7/17/21. +// + +#import "../include/MKBMocking.h" +#import + +id MKBMock(id aType) +{ + if ([NSStringFromClass([aType class]) isEqualToString:@"Protocol"]) { + return MKBMockProtocol(aType); + } else { + return MKBMockClass((Class)aType); + } +} + +// Swift Package Manager does not support mixed language targets, but Mockingbird has a +// bidirectional interop between Swift and Obj-C. This allows us to break the cyclic dependency +// caused by factoring out Swift and Obj-C into separate targets. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +id MKBMockClass(Class aClass) { + const SEL sel = NSSelectorFromString(@"initWithClass:"); + return [[NSClassFromString(@"MKBClassMock") alloc] performSelector:sel withObject:aClass]; +} + +id MKBMockProtocol(id aProtocol) { + const SEL sel = NSSelectorFromString(@"initWithProtocol:"); + return [[NSClassFromString(@"MKBProtocolMock") alloc] performSelector:sel withObject:aProtocol]; +} +#pragma clang diagnostic pop diff --git a/Sources/MockingbirdFramework/Objective-C/MKBTestUtils.m b/Sources/MockingbirdFramework/Objective-C/Bridge/sources/MKBTestUtils.m similarity index 94% rename from Sources/MockingbirdFramework/Objective-C/MKBTestUtils.m rename to Sources/MockingbirdFramework/Objective-C/Bridge/sources/MKBTestUtils.m index 56a2d129..5fced0d1 100644 --- a/Sources/MockingbirdFramework/Objective-C/MKBTestUtils.m +++ b/Sources/MockingbirdFramework/Objective-C/Bridge/sources/MKBTestUtils.m @@ -5,7 +5,7 @@ // Created by typealias on 7/25/21. // -#import "MKBTestUtils.h" +#import "../include/MKBTestUtils.h" void MKBStopTest(NSString *reason) { diff --git a/Sources/MockingbirdFramework/Objective-C/MKBTypeFacade.m b/Sources/MockingbirdFramework/Objective-C/Bridge/sources/MKBTypeFacade.m similarity index 94% rename from Sources/MockingbirdFramework/Objective-C/MKBTypeFacade.m rename to Sources/MockingbirdFramework/Objective-C/Bridge/sources/MKBTypeFacade.m index c5bdd94d..f530247b 100644 --- a/Sources/MockingbirdFramework/Objective-C/MKBTypeFacade.m +++ b/Sources/MockingbirdFramework/Objective-C/Bridge/sources/MKBTypeFacade.m @@ -5,7 +5,7 @@ // Created by typealias on 7/17/21. // -#import "MKBTypeFacade.h" +#import "../include/MKBTypeFacade.h" @implementation MKBTypeFacade diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBBoolInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBBoolInvocationHandler.m index 012c771d..f072aaa1 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBBoolInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBBoolInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBBoolInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBBoolInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBCStringInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBCStringInvocationHandler.m index cbf06653..249b9f90 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBCStringInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBCStringInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBCStringInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBCStringInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBCharInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBCharInvocationHandler.m index 16d60633..860fb129 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBCharInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBCharInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBCharInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBCharInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBClassInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBClassInvocationHandler.m index 299dd113..47db96dd 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBClassInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBClassInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBClassInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBClassInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBDoubleInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBDoubleInvocationHandler.m index 6fcdf72d..ee7a6106 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBDoubleInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBDoubleInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBDoubleInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBDoubleInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBFloatInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBFloatInvocationHandler.m index 752a53c6..4e2512a4 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBFloatInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBFloatInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBFloatInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBFloatInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBIntInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBIntInvocationHandler.m index e2e57397..f1ac3cb6 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBIntInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBIntInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBIntInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBIntInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBInvocationHandlerChain.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBInvocationHandlerChain.m index bbad95c8..dfcd137d 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBInvocationHandlerChain.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBInvocationHandlerChain.m @@ -29,7 +29,11 @@ #import "MKBUnsignedLongLongInvocationHandler.h" #import "MKBUnsignedShortInvocationHandler.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @interface MKBInvocationHandlerChain () diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBLongInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBLongInvocationHandler.m index 84b7602d..72fb57d2 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBLongInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBLongInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBLongInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBLongInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBLongLongInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBLongLongInvocationHandler.m index f7510ea0..86952b1b 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBLongLongInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBLongLongInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBLongLongInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBLongLongInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBObjectInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBObjectInvocationHandler.m index 397ee0c2..9aa14011 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBObjectInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBObjectInvocationHandler.m @@ -7,8 +7,13 @@ #import "MKBObjectInvocationHandler.h" #import "MKBComparator.h" -#import +#if MKB_SWIFTPM +@import Mockingbird; +@import MockingbirdBridge; +#else #import +#import +#endif @implementation MKBObjectInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBPointerInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBPointerInvocationHandler.m index b7841184..373e0ea3 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBPointerInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBPointerInvocationHandler.m @@ -8,7 +8,11 @@ #import "MKBPointerInvocationHandler.h" #import "MKBComparator.h" #import "NSInvocation+MKBErrorObjectType.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBPointerInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBSelectorInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBSelectorInvocationHandler.m index cfb62a97..8fc3c647 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBSelectorInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBSelectorInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBSelectorInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBSelectorInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBShortInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBShortInvocationHandler.m index 459a7258..68d24cf5 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBShortInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBShortInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBShortInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBShortInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBStructInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBStructInvocationHandler.m index a40ba8ac..98231e1a 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBStructInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBStructInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBStructInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBStructInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedCharInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedCharInvocationHandler.m index 536816af..a4a1547c 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedCharInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedCharInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBUnsignedCharInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBUnsignedCharInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedIntInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedIntInvocationHandler.m index e11e0375..901c0266 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedIntInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedIntInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBUnsignedIntInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBUnsignedIntInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedLongInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedLongInvocationHandler.m index fd98677b..40510af8 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedLongInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedLongInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBUnsignedLongInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBUnsignedLongInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedLongLongInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedLongLongInvocationHandler.m index ad07296b..d55c9b1a 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedLongLongInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedLongLongInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBUnsignedLongLongInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBUnsignedLongLongInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedShortInvocationHandler.m b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedShortInvocationHandler.m index b809b4de..b13e8c91 100644 --- a/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedShortInvocationHandler.m +++ b/Sources/MockingbirdFramework/Objective-C/InvocationHandlers/MKBUnsignedShortInvocationHandler.m @@ -7,7 +7,11 @@ #import "MKBUnsignedShortInvocationHandler.h" #import "MKBComparator.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @implementation MKBUnsignedShortInvocationHandler diff --git a/Sources/MockingbirdFramework/Objective-C/MKBConcreteMock.m b/Sources/MockingbirdFramework/Objective-C/MKBConcreteMock.m index 6b3808e7..0be1a427 100644 --- a/Sources/MockingbirdFramework/Objective-C/MKBConcreteMock.m +++ b/Sources/MockingbirdFramework/Objective-C/MKBConcreteMock.m @@ -9,7 +9,11 @@ #import "MKBProperty.h" #import "InvocationHandlers/MKBInvocationHandlerChain.h" #import "InvocationHandlers/NSInvocation+MKBErrorObjectType.h" +#if MKB_SWIFTPM +@import Mockingbird; +#else #import +#endif @interface MKBConcreteMock () @property (nonatomic, strong, readwrite) MKBInvocationHandlerChain *invocationHandlerChain; diff --git a/Sources/MockingbirdFramework/Objective-C/MKBMocking.m b/Sources/MockingbirdFramework/Objective-C/MKBMocking.m deleted file mode 100644 index ba8f51f1..00000000 --- a/Sources/MockingbirdFramework/Objective-C/MKBMocking.m +++ /dev/null @@ -1,28 +0,0 @@ -// -// MKBMocking.m -// MockingbirdFramework -// -// Created by typealias on 7/17/21. -// - -#import "MKBMocking.h" -#import "MKBClassMock.h" -#import "MKBProtocolMock.h" -#import - -id MKBMock(id aType) -{ - if ([NSStringFromClass([aType class]) isEqualToString:@"Protocol"]) { - return MKBMockProtocol(aType); - } else { - return MKBMockClass((Class)aType); - } -} - -id MKBMockClass(Class aClass) { - return [[MKBClassMock alloc] initWithClass:aClass]; -} - -id MKBMockProtocol(id aProtocol) { - return [[MKBProtocolMock alloc] initWithProtocol:aProtocol]; -} diff --git a/Sources/MockingbirdFramework/Utilities/MockingbirdBridge.swift b/Sources/MockingbirdFramework/Utilities/MockingbirdBridge.swift new file mode 100644 index 00000000..e28e04c6 --- /dev/null +++ b/Sources/MockingbirdFramework/Utilities/MockingbirdBridge.swift @@ -0,0 +1,10 @@ +// +// MockingbirdBridge.swift +// MockingbirdFramework +// +// Created by typealias on 8/24/21. +// + +#if MKB_SWIFTPM +@_exported import MockingbirdBridge +#endif diff --git a/Sources/MockingbirdGenerator/Generator/Operations/FileGenerator.swift b/Sources/MockingbirdGenerator/Generator/Operations/FileGenerator.swift index 954502f1..3f1a0bb0 100644 --- a/Sources/MockingbirdGenerator/Generator/Operations/FileGenerator.swift +++ b/Sources/MockingbirdGenerator/Generator/Operations/FileGenerator.swift @@ -36,8 +36,8 @@ class FileGenerator { private func generateFileHeader() -> PartialFileContent { var headerSections = [String]() - if let customHeader = config.header?.joined(separator: "\n") { - headerSections.append(customHeader) + if !config.header.isEmpty { + headerSections.append(String(lines: config.header)) } else { headerSections.append(""" // @@ -61,7 +61,7 @@ class FileGenerator { let implicitImports = [ ImportDeclaration("Foundation"), ImportDeclaration("Mockingbird", testable: true), - config.shouldImportModule ? ImportDeclaration(config.moduleName, testable: true) : nil, + ImportDeclaration(config.moduleName, testable: true), ].compactMap({ $0?.fullDeclaration }) let explicitImports = parsedFiles diff --git a/Sources/MockingbirdGenerator/Generator/Operations/GenerateFileOperation.swift b/Sources/MockingbirdGenerator/Generator/Operations/GenerateFileOperation.swift index 8ef33f22..4b349e53 100644 --- a/Sources/MockingbirdGenerator/Generator/Operations/GenerateFileOperation.swift +++ b/Sources/MockingbirdGenerator/Generator/Operations/GenerateFileOperation.swift @@ -13,18 +13,16 @@ import os.log public struct GenerateFileConfig { let moduleName: String let outputPath: Path - let header: [String]? + let header: [String] let compilationCondition: String? - let shouldImportModule: Bool let onlyMockProtocols: Bool let disableSwiftlint: Bool let pruningMethod: PruningMethod public init(moduleName: String, outputPath: Path, - header: [String]?, + header: [String], compilationCondition: String?, - shouldImportModule: Bool, onlyMockProtocols: Bool, disableSwiftlint: Bool, pruningMethod: PruningMethod) { @@ -32,7 +30,6 @@ public struct GenerateFileConfig { self.outputPath = outputPath self.header = header self.compilationCondition = compilationCondition - self.shouldImportModule = shouldImportModule self.onlyMockProtocols = onlyMockProtocols self.disableSwiftlint = disableSwiftlint self.pruningMethod = pruningMethod diff --git a/Sources/MockingbirdGenerator/Parser/Operations/ExtractSourcesOperation.swift b/Sources/MockingbirdGenerator/Parser/Operations/ExtractSourcesOperation.swift index 28c99862..79f5d935 100644 --- a/Sources/MockingbirdGenerator/Parser/Operations/ExtractSourcesOperation.swift +++ b/Sources/MockingbirdGenerator/Parser/Operations/ExtractSourcesOperation.swift @@ -86,24 +86,24 @@ public class ExtractSourcesOperation: BasicOperation, ExtractSourcesA } /// Returns the compiled source file paths for a single given target. - private var memoizedSourceFilePaths = [T: Set]() + private var memoizedSourceFilePaths = [String: Set]() private func sourceFilePaths(for target: T) -> Set { - if let memoized = memoizedSourceFilePaths[target] { return memoized } + if let memoized = memoizedSourceFilePaths[target.name] { return memoized } let moduleName = resolveProductModuleName(for: target) let paths = target.findSourceFilePaths(sourceRoot: sourceRoot) .map({ SourcePath(path: $0, moduleName: moduleName) }) let includedPaths = includedSourcePaths(for: Set(paths)) - memoizedSourceFilePaths[target] = includedPaths + memoizedSourceFilePaths[target.name] = includedPaths return includedPaths } /// Recursively find all targets and its dependency targets. - private var memoizedTargets = [T: Set]() + private var memoizedTargets = [String: Set]() private func allTargets(for target: T) -> Set { - if let memoized = memoizedTargets[target] { return memoized } + if let memoized = memoizedTargets[target.name] { return memoized } let targets = Set([target]).union( target.dependencies @@ -114,7 +114,7 @@ public class ExtractSourcesOperation: BasicOperation, ExtractSourcesA result.moduleDependencies[productModuleName] = Set(targets.map({ resolveProductModuleName(for: $0) })) - memoizedTargets[target] = targets + memoizedTargets[target.name] = targets return targets } @@ -153,11 +153,11 @@ public class ExtractSourcesOperation: BasicOperation, ExtractSourcesA return Set(operations.compactMap({ $0.result.sourcePath })) } - private var memoizedProductModuleNames = [T: String]() + private var memoizedProductModuleNames = [String: String]() private func resolveProductModuleName(for target: T) -> String { - if let memoized = memoizedProductModuleNames[target] { return memoized } + if let memoized = memoizedProductModuleNames[target.name] { return memoized } let productModuleName = target.resolveProductModuleName(environment: environment) - memoizedProductModuleNames[target] = productModuleName + memoizedProductModuleNames[target.name] = productModuleName return productModuleName } } diff --git a/Sources/MockingbirdGenerator/Parser/Project/ProjectDescription.swift b/Sources/MockingbirdGenerator/Parser/Project/ProjectDescription.swift index e36e87dd..0ed55be2 100644 --- a/Sources/MockingbirdGenerator/Parser/Project/ProjectDescription.swift +++ b/Sources/MockingbirdGenerator/Parser/Project/ProjectDescription.swift @@ -20,8 +20,27 @@ public struct TargetDescription: Hashable { public let sources: [Path] public let dependencies: [String] - public var productModuleName: String { - return c99name ?? name.escapingForModuleName() + // Synthesized + public let productModuleName: String + + init(name: String, + c99name: String?, + type: String, + path: Path, + sources: [Path], + dependencies: [String]) { + self.name = name + self.c99name = c99name + self.type = type + self.path = path + self.sources = sources + self.dependencies = dependencies + self.productModuleName = c99name ?? name.escapingForModuleName() + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(name) + hasher.combine(type) } } @@ -47,6 +66,7 @@ extension TargetDescription: Codable { self.type = try container.decode(String.self, forKey: .type) self.path = try container.decode(Path.self, forKey: .path) self.sources = try container.decodeIfPresent([Path].self, forKey: .sources) ?? [] + self.productModuleName = self.c99name ?? self.name.escapingForModuleName() let spmContainer = try decoder.container(keyedBy: SwiftPackageManagerKeys.self) self.dependencies = try spmContainer.decodeIfPresent([String].self, forKey: .targetDependencies) @@ -72,13 +92,12 @@ public struct DescribedTarget: Target { public let productType: TargetDescriptionType? public init(from description: TargetDescription, - descriptions: [TargetDescription], + descriptions: [String: TargetDescription], processedTargets: [String] = []) { self.description = description self.productType = TargetDescriptionType(rawValue: description.type) self.dependencies = description.dependencies.compactMap({ name in - guard let dependency = descriptions.first(where: { $0.productModuleName == name }) - else { return nil } + guard let dependency = descriptions[name] else { return nil } let attributedProcessedTargets = processedTargets + [dependency.productModuleName] guard !processedTargets.contains(dependency.productModuleName) else { logWarning("Breaking circular dependency \(attributedProcessedTargets.joined(separator: " -> "))") @@ -98,20 +117,28 @@ public struct DescribedTarget: Target { public func findSourceFilePaths(sourceRoot: Path) -> [Path] { return description.sources.map({ description.path + $0 }) } + + public func hash(into hasher: inout Hasher) { + hasher.combine(description) + } } public class JSONProject { let path: Path - let descriptions: [TargetDescription] + let targets: [TargetDescription] + let descriptions: [String: TargetDescription] required public init(path: Path) throws { self.path = path - self.descriptions = try JSONDecoder().decode(ProjectDescription.self, from: path.read()).targets + self.targets = try JSONDecoder().decode(ProjectDescription.self, from: path.read()).targets + self.descriptions = targets.reduce(into: [:]) { (descriptions, target) in + descriptions[target.productModuleName] = target + } } public func targets(named name: String) -> [DescribedTarget] { - return descriptions - .filter({ $0.name == name }) - .map({ DescribedTarget(from: $0, descriptions: descriptions) }) + return targets.filter({ $0.name == name }).map({ target in + DescribedTarget(from: target, descriptions: descriptions) + }) } } diff --git a/Sources/MockingbirdGenerator/Utilities/Log.swift b/Sources/MockingbirdGenerator/Utilities/Log.swift index be4c538d..80030da8 100644 --- a/Sources/MockingbirdGenerator/Utilities/Log.swift +++ b/Sources/MockingbirdGenerator/Utilities/Log.swift @@ -90,7 +90,7 @@ public enum LogLevel: String, RawRepresentable, CaseIterable { public static let `default` = Synchronized(.normal) } -public enum DiagnosticType: String, Hashable, CaseIterable { +public enum DiagnosticType: String, Hashable, Codable, CaseIterable { case all = "all" case notMockable = "not-mockable" case undefinedType = "undefined-type" diff --git a/Sources/MockingbirdGenerator/Utilities/String+GeneratorUtils.swift b/Sources/MockingbirdGenerator/Utilities/String+GeneratorUtils.swift index a103d4c5..237b1417 100644 --- a/Sources/MockingbirdGenerator/Utilities/String+GeneratorUtils.swift +++ b/Sources/MockingbirdGenerator/Utilities/String+GeneratorUtils.swift @@ -22,7 +22,7 @@ extension String { } } -extension String.StringInterpolation { +public extension String.StringInterpolation { mutating func appendInterpolation(singleQuoted str: String) { appendLiteral("'\(str)'") } diff --git a/Sources/MockingbirdGenerator/Utilities/String+ParserUtils.swift b/Sources/MockingbirdGenerator/Utilities/String+ParserUtils.swift index 939a5a52..bb3019b5 100644 --- a/Sources/MockingbirdGenerator/Utilities/String+ParserUtils.swift +++ b/Sources/MockingbirdGenerator/Utilities/String+ParserUtils.swift @@ -35,6 +35,9 @@ public extension String { /// Escape the string for use in module names, replacing special characters and invalid prefixes. func escapingForModuleName() -> String { + guard rangeOfCharacter(from: .letters.inverted) != nil else { + return self + } let replaced = replacingOccurrences(of: "\\W", with: "_", options: .regularExpression) if String(replaced[startIndex]).range(of: "\\d", options: .regularExpression) != nil { return replaced.replacingCharacters(in: ...startIndex, with: "_") diff --git a/Sources/Package.swift b/Sources/Package.swift deleted file mode 100644 index 87ceca18..00000000 --- a/Sources/Package.swift +++ /dev/null @@ -1,45 +0,0 @@ -// swift-tools-version:5.0 -import PackageDescription - -// Package configuration for building the CLI using `$ swift build`. -let package = Package( - name: "MockingbirdCli", - platforms: [ - .macOS(.v10_14), - .iOS(.v8), - .tvOS(.v9), - ], - products: [ - .executable(name: "mockingbird", targets: ["MockingbirdCli"]), - .library(name: "MockingbirdGenerator", targets: ["MockingbirdGenerator"]), - ], - // Keep this in sync with the Mockingbird.xcodeproj dependencies! - dependencies: [ - .package(url: "https://github.com/apple/swift-package-manager.git", .exact("0.4.0")), - .package(url: "https://github.com/apple/swift-syntax.git", .exact("0.50400.0")), - .package(url: "https://github.com/jpsim/SourceKitten.git", .exact("0.30.0")), - .package(url: "https://github.com/tuist/XcodeProj.git", .exact("7.14.0")), - .package(url: "https://github.com/weichsel/ZIPFoundation.git", .exact("0.9.11")), - ], - targets: [ - .target( - name: "MockingbirdCli", - dependencies: [ - "MockingbirdGenerator", - "SPMUtility", - "XcodeProj", - "ZIPFoundation", - ], - path: "MockingbirdCli" - ), - .target( - name: "MockingbirdGenerator", - dependencies: [ - "SourceKittenFramework", - "SwiftSyntax", - "XcodeProj", - ], - path: "MockingbirdGenerator" - ), - ] -) diff --git a/mockingbird b/mockingbird index da519389..41c3c404 100755 --- a/mockingbird +++ b/mockingbird @@ -5,23 +5,26 @@ set -eu workingDir="$(dirname "$0")" version=${MKB_VERSION:-"$(cd "${workingDir}" && make get-version)"} -# Hermetic builds extract dylib dependencies into the enclosing directory -# making them safer in sandboxed environments like a CI. -hermetic=${HERMETIC:-1} +# Installable builds extract dylib dependencies into a system library directory +# allowing the binary to be installed globally e.g. to `/usr/local/bin`. This +# makes them unsuitable for sandboxed environments like a CI. By default, the +# launcher uses non-installable builds. +installable=${MKB_INSTALLABLE:-0} versionString="${version}" -if [[ "${hermetic}" -ne 1 ]]; then - versionString="${versionString}-portable" +if [[ "${installable}" -eq 1 ]]; then + versionString="${versionString}-installable" fi echo "Using Mockingbird v${versionString}" -binaryPath="${workingDir}/bin/${versionString}/mockingbird" +binaryDir="${workingDir}/bin/${versionString}" +binaryPath="${binaryDir}/mockingbird" # Download a versioned binary if needed. if [[ ! -f "${binaryPath}" ]]; then downloadUrl="$(cd "${workingDir}" && make get-release-url)" echo "Downloading CLI from ${downloadUrl}" - (cd "${workingDir}" && HERMETIC="${hermetic}" make download) + (cd "${workingDir}" && BIN_DIR="${binaryDir}" MKB_INSTALLABLE="${installable}" make download) fi MKB_LAUNCHER="$0" "${binaryPath}" "$@"