From dcb098a39fcbbe7fc60042d0cfa505223c2be0c0 Mon Sep 17 00:00:00 2001 From: Jakub Turek Date: Sun, 11 Feb 2018 15:00:13 +0100 Subject: [PATCH] Implement setting schema variables in xcodeproj --- CHANGELOG.md | 1 + Docs/ProjectSpec.md | 15 ++++++ Package.resolved | 8 +-- Package.swift | 2 +- Sources/ProjectSpec/Scheme.swift | 53 +++++++++++++++--- Sources/ProjectSpec/Target.swift | 6 ++- Sources/XcodeGenKit/ProjectGenerator.swift | 20 +++++-- .../ProjectGeneratorTests.swift | 54 +++++++++++++++++++ Tests/XcodeGenKitTests/SpecLoadingTests.swift | 51 ++++++++++++++++++ 9 files changed, 193 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a14be05b..9270a921 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added `carthageExecutablePath` option [244](https://github.com/yonaskolb/XcodeGen/pull/244) @akkyie - Added `parallelizeBuild` and `buildImplicitDependencies` to Schemes [241](https://github.com/yonaskolb/XcodeGen/pull/241) @rahul-malik +- Added scheme environment variables [239](https://github.com/yonaskolb/XcodeGen/pull/239) @turekj #### Fixed - Fixed Mint installation from reading setting presets [248](https://github.com/yonaskolb/XcodeGen/pull/248) @yonaskolb diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index 4cd4ab75..22b81e55 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -357,6 +357,7 @@ This is a convenience used to automatically generate schemes for a target based - [ ] **testTargets**: **[String]** - a list of test targets that should be included in the scheme. These will be added to the build targets and the test entries - [ ] **gatherCoverageData**: **Bool** - a boolean that indicates if this scheme should gather coverage data. This defaults to false - [ ] **commandLineArguments**: **[String:Bool]** - a dictionary from the argument name (`String`) to if it is enabled (`Bool`). These arguments will be added to the Test, Profile and Run scheme actions +- [ ] **environmentVariables**: **[[Environment Variable](#environment-variable)]** or **[String:String]** - environment variables for Run, Test and Profile scheme actions. When passing a dictionary, every key-value entry maps to a corresponding variable that is enabled. For example, the spec below would create 3 schemes called: @@ -388,6 +389,8 @@ targets commandLineArguments: "-MyEnabledArg": true "-MyDisabledArg": false + environmentVariables: + MY_ENV_VAR: VALUE MyUnitTests: sources: Tests ``` @@ -444,6 +447,7 @@ The different actions share some properties: - [ ] **commandLineArguments**: **[String:Bool]** - `run`, `test` and `profile` actions have a map of command line arguments to whether they are enabled - [ ] **preActions**: **[[Execution Action](#execution-action)]** - Scheme run scripts that run *before* the action is run - [ ] **postActions**: **[[Execution Action](#execution-action)]** - Scheme run scripts that run *after* the action is run +- [ ] **environmentVariables**: **[[Environment Variable](#environment-variable)]** or **[String:String]** - `run`, `test` and `profile` actions can define the environment variables. When passing a dictionary, every key-value entry maps to a corresponding variable that is enabled. ### Execution Action Scheme run scripts added via **preActions** or **postActions**. They run before or after a build action, respectively, and in the order defined. Each execution action can contain: @@ -458,6 +462,11 @@ A multiline script can be written using the various YAML multiline methods, for - [ ] **gatherCoverageData**: **Bool** - a boolean that indicates if this scheme should gather coverage data. This defaults to false - [ ] **targets**: **[String]** - a list of targets to test +### Environment Variable +- [x] **key**: **String** - variable's key. +- [x] **value**: **String** - variable's value. +- [ ] **isEnabled**: **Bool** - indicates whether the environment variable is enabled. This defaults to true. + ```yaml scheme: Production: @@ -468,11 +477,17 @@ scheme: run: config: prod-debug commandLineArguments: "--option value" + environmentVariables: + RUN_ENV_VAR: VALUE test: config: prod-debug commandLineArguments: "--option testValue" gatherCoverageData: true targets: [Tester1, Tester2] + environmentVariables: + - key: TEST_ENV_VAR + value: VALUE + isEnabled: false profile: config: prod-release analyze: diff --git a/Package.resolved b/Package.resolved index 9b67b865..be052a20 100644 --- a/Package.resolved +++ b/Package.resolved @@ -75,11 +75,11 @@ }, { "package": "xcproj", - "repositoryURL": "https://github.com/xcodeswift/xcproj.git", + "repositoryURL": "https://github.com/elpassion/xcproj", "state": { - "branch": null, - "revision": "5f68ad74627a537cdd3d022644edd0fa7a80d4a7", - "version": "4.0.0" + "branch": "environment_variables", + "revision": "44ed558ef9f52e4e45b6820f8b60254fb63bc212", + "version": null } } ] diff --git a/Package.swift b/Package.swift index 6f6251fa..8707dfb3 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( .package(url: "https://github.com/yonaskolb/JSONUtilities.git", from: "3.3.0"), .package(url: "https://github.com/kylef/Spectre.git", from: "0.8.0"), .package(url: "https://github.com/onevcat/Rainbow.git", from: "3.0.0"), - .package(url: "https://github.com/xcodeswift/xcproj.git", from: "4.0.0") + .package(url: "https://github.com/elpassion/xcproj", .branch("environment_variables")) ], targets: [ .target(name: "XcodeGen", dependencies: [ diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index b8640c02..4600d38b 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -81,23 +81,27 @@ public struct Scheme: Equatable { public var commandLineArguments: [String: Bool] public var preActions: [ExecutionAction] public var postActions: [ExecutionAction] + public var environmentVariables: [XCScheme.EnvironmentVariable] public init( config: String, commandLineArguments: [String: Bool] = [:], preActions: [ExecutionAction] = [], - postActions: [ExecutionAction] = [] + postActions: [ExecutionAction] = [], + environmentVariables: [XCScheme.EnvironmentVariable] = [] ) { self.config = config self.commandLineArguments = commandLineArguments self.preActions = preActions self.postActions = postActions + self.environmentVariables = environmentVariables } public static func == (lhs: Run, rhs: Run) -> Bool { return lhs.config == rhs.config && lhs.commandLineArguments == rhs.commandLineArguments && lhs.preActions == rhs.postActions && - lhs.postActions == rhs.postActions + lhs.postActions == rhs.postActions && + lhs.environmentVariables == rhs.environmentVariables } } @@ -108,13 +112,15 @@ public struct Scheme: Equatable { public var targets: [String] public var preActions: [ExecutionAction] public var postActions: [ExecutionAction] + public var environmentVariables: [XCScheme.EnvironmentVariable] public init( config: String, gatherCoverageData: Bool = false, commandLineArguments: [String: Bool] = [:], targets: [String] = [], preActions: [ExecutionAction] = [], - postActions: [ExecutionAction] = [] + postActions: [ExecutionAction] = [], + environmentVariables: [XCScheme.EnvironmentVariable] = [] ) { self.config = config self.gatherCoverageData = gatherCoverageData @@ -122,6 +128,7 @@ public struct Scheme: Equatable { self.targets = targets self.preActions = preActions self.postActions = postActions + self.environmentVariables = environmentVariables } public static func == (lhs: Test, rhs: Test) -> Bool { @@ -130,7 +137,8 @@ public struct Scheme: Equatable { lhs.gatherCoverageData == rhs.gatherCoverageData && lhs.targets == rhs.targets && lhs.preActions == rhs.postActions && - lhs.postActions == rhs.postActions + lhs.postActions == rhs.postActions && + lhs.environmentVariables == rhs.environmentVariables } } @@ -150,23 +158,27 @@ public struct Scheme: Equatable { public var commandLineArguments: [String: Bool] public var preActions: [ExecutionAction] public var postActions: [ExecutionAction] + public var environmentVariables: [XCScheme.EnvironmentVariable] public init( config: String, commandLineArguments: [String: Bool] = [:], preActions: [ExecutionAction] = [], - postActions: [ExecutionAction] = [] + postActions: [ExecutionAction] = [], + environmentVariables: [XCScheme.EnvironmentVariable] = [] ) { self.config = config self.commandLineArguments = commandLineArguments self.preActions = preActions self.postActions = postActions + self.environmentVariables = environmentVariables } public static func == (lhs: Profile, rhs: Profile) -> Bool { return lhs.config == rhs.config && lhs.commandLineArguments == rhs.commandLineArguments && lhs.preActions == rhs.postActions && - lhs.postActions == rhs.postActions + lhs.postActions == rhs.postActions && + lhs.environmentVariables == rhs.environmentVariables } } @@ -235,6 +247,7 @@ extension Scheme.Run: JSONObjectConvertible { commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:] preActions = try jsonDictionary.json(atKeyPath: "preActions")?.map(Scheme.ExecutionAction.init) ?? [] postActions = try jsonDictionary.json(atKeyPath: "postActions")?.map(Scheme.ExecutionAction.init) ?? [] + environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary) } } @@ -247,6 +260,7 @@ extension Scheme.Test: JSONObjectConvertible { targets = jsonDictionary.json(atKeyPath: "targets") ?? [] preActions = try jsonDictionary.json(atKeyPath: "preActions")?.map(Scheme.ExecutionAction.init) ?? [] postActions = try jsonDictionary.json(atKeyPath: "postActions")?.map(Scheme.ExecutionAction.init) ?? [] + environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary) } } @@ -257,6 +271,7 @@ extension Scheme.Profile: JSONObjectConvertible { commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:] preActions = try jsonDictionary.json(atKeyPath: "preActions")?.map(Scheme.ExecutionAction.init) ?? [] postActions = try jsonDictionary.json(atKeyPath: "postActions")?.map(Scheme.ExecutionAction.init) ?? [] + environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary) } } @@ -340,3 +355,29 @@ extension BuildType: JSONPrimitiveConvertible { return [.running, .testing, .profiling, .analyzing, .archiving] } } + +extension XCScheme.EnvironmentVariable: JSONObjectConvertible, Equatable { + + public init(jsonDictionary: JSONDictionary) throws { + variable = try jsonDictionary.json(atKeyPath: "variable") + value = try jsonDictionary.json(atKeyPath: "value") + enabled = (try? jsonDictionary.json(atKeyPath: "isEnabled")) ?? true + } + + static func parseAll(jsonDictionary: JSONDictionary) throws -> [XCScheme.EnvironmentVariable] { + if let variablesDictionary: [String: String] = jsonDictionary.json(atKeyPath: "environmentVariables") { + return variablesDictionary.map { XCScheme.EnvironmentVariable(variable: $0.key, value: $0.value, enabled: true) } + } else if let variablesArray: [JSONDictionary] = jsonDictionary.json(atKeyPath: "environmentVariables") { + return try variablesArray.map(XCScheme.EnvironmentVariable.init) + } else { + return [] + } + } + + public static func == (lhs: XCScheme.EnvironmentVariable, rhs: XCScheme.EnvironmentVariable) -> Bool { + return lhs.variable == rhs.variable && + lhs.value == rhs.value && + lhs.enabled == rhs.enabled + } + +} diff --git a/Sources/ProjectSpec/Target.swift b/Sources/ProjectSpec/Target.swift index b764b4b8..da1e17a5 100644 --- a/Sources/ProjectSpec/Target.swift +++ b/Sources/ProjectSpec/Target.swift @@ -172,17 +172,20 @@ public struct TargetScheme { public var configVariants: [String] public var gatherCoverageData: Bool public var commandLineArguments: [String: Bool] + public var environmentVariables: [XCScheme.EnvironmentVariable] public init( testTargets: [String] = [], configVariants: [String] = [], gatherCoverageData: Bool = false, - commandLineArguments: [String: Bool] = [:] + commandLineArguments: [String: Bool] = [:], + environmentVariables: [XCScheme.EnvironmentVariable] = [] ) { self.testTargets = testTargets self.configVariants = configVariants self.gatherCoverageData = gatherCoverageData self.commandLineArguments = commandLineArguments + self.environmentVariables = environmentVariables } } @@ -201,6 +204,7 @@ extension TargetScheme: JSONObjectConvertible { configVariants = jsonDictionary.json(atKeyPath: "configVariants") ?? [] gatherCoverageData = jsonDictionary.json(atKeyPath: "gatherCoverageData") ?? false commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:] + environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary) } } diff --git a/Sources/XcodeGenKit/ProjectGenerator.swift b/Sources/XcodeGenKit/ProjectGenerator.swift index f2aec870..e5417010 100644 --- a/Sources/XcodeGenKit/ProjectGenerator.swift +++ b/Sources/XcodeGenKit/ProjectGenerator.swift @@ -90,6 +90,10 @@ public class ProjectGenerator { let launchCommandLineArgs = scheme.run.map { XCScheme.CommandLineArguments($0.commandLineArguments) } let profileCommandLineArgs = scheme.profile.map { XCScheme.CommandLineArguments($0.commandLineArguments) } + let testVariables = scheme.test.flatMap { $0.environmentVariables.isEmpty ? nil : $0.environmentVariables } + let launchVariables = scheme.run.flatMap { $0.environmentVariables.isEmpty ? nil : $0.environmentVariables } + let profileVariables = scheme.profile.flatMap { $0.environmentVariables.isEmpty ? nil : $0.environmentVariables } + let testAction = XCScheme.TestAction( buildConfiguration: scheme.test?.config ?? defaultDebugConfig.name, macroExpansion: buildableReference, @@ -99,6 +103,7 @@ public class ProjectGenerator { shouldUseLaunchSchemeArgsEnv: scheme.test?.commandLineArguments.isEmpty ?? true, codeCoverageEnabled: scheme.test?.gatherCoverageData ?? false, commandlineArguments: testCommandLineArgs, + environmentVariables: testVariables, language: "" ) @@ -107,7 +112,8 @@ public class ProjectGenerator { buildConfiguration: scheme.run?.config ?? defaultDebugConfig.name, preActions: scheme.run?.preActions.map(getExecutionAction) ?? [], postActions: scheme.run?.postActions.map(getExecutionAction) ?? [], - commandlineArguments: launchCommandLineArgs + commandlineArguments: launchCommandLineArgs, + environmentVariables: launchVariables ) let profileAction = XCScheme.ProfileAction( @@ -116,7 +122,8 @@ public class ProjectGenerator { preActions: scheme.profile?.preActions.map(getExecutionAction) ?? [], postActions: scheme.profile?.postActions.map(getExecutionAction) ?? [], shouldUseLaunchSchemeArgsEnv: scheme.profile?.commandLineArguments.isEmpty ?? true, - commandlineArguments: profileCommandLineArgs + commandlineArguments: profileCommandLineArgs, + environmentVariables: profileVariables ) let analyzeAction = XCScheme.AnalyzeAction(buildConfiguration: scheme.analyze?.config ?? defaultDebugConfig.name) @@ -202,17 +209,20 @@ extension Scheme { build: .init(targets: [Scheme.BuildTarget(target: target.name)]), run: .init( config: debugConfig, - commandLineArguments: targetScheme.commandLineArguments + commandLineArguments: targetScheme.commandLineArguments, + environmentVariables: targetScheme.environmentVariables ), test: .init( config: debugConfig, gatherCoverageData: targetScheme.gatherCoverageData, commandLineArguments: targetScheme.commandLineArguments, - targets: targetScheme.testTargets + targets: targetScheme.testTargets, + environmentVariables: targetScheme.environmentVariables ), profile: .init( config: releaseConfig, - commandLineArguments: targetScheme.commandLineArguments + commandLineArguments: targetScheme.commandLineArguments, + environmentVariables: targetScheme.environmentVariables ), analyze: .init(config: debugConfig), archive: .init(config: releaseConfig) diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index 8fec2c26..4a1737f2 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -352,6 +352,41 @@ func projectGeneratorTests() { 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 spec = ProjectSpec( + basePath: "", + name: "test", + targets: [application, framework], + schemes: [scheme] + ) + let project = try getProject(spec) + + guard let target = project.pbxproj.objects.nativeTargets.objectReferences + .first(where: { $0.object.name == application.name }) else { + throw failure("Target not found") + } + + guard let xcscheme = project.sharedData?.schemes.first else { + throw failure("Scheme not found") + } + + 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 = application @@ -387,6 +422,25 @@ func projectGeneratorTests() { 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 = application + target.scheme = TargetScheme(environmentVariables: variables) + + let spec = ProjectSpec(basePath: "", name: "test", targets: [target, framework]) + let project = try getProject(spec) + + try expect(project.sharedData?.schemes.count) == 1 + + guard let xcscheme = project.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.describe("Sources") { diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index 71a5bd0c..6eb55dfa 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -154,6 +154,21 @@ func specLoadingTests() { try expect(spec.targets) == [target_iOS, target_tvOS] } + $0.it("parses target schemes") { + var targetDictionary = validTarget + targetDictionary["scheme"] = [ + "environmentVariables": [ + "TEST_VAR": "TEST_VAL" + ] + ] + + let target = try Target(name: "test", jsonDictionary: targetDictionary) + + let expectedVariables = [XCScheme.EnvironmentVariable(variable: "TEST_VAR", value: "TEST_VAL", enabled: true)] + + try expect(target.scheme?.environmentVariables) == expectedVariables + } + $0.it("parses schemes") { let schemeDictionary: [String: Any] = [ "build": [ @@ -195,6 +210,42 @@ func specLoadingTests() { try expect(scheme.build.buildImplicitDependencies) == false } + $0.it("parses schemes variables") { + let schemeDictionary: [String: Any] = [ + "build": [ + "targets": ["Target1": "all"], + ], + "run": [ + "environmentVariables": [ + ["key": "ENVIRONMENT", "value": "VARIABLE"], + ["key": "OTHER_ENV_VAR", "value": "VAL", "isEnabled": false], + ], + ], + "test": [ + "environmentVariables": [ + "TEST": "VARIABLE" + ] + ], + "profile": [ + "config": "Release" + ] + ] + + let scheme = try Scheme(name: "Scheme", jsonDictionary: schemeDictionary) + + let expectedRunVariables = [ + XCScheme.EnvironmentVariable(variable: "ENVIRONMENT", value: "VARIABLE", enabled: true), + XCScheme.EnvironmentVariable(variable: "OTHER_ENV_VAR", value: "VAL", enabled: false) + ] + + let expectedTestVariables = [XCScheme.EnvironmentVariable(variable: "TEST", value: "VARIABLE", enabled: true)] + + try expect(scheme.run?.environmentVariables) == expectedRunVariables + try expect(scheme.test?.environmentVariables) == expectedTestVariables + try expect(scheme.profile?.config) == "Release" + try expect(scheme.profile?.environmentVariables.isEmpty) == true + } + $0.it("parses settings") { let spec = try ProjectSpec(path: fixturePath + "settings_test.yml") let buildSettings: BuildSettings = ["SETTING": "value"]