Merge branch 'master' into copy-files-ordering

This commit is contained in:
Brentley Jones
2018-11-05 10:38:21 -06:00
71 changed files with 7019 additions and 3758 deletions
+56
View File
@@ -0,0 +1,56 @@
import Foundation
import PathKit
import ProjectSpec
import xcodeproj
public class FileWriter {
let project: Project
public init(project: Project) {
self.project = project
}
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 {
try projectPath.copy(tempPath)
}
try xcodeProject.write(path: tempPath, override: true)
try? projectPath.delete()
try tempPath.copy(projectPath)
try? tempPath.delete()
}
public func writePlists() throws {
let infoPlistGenerator = InfoPlistGenerator()
for target in project.targets {
// write Info.plist
if let plist = target.info {
let properties = infoPlistGenerator.generateProperties(target: target).merged(plist.properties)
try writePlist(properties, path: plist.path)
}
// write entitlements
if let plist = target.entitlements {
try writePlist(plist.properties, path: plist.path)
}
}
}
private func writePlist(_ plist: [String: Any], path: String) throws {
let path = project.basePath + path
if path.exists, let data: Data = try? path.read(),
let existingPlist = (try? PropertyListSerialization.propertyList(from: data, format: nil)) as? [String: Any], NSDictionary(dictionary: plist).isEqual(to: existingPlist) {
// file is the same
return
}
let data = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0)
try? path.delete()
try path.parent().mkpath()
try path.write(data)
}
}
@@ -0,0 +1,43 @@
import Foundation
import PathKit
import ProjectSpec
public class InfoPlistGenerator {
/**
Default info plist attributes taken from:
/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/Project Templates/Base/Base_DefinitionsInfoPlist.xctemplate/TemplateInfo.plist
*/
var defaultInfoPlist: [String: Any] = {
var dictionary: [String: Any] = [:]
dictionary["CFBundleIdentifier"] = "$(PRODUCT_BUNDLE_IDENTIFIER)"
dictionary["CFBundleInfoDictionaryVersion"] = "6.0"
dictionary["CFBundleExecutable"] = "$(EXECUTABLE_NAME)"
dictionary["CFBundleName"] = "$(PRODUCT_NAME)"
dictionary["CFBundleDevelopmentRegion"] = "$(DEVELOPMENT_LANGUAGE)"
dictionary["CFBundleShortVersionString"] = "1.0"
dictionary["CFBundleVersion"] = "1"
return dictionary
}()
public func generateProperties(target: Target) -> [String: Any] {
var targetInfoPlist = defaultInfoPlist
switch target.type {
case .uiTestBundle,
.unitTestBundle:
targetInfoPlist["CFBundlePackageType"] = "BNDL"
case .application,
.watch2App:
targetInfoPlist["CFBundlePackageType"] = "APPL"
case .framework:
targetInfoPlist["CFBundlePackageType"] = "FMWK"
case .bundle:
targetInfoPlist["CFBundlePackageType"] = "BNDL"
case .xpcService,
.appExtension:
targetInfoPlist["CFBundlePackageType"] = "XPC!"
default: break
}
return targetInfoPlist
}
}
File diff suppressed because it is too large Load Diff
+13 -228
View File
@@ -2,7 +2,7 @@ import Foundation
import JSONUtilities
import PathKit
import ProjectSpec
import xcproj
import xcodeproj
import Yams
public class ProjectGenerator {
@@ -13,21 +13,21 @@ public class ProjectGenerator {
self.project = project
}
var defaultDebugConfig: Config {
return project.configs.first { $0.type == .debug }!
}
var defaultReleaseConfig: Config {
return project.configs.first { $0.type == .release }!
}
public func generateXcodeProject() throws -> XcodeProj {
try project.validate()
// generate PBXProj
let pbxProjGenerator = PBXProjGenerator(project: project)
let pbxProject = try pbxProjGenerator.generate()
let pbxProj = try pbxProjGenerator.generate()
// generate Schemes
let schemeGenerator = SchemeGenerator(project: project, pbxProj: pbxProj)
let schemes = try schemeGenerator.generateSchemes()
// generate Workspace
let workspace = try generateWorkspace()
let sharedData = try generateSharedData(pbxProject: pbxProject)
return XcodeProj(workspace: workspace, pbxproj: pbxProject, sharedData: sharedData)
let sharedData = XCSharedData(schemes: schemes)
return XcodeProj(workspace: workspace, pbxproj: pbxProj, sharedData: sharedData)
}
func generateWorkspace() throws -> XCWorkspace {
@@ -35,219 +35,4 @@ public class ProjectGenerator {
let workspaceData = XCWorkspaceData(children: [dataElement])
return XCWorkspace(data: workspaceData)
}
func generateScheme(_ scheme: Scheme, pbxProject: PBXProj) throws -> XCScheme {
func getBuildEntry(_ buildTarget: Scheme.BuildTarget) -> XCScheme.BuildAction.Entry {
guard let targetReference = pbxProject.objects.targets(named: buildTarget.target).first else {
fatalError("Unable to find target named \"\(buildTarget.target)\" in \"PBXProj.objects.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 buildableReference = XCScheme.BuildableReference(
referencedContainer: "container:\(project.name).xcodeproj",
blueprintIdentifier: targetReference.reference,
buildableName: buildableName,
blueprintName: buildTarget.target
)
return XCScheme.BuildAction.Entry(buildableReference: buildableReference, buildFor: buildTarget.buildTypes)
}
let testTargetNames = scheme.test?.targets ?? []
let testBuildTargets = testTargetNames.map {
Scheme.BuildTarget(target: $0, buildTypes: BuildType.testOnly)
}
let testBuildTargetEntries = testBuildTargets.map(getBuildEntry)
let buildActionEntries: [XCScheme.BuildAction.Entry] = 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.
let environmentBuildable = action.settingsTarget.flatMap { settingsTarget in
return (buildActionEntries + testBuildTargetEntries)
.first { settingsTarget == $0.buildableReference.blueprintName }?
.buildableReference
}
return XCScheme.ExecutionAction(scriptText: action.script, title: action.name, environmentBuildable: environmentBuildable)
}
let target = project.getTarget(scheme.build.targets.first!.target)
let shouldExecuteOnLaunch = target?.type.isExecutable == true
let buildableReference = buildActionEntries.first!.buildableReference
let productRunable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference)
let buildAction = XCScheme.BuildAction(
buildActionEntries: buildActionEntries,
preActions: scheme.build.preActions.map(getExecutionAction),
postActions: scheme.build.postActions.map(getExecutionAction),
parallelizeBuild: scheme.build.parallelizeBuild,
buildImplicitDependencies: scheme.build.buildImplicitDependencies
)
let testables = testBuildTargetEntries.map {
XCScheme.TestableReference(skipped: false, buildableReference: $0.buildableReference)
}
let testCommandLineArgs = scheme.test.map { XCScheme.CommandLineArguments($0.commandLineArguments) }
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,
testables: testables,
preActions: scheme.test?.preActions.map(getExecutionAction) ?? [],
postActions: scheme.test?.postActions.map(getExecutionAction) ?? [],
shouldUseLaunchSchemeArgsEnv: scheme.test?.shouldUseLaunchSchemeArgsEnv ?? true,
codeCoverageEnabled: scheme.test?.gatherCoverageData ?? false,
commandlineArguments: testCommandLineArgs,
environmentVariables: testVariables
)
let launchAction = XCScheme.LaunchAction(
buildableProductRunnable: shouldExecuteOnLaunch ? productRunable : nil,
buildConfiguration: scheme.run?.config ?? defaultDebugConfig.name,
preActions: scheme.run?.preActions.map(getExecutionAction) ?? [],
postActions: scheme.run?.postActions.map(getExecutionAction) ?? [],
macroExpansion: shouldExecuteOnLaunch ? nil : buildableReference,
commandlineArguments: launchCommandLineArgs,
environmentVariables: launchVariables
)
let profileAction = XCScheme.ProfileAction(
buildableProductRunnable: productRunable,
buildConfiguration: scheme.profile?.config ?? defaultReleaseConfig.name,
preActions: scheme.profile?.preActions.map(getExecutionAction) ?? [],
postActions: scheme.profile?.postActions.map(getExecutionAction) ?? [],
shouldUseLaunchSchemeArgsEnv: scheme.profile?.shouldUseLaunchSchemeArgsEnv ?? true,
commandlineArguments: profileCommandLineArgs,
environmentVariables: profileVariables
)
let analyzeAction = XCScheme.AnalyzeAction(buildConfiguration: scheme.analyze?.config ?? defaultDebugConfig.name)
let archiveAction = XCScheme.ArchiveAction(
buildConfiguration: scheme.archive?.config ?? defaultReleaseConfig.name,
revealArchiveInOrganizer: scheme.archive?.revealArchiveInOrganizer ?? true,
customArchiveName: scheme.archive?.customArchiveName,
preActions: scheme.archive?.preActions.map(getExecutionAction) ?? [],
postActions: scheme.archive?.postActions.map(getExecutionAction) ?? []
)
return XCScheme(
name: scheme.name,
lastUpgradeVersion: project.xcodeVersion,
version: project.schemeVersion,
buildAction: buildAction,
testAction: testAction,
launchAction: launchAction,
profileAction: profileAction,
analyzeAction: analyzeAction,
archiveAction: archiveAction
)
}
func generateSharedData(pbxProject: PBXProj) throws -> XCSharedData {
var xcschemes: [XCScheme] = []
for scheme in project.schemes {
let xcscheme = try generateScheme(scheme, pbxProject: pbxProject)
xcschemes.append(xcscheme)
}
for target in project.targets {
if let targetScheme = target.scheme {
if targetScheme.configVariants.isEmpty {
let schemeName = target.name
let debugConfig = project.configs.first { $0.type == .debug }!
let releaseConfig = project.configs.first { $0.type == .release }!
let scheme = Scheme(
name: schemeName,
target: target,
targetScheme: targetScheme,
debugConfig: debugConfig.name,
releaseConfig: releaseConfig.name
)
let xcscheme = try generateScheme(scheme, pbxProject: pbxProject)
xcschemes.append(xcscheme)
} else {
for configVariant in targetScheme.configVariants {
let schemeName = "\(target.name) \(configVariant)"
let debugConfig = project.configs
.first { $0.type == .debug && $0.name.contains(configVariant) }!
let releaseConfig = project.configs
.first { $0.type == .release && $0.name.contains(configVariant) }!
let scheme = Scheme(
name: schemeName,
target: target,
targetScheme: targetScheme,
debugConfig: debugConfig.name,
releaseConfig: releaseConfig.name
)
let xcscheme = try generateScheme(scheme, pbxProject: pbxProject)
xcschemes.append(xcscheme)
}
}
}
}
return XCSharedData(schemes: xcschemes)
}
}
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)]),
run: .init(
config: debugConfig,
commandLineArguments: targetScheme.commandLineArguments,
preActions: targetScheme.preActions,
postActions: targetScheme.postActions,
environmentVariables: targetScheme.environmentVariables
),
test: .init(
config: debugConfig,
gatherCoverageData: targetScheme.gatherCoverageData,
commandLineArguments: targetScheme.commandLineArguments,
targets: targetScheme.testTargets,
preActions: targetScheme.preActions,
postActions: targetScheme.postActions,
environmentVariables: targetScheme.environmentVariables
),
profile: .init(
config: releaseConfig,
commandLineArguments: targetScheme.commandLineArguments,
preActions: targetScheme.preActions,
postActions: targetScheme.postActions,
environmentVariables: targetScheme.environmentVariables
),
analyze: .init(
config: debugConfig
),
archive: .init(
config: releaseConfig,
preActions: targetScheme.preActions,
postActions: targetScheme.postActions
)
)
}
}
+242
View File
@@ -0,0 +1,242 @@
import Foundation
import ProjectSpec
import xcodeproj
public class SchemeGenerator {
let project: Project
let pbxProj: PBXProj
var defaultDebugConfig: Config {
return project.configs.first { $0.type == .debug }!
}
var defaultReleaseConfig: Config {
return project.configs.first { $0.type == .release }!
}
public init(project: Project, pbxProj: PBXProj) {
self.project = project
self.pbxProj = pbxProj
}
public func generateSchemes() throws -> [XCScheme] {
var xcschemes: [XCScheme] = []
for scheme in project.schemes {
let xcscheme = try generateScheme(scheme)
xcschemes.append(xcscheme)
}
for target in project.targets {
if let targetScheme = target.scheme {
if targetScheme.configVariants.isEmpty {
let schemeName = target.name
let debugConfig = project.configs.first { $0.type == .debug }!
let releaseConfig = project.configs.first { $0.type == .release }!
let scheme = Scheme(
name: schemeName,
target: target,
targetScheme: targetScheme,
debugConfig: debugConfig.name,
releaseConfig: releaseConfig.name
)
let xcscheme = try generateScheme(scheme)
xcschemes.append(xcscheme)
} else {
for configVariant in targetScheme.configVariants {
let schemeName = "\(target.name) \(configVariant)"
let debugConfig = project.configs
.first { $0.type == .debug && $0.name.contains(configVariant) }!
let releaseConfig = project.configs
.first { $0.type == .release && $0.name.contains(configVariant) }!
let scheme = Scheme(
name: schemeName,
target: target,
targetScheme: targetScheme,
debugConfig: debugConfig.name,
releaseConfig: releaseConfig.name
)
let xcscheme = try generateScheme(scheme)
xcschemes.append(xcscheme)
}
}
}
}
return xcschemes
}
public func generateScheme(_ scheme: Scheme) throws -> XCScheme {
func getBuildEntry(_ buildTarget: Scheme.BuildTarget) -> XCScheme.BuildAction.Entry {
guard let pbxTarget = pbxProj.targets(named: buildTarget.target).first else {
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 buildableReference = XCScheme.BuildableReference(
referencedContainer: "container:\(project.name).xcodeproj",
blueprint: pbxTarget,
buildableName: buildableName,
blueprintName: buildTarget.target
)
return XCScheme.BuildAction.Entry(buildableReference: buildableReference, buildFor: buildTarget.buildTypes)
}
let testTargets = scheme.test?.targets ?? []
let testBuildTargets = testTargets.map {
Scheme.BuildTarget(target: $0.name, buildTypes: BuildType.testOnly)
}
let testBuildTargetEntries = testBuildTargets.map(getBuildEntry)
let buildActionEntries: [XCScheme.BuildAction.Entry] = 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.
let environmentBuildable = action.settingsTarget.flatMap { settingsTarget in
return (buildActionEntries + testBuildTargetEntries)
.first { settingsTarget == $0.buildableReference.blueprintName }?
.buildableReference
}
return XCScheme.ExecutionAction(scriptText: action.script, title: action.name, environmentBuildable: environmentBuildable)
}
let target = project.getTarget(scheme.build.targets.first!.target)
let shouldExecuteOnLaunch = target?.type.isExecutable == true
let buildableReference = buildActionEntries.first!.buildableReference
let productRunable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference)
let buildAction = XCScheme.BuildAction(
buildActionEntries: buildActionEntries,
preActions: scheme.build.preActions.map(getExecutionAction),
postActions: scheme.build.postActions.map(getExecutionAction),
parallelizeBuild: scheme.build.parallelizeBuild,
buildImplicitDependencies: scheme.build.buildImplicitDependencies
)
let testables = zip(testTargets, testBuildTargetEntries).map { testTarget, testBuilEntries in
XCScheme.TestableReference(
skipped: false,
parallelizable: testTarget.parallelizable,
randomExecutionOrdering: testTarget.randomExecutionOrder,
buildableReference: testBuilEntries.buildableReference
)
}
let testCommandLineArgs = scheme.test.map { XCScheme.CommandLineArguments($0.commandLineArguments) }
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,
testables: testables,
preActions: scheme.test?.preActions.map(getExecutionAction) ?? [],
postActions: scheme.test?.postActions.map(getExecutionAction) ?? [],
shouldUseLaunchSchemeArgsEnv: scheme.test?.shouldUseLaunchSchemeArgsEnv ?? true,
codeCoverageEnabled: scheme.test?.gatherCoverageData ?? false,
commandlineArguments: testCommandLineArgs,
environmentVariables: testVariables
)
let launchAction = XCScheme.LaunchAction(
buildableProductRunnable: shouldExecuteOnLaunch ? productRunable : nil,
buildConfiguration: scheme.run?.config ?? defaultDebugConfig.name,
preActions: scheme.run?.preActions.map(getExecutionAction) ?? [],
postActions: scheme.run?.postActions.map(getExecutionAction) ?? [],
macroExpansion: shouldExecuteOnLaunch ? nil : buildableReference,
commandlineArguments: launchCommandLineArgs,
environmentVariables: launchVariables
)
let profileAction = XCScheme.ProfileAction(
buildableProductRunnable: productRunable,
buildConfiguration: scheme.profile?.config ?? defaultReleaseConfig.name,
preActions: scheme.profile?.preActions.map(getExecutionAction) ?? [],
postActions: scheme.profile?.postActions.map(getExecutionAction) ?? [],
shouldUseLaunchSchemeArgsEnv: scheme.profile?.shouldUseLaunchSchemeArgsEnv ?? true,
commandlineArguments: profileCommandLineArgs,
environmentVariables: profileVariables
)
let analyzeAction = XCScheme.AnalyzeAction(buildConfiguration: scheme.analyze?.config ?? defaultDebugConfig.name)
let archiveAction = XCScheme.ArchiveAction(
buildConfiguration: scheme.archive?.config ?? defaultReleaseConfig.name,
revealArchiveInOrganizer: scheme.archive?.revealArchiveInOrganizer ?? true,
customArchiveName: scheme.archive?.customArchiveName,
preActions: scheme.archive?.preActions.map(getExecutionAction) ?? [],
postActions: scheme.archive?.postActions.map(getExecutionAction) ?? []
)
return XCScheme(
name: scheme.name,
lastUpgradeVersion: project.xcodeVersion,
version: project.schemeVersion,
buildAction: buildAction,
testAction: testAction,
launchAction: launchAction,
profileAction: profileAction,
analyzeAction: analyzeAction,
archiveAction: archiveAction
)
}
}
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)]),
run: .init(
config: debugConfig,
commandLineArguments: targetScheme.commandLineArguments,
preActions: targetScheme.preActions,
postActions: targetScheme.postActions,
environmentVariables: targetScheme.environmentVariables
),
test: .init(
config: debugConfig,
gatherCoverageData: targetScheme.gatherCoverageData,
commandLineArguments: targetScheme.commandLineArguments,
targets: targetScheme.testTargets,
preActions: targetScheme.preActions,
postActions: targetScheme.postActions,
environmentVariables: targetScheme.environmentVariables
),
profile: .init(
config: releaseConfig,
commandLineArguments: targetScheme.commandLineArguments,
preActions: targetScheme.preActions,
postActions: targetScheme.postActions,
environmentVariables: targetScheme.environmentVariables
),
analyze: .init(
config: debugConfig
),
archive: .init(
config: releaseConfig,
preActions: targetScheme.preActions,
postActions: targetScheme.postActions
)
)
}
}
+51 -30
View File
@@ -2,7 +2,7 @@ import Foundation
import JSONUtilities
import PathKit
import ProjectSpec
import xcproj
import xcodeproj
import Yams
extension Project {
@@ -10,6 +10,15 @@ extension Project {
public func getProjectBuildSettings(config: Config) -> BuildSettings {
var buildSettings: BuildSettings = [:]
// set project SDKROOT is a single platform
if targets.count > 0 {
let platforms = Dictionary(grouping: targets) { $0.platform }
if platforms.count == 1 {
let platform = platforms.first!.key
buildSettings["SDKROOT"] = platform.sdkRoot
}
}
if let type = config.type, options.settingPresets.applyProject {
buildSettings += SettingsPresetFile.base.getBuildSettings()
buildSettings += SettingsPresetFile.config(type).getBuildSettings()
@@ -81,31 +90,27 @@ extension Project {
}
// combines all levels of a target's settings: target, target config, project, project config
public func getCombinedBuildSettings(basePath: Path, target: ProjectTarget, config: Config, includeProject: Bool = true) -> BuildSettings {
var buildSettings: BuildSettings = [:]
if includeProject {
if let configFilePath = configFiles[config.name] {
buildSettings += loadConfigFileBuildSettings(path: configFilePath)
}
buildSettings += getProjectBuildSettings(config: config)
public func getCombinedBuildSetting(_ setting: String, target: ProjectTarget, config: Config) -> Any? {
if let target = target as? Target,
let value = getTargetBuildSettings(target: target, config: config)[setting] {
return value
}
if let configFilePath = target.configFiles[config.name] {
buildSettings += loadConfigFileBuildSettings(path: configFilePath)
if let configFilePath = target.configFiles[config.name],
let value = loadConfigFileBuildSettings(path: configFilePath)?[setting] {
return value
}
if let target = target as? Target {
buildSettings += getTargetBuildSettings(target: target, config: config)
if let value = getProjectBuildSettings(config: config)[setting] {
return value
}
return buildSettings
if let configFilePath = configFiles[config.name],
let value = loadConfigFileBuildSettings(path: configFilePath)?[setting] {
return value
}
return nil
}
public func targetHasBuildSetting(_ setting: String, basePath: Path, target: Target, config: Config, includeProject: Bool = true) -> Bool {
let buildSettings = getCombinedBuildSettings(
basePath: basePath,
target: target,
config: config,
includeProject: includeProject
)
return buildSettings[setting] != nil
public func targetHasBuildSetting(_ setting: String, target: Target, config: Config) -> Bool {
return getCombinedBuildSetting(setting, target: target, config: config) != nil
}
/// Removes values from build settings if they are defined in an xcconfig file
@@ -126,28 +131,43 @@ extension Project {
/// Returns cached build settings from a config file
private func loadConfigFileBuildSettings(path: String) -> BuildSettings? {
let configFilePath = basePath + path
if let settings = configFileSettings[configFilePath.string] {
return settings
if let cached = configFileSettings[configFilePath.string] {
return cached.value
} else {
guard let configFile = try? XCConfig(path: configFilePath) else { return nil }
guard let configFile = try? XCConfig(path: configFilePath) else {
configFileSettings[configFilePath.string] = .nothing
return nil
}
let settings = configFile.flattenedBuildSettings()
configFileSettings[configFilePath.string] = settings
configFileSettings[configFilePath.string] = .cached(settings)
return settings
}
}
}
private enum Cached<T> {
case cached(T)
case nothing
var value: T? {
switch self {
case let .cached(value): return value
case .nothing: return nil
}
}
}
// cached flattened xcconfig file settings
private var configFileSettings: [String: BuildSettings] = [:]
private var configFileSettings: [String: Cached<BuildSettings>] = [:]
// cached setting preset settings
private var settingPresetSettings: [String: BuildSettings] = [:]
private var settingPresetSettings: [String: Cached<BuildSettings>] = [:]
extension SettingsPresetFile {
public func getBuildSettings() -> BuildSettings? {
if let group = settingPresetSettings[path] {
return group
if let cached = settingPresetSettings[path] {
return cached.value
}
let bundlePath = Path(Bundle.main.bundlePath)
let relativePath = Path("SettingPresets/\(path).yml")
@@ -171,6 +191,7 @@ extension SettingsPresetFile {
case .product, .productPlatform:
break
}
settingPresetSettings[path] = .nothing
return nil
}
@@ -178,7 +199,7 @@ extension SettingsPresetFile {
print("Error parsing \"\(name)\" settings")
return nil
}
settingPresetSettings[path] = buildSettings
settingPresetSettings[path] = .cached(buildSettings)
return buildSettings
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
import Foundation
import ProjectSpec
import xcproj
import xcodeproj
public enum SettingsPresetFile {
case config(ConfigType)
+60 -66
View File
@@ -1,45 +1,41 @@
import Foundation
import PathKit
import ProjectSpec
import xcproj
import xcodeproj
struct SourceFile {
let path: Path
let fileReference: String
let fileReference: PBXFileElement
let buildFile: PBXBuildFile
let buildPhase: TargetSource.BuildPhase?
}
class SourceGenerator {
var rootGroups: Set<String> = []
private var fileReferencesByPath: [String: String] = [:]
private var groupsByPath: [Path: ObjectReference<PBXGroup>] = [:]
private var variantGroupsByPath: [Path: ObjectReference<PBXVariantGroup>] = [:]
var rootGroups: Set<PBXFileElement> = []
private var fileReferencesByPath: [String: PBXFileElement] = [:]
private var groupsByPath: [Path: PBXGroup] = [:]
private var variantGroupsByPath: [Path: PBXVariantGroup] = [:]
private let project: Project
var addObjectClosure: (String, PBXObject) -> String
let pbxProj: PBXProj
var targetSourceExcludePaths: Set<Path> = []
var defaultExcludedFiles = [
".DS_Store",
]
var targetName: String = ""
private(set) var knownRegions: Set<String> = []
init(project: Project, addObjectClosure: @escaping (String, PBXObject) -> String) {
init(project: Project, pbxProj: PBXProj) {
self.project = project
self.addObjectClosure = addObjectClosure
self.pbxProj = pbxProj
}
func addObject(id: String, _ object: PBXObject) -> String {
return addObjectClosure(id, object)
}
func createObject<T: PBXObject>(id: String, _ object: T) -> ObjectReference<T> {
let reference = addObject(id: id, object)
return ObjectReference(reference: reference, object: object)
func addObject<T: PBXObject>(_ object: T, context: String? = nil) -> T {
pbxProj.add(object: object)
object.context = context
return object
}
func getAllSourceFiles(targetType: PBXProductType, sources: [TargetSource]) throws -> [SourceFile] {
@@ -91,7 +87,7 @@ class SourceGenerator {
settings["COMPILER_FLAGS"] = targetSource.compilerFlags.joined(separator: " ")
}
let buildFile = PBXBuildFile(fileRef: fileReference, settings: settings.isEmpty ? nil : settings)
let buildFile = PBXBuildFile(file: fileReference, settings: settings.isEmpty ? nil : settings)
return SourceFile(
path: path,
fileReference: fileReference,
@@ -100,7 +96,7 @@ class SourceGenerator {
)
}
func getContainedFileReference(path: Path) -> String {
func getContainedFileReference(path: Path) -> PBXFileElement {
let createIntermediateGroups = project.options.createIntermediateGroups
let parentPath = path.parent()
@@ -113,12 +109,12 @@ class SourceGenerator {
)
if createIntermediateGroups {
createIntermediaGroups(for: parentGroup.reference, at: parentPath)
createIntermediaGroups(for: parentGroup, at: parentPath)
}
return fileReference
}
func getFileReference(path: Path, inPath: Path, name: String? = nil, sourceTree: PBXSourceTree = .group, lastKnownFileType: String? = nil) -> String {
func getFileReference(path: Path, inPath: Path, name: String? = nil, sourceTree: PBXSourceTree = .group, lastKnownFileType: String? = nil) -> PBXFileElement {
let fileReferenceKey = path.string.lowercased()
if let fileReference = fileReferencesByPath[fileReferenceKey] {
return fileReference
@@ -128,7 +124,7 @@ class SourceGenerator {
if fileReferencePath.string == fileReferenceName {
fileReferenceName = nil
}
let lastKnownFileType = lastKnownFileType ?? PBXFileReference.fileType(path: path)
let lastKnownFileType = lastKnownFileType ?? Xcode.fileType(path: path)
if path.extension == "xcdatamodeld" {
let versionedModels = (try? path.children()) ?? []
@@ -138,10 +134,9 @@ class SourceGenerator {
.filter { $0.extension == "xcdatamodel" }
.sorted { $0.string.localizedStandardCompare($1.string) == .orderedAscending }
let modelFileReference =
let modelFileReferences =
sortedPaths.map { path in
createObject(
id: path.byRemovingBase(path: project.basePath).string,
addObject(
PBXFileReference(
sourceTree: .group,
lastKnownFileType: "wrapper.xcdatamodel",
@@ -152,23 +147,22 @@ class SourceGenerator {
// If no current version path is found we fall back to alphabetical
// order by taking the last item in the sortedPaths array
let currentVersionPath = findCurrentCoreDataModelVersionPath(using: versionedModels) ?? sortedPaths.last
let currentVersion: ObjectReference<PBXFileReference>? = {
let currentVersion: PBXFileReference? = {
guard let indexOf = sortedPaths.index(where: { $0 == currentVersionPath }) else { return nil }
return modelFileReference[indexOf]
return modelFileReferences[indexOf]
}()
let versionGroup = addObject(id: fileReferencePath.string, XCVersionGroup(
currentVersion: currentVersion?.reference,
let versionGroup = addObject(XCVersionGroup(
currentVersion: currentVersion,
path: fileReferencePath.string,
sourceTree: sourceTree,
versionGroupType: "wrapper.xcdatamodel",
children: modelFileReference.map { $0.reference }
children: modelFileReferences
))
fileReferencesByPath[fileReferenceKey] = versionGroup
return versionGroup
} else {
// For all extensions other than `xcdatamodeld`
let fileReference = createObject(
id: path.byRemovingBase(path: project.basePath).string,
let fileReference = addObject(
PBXFileReference(
sourceTree: sourceTree,
name: fileReferenceName,
@@ -176,8 +170,8 @@ class SourceGenerator {
path: fileReferencePath.string
)
)
fileReferencesByPath[fileReferenceKey] = fileReference.reference
return fileReference.reference
fileReferencesByPath[fileReferenceKey] = fileReference
return fileReference
}
}
}
@@ -209,12 +203,16 @@ class SourceGenerator {
/// Create a group or return an existing one at the path.
/// Any merged children are added to a new group or merged into an existing one.
private func getGroup(path: Path, name: String? = nil, mergingChildren children: [String], createIntermediateGroups: Bool, isBaseGroup: Bool) -> ObjectReference<PBXGroup> {
let groupReference: ObjectReference<PBXGroup>
private func getGroup(path: Path, name: String? = nil, mergingChildren children: [PBXFileElement], createIntermediateGroups: Bool, isBaseGroup: Bool) -> PBXGroup {
let groupReference: PBXGroup
if let cachedGroup = groupsByPath[path] {
// only add the children that aren't already in the cachedGroup
cachedGroup.object.children = Array(Set(cachedGroup.object.children + children))
for child in children {
// only add the children that aren't already in the cachedGroup
if !cachedGroup.children.contains(child) {
cachedGroup.children.append(child)
}
}
groupReference = cachedGroup
} else {
@@ -237,28 +235,27 @@ class SourceGenerator {
name: groupName != groupPath ? groupName : nil,
path: groupPath
)
groupReference = createObject(id: path.byRemovingBase(path: project.basePath).string, group)
groupReference = addObject(group)
groupsByPath[path] = groupReference
if isTopLevelGroup {
rootGroups.insert(groupReference.reference)
rootGroups.insert(groupReference)
}
}
return groupReference
}
/// Creates a variant group or returns an existing one at the path
private func getVariantGroup(path: Path, inPath: Path) -> ObjectReference<PBXVariantGroup> {
let variantGroup: ObjectReference<PBXVariantGroup>
private func getVariantGroup(path: Path, inPath: Path) -> PBXVariantGroup {
let variantGroup: PBXVariantGroup
if let cachedGroup = variantGroupsByPath[path] {
variantGroup = cachedGroup
} else {
let group = PBXVariantGroup(
children: [],
sourceTree: .group,
name: path.lastComponent
)
variantGroup = createObject(id: path.byRemovingBase(path: project.basePath).string, group)
variantGroup = addObject(group)
variantGroupsByPath[path] = variantGroup
}
return variantGroup
@@ -307,27 +304,24 @@ class SourceGenerator {
/// creates all the source files and groups they belong to for a given targetSource
private func getGroupSources(targetType: PBXProductType, targetSource: TargetSource, path: Path, isBaseGroup: Bool)
throws -> (sourceFiles: [SourceFile], groups: [ObjectReference<PBXGroup>]) {
throws -> (sourceFiles: [SourceFile], groups: [PBXGroup]) {
let children = try getSourceChildren(targetSource: targetSource, dirPath: path)
let directories = children
.filter { $0.isDirectory && $0.extension == nil && $0.extension != "lproj" }
.sorted { $0.lastComponent < $1.lastComponent }
let filePaths = children
.filter { $0.isFile || $0.extension != nil && $0.extension != "lproj" }
.sorted { $0.lastComponent < $1.lastComponent }
let localisedDirectories = children
.filter { $0.extension == "lproj" }
.sorted { $0.lastComponent < $1.lastComponent }
var groupChildren: [String] = filePaths.map { getFileReference(path: $0, inPath: path) }
var groupChildren: [PBXFileElement] = filePaths.map { getFileReference(path: $0, inPath: path) }
var allSourceFiles: [SourceFile] = filePaths.map {
generateSourceFile(targetType: targetType, targetSource: targetSource, path: $0)
}
var groups: [ObjectReference<PBXGroup>] = []
var groups: [PBXGroup] = []
for path in directories {
let subGroups = try getGroupSources(targetType: targetType, targetSource: targetSource, path: path, isBaseGroup: false)
@@ -338,11 +332,11 @@ class SourceGenerator {
allSourceFiles += subGroups.sourceFiles
guard let first = subGroups.groups.first else {
guard let firstGroup = subGroups.groups.first else {
continue
}
groupChildren.append(first.reference)
groupChildren.append(firstGroup)
groups += subGroups.groups
}
@@ -365,13 +359,13 @@ class SourceGenerator {
.filter(isIncludedPath)
.sorted() {
let variantGroup = getVariantGroup(path: filePath, inPath: path)
groupChildren.append(variantGroup.reference)
baseLocalisationVariantGroups.append(variantGroup.object)
groupChildren.append(variantGroup)
baseLocalisationVariantGroups.append(variantGroup)
let sourceFile = SourceFile(
path: filePath,
fileReference: variantGroup.reference,
buildFile: PBXBuildFile(fileRef: variantGroup.reference),
fileReference: variantGroup,
buildFile: PBXBuildFile(file: variantGroup),
buildPhase: .resources
)
allSourceFiles.append(sourceFile)
@@ -409,7 +403,7 @@ class SourceGenerator {
let sourceFile = SourceFile(
path: filePath,
fileReference: fileReference,
buildFile: PBXBuildFile(fileRef: fileReference),
buildFile: PBXBuildFile(file: fileReference),
buildPhase: .resources
)
allSourceFiles.append(sourceFile)
@@ -425,7 +419,7 @@ class SourceGenerator {
isBaseGroup: isBaseGroup
)
if project.options.createIntermediateGroups {
createIntermediaGroups(for: group.reference, at: path)
createIntermediaGroups(for: group, at: path)
}
groups.insert(group, at: 0)
@@ -442,7 +436,7 @@ class SourceGenerator {
let createIntermediateGroups = project.options.createIntermediateGroups
var sourceFiles: [SourceFile] = []
let sourceReference: String
let sourceReference: PBXFileElement
var sourcePath = path
switch type {
case .folder:
@@ -483,7 +477,7 @@ class SourceGenerator {
} else {
let parentGroup = getGroup(path: parentPath, mergingChildren: [fileReference], createIntermediateGroups: createIntermediateGroups, isBaseGroup: true)
sourcePath = parentPath
sourceReference = parentGroup.reference
sourceReference = parentGroup
}
sourceFiles.append(sourceFile)
@@ -491,11 +485,11 @@ class SourceGenerator {
let (groupSourceFiles, groups) = try getGroupSources(targetType: targetType, targetSource: targetSource, path: path, isBaseGroup: true)
let group = groups.first!
if let name = targetSource.name {
group.object.name = name
group.name = name
}
sourceFiles += groupSourceFiles
sourceReference = group.reference
sourceReference = group
}
if createIntermediateGroups {
@@ -506,7 +500,7 @@ class SourceGenerator {
}
// Add groups for all parents recursively
private func createIntermediaGroups(for groupReference: String, at path: Path) {
private func createIntermediaGroups(for fileElement: PBXFileElement, at path: Path) {
let parentPath = path.parent()
guard parentPath != project.basePath && path.string.contains(project.basePath.string) else {
@@ -515,10 +509,10 @@ class SourceGenerator {
}
let hasParentGroup = groupsByPath[parentPath] != nil
let parentGroup = getGroup(path: parentPath, mergingChildren: [groupReference], createIntermediateGroups: true, isBaseGroup: false)
let parentGroup = getGroup(path: parentPath, mergingChildren: [fileElement], createIntermediateGroups: true, isBaseGroup: false)
if !hasParentGroup {
createIntermediaGroups(for: parentGroup.reference, at: parentPath)
createIntermediaGroups(for: parentGroup, at: parentPath)
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ import ProjectSpec
extension Project {
var xcodeVersion: String {
return XCodeVersion.parse(options.xcodeVersion ?? "9.3")
return XCodeVersion.parse(options.xcodeVersion ?? "10.0")
}
var schemeVersion: String {
+23 -8
View File
@@ -1,5 +1,6 @@
import Foundation
import xcproj
import PathKit
import xcodeproj
extension PBXFileElement {
@@ -11,8 +12,8 @@ extension PBXFileElement {
extension PBXProj {
public func printGroups() -> String {
guard let project = objects.projects.first?.value,
let mainGroup = objects.groups.getReference(project.mainGroup) else {
guard let project = projects.first,
let mainGroup = project.mainGroup else {
return ""
}
return printGroup(group: mainGroup)
@@ -20,17 +21,31 @@ extension PBXProj {
public func printGroup(group: PBXGroup) -> String {
var string = group.nameOrPath
for reference in group.children {
if let group = objects.groups.getReference(reference) {
for child in group.children {
if let group = child as? PBXGroup {
string += "\n 📁 " + printGroup(group: group).replacingOccurrences(of: "\n ", with: "\n ")
} else if let fileReference = objects.fileReferences.getReference(reference) {
} else if let fileReference = child as? PBXFileReference {
string += "\n 📄 " + fileReference.nameOrPath
} else if let variantGroup = objects.variantGroups.getReference(reference) {
} else if let variantGroup = child as? PBXVariantGroup {
string += "\n 🌎 " + variantGroup.nameOrPath
} else if let versionGroup = objects.versionGroups.getReference(reference) {
} else if let versionGroup = child as? XCVersionGroup {
string += "\n 🔢 " + versionGroup.nameOrPath
}
}
return string
}
}
extension Dictionary {
public var valueArray: Array<Value> {
return Array(values)
}
}
extension Xcode {
public static func fileType(path: Path) -> String? {
return path.extension.flatMap { Xcode.filetype(extension: $0) }
}
}