Skip to content

Commit c0393be

Browse files
committed
Handle generated resources from plugins
Up until now, we were treating all generated files as Swift source files. With this, we are involving `TargetSourcesBuilder` in the decision of how to treat a certain file, provided the tools version is 5.7 or newer. Remaining limitations: - generated files aren't treated the same way as non-generated ones, e.g. we will treat anything unknown as a resource to be processed and we won't respect declarations from the manifest - files in Clang targets aren't treated at all at the moment rdar://89693335
1 parent e78566f commit c0393be

File tree

13 files changed

+241
-81
lines changed

13 files changed

+241
-81
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// swift-tools-version: 5.7
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "PluginGeneratedResources",
7+
targets: [
8+
.executableTarget(name: "PluginGeneratedResources", plugins: ["Generator"]),
9+
.plugin(name: "Generator", capability: .buildTool()),
10+
]
11+
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import PackagePlugin
2+
3+
@main
4+
struct GeneratorPlugin: BuildToolPlugin {
5+
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
6+
return [
7+
.prebuildCommand(
8+
displayName: "Generating empty file",
9+
executable: .init("/usr/bin/touch"),
10+
arguments: [context.pluginWorkDirectory.appending("best.txt")],
11+
outputFilesDirectory: context.pluginWorkDirectory
12+
)
13+
]
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Foundation
2+
3+
@main
4+
public struct PluginGeneratedResources {
5+
public private(set) var text = "Hello, World!"
6+
7+
public static func main() {
8+
let path = Bundle.module.path(forResource: "best", ofType: "txt")
9+
let exists = FileManager.default.fileExists(atPath: path!)
10+
assert(exists, "generated file is missing")
11+
print(PluginGeneratedResources().text)
12+
}
13+
}

Sources/Build/BuildOperation.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import Basics
1414
import LLBuildManifest
1515
import PackageGraph
16+
import PackageLoading
1617
import PackageModel
1718
import SPMBuildCore
1819
import SPMLLBuild
@@ -78,10 +79,14 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
7879
(try? getBuildDescription())?.builtTestProducts ?? []
7980
}
8081

82+
/// File rules to determine resource handling behavior.
83+
private let additionalFileRules: [FileRuleDescription]
84+
8185
public init(
8286
buildParameters: BuildParameters,
8387
cacheBuildManifest: Bool,
8488
packageGraphLoader: @escaping () throws -> PackageGraph,
89+
additionalFileRules: [FileRuleDescription],
8590
pluginScriptRunner: PluginScriptRunner,
8691
pluginWorkDirectory: AbsolutePath,
8792
disableSandboxForPluginCommands: Bool = false,
@@ -97,6 +102,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
97102
self.buildParameters = buildParameters
98103
self.cacheBuildManifest = cacheBuildManifest
99104
self.packageGraphLoader = packageGraphLoader
105+
self.additionalFileRules = additionalFileRules
100106
self.pluginScriptRunner = pluginScriptRunner
101107
self.pluginWorkDirectory = pluginWorkDirectory
102108
self.disableSandboxForPluginCommands = disableSandboxForPluginCommands
@@ -351,6 +357,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
351357
let plan = try BuildPlan(
352358
buildParameters: buildParameters,
353359
graph: graph,
360+
additionalFileRules: additionalFileRules,
354361
buildToolPluginInvocationResults: buildToolPluginInvocationResults,
355362
prebuildCommandResults: prebuildCommandResults,
356363
fileSystem: self.fileSystem,
@@ -443,7 +450,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
443450
return try pluginResults.map { pluginResult in
444451
// As we go we will collect a list of prebuild output directories whose contents should be input to the build,
445452
// and a list of the files in those directories after running the commands.
446-
var derivedSourceFiles: [AbsolutePath] = []
453+
var derivedFiles: [AbsolutePath] = []
447454
var prebuildOutputDirs: [AbsolutePath] = []
448455
for command in pluginResult.prebuildCommands {
449456
self.observabilityScope.emit(info: "Running" + (command.configuration.displayName ?? command.configuration.executable.basename))
@@ -463,15 +470,15 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
463470
// Add any files found in the output directory declared for the prebuild command after the command ends.
464471
let outputFilesDir = command.outputFilesDirectory
465472
if let swiftFiles = try? self.fileSystem.getDirectoryContents(outputFilesDir).sorted() {
466-
derivedSourceFiles.append(contentsOf: swiftFiles.map{ outputFilesDir.appending(component: $0) })
473+
derivedFiles.append(contentsOf: swiftFiles.map{ outputFilesDir.appending(component: $0) })
467474
}
468475

469476
// Add the output directory to the list of directories whose structure should affect the build plan.
470477
prebuildOutputDirs.append(outputFilesDir)
471478
}
472479

473480
// Add the results of running any prebuild commands for this invocation.
474-
return PrebuildCommandResult(derivedSourceFiles: derivedSourceFiles, outputDirectories: prebuildOutputDirs)
481+
return PrebuildCommandResult(derivedFiles: derivedFiles, outputDirectories: prebuildOutputDirs)
475482
}
476483
}
477484

Sources/Build/BuildPlan.swift

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,17 @@ public enum TargetBuildDescription {
174174
}
175175
}
176176

