@@ -84,7 +84,12 @@ package final class BuildFilesProcessingContext: BuildFileFilteringContext {
84
84
let belongsToPreferredArch : Bool
85
85
let currentArchSpec : ArchitectureSpec ?
86
86
87
- package init ( _ scope: MacroEvaluationScope , belongsToPreferredArch: Bool = true , currentArchSpec: ArchitectureSpec ? = nil , resolveBuildRules: Bool = true , resourcesDir: Path ? = nil , tmpResourcesDir: Path ? = nil ) {
87
+ /// If `true`, avoid emitting any diagnostics via the task producer context.
88
+ ///
89
+ /// This might be set in cases where `BuildFilesProcessingContext` is being used for ephemeral grouping operations outside of the main grouping routine.
90
+ private let repressDiagnostics : Bool
91
+
92
+ package init ( _ scope: MacroEvaluationScope , belongsToPreferredArch: Bool = true , currentArchSpec: ArchitectureSpec ? = nil , resolveBuildRules: Bool = true , resourcesDir: Path ? = nil , tmpResourcesDir: Path ? = nil , repressDiagnostics: Bool = false ) {
88
93
// Define the predicates for filtering source files.
89
94
//
90
95
// FIXME: Factor this out, and make this machinery efficient.
@@ -97,6 +102,7 @@ package final class BuildFilesProcessingContext: BuildFileFilteringContext {
97
102
self . belongsToPreferredArch = belongsToPreferredArch
98
103
self . currentArchSpec = currentArchSpec
99
104
self . currentPlatformFilter = PlatformFilter ( scope)
105
+ self . repressDiagnostics = repressDiagnostics
100
106
}
101
107
102
108
/// Adds the file to build to the appropriate group for the task producer being processed, including resolving a build rule action for that group if appropriate.
@@ -113,8 +119,10 @@ package final class BuildFilesProcessingContext: BuildFileFilteringContext {
113
119
let buildRuleMatchResult = taskProducerContext. buildRuleSet. match ( ftb, scope)
114
120
let provisionalRuleAction = buildRuleMatchResult. action
115
121
116
- for diagnostic in buildRuleMatchResult. diagnostics {
117
- taskProducerContext. emit ( diagnostic. behavior, diagnostic. message)
122
+ if !repressDiagnostics {
123
+ for diagnostic in buildRuleMatchResult. diagnostics {
124
+ taskProducerContext. emit ( diagnostic. behavior, diagnostic. message)
125
+ }
118
126
}
119
127
120
128
// If this file is the output of some task, then we perform some checks to see whether we should process it.
@@ -125,8 +133,10 @@ package final class BuildFilesProcessingContext: BuildFileFilteringContext {
125
133
if generatedByBuildRuleAction === provisionalRuleAction {
126
134
// If we should not add if we didn't find an appropriate build rule, then emit a warning and return.
127
135
guard addIfNoBuildRuleFound else {
128
- let currentArch = scope. evaluate ( BuiltinMacros . CURRENT_ARCH)
129
- taskProducerContext. warning ( " no rule to process file ' \( ftb. absolutePath. str) ' of type ' \( ftb. fileType. identifier) ' " + ( currentArch != " undefined_arch " ? " for architecture ' \( scope. evaluate ( BuiltinMacros . CURRENT_ARCH) ) ' " : " " ) )
136
+ if !repressDiagnostics {
137
+ let currentArch = scope. evaluate ( BuiltinMacros . CURRENT_ARCH)
138
+ taskProducerContext. warning ( " no rule to process file ' \( ftb. absolutePath. str) ' of type ' \( ftb. fileType. identifier) ' " + ( currentArch != " undefined_arch " ? " for architecture ' \( scope. evaluate ( BuiltinMacros . CURRENT_ARCH) ) ' " : " " ) )
139
+ }
130
140
return
131
141
}
132
142
// If we should always add, then do so as an ungrouped file.
@@ -183,7 +193,10 @@ package final class BuildFilesProcessingContext: BuildFileFilteringContext {
183
193
184
194
// If we've already processed a group with this identifier, then emit an error to the user as this is likely a project configuration error. For example, if a project is generating code (e.g. from a build rule) and multiple versions of the same file are being generated, and thus being processed, this is potentially very bad, especially if those files don't contain the same output!
185
195
guard !processedGroupIdents. contains ( groupIdent) else {
186
- return taskProducerContext. error ( " the file group with identifier ' \( groupIdent) ' has already been processed. " )
196
+ if !repressDiagnostics {
197
+ taskProducerContext. error ( " the file group with identifier ' \( groupIdent) ' has already been processed. " )
198
+ }
199
+ return
187
200
}
188
201
189
202
// Find or create the group for the identifier we got back.
@@ -214,30 +227,43 @@ package final class BuildFilesProcessingContext: BuildFileFilteringContext {
214
227
215
228
/// Allow `collectionGroups` to subsume `singletonGroups`. The initial pass in groupAndAddTasksForFiles looks at one file at a time to assign rules and groups. Certain grouping strategies need to inspect multiple files to group them (e.g. sticker packs need to group an asset catalog and loose strings files matching the sticker pack - without grouping every other strings file as well). This function allows each collectionGroup to inspect
216
229
fileprivate func mergeGroups( _ context: TaskProducerContext ) {
230
+ let allGroupedSingletonGroups = subsumeAdditionalFilesIfDesired ( from: self . singletonGroups, context)
231
+
232
+ if !allGroupedSingletonGroups. isEmpty {
233
+ self . singletonGroups = Queue ( self . singletonGroups. filter { !allGroupedSingletonGroups. contains ( $0) } )
234
+ }
235
+ }
236
+
237
+ /// Allow `collectionGroups` to subsume `filesToSubsume` if desired.
238
+ ///
239
+ /// There is no guarantee that all or even any of the files in `filesToSubsume` will actually be subsumed.
240
+ ///
241
+ /// This method will never add additional groups. It can only add to existing ones.
242
+ ///
243
+ /// - returns: The files that were subsumed.
244
+ fileprivate func subsumeAdditionalFilesIfDesired( from filesToSubsume: some Sequence < FileToBuildGroup > , _ context: TaskProducerContext ) -> Set < FileToBuildGroup > {
217
245
// This assumes that groupAdditionalFiles() rarely chooses to group anything.
218
246
219
- var allGroupedSingletonGroups = Set < FileToBuildGroup > ( )
247
+ var allSubsumedGroups = Set < FileToBuildGroup > ( )
220
248
for collectionGroup in collectionGroups {
221
249
if let rule = collectionGroup. assignedBuildRuleAction {
222
250
for grouper in rule. inputFileGroupingStrategies {
223
- let groupedSingletonGroups = grouper. groupAdditionalFiles ( to: collectionGroup, from: self . singletonGroups , context: context)
251
+ let subsumedGroups = grouper. groupAdditionalFiles ( to: collectionGroup, from: filesToSubsume , context: context)
224
252
225
- for group in groupedSingletonGroups {
253
+ for group in subsumedGroups {
226
254
collectionGroup. files. append ( contentsOf: group. files)
227
255
228
- if allGroupedSingletonGroups . contains ( group) {
256
+ if !repressDiagnostics && allSubsumedGroups . contains ( group) {
229
257
context. error ( " Multiple rules merged: \( group. files [ 0 ] . absolutePath) " )
230
258
}
231
259
}
232
260
233
- allGroupedSingletonGroups . formUnion ( groupedSingletonGroups )
261
+ allSubsumedGroups . formUnion ( subsumedGroups )
234
262
}
235
263
}
236
264
}
237
265
238
- if !allGroupedSingletonGroups. isEmpty {
239
- self . singletonGroups = Queue ( self . singletonGroups. filter { !allGroupedSingletonGroups. contains ( $0) } )
240
- }
266
+ return allSubsumedGroups
241
267
}
242
268
243
269
// Returns the next file group to process, or nil if all groups have been processed.
@@ -295,6 +321,19 @@ extension TaskProducerContext {
295
321
}
296
322
}
297
323
324
+ extension PluginManager {
325
+ /// 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).
326
+ ///
327
+ /// Asset Catalogs would be one example of this, so that they can generate symbols.
328
+ func fileTypesProducingGeneratedSources( ) -> [ String ] {
329
+ var compileToSwiftFileTypes : [ String ] = [ ]
330
+ for groupingStragegyExtensions in extensions ( of: InputFileGroupingStrategyExtensionPoint . self) {
331
+ compileToSwiftFileTypes. append ( contentsOf: groupingStragegyExtensions. fileTypesCompilingToSwiftSources ( ) )
332
+ }
333
+ return compileToSwiftFileTypes
334
+ }
335
+ }
336
+
298
337
// MARK:
299
338
300
339
@@ -352,7 +391,12 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
352
391
}
353
392
354
393
/// Allows subclasses to contribute additional build files.
355
- func additionalBuildFiles( _ scope: MacroEvaluationScope ) -> [ SWBCore . BuildFile ] {
394
+ func additionalBuildFiles( _ scope: MacroEvaluationScope ) async -> [ SWBCore . BuildFile ] {
395
+ return [ ]
396
+ }
397
+
398
+ /// Allows subclasses to specify build files that should be skipped by this task producer.
399
+ func buildFilesToSkip( _ scope: MacroEvaluationScope ) async -> Set < Ref < SWBCore . BuildFile > > {
356
400
return [ ]
357
401
}
358
402
@@ -376,14 +420,6 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
376
420
377
421
let buildPhaseFileWarningContext = BuildPhaseFileWarningContext ( context, scope)
378
422
379
- // Sadly we need to make various decisions based on codegen of Asset and String Catalogs.
380
- // We can remove this when we get rid of build phases.
381
- let sourceFileCount = ( self . targetContext. configuredTarget? . target as? SWBCore . StandardTarget) ? . sourcesBuildPhase? . buildFiles. count ?? 0
382
- let stringsFileTypes = [ " text.plist.strings " , " text.plist.stringsdict " ] . map { context. lookupFileType ( identifier: $0) ! }
383
- var xcstringsBases = Set < String > ( )
384
- let shouldCodeGenAssets = scope. evaluate ( BuiltinMacros . ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS) && sourceFileCount > 0
385
- let shouldCodeGenStrings = scope. evaluate ( BuiltinMacros . STRING_CATALOG_GENERATE_SYMBOLS) && sourceFileCount > 0
386
-
387
423
// Helper function for adding a resolved item. The build file can be nil here if the client wants to add a file divorced from any build file (e.g., because the build file contains context which shouldn't be applied to this file).
388
424
func addResolvedItem( buildFile: SWBCore . BuildFile ? , path: Path , reference: SWBCore . Reference ? , fileType: FileTypeSpec , shouldUsePrefixHeader: Bool = true ) {
389
425
let base = path. basenameWithoutSuffix. lowercased ( )
@@ -408,26 +444,16 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
408
444
addResolvedItem ( buildFile: nil , path: path, reference: nil , fileType: fileType, shouldUsePrefixHeader: shouldUsePrefixHeader)
409
445
}
410
446
411
- for buildFile in buildPhase. buildFiles + additionalBuildFiles( scope) {
447
+ let buildFilesToSkip = await self . buildFilesToSkip ( scope)
448
+ for buildFile in await buildPhase. buildFiles + additionalBuildFiles( scope) {
449
+ guard !buildFilesToSkip. contains ( Ref ( buildFile) ) else {
450
+ continue
451
+ }
452
+
412
453
// Resolve the reference.
413
454
do {
414
455
let ( reference, path, fileType) = try context. resolveBuildFileReference ( buildFile)
415
456
416
- if shouldCodeGenAssets {
417
- // Ignore xcassets in Resource Copy Phase since they're now added to the Compile Sources phase for codegen.
418
- if producer. buildPhase is SWBCore . ResourcesBuildPhase && fileType. conformsTo ( identifier: " folder.abstractassetcatalog " ) {
419
- continue
420
- }
421
- }
422
- if shouldCodeGenStrings {
423
- // Ignore xcstrings in Resource Copy Phase since they're now added to the Compile Sources phase for codegen.
424
- if producer. buildPhase is SWBCore . ResourcesBuildPhase && fileType. conformsTo ( identifier: " text.json.xcstrings " ) {
425
- // Keep the basename because later we need to ignore same-named .strings/dict files as well.
426
- xcstringsBases. insert ( path. basenameWithoutSuffix)
427
- continue
428
- }
429
- }
430
-
431
457
// Compilation of .rkassets depends on additional auxiliary inputs that are not
432
458
// accessible from a spec class. Instead, they are handled entirely by their own
433
459
// task producer (RealityAssetsTaskProducer), so skip processing them as part of
@@ -585,14 +611,10 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
585
611
}
586
612
}
587
613
588
- var compileToSwiftFileTypes : [ String ] = [ ]
589
- for groupingStragegyExtensions in await context. workspaceContext. core. pluginManager. extensions ( of: InputFileGroupingStrategyExtensionPoint . self) {
590
- compileToSwiftFileTypes. append ( contentsOf: groupingStragegyExtensions. fileTypesCompilingToSwiftSources ( ) )
591
- }
592
-
593
614
// Reorder resolvedBuildFiles so that file types which compile to Swift appear first in the list and so are processed first.
594
615
// This is needed because generated sources aren't added to the the main source code list.
595
616
// rdar://102834701 (File grouping for 'collection groups' is sensitive to ordering of build phase members)
617
+ let compileToSwiftFileTypes = await context. workspaceContext. core. pluginManager. fileTypesProducingGeneratedSources ( )
596
618
var compileToSwiftFiles = [ ResolvedBuildFile] ( )
597
619
var otherBuildFiles = [ ResolvedBuildFile] ( )
598
620
for resolvedBuildFile in resolvedBuildFiles {
@@ -645,14 +667,6 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
645
667
continue
646
668
}
647
669
648
- // Ignore certain .strings/dict files in Resources phase when codegen for xcstrings is enabled.
649
- if shouldCodeGenStrings &&
650
- producer. buildPhase is SWBCore . ResourcesBuildPhase &&
651
- fileType. conformsToAny ( stringsFileTypes) &&
652
- xcstringsBases. contains ( path. basenameWithoutSuffix) {
653
- continue
654
- }
655
-
656
670
// Have the build files context add the file to the appropriate file group.
657
671
buildFilesContext. addFile ( fileToBuild, context, scope)
658
672
}
@@ -770,6 +784,52 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
770
784
}
771
785
}
772
786
787
+ /// Filters `buildFiles` down to only those files that are necessary inputs to source code generation.
788
+ ///
789
+ /// For example, this could include Asset Catalogs (and any of the files they subsume in their grouping strategy).
790
+ func sourceGenerationInputFiles( from buildFiles: [ SWBCore . BuildFile ] , scope: MacroEvaluationScope ) async -> [ SWBCore . BuildFile ] {
791
+ guard !buildFiles. isEmpty else {
792
+ return [ ]
793
+ }
794
+
795
+ let fileIdentifiersGeneratingSources = await context. workspaceContext. core. pluginManager. fileTypesProducingGeneratedSources ( )
796
+ guard !fileIdentifiersGeneratingSources. isEmpty else {
797
+ return [ ]
798
+ }
799
+
800
+ var grouper : BuildFilesProcessingContext ?
801
+ var ungroupedFiles = [ FileToBuild] ( )
802
+ for buildFile in buildFiles {
803
+ guard let fileRef = try ? targetContext. resolveBuildFileReference ( buildFile) else {
804
+ continue
805
+ }
806
+
807
+ let ftb = FileToBuild ( absolutePath: fileRef. absolutePath, fileType: fileRef. fileType, buildFile: buildFile, regionVariantName: fileRef. absolutePath. regionVariantName)
808
+
809
+ guard fileIdentifiersGeneratingSources. contains ( where: { identifier in fileRef. fileType. conformsTo ( identifier: identifier) } ) else {
810
+ ungroupedFiles. append ( ftb)
811
+ continue
812
+ }
813
+
814
+ if grouper == nil {
815
+ grouper = BuildFilesProcessingContext ( scope, repressDiagnostics: true )
816
+ }
817
+
818
+ grouper? . addFile ( ftb, context, scope)
819
+ }
820
+
821
+ guard let grouper else {
822
+ return [ ]
823
+ }
824
+
825
+ let remainingFiles = ungroupedFiles. map { ftb in
826
+ FileToBuildGroup ( ftb. absolutePath. str, files: [ ftb] , action: nil )
827
+ }
828
+ _ = grouper. subsumeAdditionalFilesIfDesired ( from: remainingFiles, context)
829
+
830
+ return grouper. collectionGroups. flatMap ( \. files) . compactMap ( \. buildFile)
831
+ }
832
+
773
833
/// This method is used by the `installLoc` build action to return the paths to localized content within a bundle.
774
834
/// For example a bundle which is part of the project sources, where only the localized content in the bundle should be copied in the `installLoc` action.
775
835
/// - returns: A list of path strings relative to the absolute path of `ftb`.
0 commit comments