From 079a811e62f204a697a05923bce357f68a05a603 Mon Sep 17 00:00:00 2001 From: Ashley Garland Date: Thu, 9 Feb 2023 16:01:52 -0800 Subject: [PATCH] Init template cleanup Improve the `swift package init` experience using the following criteria: - Simplify the templates to include only the minimum necessary to get started without making assumptions. - Use documentation links instead of arbitrary template content to teach people about Swift. - Improve the "front door" experience with the help usage. Detailed list of changes: - Remove the `system-module` template. - Remove the `manifest` template - it's emptier than `empty` and isn't providing a lot of value on its own. Make the `empty` template only generate a manifest instead. - Remove README generation. - Don't generate empty directories. - Don't emit empty dependencies array in manifests. - Update `library` template content: - Remove struct definition, use a simple public function instead. - Include documentation links for TSPL and the Standard Library. - Include documentation links for XCTest in the generated test case. - Update `executable` template content - Use simpler top-level code instead of an `@main` struct. - Remove executable tests. - Put sources directly in `./Sources`. - Clean up and align `type` argument help text - Update Usage.md documentation - Update CHANGELOG to reflect init template changes in #6144 rdar://98999734 --- CHANGELOG.md | 4 +- Documentation/Usage.md | 12 +- Sources/Commands/PackageTools/Init.swift | 10 +- Sources/Workspace/InitPackage.swift | 154 ++++++----------------- Tests/WorkspaceTests/InitTests.swift | 71 +---------- 5 files changed, 49 insertions(+), 202 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddb24f5623d..85411aaeb6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,6 @@ Swift Next } ``` - Swift 5.9 ----------- @@ -29,6 +28,9 @@ Swift 5.9 Support for building plugin dependencies for the host when cross-compiling. +* [#6144] + + Remove the `system-module` and `manifest` templates and clean up the remaining `empty`, `library`, and `executable` templates so they include the minimum information needed to get started, with links to documentation in the generated library, executable, and test content. Swift 5.8 ----------- diff --git a/Documentation/Usage.md b/Documentation/Usage.md index ef28a4cb9ef..638a78e9d10 100644 --- a/Documentation/Usage.md +++ b/Documentation/Usage.md @@ -158,9 +158,9 @@ initialize it as a package that builds a system module: example$ cd .. $ mkdir Clibgit $ cd Clibgit - Clibgit$ swift package init --type system-module + Clibgit$ swift package init --type empty -This creates `Package.swift` and `module.modulemap` files in the directory. +This creates a `Package.swift` file in the directory. Edit `Package.swift` and add `pkgConfig` parameter: ```swift @@ -181,7 +181,7 @@ parameter you can pass the path of a directory containing the library using the example$ swift build -Xlinker -L/usr/local/lib/ -Edit `module.modulemap` so it consists of the following: +Create a `module.modulemap` file so it consists of the following: module Clibgit [system] { header "/usr/local/include/git2.h" @@ -258,10 +258,10 @@ initialize it as a package that builds a system module: example$ cd .. $ mkdir CJPEG $ cd CJPEG - CJPEG$ swift package init --type system-module + CJPEG$ swift package init --type empty -This creates `Package.swift` and `module.modulemap` files in the directory. -Edit `module.modulemap` so it consists of the following: +This creates `Package.swift` file in the directory. +Create a `module.modulemap` file so it consists of the following: module CJPEG [system] { header "shim.h" diff --git a/Sources/Commands/PackageTools/Init.swift b/Sources/Commands/PackageTools/Init.swift index d275a2b81e1..6d9f534bf80 100644 --- a/Sources/Commands/PackageTools/Init.swift +++ b/Sources/Commands/PackageTools/Init.swift @@ -25,12 +25,10 @@ extension SwiftPackageTool { @Option( name: .customLong("type"), - help: ArgumentHelp("Package type: empty | library | executable | system-module | manifest", discussion: """ - empty - Create an empty package - library - Create a package that contains a library - executable - Create a package that contains a binary executable - system-module - Create a package that contains a system module - manifest - Create a Package.swift file + help: ArgumentHelp("Package type:", discussion: """ + library - A package with a library. + executable - A package with an executable. + empty - An empty package with a Package.swift manifest. """)) var initMode: InitPackage.PackageType = .library diff --git a/Sources/Workspace/InitPackage.swift b/Sources/Workspace/InitPackage.swift index a09f06952ad..e138d5783bd 100644 --- a/Sources/Workspace/InitPackage.swift +++ b/Sources/Workspace/InitPackage.swift @@ -43,8 +43,6 @@ public final class InitPackage { case empty = "empty" case library = "library" case executable = "executable" - case systemModule = "system-module" - case manifest = "manifest" case `extension` = "extension" public var description: String { @@ -114,15 +112,8 @@ public final class InitPackage { // FIXME: We should form everything we want to write, then validate that // none of it exists, and then act. try writeManifestFile() - - if packageType == .manifest { - return - } - - try writeREADMEFile() try writeGitIgnore() try writeSources() - try writeModuleMap() try writeTests() } @@ -177,10 +168,10 @@ public final class InitPackage { """) } - if packageType == .library || packageType == .manifest { + if packageType == .library { pkgParams.append(""" products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. + // Products define the executables and libraries a package produces, making them visible to other packages. .library( name: "\(pkgname)", targets: ["\(pkgname)"]), @@ -188,40 +179,32 @@ public final class InitPackage { """) } - pkgParams.append(""" - dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), - ] - """) - - if packageType == .library || packageType == .executable || packageType == .manifest { + if packageType == .library || packageType == .executable { var param = "" param += """ targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. """ if packageType == .executable { param += """ .executableTarget( + name: "\(pkgname)", + path: "Sources"), + ] """ } else { param += """ .target( + name: "\(pkgname)"), + .testTarget( + name: "\(pkgname)Tests", + dependencies: ["\(pkgname)"]), + ] """ } - param += """ - - name: "\(pkgname)", - dependencies: []), - .testTarget( - name: "\(pkgname)Tests", - dependencies: ["\(pkgname)"]), - ] - """ pkgParams.append(param) } @@ -242,23 +225,10 @@ public final class InitPackage { ) } - private func writeREADMEFile() throws { - let readme = destinationPath.appending(component: "README.md") - guard self.fileSystem.exists(readme) == false else { + private func writeGitIgnore() throws { + guard packageType != .empty else { return } - - try writePackageFile(readme) { stream in - stream <<< """ - # \(pkgname) - - A description of this package. - - """ - } - } - - private func writeGitIgnore() throws { let gitignore = destinationPath.appending(component: ".gitignore") guard self.fileSystem.exists(gitignore) == false else { return @@ -281,9 +251,10 @@ public final class InitPackage { } private func writeSources() throws { - if packageType == .systemModule || packageType == .manifest { + if packageType == .empty { return } + let sources = destinationPath.appending(component: "Sources") guard self.fileSystem.exists(sources) == false else { return @@ -291,11 +262,9 @@ public final class InitPackage { progressReporter?("Creating \(sources.relative(to: destinationPath))/") try makeDirectories(sources) - if packageType == .empty { - return - } - - let moduleDir = sources.appending(component: "\(pkgname)") + let moduleDir = packageType == .executable + ? sources + : sources.appending(component: "\(pkgname)") try makeDirectories(moduleDir) let sourceFileName = "\(typeName).swift" @@ -305,27 +274,19 @@ public final class InitPackage { switch packageType { case .library: content = """ - public struct \(typeName) { - public private(set) var text = "Hello, World!" - - public init() { - } - } + // The Swift Programming Language + // https://docs.swift.org/swift-book """ case .executable: content = """ - @main - struct \(typeName) { - private(set) var text = "Hello, World!" + // The Swift Programming Language + // https://docs.swift.org/swift-book - static func main() { - print(\(typeName)().text) - } - } + print("Hello, world!") """ - case .systemModule, .empty, .manifest, .`extension`: + case .empty, .`extension`: throw InternalError("invalid packageType \(packageType)") } @@ -334,30 +295,10 @@ public final class InitPackage { } } - private func writeModuleMap() throws { - if packageType != .systemModule { - return - } - let modulemap = destinationPath.appending(component: "module.modulemap") - guard self.fileSystem.exists(modulemap) == false else { - return - } - - try writePackageFile(modulemap) { stream in - stream <<< """ - module \(moduleName) [system] { - header "/usr/include/\(moduleName).h" - link "\(moduleName)" - export * - } - - """ - } - } - private func writeTests() throws { - if packageType == .systemModule { - return + switch packageType { + case .empty, .executable, .`extension`: return + default: break } let tests = destinationPath.appending(component: "Tests") guard self.fileSystem.exists(tests) == false else { @@ -365,12 +306,7 @@ public final class InitPackage { } progressReporter?("Creating \(tests.relative(to: destinationPath))/") try makeDirectories(tests) - - switch packageType { - case .systemModule, .empty, .manifest, .`extension`: break - case .library, .executable: - try writeTestFileStubs(testsPath: tests) - } + try writeTestFileStubs(testsPath: tests) } private func writeLibraryTestsFile(_ path: AbsolutePath) throws { @@ -381,29 +317,11 @@ public final class InitPackage { final class \(moduleName)Tests: XCTestCase { func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - XCTAssertEqual(\(typeName)().text, "Hello, World!") - } - } - - """ - } - } + // XCTest Documenation + // https://developer.apple.com/documentation/xctest - private func writeExecutableTestsFile(_ path: AbsolutePath) throws { - try writePackageFile(path) { stream in - stream <<< """ - import XCTest - @testable import \(moduleName) - - final class \(moduleName)Tests: XCTestCase { - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - XCTAssertEqual(\(typeName)().text, "Hello, World!") + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods } } @@ -418,11 +336,9 @@ public final class InitPackage { let testClassFile = try AbsolutePath(validating: "\(moduleName)Tests.swift", relativeTo: testModule) switch packageType { - case .systemModule, .empty, .manifest, .`extension`: break + case .empty, .`extension`, .executable: break case .library: try writeLibraryTestsFile(testClassFile) - case .executable: - try writeExecutableTestsFile(testClassFile) } } } diff --git a/Tests/WorkspaceTests/InitTests.swift b/Tests/WorkspaceTests/InitTests.swift index d1975dd1c5c..614998ae568 100644 --- a/Tests/WorkspaceTests/InitTests.swift +++ b/Tests/WorkspaceTests/InitTests.swift @@ -162,76 +162,7 @@ class InitTests: XCTestCase { XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent(), "debug", "Foo.swiftmodule")) } } - - func testInitPackageSystemModule() throws { - try testWithTemporaryDirectory { tmpPath in - let fs = localFileSystem - let path = tmpPath.appending(component: "Foo") - let name = path.basename - try fs.createDirectory(path) - - // Create the package - let initPackage = try InitPackage( - name: name, - packageType: .systemModule, - destinationPath: path, - fileSystem: localFileSystem - ) - var progressMessages = [String]() - initPackage.progressReporter = { message in - progressMessages.append(message) - } - try initPackage.writePackageStructure() - - // Not picky about the specific progress messages, just checking that we got some. - XCTAssertGreaterThan(progressMessages.count, 0) - - // Verify basic file system content that we expect in the package - let manifest = path.appending(component: "Package.swift") - XCTAssertFileExists(manifest) - let manifestContents: String = try localFileSystem.readFileContents(manifest) - let version = InitPackage.newPackageToolsVersion - let versionSpecifier = "\(version.major).\(version.minor)" - XCTAssertMatch(manifestContents, .prefix("// swift-tools-version:\(version < .v5_4 ? "" : " ")\(versionSpecifier)\n")) - XCTAssertMatch(manifestContents, .contains(packageWithNameAndDependencies(with: name))) - XCTAssert(fs.exists(path.appending(component: "README.md"))) - XCTAssert(fs.exists(path.appending(component: "module.modulemap"))) - } - } - - func testInitManifest() throws { - try testWithTemporaryDirectory { tmpPath in - let fs = localFileSystem - let path = tmpPath.appending(component: "Foo") - let name = path.basename - try fs.createDirectory(path) - - // Create the package - let initPackage = try InitPackage( - name: name, - packageType: InitPackage.PackageType.manifest, - destinationPath: path, - fileSystem: localFileSystem - ) - var progressMessages = [String]() - initPackage.progressReporter = { message in - progressMessages.append(message) - } - try initPackage.writePackageStructure() - - // Not picky about the specific progress messages, just checking that we got some. - XCTAssertGreaterThan(progressMessages.count, 0) - - // Verify basic file system content that we expect in the package - let manifest = path.appending(component: "Package.swift") - XCTAssertFileExists(manifest) - let manifestContents: String = try localFileSystem.readFileContents(manifest) - let version = InitPackage.newPackageToolsVersion - let versionSpecifier = "\(version.major).\(version.minor)" - XCTAssertMatch(manifestContents, .prefix("// swift-tools-version:\(version < .v5_4 ? "" : " ")\(versionSpecifier)\n")) - } - } - + // MARK: Special case testing func testInitPackageNonc99Directory() throws {