177+
/// The resources in this target.
178+
var resources: [Resource] {
179+
switch self {
180+
case .swift(let target):
181+
return target.resources
182+
case .clang(let target):
183+
// TODO: Clang targets should support generated resources in the future.
184+
return target.target.underlyingTarget.resources
185+
}
186+
}
187+
177188
/// Path to the bundle generated for this module (if any).
178189
var bundlePath: AbsolutePath? {
179190
switch self {
@@ -239,7 +250,7 @@ public final class ClangTargetBuildDescription {
239250

240251
/// Path to the bundle generated for this module (if any).
241252
var bundlePath: AbsolutePath? {
242-
buildParameters.bundlePath(for: target)
253+
target.underlyingTarget.bundleName.map(buildParameters.bundlePath(named:))
243254
}
244255

245256
/// The modulemap file for this target, if any.
@@ -542,16 +553,28 @@ public final class SwiftTargetBuildDescription {
542553
/// These are the source files derived from plugins.
543554
private var pluginDerivedSources: Sources
544555

556+
/// These are the resource files derived from plugins.
557+
private var pluginDerivedResources: [Resource]
558+
545559
/// Path to the bundle generated for this module (if any).
546560
var bundlePath: AbsolutePath? {
547-
buildParameters.bundlePath(for: target)
561+
if let bundleName = target.underlyingTarget.potentialBundleName, !resources.isEmpty {
562+
return buildParameters.bundlePath(named: bundleName)
563+
} else {
564+
return .none
565+
}
548566
}
549567

550568
/// The list of all source files in the target, including the derived ones.
551569
public var sources: [AbsolutePath] {
552570
target.sources.paths + derivedSources.paths + pluginDerivedSources.paths
553571
}
554572

573+
/// The list of all resource files in the target, including the derived ones.
574+
public var resources: [Resource] {
575+
target.underlyingTarget.resources + pluginDerivedResources
576+
}
577+
555578
/// The objects in this target.
556579
public var objects: [AbsolutePath] {
557580
let relativePaths = target.sources.relativePaths + derivedSources.relativePaths + pluginDerivedSources.relativePaths
@@ -633,16 +656,21 @@ public final class SwiftTargetBuildDescription {
633656
/// The results of running any prebuild commands for this target.
634657
public let prebuildCommandResults: [PrebuildCommandResult]
635658

659+
/// ObservabilityScope with which to emit diagnostics
660+
private let observabilityScope: ObservabilityScope
661+
636662
/// Create a new target description with target and build parameters.
637663
init(
638664
target: ResolvedTarget,
639665
toolsVersion: ToolsVersion,
666+
additionalFileRules: [FileRuleDescription] = [],
640667
buildParameters: BuildParameters,
641668
buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [],
642669
prebuildCommandResults: [PrebuildCommandResult] = [],
643670
isTestTarget: Bool? = nil,
644671
isTestDiscoveryTarget: Bool = false,
645-
fileSystem: FileSystem
672+
fileSystem: FileSystem,
673+
observabilityScope: ObservabilityScope
646674
) throws {
647675
guard target.underlyingTarget is SwiftTarget else {
648676
throw InternalError("underlying target type mismatch \(target)")
@@ -659,25 +687,31 @@ public final class SwiftTargetBuildDescription {
659687
self.pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath)
660688
self.buildToolPluginInvocationResults = buildToolPluginInvocationResults
661689
self.prebuildCommandResults = prebuildCommandResults
690+
self.observabilityScope = observabilityScope
662691

663-
// Add any derived source files that were declared for any commands from plugin invocations.
692+
// Add any derived files that were declared for any commands from plugin invocations.
693+
var pluginDerivedFiles = [AbsolutePath]()
664694
for command in buildToolPluginInvocationResults.reduce([], { $0 + $1.buildCommands }) {
665-
// TODO: What should we do if we find non-Swift sources here?
666695
for absPath in command.outputFiles {
667-
let relPath = absPath.relative(to: self.pluginDerivedSources.root)
668-
self.pluginDerivedSources.relativePaths.append(relPath)
696+
pluginDerivedFiles.append(absPath)
669697
}
670698
}
671699

672-
// Add any derived source files that were discovered from output directories of prebuild commands.
700+
// Add any derived files that were discovered from output directories of prebuild commands.
673701
for result in self.prebuildCommandResults {
674-
// TODO: What should we do if we find non-Swift sources here?
675-
for path in result.derivedSourceFiles {
676-
let relPath = path.relative(to: self.pluginDerivedSources.root)
677-
self.pluginDerivedSources.relativePaths.append(relPath)
702+
for path in result.derivedFiles {
703+
pluginDerivedFiles.append(path)
678704
}
679705
}
680706

707+
// Let `TargetSourcesBuilder` compute the treatment of plugin generated files.
708+
let (derivedSources, derivedResources) = TargetSourcesBuilder.computeContents(for: pluginDerivedFiles, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, defaultLocalization: target.defaultLocalization, targetName: target.name, targetPath: target.underlyingTarget.path, observabilityScope: observabilityScope)
709+
self.pluginDerivedResources = derivedResources
710+
derivedSources.forEach { absPath in
711+
let relPath = absPath.relative(to: self.pluginDerivedSources.root)
712+
self.pluginDerivedSources.relativePaths.append(relPath)
713+
}
714+
681715
if shouldEmitObjCCompatibilityHeader {
682716
self.moduleMap = try self.generateModuleMap()
683717
}
@@ -1585,7 +1619,8 @@ public class BuildPlan {
15851619
toolsVersion: toolsVersion,
15861620
buildParameters: buildParameters,
15871621
isTestTarget: true,
1588-
fileSystem: fileSystem
1622+
fileSystem: fileSystem,
1623+
observabilityScope: observabilityScope
15891624
)
15901625

15911626
result.append((testProduct, desc))
@@ -1621,7 +1656,8 @@ public class BuildPlan {
16211656
buildParameters: buildParameters,
16221657
isTestTarget: true,
16231658
isTestDiscoveryTarget: true,
1624-
fileSystem: fileSystem
1659+
fileSystem: fileSystem,
1660+
observabilityScope: observabilityScope
16251661
)
16261662

16271663
result.append((testProduct, target))
@@ -1635,28 +1671,11 @@ public class BuildPlan {
16351671
return result
16361672
}
16371673

1638-
@available(*, deprecated, message: "use observability system instead")
1639-
public convenience init(
1640-
buildParameters: BuildParameters,
1641-
graph: PackageGraph,
1642-
buildToolPluginInvocationResults: [ResolvedTarget: [BuildToolPluginInvocationResult]] = [:],
1643-
prebuildCommandResults: [ResolvedTarget: [PrebuildCommandResult]] = [:],
1644-
diagnostics: DiagnosticsEngine,
1645-
fileSystem: FileSystem
1646-
) throws {
1647-
let observabilitySystem = ObservabilitySystem(diagnosticEngine: diagnostics)
1648-
try self.init(
1649-
buildParameters: buildParameters,
1650-
graph: graph,
1651-
fileSystem: fileSystem,
1652-
observabilityScope: observabilitySystem.topScope
1653-
)
1654-
}
1655-
16561674
/// Create a build plan with build parameters and a package graph.
16571675
public init(
16581676
buildParameters: BuildParameters,
16591677
graph: PackageGraph,
1678+
additionalFileRules: [FileRuleDescription] = [],
16601679
buildToolPluginInvocationResults: [ResolvedTarget: [BuildToolPluginInvocationResult]] = [:],
16611680
prebuildCommandResults: [ResolvedTarget: [PrebuildCommandResult]] = [:],
16621681
fileSystem: FileSystem,
@@ -1700,10 +1719,12 @@ public class BuildPlan {
17001719
targetMap[target] = try .swift(SwiftTargetBuildDescription(
17011720
target: target,
17021721
toolsVersion: toolsVersion,
1722+
additionalFileRules: additionalFileRules,
17031723
buildParameters: buildParameters,
17041724
buildToolPluginInvocationResults: buildToolPluginInvocationResults[target] ?? [],
17051725
prebuildCommandResults: prebuildCommandResults[target] ?? [],
1706-
fileSystem: fileSystem)
1726+
fileSystem: fileSystem,
1727+
observabilityScope: observabilityScope)
17071728
)
17081729
case is ClangTarget:
17091730
targetMap[target] = try .clang(ClangTargetBuildDescription(
@@ -2236,11 +2257,9 @@ private extension Basics.Diagnostic {
22362257
}
22372258

22382259
extension BuildParameters {
2239-
/// Returns a target's bundle path inside the build directory.
2240-
fileprivate func bundlePath(for target: ResolvedTarget) -> AbsolutePath? {
2241-
target.underlyingTarget.bundleName
2242-
.map{ $0 + triple.nsbundleExtension }
2243-
.map(buildPath.appending(component:))
2260+
/// Returns a named bundle's path inside the build directory.
2261+
fileprivate func bundlePath(named name: String) -> AbsolutePath {
2262+
return buildPath.appending(component: name + triple.nsbundleExtension)
22442263
}
22452264
}
22462265

Sources/Build/LLBuildManifestBuilder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ extension LLBuildManifestBuilder {
171171
let infoPlistDestination = RelativePath("Info.plist")
172172

173173
// Create a copy command for each resource file.
174-
for resource in target.target.underlyingTarget.resources {
174+
for resource in target.resources {
175175
let destination = bundlePath.appending(resource.destination)
176176
let (_, output) = addCopyCommand(from: resource.path, to: destination)
177177
outputs.append(output)

Sources/Commands/SwiftTool.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,7 @@ public class SwiftTool {
752752
buildParameters: customBuildParameters ?? self.buildParameters(),
753753
cacheBuildManifest: cacheBuildManifest && self.canUseCachedBuildManifest(),
754754
packageGraphLoader: customPackageGraphLoader ?? graphLoader,
755+
additionalFileRules: FileRuleDescription.swiftpmFileTypes,
755756
pluginScriptRunner: self.getPluginScriptRunner(),
756757
pluginWorkDirectory: try self.getActiveWorkspace().location.pluginWorkingDirectory,
757758
disableSandboxForPluginCommands: self.options.security.shouldDisableSandbox,

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,7 @@ public final class PackageBuilder {
767767

768768
// FIXME: use identity instead?
769769
// The name of the bundle, if one is being generated.
770-
let bundleName = resources.isEmpty ? nil : self.manifest.displayName + "_" + potentialModule.name
770+
let potentialBundleName = self.manifest.displayName + "_" + potentialModule.name
771771

772772
if sources.relativePaths.isEmpty && resources.isEmpty {
773773
return nil
@@ -808,8 +808,9 @@ public final class PackageBuilder {
808808
if sources.hasSwiftSources {
809809
return SwiftTarget(
810810
name: potentialModule.name,
811-
bundleName: bundleName,
811+
potentialBundleName: potentialBundleName,
812812
type: targetType,
813+
path: potentialModule.path,
813814
sources: sources,
814815
resources: resources,
815816
ignored: ignored,
@@ -836,13 +837,14 @@ public final class PackageBuilder {
836837

837838
return try ClangTarget(
838839
name: potentialModule.name,
839-
bundleName: bundleName,
840+
potentialBundleName: potentialBundleName,
840841
cLanguageStandard: manifest.cLanguageStandard,
841842
cxxLanguageStandard: manifest.cxxLanguageStandard,
842843
includeDir: publicHeadersPath,
843844
moduleMapType: moduleMapType,
844845
headers: headers,
845846
type: targetType,
847+
path: potentialModule.path,
846848
sources: sources,
847849
resources: resources,
848850
ignored: ignored,
@@ -1362,6 +1364,7 @@ extension PackageBuilder {
13621364
return SwiftTarget(
13631365
name: name,
13641366
type: .snippet,
1367+
path: .root,
13651368
sources: sources,
13661369
dependencies: dependencies,
13671370
swiftVersion: try swiftVersion(),

0 commit comments

Comments
 (0)