From e7ef30a241b255acc2653eebf5095b3bd7992c43 Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Sat, 3 Nov 2018 20:27:59 +1100 Subject: [PATCH] refactoring --- Package.swift | 4 + Sources/ProjectSpec/Project.swift | 2 +- Sources/XcodeGen/main.swift | 17 +- .../{ProjectWriter.swift => FileWriter.swift} | 6 +- Sources/XcodeGenKit/PBXProjGenerator.swift | 4 +- Sources/XcodeGenKit/ProjectGenerator.swift | 5 +- Sources/XcodeGenKit/SourceGenerator.swift | 12 +- .../PerformanceTests.swift | 6 +- .../TestProject.swift | 0 Tests/XcodeGenKitTests/GeneratorHelpers.swift | 1 + .../ProjectFixtureTests.swift | 52 +---- .../ProjectGeneratorTests.swift | 172 +-------------- .../SchemeGeneratorTests.swift | 208 ++++++++++++++++++ .../SourceGeneratorTests.swift | 42 ++++ 14 files changed, 286 insertions(+), 245 deletions(-) rename Sources/XcodeGenKit/{ProjectWriter.swift => FileWriter.swift} (90%) rename Tests/{XcodeGenKitTests => PerformanceTests}/PerformanceTests.swift (88%) rename Tests/{XcodeGenKitTests => PerformanceTests}/TestProject.swift (100%) create mode 100644 Tests/XcodeGenKitTests/SchemeGeneratorTests.swift diff --git a/Package.swift b/Package.swift index a8688b98..adfff4bf 100644 --- a/Package.swift +++ b/Package.swift @@ -38,6 +38,10 @@ let package = Package( .testTarget(name: "XcodeGenKitTests", dependencies: [ "XcodeGenKit", "Spectre", + ]), + .testTarget(name: "PerformanceTests", dependencies: [ + "XcodeGenKit", + "Spectre", ]) ] ) diff --git a/Sources/ProjectSpec/Project.swift b/Sources/ProjectSpec/Project.swift index acac3267..f5d1d351 100644 --- a/Sources/ProjectSpec/Project.swift +++ b/Sources/ProjectSpec/Project.swift @@ -78,7 +78,7 @@ public struct Project: BuildSettingsContainer { return configs.first { $0.name == configName } } - public var projectPath: Path { + public var defaultProjectPath: Path { return basePath + "\(name).xcodeproj" } } diff --git a/Sources/XcodeGen/main.swift b/Sources/XcodeGen/main.swift index f14670c6..4959642b 100644 --- a/Sources/XcodeGen/main.swift +++ b/Sources/XcodeGen/main.swift @@ -22,7 +22,7 @@ func generate(spec: String, project: String, isQuiet: Bool, justVersion: Bool) { } let projectSpecPath = Path(spec).absolute() - let projectPath = project == "" ? projectSpecPath.parent() : Path(project).absolute() + var projectPath = project == "" ? projectSpecPath.parent() : Path(project).absolute() if !projectSpecPath.exists { fatalError("No project spec found at \(projectSpecPath.absolute())") @@ -39,19 +39,20 @@ func generate(spec: String, project: String, isQuiet: Bool, justVersion: Bool) { } do { - logger.info("⚙️ Generating project...") - try project.validateMinimumXcodeGenVersion(version) + try project.validate() + logger.info("⚙️ Generating project...") let projectGenerator = ProjectGenerator(project: project) let xcodeProject = try projectGenerator.generateXcodeProject() - logger.info("⚙️ Writing project...") - let projectWriter = ProjectWriter(project: project) - try projectWriter.writeXcodeProject(xcodeProject) - try projectWriter.writePlists() + logger.info("⚙️ Writing project...") + let fileWriter = FileWriter(project: project) + projectPath = projectPath + "\(project.name).xcodeproj" + try fileWriter.writeXcodeProject(xcodeProject, to: projectPath) + try fileWriter.writePlists() - logger.success("💾 Saved project to \(project.projectPath.string)") + logger.success("💾 Saved project to \(projectPath)") } catch let error as SpecValidationError { fatalError(error.description) } catch { diff --git a/Sources/XcodeGenKit/ProjectWriter.swift b/Sources/XcodeGenKit/FileWriter.swift similarity index 90% rename from Sources/XcodeGenKit/ProjectWriter.swift rename to Sources/XcodeGenKit/FileWriter.swift index 9029f42e..1834b4c3 100644 --- a/Sources/XcodeGenKit/ProjectWriter.swift +++ b/Sources/XcodeGenKit/FileWriter.swift @@ -3,7 +3,7 @@ import ProjectSpec import xcodeproj import PathKit -public class ProjectWriter { +public class FileWriter { let project: Project @@ -11,8 +11,8 @@ public class ProjectWriter { self.project = project } - public func writeXcodeProject(_ xcodeProject: XcodeProj) throws { - let projectPath = project.projectPath + public func writeXcodeProject(_ xcodeProject: XcodeProj, to projectPath: Path? = nil) throws { + let projectPath = project.defaultProjectPath let tempPath = Path.temporary + "XcodeGen_\(Int(NSTimeIntervalSince1970))" try? tempPath.delete() if projectPath.exists { diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index dff158f4..653e345b 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -27,9 +27,7 @@ public class PBXProjGenerator { public init(project: Project) { self.project = project pbxProj = PBXProj(rootObject: nil, objectVersion: 46) - sourceGenerator = SourceGenerator(project: project) { [unowned self] object in - _ = self.addObject(object) - } + sourceGenerator = SourceGenerator(project: project, pbxProj: pbxProj) } func addObject(_ object: T, context: String? = nil) -> T { diff --git a/Sources/XcodeGenKit/ProjectGenerator.swift b/Sources/XcodeGenKit/ProjectGenerator.swift index b6a83d49..7ee145d9 100644 --- a/Sources/XcodeGenKit/ProjectGenerator.swift +++ b/Sources/XcodeGenKit/ProjectGenerator.swift @@ -13,10 +13,7 @@ public class ProjectGenerator { self.project = project } - public func generateXcodeProject(validate: Bool = true) throws -> XcodeProj { - if validate { - try project.validate() - } + public func generateXcodeProject() throws -> XcodeProj { // generate PBXProj let pbxProjGenerator = PBXProjGenerator(project: project) diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index a99e5ded..db11458d 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -18,7 +18,8 @@ class SourceGenerator { private var variantGroupsByPath: [Path: PBXVariantGroup] = [:] private let project: Project - var addObjectClosure: (PBXObject) -> Void + let pbxProj: PBXProj + var targetSourceExcludePaths: Set = [] var defaultExcludedFiles = [ ".DS_Store", @@ -26,13 +27,14 @@ class SourceGenerator { private(set) var knownRegions: Set = [] - init(project: Project, addObjectClosure: @escaping (PBXObject) -> Void) { + init(project: Project, pbxProj: PBXProj) { self.project = project - self.addObjectClosure = addObjectClosure + self.pbxProj = pbxProj } - func addObject(_ object: T) -> T { - addObjectClosure(object) + func addObject(_ object: T, context: String? = nil) -> T { + pbxProj.add(object: object) + object.context = context return object } diff --git a/Tests/XcodeGenKitTests/PerformanceTests.swift b/Tests/PerformanceTests/PerformanceTests.swift similarity index 88% rename from Tests/XcodeGenKitTests/PerformanceTests.swift rename to Tests/PerformanceTests/PerformanceTests.swift index 6ecaec3d..b1e89a7e 100644 --- a/Tests/XcodeGenKitTests/PerformanceTests.swift +++ b/Tests/PerformanceTests/PerformanceTests.swift @@ -23,11 +23,13 @@ class GeneratedPerformanceTests: XCTestCase { let xcodeProject = try generator.generateXcodeProject() self.measure { xcodeProject.pbxproj.invalidateUUIDs() - try! xcodeProject.write(path: project.projectPath) + try! xcodeProject.write(path: project.defaultProjectPath) } } } +let fixturePath = Path(#file).parent().parent() + "Fixtures" + class FixturePerformanceTests: XCTestCase { let specPath = fixturePath + "TestProject/project.yml" @@ -52,7 +54,7 @@ class FixturePerformanceTests: XCTestCase { let xcodeProject = try generator.generateXcodeProject() self.measure { xcodeProject.pbxproj.invalidateUUIDs() - try! xcodeProject.write(path: project.projectPath) + try! xcodeProject.write(path: project.defaultProjectPath) } } } diff --git a/Tests/XcodeGenKitTests/TestProject.swift b/Tests/PerformanceTests/TestProject.swift similarity index 100% rename from Tests/XcodeGenKitTests/TestProject.swift rename to Tests/PerformanceTests/TestProject.swift diff --git a/Tests/XcodeGenKitTests/GeneratorHelpers.swift b/Tests/XcodeGenKitTests/GeneratorHelpers.swift index 24a077eb..0f2181eb 100644 --- a/Tests/XcodeGenKitTests/GeneratorHelpers.swift +++ b/Tests/XcodeGenKitTests/GeneratorHelpers.swift @@ -10,6 +10,7 @@ extension Project { func generateXcodeProject(file: String = #file, line: Int = #line) throws -> XcodeProj { return try doThrowing(file: file, line: line) { + try validate() let generator = ProjectGenerator(project: self) return try generator.generateXcodeProject() } diff --git a/Tests/XcodeGenKitTests/ProjectFixtureTests.swift b/Tests/XcodeGenKitTests/ProjectFixtureTests.swift index 6a40b2be..3f125a77 100644 --- a/Tests/XcodeGenKitTests/ProjectFixtureTests.swift +++ b/Tests/XcodeGenKitTests/ProjectFixtureTests.swift @@ -9,62 +9,18 @@ class ProjectFixtureTests: XCTestCase { func testProjectFixture() { describe { - var xcodeProject: XcodeProj? - $0.it("generates") { - xcodeProject = try generateXcodeProject(specPath: fixturePath + "TestProject/project.yml") - } - - $0.it("generates variant group") { - guard let xcodeProject = xcodeProject else { return } - - func getFileReferences(_ path: String) -> [PBXFileReference] { - return xcodeProject.pbxproj.fileReferences.filter { $0.path == path } - } - - func getVariableGroups(_ name: String?) -> [PBXVariantGroup] { - return xcodeProject.pbxproj.variantGroups.filter { $0.name == name } - } - - let resourceName = "LocalizedStoryboard.storyboard" - let baseResource = "Base.lproj/LocalizedStoryboard.storyboard" - let localizedResource = "en.lproj/LocalizedStoryboard.strings" - - guard let variableGroup = getVariableGroups(resourceName).first else { throw failure("Couldn't find the variable group") } - - do { - let refs = getFileReferences(baseResource) - try expect(refs.count) == 1 - try expect(variableGroup.children.filter { $0 == refs.first }.count) == 1 - } - - do { - let refs = getFileReferences(localizedResource) - try expect(refs.count) == 1 - try expect(variableGroup.children.filter { $0 == refs.first }.count) == 1 - } - } - - $0.it("generates scheme execution actions") { - guard let xcodeProject = xcodeProject else { return } - - let frameworkScheme = xcodeProject.sharedData?.schemes.first { $0.name == "Framework" } - try expect(frameworkScheme?.buildAction?.preActions.first?.scriptText) == "echo Starting Framework Build" - try expect(frameworkScheme?.buildAction?.preActions.first?.title) == "Run Script" - try expect(frameworkScheme?.buildAction?.preActions.first?.environmentBuildable?.blueprintName) == "Framework_iOS" - try expect(frameworkScheme?.buildAction?.preActions.first?.environmentBuildable?.buildableName) == "Framework.framework" + try generateXcodeProject(specPath: fixturePath + "TestProject/project.yml") } } } } -fileprivate func generateXcodeProject(specPath: Path, file: String = #file, line: Int = #line) throws -> XcodeProj { +fileprivate func generateXcodeProject(specPath: Path, file: String = #file, line: Int = #line) throws { let project = try Project(path: specPath) let generator = ProjectGenerator(project: project) + let writer = FileWriter(project: project) let xcodeProject = try generator.generateXcodeProject() - let writer = ProjectWriter(project: project) - try writer.writePlists() try writer.writeXcodeProject(xcodeProject) - - return xcodeProject + try writer.writePlists() } diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index 9b0b285d..41551b55 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -854,7 +854,7 @@ class ProjectGeneratorTests: XCTestCase { let tempPath = Path.temporary + "info" let project = Project(basePath: tempPath, name: "", targets: [Target(name: "", type: .application, platform: .iOS, info: plist)]) let pbxProject = try project.generatePbxProj() - let writer = ProjectWriter(project: project) + let writer = FileWriter(project: project) try writer.writePlists() guard let targetConfig = pbxProject.nativeTargets.first?.buildConfigurationList?.buildConfigurations.first else { @@ -881,174 +881,4 @@ class ProjectGeneratorTests: XCTestCase { } } } - - func testSchemes() { - describe { - - let buildTarget = Scheme.BuildTarget(target: app.name) - $0.it("generates scheme") { - let preAction = Scheme.ExecutionAction(name: "Script", script: "echo Starting", settingsTarget: app.name) - let scheme = Scheme( - name: "MyScheme", - build: Scheme.Build(targets: [buildTarget], preActions: [preAction]) - ) - let project = Project( - basePath: "", - name: "test", - targets: [app, framework], - schemes: [scheme] - ) - let xcodeProject = try project.generateXcodeProject() - guard let target = xcodeProject.pbxproj.nativeTargets - .first(where: { $0.name == app.name }) else { - throw failure("Target not found") - } - guard let xcscheme = xcodeProject.sharedData?.schemes.first else { - throw failure("Scheme not found") - } - try expect(scheme.name) == "MyScheme" - try expect(xcscheme.buildAction?.buildImplicitDependencies) == true - try expect(xcscheme.buildAction?.parallelizeBuild) == true - try expect(xcscheme.buildAction?.preActions.first?.title) == "Script" - try expect(xcscheme.buildAction?.preActions.first?.scriptText) == "echo Starting" - try expect(xcscheme.buildAction?.preActions.first?.environmentBuildable?.buildableName) == "MyApp.app" - try expect(xcscheme.buildAction?.preActions.first?.environmentBuildable?.blueprintName) == "MyApp" - guard let buildActionEntry = xcscheme.buildAction?.buildActionEntries.first else { - throw failure("Build Action entry not found") - } - try expect(buildActionEntry.buildFor) == BuildType.all - - let buildableReferences: [XCScheme.BuildableReference] = [ - buildActionEntry.buildableReference, - xcscheme.launchAction?.buildableProductRunnable?.buildableReference, - xcscheme.profileAction?.buildableProductRunnable?.buildableReference, - xcscheme.testAction?.macroExpansion, - ].compactMap { $0 } - - for buildableReference in buildableReferences { - // FIXME: try expect(buildableReference.blueprintIdentifier) == target.reference - try expect(buildableReference.blueprintName) == target.name - try expect(buildableReference.buildableName) == "\(target.name).\(target.productType!.fileExtension!)" - } - - try expect(xcscheme.launchAction?.buildConfiguration) == "Debug" - try expect(xcscheme.testAction?.buildConfiguration) == "Debug" - try expect(xcscheme.profileAction?.buildConfiguration) == "Release" - try expect(xcscheme.analyzeAction?.buildConfiguration) == "Debug" - try expect(xcscheme.archiveAction?.buildConfiguration) == "Release" - } - - $0.it("sets environment variables for a scheme") { - let runVariables: [XCScheme.EnvironmentVariable] = [ - XCScheme.EnvironmentVariable(variable: "RUN_ENV", value: "ENABLED", enabled: true), - XCScheme.EnvironmentVariable(variable: "OTHER_RUN_ENV", value: "DISABLED", enabled: false), - ] - - let scheme = Scheme( - name: "EnvironmentVariablesScheme", - build: Scheme.Build(targets: [buildTarget]), - run: Scheme.Run(config: "Debug", environmentVariables: runVariables), - test: Scheme.Test(config: "Debug"), - profile: Scheme.Profile(config: "Debug") - ) - let project = Project( - basePath: "", - name: "test", - targets: [app, framework], - schemes: [scheme] - ) - let xcodeProject = try project.generateXcodeProject() - - guard let xcscheme = xcodeProject.sharedData?.schemes.first else { - throw failure("Scheme not found") - } - - try expect( - xcodeProject.pbxproj.nativeTargets - .contains(where: { $0.name == app.name }) - ).beTrue() - try expect(xcscheme.launchAction?.environmentVariables) == runVariables - try expect(xcscheme.testAction?.environmentVariables).to.beNil() - try expect(xcscheme.profileAction?.environmentVariables).to.beNil() - } - - $0.it("generates target schemes from config variant") { - let configVariants = ["Test", "Production"] - var target = app - target.scheme = TargetScheme(configVariants: configVariants) - let configs: [Config] = [ - Config(name: "Test Debug", type: .debug), - Config(name: "Production Debug", type: .debug), - Config(name: "Test Release", type: .release), - Config(name: "Production Release", type: .release), - ] - - let project = Project(basePath: "", name: "test", configs: configs, targets: [target, framework]) - let xcodeProject = try project.generateXcodeProject() - - try expect(xcodeProject.sharedData?.schemes.count) == 2 - - guard let xcscheme = xcodeProject.sharedData?.schemes - .first(where: { $0.name == "\(target.name) Test" }) else { - throw failure("Scheme not found") - } - guard let buildActionEntry = xcscheme.buildAction?.buildActionEntries.first else { - throw failure("Build Action entry not found") - } - - try expect(buildActionEntry.buildableReference.blueprintIdentifier.count > 0) == true - - try expect(xcscheme.launchAction?.buildConfiguration) == "Test Debug" - try expect(xcscheme.testAction?.buildConfiguration) == "Test Debug" - try expect(xcscheme.profileAction?.buildConfiguration) == "Test Release" - try expect(xcscheme.analyzeAction?.buildConfiguration) == "Test Debug" - try expect(xcscheme.archiveAction?.buildConfiguration) == "Test Release" - } - - $0.it("generates environment variables for target schemes") { - let variables: [XCScheme.EnvironmentVariable] = [XCScheme.EnvironmentVariable(variable: "env", value: "var", enabled: false)] - var target = app - target.scheme = TargetScheme(environmentVariables: variables) - - let project = Project(basePath: "", name: "test", targets: [target, framework]) - let xcodeProject = try project.generateXcodeProject() - - try expect(xcodeProject.sharedData?.schemes.count) == 1 - - guard let xcscheme = xcodeProject.sharedData?.schemes.first else { - throw failure("Scheme not found") - } - - try expect(xcscheme.launchAction?.environmentVariables) == variables - try expect(xcscheme.testAction?.environmentVariables) == variables - try expect(xcscheme.profileAction?.environmentVariables) == variables - } - - $0.it("generates pre and post actions for target schemes") { - var target = app - target.scheme = TargetScheme( - preActions: [.init(name: "Run", script: "do")], - postActions: [.init(name: "Run2", script: "post", settingsTarget: "MyApp")] - ) - - let project = Project(basePath: "", name: "test", targets: [target, framework]) - let xcodeProject = try project.generateXcodeProject() - - try expect(xcodeProject.sharedData?.schemes.count) == 1 - - guard let xcscheme = xcodeProject.sharedData?.schemes.first else { - throw failure("Scheme not found") - } - - try expect(xcscheme.launchAction?.preActions.count) == 1 - try expect(xcscheme.launchAction?.preActions.first?.title) == "Run" - try expect(xcscheme.launchAction?.preActions.first?.scriptText) == "do" - - try expect(xcscheme.testAction?.postActions.count) == 1 - try expect(xcscheme.testAction?.postActions.first?.title) == "Run2" - try expect(xcscheme.testAction?.postActions.first?.scriptText) == "post" - try expect(xcscheme.testAction?.postActions.first?.environmentBuildable?.blueprintName) == "MyApp" - } - } - } } diff --git a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift new file mode 100644 index 00000000..05c4fae6 --- /dev/null +++ b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift @@ -0,0 +1,208 @@ +import PathKit +import ProjectSpec +import Spectre +import XcodeGenKit +import xcodeproj +import XCTest +import Yams + +fileprivate let app = Target( + name: "MyApp", + type: .application, + platform: .iOS, + dependencies: [Dependency(type: .target, reference: "MyFramework")] +) + +fileprivate let framework = Target( + name: "MyFramework", + type: .framework, + platform: .iOS +) + +fileprivate let optionalFramework = Target( + name: "MyOptionalFramework", + type: .framework, + platform: .iOS +) + +fileprivate let uiTest = Target( + name: "MyAppUITests", + type: .uiTestBundle, + platform: .iOS, + dependencies: [Dependency(type: .target, reference: "MyApp")] +) + +class SchemeGeneratorTests: XCTestCase { + + + + func testSchemes() { + describe { + + let buildTarget = Scheme.BuildTarget(target: app.name) + $0.it("generates scheme") { + let preAction = Scheme.ExecutionAction(name: "Script", script: "echo Starting", settingsTarget: app.name) + let scheme = Scheme( + name: "MyScheme", + build: Scheme.Build(targets: [buildTarget], preActions: [preAction]) + ) + let project = Project( + basePath: "", + name: "test", + targets: [app, framework], + schemes: [scheme] + ) + let xcodeProject = try project.generateXcodeProject() + guard let target = xcodeProject.pbxproj.nativeTargets + .first(where: { $0.name == app.name }) else { + throw failure("Target not found") + } + guard let xcscheme = xcodeProject.sharedData?.schemes.first else { + throw failure("Scheme not found") + } + try expect(scheme.name) == "MyScheme" + try expect(xcscheme.buildAction?.buildImplicitDependencies) == true + try expect(xcscheme.buildAction?.parallelizeBuild) == true + try expect(xcscheme.buildAction?.preActions.first?.title) == "Script" + try expect(xcscheme.buildAction?.preActions.first?.scriptText) == "echo Starting" + try expect(xcscheme.buildAction?.preActions.first?.environmentBuildable?.buildableName) == "MyApp.app" + try expect(xcscheme.buildAction?.preActions.first?.environmentBuildable?.blueprintName) == "MyApp" + guard let buildActionEntry = xcscheme.buildAction?.buildActionEntries.first else { + throw failure("Build Action entry not found") + } + try expect(buildActionEntry.buildFor) == BuildType.all + + let buildableReferences: [XCScheme.BuildableReference] = [ + buildActionEntry.buildableReference, + xcscheme.launchAction?.buildableProductRunnable?.buildableReference, + xcscheme.profileAction?.buildableProductRunnable?.buildableReference, + xcscheme.testAction?.macroExpansion, + ].compactMap { $0 } + + for buildableReference in buildableReferences { + // FIXME: try expect(buildableReference.blueprintIdentifier) == target.reference + try expect(buildableReference.blueprintName) == target.name + try expect(buildableReference.buildableName) == "\(target.name).\(target.productType!.fileExtension!)" + } + + try expect(xcscheme.launchAction?.buildConfiguration) == "Debug" + try expect(xcscheme.testAction?.buildConfiguration) == "Debug" + try expect(xcscheme.profileAction?.buildConfiguration) == "Release" + try expect(xcscheme.analyzeAction?.buildConfiguration) == "Debug" + try expect(xcscheme.archiveAction?.buildConfiguration) == "Release" + } + + $0.it("sets environment variables for a scheme") { + let runVariables: [XCScheme.EnvironmentVariable] = [ + XCScheme.EnvironmentVariable(variable: "RUN_ENV", value: "ENABLED", enabled: true), + XCScheme.EnvironmentVariable(variable: "OTHER_RUN_ENV", value: "DISABLED", enabled: false), + ] + + let scheme = Scheme( + name: "EnvironmentVariablesScheme", + build: Scheme.Build(targets: [buildTarget]), + run: Scheme.Run(config: "Debug", environmentVariables: runVariables), + test: Scheme.Test(config: "Debug"), + profile: Scheme.Profile(config: "Debug") + ) + let project = Project( + basePath: "", + name: "test", + targets: [app, framework], + schemes: [scheme] + ) + let xcodeProject = try project.generateXcodeProject() + + guard let xcscheme = xcodeProject.sharedData?.schemes.first else { + throw failure("Scheme not found") + } + + try expect( + xcodeProject.pbxproj.nativeTargets + .contains(where: { $0.name == app.name }) + ).beTrue() + try expect(xcscheme.launchAction?.environmentVariables) == runVariables + try expect(xcscheme.testAction?.environmentVariables).to.beNil() + try expect(xcscheme.profileAction?.environmentVariables).to.beNil() + } + + $0.it("generates target schemes from config variant") { + let configVariants = ["Test", "Production"] + var target = app + target.scheme = TargetScheme(configVariants: configVariants) + let configs: [Config] = [ + Config(name: "Test Debug", type: .debug), + Config(name: "Production Debug", type: .debug), + Config(name: "Test Release", type: .release), + Config(name: "Production Release", type: .release), + ] + + let project = Project(basePath: "", name: "test", configs: configs, targets: [target, framework]) + let xcodeProject = try project.generateXcodeProject() + + try expect(xcodeProject.sharedData?.schemes.count) == 2 + + guard let xcscheme = xcodeProject.sharedData?.schemes + .first(where: { $0.name == "\(target.name) Test" }) else { + throw failure("Scheme not found") + } + guard let buildActionEntry = xcscheme.buildAction?.buildActionEntries.first else { + throw failure("Build Action entry not found") + } + + try expect(buildActionEntry.buildableReference.blueprintIdentifier.count > 0) == true + + try expect(xcscheme.launchAction?.buildConfiguration) == "Test Debug" + try expect(xcscheme.testAction?.buildConfiguration) == "Test Debug" + try expect(xcscheme.profileAction?.buildConfiguration) == "Test Release" + try expect(xcscheme.analyzeAction?.buildConfiguration) == "Test Debug" + try expect(xcscheme.archiveAction?.buildConfiguration) == "Test Release" + } + + $0.it("generates environment variables for target schemes") { + let variables: [XCScheme.EnvironmentVariable] = [XCScheme.EnvironmentVariable(variable: "env", value: "var", enabled: false)] + var target = app + target.scheme = TargetScheme(environmentVariables: variables) + + let project = Project(basePath: "", name: "test", targets: [target, framework]) + let xcodeProject = try project.generateXcodeProject() + + try expect(xcodeProject.sharedData?.schemes.count) == 1 + + guard let xcscheme = xcodeProject.sharedData?.schemes.first else { + throw failure("Scheme not found") + } + + try expect(xcscheme.launchAction?.environmentVariables) == variables + try expect(xcscheme.testAction?.environmentVariables) == variables + try expect(xcscheme.profileAction?.environmentVariables) == variables + } + + $0.it("generates pre and post actions for target schemes") { + var target = app + target.scheme = TargetScheme( + preActions: [.init(name: "Run", script: "do")], + postActions: [.init(name: "Run2", script: "post", settingsTarget: "MyApp")] + ) + + let project = Project(basePath: "", name: "test", targets: [target, framework]) + let xcodeProject = try project.generateXcodeProject() + + try expect(xcodeProject.sharedData?.schemes.count) == 1 + + guard let xcscheme = xcodeProject.sharedData?.schemes.first else { + throw failure("Scheme not found") + } + + try expect(xcscheme.launchAction?.preActions.count) == 1 + try expect(xcscheme.launchAction?.preActions.first?.title) == "Run" + try expect(xcscheme.launchAction?.preActions.first?.scriptText) == "do" + + try expect(xcscheme.testAction?.postActions.count) == 1 + try expect(xcscheme.testAction?.postActions.first?.title) == "Run2" + try expect(xcscheme.testAction?.postActions.first?.scriptText) == "post" + try expect(xcscheme.testAction?.postActions.first?.environmentBuildable?.blueprintName) == "MyApp" + } + } + } +} diff --git a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift index 7dec0a53..69c152cc 100644 --- a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift @@ -126,6 +126,48 @@ class SourceGeneratorTests: XCTestCase { try expect(fileReference.path) == "model2.xcdatamodel" } + $0.it("generates variant groups") { + let directories = """ + Sources: + Base.lproj: + - LocalizedStoryboard.storyboard + en.lproj: + - LocalizedStoryboard.strings + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + + let pbxProj = try project.generatePbxProj() + + func getFileReferences(_ path: String) -> [PBXFileReference] { + return pbxProj.fileReferences.filter { $0.path == path } + } + + func getVariableGroups(_ name: String?) -> [PBXVariantGroup] { + return pbxProj.variantGroups.filter { $0.name == name } + } + + let resourceName = "LocalizedStoryboard.storyboard" + let baseResource = "Base.lproj/LocalizedStoryboard.storyboard" + let localizedResource = "en.lproj/LocalizedStoryboard.strings" + + guard let variableGroup = getVariableGroups(resourceName).first else { throw failure("Couldn't find the variable group") } + + do { + let refs = getFileReferences(baseResource) + try expect(refs.count) == 1 + try expect(variableGroup.children.filter { $0 == refs.first }.count) == 1 + } + + do { + let refs = getFileReferences(localizedResource) + try expect(refs.count) == 1 + try expect(variableGroup.children.filter { $0 == refs.first }.count) == 1 + } + } + $0.it("handles duplicate names") { let directories = """ Sources: