Skip to content

Commit

Permalink
Merge pull request #921 from mac-cain13/filesynchronizedgroups
Browse files Browse the repository at this point in the history
Implement Xcode 16 file system synchronized directories
  • Loading branch information
tomlokhorst authored Nov 4, 2024
2 parents 90db951 + 19c9dca commit c05635a
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 17 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches: '*'

env:
DEVELOPER_DIR: /Applications/Xcode_14.0.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode-16.0.0.app/Contents/Developer

jobs:
unit-tests:
Expand Down Expand Up @@ -54,7 +54,7 @@ jobs:
run: pod install --project-directory=Examples/ResourceApp
- name: Test
#run: fastlane scan --workspace "Examples/ResourceApp/ResourceApp.xcworkspace" --scheme "ResourceApp"
run: xcodebuild -workspace Examples/ResourceApp/ResourceApp.xcworkspace -scheme ResourceApp -destination 'platform=iOS Simulator,name=iPhone 14,OS=16.0' test
run: xcodebuild -workspace Examples/ResourceApp/ResourceApp.xcworkspace -scheme ResourceApp -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.0' test

test-iOS-StaticFrameworks:
runs-on: self-hosted
Expand All @@ -74,7 +74,7 @@ jobs:
chmod +x .build/release/rswift
- name: Test
#run: fastlane scan --project "Examples/RswiftAppWithStaticFrameworks/RswiftAppWithStaticFrameworks.xcodeproj" --scheme "App"
run: xcodebuild -project Examples/RswiftAppWithStaticFrameworks/RswiftAppWithStaticFrameworks.xcodeproj -scheme App -destination 'platform=iOS Simulator,name=iPhone 14,OS=16.0' test
run: xcodebuild -project Examples/RswiftAppWithStaticFrameworks/RswiftAppWithStaticFrameworks.xcodeproj -scheme App -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.0' test

test-iOS-LocalizedStringApp:
runs-on: self-hosted
Expand All @@ -93,7 +93,7 @@ jobs:
mv rswift-dev/rswift .build/release/rswift
chmod +x .build/release/rswift
- name: Test
run: xcodebuild -project Examples/LocalizedStringApp/LocalizedStringApp.xcodeproj -scheme LocalizedStringApp -destination 'platform=iOS Simulator,name=iPhone 14,OS=16.0' test
run: xcodebuild -project Examples/LocalizedStringApp/LocalizedStringApp.xcodeproj -scheme LocalizedStringApp -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.0' test

test-tvOS:
runs-on: self-hosted
Expand All @@ -113,7 +113,7 @@ jobs:
chmod +x .build/release/rswift
- name: Test
#run: fastlane scan --project "Examples/RtvApp/RtvApp.xcodeproj" --scheme "ResourceApp-tvOS"
run: xcodebuild -project Examples/RtvApp/RtvApp.xcodeproj -scheme ResourceApp-tvOS -destination 'platform=tvOS Simulator,name=Apple TV,OS=16.0' test
run: xcodebuild -project Examples/RtvApp/RtvApp.xcodeproj -scheme ResourceApp-tvOS -destination 'platform=tvOS Simulator,name=Apple TV,OS=18.0' test

build-rswift:
runs-on: self-hosted
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
types: created

env:
DEVELOPER_DIR: /Applications/Xcode_14.0.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode-16.0.0.app/Contents/Developer

jobs:
release-build:
Expand Down
2 changes: 1 addition & 1 deletion Examples/ResourceApp/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: e2c390a1550dbee3d41134bbc3a65772c599b62c

