Skip to content

Commit ab3abb2

Browse files
authored
Merge pull request #605 from swiftlang/automerge/merge-main-2025-06-23_09-03
Merge `release/6.2` into `main`
2 parents d2b22f0 + 4e914cb commit ab3abb2

File tree

18 files changed

+1714
-30
lines changed

18 files changed

+1714
-30
lines changed

Sources/SWBApplePlatform/Plugin.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ struct XCStringsInputFileGroupingStrategyExtension: InputFileGroupingStrategyExt
202202
}
203203

204204
func fileTypesCompilingToSwiftSources() -> [String] {
205-
return []
205+
return ["text.json.xcstrings"]
206206
}
207207
}
208208

Sources/SWBApplePlatform/XCStringsCompiler.swift

Lines changed: 166 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import SWBUtil
14+
import SWBMacro
1415
public import SWBCore
1516
import Foundation
1617

@@ -47,6 +48,147 @@ public final class XCStringsCompilerSpec: GenericCompilerSpec, SpecIdentifierTyp
4748
return
4849
}
4950

51+
if shouldGenerateSymbols(cbc) {
52+
constructSymbolGenerationTask(cbc, delegate)
53+
}
54+
55+
if shouldCompileCatalog(cbc) {
56+
await constructCatalogCompilationTask(cbc, delegate)
57+
}
58+
}
59+
60+
public override var supportsInstallHeaders: Bool {
61+
// Yes but we will only perform symbol generation in that case.
62+
return true
63+
}
64+
65+
public override var supportsInstallAPI: Bool {
66+
// Yes but we will only perform symbol generation in that case.
67+
// This matches Asset Catalog symbol generation in order to workaround an issue with header whitespace.
68+
// rdar://106447203 (Symbols: Enabling symbols for IB causes installapi failure)
69+
return true
70+
}
71+
72+
/// Whether we should generate tasks to generate code symbols for strings.
73+
private func shouldGenerateSymbols(_ cbc: CommandBuildContext) -> Bool {
74+
guard cbc.scope.evaluate(BuiltinMacros.STRING_CATALOG_GENERATE_SYMBOLS) else {
75+
return false
76+
}
77+
78+
// Yes for standard builds/installs as well as headers/api and exportloc (which includes headers).
79+
// No for installloc.
80+
let buildComponents = cbc.scope.evaluate(BuiltinMacros.BUILD_COMPONENTS)
81+
guard buildComponents.contains("build") || buildComponents.contains("headers") || buildComponents.contains("api") else {
82+
return false
83+
}
84+
85+
// Avoid symbol generation for xcstrings inside variant groups because that implies association with a resource such as a xib.
86+
guard cbc.input.regionVariantName == nil else {
87+
return false
88+
}
89+
90+
// We are only supporting Swift symbols at the moment so don't even generate the task if there are not Swift sources.
91+
// If this is a synthesized Package resource target, we won't have Swift sources either.
92+
// That's good since the symbol gen will happen for the code target instead.
93+
let targetContainsSwiftSources = (cbc.producer.configuredTarget?.target as? StandardTarget)?.sourcesBuildPhase?.containsSwiftSources(cbc.producer, cbc.producer, cbc.scope, cbc.producer.filePathResolver) ?? false
94+
guard targetContainsSwiftSources else {
95+
return false
96+
}
97+
98+
return true
99+
}
100+
101+
/// Whether we should generate tasks to compile the .xcstrings file to .strings/dict files.
102+
private func shouldCompileCatalog(_ cbc: CommandBuildContext) -> Bool {
103+
// Yes for standard builds/installs and installloc.
104+
// No for exportloc and headers/api.
105+
let buildComponents = cbc.scope.evaluate(BuiltinMacros.BUILD_COMPONENTS)
106+
guard buildComponents.contains("build") || buildComponents.contains("installLoc") else {
107+
return false
108+
}
109+
110+
// If this is a Package target with a synthesized resource target, compile the catalog with the resources instead of here.
111+
let isMainPackageWithResourceBundle = !cbc.scope.evaluate(BuiltinMacros.PACKAGE_RESOURCE_BUNDLE_NAME).isEmpty
112+
return !isMainPackageWithResourceBundle
113+
}
114+
115+
private struct SymbolGenPayload: TaskPayload {
116+
117+
let effectivePlatformName: String
118+
119+
init(effectivePlatformName: String) {
120+
self.effectivePlatformName = effectivePlatformName
121+
}
122+
123+
func serialize<T>(to serializer: T) where T : SWBUtil.Serializer {
124+
serializer.serializeAggregate(1) {
125+
serializer.serialize(effectivePlatformName)
126+
}
127+
}
128+
129+
init(from deserializer: any SWBUtil.Deserializer) throws {
130+
try deserializer.beginAggregate(1)
131+
self.effectivePlatformName = try deserializer.deserialize()
132+
}
133+
134+
}
135+
136+
public override var payloadType: (any TaskPayload.Type)? {
137+
return SymbolGenPayload.self
138+
}
139+
140+
/// Generates a task for generating code symbols for strings.
141+
private func constructSymbolGenerationTask(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) {
142+
// The template spec file contains fields suitable for the compilation step.
143+
// But here we construct a custom command line for symbol generation.
144+
let execPath = resolveExecutablePath(cbc, Path("xcstringstool"))
145+
var commandLine = [execPath.str, "generate-symbols"]
146+
147+
// For now shouldGenerateSymbols only returns true if there are Swift sources.
148+
// So we only generate Swift symbols for now.
149+
commandLine.append(contentsOf: ["--language", "swift"])
150+
151+
let outputDir = cbc.scope.evaluate(BuiltinMacros.DERIVED_SOURCES_DIR)
152+
commandLine.append(contentsOf: ["--output-directory", outputDir.str])
153+
154+
// Input file
155+
let inputPath = cbc.input.absolutePath
156+
commandLine.append(inputPath.str)
157+
158+
let outputPaths = [
159+
"GeneratedStringSymbols_\(inputPath.basenameWithoutSuffix).swift"
160+
]
161+
.map { fileName in
162+
return outputDir.join(fileName)
163+
}
164+
165+
for output in outputPaths {
166+
delegate.declareOutput(FileToBuild(absolutePath: output, inferringTypeUsing: cbc.producer))
167+
}
168+
169+
// Use just first path for now since we're not even sure if we'll support languages beyond Swift.
170+
let ruleInfo = ["GenerateStringSymbols", outputPaths.first!.str, inputPath.str]
171+
let execDescription = "Generate symbols for \(inputPath.basename)"
172+
173+
let payload = SymbolGenPayload(effectivePlatformName: LocalizationBuildPortion.effectivePlatformName(scope: cbc.scope, sdkVariant: cbc.producer.sdkVariant))
174+
175+
delegate.createTask(
176+
type: self,
177+
payload: payload,
178+
ruleInfo: ruleInfo,
179+
commandLine: commandLine,
180+
environment: environmentFromSpec(cbc, delegate),
181+
workingDirectory: cbc.producer.defaultWorkingDirectory,
182+
inputs: [inputPath],
183+
outputs: outputPaths,
184+
execDescription: execDescription,
185+
preparesForIndexing: true,
186+
enableSandboxing: enableSandboxing
187+
)
188+
}
189+
190+
/// Generates a task for compiling the .xcstrings to .strings/dict files.
191+
private func constructCatalogCompilationTask(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async {
50192
let commandLine = await commandLineFromTemplate(cbc, delegate, optionContext: discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate)).map(\.asString)
51193

52194
// We can't know our precise outputs statically because we don't know what languages are in the xcstrings file,
@@ -75,7 +217,17 @@ public final class XCStringsCompilerSpec: GenericCompilerSpec, SpecIdentifierTyp
75217
}
76218

