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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

### Fixed
- Fix `syncedFolder` source paths being relative to the spec directory instead of the project directory when they differ, which caused Xcode to treat the synced folder as empty #1636 @Ckitakishi
- Fix `destinationFilters` and `inferDestinationFiltersByPath` being ignored for `syncedFolder` sources. They are now written as `platformFiltersByRelativePath` on the synced folder's exception set #1647 @anandghegde
- Fix nested target attributes (e.g. `attributes.SystemCapabilities`) being serialized as a stringified Swift `Dictionary` description instead of a proper nested plist dictionary, which also caused non-deterministic key ordering in generated `project.pbxproj` files across runs #1639 @imadaan @sergeyospanov

### Internal
Expand Down
10 changes: 7 additions & 3 deletions Sources/XcodeGenKit/PBXProjGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1501,15 +1501,19 @@ public class PBXProjGenerator {
}
}

guard !exceptions.isEmpty else { return }
let platformFilters = sourceGenerator.syncedFolderPlatformFilters(for: targetSource, at: syncedPath)
.filter { !exceptions.contains($0.key) }

guard !exceptions.isEmpty || !platformFilters.isEmpty else { return }

let exceptionSet = PBXFileSystemSynchronizedBuildFileExceptionSet(
target: targetObject,
membershipExceptions: exceptions.sorted(),
membershipExceptions: exceptions.isEmpty ? nil : exceptions.sorted(),
publicHeaders: nil,
privateHeaders: nil,
additionalCompilerFlagsByRelativePath: nil,
attributesByRelativePath: nil
attributesByRelativePath: nil,
platformFiltersByRelativePath: platformFilters.isEmpty ? nil : platformFilters
)
addObject(exceptionSet)
syncedGroup.exceptions = (syncedGroup.exceptions ?? []) + [exceptionSet]
Expand Down
26 changes: 26 additions & 0 deletions Sources/XcodeGenKit/SourceGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,32 @@ class SourceGenerator {
return exceptions
}

/// Returns the platform filters for each file in a synced folder, keyed by path relative to the synced folder.
/// Xcode only honors file paths in `platformFiltersByRelativePath`, so directories are expanded into their files.
func syncedFolderPlatformFilters(for targetSource: TargetSource, at syncedPath: Path) -> [String: [String]] {
guard targetSource.destinationFilters?.isEmpty == false || targetSource.inferDestinationFiltersByPath == true else {
return [:]
}

var platformFilters: [String: [String]] = [:]

func findFiles(in path: Path) {
guard let children = try? path.children() else { return }

for child in children where !child.lastComponent.hasPrefix(".") {
if child.isDirectory && !Xcode.isDirectoryFileWrapper(path: child) {
findFiles(in: child)
} else if let filters = makeDestinationFilters(for: child, with: targetSource.destinationFilters, or: targetSource.inferDestinationFiltersByPath),
let relativePath = try? child.relativePath(from: syncedPath) {
platformFilters[relativePath.string] = filters
}
}
}

findFiles(in: syncedPath)
return platformFilters
}

