Skip to content

SE-0318 Package creation #3514

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
wants to merge 25 commits into from
Closed

SE-0318 Package creation #3514

wants to merge 25 commits into from

Conversation

miggs597
Copy link
Contributor

swiftlang/swift-evolution#1349

swift package create <packageName> Makes new packages while also handling the ceremony of making the package's directory first.
Defaults to executable package

swift package add-template <url> Facilitates adding new templates from git

Templates are generic Swift package directories that are transformed when making a new packages based off of a template

Directory structure is simplified from:

OldDirStruct % tree
.
OldDirStruct % tree
.
├── Package.swift
├── README.md
├── Sources
│   └── OldDirStruct
│       └── OldDirStruct.swift
└── Tests
    └── OldDirStructTests
        └── OldDirStructTests.swift

To:

.
├── Package.swift
├── README.md
└── Sources
    └── NewDirStruct.swift

@miggs597 miggs597 marked this pull request as draft May 26, 2021 23:32
throw InternalError("Could not get the current working directory")
}

if localFileSystem.exists(cwd.appending(RelativePath(url))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when passing in a path it should be an absolute path, not relative to the working directory.


try localFileSystem.copy(from: currentTemplateLocation, to: templateDirectory)
} else {
let templatePath = configPath.appending(components: "templates", "new-package", String(url.split(separator: "/").last!))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

url parsing in fragile

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Foundation has URLComponents for this.

}
}

internal var useNewBehaviour: Mode {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useNewBehaviour -> createPackageMode

Copy link
Contributor

@tomerd tomerd May 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this var should be be in an extension to InitPackage rather than "free floating" to better encapsulate the logic

}
}

internal enum Mode {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mode -> CreatePackageMode

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function should be be in an extension to InitPackage rather than "free floating" to better encapsulate the logic

@@ -966,3 +1051,207 @@ fileprivate func logPackageChanges(changes: [(PackageReference, Workspace.Packag
}
stream.flush()
}

internal struct PackageTemplate {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this type still required? iirc this was for JSON parsing

case legacy
}

internal func getSwiftPMDefaultTemplate(type: InitPackage.PackageType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function should be be in an extension to InitPackage rather than "free floating" to better encapsulate the logic

}
}

fileprivate func makePackage(fileSystem: FileSystem,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function should be be in an extension to InitPackage rather than "free floating" to better encapsulate the logic

try initPackage.writePackageStructure()
}

fileprivate func copyTemplate(fileSystem: FileSystem, sourcePath: AbsolutePath, destinationPath: AbsolutePath, name: String) throws {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function should be be in an extension to InitPackage rather than "free floating" to better encapsulate the logic

}
}

fileprivate enum MakePackageMode {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this enum should be be in an extension to InitPackage rather than "free floating" to better encapsulate the logic

internal func getSwiftPMDefaultTemplate(type: InitPackage.PackageType,
sources: RelativePath = .init("./Sources"),
tests: RelativePath? = nil,
createSubDirectoryForModule: Bool = false) -> PackageTemplate {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can now return InitPackage.PackageTemplate since we dont need the other type now that we are not parsing JSON

let templateHomeDirectory = configPath.appending(components: "templates", "new-package")

var foundTemplate = false
var templateToUse = ""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make templateToUse optional and then you can get rid of foundTemplate

name = packageName ?? cwd.basename
destinationPath = cwd
case .create:
name = packageName!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not safe! you should guard to make sure its set

print("Copying \($0)")
try copyTemplate(fileSystem: fileSystem, sourcePath: templateHomeDirectory.appending(components: templateToUse, $0), destinationPath: destinationPath, name: name)
}
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if returning here, then the else branch is not required and you can move the let packageTemplate down

print(message)
}
try initPackage.writePackageStructure()
}
Copy link
Contributor

@tomerd tomerd May 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I read this correctly, I dont think this follow the logic defined in the evolution proposal.

I suggest restructuring the code in the following way:

  1. separate makePackage into 2 functions, one for the "legacy behavior" (e.g. makePackageLegacy) and one for the "new" one (e.g. makePackageNew).
  2. from the Init command, call makePackageLegacy when mode == .legacy and makePackageNew when mode == .new
  3. from the Create command, we can always call makePackageNew since its only available when mode == .new
  4. initPackage.writePackageStructure should wither take a mode argument, or also be split into legacy/new since per the design its supposed to work a bit differently in legacy vs. new mode (e.g. not create certain files)

