Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 17 additions & 4 deletions Sources/XcodeGraphMapper/Mappers/Graph/XcodeGraphMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,25 @@ public struct XcodeGraphMapper: XcodeGraphMapping {

private func loadProjects(_ projectPaths: [AbsolutePath]) async throws -> [AbsolutePath: Project] {
var projects = [AbsolutePath: Project]()
let xcodeProjects = try projectPaths.map {
try XcodeProj(pathString: $0.pathString)
}
let projectNativeTargets: [String: ProjectNativeTarget] = xcodeProjects.reduce(into: [:]) { acc, xcodeProject in
for nativeTarget in xcodeProject.pbxproj.nativeTargets {
acc[nativeTarget.name] = ProjectNativeTarget(
nativeTarget: nativeTarget,
project: xcodeProject
)
}
}

for path in projectPaths {
let xcodeProj = try XcodeProj(pathString: path.pathString)
for xcodeProject in xcodeProjects {
let projectMapper = PBXProjectMapper()
let project = try await projectMapper.map(xcodeProj: xcodeProj)
projects[path.parentDirectory] = project
let project = try await projectMapper.map(
xcodeProj: xcodeProject,
projectNativeTargets: projectNativeTargets
)
projects[project.path] = project
}

return projects
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ protocol PBXFrameworksBuildPhaseMapping {
/// - Throws: If any file paths or references cannot be resolved.
func map(
_ frameworksBuildPhase: PBXFrameworksBuildPhase,
xcodeProj: XcodeProj
xcodeProj: XcodeProj,
projectNativeTargets: [String: ProjectNativeTarget]
) throws -> [TargetDependency]
}

Expand All @@ -28,29 +29,55 @@ struct PBXFrameworksBuildPhaseMapper: PBXFrameworksBuildPhaseMapping {

func map(
_ frameworksBuildPhase: PBXFrameworksBuildPhase,
xcodeProj: XcodeProj
xcodeProj: XcodeProj,
projectNativeTargets: [String: ProjectNativeTarget]
) throws -> [TargetDependency] {
let files = frameworksBuildPhase.files ?? []
return try files.map { try mapFrameworkDependency($0, xcodeProj: xcodeProj) }
return try files.map {
try mapFrameworkDependency(
$0,
xcodeProj: xcodeProj,
projectNativeTargets: projectNativeTargets
)
}
}

// MARK: - Private Helpers

/// Maps a single PBXBuildFile from the frameworks build phase to a `TargetDependency`.
private func mapFrameworkDependency(
_ buildFile: PBXBuildFile,
xcodeProj: XcodeProj
xcodeProj: XcodeProj,
projectNativeTargets: [String: ProjectNativeTarget]
) throws -> TargetDependency {
if let product = buildFile.product {
return .package(
product: product.productName,
type: .runtime,
condition: nil
)
}
let fileRef = try buildFile.file.throwing(PBXFrameworksBuildPhaseMappingError.missingFileReference)
switch fileRef.sourceTree {
case .buildProductsDir:
guard let path = fileRef.path else { break }
let name = path.replacingOccurrences(of: ".framework", with: "")
return .target(
name: name,
status: .required,
condition: nil
)
let linkingStatus: LinkingStatus = (buildFile.settings?["ATTRIBUTES"] as? [String])?
.contains("Weak") == true ? .optional : .required
if let target = xcodeProj.pbxproj.targets(named: name).first {
return .target(
name: target.name,
status: linkingStatus,
condition: nil
)
} else if let projectNativeTarget = projectNativeTargets[name] {
return .project(
target: projectNativeTarget.nativeTarget.name,
path: projectNativeTarget.project.projectPath.parentDirectory,
status: linkingStatus,
condition: nil
)
}
default:
break
}
Expand Down
16 changes: 13 additions & 3 deletions Sources/XcodeGraphMapper/Mappers/Project/PBXProjectMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ protocol PBXProjectMapping {
/// - Parameter xcodeProj: The Xcode project to be transformed.
/// - Returns: A fully constructed `Project` model.
/// - Throws: If reading or transforming project data fails.
func map(xcodeProj: XcodeProj) async throws -> Project
func map(
xcodeProj: XcodeProj,
projectNativeTargets: [String: ProjectNativeTarget]
) async throws -> Project
}

/// A mapper that transforms a `.xcodeproj` into a `Project` domain model.
Expand All @@ -31,7 +34,10 @@ struct PBXProjectMapper: PBXProjectMapping {
/// - Parameter xcodeProj: The Xcode project reference containing `.pbxproj` data.
/// - Returns: A fully constructed `Project` model.
/// - Throws: If reading or transforming project data fails.
func map(xcodeProj: XcodeProj) async throws -> Project {
func map(
xcodeProj: XcodeProj,
projectNativeTargets: [String: ProjectNativeTarget]
) async throws -> Project {
let settingsMapper = XCConfigurationMapper()
let pbxProject = try xcodeProj.mainPBXProject()
let xcodeProjPath = xcodeProj.projectPath
Expand All @@ -48,7 +54,11 @@ struct PBXProjectMapper: PBXProjectMapping {
let targets = try await withThrowingTaskGroup(of: Target.self, returning: [Target].self) { taskGroup in
for pbxTarget in pbxProject.targets {
taskGroup.addTask {
try await targetMapper.map(pbxTarget: pbxTarget, xcodeProj: xcodeProj)
try await targetMapper.map(
pbxTarget: pbxTarget,
xcodeProj: xcodeProj,
projectNativeTargets: projectNativeTargets
)
}
}

Expand Down
22 changes: 17 additions & 5 deletions Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ protocol PBXTargetMapping {
/// - Returns: A fully mapped `Target` model.
/// - Throws: `PBXTargetMappingError` if required data (like a bundle identifier) is missing,
/// or if necessary files/groups cannot be found.
func map(pbxTarget: PBXTarget, xcodeProj: XcodeProj) async throws -> Target
func map(
pbxTarget: PBXTarget,
xcodeProj: XcodeProj,
projectNativeTargets: [String: ProjectNativeTarget]
) async throws -> Target
}

// swiftlint:disable function_body_length
Expand Down Expand Up @@ -93,7 +97,11 @@ struct PBXTargetMapper: PBXTargetMapping {
self.fileSystem = fileSystem
}

func map(pbxTarget: PBXTarget, xcodeProj: XcodeProj) async throws -> Target {
func map(
pbxTarget: PBXTarget,
xcodeProj: XcodeProj,
projectNativeTargets: [String: ProjectNativeTarget]
) async throws -> Target {
let platform = try pbxTarget.platform()
let deploymentTargets = pbxTarget.deploymentTargets()
let productType = pbxTarget.productType?.mapProductType()
Expand Down Expand Up @@ -146,7 +154,11 @@ struct PBXTargetMapper: PBXTargetMapping {
// Frameworks & libraries
let frameworksPhase = try pbxTarget.frameworksBuildPhase()
var frameworks = try frameworksPhase.map {
try frameworksMapper.map($0, xcodeProj: xcodeProj)
try frameworksMapper.map(
$0,
xcodeProj: xcodeProj,
projectNativeTargets: projectNativeTargets
)
} ?? []

frameworks = try await fileSystemSynchronizedGroupsFrameworks(
Expand Down Expand Up @@ -181,10 +193,10 @@ struct PBXTargetMapper: PBXTargetMapping {
let metadata = try pbxTarget.metadata()

// Dependencies
let targetDependencies = try pbxTarget.dependencies.compactMap {
let projectNativeTargets = try pbxTarget.dependencies.compactMap {
try dependencyMapper.map($0, xcodeProj: xcodeProj)
}
let allDependencies = (targetDependencies + frameworks).sorted { $0.name < $1.name }
let allDependencies = (projectNativeTargets + frameworks).sorted { $0.name < $1.name }

// Construct final Target
return Target(
Expand Down
7 changes: 7 additions & 0 deletions Sources/XcodeGraphMapper/Utilities/ProjectNativeTarget.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import XcodeProj

/// Model representing a `PBXNativeTarget` in a give `XcodeProj`
struct ProjectNativeTarget {
let nativeTarget: PBXNativeTarget
let project: XcodeProj
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,37 @@ struct PBXFrameworksBuildPhaseMapperTests {
)
let targetFrameworkBuildFile = PBXBuildFile(file: targetFrameworkRef).add(to: pbxProj)

let projectTargetPath = xcodeProj.projectPath.parentDirectory.appending(
components: "AnotherProject",
"AnotherProject.xcodeproj"
)
let projectTargetFrameworkRef = PBXFileReference(
sourceTree: .buildProductsDir,
path: "ProjectTarget.framework"
)
let projectTargetFrameworkBuildFile = PBXBuildFile(file: projectTargetFrameworkRef).add(to: pbxProj)

let weakProjectTargetFrameworkRef = PBXFileReference(
sourceTree: .buildProductsDir,
path: "WeakProjectTarget.framework"
)
let weakProjectTargetFrameworkBuildFile = PBXBuildFile(
file: weakProjectTargetFrameworkRef,
settings: ["ATTRIBUTES": ["Weak"]]
).add(to: pbxProj)

let packageProduct = XCSwiftPackageProductDependency(productName: "PackageProduct")
let packageProductBuildFile = PBXBuildFile(
product: packageProduct
).add(to: pbxProj)

let frameworksPhase = PBXFrameworksBuildPhase(
files: [
frameworkBuildFile,
targetFrameworkBuildFile,
projectTargetFrameworkBuildFile,
weakProjectTargetFrameworkBuildFile,
packageProductBuildFile,
]
).add(to: pbxProj)

Expand All @@ -43,10 +70,38 @@ struct PBXFrameworksBuildPhaseMapperTests {
.add(to: pbxProj)
.add(to: pbxProj.rootObject)

PBXNativeTarget(
name: "Target",
buildPhases: [frameworksPhase],
productType: .framework
)
.add(to: pbxProj)

let mapper = PBXFrameworksBuildPhaseMapper()

// When
let frameworks = try mapper.map(frameworksPhase, xcodeProj: xcodeProj)
let frameworks = try await mapper.map(
frameworksPhase,
xcodeProj: xcodeProj,
projectNativeTargets: [
"ProjectTarget": ProjectNativeTarget(
nativeTarget: .test(
name: "ProjectTarget"
),
project: .test(
path: projectTargetPath
)
),
"WeakProjectTarget": ProjectNativeTarget(
nativeTarget: .test(
name: "WeakProjectTarget"
),
project: .test(
path: projectTargetPath
)
),
]
)

// Then
let frameworkPath = try AbsolutePath(validating: "/tmp/TestProject/Frameworks/MyFramework.framework")
Expand All @@ -57,11 +112,28 @@ struct PBXFrameworksBuildPhaseMapperTests {
status: .required,
condition: nil
),
.package(
product: "PackageProduct",
type: .runtime,
condition: nil
),
.project(
target: "ProjectTarget",
path: projectTargetPath.parentDirectory,
status: .required,
condition: nil
),
.target(
name: "Target",
status: .required,
condition: nil
),
.project(
target: "WeakProjectTarget",
path: projectTargetPath.parentDirectory,
status: .optional,
condition: nil
),
]
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct PBXProjectMapperTests {
let mapper = PBXProjectMapper()

// When
let project = try await mapper.map(xcodeProj: xcodeProj)
let project = try await mapper.map(xcodeProj: xcodeProj, projectNativeTargets: [:])

// Then
#expect(project.name == "TestProject")
Expand All @@ -38,7 +38,7 @@ struct PBXProjectMapperTests {
xcodeProj.pbxproj.projects.first?.attributes = customAttributes

// When
let project = try await mapper.map(xcodeProj: xcodeProj)
let project = try await mapper.map(xcodeProj: xcodeProj, projectNativeTargets: [:])

// Then
#expect(project.name == "TestProject")
Expand All @@ -62,7 +62,7 @@ struct PBXProjectMapperTests {
let mapper = PBXProjectMapper()

// When
let project = try await mapper.map(xcodeProj: xcodeProj)
let project = try await mapper.map(xcodeProj: xcodeProj, projectNativeTargets: [:])

// Then
#expect(project.packages.count == 1)
Expand All @@ -82,7 +82,7 @@ struct PBXProjectMapperTests {
let mapper = PBXProjectMapper()

// When
let project = try await mapper.map(xcodeProj: xcodeProj)
let project = try await mapper.map(xcodeProj: xcodeProj, projectNativeTargets: [:])

// Then
#expect(project.defaultKnownRegions?.count == 3)
Expand All @@ -99,7 +99,7 @@ struct PBXProjectMapperTests {
let mapper = PBXProjectMapper()

// When
let project = try await mapper.map(xcodeProj: xcodeProj)
let project = try await mapper.map(xcodeProj: xcodeProj, projectNativeTargets: [:])

// Then
#expect(project.developmentRegion == "fr")
Expand All @@ -112,7 +112,7 @@ struct PBXProjectMapperTests {
let mapper = PBXProjectMapper()

// When
let project = try await mapper.map(xcodeProj: xcodeProj)
let project = try await mapper.map(xcodeProj: xcodeProj, projectNativeTargets: [:])
let synthesizers = project.resourceSynthesizers

// Then
Expand Down Expand Up @@ -145,7 +145,7 @@ struct PBXProjectMapperTests {
let mapper = PBXProjectMapper()

// When
let project = try await mapper.map(xcodeProj: xcodeProj)
let project = try await mapper.map(xcodeProj: xcodeProj, projectNativeTargets: [:])

// Then
#expect(project.schemes.count == 1)
Expand Down
Loading