Merge pull request #345 from brentleyjones/modulemap

Add support for Copy Files build phase
This commit is contained in:
Brentley Jones 2018-07-31 08:01:58 -05:00 committed by GitHub
commit 717c899179
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 169 additions and 22 deletions

View File

@ -8,7 +8,7 @@
- Added `targetTemplates` [355](https://github.com/yonaskolb/XcodeGen/pull/355) @yonaskolb - Added `targetTemplates` [355](https://github.com/yonaskolb/XcodeGen/pull/355) @yonaskolb
- Added `aggregateTargets` [354](https://github.com/yonaskolb/XcodeGen/pull/354) @yonaskolb - Added `aggregateTargets` [354](https://github.com/yonaskolb/XcodeGen/pull/354) @yonaskolb
- Added `options.groupSortPosition` [356](https://github.com/yonaskolb/XcodeGen/pull/356) @yonaskolb - Added `options.groupSortPosition` [356](https://github.com/yonaskolb/XcodeGen/pull/356) @yonaskolb
- Added ability to specify `copyFiles` build phase for sources [345](https://github.com/yonaskolb/XcodeGen/pull/345) @brentleyjones
#### Fixed #### Fixed
- Sort files using localizedStandardCompare [341](https://github.com/yonaskolb/XcodeGen/pull/341) @rohitpal440 - Sort files using localizedStandardCompare [341](https://github.com/yonaskolb/XcodeGen/pull/341) @rohitpal440

View File

@ -261,21 +261,34 @@ A source can be provided via a string (the path) or an object of the form:
- `sources` - Compile Sources phase - `sources` - Compile Sources phase
- `resources` - Copy Bundle Resources phase - `resources` - Copy Bundle Resources phase
- `headers` - Headers Phase - `headers` - Headers Phase
- `copyFiles` - Copy Files Phase. Must be specified as an object with the following fields:
- [x] **destination**: **String** - Destination of the Copy Files phase. This can be one of the following values:
- `absolutePath`
- `productsDirectory`
- `wrapper`
- `executables`
- `resources`
- `javaResources`
- `frameworks`
- `sharedFrameworks`
- `sharedSupport`
- `plugins`
- [ ] **subpath**: **String** - The path inside of the destination to copy the files.
- `none` - Will not be added to any build phases - `none` - Will not be added to any build phases
- [ ] **type**: **String**: This can be one of the following values - [ ] **type**: **String**: This can be one of the following values
- `file`: a file reference with a parent group will be created (Default for files or directories with extensions) - `file`: a file reference with a parent group will be created (Default for files or directories with extensions)
- `group`: a group with all it's containing files. (Default for directories without extensions) - `group`: a group with all it's containing files. (Default for directories without extensions)
- `folder`: a folder reference. - `folder`: a folder reference.
- [ ] **headerVisibility**: **String** The visibility of any headers. This defaults to `public`, but can be either: - [ ] **headerVisibility**: **String** - The visibility of any headers. This defaults to `public`, but can be either:
- `public` - `public`
- `private` - `private`
- `project` - `project`
```yaml ```yaml
targets: targets:
MyTarget MyTarget:
sources: MyTargetSource sources: MyTargetSource
MyOtherTarget MyOtherTarget:
sources: sources:
- MyOtherTargetSource1 - MyOtherTargetSource1
- path: MyOtherTargetSource2 - path: MyOtherTargetSource2
@ -289,6 +302,11 @@ targets:
- "-Wextra" - "-Wextra"
- path: MyOtherTargetSource3 - path: MyOtherTargetSource3
compilerFlags: "-Werror -Wextra" compilerFlags: "-Werror -Wextra"
- path: ModuleMaps
buildPhase:
copyFiles:
destination: productsDirectory
subpath: include/$(PRODUCT_NAME)
- path: Resources - path: Resources
type: folder type: folder
``` ```

View File

@ -4,7 +4,7 @@ public enum SpecParsingError: Error, CustomStringConvertible {
case unknownTargetType(String) case unknownTargetType(String)
case unknownTargetPlatform(String) case unknownTargetPlatform(String)
case invalidDependency([String: Any]) case invalidDependency([String: Any])
case unknownSourceBuildPhase(String) case invalidSourceBuildPhase(String)
case invalidVersion(String) case invalidVersion(String)
public var description: String { public var description: String {
@ -15,8 +15,8 @@ public enum SpecParsingError: Error, CustomStringConvertible {
return "Unknown Target platform: \(platform)" return "Unknown Target platform: \(platform)"
case let .invalidDependency(dependency): case let .invalidDependency(dependency):
return "Unknown Target dependency: \(dependency)" return "Unknown Target dependency: \(dependency)"
case let .unknownSourceBuildPhase(buildPhase): case let .invalidSourceBuildPhase(error):
return "Unknown Source Build Phase: \(buildPhase)" return "Invalid Source Build Phase: \(error)"
case let .invalidVersion(version): case let .invalidVersion(version):
return "Invalid version: \(version)" return "Invalid version: \(version)"
} }

View File

@ -28,17 +28,59 @@ public struct TargetSource: Equatable {
} }
} }
public enum BuildPhase: String { public enum BuildPhase: Equatable {
case sources case sources
case headers case headers
case resources case resources
case copyFiles(CopyFilesSettings)
case none case none
// Not currently exposed as selectable options, but used internally
case frameworks
case runScript
case carbonResources
public struct CopyFilesSettings: Equatable, Hashable {
public enum Destination: String {
case absolutePath
case productsDirectory
case wrapper
case executables
case resources
case javaResources
case frameworks
case sharedFrameworks
case sharedSupport
case plugins
public var destination: xcproj.PBXCopyFilesBuildPhase.SubFolder? {
switch self {
case .absolutePath: return .absolutePath
case .productsDirectory: return .productsDirectory
case .wrapper: return .wrapper
case .executables: return .executables
case .resources: return .resources
case .javaResources: return .javaResources
case .frameworks: return .frameworks
case .sharedFrameworks: return .sharedFrameworks
case .sharedSupport: return .sharedSupport
case .plugins: return .plugins
}
}
}
public var destination: Destination
public var subpath: String
}
public var buildPhase: xcproj.BuildPhase? { public var buildPhase: xcproj.BuildPhase? {
switch self { switch self {
case .sources: return .sources case .sources: return .sources
case .headers: return .headers case .headers: return .headers
case .resources: return .resources case .resources: return .resources
case .copyFiles: return .copyFiles
case .frameworks: return .frameworks
case .runScript: return .runScript
case .carbonResources: return .carbonResources
case .none: return nil case .none: return nil
} }
} }
@ -100,12 +142,42 @@ extension TargetSource: JSONObjectConvertible {
excludes = jsonDictionary.json(atKeyPath: "excludes") ?? [] excludes = jsonDictionary.json(atKeyPath: "excludes") ?? []
type = jsonDictionary.json(atKeyPath: "type") type = jsonDictionary.json(atKeyPath: "type")
optional = jsonDictionary.json(atKeyPath: "optional") ?? false optional = jsonDictionary.json(atKeyPath: "optional") ?? false
if let string: String = jsonDictionary.json(atKeyPath: "buildPhase") { if let string: String = jsonDictionary.json(atKeyPath: "buildPhase") {
if let buildPhase = BuildPhase(rawValue: string) { buildPhase = try BuildPhase(string: string)
self.buildPhase = buildPhase } else if let dict: JSONDictionary = jsonDictionary.json(atKeyPath: "buildPhase") {
} else { buildPhase = try BuildPhase(jsonDictionary: dict)
throw SpecParsingError.unknownSourceBuildPhase(string)
}
} }
} }
} }
extension TargetSource.BuildPhase {
public init(string: String) throws {
switch string {
case "sources": self = .sources
case "headers": self = .headers
case "resources": self = .resources
case "copyFiles":
throw SpecParsingError.invalidSourceBuildPhase("copyFiles must specify a \"destination\" and optional \"subpath\"")
case "none": self = .none
default:
throw SpecParsingError.invalidSourceBuildPhase(string.quoted)
}
}
}
extension TargetSource.BuildPhase: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
self = .copyFiles(try jsonDictionary.json(atKeyPath: "copyFiles"))
}
}
extension TargetSource.BuildPhase.CopyFilesSettings: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
destination = try jsonDictionary.json(atKeyPath: "destination")
subpath = jsonDictionary.json(atKeyPath: "subpath") ?? ""
}
}