// Recursively copy the template package
// Currently only replaces the string literal "$NAME"
if fileSystem.isDirectory(sourcePath) {
if let fileName = sourcePath.pathString.split(separator: "/").last {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this fileName or directory name?

renamed = renamed.replacingOccurrences(of: "___NAME_AS_C99___", with: name.spm_mangledToC99ExtendedIdentifier())

if let fileName = sourcePath.pathString.split(separator: "/").last {
if !fileSystem.exists(destinationPath.appending(component: String(fileName))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like you can move this condition up so not to do anything if the file already exists

@@ -241,7 +274,7 @@ public final class InitPackage {
# \(pkgname)

A description of this package.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove redundant space

@@ -13,6 +13,7 @@ import SPMTestSupport
import TSCBasic
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally none of these test have to change


func testCreateNoNameArgument() throws {
try XCTSkipIf(useNewBehaviour == .legacy)
fixture(name: "Miscellaneous") { prefix in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the fixture needed here?

Package.swift Outdated
@@ -273,7 +273,7 @@ let package = Package(
dependencies: ["swift-build", "swift-package", "swift-test", "swift-run", "Commands", "Workspace", "SPMTestSupport"]),
.testTarget(
name: "WorkspaceTests",
dependencies: ["Workspace", "SPMTestSupport"]),
dependencies: ["Workspace", "SPMTestSupport", "Commands"]),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this likely points to some leaky abstraction - we should not need to bring Commands into workspace tests

if useNewBehaviour == .new {
subCommands.insert(Create.self, at: 6)
subCommands.insert(AddTemplate.self, at: 7)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

@@ -13,6 +13,7 @@ import SPMTestSupport
import TSCBasic
import PackageModel
import Workspace
@testable import Commands
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above, this likely point to some leaky abstraction and should not be needed


public init() {}

public static var _errorLabel: String { "error" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a requirement of swift-argument-parser?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do need the constructor, but _errorLabel isn't needed.
From the swift-argument-parser documentation _errorLabel is used to insert the label at the beginning of an error message.

/// The label to use for "Error: ..." messages from this type. (experimental)
    public static var _errorLabel: String { get }

]

if InitPackage.createPackageMode == .new {
subCommands.insert(Create.self, at: 6)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to derive these indices from looking up Init.self in the array? If not, it would be good to have a comment up in that array, since it's very easy otherwise to add a subcommand without realizing that this hardcoded index has to be updated.

@@ -266,10 +266,15 @@ class InitTests: XCTestCase {
try localFileSystem.removeFileTree(packageRoot)
try localFileSystem.createDirectory(packageRoot)

let packageTemplate = InitPackage.PackageTemplate(sourcesDirectory: RelativePath("./Sources"),
testsDirectory: RelativePath("./Tests"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not assume that the path separator is /. You should use the join method to generate the relative paths.

Miguel Perez added 22 commits June 30, 2021 22:31
Might have hit limitation of json, would be nice to have support for multiline strings
Needed because the default directory structure has been simplified
- Moved to transforming dir
- Tests are now skipped depending on flag
- Restored InitTests.swift
- add-template is less fragile
- extension instead of free floating
testPlatforms() was removed because the refactoring also removes InitPackage.InitPackageOptions()

It wasn't possible from a user to access, it's dead code and only used by this test.
- Fixed some typos
- add-template is now more robust when determining if we have a path or web url
- rough implementation of update-template
-C needed to come first
The funny name is just a folder that is space separated
@miggs597 miggs597 marked this pull request as ready for review July 1, 2021 03:11
@abertelrud
Copy link
Contributor

@swift-ci please smoke test


struct AddTemplate: SwiftCommand {
static let configuration = CommandConfiguration(
abstract: "Donwloads/moves a template into the default template directory")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
abstract: "Donwloads/moves a template into the default template directory")
abstract: "Downloads/moves a template into the default template directory")

@OptionGroup(_hiddenFromHelp: true)
var swiftOptions: SwiftToolOptions

@Argument(help: "URL to template (absolute path to template works too)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@Argument(help: "URL to template (absolute path to template works too)")
@Argument(help: "URL or absolute path of template")

@elsh elsh marked this pull request as draft July 12, 2021 21:24
@abertelrud
Copy link
Contributor

Closing because we are exploring a different approach (evolution proposal was sent back).

@abertelrud abertelrud closed this Aug 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants