Skip to content

Add an ExtensionEvaluator for evaluating extensions in a package graph #3286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 18, 2021
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
3 changes: 3 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ let package = Package(
.testTarget(
name: "PackageDescription4Tests",
dependencies: ["PackageDescription"]),
.testTarget(
name: "SPMBuildCoreTests",
dependencies: ["SPMBuildCore", "SPMTestSupport"]),
.testTarget(
name: "PackageLoadingTests",
dependencies: ["PackageLoading", "SPMTestSupport"],
Expand Down
2 changes: 2 additions & 0 deletions Sources/PackageGraph/PackageGraph+Loading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extension PackageGraph {
diagnostics: DiagnosticsEngine,
fileSystem: FileSystem = localFileSystem,
shouldCreateMultipleTestProducts: Bool = false,
allowExtensionTargets: Bool = false,
createREPLProduct: Bool = false
) throws -> PackageGraph {

Expand Down Expand Up @@ -111,6 +112,7 @@ extension PackageGraph {
fileSystem: fileSystem,
diagnostics: diagnostics,
shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts,
allowExtensionTargets: allowExtensionTargets,
createREPLProduct: manifest.packageKind == .root ? createREPLProduct : false
)
let package = try builder.construct()
Expand Down
7 changes: 4 additions & 3 deletions Sources/PackageLoading/PackageBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ public final class PackageBuilder {
/// Temporary parameter controlling whether to warn about implicit executable targets when tools version is 5.4.
private let warnAboutImplicitExecutableTargets: Bool

/// Temporary parameter controlling whether to allow package extension targets (durning bring-up, before proposal is accepted).
/// Temporary parameter controlling whether to allow package extension targets (during bring-up, before proposal is accepted).
/// This is set if SWIFTPM_ENABLE_EXTENSION_TARGETS=1 or if the feature is enabled in the initializer (for use by unit tests).
private let allowExtensionTargets: Bool

/// Create the special REPL product for this package.
Expand Down Expand Up @@ -271,7 +272,7 @@ public final class PackageBuilder {
diagnostics: DiagnosticsEngine,
shouldCreateMultipleTestProducts: Bool = false,
warnAboutImplicitExecutableTargets: Bool = true,
allowExtensionTargets: Bool = (ProcessEnv.vars["SWIFTPM_ENABLE_EXTENSION_TARGETS"] == "1"),
allowExtensionTargets: Bool = false,
createREPLProduct: Bool = false
) {
self.manifest = manifest
Expand All @@ -283,7 +284,7 @@ public final class PackageBuilder {
self.fileSystem = fileSystem
self.diagnostics = diagnostics
self.shouldCreateMultipleTestProducts = shouldCreateMultipleTestProducts
self.allowExtensionTargets = allowExtensionTargets
self.allowExtensionTargets = allowExtensionTargets || ProcessEnv.vars["SWIFTPM_ENABLE_EXTENSION_TARGETS"] == "1"
self.createREPLProduct = createREPLProduct
self.warnAboutImplicitExecutableTargets = warnAboutImplicitExecutableTargets
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/SPMBuildCore/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
# Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
Expand All @@ -12,6 +12,7 @@ add_library(SPMBuildCore
BuildSystemCommand.swift
BuildSystemDelegate.swift
BuiltTestProduct.swift
ExtensionEvaluator.swift
Sanitizers.swift
Toolchain.swift)
# NOTE(compnerd) workaround for CMake not setting up include flags yet
Expand Down
330 changes: 330 additions & 0 deletions Sources/SPMBuildCore/ExtensionEvaluator.swift

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Sources/SPMTestSupport/PackageGraphTester.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ public final class ResolvedTargetResult {
}
body(ResolvedTargetDependencyResult(dependency))
}

public func check(type: Target.Kind, file: StaticString = #file, line: UInt = #line) {
XCTAssertEqual(type, target.type, file: file, line: line)
}
}

public final class ResolvedTargetDependencyResult {
Expand Down
2 changes: 2 additions & 0 deletions Sources/SPMTestSupport/misc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ public func loadPackageGraph(
manifests: [Manifest],
explicitProduct: String? = nil,
shouldCreateMultipleTestProducts: Bool = false,
allowExtensionTargets: Bool = false,
createREPLProduct: Bool = false
) throws -> PackageGraph {
let rootManifests = manifests.filter { $0.packageKind == .root }
Expand All @@ -236,6 +237,7 @@ public func loadPackageGraph(
diagnostics: diagnostics,
fileSystem: fs,
shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts,
allowExtensionTargets: allowExtensionTargets,
createREPLProduct: createREPLProduct
)
}
172 changes: 172 additions & 0 deletions Tests/SPMBuildCoreTests/ExtensionEvaluationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 2021 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See http://swift.org/LICENSE.txt for license information
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

import XCTest
import TSCBasic

import PackageGraph
import PackageModel
@testable import SPMBuildCore
import SPMTestSupport


class ExtensionEvaluationTests: XCTestCase {

func testBasics() throws {
// Construct a canned file system and package graph with a single package and a library that uses an extension that uses a tool.
let fileSystem = InMemoryFileSystem(emptyFiles:
"/Foo/Sources/Foo/source.swift",
"/Foo/Sources/Foo/SomeFile.abc",
"/Foo/Sources/FooExt/source.swift",
"/Foo/Sources/FooTool/source.swift"
)
let diagnostics = DiagnosticsEngine()
let graph = try loadPackageGraph(fs: fileSystem, diagnostics: diagnostics,
manifests: [
Manifest.createV4Manifest(
name: "Foo",
path: "/Foo",
packageKind: .root,
packageLocation: "/Foo",
products: [
ProductDescription(
name: "Foo",
type: .library(.dynamic),
targets: ["Foo"]
)
],
targets: [
TargetDescription(
name: "Foo",
dependencies: ["FooExt"],
type: .regular
),
TargetDescription(
name: "FooExt",
dependencies: ["FooTool"],
type: .extension,
extensionCapability: .buildTool
),
TargetDescription(
name: "FooTool",
dependencies: [],
type: .executable
),
]
)
],
allowExtensionTargets: true
)

// Check the basic integrity before running extensions.
XCTAssertNoDiagnostics(diagnostics)
PackageGraphTester(graph) { graph in
graph.check(packages: "Foo")
graph.check(targets: "Foo", "FooExt", "FooTool")
graph.checkTarget("Foo") { target in
target.check(dependencies: "FooExt")
}
graph.checkTarget("FooExt") { target in
target.check(type: .extension)
target.check(dependencies: "FooTool")
}
graph.checkTarget("FooTool") { target in
target.check(type: .executable)
}
}

// A fake ExtensionRunner that just checks the input conditions and returns canned output.
struct MockExtensionRunner: ExtensionRunner {
func runExtension(
sources: Sources,
inputJSON: Data,
diagnostics: DiagnosticsEngine,
fileSystem: FileSystem
) throws -> (outputJSON: Data, stdoutText: Data) {
// Check that we were given the right sources.
XCTAssertEqual(sources.root, AbsolutePath("/Foo/Sources/FooExt"))
XCTAssertEqual(sources.relativePaths, [RelativePath("source.swift")])

// Deserialize and check the input.
let decoder = JSONDecoder()
let context = try decoder.decode(ExtensionEvaluationInput.self, from: inputJSON)
XCTAssertEqual(context.targetName, "Foo")

// Emit and return a serialized output ExtensionEvaluationResult JSON.
let encoder = JSONEncoder()
let result = ExtensionEvaluationOutput(
version: 1,
diagnostics: [
.init(
severity: .warning,
message: "A warning",
file: "/Foo/Sources/Foo/SomeFile.abc",
line: 42
)
],
commands: [
.init(
displayName: "Do something",
executable: "/bin/FooTool",
arguments: ["-c", "/Foo/Sources/Foo/SomeFile.abc"],
workingDirectory: "/Foo/Sources/Foo",
environment: [
"X": "Y"
],
inputPaths: [],
outputPaths: [],
derivedSourcePaths: []
)
])
let outputJSON = try encoder.encode(result)
return (outputJSON: outputJSON, stdoutText: "Hello Extension!".data(using: .utf8)!)
}
}

// Construct a canned input and run extensions using our MockExtensionRunner().
let buildEnv = BuildEnvironment(platform: .macOS, configuration: .debug)
let execsDir = AbsolutePath("/Foo/.build/debug")
let outputDir = AbsolutePath("/Foo/.build")
let extRunner = MockExtensionRunner()
let results = try graph.evaluateExtensions(buildEnvironment: buildEnv, execsDir: execsDir, outputDir: outputDir, extensionRunner: extRunner, diagnostics: diagnostics, fileSystem: fileSystem)

// Check the canned output to make sure nothing was lost in transport.
XCTAssertNoDiagnostics(diagnostics)
XCTAssertEqual(results.count, 1)
let (evalTarget, evalResults) = try XCTUnwrap(results.first)
XCTAssertEqual(evalTarget.name, "Foo")

XCTAssertEqual(evalResults.count, 1)
let evalFirstResult = try XCTUnwrap(evalResults.first)
XCTAssertEqual(evalFirstResult.commands.count, 1)
let evalFirstCommand = try XCTUnwrap(evalFirstResult.commands.first)
if case .buildToolCommand(let name, let exec, let args, let wdir, let env, let inputs, let outputs, let derived) = evalFirstCommand {
XCTAssertEqual(name, "Do something")
XCTAssertEqual(exec, AbsolutePath("/bin/FooTool"))
XCTAssertEqual(args, ["-c", "/Foo/Sources/Foo/SomeFile.abc"])
XCTAssertEqual(wdir, AbsolutePath("/Foo/Sources/Foo"))
XCTAssertEqual(env, ["X": "Y"])
XCTAssertEqual(inputs, [])
XCTAssertEqual(outputs, [])
XCTAssertEqual(derived, [])
}
else {
XCTFail("The command provided by the extension didn't match expectations")
}

XCTAssertEqual(evalFirstResult.diagnostics.count, 1)
let evalFirstDiagnostic = try XCTUnwrap(evalFirstResult.diagnostics.first)
XCTAssertEqual(evalFirstDiagnostic.behavior, .warning)
XCTAssertEqual(evalFirstDiagnostic.message.text, "A warning")
XCTAssertEqual(evalFirstDiagnostic.location.description, "/Foo/Sources/Foo/SomeFile.abc:42")

XCTAssertEqual(evalFirstResult.textOutput, "Hello Extension!")
}
}