Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public final class ClangModuleBuildDescription {
if toolsVersion >= .v5_9 {
self.buildToolPluginInvocationResults = buildToolPluginInvocationResults

(self.pluginDerivedSources, self.pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles(
let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles(
target: target,
toolsVersion: toolsVersion,
additionalFileRules: additionalFileRules,
Expand All @@ -165,6 +165,23 @@ public final class ClangModuleBuildDescription {
prebuildCommandResults: prebuildCommandResults,
observabilityScope: observabilityScope
)

self.pluginDerivedSources = Sources(
paths: pluginGeneratedFiles.sources.map(\.self),
root: buildParameters.dataPath
)
self.pluginDerivedResources = pluginGeneratedFiles.resources.values.map(\.self)

// With Swift Build on the horizon, we won't add support for generated headers, modulemaps, and apinotes here
for absPath in pluginGeneratedFiles.headers {
observabilityScope.emit(warning: "Module maps generated by plugins are not supported at this time: \(absPath)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should say headers

}
for absPath in pluginGeneratedFiles.moduleMaps {
observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should say module maps

}
for absPath in pluginGeneratedFiles.apiNotes {
observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)")
}
} else {
self.buildToolPluginInvocationResults = []
self.pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ public final class SwiftModuleBuildDescription {
self.fileSystem = fileSystem
self.observabilityScope = observabilityScope

(self.pluginDerivedSources, self.pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles(
let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles(
target: target,
toolsVersion: toolsVersion,
additionalFileRules: additionalFileRules,
Expand All @@ -308,6 +308,30 @@ public final class SwiftModuleBuildDescription {
prebuildCommandResults: prebuildCommandResults,
observabilityScope: observabilityScope
)
self.pluginDerivedSources = Sources(
paths: pluginGeneratedFiles.sources.map(\.self),
root: buildParameters.dataPath
)
self.pluginDerivedResources = pluginGeneratedFiles.resources.values.map(\.self)

let nonSwiftSources = pluginDerivedSources.relativePaths.filter({ $0.extension != "swift" })
if !nonSwiftSources.isEmpty {
for source in nonSwiftSources {
let absPath = pluginDerivedSources.root.appending(source)
observabilityScope.emit(warning: "Only Swift is supported for generated plugin source files at this time: \(absPath)")
}
self.pluginDerivedSources.relativePaths = self.pluginDerivedSources.relativePaths.filter({ $0.extension == "swift" })
}

for absPath in pluginGeneratedFiles.headers {
observabilityScope.emit(warning: "Module maps generated by plugins are not supported at this time: \(absPath)")
}
for absPath in pluginGeneratedFiles.moduleMaps {
observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)")
}
for absPath in pluginGeneratedFiles.apiNotes {
observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)")
}

// default to -static on Windows
self.isWindowsStatic = buildParameters.triple.isWindows()
Expand Down
7 changes: 6 additions & 1 deletion Sources/Build/BuildPlan/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ extension BuildPlan {
// build.
let observability = ObservabilitySystem { _, _ in }
// Compute the generated files based on all results we have computed so far.
(pluginDerivedSources, pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles(
let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles(
target: module,
toolsVersion: package.manifest.toolsVersion,
additionalFileRules: additionalFileRules,
Expand All @@ -800,6 +800,11 @@ extension BuildPlan {
prebuildCommandResults: [],
observabilityScope: observability.topScope
)
pluginDerivedSources = Sources(
paths: pluginGeneratedFiles.sources.map(\.self),
root: buildParameters.dataPath
)
pluginDerivedResources = pluginGeneratedFiles.resources.values.map(\.self)
} else {
pluginDerivedSources = .init(paths: [], root: package.path)
pluginDerivedResources = []
Expand Down
91 changes: 80 additions & 11 deletions Sources/PackageLoading/TargetSourcesBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ public struct TargetSourcesBuilder {
/// Returns the `Resource` file associated with a file and a particular rule, if there is one.
private static func resource(for path: Basics.AbsolutePath, with rule: FileRuleDescription.Rule, defaultLocalization: String?, targetName: String, targetPath: Basics.AbsolutePath, observabilityScope: ObservabilityScope) -> Resource? {
switch rule {
case .compile, .header, .none, .modulemap, .ignored:
case .compile, .header, .none, .modulemap, .apinotes, .ignored:
return nil
case .processResource:
let implicitLocalization: String? = {
Expand Down Expand Up @@ -519,14 +519,21 @@ public struct TargetSourcesBuilder {
return contents
}

public static func computeContents(for generatedFiles: [Basics.AbsolutePath], toolsVersion: ToolsVersion, additionalFileRules: [FileRuleDescription], defaultLocalization: String?, targetName: String, targetPath: Basics.AbsolutePath, observabilityScope: ObservabilityScope) -> (sources: [Basics.AbsolutePath], resources: [Resource]) {
var sources = [Basics.AbsolutePath]()
var resources = [Resource]()
public static func computeContents(
for generatedFiles: [Basics.AbsolutePath],
toolsVersion: ToolsVersion,
additionalFileRules: [FileRuleDescription],
defaultLocalization: String?,
targetName: String,
targetPath: Basics.AbsolutePath,
observabilityScope: ObservabilityScope) -> GeneratedFiles
{
var files = GeneratedFiles()

generatedFiles.forEach { absPath in
// 5.6 handled treated all generated files as sources.
if toolsVersion <= .v5_6 {
sources.append(absPath)
files.sources.insert(absPath)
return
}

Expand All @@ -539,27 +546,76 @@ public struct TargetSourcesBuilder {

switch rule {
case .compile:
if absPath.extension == "swift" {
sources.append(absPath)
if absPath.extension == "swift" || toolsVersion >= .v6_3 {
files.sources.insert(absPath)
} else {
observabilityScope.emit(warning: "Only Swift is supported for generated plugin source files at this time: \(absPath)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"at this time" is weird now; this should instead mention that it's not supported in the current tools version

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's a weird statement. I guess Boris was just trying to give users hope :).

}
case .copyResource, .processResource, .embedResourceInCode:
if let resource = Self.resource(for: absPath, with: rule, defaultLocalization: defaultLocalization, targetName: targetName, targetPath: targetPath, observabilityScope: observabilityScope) {
resources.append(resource)
files.resources[resource.path] = resource
} else {
// If this is reached, `TargetSourcesBuilder` already emitted a diagnostic, so we can ignore this case here.
}
case .header:
observabilityScope.emit(warning: "Headers generated by plugins are not supported at this time: \(absPath)")
if toolsVersion >= .v6_3 {
files.headers.insert(absPath)
} else {
observabilityScope.emit(warning: "Headers generated by plugins are not supported at this time: \(absPath)")
}
case .modulemap:
observabilityScope.emit(warning: "Module maps generated by plugins are not supported at this time: \(absPath)")
if toolsVersion >= .v6_3 {
files.moduleMaps.insert(absPath)
} else {
observabilityScope.emit(warning: "Module maps generated by plugins are not supported at this time: \(absPath)")
}
case .apinotes:
if toolsVersion >= .v6_3 {
files.apiNotes.insert(absPath)
} else {
observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)")
}
case .ignored, .none:
break
}
}

return (sources, resources)
return files
}
}

public struct GeneratedFiles {
public var sources: Set<Basics.AbsolutePath>
// Order matters with the header search paths
public var headerSearchPaths: [Basics.AbsolutePath]
public var headers: Set<Basics.AbsolutePath>
public var moduleMaps: Set<Basics.AbsolutePath>
public var apiNotes: Set<Basics.AbsolutePath>
public var resources: [Basics.AbsolutePath: Resource]

public init(
sources: Set<Basics.AbsolutePath> = [],
headerSearchPaths: [Basics.AbsolutePath] = [],
headers: Set<Basics.AbsolutePath> = [],
moduleMaps: Set<Basics.AbsolutePath> = [],
apiNotes: Set<Basics.AbsolutePath> = [],
resources: [Basics.AbsolutePath: Resource] = [:])
{
self.sources = sources
self.headerSearchPaths = headerSearchPaths
self.headers = headers
self.moduleMaps = moduleMaps
self.apiNotes = apiNotes
self.resources = resources
}

public mutating func merge(_ other: GeneratedFiles) {
sources.formUnion(other.sources)
headerSearchPaths.append(contentsOf: other.headerSearchPaths)
headers.formUnion(other.headers)
moduleMaps.formUnion(other.moduleMaps)
apiNotes.formUnion(other.apiNotes)
resources.merge(other.resources, uniquingKeysWith: { winner, _ in winner })
}
}

Expand Down Expand Up @@ -589,6 +645,9 @@ public struct FileRuleDescription: Sendable {
/// A header file.
case header

/// An apinotes file, needs to be located in same directory as module map
case apinotes

/// Indicates that the file should be treated as ignored, without causing an unhandled-file warning.
case ignored

Expand Down Expand Up @@ -661,6 +720,15 @@ public struct FileRuleDescription: Sendable {
)
}()

/// the rule for detecting apinotes files.
public static let apinotes: FileRuleDescription = {
.init(
rule: .apinotes,
toolsVersion: .v6_3,
fileTypes: ["apinotes"]
)
}()

/// The rule for detecting header files.
public static let header: FileRuleDescription = {
.init(
Expand Down Expand Up @@ -748,6 +816,7 @@ public struct FileRuleDescription: Sendable {
clang,
asm,
modulemap,
apinotes,
header,
]

Expand Down
1 change: 1 addition & 0 deletions Sources/PackageModel/ToolsVersion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public struct ToolsVersion: Equatable, Hashable, Codable, Sendable {
public static let v6_0 = ToolsVersion(version: "6.0.0")
public static let v6_1 = ToolsVersion(version: "6.1.0")
public static let v6_2 = ToolsVersion(version: "6.2.0")
public static let v6_3 = ToolsVersion(version: "6.3.0")
public static let vNext = ToolsVersion(version: "999.0.0")

/// The current tools version in use.
Expand Down
57 changes: 30 additions & 27 deletions Sources/SPMBuildCore/Plugins/PluginInvocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -611,41 +611,44 @@ extension ModulesGraph {
buildToolPluginInvocationResults: [BuildToolPluginInvocationResult],
prebuildCommandResults: [CommandPluginResult],
observabilityScope: ObservabilityScope
) -> (pluginDerivedSources: Sources, pluginDerivedResources: [Resource]) {
var pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath)

) -> GeneratedFiles {
// Add any derived files that were declared for any commands from plugin invocations.
var pluginDerivedFiles = [AbsolutePath]()
for command in buildToolPluginInvocationResults.reduce([], { $0 + $1.buildCommands }) {
for absPath in command.outputFiles {
pluginDerivedFiles.append(absPath)
var generatedFiles = GeneratedFiles()

for result in buildToolPluginInvocationResults {
var files = TargetSourcesBuilder.computeContents(
for: result.buildCommands.flatMap(\.outputFiles),
toolsVersion: toolsVersion,
additionalFileRules: additionalFileRules,
defaultLocalization: target.defaultLocalization,
targetName: target.name,
targetPath: target.underlying.path,
observabilityScope: observabilityScope
)
generatedFiles.merge(files)
if !files.headers.isEmpty || !files.moduleMaps.isEmpty {
// Add plugin output directory as include path
// TODO: plugins should be able to explicity add header search paths to the target
if !generatedFiles.headerSearchPaths.contains(result.pluginOutputDirectory) {
generatedFiles.headerSearchPaths.append(result.pluginOutputDirectory)
}
}
}

// Add any derived files that were discovered from output directories of prebuild commands.
for result in prebuildCommandResults {
for path in result.derivedFiles {
pluginDerivedFiles.append(path)
}
}

// Let `TargetSourcesBuilder` compute the treatment of plugin generated files.
let (derivedSources, derivedResources) = TargetSourcesBuilder.computeContents(
for: pluginDerivedFiles,
toolsVersion: toolsVersion,
additionalFileRules: additionalFileRules,
defaultLocalization: target.defaultLocalization,
targetName: target.name,
targetPath: target.underlying.path,
observabilityScope: observabilityScope
)
let pluginDerivedResources = derivedResources
derivedSources.forEach { absPath in
let relPath = absPath.relative(to: pluginDerivedSources.root)
pluginDerivedSources.relativePaths.append(relPath)
generatedFiles.merge(TargetSourcesBuilder.computeContents(
for: result.derivedFiles,
toolsVersion: toolsVersion,
additionalFileRules: additionalFileRules,
defaultLocalization: target.defaultLocalization,
targetName: target.name,
targetPath: target.underlying.path,
observabilityScope: observabilityScope
))
}

return (pluginDerivedSources, pluginDerivedResources)
return generatedFiles
}
}

Expand Down
10 changes: 8 additions & 2 deletions Sources/SwiftBuildSupport/PIFBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,15 +265,15 @@ public final class PIFBuilder {
// to the temporary directory).
let readOnlyDirectories = [package.path]

// In tools version 6.0 and newer, we vend the list of files generated by previous plugins.

let pluginDerivedSources: Sources
let pluginDerivedResources: [Resource]
if package.manifest.toolsVersion >= .v6_0 {
// Set up dummy observability because we don't want to emit diagnostics for this before the actual
// build.
let observability = ObservabilitySystem { _, _ in }
// Compute the generated files based on all results we have computed so far.
(pluginDerivedSources, pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles(
let pluginGeneratedFiles = ModulesGraph.computePluginGeneratedFiles(
target: module,
toolsVersion: package.manifest.toolsVersion,
additionalFileRules: self.parameters.additionalFileRules,
Expand All @@ -282,6 +282,11 @@ public final class PIFBuilder {
prebuildCommandResults: [],
observabilityScope: observability.topScope
)
pluginDerivedSources = Sources(
paths: pluginGeneratedFiles.sources.map(\.self),
root: buildParameters.dataPath
)
pluginDerivedResources = pluginGeneratedFiles.resources.values.map(\.self)
} else {
pluginDerivedSources = .init(paths: [], root: package.path)
pluginDerivedResources = []
Expand Down Expand Up @@ -360,6 +365,7 @@ public final class PIFBuilder {
arguments: buildCommand.configuration.arguments,
environment: .init(newEnv),
workingDir: package.path,
pluginOutputDir: pluginOutputDir,
inputPaths: buildCommand.inputFiles,
outputPaths: buildCommand.outputFiles.map(\.pathString),
sandboxProfile:
Expand Down
3 changes: 3 additions & 0 deletions Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ extension PackagePIFBuilder {
public var arguments: [String]
public var environment: [String: String]
public var workingDir: AbsolutePath?
public var pluginOutputDir: AbsolutePath
public var inputPaths: [AbsolutePath] = []

/// Output paths can contain references with un-resolved paths (e.g. "$(DERIVED_FILE_DIR)/myOutput.txt")
Expand All @@ -76,6 +77,7 @@ extension PackagePIFBuilder {
arguments: [String],
environment: [String: String],
workingDir: AbsolutePath?,
pluginOutputDir: AbsolutePath,
inputPaths: [AbsolutePath],
outputPaths: [String],
sandboxProfile: SandboxProfile?
Expand All @@ -85,6 +87,7 @@ extension PackagePIFBuilder {
self.arguments = arguments
self.environment = environment
self.workingDir = workingDir
self.pluginOutputDir = pluginOutputDir
self.inputPaths = inputPaths
self.outputPaths = outputPaths
self.sandboxProfile = sandboxProfile
Expand Down
Loading