77219
if !outputs.isEmpty {
78-
delegate.createTask(type: self, ruleInfo: defaultRuleInfo(cbc, delegate), commandLine: commandLine, environment: environmentFromSpec(cbc, delegate), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: [cbc.input.absolutePath], outputs: outputs, execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing)
220+
delegate.createTask(
221+
type: self,
222+
ruleInfo: defaultRuleInfo(cbc, delegate),
223+
commandLine: commandLine,
224+
environment: environmentFromSpec(cbc, delegate),
225+
workingDirectory: cbc.producer.defaultWorkingDirectory,
226+
inputs: [cbc.input.absolutePath],
227+
outputs: outputs,
228+
execDescription: resolveExecutionDescription(cbc, delegate),
229+
enableSandboxing: enableSandboxing
230+
)
79231
} else {
80232
// If there won't be any outputs, there's no reason to run the compiler.
81233
// However, we still need to leave some indication in the build graph that there was a compilable xcstrings file here so that generateLocalizationInfo can discover it.
@@ -131,8 +283,7 @@ public final class XCStringsCompilerSpec: GenericCompilerSpec, SpecIdentifierTyp
131283
}
132284

133285
public override func generateLocalizationInfo(for task: any ExecutableTask, input: TaskGenerateLocalizationInfoInput) -> [TaskGenerateLocalizationInfoOutput] {
134-
// Tell the build system about the xcstrings file we took as input.
135-
// No need to use a TaskPayload for this because the only data we need is input path, which is already stored on the Task.
286+
// Tell the build system about the xcstrings file we took as input, as well as any generated symbol files.
136287

137288
// These asserts just check to make sure the broader implementation hasn't changed since we wrote this method,
138289
// in case something here would need to change.
@@ -142,7 +293,18 @@ public final class XCStringsCompilerSpec: GenericCompilerSpec, SpecIdentifierTyp
142293

143294
// Our input paths are .xcstrings (only expecting 1).
144295
// NOTE: We also take same-named .strings/dict files as input, but those are only used to diagnose errors and when they exist we fail before we ever generate the task.
145-
return [TaskGenerateLocalizationInfoOutput(compilableXCStringsPaths: task.inputPaths)]
296+
var infos = [TaskGenerateLocalizationInfoOutput(compilableXCStringsPaths: task.inputPaths)]
297+
298+
if let payload = task.payload as? SymbolGenPayload,
299+
let xcstringsPath = task.inputPaths.only {
300+
let generatedSourceFiles = task.outputPaths.filter { $0.fileExtension == "swift" }
301+
var info = TaskGenerateLocalizationInfoOutput()
302+
info.effectivePlatformName = payload.effectivePlatformName
303+
info.generatedSymbolFilesByXCStringsPath = [xcstringsPath: generatedSourceFiles]
304+
infos.append(info)
305+
}
306+
307+
return infos
146308
}
147309

