Skip to content

Commit 678e683

Browse files
Run build tool plugins for C-family targets (#6516)
While we intentionally don't support generating C sources from plugins today since we haven't figured out how to deal with headers etc, not running plugins at all without any diagnostics whatsoever seems like an implementation oversight based on the fact that we have two completely different implementations for Swift and C-family targets (which is something we also need to rectify at some point). With this change, we're running build-tool plugins in the exact same way as we are doing it for Swift targets. We are only doing this for packages with tools-version 5.9 or higher in order to have any unintentional impact on existing packages. rdar://101671614 Co-authored-by: Max Desiatov <[email protected]>
1 parent 82e7926 commit 678e683

7 files changed

+178
-60
lines changed

Sources/Build/BuildDescription/ClangTargetBuildDescription.swift

+50-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import PackageLoading
1515
import PackageModel
1616
import class PackageGraph.ResolvedTarget
1717
import struct SPMBuildCore.BuildParameters
18+
import struct SPMBuildCore.BuildToolPluginInvocationResult
19+
import struct SPMBuildCore.PrebuildCommandResult
1820

1921
import enum TSCBasic.ProcessEnv
2022

@@ -41,9 +43,22 @@ public final class ClangTargetBuildDescription {
4143
buildParameters.buildEnvironment
4244
}
4345

46+
/// The list of all resource files in the target, including the derived ones.
47+
public var resources: [Resource] {
48+
self.target.underlyingTarget.resources + self.pluginDerivedResources
49+
}
50+
4451
/// Path to the bundle generated for this module (if any).
4552
var bundlePath: AbsolutePath? {
46-
target.underlyingTarget.bundleName.map(buildParameters.bundlePath(named:))
53+
guard !resources.isEmpty else {
54+
return .none
55+
}
56+
57+
if let bundleName = target.underlyingTarget.potentialBundleName {
58+
return self.buildParameters.bundlePath(named: bundleName)
59+
} else {
60+
return .none
61+
}
4762
}
4863

4964
/// The modulemap file for this target, if any.
@@ -57,6 +72,12 @@ public final class ClangTargetBuildDescription {
5772
/// These are the source files generated during the build.
5873
private var derivedSources: Sources
5974

75+
/// These are the source files derived from plugins.
76+
private var pluginDerivedSources: Sources
77+
78+
/// These are the resource files derived from plugins.
79+
private var pluginDerivedResources: [Resource]
80+
6081
/// Path to the resource accessor header file, if generated.
6182
public private(set) var resourceAccessorHeaderFile: AbsolutePath?
6283

@@ -84,12 +105,19 @@ public final class ClangTargetBuildDescription {
84105
target.type == .test
85106
}
86107

108+
/// The results of applying any build tool plugins to this target.
109+
public let buildToolPluginInvocationResults: [BuildToolPluginInvocationResult]
110+
87111
/// Create a new target description with target and build parameters.
88112
init(
89113
target: ResolvedTarget,
90114
toolsVersion: ToolsVersion,
115+
additionalFileRules: [FileRuleDescription] = [],
91116
buildParameters: BuildParameters,
92-
fileSystem: FileSystem
117+
buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [],
118+
prebuildCommandResults: [PrebuildCommandResult] = [],
119+
fileSystem: FileSystem,
120+
observabilityScope: ObservabilityScope
93121
) throws {
94122
guard target.underlyingTarget is ClangTarget else {
95123
throw InternalError("underlying target type mismatch \(target)")
@@ -102,6 +130,25 @@ public final class ClangTargetBuildDescription {
102130
self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build")
103131
self.derivedSources = Sources(paths: [], root: tempsPath.appending("DerivedSources"))
104132

133+
// We did not use to apply package plugins to C-family targets in prior tools-versions, this preserves the behavior.
134+
if toolsVersion >= .v5_9 {
135+
self.buildToolPluginInvocationResults = buildToolPluginInvocationResults
136+
137+
(self.pluginDerivedSources, self.pluginDerivedResources) = SharedTargetBuildDescription.computePluginGeneratedFiles(
138+
target: target,
139+
toolsVersion: toolsVersion,
140+
additionalFileRules: additionalFileRules,
141+
buildParameters: buildParameters,
142+
buildToolPluginInvocationResults: buildToolPluginInvocationResults,
143+
prebuildCommandResults: prebuildCommandResults,
144+
observabilityScope: observabilityScope
145+
)
146+
} else {
147+
self.buildToolPluginInvocationResults = []
148+
self.pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath)
149+
self.pluginDerivedResources = []
150+
}
151+
105152
// Try computing modulemap path for a C library. This also creates the file in the file system, if needed.
106153
if target.type == .library {
107154
// If there's a custom module map, use it as given.
@@ -141,6 +188,7 @@ public final class ClangTargetBuildDescription {
141188
let sources = [
142189
target.sources.root: target.sources.relativePaths,
143190
derivedSources.root: derivedSources.relativePaths,
191+
pluginDerivedSources.root: pluginDerivedSources.relativePaths
144192
]
145193

146194
return try sources.flatMap { root, relativePaths in
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Basics
14+
import PackageGraph
15+
import PackageLoading
16+
import PackageModel
17+
import SPMBuildCore
18+
19+
import struct Basics.AbsolutePath
20+
21+
/// Shared functionality between `ClangTargetBuildDescription` and `SwiftTargetBuildDescription` with the eventual hope of having a single type.
22+
struct SharedTargetBuildDescription {
23+
static func computePluginGeneratedFiles(
24+
target: ResolvedTarget,
25+
toolsVersion: ToolsVersion,
26+
additionalFileRules: [FileRuleDescription],
27+
buildParameters: BuildParameters,
28+
buildToolPluginInvocationResults: [BuildToolPluginInvocationResult],
29+
prebuildCommandResults: [PrebuildCommandResult],
30+
observabilityScope: ObservabilityScope
31+
) -> (pluginDerivedSources: Sources, pluginDerivedResources: [Resource]) {
32+
var pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath)
33+
34+
// Add any derived files that were declared for any commands from plugin invocations.
35+
var pluginDerivedFiles = [AbsolutePath]()
36+
for command in buildToolPluginInvocationResults.reduce([], { $0 + $1.buildCommands }) {
37+
for absPath in command.outputFiles {
38+
pluginDerivedFiles.append(absPath)
39+
}
40+
}
41+
42+
// Add any derived files that were discovered from output directories of prebuild commands.
43+
for result in prebuildCommandResults {
44+
for path in result.derivedFiles {
45+
pluginDerivedFiles.append(path)
46+
}
47+
}
48+
49+
// Let `TargetSourcesBuilder` compute the treatment of plugin generated files.
50+
let (derivedSources, derivedResources) = TargetSourcesBuilder.computeContents(
51+
for: pluginDerivedFiles,
52+
toolsVersion: toolsVersion,
53+
additionalFileRules: additionalFileRules,
54+
defaultLocalization: target.defaultLocalization,
55+
targetName: target.name,
56+
targetPath: target.underlyingTarget.path,
57+
observabilityScope: observabilityScope
58+
)
59+
let pluginDerivedResources = derivedResources
60+
derivedSources.forEach { absPath in
61+
let relPath = absPath.relative(to: pluginDerivedSources.root)
62+
pluginDerivedSources.relativePaths.append(relPath)
63+
}
64+
65+
return (pluginDerivedSources, pluginDerivedResources)
66+
}
67+
}

Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift

+5-27
Original file line numberDiff line numberDiff line change
@@ -251,42 +251,20 @@ public final class SwiftTargetBuildDescription {
251251
self.fileSystem = fileSystem
252252
self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build")
253253
self.derivedSources = Sources(paths: [], root: self.tempsPath.appending("DerivedSources"))
254-
self.pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath)
255254
self.buildToolPluginInvocationResults = buildToolPluginInvocationResults
256255
self.prebuildCommandResults = prebuildCommandResults
257256
self.requiredMacroProducts = requiredMacroProducts
258257
self.observabilityScope = observabilityScope
259258

260-
// Add any derived files that were declared for any commands from plugin invocations.
261-
var pluginDerivedFiles = [AbsolutePath]()
262-
for command in buildToolPluginInvocationResults.reduce([], { $0 + $1.buildCommands }) {
263-
for absPath in command.outputFiles {
264-
pluginDerivedFiles.append(absPath)
265-
}
266-
}
267-
268-
// Add any derived files that were discovered from output directories of prebuild commands.
269-
for result in self.prebuildCommandResults {
270-
for path in result.derivedFiles {
271-
pluginDerivedFiles.append(path)
272-
}
273-
}
274-
275-
// Let `TargetSourcesBuilder` compute the treatment of plugin generated files.
276-
let (derivedSources, derivedResources) = TargetSourcesBuilder.computeContents(
277-
for: pluginDerivedFiles,
259+
(self.pluginDerivedSources, self.pluginDerivedResources) = SharedTargetBuildDescription.computePluginGeneratedFiles(
260+
target: target,
278261
toolsVersion: toolsVersion,
279262
additionalFileRules: additionalFileRules,
280-
defaultLocalization: target.defaultLocalization,
281-
targetName: target.name,
282-
targetPath: target.underlyingTarget.path,
263+
buildParameters: buildParameters,
264+
buildToolPluginInvocationResults: buildToolPluginInvocationResults,
265+
prebuildCommandResults: prebuildCommandResults,
283266
observabilityScope: observabilityScope
284267
)
285-
self.pluginDerivedResources = derivedResources
286-
derivedSources.forEach { absPath in
287-
let relPath = absPath.relative(to: self.pluginDerivedSources.root)
288-
self.pluginDerivedSources.relativePaths.append(relPath)
289-
}
290268

291269
if self.shouldEmitObjCCompatibilityHeader {
292270
self.moduleMap = try self.generateModuleMap()

Sources/Build/BuildDescription/TargetBuildDescription.swift

+11-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import Basics
1414
import class PackageGraph.ResolvedTarget
1515
import struct PackageModel.Resource
16+
import struct SPMBuildCore.BuildToolPluginInvocationResult
1617

1718
/// A target description which can either be for a Swift or Clang target.
1819
public enum TargetBuildDescription {
@@ -40,8 +41,7 @@ public enum TargetBuildDescription {
4041
case .swift(let target):
4142
return target.resources
4243
case .clang(let target):
43-
// TODO: Clang targets should support generated resources in the future.
44-
return target.target.underlyingTarget.resources
44+
return target.resources
4545
}
4646
}
4747

@@ -82,4 +82,13 @@ public enum TargetBuildDescription {
8282
return target.resourceBundleInfoPlistPath
8383
}
8484
}
85+
86+
var buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] {
87+
switch self {
88+
case .swift(let target):
89+
return target.buildToolPluginInvocationResults
90+
case .clang(let target):
91+
return target.buildToolPluginInvocationResults
92+
}
93+
}
8594
}

Sources/Build/BuildPlan.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -496,8 +496,13 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
496496
targetMap[target] = try .clang(ClangTargetBuildDescription(
497497
target: target,
498498
toolsVersion: toolsVersion,
499+
additionalFileRules: additionalFileRules,
499500
buildParameters: buildParameters,
500-
fileSystem: fileSystem))
501+
buildToolPluginInvocationResults: buildToolPluginInvocationResults[target] ?? [],
502+
prebuildCommandResults: prebuildCommandResults[target] ?? [],
503+
fileSystem: fileSystem,
504+
observabilityScope: observabilityScope)
505+
)
501506
case is PluginTarget:
502507
guard let package = graph.package(for: target) else {
503508
throw InternalError("package not found for \(target)")

Sources/Build/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_library(Build
1010
BuildDescription/ClangTargetBuildDescription.swift
1111
BuildDescription/PluginDescription.swift
1212
BuildDescription/ProductBuildDescription.swift
13+
BuildDescription/SharedTargetBuildDescription.swift
1314
BuildDescription/SwiftTargetBuildDescription.swift
1415
BuildDescription/TargetBuildDescription.swift
1516
BuildOperationBuildSystemDelegateHandler.swift

Sources/Build/LLBuildManifestBuilder.swift

+38-28
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,41 @@ extension LLBuildManifestBuilder {
207207
}
208208
}
209209

210+
// MARK: - Compilation
211+
212+
extension LLBuildManifestBuilder {
213+
private func addBuildToolPlugins(_ target: TargetBuildDescription) throws {
214+
// Add any regular build commands created by plugins for the target.
215+
for result in target.buildToolPluginInvocationResults {
216+
// Only go through the regular build commands — prebuild commands are handled separately.
217+
for command in result.buildCommands {
218+
// Create a shell command to invoke the executable. We include the path of the executable as a
219+
// dependency, and make sure the name is unique.
220+
let execPath = command.configuration.executable
221+
let uniquedName = ([execPath.pathString] + command.configuration.arguments).joined(separator: "|")
222+
let displayName = command.configuration.displayName ?? execPath.basename
223+
var commandLine = [execPath.pathString] + command.configuration.arguments
224+
if !self.disableSandboxForPluginCommands {
225+
commandLine = try Sandbox.apply(
226+
command: commandLine,
227+
strictness: .writableTemporaryDirectory,
228+
writableDirectories: [result.pluginOutputDirectory]
229+
)
230+
}
231+
self.manifest.addShellCmd(
232+
name: displayName + "-" + ByteString(encodingAsUTF8: uniquedName).sha256Checksum,
233+
description: displayName,
234+
inputs: command.inputFiles.map { .file($0) },
235+
outputs: command.outputFiles.map { .file($0) },
236+
arguments: commandLine,
237+
environment: command.configuration.environment,
238+
workingDirectory: command.configuration.workingDirectory?.pathString
239+
)
240+
}
241+
}
242+
}
243+
}
244+
210245
// MARK: - Compile Swift
211246

212247
extension LLBuildManifestBuilder {
@@ -681,34 +716,7 @@ extension LLBuildManifestBuilder {
681716
}
682717
}
683718

684-
// Add any regular build commands created by plugins for the target.
685-
for result in target.buildToolPluginInvocationResults {
686-
// Only go through the regular build commands — prebuild commands are handled separately.
687-
for command in result.buildCommands {
688-
// Create a shell command to invoke the executable. We include the path of the executable as a
689-
// dependency, and make sure the name is unique.
690-
let execPath = command.configuration.executable
691-
let uniquedName = ([execPath.pathString] + command.configuration.arguments).joined(separator: "|")
692-
let displayName = command.configuration.displayName ?? execPath.basename
693-
var commandLine = [execPath.pathString] + command.configuration.arguments
694-
if !self.disableSandboxForPluginCommands {
695-
commandLine = try Sandbox.apply(
696-
command: commandLine,
697-
strictness: .writableTemporaryDirectory,
698-
writableDirectories: [result.pluginOutputDirectory]
699-
)
700-
}
701-
self.manifest.addShellCmd(
702-
name: displayName + "-" + ByteString(encodingAsUTF8: uniquedName).sha256Checksum,
703-
description: displayName,
704-
inputs: command.inputFiles.map { .file($0) },
705-
outputs: command.outputFiles.map { .file($0) },
706-
arguments: commandLine,
707-
environment: command.configuration.environment,
708-
workingDirectory: command.configuration.workingDirectory?.pathString
709-
)
710-
}
711-
}
719+
try addBuildToolPlugins(.swift(target))
712720

713721
// Depend on any required macro product's output.
714722
try target.requiredMacroProducts.forEach { macro in
@@ -882,6 +890,8 @@ extension LLBuildManifestBuilder {
882890
)
883891
}
884892

893+
try addBuildToolPlugins(.clang(target))
894+
885895
// Create a phony node to represent the entire target.
886896
let targetName = target.target.getLLBuildTargetName(config: self.buildConfig)
887897
let output: Node = .virtual(targetName)

0 commit comments

Comments
 (0)