/// Collects all the excluded paths within the targetSource
private func getSourceMatches(targetSource: TargetSource, patterns: [String]) -> Set<Path> {
let rootSourcePath = project.basePath + targetSource.path
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let syncedPlatformName = "iOS"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let syncedPlatformName = "tvOS"
29 changes: 29 additions & 0 deletions Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,18 @@
);
target = 0867B0DACEF28C11442DE8F7 /* App_iOS */;
};
9A8E6A7891C068692E5463B2 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
platformFiltersByRelativePath = {
SyncedFile_iOS.swift = (
ios,
);
SyncedFile_tvOS.swift = (
tvos,
);
};
target = C0570E2FB50D830D8D423396 /* App_supportedDestinations */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
Expand Down Expand Up @@ -869,6 +881,19 @@
path = SyncedFolder;
sourceTree = "<group>";
};
E55B4EB82638DF874A0308EC /* SyncedSources */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
9A8E6A7891C068692E5463B2 /* PBXFileSystemSynchronizedBuildFileExceptionSet */,
);
explicitFileTypes = {
};
explicitFolders = (
);
name = SyncedSources;
path = SyncedSources;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -1448,6 +1473,7 @@
children = (
85FEBB7D2103B020423407A2 /* Sources */,
69C61547C081D04364A5DE42 /* Storyboards */,
E55B4EB82638DF874A0308EC /* SyncedSources */,
);
path = App_supportedDestinations;
sourceTree = "<group>";
Expand Down Expand Up @@ -2281,6 +2307,9 @@
);
dependencies = (
);
fileSystemSynchronizedGroups = (
E55B4EB82638DF874A0308EC /* SyncedSources */,
);
name = App_supportedDestinations;
packageProductDependencies = (
C7F9B7EDE85527EFEA85D46D /* Swinject */,
Expand Down
4 changes: 4 additions & 0 deletions Tests/Fixtures/TestProject/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ targets:
- path: App_supportedDestinations/Storyboards
group: App_supportedDestinations
destinationFilters: [iOS]
- path: App_supportedDestinations/SyncedSources
group: App_supportedDestinations
type: syncedFolder
inferDestinationFiltersByPath: true
dependencies:
- package: Swinject
product: Swinject
Expand Down
52 changes: 52 additions & 0 deletions Tests/XcodeGenKitTests/SourceGeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,58 @@ class SourceGeneratorTests: XCTestCase {
try expect(exceptions.contains("included.swift")) == false
}

$0.it("adds destinationFilters as platform filters for synced folder files") {
let directories = """
Sources:
- a.swift
- excluded.swift
- Nested:
- b.swift
"""
try createDirectories(directories)

let source = TargetSource(path: "Sources", excludes: ["excluded.swift"], type: .syncedFolder, destinationFilters: [.iOS])
let target = Target(name: "Test", type: .application, platform: .auto, supportedDestinations: [.iOS, .tvOS], sources: [source])
let project = Project(basePath: directoryPath, name: "Test", targets: [target])

let pbxProj = try project.generatePbxProj()
let syncedFolders = try pbxProj.getMainGroup().children.compactMap { $0 as? PBXFileSystemSynchronizedRootGroup }
let syncedFolder = try unwrap(syncedFolders.first)

let exceptionSet = try unwrap(syncedFolder.exceptions?.first as? PBXFileSystemSynchronizedBuildFileExceptionSet)
try expect(exceptionSet.membershipExceptions) == ["excluded.swift"]
try expect(exceptionSet.platformFiltersByRelativePath) == [
"a.swift": ["ios"],
"Nested/b.swift": ["ios"],
]
}

$0.it("infers platform filters by path for synced folder files") {
let directories = """
Sources:
- common.swift
- File_tvOS.swift
- iOS:
- a.swift
"""
try createDirectories(directories)

let source = TargetSource(path: "Sources", type: .syncedFolder, inferDestinationFiltersByPath: true)
let target = Target(name: "Test", type: .application, platform: .auto, supportedDestinations: [.iOS, .tvOS], sources: [source])
let project = Project(basePath: directoryPath, name: "Test", targets: [target])

let pbxProj = try project.generatePbxProj()
let syncedFolders = try pbxProj.getMainGroup().children.compactMap { $0 as? PBXFileSystemSynchronizedRootGroup }
let syncedFolder = try unwrap(syncedFolders.first)

let exceptionSet = try unwrap(syncedFolder.exceptions?.first as? PBXFileSystemSynchronizedBuildFileExceptionSet)
try expect(exceptionSet.membershipExceptions) == nil
try expect(exceptionSet.platformFiltersByRelativePath) == [
"File_tvOS.swift": ["tvos"],
"iOS/a.swift": ["ios"],
]
}

$0.it("merges explicitFolders for synced folders across targets") {
let directories = """
Sources:
Expand Down