diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index ceccfc4a..6ec6a6bd 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -32,6 +32,12 @@ class SourceGenerator { private(set) var knownRegions: Set = [] + /// The effective base path for resolving group and file paths in the generated project. + /// Uses `projectDirectory` when the xcodeproj is generated in a different location than the spec. + private var basePath: Path { + projectDirectory ?? project.basePath + } + init(project: Project, pbxProj: PBXProj, projectDirectory: Path?) { self.project = project self.pbxProj = pbxProj @@ -39,7 +45,7 @@ class SourceGenerator { } private func resolveGroupPath(_ path: Path, isTopLevelGroup: Bool) -> String { - if isTopLevelGroup, let relativePath = try? path.relativePath(from: projectDirectory ?? project.basePath).string { + if isTopLevelGroup, let relativePath = try? path.relativePath(from: basePath).string { return relativePath } else { return path.lastComponent @@ -62,7 +68,7 @@ class SourceGenerator { let absolutePath = project.basePath + path.normalize() // Get the local package's relative path from the project root - let fileReferencePath = try? absolutePath.relativePath(from: projectDirectory ?? project.basePath).string + let fileReferencePath = try? absolutePath.relativePath(from: basePath).string let fileReference = addObject( PBXFileReference( @@ -886,7 +892,7 @@ class SourceGenerator { element = parent } - let completePath = project.basePath + Path(paths.joined(separator: "/")) + let completePath = (basePath) + Path(paths.joined(separator: "/")) let relativePath = try path.relativePath(from: completePath) let relativePathString = relativePath.string diff --git a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift index 54db7f29..049a9bbb 100644 --- a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift @@ -911,6 +911,40 @@ class SourceGeneratorTests: XCTestCase { try pbxProj.expectFile(paths: ["Sources/B", "b.swift"], names: ["B", "b.swift"], buildPhase: .sources) } + $0.it("generates intermediate groups with different projectDirectory") { + + let directories = """ + Sources: + - a.swift + - b.swift + Modules: + - m.swift + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ + "../Sources", + "../Modules", + ]) + let options = SpecOptions(createIntermediateGroups: true) + // basePath is a subdirectory (simulating spec in a subdir like ProjectRoot/XcodeGen/) + // projectDirectory is the parent (simulating --project pointing to ProjectRoot/) + let subdir = directoryPath + "SubDir" + try subdir.mkpath() + let project = Project(basePath: subdir, name: "Test", targets: [target], options: options) + + let generator = PBXProjGenerator(project: project, projectDirectory: directoryPath) + let pbxProj = try generator.generate() + + // Sources and Modules should have path = "Sources"/"Modules" (not "../Sources") + // The intermediate group for TestDirectory should have path = "." + // So Xcode resolves: projectDir/./Sources = correct + // Before fix: path was "../Sources", resolving to projectDir/./../Sources = wrong + try pbxProj.expectFile(paths: [".", "Sources", "a.swift"], names: ["TestDirectory", "Sources", "a.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: [".", "Sources", "b.swift"], names: ["TestDirectory", "Sources", "b.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: [".", "Modules", "m.swift"], names: ["TestDirectory", "Modules", "m.swift"], buildPhase: .sources) + } + $0.it("generates custom groups") { let directories = """