diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c6a0fa7..6c159f3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Moved `Frameworks` and `Products` top level groups to bottom [356](https://github.com/yonaskolb/XcodeGen/pull/356) @yonaskolb - `modulemap` files are automatically copied to the products directory for static library targets [346](https://github.com/yonaskolb/XcodeGen/pull/346) @brentleyjones - Public header files are automatically copied to the products directory for static library targets [365](https://github.com/yonaskolb/XcodeGen/pull/365) @brentleyjones +- Swift Objective-C Interface Header files are automatically copied to the products directory for static library targets [366](https://github.com/yonaskolb/XcodeGen/pull/366) @brentleyjones #### Internal - Moved brew formula to homebrew core diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index f4d74eee..de0d49c0 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -629,6 +629,30 @@ public class PBXProjGenerator { buildPhases.append(resourcesBuildPhase.reference) } + + let buildSettings = project.getCombinedBuildSettings(basePath: project.basePath, target: target, config: project.configs[0]) + let swiftObjCInterfaceHeader = buildSettings["SWIFT_OBJC_INTERFACE_HEADER_NAME"] as? String + + if target.type == .staticLibrary + && swiftObjCInterfaceHeader != "" + && sourceFiles.contains(where: { $0.buildPhase == .sources && $0.path.extension == "swift" }) { + + let inputPaths = ["$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)"] + let outputPaths = ["$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)"] + let script = createObject( + id: "Swift.h" + target.name, + PBXShellScriptBuildPhase( + files: [], + name: "Copy Swift Objective-C Interface Header", + inputPaths: inputPaths, + outputPaths: outputPaths, + shellPath: "/bin/sh", + shellScript: "ditto \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\"\n" + ) + ) + buildPhases.append(script.reference) + } + let copyFilesBuildPhasesFiles = getBuildFilesForCopyFilesPhases() if !copyFilesBuildPhasesFiles.isEmpty { for (copyFiles, buildPhaseFiles) in copyFilesBuildPhasesFiles { diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj index 500a18a3..24ebbbd8 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ BF_186245454304 /* LocalizedStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_118219888726 /* LocalizedStoryboard.storyboard */; }; BF_206432481076 /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_340586388409 /* ExtensionDelegate.swift */; }; BF_211435872001 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = VG_473000061463 /* Localizable.strings */; }; + BF_212221512596 /* StaticLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_470929579339 /* StaticLibrary.swift */; }; BF_225293845818 = {isa = PBXBuildFile; fileRef = FR_507023492251 /* App_watchOS Extension.appex */; }; BF_237760701422 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FR_987043315473 /* Assets.xcassets */; }; BF_239684316986 /* Headers in Headers */ = {isa = PBXBuildFile; fileRef = FR_815403394914 /* Headers */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -61,6 +62,7 @@ BF_503484983186 /* MoreUnder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_196911129660 /* MoreUnder.swift */; }; BF_510053944904 /* Headers in Headers */ = {isa = PBXBuildFile; fileRef = FR_815403394914 /* Headers */; settings = {ATTRIBUTES = (Public, ); }; }; BF_511197657446 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_452853029807 /* Alamofire.framework */; }; + BF_526105569599 = {isa = PBXBuildFile; fileRef = FR_437179166843 /* StaticLibrary_Swift.a */; }; BF_532503207298 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_752394658615 /* Alamofire.framework */; }; BF_538515166673 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_256263906698 /* LaunchScreen.storyboard */; }; BF_561304997165 /* Standalone.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_675266829517 /* Standalone.swift */; }; @@ -312,9 +314,11 @@ FR_399755008402 /* StaticLibrary_ObjC.a */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = archive.ar; path = StaticLibrary_ObjC.a; sourceTree = BUILT_PRODUCTS_DIR; }; FR_408537768279 /* StandaloneAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = StandaloneAssets.xcassets; sourceTree = ""; }; FR_410645050443 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Alamofire.framework; sourceTree = ""; }; + FR_437179166843 /* StaticLibrary_Swift.a */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = archive.ar; path = StaticLibrary_Swift.a; sourceTree = BUILT_PRODUCTS_DIR; }; FR_438704538506 /* Framework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FR_452853029807 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Alamofire.framework; sourceTree = ""; }; FR_461737784133 /* module.modulemap */ = {isa = PBXFileReference; path = module.modulemap; sourceTree = ""; }; + FR_470929579339 /* StaticLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLibrary.swift; sourceTree = ""; }; FR_472296042419 /* Framework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FR_473000061463 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; FR_479281060337 /* Folder */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Folder; sourceTree = SOURCE_ROOT; }; @@ -512,6 +516,14 @@ path = StaticLibrary_ObjC; sourceTree = ""; }; + G_4371791668439 /* StaticLibrary_Swift */ = { + isa = PBXGroup; + children = ( + FR_470929579339 /* StaticLibrary.swift */, + ); + path = StaticLibrary_Swift; + sourceTree = ""; + }; G_4661500274312 /* Framework */ = { isa = PBXGroup; children = ( @@ -631,6 +643,7 @@ G_7189434949822 /* Resources */, G_6651250437419 /* StandaloneFiles */, G_3997550084026 /* StaticLibrary_ObjC */, + G_4371791668439 /* StaticLibrary_Swift */, FR_479281060337 /* Folder */, FR_815403394914 /* Headers */, FR_232605427418 /* Mintfile */, @@ -652,13 +665,14 @@ FR_825232110500 /* App_iOS.app */, FR_507023492251 /* App_watchOS Extension.appex */, FR_324671077936 /* App_watchOS.app */, + FR_525119120469 /* Framework.framework */, FR_662315837182 /* Framework.framework */, FR_438704538506 /* Framework.framework */, - FR_525119120469 /* Framework.framework */, FR_472296042419 /* Framework.framework */, FR_935153865209 /* iMessageApp.app */, FR_618687462494 /* iMessageExtension.appex */, FR_399755008402 /* StaticLibrary_ObjC.a */, + FR_437179166843 /* StaticLibrary_Swift.a */, ); name = Products; sourceTree = ""; @@ -782,6 +796,22 @@ productReference = FR_399755008402 /* StaticLibrary_ObjC.a */; productType = "com.apple.product-type.library.static"; }; + NT_437179166843 /* StaticLibrary_Swift */ = { + isa = PBXNativeTarget; + buildConfigurationList = CL_437179166843 /* Build configuration list for PBXNativeTarget "StaticLibrary_Swift" */; + buildPhases = ( + SBP_43717916684 /* Sources */, + SSBP_5390042403 /* Copy Swift Objective-C Interface Header */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StaticLibrary_Swift; + productName = StaticLibrary_Swift; + productReference = FR_437179166843 /* StaticLibrary_Swift.a */; + productType = "com.apple.product-type.library.static"; + }; NT_438704538506 /* Framework_watchOS */ = { isa = PBXNativeTarget; buildConfigurationList = CL_438704538506 /* Build configuration list for PBXNativeTarget "Framework_watchOS" */; @@ -995,6 +1025,7 @@ NT_438704538506 /* Framework_watchOS */, LT_479264660374 /* Legacy */, NT_399755008402 /* StaticLibrary_ObjC */, + NT_437179166843 /* StaticLibrary_Swift */, AT_445731917037 /* SuperTarget */, NT_935153865209 /* iMessageApp */, NT_618687462494 /* iMessageExtension */, @@ -1099,6 +1130,22 @@ shellPath = /bin/sh; shellScript = "################################################################################\n#\n# Copyright 2015 Realm Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n################################################################################\n\n# This script strips all non-valid architectures from dynamic libraries in\n# the application's `Frameworks` directory.\n#\n# The following environment variables are required:\n#\n# BUILT_PRODUCTS_DIR\n# FRAMEWORKS_FOLDER_PATH\n# VALID_ARCHS\n# EXPANDED_CODE_SIGN_IDENTITY\n\n\n# Signs a framework with the provided identity\ncode_sign() {\n # Use the current code_sign_identitiy\n echo \"Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}\"\n echo \"/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --preserve-metadata=identifier,entitlements $1\"\n /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --preserve-metadata=identifier,entitlements \"$1\"\n}\n\n# Set working directory to product’s embedded frameworks\ncd \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}\"\n\nif [ \"$ACTION\" = \"install\" ]; then\n echo \"Copy .bcsymbolmap files to .xcarchive\"\n find . -name '*.bcsymbolmap' -type f -exec mv {} \"${CONFIGURATION_BUILD_DIR}\" \\;\nelse\n # Delete *.bcsymbolmap files from framework bundle unless archiving\n find . -name '*.bcsymbolmap' -type f -exec rm -rf \"{}\" +\\;\nfi\n\necho \"Stripping frameworks\"\n\nfor file in $(find . -type f -perm +111); do\n # Skip non-dynamic libraries\n if ! [[ \"$(file \"$file\")\" == *\"dynamically linked shared library\"* ]]; then\n continue\n fi\n # Get architectures for current file\n archs=\"$(lipo -info \"${file}\" | rev | cut -d ':' -f1 | rev)\"\n stripped=\"\"\n for arch in $archs; do\n if ! [[ \"${VALID_ARCHS}\" == *\"$arch\"* ]]; then\n # Strip non-valid architectures in-place\n lipo -remove \"$arch\" -output \"$file\" \"$file\" || exit 1\n stripped=\"$stripped $arch\"\n fi\n done\n if [[ \"$stripped\" != \"\" ]]; then\n echo \"Stripped $file of architectures:$stripped\"\n if [ \"${CODE_SIGNING_REQUIRED}\" == \"YES\" ]; then\n code_sign \"${file}\"\n fi\n fi\ndone\n"; }; + SSBP_5390042403 /* Copy Swift Objective-C Interface Header */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)", + ); + name = "Copy Swift Objective-C Interface Header"; + outputPaths = ( + "$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "ditto \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + }; SSBP_5954948530 /* Carthage */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1213,6 +1260,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + SBP_43717916684 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BF_212221512596 /* StaticLibrary.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; SBP_43870453850 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1664,6 +1719,17 @@ }; name = "Staging Release"; }; + BC_264139320591 /* Test Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.project.StaticLibrary-Swift"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Test Release"; + }; BC_264159662496 /* Test Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2010,6 +2076,17 @@ }; name = "Staging Debug"; }; + BC_424897564588 /* Staging Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.project.StaticLibrary-Swift"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Staging Debug"; + }; BC_437463937945 /* Production Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2061,6 +2138,17 @@ }; name = "Test Debug"; }; + BC_456712168038 /* Production Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.project.StaticLibrary-Swift"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Production Debug"; + }; BC_464603633927 /* Test Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2291,6 +2379,17 @@ }; name = "Test Release"; }; + BC_530814251927 /* Test Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.project.StaticLibrary-Swift"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Test Debug"; + }; BC_533481109982 /* Staging Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2373,6 +2472,17 @@ }; name = "Test Debug"; }; + BC_570828727916 /* Production Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.project.StaticLibrary-Swift"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Production Release"; + }; BC_575374366235 /* Test Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2595,6 +2705,17 @@ }; name = "Test Debug"; }; + BC_669628489596 /* Staging Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.project.StaticLibrary-Swift"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Staging Release"; + }; BC_685135820012 /* Test Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3246,6 +3367,19 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = ""; }; + CL_437179166843 /* Build configuration list for PBXNativeTarget "StaticLibrary_Swift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BC_456712168038 /* Production Debug */, + BC_570828727916 /* Production Release */, + BC_424897564588 /* Staging Debug */, + BC_669628489596 /* Staging Release */, + BC_530814251927 /* Test Debug */, + BC_264139320591 /* Test Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ""; + }; CL_438704538506 /* Build configuration list for PBXNativeTarget "Framework_watchOS" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Tests/Fixtures/TestProject/StaticLibrary_Swift/StaticLibrary.swift b/Tests/Fixtures/TestProject/StaticLibrary_Swift/StaticLibrary.swift new file mode 100644 index 00000000..b1c0199c --- /dev/null +++ b/Tests/Fixtures/TestProject/StaticLibrary_Swift/StaticLibrary.swift @@ -0,0 +1,6 @@ + +public struct SLSwift { + public func description() -> String { + return "Hello, World!" + } +} diff --git a/Tests/Fixtures/TestProject/project.yml b/Tests/Fixtures/TestProject/project.yml index 2b68ea95..04fbc765 100644 --- a/Tests/Fixtures/TestProject/project.yml +++ b/Tests/Fixtures/TestProject/project.yml @@ -98,7 +98,7 @@ targets: PRODUCT_BUNDLE_IDENTIFIER: com.project.app.watch.extension dependencies: - carthage: Alamofire - + iMessageApp: type: application.messages platform: iOS @@ -106,7 +106,7 @@ targets: scheme: {} dependencies: - target: iMessageExtension - + iMessageExtension: type: app-extension.messages platform: iOS @@ -119,6 +119,11 @@ targets: platform: iOS sources: StaticLibrary_ObjC + StaticLibrary_Swift: + type: library.static + platform: iOS + sources: StaticLibrary_Swift + Framework: type: framework platform: [iOS, tvOS, watchOS, macOS] diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index 7f1ba5f3..7a172a1b 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -647,6 +647,64 @@ class ProjectGeneratorTests: XCTestCase { try expect(app2OtherLinkerSettings.contains("-ObjC")) == false try expect(app3OtherLinkerSettings.contains("-ObjC")) == true } + + $0.it("copies Swfit Objective-C Interface Header") { + let swiftStaticLibraryWithHeader = Target( + name: "swiftStaticLibraryWithHeader", + type: .staticLibrary, + platform: .iOS, + sources: [TargetSource(path: "StaticLibrary_Swift/StaticLibrary.swift")], + dependencies: [] + ) + let swiftStaticLibraryWithoutHeader = Target( + name: "swiftStaticLibraryWithoutHeader", + type: .staticLibrary, + platform: .iOS, + settings: Settings(buildSettings: ["SWIFT_OBJC_INTERFACE_HEADER_NAME": ""]), + sources: [TargetSource(path: "StaticLibrary_Swift/StaticLibrary.swift")], + dependencies: [] + ) + let objCStaticLibrary = Target( + name: "objCStaticLibrary", + type: .staticLibrary, + platform: .iOS, + sources: [TargetSource(path: "StaticLibrary_ObjC/StaticLibrary_ObjC.m")], + dependencies: [] + ) + + let targets = [swiftStaticLibraryWithHeader, swiftStaticLibraryWithoutHeader, objCStaticLibrary] + + let project = Project( + basePath: fixturePath + "TestProject", + name: "test", + targets: targets, + options: SpecOptions() + ) + + let pbxProject = try project.generatePbxProj() + + func scriptBuildPhases(target: Target) throws -> [PBXShellScriptBuildPhase] { + guard let nativeTarget = pbxProject.objects.nativeTargets.referenceValues.first(where: { $0.name == target.name }) else { + throw failure("PBXNativeTarget for \(target) not found") + } + let buildPhases = nativeTarget.buildPhases + let scriptPhases = pbxProject.objects.shellScriptBuildPhases.objectReferences.filter({ buildPhases.contains($0.reference) }).map { $0.object } + return scriptPhases + } + + let expectedScriptPhase = PBXShellScriptBuildPhase( + files: [], + name: "Copy Swift Objective-C Interface Header", + inputPaths: ["$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)"], + outputPaths: ["$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)"], + shellPath: "/bin/sh", + shellScript: "ditto \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\"\n" + ) + + try expect(scriptBuildPhases(target: swiftStaticLibraryWithHeader)) == [expectedScriptPhase] + try expect(scriptBuildPhases(target: swiftStaticLibraryWithoutHeader)) == [] + try expect(scriptBuildPhases(target: objCStaticLibrary)) == [] + } $0.it("generates run scripts") { var scriptSpec = project @@ -693,7 +751,7 @@ class ProjectGeneratorTests: XCTestCase { _ = try project.generatePbxProj() } - $0.it("generates run scripts") { + $0.it("generates build rules") { var scriptSpec = project scriptSpec.targets[0].buildRules = [ BuildRule(