@@ -84,7 +84,12 @@ package final class BuildFilesProcessingContext: BuildFileFilteringContext {
8484 let belongsToPreferredArch : Bool
8585 let currentArchSpec : ArchitectureSpec ?
8686
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 ) {
8893 // Define the predicates for filtering source files.
8994 //
9095 // FIXME: Factor this out, and make this machinery efficient.
@@ -97,6 +102,7 @@ package final class BuildFilesProcessingContext: BuildFileFilteringContext {
97102 self . belongsToPreferredArch = belongsToPreferredArch
98103 self . currentArchSpec = currentArchSpec
99104 self . currentPlatformFilter = PlatformFilter ( scope)
105+ self . repressDiagnostics = repressDiagnostics
100106 }
101107
102108 /// 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 {
113119 let buildRuleMatchResult = taskProducerContext. buildRuleSet. match ( ftb, scope)
114120 let provisionalRuleAction = buildRuleMatchResult. action
115121
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+ }
118126 }
119127
120128 // 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 {
125133 if generatedByBuildRuleAction === provisionalRuleAction {
126134 // If we should not add if we didn't find an appropriate build rule, then emit a warning and return.
127135 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+ }
130140 return
131141 }
132142 // If we should always add, then do so as an ungrouped file.
@@ -183,7 +193,10 @@ package final class BuildFilesProcessingContext: BuildFileFilteringContext {
183193
184194 // 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!
185195 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
187200 }
188201
189202 // Find or create the group for the identifier we got back.
@@ -214,30 +227,43 @@ package final class BuildFilesProcessingContext: BuildFileFilteringContext {
214227
215228 /// 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
216229 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 > {
217245 // This assumes that groupAdditionalFiles() rarely chooses to group anything.
218246
219- var allGroupedSingletonGroups = Set < FileToBuildGroup > ( )
247+ var allSubsumedGroups = Set < FileToBuildGroup > ( )
220248 for collectionGroup in collectionGroups {
221249 if let rule = collectionGroup. assignedBuildRuleAction {
222250 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)
224252
225- for group in groupedSingletonGroups {
253+ for group in subsumedGroups {
226254 collectionGroup. files. append ( contentsOf: group. files)
227255
228- if allGroupedSingletonGroups . contains ( group) {
256+ if !repressDiagnostics && allSubsumedGroups . contains ( group) {
229257 context. error ( " Multiple rules merged: \( group. files [ 0 ] . absolutePath) " )
230258 }
231259 }
232260
233- allGroupedSingletonGroups . formUnion ( groupedSingletonGroups )
261+ allSubsumedGroups . formUnion ( subsumedGroups )
234262 }
235263 }
236264 }
237265
238- if !allGroupedSingletonGroups. isEmpty {
239- self . singletonGroups = Queue ( self . singletonGroups. filter { !allGroupedSingletonGroups. contains ( $0) } )
240- }
266+ return allSubsumedGroups
241267 }
242268
243269 // Returns the next file group to process, or nil if all groups have been processed.
@@ -295,6 +321,19 @@ extension TaskProducerContext {
295321 }
296322}
297323
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+
298337// MARK:
299338
300339
@@ -352,7 +391,12 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
352391 }
353392
354393 /// 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 > > {
356400 return [ ]
357401 }
358402
@@ -376,14 +420,6 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
376420
377421 let buildPhaseFileWarningContext = BuildPhaseFileWarningContext ( context, scope)
378422
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-
387423 // 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).
388424 func addResolvedItem( buildFile: SWBCore . BuildFile ? , path: Path , reference: SWBCore . Reference ? , fileType: FileTypeSpec , shouldUsePrefixHeader: Bool = true ) {
389425 let base = path. basenameWithoutSuffix. lowercased ( )
@@ -408,26 +444,16 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
408444 addResolvedItem ( buildFile: nil , path: path, reference: nil , fileType: fileType, shouldUsePrefixHeader: shouldUsePrefixHeader)
409445 }
410446
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+
412453 // Resolve the reference.
413454 do {
414455 let ( reference, path, fileType) = try context. resolveBuildFileReference ( buildFile)
415456
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-
431457 // Compilation of .rkassets depends on additional auxiliary inputs that are not
432458 // accessible from a spec class. Instead, they are handled entirely by their own
433459 // task producer (RealityAssetsTaskProducer), so skip processing them as part of
@@ -585,14 +611,10 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
585611 }
586612 }
587613
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-
593614 // Reorder resolvedBuildFiles so that file types which compile to Swift appear first in the list and so are processed first.
594615 // This is needed because generated sources aren't added to the the main source code list.
595616 // rdar://102834701 (File grouping for 'collection groups' is sensitive to ordering of build phase members)
617+ let compileToSwiftFileTypes = await context. workspaceContext. core. pluginManager. fileTypesProducingGeneratedSources ( )
596618 var compileToSwiftFiles = [ ResolvedBuildFile] ( )
597619 var otherBuildFiles = [ ResolvedBuildFile] ( )
598620 for resolvedBuildFile in resolvedBuildFiles {
@@ -645,14 +667,6 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
645667 continue
646668 }
647669
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-
656670 // Have the build files context add the file to the appropriate file group.
657671 buildFilesContext. addFile ( fileToBuild, context, scope)
658672 }
@@ -770,6 +784,52 @@ package class FilesBasedBuildPhaseTaskProducerBase: PhasedTaskProducer {
770784 }
771785 }
772786
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+
773833 /// This method is used by the `installLoc` build action to return the paths to localized content within a bundle.
774834 /// 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.
775835 /// - returns: A list of path strings relative to the absolute path of `ftb`.
0 commit comments