Skip to content

Commit

Permalink
Implementation of the conditional target dependency proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
hartbit committed Dec 2, 2019
1 parent 7ecc499 commit 6446f27
Show file tree
Hide file tree
Showing 17 changed files with 965 additions and 245 deletions.
5 changes: 5 additions & 0 deletions Sources/Build/BuildParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ public struct BuildParameters: Encodable {
/// module to finish building.
public var emitSwiftModuleSeparately: Bool

/// The current build environment.
public var buildEnvironment: BuildEnvironment {
BuildEnvironment(platform: currentPlatform, configuration: configuration)
}

public init(
dataPath: AbsolutePath,
configuration: BuildConfiguration,
Expand Down
53 changes: 31 additions & 22 deletions Sources/Build/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ extension BuildParameters {
}

/// The current platform we're building for.
fileprivate var currentPlatform: PackageModel.Platform {
var currentPlatform: PackageModel.Platform {
if self.triple.isDarwin() {
return .macOS
} else if self.triple.isAndroid() {
Expand All @@ -88,10 +88,7 @@ extension BuildParameters {

/// Returns the scoped view of build settings for a given target.
fileprivate func createScope(for target: ResolvedTarget) -> BuildSettings.Scope {
return BuildSettings.Scope(
target.underlyingTarget.buildSettings,
environment: BuildEnvironment(platform: currentPlatform, configuration: configuration)
)
return BuildSettings.Scope(target.underlyingTarget.buildSettings, environment: buildEnvironment)
}
}

Expand Down Expand Up @@ -148,6 +145,10 @@ public final class ClangTargetBuildDescription {
/// The build parameters.
let buildParameters: BuildParameters

var buildEnvironment: BuildEnvironment {
buildParameters.buildEnvironment
}

/// Path to the bundle generated for this module (if any).
var bundlePath: AbsolutePath? {
buildParameters.bundlePath(for: target)
Expand Down Expand Up @@ -1039,6 +1040,10 @@ public class BuildPlan {
/// The build parameters.
public let buildParameters: BuildParameters

private var buildEnvironment: BuildEnvironment {
buildParameters.buildEnvironment
}

/// The package graph.
public let graph: PackageGraph

Expand Down Expand Up @@ -1107,10 +1112,10 @@ public class BuildPlan {
let swiftTarget = SwiftTarget(
testDiscoverySrc: src,
name: testProduct.name,
dependencies: testProduct.underlyingProduct.targets)
dependencies: testProduct.underlyingProduct.targets.map({ .target($0, conditions: []) }))
let linuxMainTarget = ResolvedTarget(
target: swiftTarget,
dependencies: testProduct.targets.map(ResolvedTarget.Dependency.target)
dependencies: testProduct.targets.map({ .target($0, conditions: []) })
)

let target = try SwiftTargetBuildDescription(
Expand Down Expand Up @@ -1145,7 +1150,7 @@ public class BuildPlan {
for dependency in target.dependencies {
switch dependency {
case .target: break
case .product(let product):
case .product(let product, _):
if buildParameters.triple.isDarwin() {
BuildPlan.validateDeploymentVersionOfProductDependency(
product, forTarget: target, diagnostics: diagnostics)
Expand Down Expand Up @@ -1320,19 +1325,19 @@ public class BuildPlan {
) {

// Sort the product targets in topological order.
let nodes = product.targets.map(ResolvedTarget.Dependency.target)
let nodes: [ResolvedTarget.Dependency] = product.targets.map({ .target($0, conditions: []) })
let allTargets = try! topologicalSort(nodes, successors: { dependency in
switch dependency {
// Include all the depenencies of a target.
case .target(let target):
return target.dependencies
case .target(let target, _):
return target.dependencies.filter({ $0.satisfies(self.buildEnvironment) })

// For a product dependency, we only include its content only if we
// need to statically link it.
case .product(let product):
case .product(let product, _):
switch product.type {
case .library(.automatic), .library(.static):
return product.targets.map(ResolvedTarget.Dependency.target)
return product.targets.map({ .target($0, conditions: []) })
case .library(.dynamic), .test, .executable:
return []
}
Expand All @@ -1346,7 +1351,7 @@ public class BuildPlan {

for dependency in allTargets {
switch dependency {
case .target(let target):
case .target(let target, _):
switch target.type {
// Include executable and tests only if they're top level contents
// of the product. Otherwise they are just build time dependency.
Expand All @@ -1362,7 +1367,7 @@ public class BuildPlan {
systemModules.append(target)
}

case .product(let product):
case .product(let product, _):
// Add the dynamic products to array of libraries to link.
if product.type == .library(.dynamic) {
linkLibraries.append(product)
Expand All @@ -1381,10 +1386,11 @@ public class BuildPlan {

/// Plan a Clang target.
private func plan(clangTarget: ClangTargetBuildDescription) {
for dependency in clangTarget.target.recursiveDependencies() {
switch dependency.underlyingTarget {
let recursiveBuildTargets = clangTarget.target.recursiveBuildTargetDependencies(in: buildEnvironment)
for targetDependency in recursiveBuildTargets {
switch targetDependency.underlyingTarget {
case is SwiftTarget:
if case let .swift(dependencyTargetDescription)? = targetMap[dependency] {
if case let .swift(dependencyTargetDescription)? = targetMap[targetDependency] {
if let moduleMap = dependencyTargetDescription.moduleMap {
clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"]
}
Expand All @@ -1395,7 +1401,7 @@ public class BuildPlan {
clangTarget.additionalFlags += ["-I", target.includeDir.pathString]

// Add the modulemap of the dependency if it has one.
if case let .clang(dependencyTargetDescription)? = targetMap[dependency] {
if case let .clang(dependencyTargetDescription)? = targetMap[targetDependency] {
if let moduleMap = dependencyTargetDescription.moduleMap {
clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"]
}
Expand All @@ -1412,10 +1418,13 @@ public class BuildPlan {
private func plan(swiftTarget: SwiftTargetBuildDescription) throws {
// We need to iterate recursive dependencies because Swift compiler needs to see all the targets a target
// depends on.
for dependency in swiftTarget.target.recursiveDependencies() {
switch dependency.underlyingTarget {
let recursiveBuildTargets = swiftTarget.target
.recursiveBuildDependencies(in: buildEnvironment)
.compactMap({ $0.target })
for targetDependency in recursiveBuildTargets {
switch targetDependency.underlyingTarget {
case let underlyingTarget as ClangTarget where underlyingTarget.type == .library:
guard case let .clang(target)? = targetMap[dependency] else {
guard case let .clang(target)? = targetMap[targetDependency] else {
fatalError("unexpected clang target \(underlyingTarget)")
}
// Add the path to modulemap of the dependency. Currently we require that all Clang targets have a
Expand Down
13 changes: 7 additions & 6 deletions Sources/Build/ManifestBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class LLBuildManifestBuilder {

var buildConfig: String { buildParameters.configuration.dirname }
var buildParameters: BuildParameters { plan.buildParameters }
var buildEnvironment: BuildEnvironment { buildParameters.buildEnvironment }

/// Create a new builder with a build plan.
public init(_ plan: BuildPlan) {
Expand Down Expand Up @@ -259,12 +260,12 @@ extension LLBuildManifestBuilder {
}
}

for dependency in target.target.dependencies {
for dependency in target.target.buildDependencies(in: buildEnvironment) {
switch dependency {
case .target(let target):
case .target(let target, _):
addStaticTargetInputs(target)

case .product(let product):
case .product(let product, _):
switch product.type {
case .executable, .library(.dynamic):
// Establish a dependency on binary of the product.
Expand Down Expand Up @@ -350,12 +351,12 @@ extension LLBuildManifestBuilder {
}
}

for dependency in target.target.dependencies {
for dependency in target.target.buildDependencies(in: buildEnvironment) {
switch dependency {
case .target(let target):
case .target(let target, _):
addStaticTargetInputs(target)

case .product(let product):
case .product(let product, _):
switch product.type {
case .executable, .library(.dynamic):
// Establish a dependency on binary of the product.
Expand Down
47 changes: 26 additions & 21 deletions Sources/PackageGraph/PackageGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public struct PackageGraph {
return rootPackages.contains(package)
}

private let inputPackages: [ResolvedPackage]

/// Construct a package graph directly.
public init(
rootPackages: [ResolvedPackage],
Expand All @@ -57,7 +59,7 @@ public struct PackageGraph {
) {
self.rootPackages = rootPackages
self.requiredDependencies = requiredDependencies
let inputPackages = rootPackages + rootDependencies
self.inputPackages = rootPackages + rootDependencies
self.packages = try! topologicalSort(inputPackages, successors: { $0.dependencies })

allTargets = Set(packages.flatMap({ package -> [ResolvedTarget] in
Expand All @@ -80,26 +82,29 @@ public struct PackageGraph {
}
}))

// Compute the input targets.
let inputTargets = inputPackages.flatMap({ $0.targets }).map(ResolvedTarget.Dependency.target)
// Find all the dependencies of the root targets.
let dependencies = try! topologicalSort(inputTargets, successors: { $0.dependencies })

// Separate out the products and targets but maintain their topological order.
var reachableTargets: Set<ResolvedTarget> = []
var reachableProducts = Set(inputPackages.flatMap({ $0.products }))

for dependency in dependencies {
switch dependency {
case .target(let target):
reachableTargets.insert(target)
case .product(let product):
reachableProducts.insert(product)
}
}
// Compute the reachable targets and products.
let inputTargets = inputPackages.lazy.flatMap({ $0.targets })
let inputProducts = inputPackages.lazy.flatMap({ $0.products })
let recursiveDependencies = inputTargets.flatMap({ $0.recursiveDependencies() })

self.reachableTargets = Set(inputTargets).union(recursiveDependencies.compactMap({ $0.target }))
self.reachableProducts = Set(inputProducts).union(recursiveDependencies.compactMap({ $0.product }))
}

public func reachableBuildTargets(in environment: BuildEnvironment) -> Set<ResolvedTarget> {
let inputTargets = inputPackages.lazy.flatMap({ $0.targets })
let recursiveBuildTargetDependencies = inputTargets
.flatMap({ $0.recursiveBuildTargetDependencies(in: environment) })
return Set(inputTargets).union(recursiveBuildTargetDependencies)
}

self.reachableTargets = reachableTargets
self.reachableProducts = reachableProducts
public func reachableBuildProducts(in environment: BuildEnvironment) -> Set<ResolvedProduct> {
let recursiveBuildProductDependencies = inputPackages
.lazy
.flatMap({ $0.targets })
.flatMap({ $0.recursiveBuildDependencies(in: environment) })
.compactMap({ $0.product })
return Set(inputPackages.flatMap({ $0.products })).union(recursiveBuildProductDependencies)
}

/// Computes a map from each executable target in any of the root packages to the corresponding test targets.
Expand All @@ -119,7 +124,7 @@ public struct PackageGraph {
for target in rootTargets where target.type == .executable {
// Find all dependencies of this target within its package.
let dependencies = try! topologicalSort(target.dependencies, successors: {
$0.dependencies.compactMap({ $0.target }).map(ResolvedTarget.Dependency.target)
$0.dependencies.compactMap({ $0.target }).map({ .target($0, conditions: []) })
}).compactMap({ $0.target })

// Include the test targets whose dependencies intersect with the
Expand Down
55 changes: 29 additions & 26 deletions Sources/PackageGraph/PackageGraphLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ private func checkAllDependenciesAreUsed(_ rootPackages: [ResolvedPackage], _ di
let productDependencies: Set<ResolvedProduct> = Set(package.targets.flatMap({ target in
return target.dependencies.compactMap({ targetDependency in
switch targetDependency {
case .product(let product):
case .product(let product, _):
return product
case .target:
return nil
Expand Down Expand Up @@ -242,7 +242,13 @@ private func createResolvedPackages(
// Establish dependencies between the targets. A target can only depend on another target present in the same package.
let targetMap = targetBuilders.spm_createDictionary({ ($0.target, $0) })
for targetBuilder in targetBuilders {
targetBuilder.dependencies += targetBuilder.target.dependencies.map({ targetMap[$0]! })
targetBuilder.dependencies += targetBuilder.target.dependencies.compactMap({ dependency in
if case .target(let target, let conditions) = dependency {
return .target(targetMap[target]!, conditions: conditions)
} else {
return nil
}
})
}

// Create product builders for each product in the package. A product can only contain a target present in the same package.
Expand Down Expand Up @@ -308,10 +314,10 @@ private func createResolvedPackages(
foundDuplicateTarget = foundDuplicateTarget || !allTargetNames.insert(targetBuilder.target.name).inserted

// Directly add all the system module dependencies.
targetBuilder.dependencies += implicitSystemTargetDeps
targetBuilder.dependencies += implicitSystemTargetDeps.map({ .target($0, conditions: []) })

// Establish product dependencies.
for productRef in targetBuilder.target.productDependencies {
for case .product(let productRef, let conditions) in targetBuilder.target.dependencies {
// Find the product in this package's dependency products.
guard let product = productDependencyMap[productRef.name] else {
// Only emit a diagnostic if there are no other diagnostics.
Expand All @@ -337,7 +343,7 @@ private func createResolvedPackages(
}
}

targetBuilder.productDeps.append(product)
targetBuilder.dependencies.append(.product(product, conditions: conditions))
}
}
}
Expand Down Expand Up @@ -410,14 +416,16 @@ private final class ResolvedProductBuilder: ResolvedBuilder<ResolvedProduct> {
/// Builder for resolved target.
private final class ResolvedTargetBuilder: ResolvedBuilder<ResolvedTarget> {

enum Dependency {
case target(_ target: ResolvedTargetBuilder, conditions: [ManifestCondition])
case product(_ product: ResolvedProductBuilder, conditions: [ManifestCondition])
}

/// The target reference.
let target: Target

/// The target dependencies of this target.
var dependencies: [ResolvedTargetBuilder] = []

/// The product dependencies of this target.
var productDeps: [ResolvedProductBuilder] = []
var dependencies: [Dependency] = []

/// The diagnostics engine.
let diagnostics: DiagnosticsEngine
Expand All @@ -441,24 +449,19 @@ private final class ResolvedTargetBuilder: ResolvedBuilder<ResolvedTarget> {
}

override func constructImpl() -> ResolvedTarget {
var deps: [ResolvedTarget.Dependency] = []
for dependency in dependencies {
deps.append(.target(dependency.construct()))
}
for dependency in productDeps {
let product = dependency.construct()

if !dependency.packageBuilder.isAllowedToVendUnsafeProducts {
diagnoseInvalidUseOfUnsafeFlags(product)
let dependencies = self.dependencies.map({ dependency -> ResolvedTarget.Dependency in
switch dependency {
case .target(let targetBuilder, let conditions):
return .target(targetBuilder.construct(), conditions: conditions)
case .product(let productBuilder, let conditions):
let product = productBuilder.construct()
if !productBuilder.packageBuilder.isAllowedToVendUnsafeProducts {
diagnoseInvalidUseOfUnsafeFlags(product)
}
return .product(product, conditions: conditions)
}

deps.append(.product(product))
}

return ResolvedTarget(
target: target,
dependencies: deps
)
})
return ResolvedTarget(target: target, dependencies: dependencies)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/PackageLoading/ModuleMapGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public struct ModuleMapGenerator {

// If the file exists with the identical contents, we don't need to rewrite it.
// Otherwise, compiler will recompile even if nothing else has changed.
if let contents = try? localFileSystem.readFileContents(file), contents == stream.bytes {
if let contents = try? fileSystem.readFileContents(file), contents == stream.bytes {
return
}
try fileSystem.writeFileContents(file, bytes: stream.bytes)
Expand Down
Loading

0 comments on commit 6446f27

Please sign in to comment.