Merge pull request #402 from yonaskolb/copy-files-ordering

Allow specifying run script and copy files phase ordering
This commit is contained in:
Yonas Kolb 2018-11-08 21:31:25 +11:00 committed by GitHub
commit 50ed42108e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 125 additions and 76 deletions

View File

@ -4,6 +4,10 @@
#### Fixed
- Fixed XPC Service package type [#435](https://github.com/yonaskolb/XcodeGen/pull/435) @alvarhansen
- Fixed phase ordering for modulemap and static libary header Copy File phases. [402](https://github.com/yonaskolb/XcodeGen/pull/402) @brentleyjones
#### Changed
- Changed spelling of build phases to **preBuildPhase** and **postBuildPhase**. [402](https://github.com/yonaskolb/XcodeGen/pull/402) @brentleyjones
## 2.0.0

View File

@ -185,8 +185,9 @@ Settings are merged in the following order: groups, base, configs.
- [ ] **transitivelyLinkDependencies**: **Bool** - If this is not specified the value from the project set in [Options](#options)`.transitivelyLinkDependencies` will be used.
- [ ] **directlyEmbedCarthageDependencies**: **Bool** - If this is `true` Carthage dependencies will be embedded using an `Embed Frameworks` build phase instead of the `copy-frameworks` script. Defaults to `true` for all targets except iOS/tvOS/watchOS Applications.
- [ ] **requiresObjCLinking**: **Bool** - If this is `true` any targets that link to this target will have `-ObjC` added to their `OTHER_LDFLAGS`. This is required if a static library has any catagories or extensions on Objective-C code. See [this guide](https://pewpewthespells.com/blog/objc_linker_flags.html#objc) for more details. Defaults to `true` if `type` is `library.static`. If you are 100% sure you don't have catagories or extensions on Objective-C code (pure Swift with no use of Foundation/UIKit) you can set this to `false`, otherwise it's best to leave it alone.
- [ ] **prebuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *before* any other build phases
- [ ] **postbuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *after* any other build phases
- [ ] **preBuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *before* any other build phases
- [ ] **postCompileScripts**: **[[Build Script](#build-script)]** - Build scripts that run after the Compile Sources phase
- [ ] **postBuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *after* any other build phases
- [ ] **buildRules**: **[[Build Rule](#build-rule)]** - Custom build rules
- [ ] **scheme**: **[Target Scheme](#target-scheme)** - Generated scheme with tests or config variants
- [ ] **legacy**: **[Legacy Target](#legacy-target)** - When present, opt-in to make an Xcode "External Build System" legacy target instead.
@ -404,7 +405,13 @@ targets:
### Build Script
Run script build phases added via **prebuildScripts** or **postBuildScripts**. They run before or after any other build phases respectively and in the order defined. Each script can contain:
Run script build phases can be added at 3 different points in the build:
- **preBuildScripts**: Before any other build phases
- **postCompileScripts**: After the compile sources build phase
- **postBuildScripts**: After any other build phases
Each script can contain:
- [x] **path**: **String** - a relative or absolute path to a shell script
- [x] **script**: **String** - an inline shell script
@ -422,7 +429,7 @@ A multiline script can be written using the various YAML multiline methods, for
```yaml
targets:
MyTarget:
prebuildScripts:
preBuildScripts:
- path: myscripts/my_script.sh
name: My Script
inputFiles:
@ -431,12 +438,15 @@ targets:
outputFiles:
- $(DERIVED_FILE_DIR)/file1
- $(DERIVED_FILE_DIR)/file2
postbuildScripts:
postCompileScripts:
- script: swiftlint
name: Swiftlint
- script: |
command do
othercommand
postBuildScripts:
- path: myscripts/my_final_script.sh
name: My Final Script
```
### Build Rule

View File

@ -11,7 +11,7 @@ public protocol ProjectTarget: BuildSettingsContainer {
extension Target {
public var buildScripts: [BuildScript] {
return prebuildScripts + postbuildScripts
return preBuildScripts + postCompileScripts + postBuildScripts
}
}

View File

@ -33,8 +33,9 @@ public struct Target: ProjectTarget {
public var transitivelyLinkDependencies: Bool?
public var directlyEmbedCarthageDependencies: Bool?
public var requiresObjCLinking: Bool?
public var prebuildScripts: [BuildScript]
public var postbuildScripts: [BuildScript]
public var preBuildScripts: [BuildScript]
public var postCompileScripts: [BuildScript]
public var postBuildScripts: [BuildScript]
public var buildRules: [BuildRule]
public var configFiles: [String: String]
public var scheme: TargetScheme?
@ -70,8 +71,9 @@ public struct Target: ProjectTarget {
transitivelyLinkDependencies: Bool? = nil,
directlyEmbedCarthageDependencies: Bool? = nil,
requiresObjCLinking: Bool? = nil,
prebuildScripts: [BuildScript] = [],
postbuildScripts: [BuildScript] = [],
preBuildScripts: [BuildScript] = [],
postCompileScripts: [BuildScript] = [],
postBuildScripts: [BuildScript] = [],
buildRules: [BuildRule] = [],
scheme: TargetScheme? = nil,
legacy: LegacyTarget? = nil,
@ -91,8 +93,9 @@ public struct Target: ProjectTarget {
self.transitivelyLinkDependencies = transitivelyLinkDependencies
self.directlyEmbedCarthageDependencies = directlyEmbedCarthageDependencies
self.requiresObjCLinking = requiresObjCLinking
self.prebuildScripts = prebuildScripts
self.postbuildScripts = postbuildScripts
self.preBuildScripts = preBuildScripts
self.postCompileScripts = postCompileScripts
self.postBuildScripts = postBuildScripts
self.buildRules = buildRules
self.scheme = scheme
self.legacy = legacy
@ -218,8 +221,9 @@ extension Target: Equatable {
lhs.info == rhs.info &&
lhs.entitlements == rhs.entitlements &&
lhs.dependencies == rhs.dependencies &&
lhs.prebuildScripts == rhs.prebuildScripts &&
lhs.postbuildScripts == rhs.postbuildScripts &&
lhs.preBuildScripts == rhs.preBuildScripts &&
lhs.postCompileScripts == rhs.postCompileScripts &&
lhs.postBuildScripts == rhs.postBuildScripts &&
lhs.buildRules == rhs.buildRules &&
lhs.scheme == rhs.scheme &&
lhs.legacy == rhs.legacy &&
@ -298,8 +302,9 @@ extension Target: NamedJSONDictionaryConvertible {
directlyEmbedCarthageDependencies = jsonDictionary.json(atKeyPath: "directlyEmbedCarthageDependencies")
requiresObjCLinking = jsonDictionary.json(atKeyPath: "requiresObjCLinking")
prebuildScripts = jsonDictionary.json(atKeyPath: "prebuildScripts") ?? []
postbuildScripts = jsonDictionary.json(atKeyPath: "postbuildScripts") ?? []
preBuildScripts = jsonDictionary.json(atKeyPath: "preBuildScripts") ?? jsonDictionary.json(atKeyPath: "prebuildScripts") ?? []
postCompileScripts = jsonDictionary.json(atKeyPath: "postCompileScripts") ?? []
postBuildScripts = jsonDictionary.json(atKeyPath: "postBuildScripts") ?? jsonDictionary.json(atKeyPath: "postbuildScripts") ?? []
buildRules = jsonDictionary.json(atKeyPath: "buildRules") ?? []
scheme = jsonDictionary.json(atKeyPath: "scheme")
legacy = jsonDictionary.json(atKeyPath: "legacy")

View File

@ -42,7 +42,8 @@ public struct TargetSource: Equatable {
public struct CopyFilesSettings: Equatable, Hashable {
public static let xpcServices = CopyFilesSettings(
destination: .productsDirectory,
subpath: "$(CONTENTS_FOLDER_PATH)/XPCServices"
subpath: "$(CONTENTS_FOLDER_PATH)/XPCServices",
phaseOrder: .postCompile
)
public enum Destination: String {
@ -73,12 +74,25 @@ public struct TargetSource: Equatable {
}
}
public enum PhaseOrder: String {
/// Run before the Compile Sources phase
case preCompile
/// Run after the Compile Sources and post-compile Run Script phases
case postCompile
}
public var destination: Destination
public var subpath: String
public var phaseOrder: PhaseOrder
public init(destination: Destination, subpath: String) {
public init(
destination: Destination,
subpath: String,
phaseOrder: PhaseOrder
) {
self.destination = destination
self.subpath = subpath
self.phaseOrder = phaseOrder
}
}
@ -190,5 +204,6 @@ extension TargetSource.BuildPhase.CopyFilesSettings: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
destination = try jsonDictionary.json(atKeyPath: "destination")
subpath = jsonDictionary.json(atKeyPath: "subpath") ?? ""
phaseOrder = .postCompile
}
}

View File

@ -297,6 +297,15 @@ public class PBXProjGenerator {
return addObject(shellScriptPhase)
}
func generateCopyFiles(targetName: String, copyFiles: TargetSource.BuildPhase.CopyFilesSettings, buildPhaseFiles: [PBXBuildFile]) -> PBXCopyFilesBuildPhase {
let copyFilesBuildPhase = PBXCopyFilesBuildPhase(
dstPath: copyFiles.subpath,
dstSubfolderSpec: copyFiles.destination.destination,
files: buildPhaseFiles
)
return addObject(copyFilesBuildPhase)
}
func generateTargetAttributes() -> [PBXTarget: [String: Any]] {
var targetAttributes: [PBXTarget: [String: Any]] = [:]
@ -634,7 +643,13 @@ public class PBXProjGenerator {
return sourceFilesByCopyFiles.mapValues { getBuildFilesForSourceFiles($0) }
}
buildPhases += try target.prebuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) }
copyFilesBuildPhasesFiles.merge(getBuildFilesForCopyFilesPhases()) { $0 + $1 }
buildPhases += try target.preBuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) }
buildPhases += copyFilesBuildPhasesFiles
.filter { $0.key.phaseOrder == .preCompile }
.map { generateCopyFiles(targetName: target.name, copyFiles: $0, buildPhaseFiles: $1) }
let headersBuildPhaseFiles = getBuildFilesForPhase(.headers)
if !headersBuildPhaseFiles.isEmpty {
@ -650,6 +665,8 @@ public class PBXProjGenerator {
let sourcesBuildPhase = addObject(PBXSourcesBuildPhase(files: sourcesBuildPhaseFiles))
buildPhases.append(sourcesBuildPhase)
buildPhases += try target.postCompileScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) }
let resourcesBuildPhaseFiles = getBuildFilesForPhase(.resources) + copyResourcesReferences
if !resourcesBuildPhaseFiles.isEmpty {
let resourcesBuildPhase = addObject(PBXResourcesBuildPhase(files: resourcesBuildPhaseFiles))
@ -676,19 +693,27 @@ public class PBXProjGenerator {
buildPhases.append(script)
}
copyFilesBuildPhasesFiles.merge(getBuildFilesForCopyFilesPhases()) { $0 + $1 }
if !copyFilesBuildPhasesFiles.isEmpty {
for (copyFiles, buildPhaseFiles) in copyFilesBuildPhasesFiles {
let copyFilesBuildPhase = addObject(
PBXCopyFilesBuildPhase(
dstPath: copyFiles.subpath,
dstSubfolderSpec: copyFiles.destination.destination,
files: buildPhaseFiles
)
)
buildPhases += copyFilesBuildPhasesFiles
.filter { $0.key.phaseOrder == .postCompile }
.map { generateCopyFiles(targetName: target.name, copyFiles: $0, buildPhaseFiles: $1) }
buildPhases.append(copyFilesBuildPhase)
}
if !carthageFrameworksToEmbed.isEmpty {
let inputPaths = carthageFrameworksToEmbed
.map { "$(SRCROOT)/\(carthageBuildPath)/\(target.platform)/\($0)\($0.contains(".") ? "" : ".framework")" }
let outputPaths = carthageFrameworksToEmbed
.map { "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/\($0)\($0.contains(".") ? "" : ".framework")" }
let carthageExecutable = project.options.carthageExecutablePath ?? "carthage"
let carthageScript = addObject(
PBXShellScriptBuildPhase(
name: "Carthage",
inputPaths: inputPaths,
outputPaths: outputPaths,
shellPath: "/bin/sh",
shellScript: "\(carthageExecutable) copy-frameworks\n"
)
)
buildPhases.append(carthageScript)
}
if !targetFrameworkBuildFiles.isEmpty {
@ -742,25 +767,6 @@ public class PBXProjGenerator {
buildPhases.append(copyFilesPhase)
}
if !carthageFrameworksToEmbed.isEmpty {
let inputPaths = carthageFrameworksToEmbed
.map { "$(SRCROOT)/\(carthageBuildPath)/\(target.platform)/\($0)\($0.contains(".") ? "" : ".framework")" }
let outputPaths = carthageFrameworksToEmbed
.map { "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/\($0)\($0.contains(".") ? "" : ".framework")" }
let carthageExecutable = project.options.carthageExecutablePath ?? "carthage"
let carthageScript = addObject(
PBXShellScriptBuildPhase(
name: "Carthage",
inputPaths: inputPaths,
outputPaths: outputPaths,
shellPath: "/bin/sh",
shellScript: "\(carthageExecutable) copy-frameworks\n"
)
)
buildPhases.append(carthageScript)
}
let buildRules = target.buildRules.map { buildRule in
addObject(
PBXBuildRule(
@ -776,7 +782,7 @@ public class PBXProjGenerator {
)
}
buildPhases += try target.postbuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) }
buildPhases += try target.postBuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) }
let configs: [XCBuildConfiguration] = project.configs.map { config in
var buildSettings = project.getTargetBuildSettings(target: target, config: config)

View File

@ -69,7 +69,8 @@ class SourceGenerator {
if headerVisibility == .public {
chosenBuildPhase = .copyFiles(TargetSource.BuildPhase.CopyFilesSettings(
destination: .productsDirectory,
subpath: "include/$(PRODUCT_NAME)"
subpath: "include/$(PRODUCT_NAME)",
phaseOrder: .preCompile
))
} else {
chosenBuildPhase = nil
@ -188,7 +189,8 @@ class SourceGenerator {
guard targetType == .staticLibrary else { return nil }
return .copyFiles(TargetSource.BuildPhase.CopyFilesSettings(
destination: .productsDirectory,
subpath: "include/$(PRODUCT_NAME)"
subpath: "include/$(PRODUCT_NAME)",
phaseOrder: .preCompile
))
case "framework": return .frameworks
case "xpc": return .copyFiles(.xpcServices)

View File

@ -967,8 +967,8 @@
isa = PBXNativeTarget;
buildConfigurationList = CL_3BB54CFB2F2EFE7302C0B0709BD0AE04 /* Build configuration list for PBXNativeTarget "StaticLibrary_ObjC_macOS" */;
buildPhases = (
SBP_52AE5DCFB4487297D6889C7F014AD281 /* Sources */,
CFBP_712E86A86BBF89DB5B5F3874B24B8996 /* CopyFiles */,
SBP_52AE5DCFB4487297D6889C7F014AD281 /* Sources */,
);
buildRules = (
);
@ -1031,8 +1031,8 @@
isa = PBXNativeTarget;
buildConfigurationList = CL_2E30D27084B352548CCAA3500158519B /* Build configuration list for PBXNativeTarget "StaticLibrary_ObjC_watchOS" */;
buildPhases = (
SBP_924258C7A9F27B13508604CAF625E0D3 /* Sources */,
CFBP_4DFB7882C9F8BAB250BB0A6B9457B4CC /* CopyFiles */,
SBP_924258C7A9F27B13508604CAF625E0D3 /* Sources */,
);
buildRules = (
);
@ -1049,8 +1049,8 @@
buildPhases = (
SBP_ED63FCA00BD617EFF125ECDF9D24FAD8 /* Sources */,
RBP_5623703FDC4F6DF4679D8BAF27518F2F /* Resources */,
CFBP_469B033759EACBB99ECBF1008677C590 /* Embed App Extensions */,
SSBP_7F147E7ED45BAAE2186975B7FF9EB08A /* Carthage */,
CFBP_469B033759EACBB99ECBF1008677C590 /* Embed App Extensions */,
);
buildRules = (
);
@ -1133,8 +1133,8 @@
isa = PBXNativeTarget;
buildConfigurationList = CL_F6ECF2D45799DBBB48DE9AE80AC280AA /* Build configuration list for PBXNativeTarget "StaticLibrary_ObjC_iOS" */;
buildPhases = (
SBP_D6209A702C851D0CFD1CC2B225986D45 /* Sources */,
CFBP_0B6C520DAC94DDCE678BE15C53528F25 /* CopyFiles */,
SBP_D6209A702C851D0CFD1CC2B225986D45 /* Sources */,
);
buildRules = (
);
@ -1278,10 +1278,10 @@
SBP_77EF8BC7FC3D693C9C0C1CB51984F3E2 /* Sources */,
RBP_85DF5DDC76E0E2A78CAFC9A3EC508232 /* Resources */,
CFBP_D7E07645BC437C4DFBB212DFC4F3B09E /* CopyFiles */,
SSBP_7F8DED07519BA4B70C40A9A755844874 /* Carthage */,
FBP_18D4325BBE506E88E1C742F57AECB0CD /* Frameworks */,
CFBP_1C40B0777F31334440F91C2DB34EF404 /* Embed Frameworks */,
CFBP_961F46C30E720E886AEFD11D45DDA199 /* Embed Watch Content */,
SSBP_7F8DED07519BA4B70C40A9A755844874 /* Carthage */,
SSBP_062CBBF024005F57EECA660F9C7B0C7D /* Strip Unused Architectures from Frameworks */,
SSBP_376C0662E4E8416C049A50660864798B /* MyScript */,
);
@ -1320,8 +1320,8 @@
isa = PBXNativeTarget;
buildConfigurationList = CL_91682ADED17A2C81A700A67F5D70BA1F /* Build configuration list for PBXNativeTarget "StaticLibrary_ObjC_tvOS" */;
buildPhases = (
SBP_645418A0FA6E3F5727685DE191C6B793 /* Sources */,
CFBP_CC1EF1963551F7E08925519982C248B9 /* CopyFiles */,
SBP_645418A0FA6E3F5727685DE191C6B793 /* Sources */,
);
buildRules = (
);

View File

@ -741,22 +741,25 @@ class ProjectGeneratorTests: XCTestCase {
$0.it("generates run scripts") {
var scriptSpec = project
scriptSpec.targets[0].prebuildScripts = [BuildScript(script: .script("script1"))]
scriptSpec.targets[0].postbuildScripts = [BuildScript(script: .script("script2"))]
scriptSpec.targets[0].preBuildScripts = [BuildScript(script: .script("script1"))]
scriptSpec.targets[0].postCompileScripts = [BuildScript(script: .script("script2"))]
scriptSpec.targets[0].postBuildScripts = [BuildScript(script: .script("script3"))]
let pbxProject = try scriptSpec.generatePbxProj()
guard let nativeTarget = pbxProject.nativeTargets
.first(where: { $0.buildPhases.count >= 2 }) else {
.first(where: { $0.buildPhases.count >= 3 }) else {
throw failure("Target with build phases not found")
}
let buildPhases = nativeTarget.buildPhases
let scripts = pbxProject.shellScriptBuildPhases
let script1 = scripts.first { $0.shellScript == "script1" }
let script2 = scripts.first { $0.shellScript == "script2" }
try expect(scripts.count) == 2
try expect(buildPhases.first) == script1
try expect(buildPhases.last) == script2
try expect(scripts.count) == 3
let script1 = scripts.first { $0.shellScript == "script1" }!
let script2 = scripts.first { $0.shellScript == "script2" }!
let script3 = scripts.first { $0.shellScript == "script3" }!
try expect(buildPhases.contains(script1)) == true
try expect(buildPhases.contains(script2)) == true
try expect(buildPhases.contains(script3)) == true
}
$0.it("generates targets with cylical dependencies") {

View File

@ -128,8 +128,9 @@ class ProjectSpecTests: XCTestCase {
configFiles: ["invalidConfig": "invalidConfigFile"],
sources: ["invalidSource"],
dependencies: [Dependency(type: .target, reference: "invalidDependency")],
prebuildScripts: [BuildScript(script: .path("invalidPrebuildScript"), name: "prebuildScript1")],
postbuildScripts: [BuildScript(script: .path("invalidPostbuildScript"))],
preBuildScripts: [BuildScript(script: .path("invalidPreBuildScript"), name: "preBuildScript1")],
postCompileScripts: [BuildScript(script: .path("invalidPostCompileScript"))],
postBuildScripts: [BuildScript(script: .path("invalidPostBuildScript"))],
scheme: TargetScheme(testTargets: ["invalidTarget"])
)]
@ -139,8 +140,9 @@ class ProjectSpecTests: XCTestCase {
try expectValidationError(project, .invalidTargetSource(target: "target1", source: "invalidSource"))
try expectValidationError(project, .invalidBuildSettingConfig("invalidConfig"))
try expectValidationError(project, .invalidSettingsGroup("invalidSettingGroup"))
try expectValidationError(project, .invalidBuildScriptPath(target: "target1", name: "prebuildScript1", path: "invalidPrebuildScript"))
try expectValidationError(project, .invalidBuildScriptPath(target: "target1", name: nil, path: "invalidPostbuildScript"))
try expectValidationError(project, .invalidBuildScriptPath(target: "target1", name: "preBuildScript1", path: "invalidPreBuildScript"))
try expectValidationError(project, .invalidBuildScriptPath(target: "target1", name: nil, path: "invalidPostCompileScript"))
try expectValidationError(project, .invalidBuildScriptPath(target: "target1", name: nil, path: "invalidPostBuildScript"))
try expectValidationError(project, .missingConfigForTargetScheme(target: "target1", configType: .debug))
try expectValidationError(project, .missingConfigForTargetScheme(target: "target1", configType: .release))

View File

@ -439,8 +439,9 @@ class SpecLoadingTests: XCTestCase {
["script": "shell script\ndo thing", "name": "myscript", "inputFiles": ["file", "file2"], "outputFiles": ["file", "file2"], "shell": "bin/customshell", "runOnlyWhenInstalling": true],
["script": "shell script\ndo thing", "name": "myscript", "inputFiles": ["file", "file2"], "outputFiles": ["file", "file2"], "shell": "bin/customshell", "showEnvVars": false],
]
target["prebuildScripts"] = scripts
target["postbuildScripts"] = scripts
target["preBuildScripts"] = scripts
target["postCompileScripts"] = scripts
target["postBuildScripts"] = scripts
let expectedScripts = [
BuildScript(script: .path("script.sh")),
@ -449,8 +450,9 @@ class SpecLoadingTests: XCTestCase {
]
let parsedTarget = try Target(name: "test", jsonDictionary: target)
try expect(parsedTarget.prebuildScripts) == expectedScripts
try expect(parsedTarget.postbuildScripts) == expectedScripts
try expect(parsedTarget.preBuildScripts) == expectedScripts
try expect(parsedTarget.postCompileScripts) == expectedScripts
try expect(parsedTarget.postBuildScripts) == expectedScripts
}
$0.it("parses build rules") {