From 213deb8a15c9d056c9d045f6566a36041eeb313f Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 14 Sep 2019 17:15:42 +0900 Subject: [PATCH 01/36] Use pbxTarget methods to get buildableName instead of using project.yml --- Sources/XcodeGenKit/SchemeGenerator.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index df9c9d533..b2ee518da 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -81,11 +81,7 @@ public class SchemeGenerator { fatalError("Unable to find target named \"\(buildTarget.target)\" in \"PBXProj.targets\"") } - guard let buildableName = - project.getTarget(buildTarget.target)?.filename ?? - project.getAggregateTarget(buildTarget.target)?.name else { - fatalError("Unable to determinate \"buildableName\" for build target: \(buildTarget.target)") - } + let buildableName = pbxTarget.productNameWithExtension() ?? pbxTarget.name let buildableReference = XCScheme.BuildableReference( referencedContainer: "container:\(project.name).xcodeproj", blueprint: pbxTarget, From c4d63e569a717b46e4c1d5757a490e63c4da7d9f Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 14 Sep 2019 17:26:31 +0900 Subject: [PATCH 02/36] Extract projectName and pbxProj dependency from getBuildEntry --- Sources/XcodeGenKit/SchemeGenerator.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index b2ee518da..5324aa0cf 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -75,9 +75,12 @@ public class SchemeGenerator { public func generateScheme(_ scheme: Scheme) throws -> XCScheme { - func getBuildEntry(_ buildTarget: Scheme.BuildTarget) -> XCScheme.BuildAction.Entry { + func getBuildEntry( + _ buildTarget: Scheme.BuildTarget, + from project: (pbxProj: PBXProj, name: String) + ) -> XCScheme.BuildAction.Entry { - guard let pbxTarget = pbxProj.targets(named: buildTarget.target).first else { + guard let pbxTarget = project.pbxProj.targets(named: buildTarget.target).first else { fatalError("Unable to find target named \"\(buildTarget.target)\" in \"PBXProj.targets\"") } @@ -96,9 +99,10 @@ public class SchemeGenerator { Scheme.BuildTarget(target: $0.name, buildTypes: BuildType.testOnly) } - let testBuildTargetEntries = testBuildTargets.map(getBuildEntry) + let testBuildTargetEntries = testBuildTargets.map { getBuildEntry($0, from: (pbxProj, project.name)) } - let buildActionEntries: [XCScheme.BuildAction.Entry] = scheme.build.targets.map(getBuildEntry) + let buildActionEntries: [XCScheme.BuildAction.Entry] = scheme.build.targets + .map { getBuildEntry($0, from: (pbxProj, project.name)) } func getExecutionAction(_ action: Scheme.ExecutionAction) -> XCScheme.ExecutionAction { // ExecutionActions can require the use of build settings. Xcode allows the settings to come from a build or test target. From 67b3256a1bd045f8c8aefa366386f880280472e3 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 14 Sep 2019 17:59:15 +0900 Subject: [PATCH 03/36] Support external project file for build target --- Sources/ProjectSpec/Scheme.swift | 9 +++++++- Sources/XcodeGenKit/SchemeGenerator.swift | 25 ++++++++++++++--------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index 6e58be0ca..bb773db5e 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -112,6 +112,7 @@ public struct Scheme: Equatable { public static let parallelizableDefault = false public let name: String + public var externalProject: String? public var randomExecutionOrder: Bool public var parallelizable: Bool public var skippedTests: [String] @@ -220,11 +221,13 @@ public struct Scheme: Equatable { public struct BuildTarget: Equatable { public var target: String + public var externalProject: String? public var buildTypes: [BuildType] - public init(target: String, buildTypes: [BuildType] = BuildType.all) { + public init(target: String, externalProject: String? = nil, buildTypes: [BuildType] = BuildType.all) { self.target = target self.buildTypes = buildTypes + self.externalProject = externalProject } } } @@ -335,6 +338,7 @@ extension Scheme.Test.TestTarget: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { name = try jsonDictionary.json(atKeyPath: "name") + externalProject = jsonDictionary.json(atKeyPath: "externalProject") randomExecutionOrder = jsonDictionary.json(atKeyPath: "randomExecutionOrder") ?? Scheme.Test.TestTarget.randomExecutionOrderDefault parallelizable = jsonDictionary.json(atKeyPath: "parallelizable") ?? Scheme.Test.TestTarget.parallelizableDefault skippedTests = jsonDictionary.json(atKeyPath: "skippedTests") ?? [] @@ -357,6 +361,9 @@ extension Scheme.Test.TestTarget: JSONEncodable { if parallelizable != Scheme.Test.TestTarget.parallelizableDefault { dict["parallelizable"] = parallelizable } + if let externalProject = externalProject { + dict["externalProject"] = externalProject + } return dict } diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index 5324aa0cf..3c9dc8e96 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -75,18 +75,24 @@ public class SchemeGenerator { public func generateScheme(_ scheme: Scheme) throws -> XCScheme { - func getBuildEntry( - _ buildTarget: Scheme.BuildTarget, - from project: (pbxProj: PBXProj, name: String) - ) -> XCScheme.BuildAction.Entry { + func getBuildEntry(_ buildTarget: Scheme.BuildTarget) throws -> XCScheme.BuildAction.Entry { + let pbxProj: PBXProj + let projectFilename: String + if let externalProject = buildTarget.externalProject { + pbxProj = try XcodeProj(pathString: externalProject).pbxproj + projectFilename = externalProject + } else { + pbxProj = self.pbxProj + projectFilename = "\(self.project.name).xcodeproj" + } - guard let pbxTarget = project.pbxProj.targets(named: buildTarget.target).first else { + guard let pbxTarget = pbxProj.targets(named: buildTarget.target).first else { fatalError("Unable to find target named \"\(buildTarget.target)\" in \"PBXProj.targets\"") } let buildableName = pbxTarget.productNameWithExtension() ?? pbxTarget.name let buildableReference = XCScheme.BuildableReference( - referencedContainer: "container:\(project.name).xcodeproj", + referencedContainer: "container:\(projectFilename)", blueprint: pbxTarget, buildableName: buildableName, blueprintName: buildTarget.target @@ -96,13 +102,12 @@ public class SchemeGenerator { let testTargets = scheme.test?.targets ?? [] let testBuildTargets = testTargets.map { - Scheme.BuildTarget(target: $0.name, buildTypes: BuildType.testOnly) + Scheme.BuildTarget(target: $0.name, externalProject: $0.externalProject, buildTypes: BuildType.testOnly) } - let testBuildTargetEntries = testBuildTargets.map { getBuildEntry($0, from: (pbxProj, project.name)) } + let testBuildTargetEntries = try testBuildTargets.map(getBuildEntry) - let buildActionEntries: [XCScheme.BuildAction.Entry] = scheme.build.targets - .map { getBuildEntry($0, from: (pbxProj, project.name)) } + let buildActionEntries: [XCScheme.BuildAction.Entry] = try scheme.build.targets.map(getBuildEntry) func getExecutionAction(_ action: Scheme.ExecutionAction) -> XCScheme.ExecutionAction { // ExecutionActions can require the use of build settings. Xcode allows the settings to come from a build or test target. From 599a2c8eadb455ceb28230d5ef24b62f47d58ea6 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 14 Sep 2019 18:43:59 +0900 Subject: [PATCH 04/36] Add test case for scheme generation --- Sources/ProjectSpec/SpecValidation.swift | 2 +- .../xcshareddata/xcschemes/Framework.xcscheme | 10 +- .../TestProject.xcodeproj/project.pbxproj | 252 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + Tests/Fixtures/scheme_test/test_project.yml | 5 + .../SchemeGeneratorTests.swift | 31 +++ 6 files changed, 301 insertions(+), 6 deletions(-) create mode 100644 Tests/Fixtures/scheme_test/TestProject.xcodeproj/project.pbxproj create mode 100644 Tests/Fixtures/scheme_test/TestProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Tests/Fixtures/scheme_test/test_project.yml diff --git a/Sources/ProjectSpec/SpecValidation.swift b/Sources/ProjectSpec/SpecValidation.swift index fa7d2add3..8d0812564 100644 --- a/Sources/ProjectSpec/SpecValidation.swift +++ b/Sources/ProjectSpec/SpecValidation.swift @@ -170,7 +170,7 @@ extension Project { for scheme in schemes { for buildTarget in scheme.build.targets { - if getProjectTarget(buildTarget.target) == nil { + if getProjectTarget(buildTarget.target) == nil && buildTarget.externalProject == nil { errors.append(.invalidSchemeTarget(scheme: scheme.name, target: buildTarget.target)) } } diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/Framework.xcscheme b/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/Framework.xcscheme index 202f16e5a..e07ddcef3 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/Framework.xcscheme +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/Framework.xcscheme @@ -15,7 +15,7 @@ @@ -33,7 +33,7 @@ @@ -52,7 +52,7 @@ @@ -78,7 +78,7 @@ @@ -107,7 +107,7 @@ diff --git a/Tests/Fixtures/scheme_test/TestProject.xcodeproj/project.pbxproj b/Tests/Fixtures/scheme_test/TestProject.xcodeproj/project.pbxproj new file mode 100644 index 000000000..e44c33d97 --- /dev/null +++ b/Tests/Fixtures/scheme_test/TestProject.xcodeproj/project.pbxproj @@ -0,0 +1,252 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXFileReference section */ + 9194D98A5CC4C58074AED541 /* ExternalTarget.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ExternalTarget.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + 2D08B11F4EE060D112B7BCA1 = { + isa = PBXGroup; + children = ( + 5B8D13EAC88739DF2D92F8AE /* Products */, + ); + sourceTree = ""; + }; + 5B8D13EAC88739DF2D92F8AE /* Products */ = { + isa = PBXGroup; + children = ( + 9194D98A5CC4C58074AED541 /* ExternalTarget.framework */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 370BCE474732AA3FDEE3019C /* ExternalTarget */ = { + isa = PBXNativeTarget; + buildConfigurationList = 983F6F982C3E47042EB01009 /* Build configuration list for PBXNativeTarget "ExternalTarget" */; + buildPhases = ( + F568C5AE0A56AB4B50C2CDDC /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ExternalTarget; + productName = ExternalTarget; + productReference = 9194D98A5CC4C58074AED541 /* ExternalTarget.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8702E0566EC7EF0ABE948569 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + }; + buildConfigurationList = E903F6E8184E2A86CEC31778 /* Build configuration list for PBXProject "TestProject" */; + compatibilityVersion = "Xcode 10.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 2D08B11F4EE060D112B7BCA1; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 370BCE474732AA3FDEE3019C /* ExternalTarget */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + F568C5AE0A56AB4B50C2CDDC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1030670B17865EFEEDFC485B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + 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_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 2342CD0E4856A52DA47045D5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 7A68130BD0BC3B52C0C49426 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + E03B593805F20F9465213BC5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + 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_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 983F6F982C3E47042EB01009 /* Build configuration list for PBXNativeTarget "ExternalTarget" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2342CD0E4856A52DA47045D5 /* Debug */, + 7A68130BD0BC3B52C0C49426 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ""; + }; + E903F6E8184E2A86CEC31778 /* Build configuration list for PBXProject "TestProject" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E03B593805F20F9465213BC5 /* Debug */, + 1030670B17865EFEEDFC485B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8702E0566EC7EF0ABE948569 /* Project object */; +} diff --git a/Tests/Fixtures/scheme_test/TestProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Tests/Fixtures/scheme_test/TestProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Tests/Fixtures/scheme_test/TestProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Tests/Fixtures/scheme_test/test_project.yml b/Tests/Fixtures/scheme_test/test_project.yml new file mode 100644 index 000000000..846fb53f2 --- /dev/null +++ b/Tests/Fixtures/scheme_test/test_project.yml @@ -0,0 +1,5 @@ +name: TestProject +targets: + ExternalTarget: + type: framework + platform: iOS diff --git a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift index ac84c0c10..cf82460a2 100644 --- a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift @@ -199,6 +199,37 @@ class SchemeGeneratorTests: XCTestCase { try expect(xcscheme.testAction?.postActions.first?.scriptText) == "post" try expect(xcscheme.testAction?.postActions.first?.environmentBuildable?.blueprintName) == "MyApp" } + + $0.it("generates scheme using external project file") { + prepareXcodeProj: do { + let project = try! Project(path: fixturePath + "scheme_test/test_project.yml") + let generator = ProjectGenerator(project: project) + let writer = FileWriter(project: project) + let xcodeProject = try! generator.generateXcodeProject() + try! writer.writeXcodeProject(xcodeProject) + try! writer.writePlists() + } + let externalProject = fixturePath + "scheme_test/TestProject.xcodeproj" + let target = Scheme.BuildTarget(target: "ExternalTarget", externalProject: externalProject.string) + let scheme = Scheme( + name: "ExternalProjectScheme", + build: Scheme.Build(targets: [target]) + ) + let project = Project( + name: "test", + targets: [], + schemes: [scheme] + ) + let xcodeProject = try project.generateXcodeProject() + guard let xcscheme = xcodeProject.sharedData?.schemes.first else { + throw failure("Scheme not found") + } + try expect(xcscheme.buildAction?.buildActionEntries.count) == 1 + let buildableReference = xcscheme.buildAction?.buildActionEntries.first?.buildableReference + try expect(buildableReference?.blueprintName) == "ExternalTarget" + try expect(buildableReference?.referencedContainer) == "container:\(externalProject.string)" + + } } } } From 66bdcdbc0ba619c181a06d02cf645dea3a6497a7 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 14 Sep 2019 19:52:17 +0900 Subject: [PATCH 05/36] Parse externalProject in build scheme --- Sources/ProjectSpec/Scheme.swift | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index bb773db5e..8a7e7b600 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -466,9 +466,10 @@ extension Scheme.Build: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { let targetDictionary: JSONDictionary = try jsonDictionary.json(atKeyPath: "targets") var targets: [Scheme.BuildTarget] = [] - for (target, possibleBuildTypes) in targetDictionary { + for (target, possibleBuildTypesOrDict) in targetDictionary { let buildTypes: [BuildType] - if let string = possibleBuildTypes as? String { + var externalProject: String? = nil + if let string = possibleBuildTypesOrDict as? String { switch string { case "all": buildTypes = BuildType.all case "none": buildTypes = [] @@ -476,14 +477,21 @@ extension Scheme.Build: JSONObjectConvertible { case "indexing": buildTypes = [.testing, .analyzing, .archiving] default: buildTypes = BuildType.all } - } else if let enabledDictionary = possibleBuildTypes as? [String: Bool] { + } else if let enabledDictionary = possibleBuildTypesOrDict as? [String: Bool] { buildTypes = enabledDictionary.filter { $0.value }.compactMap { BuildType.from(jsonValue: $0.key) } - } else if let array = possibleBuildTypes as? [String] { + } else if let array = possibleBuildTypesOrDict as? [String] { buildTypes = array.compactMap(BuildType.from) + } else if let dict = possibleBuildTypesOrDict as? [String: Any] { + if let array = dict["types"] as? [String] { + buildTypes = array.compactMap(BuildType.from) + } else { + buildTypes = BuildType.all + } + externalProject = dict["externalProject"] as? String } else { buildTypes = BuildType.all } - targets.append(Scheme.BuildTarget(target: target, buildTypes: buildTypes)) + targets.append(Scheme.BuildTarget(target: target, externalProject: externalProject, buildTypes: buildTypes)) } self.targets = targets.sorted { $0.target < $1.target } preActions = try jsonDictionary.json(atKeyPath: "preActions")?.map(Scheme.ExecutionAction.init) ?? [] From 4e087b6960f929817731eb23225864f26ac84380 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 14 Sep 2019 20:06:25 +0900 Subject: [PATCH 06/36] Add test case for parsing externalProject in project spec --- Sources/ProjectSpec/Scheme.swift | 3 +++ Tests/XcodeGenKitTests/SpecLoadingTests.swift | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index 8a7e7b600..4796e2761 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -119,11 +119,13 @@ public struct Scheme: Equatable { public init( name: String, + externalProject: String? = nil, randomExecutionOrder: Bool = randomExecutionOrderDefault, parallelizable: Bool = parallelizableDefault, skippedTests: [String] = [] ) { self.name = name + self.externalProject = externalProject self.randomExecutionOrder = randomExecutionOrder self.parallelizable = parallelizable self.skippedTests = skippedTests @@ -131,6 +133,7 @@ public struct Scheme: Equatable { public init(stringLiteral value: String) { name = value + externalProject = nil randomExecutionOrder = false parallelizable = false skippedTests = [] diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index 3c9f38928..ed2f027a0 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -748,6 +748,10 @@ class SpecLoadingTests: XCTestCase { "Target4": ["testing": true], "Target5": ["testing": false], "Target6": ["test", "analyze"], + "Target7": [ + "externalProject": "ExternalProject.xcodeproj", + "types": ["run"], + ], ], "preActions": [ [ @@ -763,6 +767,7 @@ class SpecLoadingTests: XCTestCase { "Target1", [ "name": "Target2", + "externalProject": "ExternalProject.xcodeproj", "parallelizable": true, "randomExecutionOrder": true, "skippedTests": ["Test/testExample()"], @@ -780,6 +785,7 @@ class SpecLoadingTests: XCTestCase { Scheme.BuildTarget(target: "Target4", buildTypes: [.testing]), Scheme.BuildTarget(target: "Target5", buildTypes: []), Scheme.BuildTarget(target: "Target6", buildTypes: [.testing, .analyzing]), + Scheme.BuildTarget(target: "Target7", externalProject: "ExternalProject.xcodeproj", buildTypes: [.running]), ] try expect(scheme.name) == "Scheme" try expect(scheme.build.targets) == expectedTargets @@ -798,6 +804,7 @@ class SpecLoadingTests: XCTestCase { "Target1", Scheme.Test.TestTarget( name: "Target2", + externalProject: "ExternalProject.xcodeproj", randomExecutionOrder: true, parallelizable: true, skippedTests: ["Test/testExample()"] From 0efb1be6d77b78d9f82b227009bec384584e6814 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 14 Sep 2019 20:16:07 +0900 Subject: [PATCH 07/36] Update ProjectSpec.md --- Docs/ProjectSpec.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index 76fa3074c..65ff487a2 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -650,7 +650,7 @@ Schemes allows for more control than the convenience [Target Scheme](#target-sch ### Build -- [x] **targets**: **[String:String]** or **[String:[String]]** - A map of target names to build and which build types they should be enabled for. The build types can be `all`, `none`, or an array of the following types: +- [x] **targets**: **[String:String]**, **[String:[String]]** or **[String: [String: [Build Target](#build-target)]]** - A map of target names to build and which build types they should be enabled for. The build types can be `all`, `none`, or an array of the following types: - `run` or `running` - `test` or `testing` - `profile` or `profiling` @@ -673,6 +673,10 @@ parallelizeBuild: true buildImplicitDependencies: true ``` +### Build Target +- [ ] **types** Build types they should be enabled for. +- [ ] **externalProject**: **String** - `xcodeproj` file to reference target. + ### Common Build Action options The different actions share some properties: @@ -706,6 +710,7 @@ A multiline script can be written using the various YAML multiline methods, for - [ ] **parallelizable**: **Bool** - Whether to run tests in parallel. Defaults to false - [ ] **randomExecutionOrder**: **Bool** - Whether to run tests in a random order. Defaults to false - [ ] **skippedTests**: **[String]** - List of tests in the test target to skip. Defaults to empty. +- [ ] **externalProject**: **String** - `xcodeproj` file to reference target. ### Archive Action @@ -725,6 +730,9 @@ schemes: targets: MyTarget1: all MyTarget2: [run, archive] + MyTarget3: + types: [run, test] + externalProject: ./Submodules/OtherProject.xcodeproj run: config: prod-debug commandLineArguments: "--option value" @@ -737,6 +745,7 @@ schemes: targets: - Tester1 - name: Tester2 + externalProject: ./Submodules/OtherProject.xcodeproj parallelizable: true randomExecutionOrder: true skippedTests: [Test/testExample()] From 8e378ae55b8de0219616f79262e745e9dd0c2f35 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 14 Sep 2019 20:17:53 +0900 Subject: [PATCH 08/36] CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d113e138..f5c529bae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ #### Added - Added `includes` to `sources` for a Target. This follows the same glob-style as `excludes` but functions as a way to only include files that match a specified pattern. Useful if you only want a certain file type, for example specifying `**/*.swift`. [#637](https://github.com/yonaskolb/XcodeGen/pull/637) @bclymer -- Support `dylib` SDK. [#650](https://github.com/yonaskolb/XcodeGen/pull/650) +- Support `dylib` SDK. [#650](https://github.com/yonaskolb/XcodeGen/pull/650) @kateinoigakukun +- Support External Target Reference. [#655](https://github.com/yonaskolb/XcodeGen/pull/655) @kateinoigakukun #### Fixed From e4844e927c5862f3546bebc672803f671653ed8c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 16 Sep 2019 00:53:21 +0900 Subject: [PATCH 09/36] Fix default value JSON encoding --- Sources/ProjectSpec/Scheme.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index dcc9d82b0..4f859a52c 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -370,7 +370,9 @@ extension Scheme.Test.TestTarget: JSONObjectConvertible { extension Scheme.Test.TestTarget: JSONEncodable { public func toJSONValue() -> Any { - if !randomExecutionOrder && !parallelizable { + if randomExecutionOrder == Scheme.Test.TestTarget.randomExecutionOrderDefault, + parallelizable == Scheme.Test.TestTarget.parallelizableDefault, + externalProject == nil { return name } From 30fc642b04eca07ee8339deb76bf583b59ae3e62 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 23 Sep 2019 00:23:11 +0900 Subject: [PATCH 10/36] Introduce ProjectName/Target syntax to reference target --- Sources/ProjectSpec/Project.swift | 45 ++++++++- Sources/ProjectSpec/Scheme.swift | 111 +++++++++++++-------- Sources/ProjectSpec/SpecParsingError.swift | 3 + Sources/ProjectSpec/SpecValidation.swift | 5 +- Sources/ProjectSpec/TargetScheme.swift | 2 +- Sources/XcodeGenKit/SchemeGenerator.swift | 28 +++--- 6 files changed, 138 insertions(+), 56 deletions(-) diff --git a/Sources/ProjectSpec/Project.swift b/Sources/ProjectSpec/Project.swift index adfdf0291..ee97e2e67 100644 --- a/Sources/ProjectSpec/Project.swift +++ b/Sources/ProjectSpec/Project.swift @@ -28,9 +28,15 @@ public struct Project: BuildSettingsContainer { public var fileGroups: [String] public var configFiles: [String: String] public var include: [String] = [] + public var externalProjects: [ExternalProject] = [] { + didSet { + externalProjectsMap = Dictionary(uniqueKeysWithValues: externalProjects.map { ($0.name, $0) }) + } + } private var targetsMap: [String: Target] private var aggregateTargetsMap: [String: AggregateTarget] + private var externalProjectsMap: [String: ExternalProject] public init( basePath: Path = "", @@ -44,7 +50,8 @@ public struct Project: BuildSettingsContainer { options: SpecOptions = SpecOptions(), fileGroups: [String] = [], configFiles: [String: String] = [:], - attributes: [String: Any] = [:] + attributes: [String: Any] = [:], + externalProjects: [ExternalProject] = [] ) { self.basePath = basePath self.name = name @@ -60,6 +67,12 @@ public struct Project: BuildSettingsContainer { self.fileGroups = fileGroups self.configFiles = configFiles self.attributes = attributes + self.externalProjects = externalProjects + externalProjectsMap = Dictionary(uniqueKeysWithValues: self.externalProjects.map { ($0.name, $0) }) + } + + public func getExternalProject(_ projectName: String) -> ExternalProject? { + return externalProjectsMap[projectName] } public func getTarget(_ targetName: String) -> Target? { @@ -155,6 +168,7 @@ extension Project { configs.map { Config(name: $0, type: ConfigType(rawValue: $1)) }.sorted { $0.name < $1.name } targets = try jsonDictionary.json(atKeyPath: "targets").sorted { $0.name < $1.name } aggregateTargets = try jsonDictionary.json(atKeyPath: "aggregateTargets").sorted { $0.name < $1.name } + externalProjects = try jsonDictionary.json(atKeyPath: "externalProjects").sorted { $0.name < $1.name } schemes = try jsonDictionary.json(atKeyPath: "schemes") fileGroups = jsonDictionary.json(atKeyPath: "fileGroups") ?? [] configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:] @@ -167,6 +181,7 @@ extension Project { } targetsMap = Dictionary(uniqueKeysWithValues: targets.map { ($0.name, $0) }) aggregateTargetsMap = Dictionary(uniqueKeysWithValues: aggregateTargets.map { ($0.name, $0) }) + externalProjectsMap = Dictionary(uniqueKeysWithValues: externalProjects.map { ($0.name, $0) }) } static func resolveProject(jsonDictionary: JSONDictionary) throws -> JSONDictionary { @@ -241,6 +256,7 @@ extension Project: JSONEncodable { let configsPairs = configs.map { ($0.name, $0.type?.rawValue) } let aggregateTargetsPairs = aggregateTargets.map { ($0.name, $0.toJSONValue()) } let schemesPairs = schemes.map { ($0.name, $0.toJSONValue()) } + let externalProjectsPairs = externalProjects.map { ($0.name, $0.toJSONValue()) } return [ "name": name, @@ -255,6 +271,33 @@ extension Project: JSONEncodable { "aggregateTargets": Dictionary(uniqueKeysWithValues: aggregateTargetsPairs), "schemes": Dictionary(uniqueKeysWithValues: schemesPairs), "settingGroups": settingGroups.mapValues { $0.toJSONValue() }, + "externalProjects": externalProjectsPairs, + ] + } +} + + +public struct ExternalProject { + public let name: String + public let path: String + + public init(name: String, path: String) { + self.name = name + self.path = path + } +} + +extension ExternalProject: NamedJSONDictionaryConvertible { + public init(name: String, jsonDictionary: JSONDictionary) throws { + self.name = name + self.path = try jsonDictionary.json(atKeyPath: "path") + } +} + +extension ExternalProject: JSONEncodable { + public func toJSONValue() -> Any { + return [ + "path": path, ] } } diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index 4f859a52c..243bd5da0 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -119,32 +119,33 @@ public struct Scheme: Equatable { public static let randomExecutionOrderDefault = false public static let parallelizableDefault = false - public let name: String - public var externalProject: String? + public var name: String { return targetReference.name } + public let targetReference: TargetReference public var randomExecutionOrder: Bool public var parallelizable: Bool public var skippedTests: [String] public init( - name: String, - externalProject: String? = nil, + targetReference: TargetReference, randomExecutionOrder: Bool = randomExecutionOrderDefault, parallelizable: Bool = parallelizableDefault, skippedTests: [String] = [] ) { - self.name = name - self.externalProject = externalProject + self.targetReference = targetReference self.randomExecutionOrder = randomExecutionOrder self.parallelizable = parallelizable self.skippedTests = skippedTests } public init(stringLiteral value: String) { - name = value - externalProject = nil - randomExecutionOrder = false - parallelizable = false - skippedTests = [] + do { + targetReference = try TargetReference(string: value) + randomExecutionOrder = false + parallelizable = false + skippedTests = [] + } catch { + fatalError(SpecParsingError.invalidTargetReference(value).description) + } } } @@ -235,14 +236,56 @@ public struct Scheme: Equatable { } public struct BuildTarget: Equatable { - public var target: String - public var externalProject: String? + public var target: TargetReference public var buildTypes: [BuildType] - public init(target: String, externalProject: String? = nil, buildTypes: [BuildType] = BuildType.all) { + public init(target: TargetReference, buildTypes: [BuildType] = BuildType.all) { self.target = target self.buildTypes = buildTypes - self.externalProject = externalProject + } + } +} + + +public struct TargetReference: Equatable { + public let name: String + public let location: Location + + public enum Location: Equatable { + case local + case project(String) + } + + public init(name: String, location: Location = .local) { + self.name = name + self.location = location + } +} + +extension TargetReference { + public init(string: String) throws { + let paths = string.split(separator: "/") + guard paths.count <= 2 && !paths.isEmpty else { + throw SpecParsingError.invalidTargetReference(string) + } + switch paths.count { + case 2: + location = .project(String(paths[0])) + name = String(paths[1]) + case 1: + location = .local + name = String(paths[0]) + default: fatalError("unreachable") + } + } +} + +extension TargetReference { + public func toString() -> String { + switch location { + case .local: return name + case .project(let projectPath): + return "\(projectPath)/\(name)" } } } @@ -314,7 +357,7 @@ extension Scheme.Test: JSONObjectConvertible { if let targets = jsonDictionary["targets"] as? [Any] { self.targets = try targets.compactMap { target in if let string = target as? String { - return TestTarget(name: string) + return TestTarget(stringLiteral: string) } else if let dictionary = target as? JSONDictionary { return try TestTarget(jsonDictionary: dictionary) } else { @@ -360,8 +403,7 @@ extension Scheme.Test: JSONEncodable { extension Scheme.Test.TestTarget: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { - name = try jsonDictionary.json(atKeyPath: "name") - externalProject = jsonDictionary.json(atKeyPath: "externalProject") + targetReference = try TargetReference(string: jsonDictionary.json(atKeyPath: "name")) randomExecutionOrder = jsonDictionary.json(atKeyPath: "randomExecutionOrder") ?? Scheme.Test.TestTarget.randomExecutionOrderDefault parallelizable = jsonDictionary.json(atKeyPath: "parallelizable") ?? Scheme.Test.TestTarget.parallelizableDefault skippedTests = jsonDictionary.json(atKeyPath: "skippedTests") ?? [] @@ -371,13 +413,12 @@ extension Scheme.Test.TestTarget: JSONObjectConvertible { extension Scheme.Test.TestTarget: JSONEncodable { public func toJSONValue() -> Any { if randomExecutionOrder == Scheme.Test.TestTarget.randomExecutionOrderDefault, - parallelizable == Scheme.Test.TestTarget.parallelizableDefault, - externalProject == nil { - return name + parallelizable == Scheme.Test.TestTarget.parallelizableDefault { + return targetReference.toString() } var dict: JSONDictionary = [ - "name": name, + "name": targetReference.toString(), ] if randomExecutionOrder != Scheme.Test.TestTarget.randomExecutionOrderDefault { @@ -386,9 +427,6 @@ extension Scheme.Test.TestTarget: JSONEncodable { if parallelizable != Scheme.Test.TestTarget.parallelizableDefault { dict["parallelizable"] = parallelizable } - if let externalProject = externalProject { - dict["externalProject"] = externalProject - } return dict } @@ -491,10 +529,9 @@ extension Scheme.Build: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { let targetDictionary: JSONDictionary = try jsonDictionary.json(atKeyPath: "targets") var targets: [Scheme.BuildTarget] = [] - for (target, possibleBuildTypesOrDict) in targetDictionary { + for (targetRepr, possibleBuildTypes) in targetDictionary { let buildTypes: [BuildType] - var externalProject: String? = nil - if let string = possibleBuildTypesOrDict as? String { + if let string = possibleBuildTypes as? String { switch string { case "all": buildTypes = BuildType.all case "none": buildTypes = [] @@ -502,23 +539,17 @@ extension Scheme.Build: JSONObjectConvertible { case "indexing": buildTypes = [.testing, .analyzing, .archiving] default: buildTypes = BuildType.all } - } else if let enabledDictionary = possibleBuildTypesOrDict as? [String: Bool] { + } else if let enabledDictionary = possibleBuildTypes as? [String: Bool] { buildTypes = enabledDictionary.filter { $0.value }.compactMap { BuildType.from(jsonValue: $0.key) } - } else if let array = possibleBuildTypesOrDict as? [String] { + } else if let array = possibleBuildTypes as? [String] { buildTypes = array.compactMap(BuildType.from) - } else if let dict = possibleBuildTypesOrDict as? [String: Any] { - if let array = dict["types"] as? [String] { - buildTypes = array.compactMap(BuildType.from) - } else { - buildTypes = BuildType.all - } - externalProject = dict["externalProject"] as? String } else { buildTypes = BuildType.all } - targets.append(Scheme.BuildTarget(target: target, externalProject: externalProject, buildTypes: buildTypes)) + let target = try TargetReference(string: targetRepr) + targets.append(Scheme.BuildTarget(target: target, buildTypes: buildTypes)) } - self.targets = targets.sorted { $0.target < $1.target } + self.targets = targets.sorted { $0.target.name < $1.target.name } preActions = try jsonDictionary.json(atKeyPath: "preActions")?.map(Scheme.ExecutionAction.init) ?? [] postActions = try jsonDictionary.json(atKeyPath: "postActions")?.map(Scheme.ExecutionAction.init) ?? [] parallelizeBuild = jsonDictionary.json(atKeyPath: "parallelizeBuild") ?? Scheme.Build.parallelizeBuildDefault @@ -528,7 +559,7 @@ extension Scheme.Build: JSONObjectConvertible { extension Scheme.Build: JSONEncodable { public func toJSONValue() -> Any { - let targetPairs = targets.map { ($0.target, $0.buildTypes.map { $0.toJSONValue() }) } + let targetPairs = targets.map { ($0.target.toString(), $0.buildTypes.map { $0.toJSONValue() }) } var dict: JSONDictionary = [ "targets": Dictionary(uniqueKeysWithValues: targetPairs), diff --git a/Sources/ProjectSpec/SpecParsingError.swift b/Sources/ProjectSpec/SpecParsingError.swift index 5d48d4d0b..9c252dca8 100644 --- a/Sources/ProjectSpec/SpecParsingError.swift +++ b/Sources/ProjectSpec/SpecParsingError.swift @@ -5,6 +5,7 @@ public enum SpecParsingError: Error, CustomStringConvertible { case unknownTargetPlatform(String) case invalidDependency([String: Any]) case invalidSourceBuildPhase(String) + case invalidTargetReference(String) case invalidVersion(String) public var description: String { @@ -17,6 +18,8 @@ public enum SpecParsingError: Error, CustomStringConvertible { return "Unknown Target dependency: \(dependency)" case let .invalidSourceBuildPhase(error): return "Invalid Source Build Phase: \(error)" + case let .invalidTargetReference(targetReference): + return "Invalid Target Reference Syntax: \(targetReference)" case let .invalidVersion(version): return "Invalid version: \(version)" } diff --git a/Sources/ProjectSpec/SpecValidation.swift b/Sources/ProjectSpec/SpecValidation.swift index 8d0812564..eae613a62 100644 --- a/Sources/ProjectSpec/SpecValidation.swift +++ b/Sources/ProjectSpec/SpecValidation.swift @@ -170,8 +170,9 @@ extension Project { for scheme in schemes { for buildTarget in scheme.build.targets { - if getProjectTarget(buildTarget.target) == nil && buildTarget.externalProject == nil { - errors.append(.invalidSchemeTarget(scheme: scheme.name, target: buildTarget.target)) + guard buildTarget.target.location == .local else { continue } + if getProjectTarget(buildTarget.target.name) == nil { + errors.append(.invalidSchemeTarget(scheme: scheme.name, target: buildTarget.target.name)) } } if let action = scheme.run, let config = action.config, getConfig(config) == nil { diff --git a/Sources/ProjectSpec/TargetScheme.swift b/Sources/ProjectSpec/TargetScheme.swift index c4c4673ec..2bb186227 100644 --- a/Sources/ProjectSpec/TargetScheme.swift +++ b/Sources/ProjectSpec/TargetScheme.swift @@ -42,7 +42,7 @@ extension TargetScheme: JSONObjectConvertible { if let targets = jsonDictionary["testTargets"] as? [Any] { testTargets = try targets.compactMap { target in if let string = target as? String { - return .init(name: string) + return .init(stringLiteral: string) } else if let dictionary = target as? JSONDictionary { return try .init(jsonDictionary: dictionary) } else { diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index 8f214e885..e9f2bddf5 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -77,32 +77,36 @@ public class SchemeGenerator { func getBuildEntry(_ buildTarget: Scheme.BuildTarget) throws -> XCScheme.BuildAction.Entry { let pbxProj: PBXProj - let projectFilename: String - if let externalProject = buildTarget.externalProject { - pbxProj = try XcodeProj(pathString: externalProject).pbxproj - projectFilename = externalProject - } else { + let projectFilePath: String + switch buildTarget.target.location { + case .project(let project): + guard let externalProject = self.project.getExternalProject(project) else { + fatalError("Unable to find external project named \"\(project)\" in project.yml") + } + pbxProj = try XcodeProj(pathString: externalProject.path).pbxproj + projectFilePath = externalProject.path + case .local: pbxProj = self.pbxProj - projectFilename = "\(self.project.name).xcodeproj" + projectFilePath = "\(self.project.name).xcodeproj" } - guard let pbxTarget = pbxProj.targets(named: buildTarget.target).first else { + guard let pbxTarget = pbxProj.targets(named: buildTarget.target.name).first else { fatalError("Unable to find target named \"\(buildTarget.target)\" in \"PBXProj.targets\"") } let buildableName = pbxTarget.productNameWithExtension() ?? pbxTarget.name let buildableReference = XCScheme.BuildableReference( - referencedContainer: "container:\(projectFilename)", + referencedContainer: "container:\(projectFilePath)", blueprint: pbxTarget, buildableName: buildableName, - blueprintName: buildTarget.target + blueprintName: buildTarget.target.name ) return XCScheme.BuildAction.Entry(buildableReference: buildableReference, buildFor: buildTarget.buildTypes) } let testTargets = scheme.test?.targets ?? [] let testBuildTargets = testTargets.map { - Scheme.BuildTarget(target: $0.name, externalProject: $0.externalProject, buildTypes: BuildType.testOnly) + Scheme.BuildTarget(target: TargetReference(name: $0.name, location: .local), buildTypes: BuildType.testOnly) } let testBuildTargetEntries = try testBuildTargets.map(getBuildEntry) @@ -119,7 +123,7 @@ public class SchemeGenerator { return XCScheme.ExecutionAction(scriptText: action.script, title: action.name, environmentBuildable: environmentBuildable) } - let target = project.getTarget(scheme.build.targets.first!.target) + let target = project.getTarget(scheme.build.targets.first!.target.name) let shouldExecuteOnLaunch = target?.type.isExecutable == true let buildableReference = buildActionEntries.first!.buildableReference @@ -217,7 +221,7 @@ extension Scheme { public init(name: String, target: Target, targetScheme: TargetScheme, debugConfig: String, releaseConfig: String) { self.init( name: name, - build: .init(targets: [Scheme.BuildTarget(target: target.name)]), + build: .init(targets: [Scheme.BuildTarget(target: TargetReference(name: target.name, location: .local))]), run: .init( config: debugConfig, commandLineArguments: targetScheme.commandLineArguments, From 5f02e6883bc204d5ffc97883c14e8243702f554f Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 23 Sep 2019 00:23:51 +0900 Subject: [PATCH 11/36] Fix test cases for external project --- Tests/XcodeGenKitTests/ProjectSpecTests.swift | 12 ++++----- .../SchemeGeneratorTests.swift | 12 +++++---- Tests/XcodeGenKitTests/SpecLoadingTests.swift | 25 ++++++++----------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/Tests/XcodeGenKitTests/ProjectSpecTests.swift b/Tests/XcodeGenKitTests/ProjectSpecTests.swift index 362a657ec..19daf4d3d 100644 --- a/Tests/XcodeGenKitTests/ProjectSpecTests.swift +++ b/Tests/XcodeGenKitTests/ProjectSpecTests.swift @@ -201,7 +201,7 @@ class ProjectSpecTests: XCTestCase { var project = baseProject project.schemes = [Scheme( name: "scheme1", - build: .init(targets: [.init(target: "invalidTarget")]), + build: .init(targets: [.init(target: .init(name: "invalidTarget", location: .local))]), run: .init(config: "debugInvalid"), archive: .init(config: "releaseInvalid") )] @@ -249,7 +249,7 @@ class ProjectSpecTests: XCTestCase { attributes: [:] ) project.aggregateTargets = [aggregatedTarget] - let buildTarget = Scheme.BuildTarget(target: "target1") + let buildTarget = Scheme.BuildTarget(target: .init(name: "target1")) let scheme = Scheme(name: "target1-Scheme", build: Scheme.Build(targets: [buildTarget])) project.schemes = [scheme] try project.validate() @@ -332,7 +332,7 @@ class ProjectSpecTests: XCTestCase { name: nil, outputFiles: ["bar"], outputFilesCompilerFlags: ["foo"])], - scheme: TargetScheme(testTargets: [Scheme.Test.TestTarget(name: "test target", + scheme: TargetScheme(testTargets: [Scheme.Test.TestTarget(targetReference: .init(name: "test target"), randomExecutionOrder: false, parallelizable: false)], configVariants: ["foo"], @@ -370,7 +370,7 @@ class ProjectSpecTests: XCTestCase { shell: "/bin/bash", runOnlyWhenInstalling: true, showEnvVars: false)], - scheme: TargetScheme(testTargets: [Scheme.Test.TestTarget(name: "test target", + scheme: TargetScheme(testTargets: [Scheme.Test.TestTarget(targetReference: .init(name: "test target"), randomExecutionOrder: false, parallelizable: false)], configVariants: ["foo"], @@ -398,7 +398,7 @@ class ProjectSpecTests: XCTestCase { groups: ["config-setting-group"])], groups: ["setting-group"])], schemes: [Scheme(name: "scheme", - build: Scheme.Build(targets: [Scheme.BuildTarget(target: "foo", + build: Scheme.Build(targets: [Scheme.BuildTarget(target: .init(name: "foo"), buildTypes: [.archiving, .analyzing])], parallelizeBuild: false, buildImplicitDependencies: false, @@ -425,7 +425,7 @@ class ProjectSpecTests: XCTestCase { randomExecutionOrder: false, parallelizable: false, commandLineArguments: ["foo": true], - targets: [Scheme.Test.TestTarget(name: "foo", + targets: [Scheme.Test.TestTarget(targetReference: .init(name: "foo"), randomExecutionOrder: false, parallelizable: false)], preActions: [Scheme.ExecutionAction(name: "preAction", diff --git a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift index cf82460a2..9d828fd49 100644 --- a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift @@ -37,7 +37,7 @@ class SchemeGeneratorTests: XCTestCase { func testSchemes() { describe { - let buildTarget = Scheme.BuildTarget(target: app.name) + let buildTarget = Scheme.BuildTarget(target: .init(name: app.name, location: .local)) $0.it("generates scheme") { let preAction = Scheme.ExecutionAction(name: "Script", script: "echo Starting", settingsTarget: app.name) let scheme = Scheme( @@ -209,8 +209,9 @@ class SchemeGeneratorTests: XCTestCase { try! writer.writeXcodeProject(xcodeProject) try! writer.writePlists() } - let externalProject = fixturePath + "scheme_test/TestProject.xcodeproj" - let target = Scheme.BuildTarget(target: "ExternalTarget", externalProject: externalProject.string) + let externalProjectPath = fixturePath + "scheme_test/TestProject.xcodeproj" + let externalProject = ExternalProject(name: "ExternalProject", path: externalProjectPath.string) + let target = Scheme.BuildTarget(target: .init(name: "ExternalTarget", location: .project("ExternalProject"))) let scheme = Scheme( name: "ExternalProjectScheme", build: Scheme.Build(targets: [target]) @@ -218,7 +219,8 @@ class SchemeGeneratorTests: XCTestCase { let project = Project( name: "test", targets: [], - schemes: [scheme] + schemes: [scheme], + externalProjects: [externalProject] ) let xcodeProject = try project.generateXcodeProject() guard let xcscheme = xcodeProject.sharedData?.schemes.first else { @@ -227,7 +229,7 @@ class SchemeGeneratorTests: XCTestCase { try expect(xcscheme.buildAction?.buildActionEntries.count) == 1 let buildableReference = xcscheme.buildAction?.buildActionEntries.first?.buildableReference try expect(buildableReference?.blueprintName) == "ExternalTarget" - try expect(buildableReference?.referencedContainer) == "container:\(externalProject.string)" + try expect(buildableReference?.referencedContainer) == "container:\(externalProjectPath.string)" } } diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index ed2f027a0..011a5d44c 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -748,10 +748,7 @@ class SpecLoadingTests: XCTestCase { "Target4": ["testing": true], "Target5": ["testing": false], "Target6": ["test", "analyze"], - "Target7": [ - "externalProject": "ExternalProject.xcodeproj", - "types": ["run"], - ], + "ExternalProject/Target7": ["run"], ], "preActions": [ [ @@ -766,8 +763,7 @@ class SpecLoadingTests: XCTestCase { "targets": [ "Target1", [ - "name": "Target2", - "externalProject": "ExternalProject.xcodeproj", + "name": "ExternalProject/Target2", "parallelizable": true, "randomExecutionOrder": true, "skippedTests": ["Test/testExample()"], @@ -779,13 +775,13 @@ class SpecLoadingTests: XCTestCase { ] let scheme = try Scheme(name: "Scheme", jsonDictionary: schemeDictionary) let expectedTargets: [Scheme.BuildTarget] = [ - Scheme.BuildTarget(target: "Target1", buildTypes: BuildType.all), - Scheme.BuildTarget(target: "Target2", buildTypes: [.testing, .analyzing]), - Scheme.BuildTarget(target: "Target3", buildTypes: []), - Scheme.BuildTarget(target: "Target4", buildTypes: [.testing]), - Scheme.BuildTarget(target: "Target5", buildTypes: []), - Scheme.BuildTarget(target: "Target6", buildTypes: [.testing, .analyzing]), - Scheme.BuildTarget(target: "Target7", externalProject: "ExternalProject.xcodeproj", buildTypes: [.running]), + Scheme.BuildTarget(target: .init(name: "Target1"), buildTypes: BuildType.all), + Scheme.BuildTarget(target: .init(name: "Target2"), buildTypes: [.testing, .analyzing]), + Scheme.BuildTarget(target: .init(name: "Target3"), buildTypes: []), + Scheme.BuildTarget(target: .init(name: "Target4"), buildTypes: [.testing]), + Scheme.BuildTarget(target: .init(name: "Target5"), buildTypes: []), + Scheme.BuildTarget(target: .init(name: "Target6"), buildTypes: [.testing, .analyzing]), + Scheme.BuildTarget(target: .init(name: "Target7", location: .project("ExternalProject")), buildTypes: [.running]), ] try expect(scheme.name) == "Scheme" try expect(scheme.build.targets) == expectedTargets @@ -803,8 +799,7 @@ class SpecLoadingTests: XCTestCase { targets: [ "Target1", Scheme.Test.TestTarget( - name: "Target2", - externalProject: "ExternalProject.xcodeproj", + targetReference: .init(name: "Target2", location: .project("ExternalProject")), randomExecutionOrder: true, parallelizable: true, skippedTests: ["Test/testExample()"] From 1bf6d7672e22c271016081a13fef65e3ca8e45a1 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 30 Sep 2019 17:10:17 +0900 Subject: [PATCH 12/36] Revert unnecesasry changes --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1b53defa..01a29e16d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,9 @@ - Added support for Swift Package dependencies [#624](https://github.com/yonaskolb/XcodeGen/pull/624) @yonaskolb - Added `includes` to `sources` for a Target. This follows the same glob-style as `excludes` but functions as a way to only include files that match a specified pattern. Useful if you only want a certain file type, for example specifying `**/*.swift`. [#637](https://github.com/yonaskolb/XcodeGen/pull/637) @bclymer -- Support `dylib` SDK. [#650](https://github.com/yonaskolb/XcodeGen/pull/650) @kateinoigakukun -- Added `language` and `region` options for `run` and `test` scheme [#654](https://github.com/yonaskolb/XcodeGen/pull/654) @kateinoigakukun -- Added `debugEnabled` option for `run` and `test` scheme [#657](https://github.com/yonaskolb/XcodeGen/pull/657) @kateinoigakukun +- Support `dylib` SDK. [#650](https://github.com/yonaskolb/XcodeGen/pull/650) @kateinoigakukun +- Added `language` and `region` options for `run` and `test` scheme [#654](https://github.com/yonaskolb/XcodeGen/pull/654) @kateinoigakukun +- Added `debugEnabled` option for `run` and `test` scheme [#657](https://github.com/yonaskolb/XcodeGen/pull/657) @kateinoigakukun #### Fixed From 7d180522ad5c5cb4777a06c234746607c949f4a9 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 30 Sep 2019 17:11:58 +0900 Subject: [PATCH 13/36] Revert "Update ProjectSpec.md" This reverts commit 0efb1be6d77b78d9f82b227009bec384584e6814. --- Docs/ProjectSpec.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index 925f1524c..0321f2226 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -674,7 +674,7 @@ Schemes allows for more control than the convenience [Target Scheme](#target-sch ### Build -- [x] **targets**: **[String:String]**, **[String:[String]]** or **[String: [String: [Build Target](#build-target)]]** - A map of target names to build and which build types they should be enabled for. The build types can be `all`, `none`, or an array of the following types: +- [x] **targets**: **[String:String]** or **[String:[String]]** - A map of target names to build and which build types they should be enabled for. The build types can be `all`, `none`, or an array of the following types: - `run` or `running` - `test` or `testing` - `profile` or `profiling` @@ -697,10 +697,6 @@ parallelizeBuild: true buildImplicitDependencies: true ``` -### Build Target -- [ ] **types** Build types they should be enabled for. -- [ ] **externalProject**: **String** - `xcodeproj` file to reference target. - ### Common Build Action options The different actions share some properties: @@ -737,7 +733,6 @@ A multiline script can be written using the various YAML multiline methods, for - [ ] **parallelizable**: **Bool** - Whether to run tests in parallel. Defaults to false - [ ] **randomExecutionOrder**: **Bool** - Whether to run tests in a random order. Defaults to false - [ ] **skippedTests**: **[String]** - List of tests in the test target to skip. Defaults to empty. -- [ ] **externalProject**: **String** - `xcodeproj` file to reference target. ### Archive Action @@ -757,9 +752,6 @@ schemes: targets: MyTarget1: all MyTarget2: [run, archive] - MyTarget3: - types: [run, test] - externalProject: ./Submodules/OtherProject.xcodeproj run: config: prod-debug commandLineArguments: "--option value" @@ -772,7 +764,6 @@ schemes: targets: - Tester1 - name: Tester2 - externalProject: ./Submodules/OtherProject.xcodeproj parallelizable: true randomExecutionOrder: true skippedTests: [Test/testExample()] From 566d5382fdc1a6a21a3d689c1253545b5e45772e Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 30 Sep 2019 18:36:07 +0900 Subject: [PATCH 14/36] Update docs --- Docs/ProjectSpec.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index 0321f2226..13bdd49c3 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -49,6 +49,7 @@ You can also use environment variables in your configuration file, by using `${S - [ ] **targetTemplates**: **[String: [Target Template](#target-template)]** - a list of targets that can be used as templates for actual targets which reference them via a `template` property. They can be used to extract common target settings. Works great in combination with `include`. - [ ] **packages**: **[String: [Swift Package](#swift-package)]** - a map of Swift packages by name - [ ] **localPackages**: **[String]** - A list of paths to local Swift Packages. The paths must be directories with a `Package.swift` file in them. This is used to override `packages` with a local version for development purposes. +- [ ] **externalProjects**: **[String: [External Project](#external-project)]** - a map of external projects by name ### Include @@ -804,4 +805,21 @@ targets: App: dependencies: - package: Yams -``` \ No newline at end of file +``` + +## External Project + +External project references are defined at a project level, and then you can use the project name to refer its target via a [Scheme](#scheme) + +- [x] **path**: **String** - The path to the `xcodeproj` file to reference. + +```yml +externalProjects: + YamsProject: + path: ./Carthage/Checkouts/Yams/Yams.xcodeproj +schemes: + TestTarget: + build: + targets: + YamsProject/Yams: ["run"] +``` From 2ec09a77d9e3294a11efef28bcb723dffe73d89c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 30 Sep 2019 20:22:15 +0900 Subject: [PATCH 15/36] Update fixtures --- .../scheme_test/TestProject.xcodeproj/project.pbxproj | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Tests/Fixtures/scheme_test/TestProject.xcodeproj/project.pbxproj b/Tests/Fixtures/scheme_test/TestProject.xcodeproj/project.pbxproj index e44c33d97..adfd87b16 100644 --- a/Tests/Fixtures/scheme_test/TestProject.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/scheme_test/TestProject.xcodeproj/project.pbxproj @@ -140,7 +140,10 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; @@ -158,7 +161,10 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; From 236c7c4f13271b4c3dc96c280d7b9cb8f328a243 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 10 Oct 2019 23:49:41 +0900 Subject: [PATCH 16/36] Add Hashable conformance for TargetReference --- Sources/ProjectSpec/Scheme.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index 5afe8a205..710930d4c 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -255,11 +255,11 @@ public struct Scheme: Equatable { } -public struct TargetReference: Equatable { +public struct TargetReference: Equatable, Hashable { public let name: String public let location: Location - public enum Location: Equatable { + public enum Location: Equatable, Hashable { case local case project(String) } From d1bed7191cedf94e7876d5c51c6872fee8dc08c7 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 10 Oct 2019 23:57:55 +0900 Subject: [PATCH 17/36] Fix unit test build error for TargetReference --- Tests/XcodeGenKitTests/SchemeGeneratorTests.swift | 2 +- Tests/XcodeGenKitTests/SpecLoadingTests.swift | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift index 2d23c19de..050cd7656 100644 --- a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift @@ -109,7 +109,7 @@ class SchemeGeneratorTests: XCTestCase { name: "MyFramework", type: .application, platform: .iOS, - scheme: TargetScheme(testTargets: [.init(name: "MyFrameworkTests")]) + scheme: TargetScheme(testTargets: ["MyFrameworkTests"]) ) let project = Project( name: "test", diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index a180bb0c3..8b65e0ca4 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -937,12 +937,12 @@ class SpecLoadingTests: XCTestCase { let scheme = project.schemes.first! let expectedTargets: [Scheme.BuildTarget] = [ - Scheme.BuildTarget(target: "TargetFirstTarget", buildTypes: BuildType.all), - Scheme.BuildTarget(target: "Target2", buildTypes: [.testing, .analyzing]), - Scheme.BuildTarget(target: "TargetThirdTarget", buildTypes: []), - Scheme.BuildTarget(target: "Target4", buildTypes: [.testing]), - Scheme.BuildTarget(target: "Target5", buildTypes: []), - Scheme.BuildTarget(target: "Target6", buildTypes: [.testing, .analyzing]), + Scheme.BuildTarget(target: .init(name: "TargetFirstTarget"), buildTypes: BuildType.all), + Scheme.BuildTarget(target: .init(name: "Target2"), buildTypes: [.testing, .analyzing]), + Scheme.BuildTarget(target: .init(name: "TargetThirdTarget"), buildTypes: []), + Scheme.BuildTarget(target: .init(name: "Target4"), buildTypes: [.testing]), + Scheme.BuildTarget(target: .init(name: "Target5"), buildTypes: []), + Scheme.BuildTarget(target: .init(name: "Target6"), buildTypes: [.testing, .analyzing]), ] try expect(scheme.name) == "temp2" try expect(Set(scheme.build.targets)) == Set(expectedTargets) @@ -960,7 +960,7 @@ class SpecLoadingTests: XCTestCase { targets: [ "TargetFirstTarget", Scheme.Test.TestTarget( - name: "Target2", + targetReference: .init(name: "Target2"), randomExecutionOrder: true, parallelizable: true, skippedTests: ["Test/testExample()"] From d978cf4df2a11570ede45bf338a7123060139458 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 15 Oct 2019 15:08:30 +0900 Subject: [PATCH 18/36] Rename ExternalProject -> ProjectReference --- Sources/ProjectSpec/Project.swift | 50 +++++-------------- Sources/ProjectSpec/ProjectReference.swift | 34 +++++++++++++ Sources/XcodeGenKit/SchemeGenerator.swift | 8 +-- .../SchemeGeneratorTests.swift | 4 +- 4 files changed, 52 insertions(+), 44 deletions(-) create mode 100644 Sources/ProjectSpec/ProjectReference.swift diff --git a/Sources/ProjectSpec/Project.swift b/Sources/ProjectSpec/Project.swift index a08c5f09c..1a4d16a39 100644 --- a/Sources/ProjectSpec/Project.swift +++ b/Sources/ProjectSpec/Project.swift @@ -31,15 +31,15 @@ public struct Project: BuildSettingsContainer { public var fileGroups: [String] public var configFiles: [String: String] public var include: [String] = [] - public var externalProjects: [ExternalProject] = [] { + public var projectReferences: [ProjectReference] = [] { didSet { - externalProjectsMap = Dictionary(uniqueKeysWithValues: externalProjects.map { ($0.name, $0) }) + projectReferencesMap = Dictionary(uniqueKeysWithValues: projectReferences.map { ($0.name, $0) }) } } private var targetsMap: [String: Target] private var aggregateTargetsMap: [String: AggregateTarget] - private var externalProjectsMap: [String: ExternalProject] + private var projectReferencesMap: [String: ProjectReference] public init( basePath: Path = "", @@ -56,7 +56,7 @@ public struct Project: BuildSettingsContainer { fileGroups: [String] = [], configFiles: [String: String] = [:], attributes: [String: Any] = [:], - externalProjects: [ExternalProject] = [] + projectReferences: [ProjectReference] = [] ) { self.basePath = basePath self.name = name @@ -74,12 +74,12 @@ public struct Project: BuildSettingsContainer { self.fileGroups = fileGroups self.configFiles = configFiles self.attributes = attributes - self.externalProjects = externalProjects - externalProjectsMap = Dictionary(uniqueKeysWithValues: self.externalProjects.map { ($0.name, $0) }) + self.projectReferences = projectReferences + projectReferencesMap = Dictionary(uniqueKeysWithValues: self.projectReferences.map { ($0.name, $0) }) } - public func getExternalProject(_ projectName: String) -> ExternalProject? { - return externalProjectsMap[projectName] + public func getProjectReference(_ projectName: String) -> ProjectReference? { + return projectReferencesMap[projectName] } public func getTarget(_ targetName: String) -> Target? { @@ -177,7 +177,7 @@ extension Project { configs.map { Config(name: $0, type: ConfigType(rawValue: $1)) }.sorted { $0.name < $1.name } targets = try jsonDictionary.json(atKeyPath: "targets").sorted { $0.name < $1.name } aggregateTargets = try jsonDictionary.json(atKeyPath: "aggregateTargets").sorted { $0.name < $1.name } - externalProjects = try jsonDictionary.json(atKeyPath: "externalProjects").sorted { $0.name < $1.name } + projectReferences = try jsonDictionary.json(atKeyPath: "projectReferences").sorted { $0.name < $1.name } schemes = try jsonDictionary.json(atKeyPath: "schemes") fileGroups = jsonDictionary.json(atKeyPath: "fileGroups") ?? [] configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:] @@ -196,7 +196,7 @@ extension Project { } targetsMap = Dictionary(uniqueKeysWithValues: targets.map { ($0.name, $0) }) aggregateTargetsMap = Dictionary(uniqueKeysWithValues: aggregateTargets.map { ($0.name, $0) }) - externalProjectsMap = Dictionary(uniqueKeysWithValues: externalProjects.map { ($0.name, $0) }) + projectReferencesMap = Dictionary(uniqueKeysWithValues: projectReferences.map { ($0.name, $0) }) } static func resolveProject(jsonDictionary: JSONDictionary) -> JSONDictionary { @@ -273,7 +273,7 @@ extension Project: JSONEncodable { let configsPairs = configs.map { ($0.name, $0.type?.rawValue) } let aggregateTargetsPairs = aggregateTargets.map { ($0.name, $0.toJSONValue()) } let schemesPairs = schemes.map { ($0.name, $0.toJSONValue()) } - let externalProjectsPairs = externalProjects.map { ($0.name, $0.toJSONValue()) } + let projectReferencesPairs = projectReferences.map { ($0.name, $0.toJSONValue()) } var dictionary: JSONDictionary = [:] dictionary["name"] = name @@ -290,34 +290,8 @@ extension Project: JSONEncodable { dictionary["aggregateTargets"] = Dictionary(uniqueKeysWithValues: aggregateTargetsPairs) dictionary["schemes"] = Dictionary(uniqueKeysWithValues: schemesPairs) dictionary["settingGroups"] = settingGroups.mapValues { $0.toJSONValue() } - dictionary["externalProjects"] = externalProjectsPairs + dictionary["projectReferences"] = projectReferencesPairs return dictionary } } - - -public struct ExternalProject { - public let name: String - public let path: String - - public init(name: String, path: String) { - self.name = name - self.path = path - } -} - -extension ExternalProject: NamedJSONDictionaryConvertible { - public init(name: String, jsonDictionary: JSONDictionary) throws { - self.name = name - self.path = try jsonDictionary.json(atKeyPath: "path") - } -} - -extension ExternalProject: JSONEncodable { - public func toJSONValue() -> Any { - return [ - "path": path, - ] - } -} diff --git a/Sources/ProjectSpec/ProjectReference.swift b/Sources/ProjectSpec/ProjectReference.swift new file mode 100644 index 000000000..662c4ad85 --- /dev/null +++ b/Sources/ProjectSpec/ProjectReference.swift @@ -0,0 +1,34 @@ +// +// ProjectReference.swift +// ProjectSpec +// +// Created by Yuta Saito on 2019/10/15. +// + +import Foundation +import JSONUtilities + +public struct ProjectReference { + public let name: String + public let path: String + + public init(name: String, path: String) { + self.name = name + self.path = path + } +} + +extension ProjectReference: NamedJSONDictionaryConvertible { + public init(name: String, jsonDictionary: JSONDictionary) throws { + self.name = name + self.path = try jsonDictionary.json(atKeyPath: "path") + } +} + +extension ProjectReference: JSONEncodable { + public func toJSONValue() -> Any { + return [ + "path": path, + ] + } +} diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index 863cc273f..0d86456e1 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -88,11 +88,11 @@ public class SchemeGenerator { let projectFilePath: String switch buildTarget.target.location { case .project(let project): - guard let externalProject = self.project.getExternalProject(project) else { - fatalError("Unable to find external project named \"\(project)\" in project.yml") + guard let projectReference = self.project.getProjectReference(project) else { + fatalError("Unable to find project reference named \"\(project)\" in project.yml") } - pbxProj = try XcodeProj(pathString: externalProject.path).pbxproj - projectFilePath = externalProject.path + pbxProj = try XcodeProj(pathString: projectReference.path).pbxproj + projectFilePath = projectReference.path case .local: pbxProj = self.pbxProj projectFilePath = "\(self.project.name).xcodeproj" diff --git a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift index 050cd7656..2ce566c5c 100644 --- a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift @@ -270,7 +270,7 @@ class SchemeGeneratorTests: XCTestCase { try! writer.writePlists() } let externalProjectPath = fixturePath + "scheme_test/TestProject.xcodeproj" - let externalProject = ExternalProject(name: "ExternalProject", path: externalProjectPath.string) + let projectReference = ProjectReference(name: "ExternalProject", path: externalProjectPath.string) let target = Scheme.BuildTarget(target: .init(name: "ExternalTarget", location: .project("ExternalProject"))) let scheme = Scheme( name: "ExternalProjectScheme", @@ -280,7 +280,7 @@ class SchemeGeneratorTests: XCTestCase { name: "test", targets: [], schemes: [scheme], - externalProjects: [externalProject] + projectReferences: [projectReference] ) let xcodeProject = try project.generateXcodeProject() guard let xcscheme = xcodeProject.sharedData?.schemes.first else { From 55613785da23a826277f1097da9c94c13e05c128 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 15 Oct 2019 15:12:44 +0900 Subject: [PATCH 19/36] Move TargetReference to it's own file --- Sources/ProjectSpec/Scheme.swift | 44 ------------------- Sources/ProjectSpec/TargetReference.swift | 52 +++++++++++++++++++++++ 2 files changed, 52 insertions(+), 44 deletions(-) create mode 100644 Sources/ProjectSpec/TargetReference.swift diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index 710930d4c..448f4c59b 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -254,50 +254,6 @@ public struct Scheme: Equatable { } } - -public struct TargetReference: Equatable, Hashable { - public let name: String - public let location: Location - - public enum Location: Equatable, Hashable { - case local - case project(String) - } - - public init(name: String, location: Location = .local) { - self.name = name - self.location = location - } -} - -extension TargetReference { - public init(string: String) throws { - let paths = string.split(separator: "/") - guard paths.count <= 2 && !paths.isEmpty else { - throw SpecParsingError.invalidTargetReference(string) - } - switch paths.count { - case 2: - location = .project(String(paths[0])) - name = String(paths[1]) - case 1: - location = .local - name = String(paths[0]) - default: fatalError("unreachable") - } - } -} - -extension TargetReference { - public func toString() -> String { - switch location { - case .local: return name - case .project(let projectPath): - return "\(projectPath)/\(name)" - } - } -} - protocol BuildAction: Equatable { var config: String? { get } } diff --git a/Sources/ProjectSpec/TargetReference.swift b/Sources/ProjectSpec/TargetReference.swift new file mode 100644 index 000000000..3b7629b26 --- /dev/null +++ b/Sources/ProjectSpec/TargetReference.swift @@ -0,0 +1,52 @@ +// +// TargetReference.swift +// ProjectSpec +// +// Created by Yuta Saito on 2019/10/15. +// + +import Foundation +import JSONUtilities + +public struct TargetReference: Equatable, Hashable { + public let name: String + public let location: Location + + public enum Location: Equatable, Hashable { + case local + case project(String) + } + + public init(name: String, location: Location = .local) { + self.name = name + self.location = location + } +} + +extension TargetReference { + public init(string: String) throws { + let paths = string.split(separator: "/") + guard paths.count <= 2 && !paths.isEmpty else { + throw SpecParsingError.invalidTargetReference(string) + } + switch paths.count { + case 2: + location = .project(String(paths[0])) + name = String(paths[1]) + case 1: + location = .local + name = String(paths[0]) + default: fatalError("unreachable") + } + } +} + +extension TargetReference { + public func toString() -> String { + switch location { + case .local: return name + case .project(let projectPath): + return "\(projectPath)/\(name)" + } + } +} From e0dfc72f854417148de5c58d7dbcc6e8ba272341 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 15 Oct 2019 15:15:25 +0900 Subject: [PATCH 20/36] Conform TargetReference to CustomStringConvertible --- Sources/ProjectSpec/Scheme.swift | 6 +++--- Sources/ProjectSpec/TargetReference.swift | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index 448f4c59b..151bb1d8e 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -387,11 +387,11 @@ extension Scheme.Test.TestTarget: JSONEncodable { public func toJSONValue() -> Any { if randomExecutionOrder == Scheme.Test.TestTarget.randomExecutionOrderDefault, parallelizable == Scheme.Test.TestTarget.parallelizableDefault { - return targetReference.toString() + return targetReference.reference } var dict: JSONDictionary = [ - "name": targetReference.toString(), + "name": targetReference.reference, ] if randomExecutionOrder != Scheme.Test.TestTarget.randomExecutionOrderDefault { @@ -532,7 +532,7 @@ extension Scheme.Build: JSONObjectConvertible { extension Scheme.Build: JSONEncodable { public func toJSONValue() -> Any { - let targetPairs = targets.map { ($0.target.toString(), $0.buildTypes.map { $0.toJSONValue() }) } + let targetPairs = targets.map { ($0.target.reference, $0.buildTypes.map { $0.toJSONValue() }) } var dict: JSONDictionary = [ "targets": Dictionary(uniqueKeysWithValues: targetPairs), diff --git a/Sources/ProjectSpec/TargetReference.swift b/Sources/ProjectSpec/TargetReference.swift index 3b7629b26..b6c1fc38f 100644 --- a/Sources/ProjectSpec/TargetReference.swift +++ b/Sources/ProjectSpec/TargetReference.swift @@ -41,12 +41,16 @@ extension TargetReference { } } -extension TargetReference { - public func toString() -> String { +extension TargetReference: CustomStringConvertible { + public var reference: String { switch location { case .local: return name case .project(let projectPath): return "\(projectPath)/\(name)" } } + + public var description: String { + return reference + } } From 1c6409266b326da8c67e076fa4d0fddc4cfa8205 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 15 Oct 2019 15:27:42 +0900 Subject: [PATCH 21/36] Improve TargetReference initializer interface --- Sources/ProjectSpec/TargetReference.swift | 12 +++++++- Sources/ProjectSpec/TargetScheme.swift | 2 +- Tests/XcodeGenKitTests/ProjectSpecTests.swift | 12 ++++---- .../SchemeGeneratorTests.swift | 2 +- Tests/XcodeGenKitTests/SpecLoadingTests.swift | 30 +++++++++---------- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/Sources/ProjectSpec/TargetReference.swift b/Sources/ProjectSpec/TargetReference.swift index b6c1fc38f..1cf3bacab 100644 --- a/Sources/ProjectSpec/TargetReference.swift +++ b/Sources/ProjectSpec/TargetReference.swift @@ -17,7 +17,7 @@ public struct TargetReference: Equatable, Hashable { case project(String) } - public init(name: String, location: Location = .local) { + public init(name: String, location: Location) { self.name = name self.location = location } @@ -39,6 +39,16 @@ extension TargetReference { default: fatalError("unreachable") } } + + public static func local(_ name: String) -> TargetReference { + return TargetReference(name: name, location: .local) + } +} + +extension TargetReference: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + try! self.init(string: value) + } } extension TargetReference: CustomStringConvertible { diff --git a/Sources/ProjectSpec/TargetScheme.swift b/Sources/ProjectSpec/TargetScheme.swift index 2bb186227..5fbb70c55 100644 --- a/Sources/ProjectSpec/TargetScheme.swift +++ b/Sources/ProjectSpec/TargetScheme.swift @@ -42,7 +42,7 @@ extension TargetScheme: JSONObjectConvertible { if let targets = jsonDictionary["testTargets"] as? [Any] { testTargets = try targets.compactMap { target in if let string = target as? String { - return .init(stringLiteral: string) + return .init(targetReference: try TargetReference(string: string)) } else if let dictionary = target as? JSONDictionary { return try .init(jsonDictionary: dictionary) } else { diff --git a/Tests/XcodeGenKitTests/ProjectSpecTests.swift b/Tests/XcodeGenKitTests/ProjectSpecTests.swift index 379efd24f..2eb9bd946 100644 --- a/Tests/XcodeGenKitTests/ProjectSpecTests.swift +++ b/Tests/XcodeGenKitTests/ProjectSpecTests.swift @@ -207,7 +207,7 @@ class ProjectSpecTests: XCTestCase { var project = baseProject project.schemes = [Scheme( name: "scheme1", - build: .init(targets: [.init(target: .init(name: "invalidTarget", location: .local))]), + build: .init(targets: [.init(target: "invalidTarget")]), run: .init(config: "debugInvalid"), archive: .init(config: "releaseInvalid") )] @@ -255,7 +255,7 @@ class ProjectSpecTests: XCTestCase { attributes: [:] ) project.aggregateTargets = [aggregatedTarget] - let buildTarget = Scheme.BuildTarget(target: .init(name: "target1")) + let buildTarget = Scheme.BuildTarget(target: "target1") let scheme = Scheme(name: "target1-Scheme", build: Scheme.Build(targets: [buildTarget])) project.schemes = [scheme] try project.validate() @@ -338,7 +338,7 @@ class ProjectSpecTests: XCTestCase { name: nil, outputFiles: ["bar"], outputFilesCompilerFlags: ["foo"])], - scheme: TargetScheme(testTargets: [Scheme.Test.TestTarget(targetReference: .init(name: "test target"), + scheme: TargetScheme(testTargets: [Scheme.Test.TestTarget(targetReference: "test target", randomExecutionOrder: false, parallelizable: false)], configVariants: ["foo"], @@ -376,7 +376,7 @@ class ProjectSpecTests: XCTestCase { shell: "/bin/bash", runOnlyWhenInstalling: true, showEnvVars: false)], - scheme: TargetScheme(testTargets: [Scheme.Test.TestTarget(targetReference: .init(name: "test target"), + scheme: TargetScheme(testTargets: [Scheme.Test.TestTarget(targetReference: "test target", randomExecutionOrder: false, parallelizable: false)], configVariants: ["foo"], @@ -404,7 +404,7 @@ class ProjectSpecTests: XCTestCase { groups: ["config-setting-group"])], groups: ["setting-group"])], schemes: [Scheme(name: "scheme", - build: Scheme.Build(targets: [Scheme.BuildTarget(target: .init(name: "foo"), + build: Scheme.Build(targets: [Scheme.BuildTarget(target: "foo", buildTypes: [.archiving, .analyzing])], parallelizeBuild: false, buildImplicitDependencies: false, @@ -431,7 +431,7 @@ class ProjectSpecTests: XCTestCase { randomExecutionOrder: false, parallelizable: false, commandLineArguments: ["foo": true], - targets: [Scheme.Test.TestTarget(targetReference: .init(name: "foo"), + targets: [Scheme.Test.TestTarget(targetReference: "foo", randomExecutionOrder: false, parallelizable: false)], preActions: [Scheme.ExecutionAction(name: "preAction", diff --git a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift index 2ce566c5c..6d274d992 100644 --- a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift @@ -43,7 +43,7 @@ class SchemeGeneratorTests: XCTestCase { func testSchemes() { describe { - let buildTarget = Scheme.BuildTarget(target: .init(name: app.name, location: .local)) + let buildTarget = Scheme.BuildTarget(target: .local(app.name)) $0.it("generates scheme") { let preAction = Scheme.ExecutionAction(name: "Script", script: "echo Starting", settingsTarget: app.name) let scheme = Scheme( diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index 8b65e0ca4..3f6563ed9 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -775,13 +775,13 @@ class SpecLoadingTests: XCTestCase { ] let scheme = try Scheme(name: "Scheme", jsonDictionary: schemeDictionary) let expectedTargets: [Scheme.BuildTarget] = [ - Scheme.BuildTarget(target: .init(name: "Target1"), buildTypes: BuildType.all), - Scheme.BuildTarget(target: .init(name: "Target2"), buildTypes: [.testing, .analyzing]), - Scheme.BuildTarget(target: .init(name: "Target3"), buildTypes: []), - Scheme.BuildTarget(target: .init(name: "Target4"), buildTypes: [.testing]), - Scheme.BuildTarget(target: .init(name: "Target5"), buildTypes: []), - Scheme.BuildTarget(target: .init(name: "Target6"), buildTypes: [.testing, .analyzing]), - Scheme.BuildTarget(target: .init(name: "Target7", location: .project("ExternalProject")), buildTypes: [.running]), + Scheme.BuildTarget(target: "Target1", buildTypes: BuildType.all), + Scheme.BuildTarget(target: "Target2", buildTypes: [.testing, .analyzing]), + Scheme.BuildTarget(target: "Target3", buildTypes: []), + Scheme.BuildTarget(target: "Target4", buildTypes: [.testing]), + Scheme.BuildTarget(target: "Target5", buildTypes: []), + Scheme.BuildTarget(target: "Target6", buildTypes: [.testing, .analyzing]), + Scheme.BuildTarget(target: "ExternalProject/Target7", buildTypes: [.running]), ] try expect(scheme.name) == "Scheme" try expect(scheme.build.targets) == expectedTargets @@ -799,7 +799,7 @@ class SpecLoadingTests: XCTestCase { targets: [ "Target1", Scheme.Test.TestTarget( - targetReference: .init(name: "Target2", location: .project("ExternalProject")), + targetReference: "ExternalProject/Target2", randomExecutionOrder: true, parallelizable: true, skippedTests: ["Test/testExample()"] @@ -937,12 +937,12 @@ class SpecLoadingTests: XCTestCase { let scheme = project.schemes.first! let expectedTargets: [Scheme.BuildTarget] = [ - Scheme.BuildTarget(target: .init(name: "TargetFirstTarget"), buildTypes: BuildType.all), - Scheme.BuildTarget(target: .init(name: "Target2"), buildTypes: [.testing, .analyzing]), - Scheme.BuildTarget(target: .init(name: "TargetThirdTarget"), buildTypes: []), - Scheme.BuildTarget(target: .init(name: "Target4"), buildTypes: [.testing]), - Scheme.BuildTarget(target: .init(name: "Target5"), buildTypes: []), - Scheme.BuildTarget(target: .init(name: "Target6"), buildTypes: [.testing, .analyzing]), + Scheme.BuildTarget(target: "TargetFirstTarget", buildTypes: BuildType.all), + Scheme.BuildTarget(target: "Target2", buildTypes: [.testing, .analyzing]), + Scheme.BuildTarget(target: "TargetThirdTarget", buildTypes: []), + Scheme.BuildTarget(target: "Target4", buildTypes: [.testing]), + Scheme.BuildTarget(target: "Target5", buildTypes: []), + Scheme.BuildTarget(target: "Target6", buildTypes: [.testing, .analyzing]), ] try expect(scheme.name) == "temp2" try expect(Set(scheme.build.targets)) == Set(expectedTargets) @@ -960,7 +960,7 @@ class SpecLoadingTests: XCTestCase { targets: [ "TargetFirstTarget", Scheme.Test.TestTarget( - targetReference: .init(name: "Target2"), + targetReference: "Target2", randomExecutionOrder: true, parallelizable: true, skippedTests: ["Test/testExample()"] From c2ab781acbe9074513110f7aad6c1a1be62ae2f1 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 15 Oct 2019 15:47:28 +0900 Subject: [PATCH 22/36] Fix buildableName generation logic --- Sources/XcodeGenKit/SchemeGenerator.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index 0d86456e1..a034caf69 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -102,7 +102,20 @@ public class SchemeGenerator { fatalError("Unable to find target named \"\(buildTarget.target)\" in \"PBXProj.targets\"") } - let buildableName = pbxTarget.productNameWithExtension() ?? pbxTarget.name + let buildableName: String + + switch buildTarget.target.location { + case .project: + buildableName = pbxTarget.productNameWithExtension() ?? pbxTarget.name + case .local: + guard let _buildableName = + project.getTarget(buildTarget.target.name)?.filename ?? + project.getAggregateTarget(buildTarget.target.name)?.name else { + fatalError("Unable to determinate \"buildableName\" for build target: \(buildTarget.target)") + } + buildableName = _buildableName + } + let buildableReference = XCScheme.BuildableReference( referencedContainer: "container:\(projectFilePath)", blueprint: pbxTarget, From f49a172117a660ebd615bd34b4105e1ba98e8902 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 15 Oct 2019 15:47:40 +0900 Subject: [PATCH 23/36] Update Fixture --- .../xcshareddata/xcschemes/Framework.xcscheme | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/Framework.xcscheme b/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/Framework.xcscheme index ecc9364bd..85c9e4989 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/Framework.xcscheme +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/Framework.xcscheme @@ -15,7 +15,7 @@ @@ -33,7 +33,7 @@ @@ -54,7 +54,7 @@ @@ -80,7 +80,7 @@ @@ -109,7 +109,7 @@ From 552af503510242e2b4038b85dab1bd500dab8b77 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 15 Oct 2019 15:52:52 +0900 Subject: [PATCH 24/36] Replace External Project with Project Reference in ProjectSpec.md --- Docs/ProjectSpec.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index 12e0fd2dd..d496fcb7a 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -50,7 +50,7 @@ You can also use environment variables in your configuration file, by using `${S - [ ] **targetTemplates**: **[String: [Target Template](#target-template)]** - a list of targets that can be used as templates for actual targets which reference them via a `template` property. They can be used to extract common target settings. Works great in combination with `include`. - [ ] **packages**: **[String: [Swift Package](#swift-package)]** - a map of Swift packages by name - [ ] **localPackages**: **[String]** - A list of paths to local Swift Packages. The paths must be directories with a `Package.swift` file in them. This is used to override `packages` with a local version for development purposes. -- [ ] **externalProjects**: **[String: [External Project](#external-project)]** - a map of external projects by name +- [ ] **projectReferences**: **[String: [Project Reference](#project-reference)]** - a map of project references by name ### Include @@ -842,14 +842,14 @@ targets: ``` -## External Project +## Project Reference -External project references are defined at a project level, and then you can use the project name to refer its target via a [Scheme](#scheme) +Project References are defined at a project level, and then you can use the project name to refer its target via a [Scheme](#scheme) - [x] **path**: **String** - The path to the `xcodeproj` file to reference. ```yml -externalProjects: +projectReferences: YamsProject: path: ./Carthage/Checkouts/Yams/Yams.xcodeproj schemes: From d7864ffba0c450bd377fad84d81c755edd640a1b Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 15 Oct 2019 15:55:45 +0900 Subject: [PATCH 25/36] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e55d5b90c..5b19298b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Next Version #### Added -- Support External Target Reference. [#655](https://github.com/yonaskolb/XcodeGen/pull/655) @kateinoigakukun +- Support Target Reference to another project. [#655](https://github.com/yonaskolb/XcodeGen/pull/655) @kateinoigakukun ## 2.9.0 From 4d8ffe76cb54945cf719692630714cc1a6c8ec10 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 21 Oct 2019 18:01:05 +0900 Subject: [PATCH 26/36] Remove file header --- Sources/ProjectSpec/ProjectReference.swift | 7 ------- Sources/ProjectSpec/TargetReference.swift | 7 ------- 2 files changed, 14 deletions(-) diff --git a/Sources/ProjectSpec/ProjectReference.swift b/Sources/ProjectSpec/ProjectReference.swift index 662c4ad85..f6d598045 100644 --- a/Sources/ProjectSpec/ProjectReference.swift +++ b/Sources/ProjectSpec/ProjectReference.swift @@ -1,10 +1,3 @@ -// -// ProjectReference.swift -// ProjectSpec -// -// Created by Yuta Saito on 2019/10/15. -// - import Foundation import JSONUtilities diff --git a/Sources/ProjectSpec/TargetReference.swift b/Sources/ProjectSpec/TargetReference.swift index 1cf3bacab..fd2ce761b 100644 --- a/Sources/ProjectSpec/TargetReference.swift +++ b/Sources/ProjectSpec/TargetReference.swift @@ -1,10 +1,3 @@ -// -// TargetReference.swift -// ProjectSpec -// -// Created by Yuta Saito on 2019/10/15. -// - import Foundation import JSONUtilities From 7075777f99a2ba9b6c478d54e7be7b5cc55bb2ab Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 21 Oct 2019 18:02:58 +0900 Subject: [PATCH 27/36] Make 'let' properties as 'var' --- Sources/ProjectSpec/ProjectReference.swift | 4 ++-- Sources/ProjectSpec/TargetReference.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/ProjectSpec/ProjectReference.swift b/Sources/ProjectSpec/ProjectReference.swift index f6d598045..126d31d59 100644 --- a/Sources/ProjectSpec/ProjectReference.swift +++ b/Sources/ProjectSpec/ProjectReference.swift @@ -2,8 +2,8 @@ import Foundation import JSONUtilities public struct ProjectReference { - public let name: String - public let path: String + public var name: String + public var path: String public init(name: String, path: String) { self.name = name diff --git a/Sources/ProjectSpec/TargetReference.swift b/Sources/ProjectSpec/TargetReference.swift index fd2ce761b..fd556eed9 100644 --- a/Sources/ProjectSpec/TargetReference.swift +++ b/Sources/ProjectSpec/TargetReference.swift @@ -2,8 +2,8 @@ import Foundation import JSONUtilities public struct TargetReference: Equatable, Hashable { - public let name: String - public let location: Location + public var name: String + public var location: Location public enum Location: Equatable, Hashable { case local From 70cb2df5f7d557b69f5806b4e7125c61e1363c4a Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 21 Oct 2019 18:04:12 +0900 Subject: [PATCH 28/36] Minimize protocol conformance declarations --- Sources/ProjectSpec/TargetReference.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ProjectSpec/TargetReference.swift b/Sources/ProjectSpec/TargetReference.swift index fd556eed9..46a61de3d 100644 --- a/Sources/ProjectSpec/TargetReference.swift +++ b/Sources/ProjectSpec/TargetReference.swift @@ -1,11 +1,11 @@ import Foundation import JSONUtilities -public struct TargetReference: Equatable, Hashable { +public struct TargetReference: Hashable { public var name: String public var location: Location - public enum Location: Equatable, Hashable { + public enum Location: Hashable { case local case project(String) } From c432337ef737475e5c712b834c9a9298116bf016 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 21 Oct 2019 18:32:49 +0900 Subject: [PATCH 29/36] Throw error instead of fatalError --- Sources/ProjectSpec/TargetReference.swift | 6 ++---- Sources/XcodeGenKit/SchemeGenerator.swift | 24 ++++++++++++++++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Sources/ProjectSpec/TargetReference.swift b/Sources/ProjectSpec/TargetReference.swift index 46a61de3d..afff27c09 100644 --- a/Sources/ProjectSpec/TargetReference.swift +++ b/Sources/ProjectSpec/TargetReference.swift @@ -19,9 +19,6 @@ public struct TargetReference: Hashable { extension TargetReference { public init(string: String) throws { let paths = string.split(separator: "/") - guard paths.count <= 2 && !paths.isEmpty else { - throw SpecParsingError.invalidTargetReference(string) - } switch paths.count { case 2: location = .project(String(paths[0])) @@ -29,7 +26,8 @@ extension TargetReference { case 1: location = .local name = String(paths[0]) - default: fatalError("unreachable") + default: + throw SpecParsingError.invalidTargetReference(string) } } diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index a034caf69..06f057d59 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -89,7 +89,7 @@ public class SchemeGenerator { switch buildTarget.target.location { case .project(let project): guard let projectReference = self.project.getProjectReference(project) else { - fatalError("Unable to find project reference named \"\(project)\" in project.yml") + throw SchemeGenerationError.missingProject(project) } pbxProj = try XcodeProj(pathString: projectReference.path).pbxproj projectFilePath = projectReference.path @@ -99,7 +99,7 @@ public class SchemeGenerator { } guard let pbxTarget = pbxProj.targets(named: buildTarget.target.name).first else { - fatalError("Unable to find target named \"\(buildTarget.target)\" in \"PBXProj.targets\"") + throw SchemeGenerationError.missingTarget(buildTarget, projectPath: projectFilePath) } let buildableName: String @@ -111,7 +111,7 @@ public class SchemeGenerator { guard let _buildableName = project.getTarget(buildTarget.target.name)?.filename ?? project.getAggregateTarget(buildTarget.target.name)?.name else { - fatalError("Unable to determinate \"buildableName\" for build target: \(buildTarget.target)") + throw SchemeGenerationError.unresolvableBuildableName(buildTarget) } buildableName = _buildableName } @@ -241,6 +241,24 @@ public class SchemeGenerator { } } +enum SchemeGenerationError: Error, CustomStringConvertible { + + case unresolvableBuildableName(Scheme.BuildTarget) + case missingTarget(Scheme.BuildTarget, projectPath: String) + case missingProject(String) + + var description: String { + switch self { + case .unresolvableBuildableName(let buildTarget): + return "Unable to determinate \"buildableName\" for build target: \(buildTarget.target)" + case .missingTarget(let buildTarget, let projectPath): + return "Unable to find target named \"\(buildTarget.target)\" in \"\(projectPath)\"" + case .missingProject(let project): + return "Unable to find project reference named \"\(project)\" in project.yml" + } + } +} + extension Scheme { public init(name: String, target: Target, targetScheme: TargetScheme, debugConfig: String, releaseConfig: String) { self.init( From 31667516a5afb879e548c927498b3b4f9a46b880 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 23 Oct 2019 07:50:29 +0900 Subject: [PATCH 30/36] Revert 'Throw error instead of fatalError' --- Sources/XcodeGenKit/SchemeGenerator.swift | 24 +++-------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index 06f057d59..a034caf69 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -89,7 +89,7 @@ public class SchemeGenerator { switch buildTarget.target.location { case .project(let project): guard let projectReference = self.project.getProjectReference(project) else { - throw SchemeGenerationError.missingProject(project) + fatalError("Unable to find project reference named \"\(project)\" in project.yml") } pbxProj = try XcodeProj(pathString: projectReference.path).pbxproj projectFilePath = projectReference.path @@ -99,7 +99,7 @@ public class SchemeGenerator { } guard let pbxTarget = pbxProj.targets(named: buildTarget.target.name).first else { - throw SchemeGenerationError.missingTarget(buildTarget, projectPath: projectFilePath) + fatalError("Unable to find target named \"\(buildTarget.target)\" in \"PBXProj.targets\"") } let buildableName: String @@ -111,7 +111,7 @@ public class SchemeGenerator { guard let _buildableName = project.getTarget(buildTarget.target.name)?.filename ?? project.getAggregateTarget(buildTarget.target.name)?.name else { - throw SchemeGenerationError.unresolvableBuildableName(buildTarget) + fatalError("Unable to determinate \"buildableName\" for build target: \(buildTarget.target)") } buildableName = _buildableName } @@ -241,24 +241,6 @@ public class SchemeGenerator { } } -enum SchemeGenerationError: Error, CustomStringConvertible { - - case unresolvableBuildableName(Scheme.BuildTarget) - case missingTarget(Scheme.BuildTarget, projectPath: String) - case missingProject(String) - - var description: String { - switch self { - case .unresolvableBuildableName(let buildTarget): - return "Unable to determinate \"buildableName\" for build target: \(buildTarget.target)" - case .missingTarget(let buildTarget, let projectPath): - return "Unable to find target named \"\(buildTarget.target)\" in \"\(projectPath)\"" - case .missingProject(let project): - return "Unable to find project reference named \"\(project)\" in project.yml" - } - } -} - extension Scheme { public init(name: String, target: Target, targetScheme: TargetScheme, debugConfig: String, releaseConfig: String) { self.init( From 848dfe28bab34819d49c1997604b5b6c2a88e711 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 24 Oct 2019 08:21:32 +0900 Subject: [PATCH 31/36] Re-revert 'Throw error instead of fatalError' partially --- Sources/XcodeGenKit/SchemeGenerator.swift | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index a034caf69..298368536 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -89,7 +89,7 @@ public class SchemeGenerator { switch buildTarget.target.location { case .project(let project): guard let projectReference = self.project.getProjectReference(project) else { - fatalError("Unable to find project reference named \"\(project)\" in project.yml") + throw SchemeGenerationError.missingProject(project) } pbxProj = try XcodeProj(pathString: projectReference.path).pbxproj projectFilePath = projectReference.path @@ -99,7 +99,7 @@ public class SchemeGenerator { } guard let pbxTarget = pbxProj.targets(named: buildTarget.target.name).first else { - fatalError("Unable to find target named \"\(buildTarget.target)\" in \"PBXProj.targets\"") + throw SchemeGenerationError.missingTarget(buildTarget, projectPath: projectFilePath) } let buildableName: String @@ -241,6 +241,21 @@ public class SchemeGenerator { } } +enum SchemeGenerationError: Error, CustomStringConvertible { + + case missingTarget(Scheme.BuildTarget, projectPath: String) + case missingProject(String) + + var description: String { + switch self { + case .missingTarget(let buildTarget, let projectPath): + return "Unable to find target named \"\(buildTarget.target)\" in \"\(projectPath)\"" + case .missingProject(let project): + return "Unable to find project reference named \"\(project)\" in project.yml" + } + } +} + extension Scheme { public init(name: String, target: Target, targetScheme: TargetScheme, debugConfig: String, releaseConfig: String) { self.init( From c4305dd02e4dfdbc566d733e948408e4c6aa78c2 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 27 Oct 2019 09:00:21 +0900 Subject: [PATCH 32/36] Add validation for project reference --- Sources/ProjectSpec/SpecValidation.swift | 12 +++++++++--- Sources/ProjectSpec/SpecValidationError.swift | 3 +++ Tests/XcodeGenKitTests/ProjectSpecTests.swift | 9 +++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Sources/ProjectSpec/SpecValidation.swift b/Sources/ProjectSpec/SpecValidation.swift index 76ce4afbb..7293b8418 100644 --- a/Sources/ProjectSpec/SpecValidation.swift +++ b/Sources/ProjectSpec/SpecValidation.swift @@ -180,9 +180,15 @@ extension Project { for scheme in schemes { for buildTarget in scheme.build.targets { - guard buildTarget.target.location == .local else { continue } - if getProjectTarget(buildTarget.target.name) == nil { - errors.append(.invalidSchemeTarget(scheme: scheme.name, target: buildTarget.target.name)) + switch buildTarget.target.location { + case .local: + if getProjectTarget(buildTarget.target.name) == nil { + errors.append(.invalidSchemeTarget(scheme: scheme.name, target: buildTarget.target.name)) + } + case .project(let project): + if getProjectReference(project) == nil { + errors.append(.invalidProjectReference(scheme: scheme.name, reference: project)) + } } } if let action = scheme.run, let config = action.config, getConfig(config) == nil { diff --git a/Sources/ProjectSpec/SpecValidationError.swift b/Sources/ProjectSpec/SpecValidationError.swift index 98e78d2d4..5fa568a20 100644 --- a/Sources/ProjectSpec/SpecValidationError.swift +++ b/Sources/ProjectSpec/SpecValidationError.swift @@ -29,6 +29,7 @@ public struct SpecValidationError: Error, CustomStringConvertible { case missingConfigForTargetScheme(target: String, configType: ConfigType) case missingDefaultConfig(configName: String) case invalidPerConfigSettings + case invalidProjectReference(scheme: String, reference: String) case deprecatedUsageOfPlaceholder(placeholderName: String) public var description: String { @@ -73,6 +74,8 @@ public struct SpecValidationError: Error, CustomStringConvertible { return "Default configuration \(name) doesn't exist" case .invalidPerConfigSettings: return "Settings that are for a specific config must go in \"configs\". \"base\" can be used for common settings" + case let .invalidProjectReference(scheme, project): + return "Scheme \(scheme.quoted) has invalid project reference \(project.quoted)" case let .deprecatedUsageOfPlaceholder(placeholderName: placeholderName): return "Usage of $\(placeholderName) is deprecated and will stop working in an upcoming version. Use ${\(placeholderName)} instead." } diff --git a/Tests/XcodeGenKitTests/ProjectSpecTests.swift b/Tests/XcodeGenKitTests/ProjectSpecTests.swift index 2eb9bd946..f0dd1ef12 100644 --- a/Tests/XcodeGenKitTests/ProjectSpecTests.swift +++ b/Tests/XcodeGenKitTests/ProjectSpecTests.swift @@ -217,6 +217,15 @@ class ProjectSpecTests: XCTestCase { try expectValidationError(project, .invalidSchemeConfig(scheme: "scheme1", config: "releaseInvalid")) } + $0.it("fails with invalid project reference") { + var project = baseProject + project.schemes = [Scheme( + name: "scheme1", + build: .init(targets: [.init(target: "invalidProjectRef/target1")]) + )] + try expectValidationError(project, .invalidProjectReference(scheme: "scheme1", reference: "invalidProjectRef")) + } + $0.it("allows missing optional file") { var project = baseProject project.targets = [Target( From 1285a3e25eeaaefc83965f7d02268dacdade2751 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 27 Oct 2019 09:07:04 +0900 Subject: [PATCH 33/36] Rename TargetReference.init label --- Sources/ProjectSpec/Scheme.swift | 6 +++--- Sources/ProjectSpec/TargetReference.swift | 4 ++-- Sources/ProjectSpec/TargetScheme.swift | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index 151bb1d8e..5c3224a48 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -145,7 +145,7 @@ public struct Scheme: Equatable { public init(stringLiteral value: String) { do { - targetReference = try TargetReference(string: value) + targetReference = try TargetReference(value) randomExecutionOrder = false parallelizable = false skippedTests = [] @@ -376,7 +376,7 @@ extension Scheme.Test: JSONEncodable { extension Scheme.Test.TestTarget: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { - targetReference = try TargetReference(string: jsonDictionary.json(atKeyPath: "name")) + targetReference = try TargetReference(jsonDictionary.json(atKeyPath: "name")) randomExecutionOrder = jsonDictionary.json(atKeyPath: "randomExecutionOrder") ?? Scheme.Test.TestTarget.randomExecutionOrderDefault parallelizable = jsonDictionary.json(atKeyPath: "parallelizable") ?? Scheme.Test.TestTarget.parallelizableDefault skippedTests = jsonDictionary.json(atKeyPath: "skippedTests") ?? [] @@ -519,7 +519,7 @@ extension Scheme.Build: JSONObjectConvertible { } else { buildTypes = BuildType.all } - let target = try TargetReference(string: targetRepr) + let target = try TargetReference(targetRepr) targets.append(Scheme.BuildTarget(target: target, buildTypes: buildTypes)) } self.targets = targets.sorted { $0.target.name < $1.target.name } diff --git a/Sources/ProjectSpec/TargetReference.swift b/Sources/ProjectSpec/TargetReference.swift index afff27c09..f7de4b7e7 100644 --- a/Sources/ProjectSpec/TargetReference.swift +++ b/Sources/ProjectSpec/TargetReference.swift @@ -17,7 +17,7 @@ public struct TargetReference: Hashable { } extension TargetReference { - public init(string: String) throws { + public init(_ string: String) throws { let paths = string.split(separator: "/") switch paths.count { case 2: @@ -38,7 +38,7 @@ extension TargetReference { extension TargetReference: ExpressibleByStringLiteral { public init(stringLiteral value: String) { - try! self.init(string: value) + try! self.init(value) } } diff --git a/Sources/ProjectSpec/TargetScheme.swift b/Sources/ProjectSpec/TargetScheme.swift index 5fbb70c55..1705cf1a6 100644 --- a/Sources/ProjectSpec/TargetScheme.swift +++ b/Sources/ProjectSpec/TargetScheme.swift @@ -42,7 +42,7 @@ extension TargetScheme: JSONObjectConvertible { if let targets = jsonDictionary["testTargets"] as? [Any] { testTargets = try targets.compactMap { target in if let string = target as? String { - return .init(targetReference: try TargetReference(string: string)) + return .init(targetReference: try TargetReference(string)) } else if let dictionary = target as? JSONDictionary { return try .init(jsonDictionary: dictionary) } else { From fbc7e9442de8dfa321a6b3e8ad55808c1a886659 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 27 Oct 2019 09:08:51 +0900 Subject: [PATCH 34/36] Use propery initializer for TestTarget --- Sources/ProjectSpec/Scheme.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index 5c3224a48..46c7909cc 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -325,7 +325,7 @@ extension Scheme.Test: JSONObjectConvertible { if let targets = jsonDictionary["targets"] as? [Any] { self.targets = try targets.compactMap { target in if let string = target as? String { - return TestTarget(stringLiteral: string) + return try TestTarget(targetReference: TargetReference(string)) } else if let dictionary = target as? JSONDictionary { return try TestTarget(jsonDictionary: dictionary) } else { From d3560e7f6bf8f184e378eebf078b9935a126da7b Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 27 Oct 2019 09:11:27 +0900 Subject: [PATCH 35/36] Use convenience initializer for TargetReference.local --- Sources/XcodeGenKit/SchemeGenerator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index 298368536..d830536c5 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -127,7 +127,7 @@ public class SchemeGenerator { let testTargets = scheme.test?.targets ?? [] let testBuildTargets = testTargets.map { - Scheme.BuildTarget(target: TargetReference(name: $0.name, location: .local), buildTypes: BuildType.testOnly) + Scheme.BuildTarget(target: .local($0.name), buildTypes: BuildType.testOnly) } let testBuildTargetEntries = try testBuildTargets.map(getBuildEntry) @@ -260,7 +260,7 @@ extension Scheme { public init(name: String, target: Target, targetScheme: TargetScheme, debugConfig: String, releaseConfig: String) { self.init( name: name, - build: .init(targets: [Scheme.BuildTarget(target: TargetReference(name: target.name, location: .local))]), + build: .init(targets: [Scheme.BuildTarget(target: TargetReference.local(target.name))]), run: .init( config: debugConfig, commandLineArguments: targetScheme.commandLineArguments, From bbed01d1d67a31dcd624fd671fa98569a849b962 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 27 Oct 2019 09:23:35 +0900 Subject: [PATCH 36/36] Cache pbxproj by reference name --- Sources/ProjectSpec/ProjectReference.swift | 2 +- Sources/XcodeGenKit/SchemeGenerator.swift | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Sources/ProjectSpec/ProjectReference.swift b/Sources/ProjectSpec/ProjectReference.swift index 126d31d59..3781e4d06 100644 --- a/Sources/ProjectSpec/ProjectReference.swift +++ b/Sources/ProjectSpec/ProjectReference.swift @@ -1,7 +1,7 @@ import Foundation import JSONUtilities -public struct ProjectReference { +public struct ProjectReference: Hashable { public var name: String public var path: String diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index d830536c5..9c5675ce0 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -28,6 +28,17 @@ public class SchemeGenerator { self.pbxProj = pbxProj } + private var projects: [ProjectReference: PBXProj] = [:] + + func getPBXProj(from reference: ProjectReference) throws -> PBXProj { + if let cachedProject = projects[reference] { + return cachedProject + } + let pbxproj = try XcodeProj(pathString: reference.path).pbxproj + projects[reference] = pbxproj + return pbxproj + } + public func generateSchemes() throws -> [XCScheme] { var xcschemes: [XCScheme] = [] @@ -91,7 +102,7 @@ public class SchemeGenerator { guard let projectReference = self.project.getProjectReference(project) else { throw SchemeGenerationError.missingProject(project) } - pbxProj = try XcodeProj(pathString: projectReference.path).pbxproj + pbxProj = try getPBXProj(from: projectReference) projectFilePath = projectReference.path case .local: pbxProj = self.pbxProj