Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
14 changes: 10 additions & 4 deletions Sources/SWBApplePlatform/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,10 @@ struct ActoolInputFileGroupingStrategyExtension: InputFileGroupingStrategyExtens
return ["actool": Factory()]
}

func fileTypesCompilingToSwiftSources() -> [String] {
func fileTypesCompilingToSwiftSources(scope: MacroEvaluationScope) -> [String] {
guard scope.evaluate(BuiltinMacros.ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS) else {
return []
}
return ["folder.abstractassetcatalog"]
}
}
Expand All @@ -196,7 +199,7 @@ struct ImageScaleFactorsInputFileGroupingStrategyExtension: InputFileGroupingStr
return ["image-scale-factors": Factory()]
}

func fileTypesCompilingToSwiftSources() -> [String] {
func fileTypesCompilingToSwiftSources(scope: MacroEvaluationScope) -> [String] {
return []
}
}
Expand All @@ -211,7 +214,7 @@ struct LocalizationInputFileGroupingStrategyExtension: InputFileGroupingStrategy
return ["region": Factory()]
}

func fileTypesCompilingToSwiftSources() -> [String] {
func fileTypesCompilingToSwiftSources(scope: MacroEvaluationScope) -> [String] {
return []
}
}
Expand All @@ -226,7 +229,10 @@ struct XCStringsInputFileGroupingStrategyExtension: InputFileGroupingStrategyExt
return ["xcstrings": Factory()]
}