COCOAPODS: 1.12.1
COCOAPODS: 1.15.2
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/tomlokhorst/XcodeEdit",
"state" : {
"revision" : "cd466d6e8c5ffd2f2b61165d37b0646f09068e1e",
"version" : "2.9.0"
"revision" : "1e761a55dd8d73b4e9cc227a297f438413953571",
"version" : "2.11.1"
}
}
],
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let package = Package(
.plugin(name: "RswiftModifyXcodePackages", targets: ["RswiftModifyXcodePackages"]),
],
dependencies: [
.package(url: "https://github.com/tomlokhorst/XcodeEdit", from: "2.10.2"),
.package(url: "https://github.com/tomlokhorst/XcodeEdit", from: "2.11.1"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.1.0"),
],
targets: [
Expand Down
4 changes: 2 additions & 2 deletions Sources/RswiftGenerators/StringsTable+Generator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ extension StringsTable {
let fewParams = allParams.filter { $0.0 == badKey }.map { $0.1 }

if let params = fewParams.first {
let locales = params.compactMap { $0.0.localeDescription }.joined(separator: ", ")
let locales = params.compactMap { $0.0.localeDescription }.sorted().joined(separator: ", ")
warning("Skipping string for key \(badKey) (\(filename)), format specifiers don't match for all locales: \(locales)")
}
}
Expand Down Expand Up @@ -408,7 +408,7 @@ private struct StringWithParams {
}

let locales = values.compactMap { $0.0.localeDescription }
results.append("Locales: \(locales.joined(separator: ", "))")
results.append("Locales: \(locales.sorted().joined(separator: ", "))")
}

return results
Expand Down
99 changes: 94 additions & 5 deletions Sources/RswiftParsers/ProjectResources.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,14 @@ public struct ProjectResources {

let buildConfigurations = try xcodeproj.buildConfigurations(forTarget: targetName)

let paths = try xcodeproj.resourcePaths(forTarget: targetName)
let urls = paths
.map { $0.url(with: sourceTreeURLs.url(for:)) }
.filter { !ignoreFile.matches(url: $0) }

var excludeURLs: [URL] = []
let infoPlists: [PropertyListResource]
let entitlements: [PropertyListResource]

if resourceTypes.contains(.info) {
infoPlists = try buildConfigurations.compactMap { config -> PropertyListResource? in
guard let url = infoPlistFile else { return nil }
excludeURLs.append(url)
return try parse(with: warning) {
try PropertyListResource.parse(url: url, buildConfigurationName: config.name)
}
Expand All @@ -75,12 +72,39 @@ public struct ProjectResources {
if resourceTypes.contains(.entitlements) {
entitlements = try buildConfigurations.compactMap { config -> PropertyListResource? in
guard let url = codeSignEntitlements else { return nil }
excludeURLs.append(url)
return try parse(with: warning) { try PropertyListResource.parse(url: url, buildConfigurationName: config.name) }
}
} else {
entitlements = []
}

let paths = try xcodeproj.resourcePaths(forTarget: targetName)
let pathURLs = paths.map { $0.url(with: sourceTreeURLs.url(for:)) }

let extraURLs = try xcodeproj.extraResourceURLs(forTarget: targetName, sourceTreeURLs: sourceTreeURLs)

// Combine URLs from Xcode project file with extra URLs found by scanning file system
var pathAndExtraURLs = Array(Set(pathURLs + extraURLs))

// Find all localized strings files for ignore extension so that those can be removed
let localizedExtensions = ["xib", "storyboard", "intentdefinition"]
let localizedStringURLs = findLocalizedStrings(inputURLs: pathAndExtraURLs, ignoreExtensions: localizedExtensions)

// These file types are compiled, and shouldn't be included as resources
// Note that this should be done after finding localized files
let sourceCodeExtensions = [
"swift", "h", "m", "mm", "c", "cpp", "metal",
"xcdatamodeld", "entitlements", "intentdefinition",
]
pathAndExtraURLs.removeAll(where: { sourceCodeExtensions.contains($0.pathExtension) })

// Remove all ignored files, excluded files and localized strings files
let urls = pathAndExtraURLs
.filter { !ignoreFile.matches(url: $0) }
.filter { !excludeURLs.contains($0) }
.filter { !localizedStringURLs.contains($0) }

return try parseURLs(
urls: urls,
infoPlists: infoPlists,
Expand Down Expand Up @@ -184,6 +208,71 @@ public struct ProjectResources {
}
}

// Finds strings files for Xcode generated files
//
// Example 1:
// some-dir/Base.lproj/MyIntents.intentdefinition
// some-dir/nl.lproj/MyIntents.string
//
// Example 2:
// some-dir/Base.lproj/Main.storyboard
// some-dir/nl.lproj/Main.string
private func findLocalizedStrings(inputURLs: [URL], ignoreExtensions: [String]) -> [URL] {
// Dictionary to map each parent directory to its `.lproj` subdirectories
var parentToLprojDirectories = [URL: [URL]]()

// Dictionary to keep track of files in each `.lproj` directory
var directoryContents = [URL: [URL]]()

// Populate the dictionaries
for url in inputURLs {
let directoryURL = url.deletingLastPathComponent()
let parentDirectory = directoryURL.deletingLastPathComponent()
if directoryURL.lastPathComponent.hasSuffix(".lproj") {
parentToLprojDirectories[parentDirectory, default: []].append(directoryURL)
directoryContents[directoryURL, default: []].append(url)
}
}

// Set of URLs to remove
var urlsToRemove = Set<URL>()

// Analyze each group of sibling `.lproj` directories under the same parent
for (_, lprojDirectories) in parentToLprojDirectories {
var baseFilenameToFileUrls = [String: [URL]]()
var baseFilenamesWithIgnoreExtension = Set<String>()

// Collect all files by base filename and check for files with an ignoreExtension
for directory in lprojDirectories {
guard let files = directoryContents[directory] else { continue }
for file in files {
let baseFilename = file.deletingPathExtension().lastPathComponent
let fileExtension = file.pathExtension

baseFilenameToFileUrls[baseFilename, default: []].append(file)

if ignoreExtensions.contains(fileExtension) {
baseFilenamesWithIgnoreExtension.insert(baseFilename)
}
}
}

// Determine which files to remove based on the presence of files with an ignoreExtension
for baseFilename in baseFilenamesWithIgnoreExtension {
if let files = baseFilenameToFileUrls[baseFilename] {
for file in files {
if file.pathExtension == "strings" {
urlsToRemove.insert(file)
}
}
}
}
}

return Array(urlsToRemove)
}


private func parse<R>(with warning: (String) -> Void, closure: () throws -> R) throws -> R? {
do {
return try closure()
Expand Down
Loading

0 comments on commit c05635a

Please sign in to comment.