Skip to content

Commit 0f5276c

Browse files
authored
[5.5] Align experimental implementations of SE-0303 and SE-0305 with proposal (#3574)
* Adjust to SE-0303: Package plugins default to a "Plugins" directory next to "Sources" and "Tests" (cherry picked from commit fd7d22b) * Correct a comment for PluginCapability. (cherry picked from commit b543c86) * Adjust to SE-0303: Add `path`, `exclude`, and `sources` parameters to plugin targets. (cherry picked from commit caab591) # Conflicts: # Tests/PackageLoadingTests/PD5_5LoadingTests.swift * Adjust to SE-0303: Change PackagePlugin API to match proposal. (cherry picked from commit 39a8e7f) * Adjust to SE-0305: Adjust suffixes for artifact bundles and artifact indices (cherry picked from commit eba6e97)
1 parent 0a2ac74 commit 0f5276c

File tree

26 files changed

+272
-112
lines changed

26 files changed

+272
-112
lines changed

Fixtures/Miscellaneous/Plugins/ContrivedTestPlugin/Sources/MySourceGenBuildToolPlugin/plugin.swift renamed to Fixtures/Miscellaneous/Plugins/ContrivedTestPlugin/Plugins/MySourceGenBuildToolPlugin/plugin.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ print("Hello from the Build Tool Plugin!")
55
for inputFile in targetBuildContext.inputFiles.filter({ $0.path.extension == "dat" }) {
66
let inputPath = inputFile.path
77
let outputName = inputPath.stem + ".swift"
8-
let outputPath = targetBuildContext.outputDirectory.appending(outputName)
9-
commandConstructor.createBuildCommand(
8+
let outputPath = targetBuildContext.pluginWorkDirectory.appending(outputName)
9+
commandConstructor.addBuildCommand(
1010
displayName:
1111
"Generating \(outputName) from \(inputPath.lastComponent)",
1212
executable:

Fixtures/Miscellaneous/Plugins/MyBinaryToolPlugin/Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ let package = Package(
2222
// The vended executable that generates source files.
2323
.binaryTarget(
2424
name: "MyVendedSourceGenBuildTool",
25-
path: "Binaries/MyVendedSourceGenBuildTool.arar"
25+
path: "Binaries/MyVendedSourceGenBuildTool.artifactbundle"
2626
),
2727
]
2828
)

Fixtures/Miscellaneous/Plugins/MyBinaryToolPlugin/Sources/MySourceGenBuildToolPlugin/plugin.swift renamed to Fixtures/Miscellaneous/Plugins/MyBinaryToolPlugin/Plugins/MySourceGenBuildToolPlugin/plugin.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ print("Hello from the Build Tool Plugin!")
55
for inputFile in targetBuildContext.inputFiles.filter({ $0.path.extension == "dat" }) {
66
let inputPath = inputFile.path
77
let outputName = inputPath.stem + ".swift"
8-
let outputPath = targetBuildContext.outputDirectory.appending(outputName)
9-
commandConstructor.createBuildCommand(
8+
let outputPath = targetBuildContext.pluginWorkDirectory.appending(outputName)
9+
commandConstructor.addBuildCommand(
1010
displayName:
1111
"Generating \(outputName) from \(inputPath.lastComponent)",
1212
executable:

Fixtures/Miscellaneous/Plugins/MySourceGenPlugin/Sources/MySourceGenBuildToolPlugin/plugin.swift renamed to Fixtures/Miscellaneous/Plugins/MySourceGenPlugin/Plugins/MySourceGenBuildToolPlugin/plugin.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ print("Hello from the Build Tool Plugin!")
55
for inputPath in targetBuildContext.inputFiles.map{ $0.path } {
66
guard inputPath.extension == "dat" else { continue }
77
let outputName = inputPath.stem + ".swift"
8-
let outputPath = targetBuildContext.outputDirectory.appending(outputName)
9-
commandConstructor.createBuildCommand(
8+
let outputPath = targetBuildContext.pluginWorkDirectory.appending(outputName)
9+
commandConstructor.addBuildCommand(
1010
displayName:
1111
"Generating \(outputName) from \(inputPath.lastComponent)",
1212
executable:

Fixtures/Miscellaneous/Plugins/MySourceGenPlugin/Sources/MySourceGenPrebuildPlugin/plugin.swift renamed to Fixtures/Miscellaneous/Plugins/MySourceGenPlugin/Plugins/MySourceGenPrebuildPlugin/plugin.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@ import PackagePlugin
33
print("Hello from the Prebuild Plugin!")
44

55
let outputPaths: [Path] = targetBuildContext.inputFiles.filter{ $0.path.extension == "dat" }.map { file in
6-
targetBuildContext.outputDirectory.appending(file.path.stem + ".swift")
6+
targetBuildContext.pluginWorkDirectory.appending(file.path.stem + ".swift")
77
}
88

99
if !outputPaths.isEmpty {
10-
commandConstructor.createPrebuildCommand(
10+
commandConstructor.addPrebuildCommand(
1111
displayName:
1212
"Running prebuild command for target \(targetBuildContext.targetName)",
1313
executable:
1414
Path("/usr/bin/touch"),
1515
arguments:
1616
outputPaths.map{ $0.string },
1717
outputFilesDirectory:
18-
targetBuildContext.outputDirectory
18+
targetBuildContext.pluginWorkDirectory
1919
)
2020
}

Fixtures/Miscellaneous/Plugins/SandboxTesterPlugin/Sources/MySourceGenBuildToolPlugin/plugin.swift renamed to Fixtures/Miscellaneous/Plugins/SandboxTesterPlugin/Plugins/MySourceGenBuildToolPlugin/plugin.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import PackagePlugin
22
import Foundation
33

44
// Check that we can write to the output directory.
5-
let allowedOutputPath = targetBuildContext.outputDirectory.appending("Foo")
5+
let allowedOutputPath = targetBuildContext.pluginWorkDirectory.appending("Foo")
66
if mkdir(allowedOutputPath.string, 0o777) != 0 {
77
throw StringError("unexpectedly could not write to '\(allowedOutputPath)': \(String(utf8String: strerror(errno)))")
88
}

Sources/PackageDescription/Target.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,6 @@ public final class Target {
268268
case .plugin:
269269
precondition(
270270
url == nil &&
271-
exclude.isEmpty &&
272-
sources == nil &&
273271
resources == nil &&
274272
publicHeadersPath == nil &&
275273
pkgConfig == nil &&
@@ -901,14 +899,17 @@ public final class Target {
901899
public static func plugin(
902900
name: String,
903901
capability: PluginCapability,
904-
dependencies: [Dependency] = []
902+
dependencies: [Dependency] = [],
903+
path: String? = nil,
904+
exclude: [String] = [],
905+
sources: [String]? = nil
905906
) -> Target {
906907
return Target(
907908
name: name,
908909
dependencies: dependencies,
909-
path: nil,
910-
exclude: [],
911-
sources: nil,
910+
path: path,
911+
exclude: exclude,
912+
sources: sources,
912913
publicHeadersPath: nil,
913914
type: .plugin,
914915
pluginCapability: capability)
@@ -1062,7 +1063,7 @@ extension Target.Dependency {
10621063

10631064
extension Target.PluginCapability {
10641065

1065-
/// Specifies that the plugin provides a prebuild capability. The plugin
1066+
/// Specifies that the plugin provides a build tool capability. The plugin
10661067
/// will be applied to each target that uses it and should create commands
10671068
/// that will run before or during the build of the target.
10681069
@available(_PackageDescription, introduced: 5.5)

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ extension ModuleError: CustomStringConvertible {
8383
let packages = packages.joined(separator: "', '")
8484
return "multiple targets named '\(name)' in: '\(packages)'"
8585
case .moduleNotFound(let target, let type):
86-
let folderName = type == .test ? "Tests" : "Sources"
86+
let folderName = (type == .test) ? "Tests" : (type == .plugin) ? "Plugins" : "Sources"
8787
return "Source files for target \(target) should be located under '\(folderName)/\(target)', or a custom sources path can be set with the 'path' property in Package.swift"
8888
case .artifactNotFound(let target):
8989
return "artifact not found for target '\(target)'"
@@ -535,9 +535,12 @@ public final class PackageBuilder {
535535

536536
/// Predefined test directories, in order of preference.
537537
public static let predefinedTestDirectories = ["Tests", "Sources", "Source", "src", "srcs"]
538+
539+
/// Predefined plugin directories, in order of preference.
540+
public static let predefinedPluginDirectories = ["Plugins"]
538541

539-
/// Finds the predefined directories for regular and test targets.
540-
private func findPredefinedTargetDirectory() -> (targetDir: String, testTargetDir: String) {
542+
/// Finds the predefined directories for regular targets, test targets, and plugin targets.
543+
private func findPredefinedTargetDirectory() -> (targetDir: String, testTargetDir: String, pluginTargetDir: String) {
541544
let targetDir = PackageBuilder.predefinedSourceDirectories.first(where: {
542545
fileSystem.isDirectory(packagePath.appending(component: $0))
543546
}) ?? PackageBuilder.predefinedSourceDirectories[0]
@@ -546,7 +549,11 @@ public final class PackageBuilder {
546549
fileSystem.isDirectory(packagePath.appending(component: $0))
547550
}) ?? PackageBuilder.predefinedTestDirectories[0]
548551

549-
return (targetDir, testTargetDir)
552+
let pluginTargetDir = PackageBuilder.predefinedPluginDirectories.first(where: {
553+
fileSystem.isDirectory(packagePath.appending(component: $0))
554+
}) ?? PackageBuilder.predefinedPluginDirectories[0]
555+
556+
return (targetDir, testTargetDir, pluginTargetDir)
550557
}
551558

552559
struct PredefinedTargetDirectory {
@@ -566,6 +573,7 @@ public final class PackageBuilder {
566573

567574
let predefinedTargetDirectory = PredefinedTargetDirectory(fs: fileSystem, path: packagePath.appending(component: predefinedDirs.targetDir))
568575
let predefinedTestTargetDirectory = PredefinedTargetDirectory(fs: fileSystem, path: packagePath.appending(component: predefinedDirs.testTargetDir))
576+
let predefinedPluginTargetDirectory = PredefinedTargetDirectory(fs: fileSystem, path: packagePath.appending(component: predefinedDirs.pluginTargetDir))
569577

570578
/// Returns the path of the given target.
571579
func findPath(for target: TargetDescription) throws -> AbsolutePath {
@@ -598,7 +606,15 @@ public final class PackageBuilder {
598606
}
599607

600608
// Check if target is present in the predefined directory.
601-
let predefinedDir = target.isTest ? predefinedTestTargetDirectory : predefinedTargetDirectory
609+
let predefinedDir: PredefinedTargetDirectory
610+
switch target.type {
611+
case .test:
612+
predefinedDir = predefinedTestTargetDirectory
613+
case .plugin:
614+
predefinedDir = predefinedPluginTargetDirectory
615+
default:
616+
predefinedDir = predefinedTargetDirectory
617+
}
602618
let path = predefinedDir.path.appending(component: target.name)
603619

604620
// Return the path if the predefined directory contains it.

Sources/PackageModel/Manifest/TargetDescription.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,6 @@ public struct TargetDescription: Equatable, Codable {
174174
if pluginUsages != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginUsages") }
175175
case .plugin:
176176
if url != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "url") }
177-
if !exclude.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "exclude") }
178-
if sources != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "sources") }
179177
if !resources.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "resources") }
180178
if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "publicHeadersPath") }
181179
if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") }

Sources/PackageModel/Target.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ public final class BinaryTarget: Target {
547547
case .xcframework:
548548
return "xcframework"
549549
case .artifactsArchive:
550-
return "arar"
550+
return "artifactbundle"
551551
}
552552
}
553553

Sources/PackagePlugin/ImplementationDetails.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ struct BuildCommand: Encodable {
6161
let displayName: String?
6262
let executable: Path
6363
let arguments: [String]
64-
let environment: [String: String]?
64+
let environment: [String: String]
6565
let workingDirectory: Path?
6666
let inputFiles: [Path]
6767
let outputFiles: [Path]
@@ -71,7 +71,7 @@ struct PrebuildCommand: Encodable {
7171
let displayName: String?
7272
let executable: Path
7373
let arguments: [String]
74-
let environment: [String: String]?
74+
let environment: [String: String]
7575
let workingDirectory: Path?
7676
let outputFilesDirectory: Path
7777
}

Sources/PackagePlugin/PublicAPI/CommandConstructor.swift

Lines changed: 119 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,82 @@
88
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
99
*/
1010

11-
/// Constructs commands to run during the build, including full command lines.
12-
/// All paths should be based on the ones passed to the plugin in the target
13-
/// build context.
11+
/// Constructs commands to run during the build, including command lines,
12+
/// environment variables, initial working directory, etc. All paths should
13+
/// be based on the ones passed to the plugin in the target build context.
1414
public final class CommandConstructor {
15+
1516
/// Prevents the CommandConstructor from being instantiated by the script.
1617
internal init() {}
1718

1819
/// Creates a command to run during the build. The executable should be a
19-
/// path returned by `TargetBuildContext.tool(named:)`, the inputs should
20-
/// be the files that are used by the command, and the outputs should be
21-
/// the files that are produced by the command.
22-
public func createBuildCommand(
20+
/// tool returned by `TargetBuildContext.tool(named:)`, and any paths in
21+
/// the arguments list as well as in the input and output lists should be
22+
/// based on the paths provided in the target build context structure.
23+
///
24+
/// The build command will run whenever its outputs are missing or if its
25+
/// inputs have changed since the command was last run. In other words,
26+
/// it is incorporated into the build command graph.
27+
///
28+
/// This is the preferred kind of command to create when the outputs that
29+
/// will be generated are known ahead of time.
30+
///
31+
/// - parameters:
32+
/// - displayName: An optional string to show in build logs and other
33+
/// status areas.
34+
/// - executable: The executable to be invoked; should be a tool looked
35+
/// up using `tool(named:)`, which may reference either a tool provided
36+
/// by a binary target or build from source.
37+
/// - arguments: Arguments to be passed to the tool. Any paths should be
38+
/// based on the paths provided in the target build context.
39+
/// - environment: Any custom environment assignments for the subprocess.
40+
/// - inputFiles: Input files to the build command. Any changes to the
41+
/// input files cause the command to be rerun.
42+
/// - outputFiles: Output files that should be processed further according
43+
/// to the rules defined by the build system.
44+
public func addBuildCommand(
2345
displayName: String?,
2446
executable: Path,
2547
arguments: [String],
26-
environment: [String: String]? = nil,
48+
environment: [String: String] = [:],
2749
inputFiles: [Path] = [],
2850
outputFiles: [Path] = []
2951
) {
3052
output.buildCommands.append(BuildCommand(displayName: displayName, executable: executable, arguments: arguments, environment: environment, workingDirectory: nil, inputFiles: inputFiles, outputFiles: outputFiles))
3153
}
3254

55+
/// Creates a command to run during the build. The executable should be a
56+
/// tool returned by `TargetBuildContext.tool(named:)`, and any paths in
57+
/// the arguments list as well as in the input and output lists should be
58+
/// based on the paths provided in the target build context structure.
59+
///
60+
/// The build command will run whenever its outputs are missing or if its
61+
/// inputs have changed since the command was last run. In other words,
62+
/// it is incorporated into the build command graph.
63+
///
64+
/// This is the preferred kind of command to create when the outputs that
65+
/// will be generated are known ahead of time.
66+
///
67+
/// - parameters:
68+
/// - displayName: An optional string to show in build logs and other
69+
/// status areas.
70+
/// - executable: The executable to be invoked; should be a tool looked
71+
/// up using `tool(named:)`, which may reference either a tool provided
72+
/// by a binary target or build from source.
73+
/// - arguments: Arguments to be passed to the tool. Any paths should be
74+
/// based on the paths provided in the target build context.
75+
/// - environment: Any custom environment assignments for the subprocess.
76+
/// - workingDirectory: Optional initial working directory of the command.
77+
/// - inputFiles: Input files to the build command. Any changes to the
78+
/// input files cause the command to be rerun.
79+
/// - outputFiles: Output files that should be processed further according
80+
/// to the rules defined by the build system.
3381
@available(*, unavailable, message: "specifying the initial working directory for a command is not yet supported")
34-
public func createBuildCommand(
82+
public func addBuildCommand(
3583
displayName: String?,
3684
executable: Path,
3785
arguments: [String],
38-
environment: [String: String]? = nil,
86+
environment: [String: String] = [:],
3987
workingDirectory: Path? = nil,
4088
inputFiles: [Path] = [],
4189
outputFiles: [Path] = []
@@ -44,25 +92,80 @@ public final class CommandConstructor {
4492
}
4593

4694
/// Creates a command to run before the build. The executable should be a
47-
/// path returned by `TargetBuildContext.tool(named:)`, the output direc-
48-
/// tory should be a directory in which the command will create the output
49-
/// files that should be subject to further processing.
50-
public func createPrebuildCommand(
95+
/// tool returned by `TargetBuildContext.tool(named:)`, and any paths in
96+
/// the arguments list and the output files directory should be based on
97+
/// the paths provided in the target build context structure.
98+
///
99+
/// The build command will run before the build starts, and is allowed to
100+
/// create an arbitrary set of output files based on the contents of the
101+
/// inputs.
102+
///
103+
/// Because prebuild commands are run on every build, they are can have
104+
/// significant performance impact and should only be used when there is
105+
/// no way to know the names of the outputs before the command is run.
106+
///
107+
/// The `outputFilesDirectory` parameter is the path of a directory into
108+
/// which the command will write its output files. Any files that are in
109+
/// that directory after the prebuild command finishes will be interpreted
110+
/// according to same build rules as for sources.
111+
///
112+
/// - parameters:
113+
/// - displayName: An optional string to show in build logs and other
114+
/// status areas.
115+
/// - executable: The executable to be invoked; should be a tool looked
116+
/// up using `tool(named:)`, which may reference either a tool provided
117+
/// by a binary target or build from source.
118+
/// - arguments: Arguments to be passed to the tool. Any paths should be
119+
/// based on the paths provided in the target build context.
120+
/// - environment: Any custom environment assignments for the subprocess.
121+
/// - outputFilesDirectory: A directory into which the command can write
122+
/// output files that should be processed further.
123+
public func addPrebuildCommand(
51124
displayName: String?,
52125
executable: Path,
53126
arguments: [String],
54-
environment: [String: String]? = nil,
127+
environment: [String: String] = [:],
55128
outputFilesDirectory: Path
56129
) {
57130
output.prebuildCommands.append(PrebuildCommand(displayName: displayName, executable: executable, arguments: arguments, environment: environment, workingDirectory: nil, outputFilesDirectory: outputFilesDirectory))
58131
}
59132

133+
/// Creates a command to run before the build. The executable should be a
134+
/// tool returned by `TargetBuildContext.tool(named:)`, and any paths in
135+
/// the arguments list and the output files directory should be based on
136+
/// the paths provided in the target build context structure.
137+
///
138+
/// The build command will run before the build starts, and is allowed to
139+
/// create an arbitrary set of output files based on the contents of the
140+
/// inputs.
141+
///
142+
/// Because prebuild commands are run on every build, they are can have
143+
/// significant performance impact and should only be used when there is
144+
/// no way to know the names of the outputs before the command is run.
145+
///
146+
/// The `outputFilesDirectory` parameter is the path of a directory into
147+
/// which the command will write its output files. Any files that are in
148+
/// that directory after the prebuild command finishes will be interpreted
149+
/// according to same build rules as for sources.
150+
///
151+
/// - parameters:
152+
/// - displayName: An optional string to show in build logs and other
153+
/// status areas.
154+
/// - executable: The executable to be invoked; should be a tool looked
155+
/// up using `tool(named:)`, which may reference either a tool provided
156+
/// by a binary target or build from source.
157+
/// - arguments: Arguments to be passed to the tool. Any paths should be
158+
/// based on the paths provided in the target build context.
159+
/// - environment: Any custom environment assignments for the subprocess.
160+
/// - workingDirectory: Optional initial working directory of the command.
161+
/// - outputFilesDirectory: A directory into which the command can write
162+
/// output files that should be processed further.
60163
@available(*, unavailable, message: "specifying the initial working directory for a command is not yet supported")
61164
public func createPrebuildCommand(
62165
displayName: String?,
63166
executable: Path,
64167
arguments: [String],
65-
environment: [String: String]? = nil,
168+
environment: [String: String] = [:],
66169
workingDirectory: Path? = nil,
67170
outputFilesDirectory: Path
68171
) {

0 commit comments

Comments
 (0)