Support external target dependencies via subprojects (#701)

* Allow external target dependencies via subprojects

* Update CHANGELOG

* Update ProjectSpec

* Fix test

* Use existing fixture for test

* Sort subprojects by name

* Throw subproject generation error instead of crashing

* Cleanup target dependency generation

* Update test fixture project

* Combine extentions in Linkage

* Update XcodeProj to 7.7.0

* Update CHANGELOG.md

* Update ProjectSpec.md

Co-authored-by: Yonas Kolb <yonaskolb@users.noreply.github.com>
This commit is contained in:
Evan Coleman 2020-02-01 17:44:56 -05:00 committed by GitHub
parent 6b7b7e6134
commit 6bfd620549
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 549 additions and 70 deletions

View File

@ -2,6 +2,9 @@
## Next Version
#### Added
- Support External Target References via subprojects. [#701](https://github.com/yonaskolb/XcodeGen/pull/701) @evandcoleman
#### Fixed
- Fixed compilation as library by locking down XcodeProj version [#767](https://github.com/yonaskolb/XcodeGen/pull/767) @yonaskolb

View File

@ -382,7 +382,7 @@ targets:
A dependency can be one of a 6 types:
- `target: name` - links to another target
- `target: name` - links to another target. If you are using project references you can specify a target within another project by using `ProjectName/TargetName` for the name
- `framework: path` - links to a framework
- `carthage: name` - helper for linking to a Carthage framework
- `sdk: name` - links to a dependency with the SDK. This can either be a relative path within the sdk root or a single filename that references a framework (.framework) or lib (.tbd)
@ -423,10 +423,14 @@ Carthage officially supports static frameworks. In this case, frameworks are exp
You can specify `linkType` to `static` to integrate static ones.
```yaml
projectReferences:
FooLib:
path: path/to/FooLib.xcodeproj
targets:
MyTarget:
dependencies:
- target: MyFramework
- target: FooLib/FooTarget
- framework: path/to/framework.framework
- carthage: Result
findFrameworks: false

View File

@ -1,4 +1,5 @@
import Foundation
import XcodeProj
public enum Linkage {
case dynamic
@ -6,10 +7,10 @@ public enum Linkage {
case none
}
extension Target {
extension PBXProductType {
public var defaultLinkage: Linkage {
switch type {
switch self {
case .none,
.appExtension,
.application,

View File

@ -147,8 +147,17 @@ extension Project {
for dependency in target.dependencies {
switch dependency.type {
case .target:
if getProjectTarget(dependency.reference) == nil {
errors.append(.invalidTargetDependency(target: target.name, dependency: dependency.reference))
let dependencyTargetReference = try TargetReference(dependency.reference)
switch dependencyTargetReference.location {
case .local:
if getProjectTarget(dependency.reference) == nil {
errors.append(.invalidTargetDependency(target: target.name, dependency: dependency.reference))
}
case .project(let dependencyProjectName):
if getProjectReference(dependencyProjectName) == nil {
errors.append(.invalidTargetDependency(target: target.name, dependency: dependency.reference))
}
}
case .sdk:
let path = Path(dependency.reference)

View File

@ -26,6 +26,8 @@ public class PBXProjGenerator {
var generated = false
private var projects: [ProjectReference: PBXProj] = [:]
public init(project: Project, projectDirectory: Path? = nil) {
self.project = project
carthageResolver = CarthageDependencyResolver(project: project)
@ -168,9 +170,6 @@ public class PBXProjGenerator {
addObject(packageReference)
}
try project.targets.forEach(generateTarget)
try project.aggregateTargets.forEach(generateAggregateTarget)
let productGroup = addObject(
PBXGroup(
children: targetFileReferences.valueArray,
@ -180,6 +179,49 @@ public class PBXProjGenerator {
)
derivedGroups.append(productGroup)
let sortedProjectReferences = project.projectReferences.sorted { $0.name < $1.name }
let subprojectFileReferences: [PBXFileReference] = sortedProjectReferences.map { projectReference in
let projectPath = Path(projectReference.path)
return addObject(
PBXFileReference(
sourceTree: .group,
name: projectReference.name,
lastKnownFileType: Xcode.fileType(path: projectPath),
path: projectPath.normalize().string
)
)
}
if subprojectFileReferences.count > 0 {
let subprojectsGroups = addObject(
PBXGroup(
children: subprojectFileReferences,
sourceTree: .group,
name: "Projects"
)
)
derivedGroups.append(subprojectsGroups)
let subprojects: [[String: PBXFileElement]] = subprojectFileReferences.map { projectReference in
let group = addObject(
PBXGroup(
children: [],
sourceTree: .group,
name: "Products"
)
)
return [
"ProductGroup": group,
"ProjectRef": projectReference
]
}
pbxProject.projects = subprojects
}
try project.targets.forEach(generateTarget)
try project.aggregateTargets.forEach(generateAggregateTarget)
if !carthageFrameworksByPlatform.isEmpty {
var platforms: [PBXGroup] = []
for (platform, files) in carthageFrameworksByPlatform {
@ -316,6 +358,90 @@ public class PBXProjGenerator {
return targetDependency
}
func generateExternalTargetDependency(from: String, to target: String, in project: String, platform: Platform) throws -> (PBXTargetDependency, Target, PBXReferenceProxy) {
guard let projectReference = self.project.getProjectReference(project) else {
fatalError("project not found")
}
let pbxProj = try getPBXProj(from: projectReference)
guard let targetObject = pbxProj.targets(named: target).first else {
fatalError("target not found")
}
let projectFileReferenceIndex = self.pbxProj.rootObject!
.projects
.map { $0["ProjectRef"] as? PBXFileReference }
.firstIndex { $0?.path == Path(projectReference.path).normalize().string }
guard let index = projectFileReferenceIndex,
let projectFileReference = self.pbxProj.rootObject?.projects[index]["ProjectRef"] as? PBXFileReference,
let productsGroup = self.pbxProj.rootObject?.projects[index]["ProductGroup"] as? PBXGroup else {
fatalError("Missing subproject file reference")
}
let targetProxy = addObject(
PBXContainerItemProxy(
containerPortal: .fileReference(projectFileReference),
remoteGlobalID: .object(targetObject),
proxyType: .nativeTarget,
remoteInfo: target
)
)
let productProxy = addObject(
PBXContainerItemProxy(
containerPortal: .fileReference(projectFileReference),
remoteGlobalID: .object(targetObject.product!),
proxyType: .reference,
remoteInfo: target
)
)
let productReferenceProxy = addObject(
PBXReferenceProxy(
fileType: Xcode.fileType(path: Path(targetObject.productNameWithExtension()!)),
path: targetObject.productNameWithExtension(),
remote: productProxy,
sourceTree: .buildProductsDir
)
)
productsGroup.children.append(productReferenceProxy)
let targetDependency = addObject(
PBXTargetDependency(
target: targetObject,
targetProxy: targetProxy
)
)
guard let productType = targetObject.productType,
let buildConfigurations = targetObject.buildConfigurationList?.buildConfigurations,
let defaultConfigurationName = targetObject.buildConfigurationList?.defaultConfigurationName,
let defaultConfiguration = buildConfigurations.first(where: { $0.name == defaultConfigurationName }) ?? buildConfigurations.first else {
fatalError("Missing target info")
}
let buildSettings = defaultConfiguration.buildSettings
let settings = Settings(buildSettings: buildSettings, configSettings: [:], groups: [])
let deploymentTargetString = buildSettings[platform.deploymentTargetSetting] as? String
let deploymentTarget = deploymentTargetString == nil ? nil : try Version(deploymentTargetString!)
let requiresObjCLinking = (buildSettings["OTHER_LDFLAGS"] as? String)?.contains("-ObjC") ?? (productType == .staticLibrary)
let dependencyTarget = Target(
name: targetObject.name,
type: productType,
platform: platform,
productName: targetObject.productName,
deploymentTarget: deploymentTarget,
settings: settings,
requiresObjCLinking: requiresObjCLinking
)
return (targetDependency, dependencyTarget, productReferenceProxy)
}
func generateBuildScript(targetName: String, buildScript: BuildScript) throws -> PBXShellScriptBuildPhase {
let shellScript: String
@ -439,6 +565,15 @@ public class PBXProjGenerator {
childGroups.forEach(sortGroups)
}
func getPBXProj(from reference: ProjectReference) throws -> PBXProj {
if let cachedProject = projects[reference] {
return cachedProject
}
let pbxproj = try XcodeProj(pathString: (project.basePath + Path(reference.path).normalize()).string).pbxproj
projects[reference] = pbxproj
return pbxproj
}
func generateTarget(_ target: Target) throws {
let carthageDependencies = carthageResolver.dependencies(for: target)
@ -487,60 +622,72 @@ public class PBXProjGenerator {
return !linkingAttributes.isEmpty ? ["ATTRIBUTES": linkingAttributes] : nil
}
func processTargetDependency(_ dependency: Dependency, dependencyTarget: Target, embedFileReference: PBXFileElement) {
let dependencyLinkage = dependencyTarget.type.defaultLinkage
let link = dependency.link ??
((dependencyLinkage == .dynamic && target.type != .staticLibrary) ||
(dependencyLinkage == .static && target.type.isExecutable))
if link {
let dependencyFile = embedFileReference
let buildFile = addObject(
PBXBuildFile(file: dependencyFile, settings: getDependencyFrameworkSettings(dependency: dependency))
)
targetFrameworkBuildFiles.append(buildFile)
if !anyDependencyRequiresObjCLinking
&& dependencyTarget.requiresObjCLinking ?? (dependencyTarget.type == .staticLibrary) {
anyDependencyRequiresObjCLinking = true
}
}
let embed = dependency.embed ?? (!dependencyTarget.type.isLibrary && (
target.type.isApp
|| (target.type.isTest && (dependencyTarget.type.isFramework || dependencyTarget.type == .bundle))
))
if embed {
let embedFile = addObject(
PBXBuildFile(
file: embedFileReference,
settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? !dependencyTarget.type.isExecutable)
)
)
if dependencyTarget.type.isExtension {
// embed app extension
extensions.append(embedFile)
} else if dependencyTarget.type.isFramework {
copyFrameworksReferences.append(embedFile)
} else if dependencyTarget.type.isApp && dependencyTarget.platform == .watchOS {
copyWatchReferences.append(embedFile)
} else if dependencyTarget.type == .xpcService {
copyFilesBuildPhasesFiles[.xpcServices, default: []].append(embedFile)
} else {
copyResourcesReferences.append(embedFile)
}
}
}
for dependency in targetDependencies {
let embed = dependency.embed ?? target.shouldEmbedDependencies
switch dependency.type {
case .target:
let dependencyTargetName = dependency.reference
let targetDependency = generateTargetDependency(from: target.name, to: dependencyTargetName)
dependencies.append(targetDependency)
let dependencyTargetReference = try TargetReference(dependency.reference)
guard let dependencyTarget = project.getTarget(dependencyTargetName) else { continue }
let dependecyLinkage = dependencyTarget.defaultLinkage
let link = dependency.link ??
((dependecyLinkage == .dynamic && target.type != .staticLibrary) ||
(dependecyLinkage == .static && target.type.isExecutable))
if link {
let dependencyFile = targetFileReferences[dependencyTarget.name]!
let buildFile = addObject(
PBXBuildFile(file: dependencyFile, settings: getDependencyFrameworkSettings(dependency: dependency))
)
targetFrameworkBuildFiles.append(buildFile)
if !anyDependencyRequiresObjCLinking
&& dependencyTarget.requiresObjCLinking ?? (dependencyTarget.type == .staticLibrary) {
anyDependencyRequiresObjCLinking = true
}
}
let embed = dependency.embed ?? (!dependencyTarget.type.isLibrary && (
target.type.isApp
|| (target.type.isTest && (dependencyTarget.type.isFramework || dependencyTarget.type == .bundle))
))
if embed {
let embedFile = addObject(
PBXBuildFile(
file: targetFileReferences[dependencyTarget.name]!,
settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? !dependencyTarget.type.isExecutable)
)
)
if dependencyTarget.type.isExtension {
// embed app extension
extensions.append(embedFile)
} else if dependencyTarget.type.isFramework {
copyFrameworksReferences.append(embedFile)
} else if dependencyTarget.type.isApp && dependencyTarget.platform == .watchOS {
copyWatchReferences.append(embedFile)
} else if dependencyTarget.type == .xpcService {
copyFilesBuildPhasesFiles[.xpcServices, default: []].append(embedFile)
} else {
copyResourcesReferences.append(embedFile)
}
switch dependencyTargetReference.location {
case .local:
let dependencyTargetName = dependency.reference
let targetDependency = generateTargetDependency(from: target.name, to: dependencyTargetName)
dependencies.append(targetDependency)
guard let dependencyTarget = project.getTarget(dependencyTargetName) else { continue }
processTargetDependency(dependency, dependencyTarget: dependencyTarget, embedFileReference: targetFileReferences[dependencyTarget.name]!)
case .project(let dependencyProjectName):
let dependencyTargetName = dependencyTargetReference.name
let (targetDependency, dependencyTarget, dependencyProductProxy) = try generateExternalTargetDependency(from: target.name, to: dependencyTargetName, in: dependencyProjectName, platform: target.platform)
dependencies.append(targetDependency)
processTargetDependency(dependency, dependencyTarget: dependencyTarget, embedFileReference: dependencyProductProxy)
}
case .framework:
@ -1086,15 +1233,24 @@ public class PBXProjGenerator {
dependencies[dependency.reference] = dependency
}
case .target:
if isTopLevel || dependency.embed == nil {
if let dependencyTarget = project.getTarget(dependency.reference) {
dependencies[dependency.reference] = dependency
if !dependencyTarget.shouldEmbedDependencies {
// traverse target's dependencies if it doesn't embed them itself
queue.append(dependencyTarget)
let dependencyTargetReference = try! TargetReference(dependency.reference)
switch dependencyTargetReference.location {
case .local:
if isTopLevel || dependency.embed == nil {
if let dependencyTarget = project.getTarget(dependency.reference) {
dependencies[dependency.reference] = dependency
if !dependencyTarget.shouldEmbedDependencies {
// traverse target's dependencies if it doesn't embed them itself
queue.append(dependencyTarget)
}
} else if project.getAggregateTarget(dependency.reference) != nil {
// Aggregate targets should be included
dependencies[dependency.reference] = dependency
}
} else if project.getAggregateTarget(dependency.reference) != nil {
// Aggregate targets should be included
}
case .project:
if isTopLevel || dependency.embed == nil {
dependencies[dependency.reference] = dependency
}
}

View File

@ -9,6 +9,7 @@
/* Begin PBXFileReference section */
6023D61BF2C57E6AE09CE7A3 /* BundleX.bundle */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.plug-in"; path = BundleX.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
60D6679FB526839EAFEA2EEE /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = config.xcconfig; sourceTree = "<group>"; };
D6340FC7DEBC81E0127BAFD6 /* ExternalTarget.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ExternalTarget.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXGroup section */
@ -33,6 +34,7 @@
isa = PBXGroup;
children = (
6023D61BF2C57E6AE09CE7A3 /* BundleX.bundle */,
D6340FC7DEBC81E0127BAFD6 /* ExternalTarget.framework */,
);
name = Products;
sourceTree = "<group>";
@ -55,6 +57,21 @@
productReference = 6023D61BF2C57E6AE09CE7A3 /* BundleX.bundle */;
productType = "com.apple.product-type.bundle";
};
E76A5F5E363E470416D3B487 /* ExternalTarget */ = {
isa = PBXNativeTarget;
buildConfigurationList = B5049D72EC10A5C87F29B6B1 /* Build configuration list for PBXNativeTarget "ExternalTarget" */;
buildPhases = (
F08051CAC5E72EEEB8FB447B /* Sources */,
);
buildRules = (
);
dependencies = (
);
name = ExternalTarget;
productName = ExternalTarget;
productReference = D6340FC7DEBC81E0127BAFD6 /* ExternalTarget.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@ -78,6 +95,7 @@
projectRoot = "";
targets = (
63A2D4898D974A06E85B07F8 /* BundleX */,
E76A5F5E363E470416D3B487 /* ExternalTarget */,
);
};
/* End PBXProject section */
@ -90,6 +108,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F08051CAC5E72EEEB8FB447B /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
@ -248,6 +273,27 @@
};
name = "Staging Release";
};
1B1B4A623B8E9937627EF22C /* Test Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "Test Debug";
};
270E1D32776D2D196D435FDA /* Staging Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -308,6 +354,69 @@
};
name = "Staging Debug";
};
30E2E954A636A240B1CE5C15 /* Staging Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "Staging Release";
};
3B4B85DEBC49A5BEF2196886 /* Staging Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "Staging Debug";
};
42D8986753AED7AA1F3DB83D /* Production Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "Production Debug";
};
49FA7F235A6CA1F941192151 /* Staging Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -415,6 +524,48 @@
};
name = "Test Debug";
};
6081D1A42688EBF6CF4B2579 /* Production Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "Production Release";
};
7919FDD28F9A535EA26FB151 /* Test Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = "Test Release";
};
9BD6CAD5463121A1C3FED138 /* Production Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -529,6 +680,19 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = "Production Debug";
};
B5049D72EC10A5C87F29B6B1 /* Build configuration list for PBXNativeTarget "ExternalTarget" */ = {
isa = XCConfigurationList;
buildConfigurations = (
42D8986753AED7AA1F3DB83D /* Production Debug */,
6081D1A42688EBF6CF4B2579 /* Production Release */,
3B4B85DEBC49A5BEF2196886 /* Staging Debug */,
30E2E954A636A240B1CE5C15 /* Staging Release */,
1B1B4A623B8E9937627EF22C /* Test Debug */,
7919FDD28F9A535EA26FB151 /* Test Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = "";
};
/* End XCConfigurationList section */
};
rootObject = 1B166EB49192B73A9DD8E108 /* Project object */;

View File

@ -6,3 +6,6 @@ targets:
BundleX:
type: bundle
platform: iOS
ExternalTarget:
type: framework
platform: iOS

View File

@ -110,6 +110,7 @@
BFCCC56337A5D9D513C1C791 /* module.modulemap in CopyFiles */ = {isa = PBXBuildFile; fileRef = F2950763C4C568CC85021D18 /* module.modulemap */; };
C3672B561F456794151C047C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4C3FE6B986506724DAB5D0F /* ViewController.swift */; };
C400EBD25886ACB5CD9035EB /* module.modulemap in CopyFiles */ = {isa = PBXBuildFile; fileRef = F2950763C4C568CC85021D18 /* module.modulemap */; };
C7B9C14C4AE497A9A9556603 /* ExternalTarget.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3DCDCD083AABD1D30EA34576 /* ExternalTarget.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
C836F09B677937EFF69B1FCE /* NotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C934C1F7A68CCD0AB6B38478 /* NotificationController.swift */; };
C88598A49087A212990F4E8B /* ResourceFolder in Resources */ = {isa = PBXBuildFile; fileRef = 6B1603BA83AA0C7B94E45168 /* ResourceFolder */; };
CCA17097382757012B58C17C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1BC32A813B80A53962A1F365 /* Assets.xcassets */; };
@ -123,6 +124,7 @@
E4D0F435405DABCB51C5B684 /* TestFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E43116070AFEF5D8C3A5A957 /* TestFramework.framework */; };
E5DD0AD6F7AE1DD4AF98B83E /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 65C8D6D1DDC1512D396C07B7 /* Localizable.stringsdict */; };
E8A135F768448632F8D77C8F /* StandaloneAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3571E41E19A5AB8AAAB04109 /* StandaloneAssets.xcassets */; };
EB29B16FF4DE8A7F3EDD0A0F /* ExternalTarget.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DCDCD083AABD1D30EA34576 /* ExternalTarget.framework */; };
F07FC32458F3193917261C15 /* BundleX.bundle in Copy Bundles to Resources directory */ = {isa = PBXBuildFile; fileRef = 45C12576F5AA694DD0CE2132 /* BundleX.bundle */; };
F5D71267BB5A326BDD69D532 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E55F45EACB0F382722D61C8D /* Assets.xcassets */; };
F6537CE373C94809E6653758 /* Headers in Headers */ = {isa = PBXBuildFile; fileRef = 2E1E747C7BC434ADB80CC269 /* Headers */; settings = {ATTRIBUTES = (Public, ); }; };
@ -202,6 +204,13 @@
remoteGlobalIDString = E7815F2F0D9CDECF9185AAF3;
remoteInfo = "XPC Service";
};
8F4740ACBFA31AAF289F64AC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = F192E783CCA898FBAA5C34EA /* AnotherProject */;
proxyType = 2;
remoteGlobalIDString = D6340FC7DEBC81E0127BAFD6;
remoteInfo = ExternalTarget;
};
9636E133F9CACD17C56B7915 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */;
@ -272,6 +281,13 @@
remoteGlobalIDString = AE3F93DB94E7208F2F1D9A78;
remoteInfo = Framework_iOS;
};
FF75DC967D1097BC31DCF5E6 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = F192E783CCA898FBAA5C34EA /* AnotherProject */;
proxyType = 1;
remoteGlobalIDString = E76A5F5E363E470416D3B487;
remoteInfo = ExternalTarget;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@ -435,6 +451,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
C7B9C14C4AE497A9A9556603 /* ExternalTarget.framework in Embed Frameworks */,
63D8E7F00276736EDA62D227 /* Framework2.framework in Embed Frameworks */,
B2D43A31C184E34EF9CB743C /* Framework.framework in Embed Frameworks */,
);
@ -548,6 +565,7 @@
EE1343F2238429D4DA9D830B /* File1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = File1.swift; path = Group/File1.swift; sourceTree = "<group>"; };
EF92E90B6F1D583382BD85BE /* StaticLibrary_ObjC.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = StaticLibrary_ObjC.a; sourceTree = BUILT_PRODUCTS_DIR; };
F0D48A913C087D049C8EDDD7 /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
F192E783CCA898FBAA5C34EA /* AnotherProject */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AnotherProject; path = AnotherProject/AnotherProject.xcodeproj; sourceTree = "<group>"; };
F2950763C4C568CC85021D18 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
F2FA55A558627ED576A4AFD6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
FA86D418796C1A6864414460 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
@ -562,6 +580,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
EB29B16FF4DE8A7F3EDD0A0F /* ExternalTarget.framework in Frameworks */,
4CB673A7C0C11E04F8544BDB /* Contacts.framework in Frameworks */,
262891CCD5F74316610437FA /* Framework2.framework in Frameworks */,
2FAE950E8FF2E7C0F7FF1FE9 /* Framework.framework in Frameworks */,
@ -728,6 +747,7 @@
79DC4A1E4D2E0D3A215179BC /* Bundles */,
FC1515684236259C50A7747F /* Frameworks */,
AC523591AC7BE9275003D2DB /* Products */,
D7929BCBA599BD85A3B8A039 /* Projects */,
);
indentWidth = 2;
sourceTree = "<group>";
@ -850,6 +870,14 @@
path = StandaloneFiles;
sourceTree = "<group>";
};
9DCF6BEAF3BA2AD29290E9B3 /* Products */ = {
isa = PBXGroup;
children = (
3DCDCD083AABD1D30EA34576 /* ExternalTarget.framework */,
);
name = Products;
sourceTree = "<group>";
};
9EDF27BB8A57733E6639D36D /* Resources */ = {
isa = PBXGroup;
children = (
@ -949,6 +977,14 @@
path = tvOS;
sourceTree = "<group>";
};
D7929BCBA599BD85A3B8A039 /* Projects */ = {
isa = PBXGroup;
children = (
F192E783CCA898FBAA5C34EA /* AnotherProject */,
);
name = Projects;
sourceTree = "<group>";
};
DBF93518FC96D95A54552713 /* iOS */ = {
isa = PBXGroup;
children = (
@ -1154,6 +1190,7 @@
buildRules = (
);
dependencies = (
7C4CC3918BD0DA1E04A5E2B0 /* PBXTargetDependency */,
0D33D01C71E8002A07F02122 /* PBXTargetDependency */,
A94F38390A74E215EC107BB5 /* PBXTargetDependency */,
E84285243DE0BB361A708079 /* PBXTargetDependency */,
@ -1609,6 +1646,12 @@
);
mainGroup = 293D0FF827366B513839236A;
projectDirPath = "";
projectReferences = (
{
ProductGroup = 9DCF6BEAF3BA2AD29290E9B3 /* Products */;
ProjectRef = F192E783CCA898FBAA5C34EA /* AnotherProject */;
},
);
projectRoot = "";
targets = (
0867B0DACEF28C11442DE8F7 /* App_iOS */,
@ -1644,6 +1687,16 @@
};
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
3DCDCD083AABD1D30EA34576 /* ExternalTarget.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = ExternalTarget.framework;
remoteRef = 8F4740ACBFA31AAF289F64AC /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
1C008C34DA06FEE7841B0AC3 /* Resources */ = {
isa = PBXResourcesBuildPhase;
@ -2167,6 +2220,11 @@
target = 0636AAF06498C336E1CEEDE4 /* TestFramework */;
targetProxy = C42BA4EA0239AF536F0F0993 /* PBXContainerItemProxy */;
};
7C4CC3918BD0DA1E04A5E2B0 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = E76A5F5E363E470416D3B487;
targetProxy = FF75DC967D1097BC31DCF5E6 /* PBXContainerItemProxy */;
};
7EFC0278E67CD35F8981993C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 578C80E461E675508CED5DC3 /* StaticLibrary_ObjC_macOS */;

View File

@ -15,6 +15,9 @@ fileGroups:
- Configs
- FileGroup
- SomeFile
projectReferences:
AnotherProject:
path: ./AnotherProject/AnotherProject.xcodeproj
configFiles:
Test Debug: Configs/config.xcconfig
targets:
@ -106,6 +109,7 @@ targets:
- target: iMessageApp
- sdk: Contacts.framework
- bundle: BundleX.bundle
- target: AnotherProject/ExternalTarget
scheme:
testTargets:
- App_iOS_Tests

View File

@ -217,7 +217,7 @@ class ProjectSpecTests: XCTestCase {
try expectValidationError(project, .invalidSchemeConfig(scheme: "scheme1", config: "releaseInvalid"))
}
$0.it("fails with invalid project reference") {
$0.it("fails with invalid project reference in scheme") {
var project = baseProject
project.schemes = [Scheme(
name: "scheme1",
@ -233,6 +233,36 @@ class ProjectSpecTests: XCTestCase {
try expectValidationError(project, .invalidProjectReferencePath(reference))
}
$0.it("fails with invalid project reference in dependency") {
var project = baseProject
project.targets = [
Target(
name: "target1",
type: .application,
platform: .iOS,
dependencies: [Dependency(type: .target, reference: "invalidProjectRef/target2")]
),
]
try expectValidationError(project, .invalidTargetDependency(target: "target1", dependency: "invalidProjectRef/target2"))
}
$0.it("allows project reference in target dependency") {
var project = baseProject
let externalProjectPath = fixturePath + "TestProject/AnotherProject/AnotherProject.xcodeproj"
project.projectReferences = [
ProjectReference(name: "validProjectRef", path: externalProjectPath.string)
]
project.targets = [
Target(
name: "target1",
type: .application,
platform: .iOS,
dependencies: [Dependency(type: .target, reference: "validProjectRef/ExternalTarget")]
),
]
try project.validate()
}
$0.it("allows missing optional file") {
var project = baseProject
project.targets = [Target(

View File

@ -376,6 +376,7 @@ class SpecLoadingTests: XCTestCase {
var targetDictionary = validTarget
targetDictionary["dependencies"] = [
["target": "name", "embed": false],
["target": "project/name", "embed": false],
["carthage": "name", "findFrameworks": true],
["carthage": "name", "findFrameworks": true, "linkType": "static"],
["framework": "path", "weak": true],
@ -386,13 +387,14 @@ class SpecLoadingTests: XCTestCase {
],
]
let target = try Target(name: "test", jsonDictionary: targetDictionary)
try expect(target.dependencies.count) == 6
try expect(target.dependencies.count) == 7
try expect(target.dependencies[0]) == Dependency(type: .target, reference: "name", embed: false)
try expect(target.dependencies[1]) == Dependency(type: .carthage(findFrameworks: true, linkType: .dynamic), reference: "name")
try expect(target.dependencies[2]) == Dependency(type: .carthage(findFrameworks: true, linkType: .static), reference: "name")
try expect(target.dependencies[3]) == Dependency(type: .framework, reference: "path", weakLink: true)
try expect(target.dependencies[4]) == Dependency(type: .sdk(root: nil), reference: "Contacts.framework")
try expect(target.dependencies[5]) == Dependency(type: .sdk(root: "DEVELOPER_DIR"), reference: "Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework")
try expect(target.dependencies[1]) == Dependency(type: .target, reference: "project/name", embed: false)
try expect(target.dependencies[2]) == Dependency(type: .carthage(findFrameworks: true, linkType: .dynamic), reference: "name")
try expect(target.dependencies[3]) == Dependency(type: .carthage(findFrameworks: true, linkType: .static), reference: "name")
try expect(target.dependencies[4]) == Dependency(type: .framework, reference: "path", weakLink: true)
try expect(target.dependencies[5]) == Dependency(type: .sdk(root: nil), reference: "Contacts.framework")
try expect(target.dependencies[6]) == Dependency(type: .sdk(root: "DEVELOPER_DIR"), reference: "Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework")
}
$0.it("parses info plist") {

View File

@ -312,6 +312,51 @@ class ProjectGeneratorTests: XCTestCase {
try expect(dependencies).contains { $0.target == frameworkTarget }
}
$0.it("generates dependency from external project file") {
let subproject: PBXProj
prepareXcodeProj: do {
let project = try! Project(path: fixturePath + "TestProject/AnotherProject/project.yml")
let generator = ProjectGenerator(project: project)
let writer = FileWriter(project: project)
let xcodeProject = try! generator.generateXcodeProject()
try! writer.writeXcodeProject(xcodeProject)
try! writer.writePlists()
subproject = xcodeProject.pbxproj
}
let externalProjectPath = fixturePath + "TestProject/AnotherProject/AnotherProject.xcodeproj"
let projectReference = ProjectReference(name: "AnotherProject", path: externalProjectPath.string)
var target = app
target.dependencies = [
Dependency(type: .target, reference: "AnotherProject/ExternalTarget")
]
let project = Project(
name: "test",
targets: [target],
schemes: [],
projectReferences: [projectReference]
)
let pbxProject = try project.generatePbxProj()
let projectReferences = pbxProject.rootObject?.projects ?? []
try expect(projectReferences.count) == 1
try expect((projectReferences.first?["ProjectRef"])?.name) == "AnotherProject"
let dependencies = pbxProject.targetDependencies
let targetUuid = subproject.targets(named: "ExternalTarget").first?.uuid
try expect(dependencies.count) == 1
try expect(dependencies).contains { dependency in
guard let id = dependency.targetProxy?.remoteGlobalID else { return false }
switch id {
case .object(let object):
return object.uuid == targetUuid
case .string(let value):
return value == targetUuid
}
}
}
$0.it("generates targets with correct transitive embeds") {
// App # Embeds it's frameworks, so shouldn't embed in tests
// dependencies: