Skip to content

Finish SE‐0226 (Ignore Unused Products) #2749

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 8 commits into from
Jun 26, 2020
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
2 changes: 1 addition & 1 deletion Fixtures/ModuleMaps/Transitive/packageC/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ let package = Package(
.package(url: "../packageD", from: "1.0.0"),
],
targets: [
.target(name: "x", dependencies: []),
.target(name: "x", dependencies: ["CFoo"]),
]
)
7 changes: 7 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ let package = Package(
.testTarget(
name: "XCBuildSupportTests",
dependencies: ["XCBuildSupport", "SPMTestSupport"]),

// Examples (These are built to ensure they stay up to date with the API.)
.target(
name: "package-info",
dependencies: ["PackageModel", "PackageLoading", "PackageGraph", "Workspace"],
path: "Examples/package-info/Sources/package-info"
)
],
swiftLanguageVersions: [.v5]
)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Commands/SwiftBuildTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class SwiftBuildTool: SwiftTool<BuildToolOptions> {
#endif

guard let subset = options.buildSubset(diagnostics: diagnostics) else { return }
let buildSystem = try createBuildSystem()
let buildSystem = try createBuildSystem(explicitProduct: options.product)
try buildSystem.build(subset: subset)

case .binPath:
Expand Down
10 changes: 6 additions & 4 deletions Sources/Commands/SwiftPackageTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public class SwiftPackageTool: SwiftTool<PackageToolOptions> {

let builder = PackageBuilder(
manifest: manifest,
productFilter: .everything,
path: try getPackageRoot(),
xcTestMinimumDeploymentTargets: [:], // Minimum deployment target does not matter for this operation.
diagnostics: diagnostics
Expand Down Expand Up @@ -354,6 +355,7 @@ public class SwiftPackageTool: SwiftTool<PackageToolOptions> {

let builder = PackageBuilder(
manifest: manifest,
productFilter: .everything,
path: try getPackageRoot(),
xcTestMinimumDeploymentTargets: MinimumDeploymentTarget.default.xcTestMinimumDeploymentTargets,
diagnostics: diagnostics
Expand Down Expand Up @@ -933,10 +935,10 @@ fileprivate extension SwiftPackageTool {
for (package, change) in changes {
let currentVersion = pins.pinsMap[package.identity]?.state.description ?? ""
switch change {
case let .added(requirement):
stream <<< "+ \(package.name) \(requirement.prettyPrinted)"
case let .updated(requirement):
stream <<< "~ \(package.name) \(currentVersion) -> \(package.name) \(requirement.prettyPrinted)"
case let .added(state):
stream <<< "+ \(package.name) \(state.requirement.prettyPrinted)"
case let .updated(state):
stream <<< "~ \(package.name) \(currentVersion) -> \(package.name) \(state.requirement.prettyPrinted)"
case .removed:
stream <<< "- \(package.name) \(currentVersion)"
case .unchanged:
Expand Down
8 changes: 6 additions & 2 deletions Sources/Commands/SwiftRunTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ public class SwiftRunTool: SwiftTool<RunToolOptions> {

case .repl:
// Load a custom package graph which has a special product for REPL.
let graphLoader = { try self.loadPackageGraph(createREPLProduct: self.options.shouldLaunchREPL) }
let graphLoader = {
try self.loadPackageGraph(
explicitProduct: self.options.executable,
createREPLProduct: self.options.shouldLaunchREPL)
}
let buildParameters = try self.buildParameters()

// Construct the build operation.
Expand Down Expand Up @@ -140,7 +144,7 @@ public class SwiftRunTool: SwiftTool<RunToolOptions> {
return
}

let buildSystem = try createBuildSystem()
let buildSystem = try createBuildSystem(explicitProduct: options.executable)
let productName = try findProductName(in: buildSystem.getPackageGraph())

if options.shouldBuildTests {
Expand Down
15 changes: 10 additions & 5 deletions Sources/Commands/SwiftTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -667,8 +667,12 @@ public class SwiftTool<Options: ToolOptions> {
}

/// Fetch and load the complete package graph.
///
/// - Parameters:
/// - explicitProduct: The product specified on the command line to a “swift run” or “swift build” command. This allows executables from dependencies to be run directly without having to hook them up to any particular target.
@discardableResult
func loadPackageGraph(
explicitProduct: String? = nil,
createMultipleTestProducts: Bool = false,
createREPLProduct: Bool = false
) throws -> PackageGraph {
Expand All @@ -678,6 +682,7 @@ public class SwiftTool<Options: ToolOptions> {
// Fetch and load the package graph.
let graph = try workspace.loadPackageGraph(
root: getWorkspaceRoot(),
explicitProduct: explicitProduct,
createMultipleTestProducts: createMultipleTestProducts,
createREPLProduct: createREPLProduct,
forceResolvedVersions: options.forceResolvedVersions,
Expand Down Expand Up @@ -720,9 +725,9 @@ public class SwiftTool<Options: ToolOptions> {
return enableBuildManifestCaching && haveBuildManifestAndDescription && !hasEditedPackages
}

func createBuildOperation(useBuildManifestCaching: Bool = true) throws -> BuildOperation {
func createBuildOperation(explicitProduct: String? = nil, useBuildManifestCaching: Bool = true) throws -> BuildOperation {
// Load a custom package graph which has a special product for REPL.
let graphLoader = { try self.loadPackageGraph() }
let graphLoader = { try self.loadPackageGraph(explicitProduct: explicitProduct) }

// Construct the build operation.
let buildOp = try BuildOperation(
Expand All @@ -738,11 +743,11 @@ public class SwiftTool<Options: ToolOptions> {
return buildOp
}

func createBuildSystem(useBuildManifestCaching: Bool = true) throws -> BuildSystem {
func createBuildSystem(explicitProduct: String? = nil, useBuildManifestCaching: Bool = true) throws -> BuildSystem {
let buildSystem: BuildSystem
switch options.buildSystem {
case .native:
let graphLoader = { try self.loadPackageGraph() }
let graphLoader = { try self.loadPackageGraph(explicitProduct: explicitProduct) }
buildSystem = try BuildOperation(
buildParameters: buildParameters(),
useBuildManifestCaching: useBuildManifestCaching && canUseBuildManifestCaching(),
Expand All @@ -751,7 +756,7 @@ public class SwiftTool<Options: ToolOptions> {
stdoutStream: stdoutStream
)
case .xcode:
let graphLoader = { try self.loadPackageGraph(createMultipleTestProducts: true) }
let graphLoader = { try self.loadPackageGraph(explicitProduct: explicitProduct, createMultipleTestProducts: true) }
buildSystem = try XcodeBuildSystem(
buildParameters: buildParameters(),
packageGraphLoader: graphLoader,
Expand Down
153 changes: 144 additions & 9 deletions Sources/PackageGraph/DependencyResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/

import TSCBasic
import struct PackageModel.PackageReference
import PackageModel
import struct TSCUtility.Version
import class Foundation.NSDate

Expand Down Expand Up @@ -111,7 +111,7 @@ public protocol PackageContainer {
/// - Precondition: `versions.contains(version)`
/// - Throws: If the version could not be resolved; this will abort
/// dependency resolution completely.
func getDependencies(at version: Version) throws -> [PackageContainerConstraint]
func getDependencies(at version: Version, productFilter: ProductFilter) throws -> [PackageContainerConstraint]

/// Fetch the declared dependencies for a particular revision.
///
Expand All @@ -120,12 +120,12 @@ public protocol PackageContainer {
///
/// - Throws: If the revision could not be resolved; this will abort
/// dependency resolution completely.
func getDependencies(at revision: String) throws -> [PackageContainerConstraint]
func getDependencies(at revision: String, productFilter: ProductFilter) throws -> [PackageContainerConstraint]

/// Fetch the dependencies of an unversioned package container.
///
/// NOTE: This method should not be called on a versioned container.
func getUnversionedDependencies() throws -> [PackageContainerConstraint]
func getUnversionedDependencies(productFilter: ProductFilter) throws -> [PackageContainerConstraint]

/// Get the updated identifier at a bound version.
///
Expand Down Expand Up @@ -154,21 +154,25 @@ public struct PackageContainerConstraint: CustomStringConvertible, Equatable, Ha
/// The constraint requirement.
public let requirement: PackageRequirement

/// The required products.
public let products: ProductFilter

/// Create a constraint requiring the given `container` satisfying the
/// `requirement`.
public init(container identifier: PackageReference, requirement: PackageRequirement) {
public init(container identifier: PackageReference, requirement: PackageRequirement, products: ProductFilter) {
self.identifier = identifier
self.requirement = requirement
self.products = products
}

/// Create a constraint requiring the given `container` satisfying the
/// `versionRequirement`.
public init(container identifier: PackageReference, versionRequirement: VersionSetSpecifier) {
self.init(container: identifier, requirement: .versionSet(versionRequirement))
public init(container identifier: PackageReference, versionRequirement: VersionSetSpecifier, products: ProductFilter) {
self.init(container: identifier, requirement: .versionSet(versionRequirement), products: products)
}

public var description: String {
return "Constraint(\(identifier), \(requirement))"
return "Constraint(\(identifier), \(requirement), \(products)"
}
}

Expand Down Expand Up @@ -209,7 +213,7 @@ public enum BoundVersion: Equatable, CustomStringConvertible {
}

public class DependencyResolver {
public typealias Binding = (container: PackageReference, binding: BoundVersion)
public typealias Binding = (container: PackageReference, binding: BoundVersion, products: ProductFilter)

/// The dependency resolver result.
public enum Result {
Expand All @@ -220,3 +224,134 @@ public class DependencyResolver {
case error(Swift.Error)
}
}

/// A node in the dependency resolution graph.
///
/// See the documentation of each case for more detailed descriptions of each kind and how they interact.
///
/// - SeeAlso: `GraphLoadingNode`
public enum DependencyResolutionNode: Equatable, Hashable, CustomStringConvertible {

/// An empty package node.
///
/// This node indicates that a package needs to be present, but does not indicate that any of its contents are needed.
///
/// Empty package nodes are always leaf nodes; they have no dependencies.
case empty(package: PackageReference)

/// A product node.
///
/// This node indicates that a particular product in a particular package is required.
///
/// Product nodes always have dependencies. A product node has...
///
/// - one implicit dependency on its own package at an exact version (as an empty package node).
/// This dependency is what ensures the resolver does not select two products from the same package at different versions.
/// - zero or more dependencies on the product nodes of other packages.
/// These are all the external products required to build all of the targets vended by this product.
/// They derive from the manifest.
///
/// Tools versions before 5.2 do not know which products belong to which packages, so each product is required from every dependency.
/// Since a non‐existant product ends up with only its implicit dependency on its own package,
/// only whichever package contains the product will end up adding additional constraints.
/// See `ProductFilter` and `Manifest.register(...)`.
case product(String, package: PackageReference)

/// A root node.
///
/// This node indicates a root node in the graph, which is required no matter what.
///
/// Root nodes may have dependencies. A root node has...
///
/// - zero or more dependencies on each external product node required to build any of its targets (vended or not).
/// - zero or more dependencies directly on external empty package nodes.
/// This special case occurs when a dependecy is declared but not used.
/// It is a warning condition, and builds do not actually need these dependencies.
/// However, forcing the graph to resolve and fetch them anyway allows the diagnostics passes access
/// to the information needed in order to provide actionable suggestions to help the user stitch up the dependency declarations properly.
case root(package: PackageReference)

/// The package.
public var package: PackageReference {
switch self {
case .empty(let package), .product(_, let package), .root(let package):
return package
}
}

/// The name of the specific product if the node is a product node, otherwise `nil`.
public var specificProduct: String? {
switch self {
case .empty, .root:
return nil
case .product(let product, _):
return product
}
}

// To ensure cyclical dependencies are detected properly,
// hashing cannot include whether the node behaves as a root.
private struct Identity: Equatable, Hashable {
fileprivate let package: PackageReference
fileprivate let specificProduct: String?
}
private var identity: Identity {
return Identity(package: package, specificProduct: specificProduct)
}
public static func ==(lhs: DependencyResolutionNode, rhs: DependencyResolutionNode) -> Bool {
return lhs.identity == rhs.identity
}
public func hash(into hasher: inout Hasher) {
hasher.combine(identity)
}

/// Assembles the product filter to use on the manifest for this node to determine it’s dependencies.
internal func productFilter() -> ProductFilter {
switch self {
case .empty:
return .specific([])
case .product(let product, _):
return .specific([product])
case .root:
return .everything
}
}

/// Returns the dependency that a product has on its own package, if relevant.
///
/// This is the constraint that requires all products from a package resolve to the same version.
internal func versionLock(version: Version) -> RepositoryPackageConstraint? {
// Don’t create a version lock for anything but a product.
guard specificProduct != nil else { return nil }
return RepositoryPackageConstraint(
container: package,
versionRequirement: .exact(version),
products: .specific([])
)
}

/// Returns the dependency that a product has on its own package, if relevant.
///
/// This is the constraint that requires all products from a package resolve to the same revision.
internal func revisionLock(revision: String) -> RepositoryPackageConstraint? {
// Don’t create a revision lock for anything but a product.
guard specificProduct != nil else { return nil }
return RepositoryPackageConstraint(
container: package,
requirement: .revision(revision),
products: .specific([])
)
}

public var description: String {
return "\(package.name)\(productFilter())"
}

public func nameForDiagnostics() -> String {
if let product = specificProduct {
return "\(package.name)[\(product)]"
} else {
return "\(package.name)"
}
}
}
Loading