diff --git a/Sources/ProjectSpec/SpecOptions.swift b/Sources/ProjectSpec/SpecOptions.swift index c1f0645d..fef7a14b 100644 --- a/Sources/ProjectSpec/SpecOptions.swift +++ b/Sources/ProjectSpec/SpecOptions.swift @@ -10,9 +10,11 @@ public struct SpecOptions: Equatable { public static let generateEmptyDirectoriesDefault = false public static let findCarthageFrameworksDefault = false public static let useBaseInternationalizationDefault = true + public static let carthageXCFrameworksDefault = false public static let schemePathPrefixDefault = "../../" public var minimumXcodeGenVersion: Version? + public var carthageXCFrameworks: Bool public var carthageBuildPath: String? public var carthageExecutablePath: String? public var createIntermediateGroups: Bool @@ -76,6 +78,7 @@ public struct SpecOptions: Equatable { public init( minimumXcodeGenVersion: Version? = nil, + carthageXCFrameworks: Bool = carthageXCFrameworksDefault, carthageBuildPath: String? = nil, carthageExecutablePath: String? = nil, createIntermediateGroups: Bool = createIntermediateGroupsDefault, @@ -102,6 +105,7 @@ public struct SpecOptions: Equatable { schemePathPrefix: String = schemePathPrefixDefault ) { self.minimumXcodeGenVersion = minimumXcodeGenVersion + self.carthageXCFrameworks = carthageXCFrameworks self.carthageBuildPath = carthageBuildPath self.carthageExecutablePath = carthageExecutablePath self.createIntermediateGroups = createIntermediateGroups @@ -136,6 +140,7 @@ extension SpecOptions: JSONObjectConvertible { minimumXcodeGenVersion = try Version.parse(string) } + carthageXCFrameworks = jsonDictionary.json(atKeyPath: "carthageXCFrameworks") ?? SpecOptions.carthageXCFrameworksDefault carthageBuildPath = jsonDictionary.json(atKeyPath: "carthageBuildPath") carthageExecutablePath = jsonDictionary.json(atKeyPath: "carthageExecutablePath") bundleIdPrefix = jsonDictionary.json(atKeyPath: "bundleIdPrefix") @@ -205,6 +210,9 @@ extension SpecOptions: JSONEncodable { if useBaseInternationalization != SpecOptions.useBaseInternationalizationDefault { dict["useBaseInternationalization"] = useBaseInternationalization } + if carthageXCFrameworks != SpecOptions.carthageXCFrameworksDefault { + dict["carthageXCFrameworks"] = carthageXCFrameworks + } if schemePathPrefix != SpecOptions.schemePathPrefixDefault { dict["schemePathPrefix"] = schemePathPrefix } diff --git a/Sources/XcodeGenKit/CarthageDependencyResolver.swift b/Sources/XcodeGenKit/CarthageDependencyResolver.swift index 8a0b256a..274231ed 100644 --- a/Sources/XcodeGenKit/CarthageDependencyResolver.swift +++ b/Sources/XcodeGenKit/CarthageDependencyResolver.swift @@ -39,14 +39,43 @@ public class CarthageDependencyResolver { versionLoader = CarthageVersionLoader(buildPath: project.basePath + CarthageDependencyResolver.getBuildPath(project)) } + func frameworkPath(for name: String, platform: Platform, linkType: Dependency.CarthageLinkType) -> Path { + var file = Path(name) + var xcFramework = project.options.carthageXCFrameworks + switch file.extension { + case "xcframework": + xcFramework = true + case "framework": + xcFramework = false + case .none: + let fileExtension: String + if xcFramework { + fileExtension = "xcframework" + } else { + fileExtension = "framework" + } + file = Path(file.string + ".\(fileExtension)") + default: + break + } + + let parentPath = Path(buildPath(for: platform, linkType: linkType, xcFramework: xcFramework)) + return parentPath + file + } + /// Carthage's build path for the given platform - func buildPath(for platform: Platform, linkType: Dependency.CarthageLinkType) -> String { + func buildPath(for platform: Platform, linkType: Dependency.CarthageLinkType, xcFramework: Bool) -> String { + var path = buildPath + if !xcFramework { + path += "/\(platform.carthageName)" + } switch linkType { case .static: - return "\(buildPath)/\(platform.carthageName)/Static" + path += "/Static" case .dynamic: - return "\(buildPath)/\(platform.carthageName)" + break } + return path } /// Fetches all carthage dependencies for a given target diff --git a/Sources/XcodeGenKit/CarthageVersionLoader.swift b/Sources/XcodeGenKit/CarthageVersionLoader.swift index 4a15a634..395459df 100644 --- a/Sources/XcodeGenKit/CarthageVersionLoader.swift +++ b/Sources/XcodeGenKit/CarthageVersionLoader.swift @@ -57,8 +57,13 @@ class CarthageVersionLoader { struct CarthageVersionFile: Decodable { private struct Reference: Decodable, Equatable { - public let name: String - public let hash: String + let name: String + let hash: String + let container: String? + + var frameworkFilename: String { + container ?? "\(name).framework" + } } private let data: [Platform: [String]] @@ -67,7 +72,7 @@ struct CarthageVersionFile: Decodable { let container = try decoder.container(keyedBy: Platform.self) data = try Platform.allCases.reduce(into: [:]) { data, platform in let references = try container.decodeIfPresent([Reference].self, forKey: platform) ?? [] - let frameworks = Set(references.map { $0.name }).sorted() + let frameworks = Set(references.map { $0.frameworkFilename }).sorted() data[platform] = frameworks } } diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index 9b4f8097..d54b646d 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -24,6 +24,7 @@ public class PBXProjGenerator { var packageReferences: [String: XCRemoteSwiftPackageReference] = [:] var carthageFrameworksByPlatform: [String: Set] = [:] + var carthageXCFrameworks: Set = [] var frameworkFiles: [PBXFileElement] = [] var bundleFiles: [PBXFileElement] = [] @@ -225,8 +226,8 @@ public class PBXProjGenerator { try project.targets.forEach(generateTarget) try project.aggregateTargets.forEach(generateAggregateTarget) - if !carthageFrameworksByPlatform.isEmpty { - var platforms: [PBXGroup] = [] + if !carthageFrameworksByPlatform.isEmpty || !carthageXCFrameworks.isEmpty { + var children: [PBXFileElement] = [] for (platform, files) in carthageFrameworksByPlatform { let platformGroup: PBXGroup = addObject( PBXGroup( @@ -235,11 +236,14 @@ public class PBXProjGenerator { path: platform ) ) - platforms.append(platformGroup) + children.append(platformGroup) + } + if !carthageXCFrameworks.isEmpty { + children += Array(carthageXCFrameworks) } let carthageGroup = addObject( PBXGroup( - children: platforms, + children: children, sourceTree: .group, name: "Carthage", path: carthageResolver.buildPath @@ -677,7 +681,7 @@ public class PBXProjGenerator { let targetSupportsDirectEmbed = !(target.platform.requiresSimulatorStripping && (target.type.isApp || target.type == .watch2Extension)) - let directlyEmbedCarthage = target.directlyEmbedCarthageDependencies ?? targetSupportsDirectEmbed + let directlyEmbedCarthage = target.directlyEmbedCarthageDependencies ?? project.options.carthageXCFrameworks || targetSupportsDirectEmbed func getEmbedSettings(dependency: Dependency, codeSign: Bool) -> [String: Any] { var embedAttributes: [String] = [] @@ -871,14 +875,14 @@ public class PBXProjGenerator { ? carthageResolver.relatedDependencies(for: dependency, in: target.platform) : [dependency] allDependencies.forEach { dependency in - let platformPath = Path(carthageResolver.buildPath(for: target.platform, linkType: linkType)) - var frameworkPath = platformPath + dependency.reference - if frameworkPath.extension == nil { - frameworkPath = Path(frameworkPath.string + ".framework") - } - let fileReference = self.sourceGenerator.getFileReference(path: frameworkPath, inPath: platformPath) + let frameworkPath = carthageResolver.frameworkPath(for: dependency.reference, platform: target.platform, linkType: linkType) + let fileReference = self.sourceGenerator.getFileReference(path: frameworkPath, inPath: frameworkPath.parent()) - self.carthageFrameworksByPlatform[target.platform.carthageName, default: []].insert(fileReference) + if frameworkPath.extension == "xcframework" { + self.carthageXCFrameworks.insert(fileReference) + } else { + self.carthageFrameworksByPlatform[target.platform.carthageName, default: []].insert(fileReference) + } let isStaticLibrary = target.type == .staticLibrary let isCarthageStaticLink = dependency.carthageLinkType == .static @@ -954,12 +958,8 @@ public class PBXProjGenerator { let isFromTopLevelTarget = carthageDependency.isFromTopLevelTarget let embed = dependency.embed ?? target.shouldEmbedCarthageDependencies - let platformPath = Path(carthageResolver.buildPath(for: target.platform, linkType: dependency.carthageLinkType ?? .default)) - var frameworkPath = platformPath + dependency.reference - if frameworkPath.extension == nil { - frameworkPath = Path(frameworkPath.string + ".framework") - } - let fileReference = sourceGenerator.getFileReference(path: frameworkPath, inPath: platformPath) + let frameworkPath = carthageResolver.frameworkPath(for: dependency.reference, platform: target.platform, linkType: dependency.carthageLinkType ?? .default) + let fileReference = self.sourceGenerator.getFileReference(path: frameworkPath, inPath: frameworkPath.parent()) if dependency.carthageLinkType == .static { guard isFromTopLevelTarget else { continue } // ignore transitive dependencies if static @@ -1079,7 +1079,7 @@ public class PBXProjGenerator { if !carthageFrameworksToEmbed.isEmpty { let inputPaths = carthageFrameworksToEmbed - .map { "$(SRCROOT)/\(carthageResolver.buildPath(for: target.platform, linkType: .dynamic))/\($0)\($0.contains(".") ? "" : ".framework")" } + .map { "$(SRCROOT)/\(carthageResolver.frameworkPath(for: $0, platform: target.platform, linkType: .dynamic))" } let outputPaths = carthageFrameworksToEmbed .map { "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/\($0)\($0.contains(".") ? "" : ".framework")" } let carthageExecutable = carthageResolver.executable @@ -1249,15 +1249,21 @@ public class PBXProjGenerator { let configFrameworkBuildPaths: [String] if !carthageDependencies.isEmpty { var carthagePlatformBuildPaths: Set = [] - if carthageDependencies.contains(where: { $0.dependency.carthageLinkType == .static }) { - let carthagePlatformBuildPath = "$(PROJECT_DIR)/" + carthageResolver.buildPath(for: target.platform, linkType: .static) - carthagePlatformBuildPaths.insert(carthagePlatformBuildPath) + func getCarthageBuildPaths(linkType: Dependency.CarthageLinkType) -> Set { + var paths: Set = [] + if carthageDependencies.contains(where: { $0.dependency.carthageLinkType == linkType }) { + let path = "$(PROJECT_DIR)/\(carthageResolver.buildPath(for: target.platform, linkType: linkType, xcFramework: false))" + paths.insert(path) + if project.options.carthageXCFrameworks { + let path = "$(PROJECT_DIR)/\(carthageResolver.buildPath(for: target.platform, linkType: linkType, xcFramework: true))/**" + paths.insert(path) + } + } + return paths } - if carthageDependencies.contains(where: { $0.dependency.carthageLinkType == .dynamic }) { - let carthagePlatformBuildPath = "$(PROJECT_DIR)/" + carthageResolver.buildPath(for: target.platform, linkType: .dynamic) - carthagePlatformBuildPaths.insert(carthagePlatformBuildPath) - } - configFrameworkBuildPaths = carthagePlatformBuildPaths.sorted() + frameworkBuildPaths.sorted() + carthagePlatformBuildPaths = carthagePlatformBuildPaths.union(getCarthageBuildPaths(linkType: .static)) + carthagePlatformBuildPaths = carthagePlatformBuildPaths.union(getCarthageBuildPaths(linkType: .dynamic)) + configFrameworkBuildPaths = carthagePlatformBuildPaths.union(frameworkBuildPaths).sorted() } else { configFrameworkBuildPaths = frameworkBuildPaths.sorted() } diff --git a/Tests/XcodeGenKitTests/CarthageDependencyResolverTests.swift b/Tests/XcodeGenKitTests/CarthageDependencyResolverTests.swift index 935c8842..70aab13e 100644 --- a/Tests/XcodeGenKitTests/CarthageDependencyResolverTests.swift +++ b/Tests/XcodeGenKitTests/CarthageDependencyResolverTests.swift @@ -55,7 +55,7 @@ class CarthageDependencyResolverTests: XCTestCase { } try allPlatforms.forEach { platform in - try expect(expectedByPlatform[platform]) == resolver.buildPath(for: platform, linkType: .dynamic) + try expect(expectedByPlatform[platform]) == resolver.buildPath(for: platform, linkType: .dynamic, xcFramework: false) } } }