Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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 @@ -206,7 +206,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)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a new basePath getter that may be a better value, as xcodegen can be run in a different directory than the project base path.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I did't see it.
I just changed with basePath, it's way more cleaner

}

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

let parentGroup = getGroup(
path: parentPath,
mergingChildren: [fileReference],
Expand Down Expand Up @@ -283,6 +289,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 {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned about the performance implications of such a loop

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. In practice it's pretty small I think because it is only called from two cold paths (getContainedFileReference for configFiles and the getGroup root insertion guard), not per-file.

But I can easily cache it with a lazy, it could look like this:

private lazy var syncedFolderPrefixes: Set<String> = {
    var prefixes = Set<String>()
    for target in project.targets {
        for source in target.sources {
            let type = source.type ?? (project.options.defaultSourceDirectoryType ?? .group)
            if type == .syncedFolder {
                prefixes.insert(source.path + "/")
            }
        }
    }
    return prefixes
}()

private func isInsideSyncedFolder(path: Path) -> Bool {
    let relativePath = (try? path.relativePath(from: basePath)) ?? path
    return syncedFolderPrefixes.contains { relativePath.string.hasPrefix($0) }
}
´´´
Want me to go with that?

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 @@ -356,7 +375,7 @@ class SourceGenerator {
groupReference = addObject(group)
groupsByPath[path] = groupReference

if isTopLevelGroup {
if isTopLevelGroup && !isInsideSyncedFolder(path: path) {
rootGroups.insert(groupReference)
}
}
Expand Down Expand Up @@ -402,6 +421,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