Skip to content

Commit

Permalink
Init template cleanup
Browse files Browse the repository at this point in the history
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 swiftlang#6144

rdar://98999734
  • Loading branch information
bitjammer committed Feb 11, 2023
1 parent 2d90cc8 commit 079a811
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 202 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ Swift Next
}
```


Swift 5.9
-----------

Expand All @@ -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
-----------
Expand Down
12 changes: 6 additions & 6 deletions Documentation/Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
10 changes: 4 additions & 6 deletions Sources/Commands/PackageTools/Init.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
154 changes: 35 additions & 119 deletions Sources/Workspace/InitPackage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -177,51 +168,43 @@ 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)"]),
]
""")
}

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)
}
Expand All @@ -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
Expand All @@ -281,21 +251,20 @@ 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
}
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"
Expand All @@ -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)")
}

Expand All @@ -334,43 +295,18 @@ 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 {
return
}
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 {
Expand All @@ -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
}
}
Expand All @@ -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)
}
}
}
Expand Down
Loading

0 comments on commit 079a811

Please sign in to comment.