Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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 pluginTargetFiles = ModulesGraph.computePluginGeneratedFiles(
target: target,
toolsVersion: toolsVersion,
additionalFileRules: additionalFileRules,
Expand All @@ -165,6 +165,22 @@ public final class ClangModuleBuildDescription {
prebuildCommandResults: prebuildCommandResults,
observabilityScope: observabilityScope
)
self.pluginDerivedSources = Sources(
paths: pluginTargetFiles.sources.map(\.self),
root: buildParameters.dataPath
)
self.pluginDerivedResources = pluginTargetFiles.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 pluginTargetFiles.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 pluginTargetFiles.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 pluginTargetFiles.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 pluginTargetFiles = 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: pluginTargetFiles.sources.map(\.self),
root: buildParameters.dataPath
)
self.pluginDerivedResources = pluginTargetFiles.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 pluginTargetFiles.headers {
observabilityScope.emit(warning: "Module maps generated by plugins are not supported at this time: \(absPath)")
}
for absPath in pluginTargetFiles.moduleMaps {
observabilityScope.emit(warning: "API Notes generated by plugins are not supported at this time: \(absPath)")
}
for absPath in pluginTargetFiles.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
11 changes: 8 additions & 3 deletions Sources/Build/BuildPlan/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -785,13 +785,13 @@ extension BuildPlan {

// In tools version 6.0 and newer, we vend the list of files generated by previous plugins.
let pluginDerivedSources: Sources
let pluginDerivedResources: [Resource]
let pluginDerivedResources: [Basics.AbsolutePath]
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 pluginTargetFiles = 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: pluginTargetFiles.sources.map(\.self),
root: buildParameters.dataPath
)
pluginDerivedResources = pluginTargetFiles.resources.keys.map(\.self)
} else {
pluginDerivedSources = .init(paths: [], root: package.path)
pluginDerivedResources = []
Expand All @@ -811,7 +816,7 @@ extension BuildPlan {
package: package,
target: module,
pluginGeneratedSources: pluginDerivedSources.paths,
pluginGeneratedResources: pluginDerivedResources.map(\.path)
pluginGeneratedResources: pluginDerivedResources
),
buildEnvironment: buildParameters.buildEnvironment,
scriptRunner: configuration.scriptRunner,
Expand Down
90 changes: 79 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,75 @@ 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>
public var headerSearchPaths: Set<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: Set<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.formUnion(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 +644,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 +719,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 +815,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
61 changes: 31 additions & 30 deletions Sources/SPMBuildCore/Plugins/PluginInvocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -611,41 +611,42 @@ 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
)
if !files.headers.isEmpty || !files.moduleMaps.isEmpty {
// Add plugin output directory as include path
// FIXME: plugins should be able to explicity add header search paths to the target
files.headerSearchPaths.insert(result.pluginOutputDirectory)
}
generatedFiles.merge(files)
}

// 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 Expand Up @@ -774,21 +775,21 @@ public struct BuildToolPluginInvocationResult {
public var prebuildCommands: [PrebuildCommand]

/// A command to incorporate into the build graph so that it runs during the build whenever it needs to.
public struct BuildCommand {
public struct BuildCommand: Equatable {
public var configuration: CommandConfiguration
public var inputFiles: [AbsolutePath]
public var outputFiles: [AbsolutePath]
}

/// A command to run before the start of every build.
public struct PrebuildCommand {
public struct PrebuildCommand: Equatable {
// TODO: In the future these should be folded into regular build commands when the build system can handle not knowing the names of all the outputs before the command runs.
public var configuration: CommandConfiguration
public var outputFilesDirectory: AbsolutePath
}

/// Launch configuration of a command that can be run (including a display name to show in logs etc).
public struct CommandConfiguration {
public struct CommandConfiguration: Equatable {
public var displayName: String?
public var executable: AbsolutePath
public var arguments: [String]
Expand Down
Loading