func fileTypesCompilingToSwiftSources() -> [String] {
func fileTypesCompilingToSwiftSources(scope: MacroEvaluationScope) -> [String] {
guard scope.evaluate(BuiltinMacros.STRING_CATALOG_GENERATE_SYMBOLS) else {
return []
}
return ["text.json.xcstrings"]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//

public import SWBUtil
public import SWBMacro

public struct InputFileGroupingStrategyExtensionPoint: ExtensionPoint, Sendable {
public typealias ExtensionProtocol = InputFileGroupingStrategyExtension
Expand All @@ -22,5 +23,5 @@ public struct InputFileGroupingStrategyExtensionPoint: ExtensionPoint, Sendable

public protocol InputFileGroupingStrategyExtension: Sendable {
func groupingStrategies() -> [String: any InputFileGroupingStrategyFactory]
func fileTypesCompilingToSwiftSources() -> [String]
func fileTypesCompilingToSwiftSources(scope: MacroEvaluationScope) -> [String]
}
Original file line number Diff line number Diff line change
Expand Up @@ -325,10 +325,10 @@ extension PluginManager {
/// Returns identifiers of file types that can generate sources, and therefore need to be processed within the Sources build phase (at least if there are any existing source files).
///
/// Asset Catalogs would be one example of this, so that they can generate symbols.
func fileTypesProducingGeneratedSources() -> [String] {
func fileTypesProducingGeneratedSources(scope: MacroEvaluationScope) -> [String] {
var compileToSwiftFileTypes : [String] = []
for groupingStragegyExtensions in extensions(of: InputFileGroupingStrategyExtensionPoint.self) {
compileToSwiftFileTypes.append(contentsOf: groupingStragegyExtensions.fileTypesCompilingToSwiftSources())
compileToSwiftFileTypes.append(contentsOf: groupingStragegyExtensions.fileTypesCompilingToSwiftSources(scope: scope))
}
return compileToSwiftFileTypes
}
Expand Down Expand Up @@ -614,7 +614,7 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
// Reorder resolvedBuildFiles so that file types which compile to Swift appear first in the list and so are processed first.
// This is needed because generated sources aren't added to the the main source code list.
// rdar://102834701 (File grouping for 'collection groups' is sensitive to ordering of build phase members)
let compileToSwiftFileTypes = context.workspaceContext.core.pluginManager.fileTypesProducingGeneratedSources()
let compileToSwiftFileTypes = context.workspaceContext.core.pluginManager.fileTypesProducingGeneratedSources(scope: scope)
var compileToSwiftFiles = [ResolvedBuildFile]()
var otherBuildFiles = [ResolvedBuildFile]()
for resolvedBuildFile in resolvedBuildFiles {
Expand Down Expand Up @@ -792,7 +792,7 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
return []
}

let fileIdentifiersGeneratingSources = context.workspaceContext.core.pluginManager.fileTypesProducingGeneratedSources()
let fileIdentifiersGeneratingSources = context.workspaceContext.core.pluginManager.fileTypesProducingGeneratedSources(scope: scope)
guard !fileIdentifiersGeneratingSources.isEmpty else {
return []
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -528,4 +528,75 @@ fileprivate struct AssetCatalogTaskConstructionTests: CoreBasedTests {
}
}
}

/// When asset symbol generation is disabled, the asset catalog must stay in the Resources phase
/// and not be moved to Sources. This matters for projects where the xcassets is generated by a
/// preceding script phase and doesn't exist during Sources processing.
@Test(.requireSDKs(.macOS))
func disabledAssetSymbolGeneration() async throws {
let swiftFeatures = try await self.swiftFeatures

let testProject = try await TestProject(
"Project",
groupTree: TestGroup(
"ProjectSources",
path: "Sources",
children: [
TestFile("App.swift"),
TestFile("Assets.xcassets"),
]
),
buildConfigurations: [
TestBuildConfiguration("Debug", buildSettings: [
"PRODUCT_NAME": "$(TARGET_NAME)",
"ASSETCATALOG_EXEC": actoolPath.str,
])
],
targets: [
TestStandardTarget(
"App",
type: .framework,
buildConfigurations: [
TestBuildConfiguration("Debug", buildSettings: [
"SKIP_INSTALL": "YES",
"SWIFT_EXEC": swiftCompilerPath.str,
"SWIFT_VERSION": "5.5",
"GENERATE_INFOPLIST_FILE": "YES",
"ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS": "NO",
// Disable phase fusion so Sources and Resources have separate gate
// tasks, allowing us to verify the xcassets file stays in Resources.
"FUSE_BUILD_PHASES": "NO",
]),
],
buildPhases: [
TestSourcesBuildPhase([
"App.swift",
]),
TestResourcesBuildPhase([
"Assets.xcassets",
]),
]
)
]
)

let tester = try await TaskConstructionTester(getCore(), testProject)

await tester.checkBuild(runDestination: .macOS) { results in
results.checkNoDiagnostics()

results.checkTarget("App") { target in
results.checkNoTask(.matchTarget(target), .matchRuleType("GenerateAssetSymbols"))

// The asset catalog must stay in the Resources phase when symbol generation is
// disabled. Verify by checking that asset catalog compilation follows Swift
// compilation through the phase gate (Resources runs after Sources).
let targetArchitecture = results.runDestinationTargetArchitecture
let swiftRuleType = swiftFeatures.has(.emitLocalizedStrings) ? "SwiftDriver Compilation" : "CompileSwiftSources"
results.checkTask(.matchTarget(target), .matchRuleType("CompileAssetCatalogVariant"), .matchRuleItem("thinned")) { compileAssetCatalogTask in
results.checkTaskFollows(compileAssetCatalogTask, .matchTarget(target), .matchRuleType(swiftRuleType), .matchRuleItem("normal"), .matchRuleItem(targetArchitecture))
}
}
}
}
}
91 changes: 91 additions & 0 deletions Tests/SWBTaskConstructionTests/XCStringsSymbolGenTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1070,4 +1070,95 @@ fileprivate struct XCStringsSymbolGenTests: CoreBasedTests {
}
}

/// When string catalog symbol generation is disabled, the xcstrings file must stay in the Resources
/// phase and not be moved to Sources.
@Test(.requireSDKs(.macOS))
func disabledSymbolGeneration() async throws {
let swiftFeatures = try await self.swiftFeatures

let testProject = try await TestProject(
"Project",
groupTree: TestGroup(
"ProjectSources",
path: "Sources",
children: [
TestFile("MyFramework.swift"),
TestFile("Localizable.xcstrings"),
]
),
buildConfigurations: [
TestBuildConfiguration("Debug", buildSettings: [
"PRODUCT_NAME": "$(TARGET_NAME)",
])
],
targets: [
TestStandardTarget(
"MyFramework",
type: .framework,
buildConfigurations: [
TestBuildConfiguration("Debug", buildSettings: [
"SKIP_INSTALL": "YES",
"SWIFT_EXEC": swiftCompilerPath.str,
"SWIFT_VERSION": "5.5",
"GENERATE_INFOPLIST_FILE": "YES",
"STRING_CATALOG_GENERATE_SYMBOLS": "NO",
// Disable phase fusion so Sources and Resources have separate gate
// tasks, allowing us to verify the xcstrings file stays in Resources.
"FUSE_BUILD_PHASES": "NO",
]),
],
buildPhases: [
TestSourcesBuildPhase([
"MyFramework.swift"
]),
TestResourcesBuildPhase([
"Localizable.xcstrings"
])
]
)
],
developmentRegion: "en"
)

let core = try await getCore()
let xcstringsTool = MockXCStringsTool(hostOS: core.hostOperatingSystem, relativeOutputFilePaths: [ "/tmp/Test/Project/Sources/Localizable.xcstrings" : [
"en.lproj/Localizable.strings",
"en.lproj/Localizable.stringsdict",
"de.lproj/Localizable.strings",
"de.lproj/Localizable.stringsdict",
]], requiredCommandLine: [
"xcstringstool", "compile",
"--dry-run",
"--output-directory", "/tmp/Test/Project/build/Project.build/Debug/MyFramework.build",
"/tmp/Test/Project/Sources/Localizable.xcstrings"
])

let tester = try TaskConstructionTester(core, testProject)

await tester.checkBuild(runDestination: .macOS, clientDelegate: xcstringsTool) { results in
results.checkNoDiagnostics()

results.checkTarget("MyFramework") { target in
results.checkNoTask(.matchTarget(target), .matchRuleType("GenerateStringSymbols"))
results.checkNoTask(.matchTarget(target), .matchRuleType("CpResource"))

results.checkTask(.matchTarget(target), .matchRule(["CopyStringsFile", "/tmp/Test/Project/build/Debug/MyFramework.framework/Versions/A/Resources/en.lproj/Localizable.strings", "/tmp/Test/Project/build/Project.build/Debug/MyFramework.build/en.lproj/Localizable.strings"])) { _ in }
results.checkTask(.matchTarget(target), .matchRule(["CopyStringsFile", "/tmp/Test/Project/build/Debug/MyFramework.framework/Versions/A/Resources/en.lproj/Localizable.stringsdict", "/tmp/Test/Project/build/Project.build/Debug/MyFramework.build/en.lproj/Localizable.stringsdict"])) { _ in }
results.checkTask(.matchTarget(target), .matchRule(["CopyStringsFile", "/tmp/Test/Project/build/Debug/MyFramework.framework/Versions/A/Resources/de.lproj/Localizable.strings", "/tmp/Test/Project/build/Project.build/Debug/MyFramework.build/de.lproj/Localizable.strings"])) { _ in }
results.checkTask(.matchTarget(target), .matchRule(["CopyStringsFile", "/tmp/Test/Project/build/Debug/MyFramework.framework/Versions/A/Resources/de.lproj/Localizable.stringsdict", "/tmp/Test/Project/build/Project.build/Debug/MyFramework.build/de.lproj/Localizable.stringsdict"])) { _ in }

results.checkNoTask(.matchTarget(target), .matchRuleType("CopyStringsFile"))

// The xcstrings file must stay in the Resources phase when symbol generation is
// disabled. Verify by checking that xcstrings compilation follows Swift compilation
// through the phase gate (Resources runs after Sources).
let targetArchitecture = results.runDestinationTargetArchitecture
let swiftRuleType = swiftFeatures.has(.emitLocalizedStrings) ? "SwiftDriver Compilation" : "CompileSwiftSources"
results.checkTask(.matchTarget(target), .matchRuleType("CompileXCStrings")) { compileXCStringsTask in
results.checkTaskFollows(compileXCStringsTask, .matchTarget(target), .matchRuleType(swiftRuleType), .matchRuleItem("normal"), .matchRuleItem(targetArchitecture))
}
}
}
}

}
Loading