Skip to content
Merged
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
20 changes: 20 additions & 0 deletions Fixtures/Macros/MacroPackage/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// swift-tools-version: 999.0
import PackageDescription
import CompilerPluginSupport

let settings: [SwiftSetting] = [
.enableExperimentalFeature("Macros"),
.unsafeFlags(["-Xfrontend", "-dump-macro-expansions"])
]

let package = Package(
name: "MacroPackage",
platforms: [
.macOS(.v10_15),
],
targets: [
.macro(name: "MacroImpl"),
.target(name: "MacroDef", dependencies: ["MacroImpl"], swiftSettings: settings),
.executableTarget(name: "MacroClient", dependencies: ["MacroDef"], swiftSettings: settings),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import MacroDef

struct Font: ExpressibleByFontLiteral {
init(fontLiteralName: String, size: Int, weight: MacroDef.FontWeight) {
}
}

let _: Font = #fontLiteral(name: "Comic Sans", size: 14, weight: .thin)
16 changes: 16 additions & 0 deletions Fixtures/Macros/MacroPackage/Sources/MacroDef/definition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

public enum FontWeight {
case thin
case normal
case medium
case semiBold
case bold
}

public protocol ExpressibleByFontLiteral {
init(fontLiteralName: String, size: Int, weight: FontWeight)
}

/// Font literal similar to, e.g., #colorLiteral.
@freestanding(expression) public macro fontLiteral<T>(name: String, size: Int, weight: FontWeight) -> T = #externalMacro(module: "MacroImpl", type: "FontLiteralMacro")
where T: ExpressibleByFontLiteral
39 changes: 39 additions & 0 deletions Fixtures/Macros/MacroPackage/Sources/MacroImpl/macro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros

/// Implementation of the `#fontLiteral` macro, which is similar in spirit
/// to the built-in expressions `#colorLiteral`, `#imageLiteral`, etc., but in
/// a small macro.
public struct FontLiteralMacro: ExpressionMacro {
public static func expansion(
of macro: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
let argList = replaceFirstLabel(
of: macro.argumentList,
with: "fontLiteralName"
)
let initSyntax: ExprSyntax = ".init(\(argList))"
if let leadingTrivia = macro.leadingTrivia {
return initSyntax.with(\.leadingTrivia, leadingTrivia)
}
return initSyntax
}
}

/// Replace the label of the first element in the tuple with the given
/// new label.
private func replaceFirstLabel(
of tuple: TupleExprElementListSyntax,
with newLabel: String
) -> TupleExprElementListSyntax {
guard let firstElement = tuple.first else {
return tuple
}

return tuple.replacing(
childAt: 0,
with: firstElement.with(\.label, .identifier(newLabel))
)
}
19 changes: 18 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ let package = Package(
.library(
name: "PackageDescription",
type: .dynamic,
targets: ["PackageDescription"]
targets: ["PackageDescription", "CompilerPluginSupport"]
),
.library(
name: "PackagePlugin",
Expand Down Expand Up @@ -515,6 +515,18 @@ let package = Package(
.unsafeFlags(["-Xlinker", "-rpath", "-Xlinker", "@executable_path/../../../lib/swift/macosx"], .when(platforms: [.macOS])),
]),

// MARK: Support for Swift macros, should eventually move to a plugin-based solution

.target(
name: "CompilerPluginSupport",
dependencies: ["PackageDescription"],
exclude: ["CMakeLists.txt"],
swiftSettings: [
.unsafeFlags(["-package-description-version", "999.0"]),
.unsafeFlags(["-enable-library-evolution"]),
]
),

// MARK: Additional Test Dependencies

.target(
Expand Down Expand Up @@ -726,3 +738,8 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
.package(path: "../swift-collections"),
]
}

// Enable building macros as dynamic libraries by default for bring-up.
for target in package.targets.filter({ $0.type == .regular || $0.type == .test }) {
target.swiftSettings = (target.swiftSettings ?? []) + [ .define("BUILD_MACROS_AS_DYLIBS") ]
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This essentially flips the logic for now since dylib macros are the only ones which can be tested end-to-end right now.

25 changes: 23 additions & 2 deletions Sources/Build/BuildDescription/ProductBuildDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,21 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription

let containsSwiftTargets = self.product.containsSwiftTargets

let derivedProductType: ProductType
switch self.product.type {
case .macro:
#if BUILD_MACROS_AS_DYLIBS
derivedProductType = .library(.dynamic)
#else
derivedProductType = .executable
#endif
default:
derivedProductType = self.product.type
}

switch derivedProductType {
case .macro:
throw InternalError("macro not supported") // should never be reached
case .library(.automatic):
throw InternalError("automatic library not supported")
case .library(.static):
Expand Down Expand Up @@ -221,7 +235,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription
// version of the package that defines the executable product.
let executableTarget = try product.executableTarget
if executableTarget.underlyingTarget is SwiftTarget, self.toolsVersion >= .v5_5,
self.buildParameters.canRenameEntrypointFunctionName
self.buildParameters.canRenameEntrypointFunctionName, executableTarget.underlyingTarget.type != .macro
Comment thread
neonichu marked this conversation as resolved.
{
if let flags = buildParameters.linkerFlagsForRenamingMainFunction(of: executableTarget) {
args += flags
Expand All @@ -247,7 +261,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription
switch self.product.type {
case .library(let type):
useStdlibRpath = type == .dynamic
case .test, .executable, .snippet:
case .test, .executable, .snippet, .macro:
useStdlibRpath = true
case .plugin:
throw InternalError("unexpectedly asked to generate linker arguments for a plugin product")
Expand Down Expand Up @@ -302,6 +316,13 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription
args += ["-L", toolchainLibDir.pathString]
}

// Library search path for the toolchain's copy of SwiftSyntax.
#if BUILD_MACROS_AS_DYLIBS
if product.type == .macro {
args += try ["-L", buildParameters.toolchain.hostLibDir.pathString]
}
#endif

return args
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public final class SwiftTargetBuildDescription {
var moduleOutputPath: AbsolutePath {
// If we're an executable and we're not allowing test targets to link against us, we hide the module.
let allowLinkingAgainstExecutables = (buildParameters.triple.isDarwin() || self.buildParameters.triple
.isLinux() || self.buildParameters.triple.isWindows()) && self.toolsVersion >= .v5_5
.isLinux() || self.buildParameters.triple.isWindows()) && self.toolsVersion >= .v5_5 && target.type != .macro
let dirPath = (target.type == .executable && !allowLinkingAgainstExecutables) ? self.tempsPath : self
.buildParameters.buildPath
return dirPath.appending(component: self.target.c99name + ".swiftmodule")
Expand Down Expand Up @@ -213,6 +213,9 @@ public final class SwiftTargetBuildDescription {
/// The results of running any prebuild commands for this target.
public let prebuildCommandResults: [PrebuildCommandResult]

/// Any macro products that this target requires to build.
public let requiredMacroProducts: [ResolvedProduct]

/// ObservabilityScope with which to emit diagnostics
private let observabilityScope: ObservabilityScope

Expand All @@ -225,6 +228,7 @@ public final class SwiftTargetBuildDescription {
buildParameters: BuildParameters,
buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [],
prebuildCommandResults: [PrebuildCommandResult] = [],
requiredMacroProducts: [ResolvedProduct] = [],
testTargetRole: TestTargetRole? = nil,
fileSystem: FileSystem,
observabilityScope: ObservabilityScope
Expand All @@ -250,6 +254,7 @@ public final class SwiftTargetBuildDescription {
self.pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath)
self.buildToolPluginInvocationResults = buildToolPluginInvocationResults
self.prebuildCommandResults = prebuildCommandResults
self.requiredMacroProducts = requiredMacroProducts
self.observabilityScope = observabilityScope

// Add any derived files that were declared for any commands from plugin invocations.
Expand Down Expand Up @@ -381,6 +386,22 @@ public final class SwiftTargetBuildDescription {
try self.fileSystem.writeIfChanged(path: path, bytes: stream.bytes)
}

private func macroArguments() throws -> [String] {
var args = [String]()

#if BUILD_MACROS_AS_DYLIBS
self.requiredMacroProducts.forEach { macro in
args += ["-Xfrontend", "-load-plugin-library", "-Xfrontend", self.buildParameters.binaryPath(for: macro).pathString]
}
#else
if self.requiredMacroProducts.isEmpty == false {
throw InternalError("building macros is not supported yet")
}
#endif

return args
}

/// The arguments needed to compile this target.
public func compileArguments() throws -> [String] {
var args = [String]()
Expand Down Expand Up @@ -480,6 +501,8 @@ public final class SwiftTargetBuildDescription {
}
}

args += try self.macroArguments()

return args
}

Expand Down Expand Up @@ -571,6 +594,7 @@ public final class SwiftTargetBuildDescription {
result += try self.moduleCacheArgs
result += self.stdlibArguments
result += try self.buildSettingsFlags()
result += try self.macroArguments()

return result
}
Expand Down Expand Up @@ -626,6 +650,7 @@ public final class SwiftTargetBuildDescription {
result += try self.buildSettingsFlags()
result += self.buildParameters.toolchain.extraFlags.swiftCompilerFlags
result += self.buildParameters.swiftCompilerFlags
result += try self.macroArguments()
return result
}

Expand Down Expand Up @@ -740,6 +765,13 @@ public final class SwiftTargetBuildDescription {
// Other C flags.
flags += scope.evaluate(.OTHER_CFLAGS).flatMap { ["-Xcc", $0] }

// Include path for the toolchain's copy of SwiftSyntax.
#if BUILD_MACROS_AS_DYLIBS
if target.type == .macro {
flags += try ["-I", self.buildParameters.toolchain.hostLibDir.pathString]
}
#endif

return flags
}

Expand Down
3 changes: 3 additions & 0 deletions Sources/Build/BuildOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,9 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
if importedTarget.target.type == .executable {
return "module '\(importedModule)' is the main module of an executable, and cannot be imported by tests and other targets"
}
if importedTarget.target.type == .macro {
return "module '\(importedModule)' is a macro, and cannot be imported by tests and other targets"
}

// Here we can add more checks in the future.
}
Expand Down
53 changes: 33 additions & 20 deletions Sources/Build/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,30 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
self.fileSystem = fileSystem
self.observabilityScope = observabilityScope.makeChildScope(description: "Build Plan")

var productMap: [ResolvedProduct: ProductBuildDescription] = [:]
// Create product description for each product we have in the package graph that is eligible.
for product in graph.allProducts where product.shouldCreateProductDescription {
guard let package = graph.package(for: product) else {
throw InternalError("unknown package for \(product)")
}
// Determine the appropriate tools version to use for the product.
// This can affect what flags to pass and other semantics.
let toolsVersion = package.manifest.toolsVersion
productMap[product] = try ProductBuildDescription(
package: package,
product: product,
toolsVersion: toolsVersion,
buildParameters: buildParameters,
fileSystem: fileSystem,
observabilityScope: observabilityScope
)
}
let macroProductsByTarget = productMap.keys.filter { $0.type == .macro }.reduce(into: [ResolvedTarget: ResolvedProduct]()) {
if let target = $1.targets.first {
$0[target] = $1
}
}

// Create build target description for each target which we need to plan.
// Plugin targets are noted, since they need to be compiled, but they do
// not get directly incorporated into the build description that will be
Expand Down Expand Up @@ -448,6 +472,9 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
guard let package = graph.package(for: target) else {
throw InternalError("package not found for \(target)")
}

let requiredMacroProducts = try target.recursiveTargetDependencies().filter { $0.underlyingTarget.type == .macro }.compactMap { macroProductsByTarget[$0] }

targetMap[target] = try .swift(SwiftTargetBuildDescription(
package: package,
target: target,
Expand All @@ -456,6 +483,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
buildParameters: buildParameters,
buildToolPluginInvocationResults: buildToolPluginInvocationResults[target] ?? [],
prebuildCommandResults: prebuildCommandResults[target] ?? [],
requiredMacroProducts: requiredMacroProducts,
fileSystem: fileSystem,
observabilityScope: observabilityScope)
)
Expand Down Expand Up @@ -509,25 +537,6 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
}
}

var productMap: [ResolvedProduct: ProductBuildDescription] = [:]
// Create product description for each product we have in the package graph that is eligible.
for product in graph.allProducts where product.shouldCreateProductDescription {
guard let package = graph.package(for: product) else {
throw InternalError("unknown package for \(product)")
}
// Determine the appropriate tools version to use for the product.
// This can affect what flags to pass and other semantics.
let toolsVersion = package.manifest.toolsVersion
productMap[product] = try ProductBuildDescription(
package: package,
product: product,
toolsVersion: toolsVersion,
buildParameters: buildParameters,
fileSystem: fileSystem,
observabilityScope: observabilityScope
)
}

Comment thread
neonichu marked this conversation as resolved.
self.productMap = productMap
self.targetMap = targetMap
self.pluginDescriptions = pluginDescriptions
Expand Down Expand Up @@ -701,7 +710,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
switch product.type {
case .library(.automatic), .library(.static), .plugin:
return product.targets.map { .target($0, conditions: []) }
case .library(.dynamic), .test, .executable, .snippet:
case .library(.dynamic), .test, .executable, .snippet, .macro:
return []
}
}
Expand Down Expand Up @@ -759,6 +768,10 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
case.unknown:
throw InternalError("unknown binary target '\(target.name)' type")
}
case .macro:
if product.type == .macro {
staticTargets.append(target)
}
case .plugin:
continue
}
Expand Down
Loading