diff --git a/.github/workflows/build-framework-cli.yml b/.github/workflows/build-framework-cli.yml index bd1fe7db..d7be8741 100644 --- a/.github/workflows/build-framework-cli.yml +++ b/.github/workflows/build-framework-cli.yml @@ -7,6 +7,34 @@ jobs: name: Run on latest macOS runs-on: macOS-latest + steps: + - uses: actions/checkout@v1 + - name: Print Debug Info + run: make print-debug-info + - name: Set Up Project + run: make setup-project + - name: Clean + run: make clean + - name: Build + run: make build + - name: Install + run: 'export PREFIX=$(pwd) && make install' + - name: Set Up Target + run: | + ./bin/mockingbird install \ + --target MockingbirdTests \ + --source MockingbirdTestsHost \ + --loglevel verbose \ + --verbose + - name: Test + run: make clean-test + - name: Cached Test + run: make test + + build-xcode-11_4_0: + name: Xcode 11.4.0 toolchain + runs-on: macOS-latest + steps: - uses: actions/checkout@v1 - name: Set Up Environment @@ -32,3 +60,33 @@ jobs: run: make clean-test - name: Cached Test run: make test + + build-xcode-11_3_1: + name: Xcode 11.3.1 toolchain + runs-on: macOS-latest + + steps: + - uses: actions/checkout@v1 + - name: Set Up Environment + run: sudo xcode-select -s /Applications/Xcode_11.3.1.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 + run: make build + - name: Install + run: 'export PREFIX=$(pwd) && make install' + - name: Set Up Target + run: | + ./bin/mockingbird install \ + --target MockingbirdTests \ + --source MockingbirdTestsHost \ + --loglevel verbose \ + --verbose + - name: Test + run: make clean-test + - name: Cached Test + run: make test diff --git a/.gitignore b/.gitignore index 8e0b0859..a55c97b8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ mockingbird ## Caches *.xcodeproj/MockingbirdCache/*.lock +## Embedded resources +MockingbirdCli/Libraries/*.generated.swift + ## Various settings *.pbxuser !default.pbxuser diff --git a/Makefile b/Makefile index 19e15c17..322a5937 100644 --- a/Makefile +++ b/Makefile @@ -6,12 +6,13 @@ BUILD_TOOL?=xcodebuild 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) SIMULATOR_NAME=iphone11-mockingbird SIMULATOR_DEVICE_TYPE=com.apple.CoreSimulator.SimDeviceType.iPhone-11 SIMULATOR_RUNTIME=com.apple.CoreSimulator.SimRuntime.iOS-13-3 -SWIFT_BUILD_FLAGS=--configuration release +SWIFT_BUILD_FLAGS=--configuration release -Xlinker -weak-l_InternalSwiftSyntaxParser XCODEBUILD_FLAGS=-project 'Mockingbird.xcodeproj' DSTROOT=$(TEMPORARY_FOLDER) XCODEBUILD_MACOS_FLAGS=$(XCODEBUILD_FLAGS) -destination 'platform=OS X' XCODEBUILD_FRAMEWORK_FLAGS=$(XCODEBUILD_FLAGS) \ @@ -32,6 +33,9 @@ EXAMPLE_CARTHAGE_XCODEBUILD_FLAGS=$(EXAMPLE_XCODEBUILD_FLAGS) \ FRAMEWORKS_FOLDER=/Library/Frameworks BINARIES_FOLDER=$(PREFIX)/bin +DEFAULT_XCODE_RPATH=$(XCODE_PATH)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx +MOCKINGBIRD_RPATH=/var/tmp/mockingbird/$(VERSION_STRING)/libs + PKG_BUNDLE_IDENTIFIER=co.bird.mockingbird PKG_IDENTITY_NAME=3rd Party Mac Developer Installer: Bird Rides, Inc. (P2T4T6R4SL) ZIP_IDENTITY_NAME=3rd Party Mac Developer Application: Bird Rides, Inc. (P2T4T6R4SL) @@ -59,7 +63,11 @@ APPLETVSIMULATOR_FRAMEWORK_PATH=$(APPLETVSIMULATOR_FRAMEWORK_FOLDER)/$(FRAMEWORK LICENSE_FILENAME=LICENSE LICENSE_PATH=$(shell pwd)/$(LICENSE_FILENAME) -INSTALLABLE_FILENAMES="$(CLI_FILENAME)" "$(MACOS_FRAMEWORK_FILENAME)" "$(IPHONESIMULATOR_FRAMEWORK_FILENAME)" "$(APPLETVSIMULATOR_FRAMEWORK_FILENAME)" "$(LICENSE_FILENAME)" +INSTALLABLE_FILENAMES="$(CLI_FILENAME)" \ + "$(MACOS_FRAMEWORK_FILENAME)" \ + "$(IPHONESIMULATOR_FRAMEWORK_FILENAME)" \ + "$(APPLETVSIMULATOR_FRAMEWORK_FILENAME)" \ + "$(LICENSE_FILENAME)" OUTPUT_PACKAGE=Mockingbird.pkg OUTPUT_ZIP=Mockingbird.zip @@ -81,6 +89,7 @@ ERROR_MSG=[ERROR] The downloaded Mockingbird CLI binary does not have the expect clean \ setup-project \ save-xcschemes \ + generate-embedded-dylibs \ build-cli \ build-framework-macos \ build-framework-iphonesimulator \ @@ -138,16 +147,41 @@ setup-project: save-xcschemes: cp -rf Mockingbird.xcodeproj/xcshareddata/xcschemes/*.xcscheme Xcode/XCSchemes -print-debug-info: - pwd - ls -la - swift --version - xcode-select --print-path - xcodebuild -version - xcodebuild -showsdks - -build-cli: +# Generate a random number. +# This is not run initially. +GENERATE_ID = $(shell od -vAn -N2 -tu2 < /dev/urandom) + +# Generate a random number, and assign it to MY_ID +# This is not run initially. +SET_ID = $(eval MY_ID=$(GENERATE_ID)) + +print-debug-info: + @echo "Mockingbird version: $(VERSION_STRING)" + @echo "Installation prefix: $(PREFIX)" + @echo "Temporary folder: $(TEMPORARY_FOLDER_ROOT)" + @echo "Build tool: $(BUILD_TOOL)" + $(eval XCODE_PATH_VAR = $(XCODE_PATH)) + @echo "Xcode path: $(XCODE_PATH_VAR)" + @echo "Built CLI path: $(EXECUTABLE_PATH)" + $(eval CURRENT_REV = $(shell git rev-parse HEAD)) + @echo "Current revision: $(CURRENT_REV)" + $(eval SWIFT_VERSION = $(shell swift --version)) + @echo "Swift version: $(SWIFT_VERSION)" + $(eval XCODEBUILD_VERSION = $(shell xcodebuild -version)) + @echo "Xcodebuild version: $(XCODEBUILD_VERSION)" + +generate-embedded-dylibs: + Scripts/generate-resource-file.sh \ + MockingbirdCli/Libraries/lib_InternalSwiftSyntaxParser.dylib \ + MockingbirdCli/Libraries/SwiftSyntaxParserDylib.generated.swift \ + 'swiftSyntaxParserDylib' + +build-cli: generate-embedded-dylibs swift build $(SWIFT_BUILD_FLAGS) --product mockingbird + # Inject custom rpath into binary. + $(eval RPATH = $(DEFAULT_XCODE_RPATH)) + install_name_tool -delete_rpath "$(RPATH)" "$(EXECUTABLE_PATH)" + install_name_tool -add_rpath "$(MOCKINGBIRD_RPATH)" "$(EXECUTABLE_PATH)" build-framework-macos: $(BUILD_TOOL) -scheme 'MockingbirdFramework' -configuration 'Release' -sdk macosx -arch x86_64 $(XCODEBUILD_FRAMEWORK_FLAGS) diff --git a/Mockingbird.xcodeproj/project.pbxproj b/Mockingbird.xcodeproj/project.pbxproj index 2ad0370b..9b3e6623 100644 --- a/Mockingbird.xcodeproj/project.pbxproj +++ b/Mockingbird.xcodeproj/project.pbxproj @@ -45,6 +45,10 @@ /* Begin PBXBuildFile section */ 5D5FAC84DF438200D4E44D85 /* MockingbirdTestsHostMocks.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93974737AF2D18DBCBCCA8F1 /* MockingbirdTestsHostMocks.generated.swift */; }; D372A4622454DFB60000E80A /* SequentialValueStubbingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D372A4612454DFB60000E80A /* SequentialValueStubbingTests.swift */; }; + D372A44D2453CB2A0000E80A /* OutputStream+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D372A44C2453CB2A0000E80A /* OutputStream+Extensions.swift */; }; + D372A4592454A5DD0000E80A /* SwiftSyntaxParserDylib.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = D372A4492453C4C40000E80A /* SwiftSyntaxParserDylib.generated.swift */; }; + D372A45B2454A7110000E80A /* LoadDylib.swift in Sources */ = {isa = PBXBuildFile; fileRef = D372A44E2453CCF60000E80A /* LoadDylib.swift */; }; + D372A45D2454ADCB0000E80A /* Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D372A45C2454ADCB0000E80A /* Resource.swift */; }; D3D936F22450F61400078751 /* MockingbirdTestsHost.h in Headers */ = {isa = PBXBuildFile; fileRef = OBJ_130 /* MockingbirdTestsHost.h */; settings = {ATTRIBUTES = (Public, ); }; }; D3D936F62450F85100078751 /* ParsedFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3D936F52450F85100078751 /* ParsedFile.swift */; }; D3D936F82451606000078751 /* ParseSingleFileOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3D936F72451606000078751 /* ParseSingleFileOperation.swift */; }; @@ -1104,6 +1108,12 @@ 93974737AF2D18DBCBCCA8F1 /* MockingbirdTestsHostMocks.generated.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; name = MockingbirdTestsHostMocks.generated.swift; path = MockingbirdMocks/MockingbirdTestsHostMocks.generated.swift; sourceTree = ""; }; "AEXML::AEXML::Product" /* AEXML.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AEXML.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D372A4612454DFB60000E80A /* SequentialValueStubbingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SequentialValueStubbingTests.swift; sourceTree = ""; }; + D372A4422452CF280000E80A /* lib_InternalSwiftSyntaxParser.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = lib_InternalSwiftSyntaxParser.dylib; path = MockingbirdGenerator/Libraries/lib_InternalSwiftSyntaxParser.dylib; sourceTree = ""; }; + D372A4452452D9280000E80A /* lib_InternalSwiftSyntaxParser.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = lib_InternalSwiftSyntaxParser.dylib; sourceTree = ""; }; + D372A4492453C4C40000E80A /* SwiftSyntaxParserDylib.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftSyntaxParserDylib.generated.swift; sourceTree = ""; }; + D372A44C2453CB2A0000E80A /* OutputStream+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OutputStream+Extensions.swift"; sourceTree = ""; }; + D372A44E2453CCF60000E80A /* LoadDylib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadDylib.swift; sourceTree = ""; }; + D372A45C2454ADCB0000E80A /* Resource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resource.swift; sourceTree = ""; }; D3A53F412450C59F00A1DB4B /* MockingbirdTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = MockingbirdTests.xcconfig; sourceTree = ""; }; D3A53F422450C59F00A1DB4B /* MockingbirdModuleTestsHost.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = MockingbirdModuleTestsHost.xcconfig; sourceTree = ""; }; D3A53F432450C59F00A1DB4B /* MockingbirdFramework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = MockingbirdFramework.xcconfig; sourceTree = ""; }; @@ -1897,6 +1907,32 @@ name = "Generated Mocks"; sourceTree = ""; }; + D372A4412452CF280000E80A /* Frameworks */ = { + isa = PBXGroup; + children = ( + D372A4422452CF280000E80A /* lib_InternalSwiftSyntaxParser.dylib */, + ); + name = Frameworks; + sourceTree = ""; + }; + D372A4442452D9280000E80A /* Libraries */ = { + isa = PBXGroup; + children = ( + D372A4452452D9280000E80A /* lib_InternalSwiftSyntaxParser.dylib */, + D372A4492453C4C40000E80A /* SwiftSyntaxParserDylib.generated.swift */, + ); + path = Libraries; + sourceTree = ""; + }; + D372A45E2454ADDF0000E80A /* Launcher */ = { + isa = PBXGroup; + children = ( + D372A44E2453CCF60000E80A /* LoadDylib.swift */, + D372A45C2454ADCB0000E80A /* Resource.swift */, + ); + path = Launcher; + sourceTree = ""; + }; D3A53F402450C59F00A1DB4B /* XCConfigs */ = { isa = PBXGroup; children = ( @@ -1946,6 +1982,7 @@ OBJ_111 /* Log.swift */, OBJ_113 /* OperationQueue+Extensions.swift */, OBJ_112 /* OSLog+Extensions.swift */, + D372A44C2453CB2A0000E80A /* OutputStream+Extensions.swift */, OBJ_114 /* Path+Fnmatch.swift */, OBJ_115 /* Path+WriteUtf8Strings.swift */, OBJ_116 /* String+Extensions.swift */, @@ -2916,6 +2953,7 @@ OBJ_668 /* README.md */, OBJ_669 /* CONTRIBUTING.md */, OBJ_670 /* MockingbirdFramework.podspec */, + D372A4412452CF280000E80A /* Frameworks */, ); sourceTree = ""; }; @@ -3330,6 +3368,8 @@ isa = PBXGroup; children = ( OBJ_9 /* Info.plist */, + D372A45E2454ADDF0000E80A /* Launcher */, + D372A4442452D9280000E80A /* Libraries */, OBJ_10 /* Interface */, OBJ_22 /* main.swift */, ); @@ -3343,11 +3383,11 @@ OBJ_90 /* CheckCacheOperation.swift */, OBJ_92 /* ExtractSourcesOperation.swift */, OBJ_93 /* FlattenInheritanceOperation.swift */, - OBJ_94 /* SwiftFileParser.swift */, OBJ_95 /* ParseFilesOperation.swift */, D3D936F72451606000078751 /* ParseSingleFileOperation.swift */, OBJ_96 /* ProcessStructuresOperation.swift */, OBJ_97 /* ProcessTypesOperation.swift */, + OBJ_94 /* SwiftFileParser.swift */, ); path = Operations; sourceTree = ""; @@ -3444,6 +3484,7 @@ isa = PBXNativeTarget; buildConfigurationList = OBJ_779 /* Build configuration list for PBXNativeTarget "MockingbirdCli" */; buildPhases = ( + D372A45A2454A5F00000E80A /* Generate Embedded Dylibs */, OBJ_782 /* Sources */, OBJ_794 /* Frameworks */, ); @@ -3983,6 +4024,11 @@ attributes = { LastSwiftMigration = 9999; LastUpgradeCheck = 1140; + TargetAttributes = { + "Mockingbird::MockingbirdGenerator" = { + LastSwiftMigration = 1140; + }; + }; }; buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Mockingbird" */; compatibilityVersion = "Xcode 3.2"; @@ -4052,6 +4098,27 @@ shellPath = /bin/sh; shellScript = "${BUILT_PRODUCTS_DIR}/MockingbirdCli generate \\\n --targets 'MockingbirdTestsHost' \\\n --outputs \"${SRCROOT}/MockingbirdMocks/MockingbirdTestsHostMocks.generated.swift\" \\\n --support \"${SRCROOT}/MockingbirdSupport\" \\\n --disable-cache \\\n --verbose\n\n# Ensure mocks are generated prior to running Compile Sources\nrm -f '/tmp/Mockingbird-718E12D6-8781-4D1E-92F1-32213EBE78AA'\n"; }; + D372A45A2454A5F00000E80A /* Generate Embedded Dylibs */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/MockingbirdCli/Libraries/lib_InternalSwiftSyntaxParser.dylib", + "", + ); + name = "Generate Embedded Dylibs"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(SRCROOT)/MockingbirdCli/Libraries/SwiftSyntaxParserDylib.generated.swift", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Scripts/generate-resource-file.sh\" \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\" 'swiftSyntaxParserDylib'\n"; + }; E0C37AF4D09E1A81AA94E903 /* Clean Mockingbird Mocks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -4593,14 +4660,17 @@ OBJ_783 /* ArgumentParser+Extensions.swift in Sources */, OBJ_784 /* GenerateCommand.swift in Sources */, OBJ_785 /* InstallCommand.swift in Sources */, + D372A4592454A5DD0000E80A /* SwiftSyntaxParserDylib.generated.swift in Sources */, OBJ_786 /* TestbedCommand.swift in Sources */, OBJ_787 /* UninstallCommand.swift in Sources */, OBJ_788 /* VersionCommand.swift in Sources */, OBJ_789 /* Generator.swift in Sources */, OBJ_790 /* Installer.swift in Sources */, + D372A45B2454A7110000E80A /* LoadDylib.swift in Sources */, OBJ_791 /* LocalizedError+Extensions.swift in Sources */, OBJ_792 /* Program.swift in Sources */, OBJ_793 /* main.swift in Sources */, + D372A45D2454ADCB0000E80A /* Resource.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4649,6 +4719,7 @@ OBJ_879 /* MockableTypeInitializerTemplate.swift in Sources */, OBJ_880 /* MockableTypeTemplate.swift in Sources */, OBJ_881 /* Template.swift in Sources */, + D372A44D2453CB2A0000E80A /* OutputStream+Extensions.swift in Sources */, OBJ_882 /* VariableTemplate.swift in Sources */, OBJ_883 /* GenericType.swift in Sources */, D3D936FA245193E000078751 /* Deallocation.swift in Sources */, @@ -6506,6 +6577,10 @@ ); INFOPLIST_FILE = Mockingbird.xcodeproj/MockingbirdGenerator_Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/MockingbirdGenerator/Libraries", + ); OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/Mockingbird.xcodeproj/GeneratedModuleMap/_CSwiftSyntax/module.modulemap"; @@ -6538,6 +6613,10 @@ ); INFOPLIST_FILE = Mockingbird.xcodeproj/MockingbirdGenerator_Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/MockingbirdGenerator/Libraries", + ); OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/Mockingbird.xcodeproj/GeneratedModuleMap/_CSwiftSyntax/module.modulemap"; diff --git a/MockingbirdCli/Interface/Program.swift b/MockingbirdCli/Interface/Program.swift index cde657a3..86d497f2 100644 --- a/MockingbirdCli/Interface/Program.swift +++ b/MockingbirdCli/Interface/Program.swift @@ -48,15 +48,18 @@ class BaseCommand: Command { struct Program { private let parser: ArgumentParser private let commands: [Command] + private let environment: [String: String] private let fileManager: FileManager init(usage: String, overview: String, commands: [Command.Type], + environment: [String: String] = ProcessInfo.processInfo.environment, fileManager: FileManager = FileManager.default) { let parser = ArgumentParser(usage: usage, overview: overview) self.parser = parser self.commands = commands.map({ $0.init(parser: parser) }) + self.environment = environment self.fileManager = fileManager } @@ -76,7 +79,6 @@ struct Program { exitStatus = 1 } } - flushLogs() return exitStatus } @@ -87,8 +89,13 @@ struct Program { return } try command.run(with: arguments, - environment: ProcessInfo.processInfo.environment, + environment: environment, workingPath: Path(fileManager.currentDirectoryPath)) } } + +func exit(_ exitStatus: Int32) -> Never { + flushLogs() + Darwin.exit(exitStatus) +} diff --git a/MockingbirdCli/Launcher/LoadDylib.swift b/MockingbirdCli/Launcher/LoadDylib.swift new file mode 100644 index 00000000..e54cf743 --- /dev/null +++ b/MockingbirdCli/Launcher/LoadDylib.swift @@ -0,0 +1,95 @@ +// +// LoadDylib.swift +// MockingbirdGenerator +// +// Created by Andrew Chang on 4/24/20. +// + +import Foundation +import PathKit +import MockingbirdGenerator + +/// Global world-writable place to output dylibs (must be added as an rpath). +private let globalLibraryDirectory = Path("/var/tmp/mockingbird/\(mockingbirdVersion)/libs/") +private let subprocessEnvironmentKey = "MKB_SUBPROCESS" + +enum LoadingError: Error, CustomStringConvertible { + case invalidSubprocessState(path: Path) + case streamCreationFailed(path: Path) + case subprocessLaunchFailed(error: Error) + + var description: String { + switch self { + case .invalidSubprocessState(let path): + return "Expected dylib to exist at \(path)" + case .streamCreationFailed(let path): + return "Failed to create dylib at \(path)" + case .subprocessLaunchFailed(let error): + return "Failed to launch subprocess after loading dylibs: \(error.localizedDescription)" + } + } +} + +/// Load embedded _dependent_ dylibs (see `generate-resource-file.sh`). +/// +/// The dylibs are linked dependent libraries added to the CLI's `globalLibraryDirectory` rpath. If +/// a dylib does not currently exist in the rpath and was created after launch, the process will +/// relaunch itself as a subprocess in order to properly trigger `__dyld_start`. The `onLoad` block +/// will not be called in this case. Use `DYLD_PRINT_RPATHS=1` to debug. +/// +/// - Parameters: +/// - resources: Dylibs to load. +/// - shouldOverwrite: Whether to overwrite an existing dylib. +/// - block: The block to call +func loadDylibs(_ dylibs: [Resource], + shouldOverwrite: Bool = false, + processInfo: ProcessInfo = ProcessInfo.processInfo, + onLoad block: () -> Void) { + let environment = processInfo.environment + + var shouldRelaunch = false + for dylib in dylibs { + let dylibPath = globalLibraryDirectory + dylib.fileName + guard !dylibPath.isFile || shouldOverwrite else { continue } + shouldRelaunch = true // Need to relaunch if even a single expected dylib is missing. + + guard environment[subprocessEnvironmentKey] != "1" else { + // Avoid creating dylibs and relaunching subprocesses infinitely if something has gone wrong. + log(LoadingError.invalidSubprocessState(path: dylibPath)) + exit(1) + } + + try? dylibPath.parent().mkpath() + + guard let outputStream = OutputStream(toFileAtPath: "\(dylibPath)", append: false) else { + log(LoadingError.streamCreationFailed(path: dylibPath)) + exit(1) + } + + outputStream.open() + outputStream.write(data: dylib.data) + outputStream.close() + } + + guard shouldRelaunch else { return block() } + + let subprocess = Process() + subprocess.executableURL = URL(fileURLWithPath: processInfo.arguments.first!, isDirectory: false) + subprocess.arguments = Array(processInfo.arguments.dropFirst()) + subprocess.qualityOfService = .userInitiated + + // Subprocesses should not try to load dylibs themselves. + var attributedEnvironment = environment + attributedEnvironment[subprocessEnvironmentKey] = "1" + subprocess.environment = attributedEnvironment + + do { + try subprocess.run() + } catch { + log(LoadingError.subprocessLaunchFailed(error: error)) + exit(1) + } + + subprocess.waitUntilExit() + exit(subprocess.terminationStatus) +} diff --git a/MockingbirdCli/Launcher/Resource.swift b/MockingbirdCli/Launcher/Resource.swift new file mode 100644 index 00000000..c7af97cf --- /dev/null +++ b/MockingbirdCli/Launcher/Resource.swift @@ -0,0 +1,14 @@ +// +// Resource.swift +// MockingbirdCli +// +// Created by Andrew Chang on 4/25/20. +// + +import Foundation + +struct Resource { + let encodedData: String + let fileName: String + var data: Data { return Data(base64Encoded: encodedData)! } +} diff --git a/MockingbirdCli/Libraries/LICENSE.txt b/MockingbirdCli/Libraries/LICENSE.txt new file mode 100644 index 00000000..004d67af --- /dev/null +++ b/MockingbirdCli/Libraries/LICENSE.txt @@ -0,0 +1,211 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +## Runtime Library Exception to the Apache 2.0 License: ## + + + As an exception, if you use this Software to compile your source code and + portions of this Software are embedded into the binary product as a result, + you may redistribute such product without providing attribution as would + otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. \ No newline at end of file diff --git a/MockingbirdCli/Libraries/lib_InternalSwiftSyntaxParser.dylib b/MockingbirdCli/Libraries/lib_InternalSwiftSyntaxParser.dylib new file mode 100755 index 00000000..0d0880d7 Binary files /dev/null and b/MockingbirdCli/Libraries/lib_InternalSwiftSyntaxParser.dylib differ diff --git a/MockingbirdCli/main.swift b/MockingbirdCli/main.swift index cac416ee..cd7e0673 100644 --- a/MockingbirdCli/main.swift +++ b/MockingbirdCli/main.swift @@ -19,4 +19,6 @@ func main(arguments: [String]) -> Int32 { return program.run(with: arguments) } -exit(main(arguments: ProcessInfo.processInfo.arguments)) +loadDylibs([swiftSyntaxParserDylib]) { + exit(main(arguments: ProcessInfo.processInfo.arguments)) +} diff --git a/MockingbirdGenerator/Parser/Operations/ParseFilesOperation.swift b/MockingbirdGenerator/Parser/Operations/ParseFilesOperation.swift index 623eb891..77bced3d 100644 --- a/MockingbirdGenerator/Parser/Operations/ParseFilesOperation.swift +++ b/MockingbirdGenerator/Parser/Operations/ParseFilesOperation.swift @@ -32,8 +32,9 @@ public class ParseFilesOperation: BasicOperation { self.checkCacheResult = checkCacheResult } - override func run() { + override func run() throws { guard checkCacheResult?.isCached != true else { return } + time(.parseFiles) { let createOperations: (SourcePath, Bool) -> [BasicOperation] = { (sourcePath, shouldMock) in let parseSourceKit = ParseSourceKitOperation(sourcePath: sourcePath) @@ -44,19 +45,24 @@ public class ParseFilesOperation: BasicOperation { swiftSyntaxResult: parseSwiftSyntax.result) parseSingleFile.addDependency(parseSourceKit) parseSingleFile.addDependency(parseSwiftSyntax) + retainForever(parseSourceKit) retainForever(parseSwiftSyntax) retainForever(parseSingleFile) + return [parseSourceKit, parseSwiftSyntax, parseSingleFile] } + + let queue = OperationQueue.createForActiveProcessors() let operations = extractSourcesResult.targetPaths.flatMap({ createOperations($0, true) }) + extractSourcesResult.dependencyPaths.flatMap({ createOperations($0, false) }) - let queue = OperationQueue.createForActiveProcessors() queue.addOperations(operations, waitUntilFinished: true) + result.parsedFiles = operations.compactMap({ operation in return (operation as? ParseSingleFileOperation)?.result.parsedFile }) } + result.imports = Set(result.parsedFiles.filter({ $0.shouldMock }).flatMap({ $0.imports })) result.moduleDependencies = extractSourcesResult.moduleDependencies } diff --git a/MockingbirdGenerator/Parser/Operations/ParseSingleFileOperation.swift b/MockingbirdGenerator/Parser/Operations/ParseSingleFileOperation.swift index 1fd5c8c5..13361014 100644 --- a/MockingbirdGenerator/Parser/Operations/ParseSingleFileOperation.swift +++ b/MockingbirdGenerator/Parser/Operations/ParseSingleFileOperation.swift @@ -73,15 +73,13 @@ class ParseSourceKitOperation: BasicOperation { } let result = Result() - override var description: String { "Parse Source Kit" } + override var description: String { "Parse SourceKit" } let sourcePath: SourcePath init(sourcePath: SourcePath) { self.sourcePath = sourcePath } - private static var memoizedParsedFiles = Synchronized<[SourcePath: ParsedFile]>([:]) - override func run() throws { let file = try sourcePath.path.getFile() result.file = file @@ -96,15 +94,13 @@ class ParseSwiftSyntaxOperation: BasicOperation { } let result = Result() - override var description: String { "Parse Swift Syntax" } + override var description: String { "Parse SwiftSyntax" } let sourcePath: SourcePath init(sourcePath: SourcePath) { self.sourcePath = sourcePath } - private static var memoizedParsedFiles = Synchronized<[SourcePath: ParsedFile]>([:]) - override func run() throws { // File reading is not shared with the parse SourceKit operation, but this is mostly CPU-bound. let file = try sourcePath.path.getFile() diff --git a/MockingbirdGenerator/Utilities/Log.swift b/MockingbirdGenerator/Utilities/Log.swift index e448a920..c13b8756 100644 --- a/MockingbirdGenerator/Utilities/Log.swift +++ b/MockingbirdGenerator/Utilities/Log.swift @@ -78,5 +78,5 @@ public func log(_ error: Error, output: UnsafeMutablePointer? = nil, file: StaticString = #file, line: UInt = #line) { - log(error.localizedDescription, type: .error, level: level, output: output, file: file, line: line) + log("\(error)", type: .error, level: level, output: output, file: file, line: line) } diff --git a/MockingbirdGenerator/Utilities/OSLog+Extensions.swift b/MockingbirdGenerator/Utilities/OSLog+Extensions.swift index 475e45d1..3505b98f 100644 --- a/MockingbirdGenerator/Utilities/OSLog+Extensions.swift +++ b/MockingbirdGenerator/Utilities/OSLog+Extensions.swift @@ -18,6 +18,7 @@ public enum SignpostType { case parseXcodeProject case extractSources case checkCache + case createDylibs case parseFiles case processTypes case renderMocks @@ -31,6 +32,7 @@ public enum SignpostType { case .parseXcodeProject: return "Parse Xcode Project" case .extractSources: return "Extract Sources" case .checkCache: return "Check Cache" + case .createDylibs: return "Create Dylibs" case .parseFiles: return "Parse Files" case .processTypes: return "Process Types" case .renderMocks: return "Render Mocks" diff --git a/MockingbirdGenerator/Utilities/OutputStream+Extensions.swift b/MockingbirdGenerator/Utilities/OutputStream+Extensions.swift new file mode 100644 index 00000000..1059cd9a --- /dev/null +++ b/MockingbirdGenerator/Utilities/OutputStream+Extensions.swift @@ -0,0 +1,21 @@ +// +// OutputStream+Extensions.swift +// MockingbirdGenerator +// +// Created by Andrew Chang on 4/24/20. +// + +import Foundation + +public extension OutputStream { + /// Write UTF-8 encoded data to this output stream. + /// + /// - Parameter data: UTF-8 encoded data to write. + /// - Returns: The number of bytes written. + @discardableResult + func write(data: Data) -> Int { + return data.withUnsafeBytes({ + write($0.bindMemory(to: UInt8.self).baseAddress!, maxLength: data.count) + }) + } +} diff --git a/Package.swift b/Package.swift index 8f83aea3..935b913f 100644 --- a/Package.swift +++ b/Package.swift @@ -3,6 +3,10 @@ import PackageDescription let package = Package( name: "Mockingbird", + platforms: [ + .macOS(.v10_14), + .iOS(.v8), + ], products: [ .library(name: "Mockingbird", targets: ["MockingbirdFramework"]), .executable(name: "mockingbird", targets: ["MockingbirdCli"]), diff --git a/Scripts/generate-resource-file.sh b/Scripts/generate-resource-file.sh new file mode 100755 index 00000000..7c629e89 --- /dev/null +++ b/Scripts/generate-resource-file.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Swift <5.3 does not support embedding arbitrary resources into the package, so this is a +# workaround that generates a Swift file containing the encoded data. + +set -e + +if [[ $# -ne 3 ]]; then + echo "Usage: generate-resource-file.sh " + exit 1 +fi + +readonly resourceFile=$1 +readonly outputFile=$2 +readonly resourceName=$3 +readonly resourceFileName=$(basename "$resourceFile") +readonly encodedResource=$(base64 -i "$resourceFile") + +cat < "${outputFile}" +// +// $outputFile +// Mockingbird +// +// Generated by generate-resource-file.sh. +// DO NOT EDIT +// + +import Foundation + +private let encodedData = #"$encodedResource"# +let $resourceName = Resource(encodedData: encodedData, fileName: "$resourceFileName") + +EOF + +echo "Generated encoded resource file" +echo " Resource file: $resourceFile" +echo " Output file: $outputFile" +echo " Resource name: $resourceName"