Skip to content

Adding support for generating library product schemes #2768

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

Closed
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
7 changes: 7 additions & 0 deletions Sources/Commands/SwiftPackageTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,13 @@ public class SwiftPackageTool: SwiftTool<PackageToolOptions> {
let generateXcodeParser = parser.add(
subparser: PackageMode.generateXcodeproj.rawValue,
overview: "Generates an Xcode project")
binder.bind(
option: generateXcodeParser.add(
option: "--enable-library-schemes", kind: Bool.self,
usage: "Enables creation of schemes for library products"),
to: {
$0.xcodeprojOptions.isLibrarySchemeGenerationEnabled = $1
})
binder.bind(
generateXcodeParser.add(
option: "--xcconfig-overrides", kind: PathArgument.self,
Expand Down
10 changes: 6 additions & 4 deletions Sources/PackageGraph/PackageGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ public struct PackageGraph {
self.reachableProducts = Set(inputProducts).union(recursiveDependencies.compactMap { $0.product })
}

/// Computes a map from each executable target in any of the root packages to the corresponding test targets.
public func computeTestTargetsForExecutableTargets() -> [ResolvedTarget: [ResolvedTarget]] {
/// Computes a map from each target in in any of the root packages to the corresponding test targets.
/// - Parameter types: the list of types to include in the filter
/// - Returns: a map from each target to the corresponding test targets
public func computeTestTargets(for types: [Target.Kind]) -> [ResolvedTarget: [ResolvedTarget]] {
var result = [ResolvedTarget: [ResolvedTarget]]()

let rootTargets = rootPackages.map({ $0.targets }).flatMap({ $0 })
Expand All @@ -106,12 +108,12 @@ public struct PackageGraph {
return Dictionary(uniqueKeysWithValues: testTargetDeps)
}()

for target in rootTargets where target.type == .executable {
for target in rootTargets where types.contains(target.type) {
// Find all dependencies of this target within its package.
let dependencies = try! topologicalSort(target.dependencies, successors: {
$0.dependencies.compactMap { $0.target }.map { .target($0, conditions: []) }
}).compactMap({ $0.target })

// Include the test targets whose dependencies intersect with the
// current target's (recursive) dependencies.
let testTargets = testTargetDepMap.filter({ (testTarget, deps) in
Expand Down
23 changes: 21 additions & 2 deletions Sources/Xcodeproj/SchemesGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,34 @@ public final class SchemesGenerator {
private let container: String
private let schemesDir: AbsolutePath
private let isCodeCoverageEnabled: Bool
private let isLibrarySchemeGenerationEnabled: Bool
private let fs: FileSystem

public init(
graph: PackageGraph,
container: String,
schemesDir: AbsolutePath,
isCodeCoverageEnabled: Bool,
isLibrarySchemeGenerationEnabled: Bool,
fs: FileSystem
) {
self.graph = graph
self.container = container
self.schemesDir = schemesDir
self.isCodeCoverageEnabled = isCodeCoverageEnabled
self.isLibrarySchemeGenerationEnabled = isLibrarySchemeGenerationEnabled
self.fs = fs
}

private var schemeTypesToGenerate: [Target.Kind] {
isLibrarySchemeGenerationEnabled ? [.executable, .library] : [.executable]
}

public func buildSchemes() -> [Scheme] {
let rootPackage = graph.rootPackages[0]

var schemes: [Scheme] = []

let testTargetsMap = graph.computeTestTargetsForExecutableTargets()
let testTargetsMap = graph.computeTestTargets(for: self.schemeTypesToGenerate)

// Create one scheme per executable target.
for target in rootPackage.targets where target.type == .executable {
Expand All @@ -75,6 +81,19 @@ public final class SchemesGenerator {
testTargets: testTargets ?? []
))
}

// If enabled, create one scheme per library target
if self.isLibrarySchemeGenerationEnabled {
for target in rootPackage.targets where target.type == .library {
let testTargets = testTargetsMap[target]

schemes.append(Scheme(
name: target.name,
regularTargets: [target],
testTargets: testTargets ?? []
))
}
}

// Finally, create one master scheme for the entire package.
let regularTargets = rootPackage.targets.filter({
Expand Down
6 changes: 6 additions & 0 deletions Sources/Xcodeproj/generate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public struct XcodeprojOptions {

/// Whether code coverage should be enabled in the generated scheme.
public var isCodeCoverageEnabled: Bool

/// Whether library products are included while generating schemes.
public var isLibrarySchemeGenerationEnabled: Bool

/// Whether to use legacy scheme generation logic.
public var useLegacySchemeGenerator: Bool
Expand All @@ -43,13 +46,15 @@ public struct XcodeprojOptions {
flags: BuildFlags = BuildFlags(),
xcconfigOverrides: AbsolutePath? = nil,
isCodeCoverageEnabled: Bool? = nil,
isLibrarySchemeGenerationEnabled: Bool? = nil,
useLegacySchemeGenerator: Bool? = nil,
enableAutogeneration: Bool? = nil,
addExtraFiles: Bool? = nil
) {
self.flags = flags
self.xcconfigOverrides = xcconfigOverrides
self.isCodeCoverageEnabled = isCodeCoverageEnabled ?? false
self.isLibrarySchemeGenerationEnabled = isLibrarySchemeGenerationEnabled ?? false
self.useLegacySchemeGenerator = useLegacySchemeGenerator ?? false
self.enableAutogeneration = enableAutogeneration ?? false
self.addExtraFiles = addExtraFiles ?? true
Expand Down Expand Up @@ -259,6 +264,7 @@ func generateSchemes(
container: schemeContainer,
schemesDir: schemesDir,
isCodeCoverageEnabled: options.isCodeCoverageEnabled,
isLibrarySchemeGenerationEnabled: options.isLibrarySchemeGenerationEnabled,
fs: localFileSystem
).generate()
}
Expand Down
23 changes: 23 additions & 0 deletions Tests/XcodeprojTests/PackageGraphTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ class PackageGraphTests: XCTestCase {
container: "Foo.xcodeproj",
schemesDir: AbsolutePath("/Foo.xcodeproj/xcshareddata/xcschemes"),
isCodeCoverageEnabled: true,
isLibrarySchemeGenerationEnabled: false,
fs: fs).buildSchemes()

let schemes = Dictionary(uniqueKeysWithValues: generatedSchemes.map({ ($0.name, $0) }))
Expand All @@ -382,6 +383,28 @@ class PackageGraphTests: XCTestCase {

XCTAssertEqual(schemes["Foo-Package"]?.testTargets.map({ $0.name }).sorted(), ["aTests", "bcTests", "dTests", "libdTests"])
XCTAssertEqual(schemes["Foo-Package"]?.regularTargets.map({ $0.name }).sorted(), ["a", "b", "c", "d", "libd"])

let generatedSchemesWithLibrariesEnabled = SchemesGenerator(
graph: graph,
container: "Foo.xcodeproj",
schemesDir: AbsolutePath("/Foo.xcodeproj/xcshareddata/xcschemes"),
isCodeCoverageEnabled: true,
isLibrarySchemeGenerationEnabled: true,
fs: fs).buildSchemes()

let schemesWithLibrariesEnabled = Dictionary(uniqueKeysWithValues: generatedSchemesWithLibrariesEnabled.map({ ($0.name, $0) }))

XCTAssertEqual(generatedSchemesWithLibrariesEnabled.count, 6)
XCTAssertEqual(schemesWithLibrariesEnabled["a"]?.testTargets.map({ $0.name }).sorted(), ["aTests"])
XCTAssertEqual(schemesWithLibrariesEnabled["a"]?.regularTargets.map({ $0.name }).sorted(), ["a"])

XCTAssertEqual(schemesWithLibrariesEnabled["b"]?.testTargets.map({ $0.name }).sorted(), ["aTests", "bcTests"])
XCTAssertEqual(schemesWithLibrariesEnabled["c"]?.testTargets.map({ $0.name }).sorted(), ["aTests", "bcTests"])
XCTAssertEqual(schemesWithLibrariesEnabled["d"]?.testTargets.map({ $0.name }).sorted(), ["aTests", "bcTests", "dTests"])
XCTAssertEqual(schemesWithLibrariesEnabled["libd"]?.regularTargets.map({ $0.name }).sorted(), ["libd"])
XCTAssertEqual(schemesWithLibrariesEnabled["libd"]?.testTargets.map({ $0.name }).sorted(), ["aTests", "bcTests", "dTests", "libdTests"])
XCTAssertEqual(schemesWithLibrariesEnabled["Foo-Package"]?.testTargets.map({ $0.name }).sorted(), ["aTests", "bcTests", "dTests", "libdTests"])
XCTAssertEqual(schemesWithLibrariesEnabled["Foo-Package"]?.regularTargets.map({ $0.name }).sorted(), ["a", "b", "c", "d", "libd"])
}

func testSwiftVersion() throws {
Expand Down