View File

@ -590,9 +590,8 @@ public class PBXProjGenerator {
let fileReference = targetFileReferences[target.name] let fileReference = targetFileReferences[target.name]
var buildPhases: [String] = [] var buildPhases: [String] = []
func getBuildFilesForPhase(_ buildPhase: BuildPhase) -> [String] { func getBuildFilesForSourceFiles(_ sourceFiles: [SourceFile]) -> [String] {
let files = sourceFiles let files = sourceFiles
.filter { $0.buildPhase == buildPhase }
.reduce(into: [SourceFile]()) { output, sourceFile in .reduce(into: [SourceFile]()) { output, sourceFile in
if !output.contains(where: { $0.fileReference == sourceFile.fileReference }) { if !output.contains(where: { $0.fileReference == sourceFile.fileReference }) {
output.append(sourceFile) output.append(sourceFile)
@ -602,6 +601,21 @@ public class PBXProjGenerator {
return files.map { createObject(id: $0.fileReference + target.name, $0.buildFile) } return files.map { createObject(id: $0.fileReference + target.name, $0.buildFile) }
.map { $0.reference } .map { $0.reference }
} }
func getBuildFilesForPhase(_ buildPhase: BuildPhase) -> [String] {
let filteredSourceFiles = sourceFiles
.filter { $0.buildPhase?.buildPhase == buildPhase }
return getBuildFilesForSourceFiles(filteredSourceFiles)
}
func getBuildFilesForCopyFilesPhases() -> [TargetSource.BuildPhase.CopyFilesSettings: [String]] {
var sourceFilesByCopyFiles: [TargetSource.BuildPhase.CopyFilesSettings: [SourceFile]] = [:]
for sourceFile in sourceFiles {
guard case let .copyFiles(copyFilesSettings)? = sourceFile.buildPhase else { continue }
sourceFilesByCopyFiles[copyFilesSettings, default: []].append(sourceFile)
}
return sourceFilesByCopyFiles.mapValues { getBuildFilesForSourceFiles($0) }
}
buildPhases += try target.prebuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) } buildPhases += try target.prebuildScripts.map { try generateBuildScript(targetName: target.name, buildScript: $0) }
@ -614,6 +628,22 @@ public class PBXProjGenerator {
let resourcesBuildPhase = createObject(id: target.name, PBXResourcesBuildPhase(files: resourcesBuildPhaseFiles)) let resourcesBuildPhase = createObject(id: target.name, PBXResourcesBuildPhase(files: resourcesBuildPhaseFiles))
buildPhases.append(resourcesBuildPhase.reference) buildPhases.append(resourcesBuildPhase.reference)
} }
let copyFilesBuildPhasesFiles = getBuildFilesForCopyFilesPhases()
if !copyFilesBuildPhasesFiles.isEmpty {
for (copyFiles, buildPhaseFiles) in copyFilesBuildPhasesFiles {
let copyFilesBuildPhase = createObject(
id: "copy files" + copyFiles.destination.rawValue + copyFiles.subpath + target.name,
PBXCopyFilesBuildPhase(
dstPath: copyFiles.subpath,
dstSubfolderSpec: copyFiles.destination.destination,
files: buildPhaseFiles
)
)
buildPhases.append(copyFilesBuildPhase.reference)
}
}
let headersBuildPhaseFiles = getBuildFilesForPhase(.headers) let headersBuildPhaseFiles = getBuildFilesForPhase(.headers)
if !headersBuildPhaseFiles.isEmpty && (target.type == .framework || target.type == .dynamicLibrary) { if !headersBuildPhaseFiles.isEmpty && (target.type == .framework || target.type == .dynamicLibrary) {

View File

@ -7,7 +7,7 @@ struct SourceFile {
let path: Path let path: Path
let fileReference: String let fileReference: String
let buildFile: PBXBuildFile let buildFile: PBXBuildFile
let buildPhase: BuildPhase? let buildPhase: TargetSource.BuildPhase?
} }
class SourceGenerator { class SourceGenerator {
@ -52,15 +52,15 @@ class SourceGenerator {
_ = try getSourceFiles(targetSource: TargetSource(path: path), path: fullPath) _ = try getSourceFiles(targetSource: TargetSource(path: path), path: fullPath)
} }
func generateSourceFile(targetSource: TargetSource, path: Path, buildPhase: BuildPhase? = nil) -> SourceFile { func generateSourceFile(targetSource: TargetSource, path: Path, buildPhase: TargetSource.BuildPhase? = nil) -> SourceFile {
let fileReference = fileReferencesByPath[path.string.lowercased()]! let fileReference = fileReferencesByPath[path.string.lowercased()]!
var settings: [String: Any] = [:] var settings: [String: Any] = [:]
let chosenBuildPhase: BuildPhase? let chosenBuildPhase: TargetSource.BuildPhase?
if let buildPhase = buildPhase { if let buildPhase = buildPhase {
chosenBuildPhase = buildPhase chosenBuildPhase = buildPhase
} else if let buildPhase = targetSource.buildPhase { } else if let buildPhase = targetSource.buildPhase {
chosenBuildPhase = buildPhase.buildPhase chosenBuildPhase = buildPhase
} else { } else {
chosenBuildPhase = getDefaultBuildPhase(for: path) chosenBuildPhase = getDefaultBuildPhase(for: path)
} }
@ -156,7 +156,7 @@ class SourceGenerator {
} }
/// returns a default build phase for a given path. This is based off the filename /// returns a default build phase for a given path. This is based off the filename
private func getDefaultBuildPhase(for path: Path) -> BuildPhase? { private func getDefaultBuildPhase(for path: Path) -> TargetSource.BuildPhase? {
if path.lastComponent == "Info.plist" { if path.lastComponent == "Info.plist" {
return nil return nil
} }
@ -424,9 +424,9 @@ class SourceGenerator {
rootGroups.insert(fileReference) rootGroups.insert(fileReference)
} }
let buildPhase: BuildPhase? let buildPhase: TargetSource.BuildPhase?
if let targetBuildPhase = targetSource.buildPhase { if let targetBuildPhase = targetSource.buildPhase {
buildPhase = targetBuildPhase.buildPhase buildPhase = targetBuildPhase
} else { } else {
buildPhase = .resources buildPhase = .resources
} }

View File

@ -79,6 +79,7 @@
BF_734036107922 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_183521624014 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; }; BF_734036107922 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_183521624014 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
BF_747443236192 /* App_watchOS.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = FR_324671077936 /* App_watchOS.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; BF_747443236192 /* App_watchOS.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = FR_324671077936 /* App_watchOS.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
BF_757906110813 = {isa = PBXBuildFile; fileRef = FR_662315837182 /* Framework.framework */; }; BF_757906110813 = {isa = PBXBuildFile; fileRef = FR_662315837182 /* Framework.framework */; };
BF_807556340853 /* Empty.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_837374194182 /* Empty.h */; };
BF_813358525536 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_183521624014 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; }; BF_813358525536 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_183521624014 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
BF_828878846239 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_201160695646 /* MainInterface.storyboard */; }; BF_828878846239 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_201160695646 /* MainInterface.storyboard */; };
BF_830383951771 /* NotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_304712043717 /* NotificationController.swift */; }; BF_830383951771 /* NotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_304712043717 /* NotificationController.swift */; };
@ -261,6 +262,16 @@
name = "Embed Frameworks"; name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
CFBP_7191905390 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
BF_807556340853 /* Empty.h in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -323,6 +334,7 @@
FR_815403394914 /* Headers */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Headers; sourceTree = SOURCE_ROOT; }; FR_815403394914 /* Headers */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Headers; sourceTree = SOURCE_ROOT; };
FR_825232110500 /* App_iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = App_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; FR_825232110500 /* App_iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = App_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
FR_830053537293 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; FR_830053537293 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
FR_837374194182 /* Empty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Empty.h; sourceTree = "<group>"; };
FR_854336462818 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; FR_854336462818 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
FR_868653349092 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; FR_868653349092 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
FR_935153865209 /* iMessageApp.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = iMessageApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; FR_935153865209 /* iMessageApp.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = iMessageApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -412,6 +424,14 @@
path = "iMessage MessagesExtension"; path = "iMessage MessagesExtension";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
G_1885780145626 /* CopyFiles */ = {
isa = PBXGroup;
children = (
FR_837374194182 /* Empty.h */,
);
path = CopyFiles;
sourceTree = "<group>";
};
G_1952740716080 /* Frameworks */ = { G_1952740716080 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -585,6 +605,7 @@
G_3246710779368 /* App_watchOS */, G_3246710779368 /* App_watchOS */,
G_5070234922517 /* App_watchOS Extension */, G_5070234922517 /* App_watchOS Extension */,
G_8340618952527 /* Configs */, G_8340618952527 /* Configs */,
G_1885780145626 /* CopyFiles */,
G_3234630030493 /* FileGroup */, G_3234630030493 /* FileGroup */,
G_4661500274312 /* Framework */, G_4661500274312 /* Framework */,
G_8268950006174 /* iMessage */, G_8268950006174 /* iMessage */,
@ -874,6 +895,7 @@
buildPhases = ( buildPhases = (
SBP_82523211050 /* Sources */, SBP_82523211050 /* Sources */,
RBP_82523211050 /* Resources */, RBP_82523211050 /* Resources */,
CFBP_7191905390 /* CopyFiles */,
FBP_82523211050 /* Frameworks */, FBP_82523211050 /* Frameworks */,
CFBP_6493932244 /* Embed Frameworks */, CFBP_6493932244 /* Embed Frameworks */,
CFBP_2836118931 /* Embed Watch Content */, CFBP_2836118931 /* Embed Watch Content */,

View File

@ -47,6 +47,11 @@ targets:
- path: Mintfile - path: Mintfile
type: file type: file
buildPhase: none buildPhase: none
- path: CopyFiles
buildPhase:
copyFiles:
destination: productsDirectory
subpath: include/$(PRODUCT_NAME)
settings: settings:
INFOPLIST_FILE: App_iOS/Info.plist INFOPLIST_FILE: App_iOS/Info.plist
PRODUCT_BUNDLE_IDENTIFIER: com.project.app PRODUCT_BUNDLE_IDENTIFIER: com.project.app