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 @@
-
+
-
+
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 @@
-
+
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