148310
}

Sources/SWBBuildService/LocalizationInfo.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ struct LocalizationInfoOutput {
4141

4242
/// Paths to .stringsdata files produced by this target, grouped by build attributes such as platform and architecture.
4343
fileprivate(set) var producedStringsdataPaths: [LocalizationBuildPortion: Set<Path>] = [:]
44+
45+
/// The name of the primary platform we were building for.
46+
///
47+
/// Mac Catalyst is treated as its own platform.
48+
fileprivate(set) var effectivePlatformName: String?
49+
50+
/// Paths to generated source code files holding string symbols, keyed by xcstrings file path.
51+
fileprivate(set) var generatedSymbolFilesByXCStringsPath = [Path: Set<Path>]()
52+
4453
}
4554

4655
extension BuildDescriptionManager {
@@ -98,9 +107,24 @@ extension BuildDescription {
98107
.reduce([:], { aggregate, partial in aggregate.merging(partial, uniquingKeysWith: +) })
99108
.mapValues { Set($0) }
100109

110+
// Only really expecting to have one platform for a given build.
111+
// So just use the first seen one as primary.
112+
let effectivePlatformName = taskLocalizationOutputs.compactMap(\.effectivePlatformName).first
113+
101114
outputsByTarget[targetGUID, default: LocalizationInfoOutput(targetIdentifier: targetGUID)]
102115
.compilableXCStringsPaths.formUnion(taskXCStringsPaths)
103116
outputsByTarget[targetGUID]?.producedStringsdataPaths.merge(taskStringsdataPaths, uniquingKeysWith: { $0.union($1) })
117+
118+
if outputsByTarget[targetGUID]?.effectivePlatformName == nil && effectivePlatformName != nil {
119+
outputsByTarget[targetGUID]?.effectivePlatformName = effectivePlatformName
120+
}
121+
122+
let taskGeneratedSymbolFiles = taskLocalizationOutputs
123+
.map(\.generatedSymbolFilesByXCStringsPath)
124+
.reduce([:], { aggregate, partial in aggregate.merging(partial, uniquingKeysWith: +) })
125+
.mapValues { Set($0) }
126+
127+
outputsByTarget[targetGUID]?.generatedSymbolFilesByXCStringsPath.merge(taskGeneratedSymbolFiles, uniquingKeysWith: { $0.union($1) })
104128
}
105129

106130
return Array(outputsByTarget.values)

Sources/SWBBuildService/Messages.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,9 @@ private struct GetLocalizationInfoMsg: MessageHandler {
901901
for (buildPortion, paths) in infoOutput.producedStringsdataPaths {
902902
stringsdataPaths[LocalizationInfoBuildPortion(effectivePlatformName: buildPortion.effectivePlatformName, variant: buildPortion.variant, architecture: buildPortion.architecture)] = paths
903903
}
904-
return LocalizationInfoMessagePayload(targetIdentifier: infoOutput.targetIdentifier, compilableXCStringsPaths: infoOutput.compilableXCStringsPaths, producedStringsdataPaths: stringsdataPaths)
904+
var payload = LocalizationInfoMessagePayload(targetIdentifier: infoOutput.targetIdentifier, compilableXCStringsPaths: infoOutput.compilableXCStringsPaths, producedStringsdataPaths: stringsdataPaths, effectivePlatformName: infoOutput.effectivePlatformName)
905+
payload.generatedSymbolFilesByXCStringsPath = infoOutput.generatedSymbolFilesByXCStringsPath
906+
return payload
905907
}))
906908
return response
907909
} catch {

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,7 @@ public final class BuiltinMacros {
983983
public static let SHALLOW_BUNDLE = BuiltinMacros.declareBooleanMacro("SHALLOW_BUNDLE")
984984
public static let SHARED_FRAMEWORKS_FOLDER_PATH = BuiltinMacros.declarePathMacro("SHARED_FRAMEWORKS_FOLDER_PATH")
985985
public static let SHARED_SUPPORT_FOLDER_PATH = BuiltinMacros.declarePathMacro("SHARED_SUPPORT_FOLDER_PATH")
986+
public static let STRING_CATALOG_GENERATE_SYMBOLS = BuiltinMacros.declareBooleanMacro("STRING_CATALOG_GENERATE_SYMBOLS")
986987
public static let STRINGS_FILE_INPUT_ENCODING = BuiltinMacros.declareStringMacro("STRINGS_FILE_INPUT_ENCODING")
987988
public static let STRINGS_FILE_OUTPUT_ENCODING = BuiltinMacros.declareStringMacro("STRINGS_FILE_OUTPUT_ENCODING")
988989
public static let STRINGS_FILE_OUTPUT_FILENAME = BuiltinMacros.declareStringMacro("STRINGS_FILE_OUTPUT_FILENAME")
@@ -2142,6 +2143,7 @@ public final class BuiltinMacros {
21422143
SOURCE_ROOT,
21432144
SPECIALIZATION_SDK_OPTIONS,
21442145
SRCROOT,
2146+
STRING_CATALOG_GENERATE_SYMBOLS,
21452147
STRINGSDATA_DIR,
21462148
STRINGS_FILE_INPUT_ENCODING,
21472149
STRINGS_FILE_OUTPUT_ENCODING,

Sources/SWBCore/Settings/Settings.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4147,6 +4147,7 @@ private class SettingsBuilder {
41474147
if let project, project.isPackage, project.developmentRegion != nil {
41484148
table.push(BuiltinMacros.LOCALIZATION_EXPORT_SUPPORTED, literal: true)
41494149
table.push(BuiltinMacros.SWIFT_EMIT_LOC_STRINGS, literal: true)
4150+
table.push(BuiltinMacros.STRING_CATALOG_GENERATE_SYMBOLS, literal: true)
41504151
}
41514152

41524153
return table

Sources/SWBCore/TaskGeneration.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1161,12 +1161,21 @@ public struct TaskGenerateLocalizationInfoOutput {
11611161
/// Paths to .stringsdata files produced by this task, grouped by build attributes such as platform and architecture.
11621162
public let producedStringsdataPaths: [LocalizationBuildPortion: [Path]]
11631163

1164+
/// The name of the primary platform we were building for.
1165+
///
1166+
/// Mac Catalyst is treated as its own platform.
1167+
public var effectivePlatformName: String?
1168+
1169+
/// Paths to generated source code files holding string symbols, keyed by xcstrings file path.
1170+
public var generatedSymbolFilesByXCStringsPath = [Path: [Path]]()
1171+
11641172
/// Create output to describe some portion of localization info for a Task.
11651173
///
11661174
/// - Parameters:
11671175
/// - compilableXCStringsPaths: Paths to input source .xcstrings files.
11681176
/// - producedStringsdataPaths: Paths to output .stringsdata files.
1169-
public init(compilableXCStringsPaths: [Path] = [], producedStringsdataPaths: [LocalizationBuildPortion: [Path]] = [:]) {
1177+
public init(compilableXCStringsPaths: [Path] = [],
1178+
producedStringsdataPaths: [LocalizationBuildPortion: [Path]] = [:]) {
11701179
self.compilableXCStringsPaths = compilableXCStringsPaths
11711180
self.producedStringsdataPaths = producedStringsdataPaths
11721181
}

Sources/SWBProjectModel/PIFGenerationModel.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,11 @@ public struct SwiftBuildFileType: Sendable {
11301130
fileTypeIdentifier: "folder.abstractassetcatalog"
11311131
)
11321132

1133+
public static let xcstrings: SwiftBuildFileType = SwiftBuildFileType(
1134+
fileType: "xcstrings",
1135+
fileTypeIdentifier: "text.json.xcstrings"
1136+
)
1137+
11331138
public static let xcdatamodeld: SwiftBuildFileType = SwiftBuildFileType(
11341139
fileType: "xcdatamodeld",
11351140
fileTypeIdentifier: "wrapper.xcdatamodeld"
@@ -1167,6 +1172,7 @@ public struct SwiftBuildFileType: Sendable {
11671172

11681173
public static let all: [SwiftBuildFileType] = [
11691174
.xcassets,
1175+
.xcstrings,
11701176
.xcdatamodeld,
11711177
.xcdatamodel,
11721178
.xcmappingmodel,

Sources/SWBProtocol/MessageSupport.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,9 +545,21 @@ public struct LocalizationInfoMessagePayload: SerializableCodable, Equatable, Se
545545
/// Paths to .stringsdata files produced by this target, grouped by build attributes such as platform and architecture.
546546
public let producedStringsdataPaths: [LocalizationInfoBuildPortion: Set<Path>]
547547

548-
public init(targetIdentifier: String, compilableXCStringsPaths: Set<Path>, producedStringsdataPaths: [LocalizationInfoBuildPortion: Set<Path>]) {
548+
/// The name of the primary platform we were building for.
549+
///
550+
/// Mac Catalyst is treated as its own platform.
551+
public let effectivePlatformName: String?
552+
553+
/// Paths to generated source code files holding string symbols, keyed by xcstrings file path.
554+
public var generatedSymbolFilesByXCStringsPath = [Path: Set<Path>]()
555+
556+
public init(targetIdentifier: String,
557+
compilableXCStringsPaths: Set<Path>,
558+
producedStringsdataPaths: [LocalizationInfoBuildPortion: Set<Path>],
559+
effectivePlatformName: String?) {
549560
self.targetIdentifier = targetIdentifier
550561
self.compilableXCStringsPaths = compilableXCStringsPaths
551562
self.producedStringsdataPaths = producedStringsdataPaths
563+
self.effectivePlatformName = effectivePlatformName
552564
}
553565
}

0 commit comments

Comments
 (0)