Skip to content

Allow specifying testing libraries for swift package init #7186

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 4 commits into from
Dec 12, 2023
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
21 changes: 21 additions & 0 deletions Sources/Commands/PackageTools/Init.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ArgumentParser
import Basics
import CoreCommands
import Workspace
import SPMBuildCore

extension SwiftPackageTool {
struct Init: SwiftCommand {
Expand All @@ -38,6 +39,18 @@ extension SwiftPackageTool {
"""))
var initMode: InitPackage.PackageType = .library

/// Whether to enable support for XCTest.
@Flag(name: .customLong("xctest"),
inversion: .prefixedEnableDisable,
help: "Enable support for XCTest")
var enableXCTestSupport: Bool = true

/// Whether to enable support for swift-testing.
@Flag(name: .customLong("experimental-swift-testing"),
inversion: .prefixedEnableDisable,
help: "Enable experimental support for swift-testing")
var enableSwiftTestingLibrarySupport: Bool = false

@Option(name: .customLong("name"), help: "Provide custom package name")
var packageName: String?

Expand All @@ -46,10 +59,18 @@ extension SwiftPackageTool {
throw InternalError("Could not find the current working directory")
}

var testingLibraries: Set<BuildParameters.Testing.Library> = []
if enableXCTestSupport {
testingLibraries.insert(.xctest)
}
if enableSwiftTestingLibrarySupport {
testingLibraries.insert(.swiftTesting)
}
let packageName = self.packageName ?? cwd.basename
let initPackage = try InitPackage(
name: packageName,
packageType: initMode,
supportedTestingLibraries: testingLibraries,
destinationPath: cwd,
installedSwiftPMConfiguration: swiftTool.getHostToolchain().installedSwiftPMConfiguration,
fileSystem: swiftTool.fileSystem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ extension BuildParameters {
public var testProductStyle: TestProductStyle

/// The testing libraries supported by the package manager.
public enum Library: String, Codable {
public enum Library: String, Codable, CustomStringConvertible {
/// The XCTest library.
///
/// This case represents both the open-source swift-corelibs-xctest
Expand All @@ -107,6 +107,10 @@ extension BuildParameters {

/// The swift-testing library.
case swiftTesting = "swift-testing"

public var description: String {
rawValue
}
}

/// Which testing library to use for this build.
Expand Down
3 changes: 2 additions & 1 deletion Sources/SPMTestSupport/misc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -444,12 +444,13 @@ extension InitPackage {
public convenience init(
name: String,
packageType: PackageType,
supportedTestingLibraries: Set<BuildParameters.Testing.Library> = [.xctest],
destinationPath: AbsolutePath,
fileSystem: FileSystem
) throws {
try self.init(
name: name,
options: InitPackageOptions(packageType: packageType),
options: InitPackageOptions(packageType: packageType, supportedTestingLibraries: supportedTestingLibraries),
destinationPath: destinationPath,
installedSwiftPMConfiguration: .default,
fileSystem: fileSystem
Expand Down
193 changes: 154 additions & 39 deletions Sources/Workspace/InitPackage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import Basics
import PackageModel
import SPMBuildCore

import protocol TSCBasic.OutputByteStream

Expand All @@ -25,16 +26,21 @@ public final class InitPackage {
/// The type of package to create.
public var packageType: PackageType

/// The set of supported testing libraries to include in the package.
public var supportedTestingLibraries: Set<BuildParameters.Testing.Library>

/// The list of platforms in the manifest.
///
/// Note: This should only contain Apple platforms right now.
public var platforms: [SupportedPlatform]

public init(
packageType: PackageType,
supportedTestingLibraries: Set<BuildParameters.Testing.Library> = [.xctest],
platforms: [SupportedPlatform] = []
) {
self.packageType = packageType
self.supportedTestingLibraries = supportedTestingLibraries
self.platforms = platforms
}
}
Expand Down Expand Up @@ -87,13 +93,14 @@ public final class InitPackage {
public convenience init(
name: String,
packageType: PackageType,
supportedTestingLibraries: Set<BuildParameters.Testing.Library>,
destinationPath: AbsolutePath,
installedSwiftPMConfiguration: InstalledSwiftPMConfiguration,
fileSystem: FileSystem
) throws {
try self.init(
name: name,
options: InitPackageOptions(packageType: packageType),
options: InitPackageOptions(packageType: packageType, supportedTestingLibraries: supportedTestingLibraries),
destinationPath: destinationPath,
installedSwiftPMConfiguration: installedSwiftPMConfiguration,
fileSystem: fileSystem
Expand All @@ -108,6 +115,11 @@ public final class InitPackage {
installedSwiftPMConfiguration: InstalledSwiftPMConfiguration,
fileSystem: FileSystem
) throws {
if options.packageType == .macro && options.supportedTestingLibraries.contains(.swiftTesting) {
// FIXME: https://github.com/apple/swift-syntax/issues/2400
throw InitError.unsupportedTestingLibraryForPackageType(.swiftTesting, .macro)
}

self.options = options
self.pkgname = name
self.moduleName = name.spm_mangledToC99ExtendedIdentifier()
Expand Down Expand Up @@ -257,16 +269,22 @@ public final class InitPackage {
}

// Package dependencies
var dependencies = [String]()
if packageType == .tool {
pkgParams.append("""
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
]
""")
dependencies.append(#".package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0")"#)
} else if packageType == .macro {
dependencies.append(#".package(url: "https://github.com/apple/swift-syntax.git", from: "\#(self.installedSwiftPMConfiguration.swiftSyntaxVersionForMacroTemplate.description)")"#)
}
if options.supportedTestingLibraries.contains(.swiftTesting) {
dependencies.append(#".package(url: "https://github.com/apple/swift-testing.git", from: "0.2.0")"#)
}
if !dependencies.isEmpty {
let dependencies = dependencies.map { dependency in
" \(dependency),"
}.joined(separator: "\n")
pkgParams.append("""
dependencies: [
.package(url: "https://github.com/apple/swift-syntax.git", from: "\(self.installedSwiftPMConfiguration.swiftSyntaxVersionForMacroTemplate.description)"),
\(dependencies)
]
""")
}
Expand Down Expand Up @@ -317,6 +335,35 @@ public final class InitPackage {
]
"""
} else if packageType == .macro {
let testTarget: String
if options.supportedTestingLibraries.contains(.swiftTesting) {
testTarget = """

// A test target used to develop the macro implementation.
.testTarget(
name: "\(pkgname)Tests",
dependencies: [
"\(pkgname)Macros",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
.product(name: "Testing", package: "swift-testing"),
]
),
"""
} else if options.supportedTestingLibraries.contains(.xctest) {
testTarget = """

// A test target used to develop the macro implementation.
.testTarget(
name: "\(pkgname)Tests",
dependencies: [
"\(pkgname)Macros",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
]
),
"""
} else {
testTarget = ""
}
param += """
// Macro implementation that performs the source transformation of a macro.
.macro(
Expand All @@ -332,24 +379,36 @@ public final class InitPackage {

// A client of the library, which is able to use the macro in its own code.
.executableTarget(name: "\(pkgname)Client", dependencies: ["\(pkgname)"]),

// A test target used to develop the macro implementation.
.testTarget(
name: "\(pkgname)Tests",
dependencies: [
"\(pkgname)Macros",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
]
),
\(testTarget)
]
"""
} else {
let testTarget: String
if options.supportedTestingLibraries.contains(.swiftTesting) {
testTarget = """
.testTarget(
name: "\(pkgname)Tests",
dependencies: [
"\(pkgname)",
.product(name: "Testing", package: "swift-testing"),
]
),
"""
} else if options.supportedTestingLibraries.contains(.xctest) {
testTarget = """
.testTarget(
name: "\(pkgname)Tests",
dependencies: ["\(pkgname)"]
),
"""
} else {
testTarget = ""
}

param += """
.target(
name: "\(pkgname)"),
.testTarget(
name: "\(pkgname)Tests",
dependencies: ["\(pkgname)"]),
\(testTarget)
]
"""
}
Expand Down Expand Up @@ -606,6 +665,12 @@ public final class InitPackage {
}

private func writeTests() throws {
if options.supportedTestingLibraries.isEmpty {
// If the developer disabled all testing libraries, do not bother to
// emit any test content.
return
}

switch packageType {
case .empty, .executable, .tool, .buildToolPlugin, .commandPlugin: return
default: break
Expand All @@ -620,11 +685,31 @@ public final class InitPackage {
}

private func writeLibraryTestsFile(_ path: AbsolutePath) throws {
try writePackageFile(path) { stream in
stream.send(
var content = ""

if options.supportedTestingLibraries.contains(.swiftTesting) {
content += "import Testing\n"
}
if options.supportedTestingLibraries.contains(.xctest) {
content += "import XCTest\n"
}
content += "@testable import \(moduleName)\n"

// Prefer swift-testing if specified, otherwise XCTest. If both are
// specified, the developer is free to write tests using both
// libraries, but we still only want to present a single library's
// example tests.
if options.supportedTestingLibraries.contains(.swiftTesting) {
content += """

@Test func example() throws {
// swift-testing Documentation
// https://swiftpackageindex.com/apple/swift-testing/main/documentation/testing
}

"""
import XCTest
@testable import \(moduleName)
} else if options.supportedTestingLibraries.contains(.xctest) {
content += """

final class \(moduleName)Tests: XCTestCase {
func testExample() throws {
Expand All @@ -637,28 +722,52 @@ public final class InitPackage {
}

"""
)
}

try writePackageFile(path) { stream in
stream.send(content)
}
}

private func writeMacroTestsFile(_ path: AbsolutePath) throws {
try writePackageFile(path) { stream in
stream.send(##"""
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import XCTest
var content = ""

// Macro implementations build for the host, so the corresponding module is not available when cross-compiling. Cross-compiled tests may still make use of the macro itself in end-to-end tests.
#if canImport(\##(moduleName)Macros)
import \##(moduleName)Macros
content += ##"""
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
"""##

let testMacros: [String: Macro.Type] = [
"stringify": StringifyMacro.self,
]
#endif
if options.supportedTestingLibraries.contains(.swiftTesting) {
content += "import Testing\n"
}
if options.supportedTestingLibraries.contains(.xctest) {
content += "import XCTest\n"
}

content += ##"""

// Macro implementations build for the host, so the corresponding module is not available when cross-compiling. Cross-compiled tests may still make use of the macro itself in end-to-end tests.
#if canImport(\##(moduleName)Macros)
import \##(moduleName)Macros

let testMacros: [String: Macro.Type] = [
"stringify": StringifyMacro.self,
]
#endif


"""##

// Prefer swift-testing if specified, otherwise XCTest. If both are
// specified, the developer is free to write tests using both
// libraries, but we still only want to present a single library's
// example tests.
if options.supportedTestingLibraries.contains(.swiftTesting) {
// FIXME: https://github.com/apple/swift-syntax/issues/2400
} else if options.supportedTestingLibraries.contains(.xctest) {
content += ##"""
final class \##(moduleName)Tests: XCTestCase {
func testMacro() throws {
#if canImport(\##(moduleName)Macros)
Expand Down Expand Up @@ -694,7 +803,10 @@ public final class InitPackage {
}

"""##
)
}

try writePackageFile(path) { stream in
stream.send(content)
}
}

Expand Down Expand Up @@ -783,13 +895,16 @@ public final class InitPackage {

private enum InitError: Swift.Error {
case manifestAlreadyExists
case unsupportedTestingLibraryForPackageType(_ testingLibrary: BuildParameters.Testing.Library, _ packageType: InitPackage.PackageType)
}

extension InitError: CustomStringConvertible {
var description: String {
switch self {
case .manifestAlreadyExists:
return "a manifest file already exists in this directory"
case let .unsupportedTestingLibraryForPackageType(library, packageType):
return "\(library) cannot be used when initializing a \(packageType) package"
}
}
}
Expand Down
Loading