Skip to content
Open
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
23 changes: 22 additions & 1 deletion Sources/XcodeGenKit/SourceGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,13 @@ class SourceGenerator {
let createIntermediateGroups = project.options.createIntermediateGroups

let parentPath = path.parent()

guard !isInsideSyncedFolder(path: path) else {
return getFileReference(path: path, inPath: project.basePath, sourceTree: .sourceRoot)
}

let fileReference = getFileReference(path: path, inPath: parentPath)

let parentGroup = getGroup(
path: parentPath,
mergingChildren: [fileReference],
Expand Down Expand Up @@ -277,6 +283,19 @@ class SourceGenerator {
}
}

/// Whether the given path falls inside a target source configured as a synced folder.
/// Checks the project spec directly because configFiles are resolved before target sources
/// populate `syncedGroupsByPath`.
private func isInsideSyncedFolder(path: Path) -> Bool {
let relativePath = (try? path.relativePath(from: project.basePath)) ?? path
return project.targets.contains { target in
target.sources.contains { source in
let type = source.type ?? (project.options.defaultSourceDirectoryType ?? .group)
return type == .syncedFolder && relativePath.string.hasPrefix(source.path + "/")
}
}
}

/// returns a default build phase for a given path. This is based off the filename
private func getDefaultBuildPhase(for path: Path, targetType: PBXProductType) -> BuildPhaseSpec? {
if let buildPhase = getFileType(path: path)?.buildPhase {
Expand Down Expand Up @@ -350,7 +369,7 @@ class SourceGenerator {
groupReference = addObject(group)
groupsByPath[path] = groupReference

if isTopLevelGroup {
if isTopLevelGroup && !isInsideSyncedFolder(path: path) {
rootGroups.insert(groupReference)
}
}
Expand Down Expand Up @@ -396,6 +415,8 @@ class SourceGenerator {
if child.isDirectory && !Xcode.isDirectoryFileWrapper(path: child) {
findExceptions(in: child)
}
} else if child.isDirectory && !Xcode.isDirectoryFileWrapper(path: child) {
findExceptions(in: child)
} else {
exceptions.insert(child)
}
Expand Down
39 changes: 34 additions & 5 deletions Tests/XcodeGenKitTests/SourceGeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ class SourceGeneratorTests: XCTestCase {
try expect(exceptions.contains("Nested/b.swift")) == false
}

$0.it("excludes entire subdirectory as single exception when no files in it are included") {
$0.it("excludes individual files in subdirectory when no files in it are included") {
let directories = """
Sources:
- a.swift
Expand All @@ -436,10 +436,11 @@ class SourceGeneratorTests: XCTestCase {
let exceptionSet = try unwrap(syncedFolder.exceptions?.first as? PBXFileSystemSynchronizedBuildFileExceptionSet)
let exceptions = try unwrap(exceptionSet.membershipExceptions)

// The whole directory should be a single exception entry, not each file within it
try expect(exceptions.contains("ExcludedDir")) == true
try expect(exceptions.contains("ExcludedDir/x.swift")) == false
try expect(exceptions.contains("ExcludedDir/y.swift")) == false
// Xcode does not recursively exclude directory contents from membershipExceptions,
// so individual files must be listed instead of the directory name
try expect(exceptions.contains("ExcludedDir")) == false
try expect(exceptions.contains("ExcludedDir/x.swift")) == true
try expect(exceptions.contains("ExcludedDir/y.swift")) == true
try expect(exceptions.contains("a.swift")) == false
}

Expand Down Expand Up @@ -491,6 +492,34 @@ class SourceGeneratorTests: XCTestCase {
try expect(appGroup === testsGroup) == true
}

$0.it("does not create duplicate group for configFiles inside synced folder") {
let directories = """
Sources:
- a.swift
- Config:
- config.xcconfig
"""
try createDirectories(directories)

let source = TargetSource(path: "Sources", type: .syncedFolder)
let target = Target(name: "Target1", type: .application, platform: .iOS, sources: [source])
let project = Project(
basePath: directoryPath,
name: "Test",
targets: [target],
configFiles: ["Debug": "Sources/Config/config.xcconfig"]
)

let pbxProj = try project.generatePbxProj()
let mainGroup = try pbxProj.getMainGroup()

let sourcesChildren = mainGroup.children.filter { $0.path == "Sources" || $0.name == "Sources" }
try expect(sourcesChildren.count) == 1

let syncedFolders = mainGroup.children.compactMap { $0 as? PBXFileSystemSynchronizedRootGroup }
try expect(syncedFolders.count) == 1
}

$0.it("supports frameworks in sources") {
let directories = """
Sources:
Expand Down