Skip to content
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

[SE-0301] Updating a project and its manifest programmatically and from the command line #7467

Merged
merged 8 commits into from Apr 22, 2024

Conversation

DougGregor
Copy link
Member

@DougGregor DougGregor commented Apr 17, 2024

Package manifest files are Swift source code, so editing them means working with the source code directly. Introduce a new library PackageModelSyntax based on swift-syntax that allows us to perform targeted manipulations of the manifest file programmatically, making suggested edits to the file without having to understand everything in it.

Introduce two editing commands:

  • addPackageDependency: adds a new package dependency to the manifest file, e.g.,
     .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1"),
  • addTarget: adds a new target to the manifest file, e.g.,
     .executableTarget(
      name: "MyProgram",
      dependencies: [ "MyLib" ]
    )

In both cases, this is handled as a limited transform on the swift-syntax tree that then produces a series of edits that update the package manifest. We don't make any attempts to reason about the whole package, so this doesn't require any round-tripping with a semantic model. The editing commands can also introduce new files into the package. For example, adding target will create a new source file Sources/\(targetName)/\(targetName).swift with contents appropriate for the target type (struct for a library, @main for an executable, macro implementation struct for a macro target, etc.).

These editing operations are surfaced by implementing two of the commands from SE-0301 "Package Editing Commands", swift package add-dependency and swift package add-target. Adding a dependency looks a bit like this:

swift package add-dependency --from 510.0.1 https://github.com/apple/swift-syntax.git

and will produce the .package(...) example above and insert it into the appropriate place in the manifest.

Adding a target looks like this:

swift package add-target MyProgram --type executable --dependencies MyLib OtherLib

and adds this target to the manifest

.executableTarget(
    name: "MyProgram",
    dependencies: [
        "MyLib",
        "OtherLib"
    ]
),

as well as Sources/MyProgram/MyProgram.swift:

import MyLib
import OtherLib

@main
struct MyProgramMain {
    static func main() {
        print("Hello, world")
    }
}

@MaxDesiatov
Copy link
Member

@swift-ci test

@DougGregor DougGregor changed the title Add a PackageModelSyntax library that manipulates the source of a manifest file Adding package dependencies to the manifest programmatically and from the command line Apr 17, 2024
Sources/PackageModelSyntax/AddPackageDependency.swift Outdated Show resolved Hide resolved

/// Default indent when we have to introduce indentation but have no context
/// to get it right.
let defaultIndent = TriviaPiece.spaces(4)
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, huh. Should I be worried that this is processing the entire file? I suppose I should thread through an optional indentation value if we're going to do that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don’t think it should be a huge deal if the user actually invokes swift package add and better than just using 4 spaces which does tend to look stupid if the rest of the file is indented with 2 spaces.

Sources/PackageModelSyntax/SyntaxEditUtils.swift Outdated Show resolved Hide resolved
Comment on lines +62 to +60
static func findFirst(
in node: some SyntaxProtocol,
matching predicate: (Self) -> Bool
) -> Self? {
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn’t it make more sense for this to be an instance function on SyntaxProtocol instead of a static function?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, because you are looking for something of the Self type.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was thinking if it is a cleaner API to have it be like

tree.findFirst { (node: FunctionCallSyntax) -> Bool in
  doChecks(for: node)
}

Instead of

FunctionCallSyntax.findFirst(in: tree) { node in
  doChecks(for: node)
}

which reads a little backwards to me. But maybe it’s a matter of taste.

Copy link
Member Author

Choose a reason for hiding this comment

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

I hate requiring type annotations on closures... but I can create an ofType argument or similar to make this cleaner.

Sources/PackageModelSyntax/SyntaxEditUtils.swift Outdated Show resolved Hide resolved
Sources/PackageModelSyntax/SyntaxEditUtils.swift Outdated Show resolved Hide resolved
@DougGregor DougGregor changed the title Adding package dependencies to the manifest programmatically and from the command line [SE-0301] Adding package dependencies to the manifest programmatically and from the command line Apr 18, 2024
@DougGregor
Copy link
Member Author

This is in decent shape except that the bootstrap script is totally broken.

@DougGregor
Copy link
Member Author

@swift-ci please test

@DougGregor
Copy link
Member Author

@swift-ci please test macOS

@DougGregor
Copy link
Member Author

@swift-ci please test Windows

@DougGregor
Copy link
Member Author

@swift-ci please test macOS

@DougGregor
Copy link
Member Author

@swift-ci please test

@DougGregor
Copy link
Member Author

@swift-ci please test Windows

/// The set of argument labels that can occur after the "dependencies"
/// argument in the Package initializers.
///
/// TODO: Could we generate this from the the PackageDescription module, so
Copy link
Contributor

Choose a reason for hiding this comment

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

If we ship the abi.json file for PackageDescription in the toolchain, that might work

Copy link
Member Author

Choose a reason for hiding this comment

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

If we're going to look there, we might as well just parse the PackageDescription module sources, since we have swift-syntax around.

@MaxDesiatov
Copy link
Member

@swift-ci test windows

@DougGregor
Copy link
Member Author

@swift-ci please test

@DougGregor
Copy link
Member Author

@swift-ci please test macOS

@DougGregor
Copy link
Member Author

@swift-ci please test Windows

…ifest file

Package manifest files are Swift source code, so editing them means working
with the source code directly. Introduce a new library based on swift-syntax
that allows us to perform targeted manipulations of the manifest file
programmatically, making suggested edits to the file without having to
understand everything in it.

Introduce one editing operation that adds a particular package dependency
to the manifest file. For example, it takes the programmatic representation
of a package dependency in the model
(`PackageModel.PackageDependency`) and the syntax for a manifest file,
then it will produce a set of edits that extend (or add) the
dependencies in the `Package` instance to the manifest file, e.g.,

    .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1"),

We make an attempt to match the surrounding trivia so that we don't make
an ugly mess of the resulting manifest. I have tests for a number of
cases to make sure they look nice, but I expect we'll have to refine
the heuristics over time.
…nifest

Introduce a new `package add-dependency` command that adds a package
dependency to your manifest file, given command-line arguments
containing the URL and the version requiements (branch, from-version,
etc.). This utilizes the new infrastructure for swift-syntax based
package editing, putting a command-line interface over it.

Here's the help output:

    OVERVIEW: Add a package dependency to the manifest

    USAGE: swift package add-dependency <dependency> [--exact <exact>] [--revision <revision>] [--branch <branch>] [--from <from>] [--up-to-next-minor-from <up-to-next-minor-from>] [--to <to>]

    ARGUMENTS:
      <dependency>            The URL or directory of the package to add

    OPTIONS:
      --exact <exact>         The exact package version to depend on
      --revision <revision>   The specific package revision to depend on
      --branch <branch>       The branch of the package to depend on
      --from <from>           The package version to depend on (up to the next
                              major version)
      --up-to-next-minor-from <up-to-next-minor-from>
                              The package version to depend on (up to the next
                              minor version)
      --to <to>               Specify upper bound on the package version range
                              (exclusive)
      --version               Show the version.
      -h, -help, --help       Show help information.
Blame Owen for the evil genius of this hack, me for trying to put it in
The formatting is a mess, but the functionality is sound.
… to a package

Add a new package command to add a target with the given name, type, and
dependencies to the package. This includes adding the target to the
package manifest as well as creating stub source files for the target to
guide the user. For example, this command:

    swift package add-target SocialGraphClientTests --dependencies SocialGraphClient --type test

adds the following to the targets in the package manifest:

    .testTarget(name: "SocialGraphClientTests", dependencies: ["SocialGraphClient"]),

as well as creating the file `Tests/SocialGraphClientTests/SocialGraphClientTests.swift`,
which looks like this:

    import SocialGraphClient
    import XCTest

    class SocialGraphClientTests: XCTestCase {
        func testSocialGraphClientTests() {
            XCTAssertEqual(42, 17 + 25)
        }
    }

There is, undoubtedly, some tuning to do to clean this up. Here's the
command-line interface, which mostly aligns with SE-0301:

    OVERVIEW: Add a new target to the manifest

    USAGE: swift package add-target <name> [--type <type>]
[--dependencies <dependencies> ...] [--url <url>] [--path <path>]
[--checksum <checksum>]

    ARGUMENTS:
      <name>                  The name of the new target

    OPTIONS:
      --type <type>           The type of target to add, which can be
one of
                              (default: library)
      --dependencies <dependencies>
                              A list of target dependency names
      --url <url>             The URL for a remote binary target
      --path <path>           The path to a local binary target
      --checksum <checksum>   The checksum for a remote binary target
      --version               Show the version.
      -h, -help, --help       Show help information.
Rather than trying to thread through trivia everywhere, apply BasicFormat
to arguments.
For macro targets, we need to add two new files: one with a macro definition,
and another with the list of provided macros for the macro plugin. Reshuffle
some code to make that easier.
…ands

Start building package editing commands with the CMake build system
as well, using FetchContent to get the swift-syntax libraries.
@DougGregor
Copy link
Member Author

@swift-ci please test

1 similar comment
@DougGregor
Copy link
Member Author

@swift-ci please test

@DougGregor DougGregor changed the title [SE-0301] Adding package dependencies to the manifest programmatically and from the command line [SE-0301] Updating a project and its manifest programmatically and from the command line Apr 22, 2024
@DougGregor DougGregor merged commit 47573ce into apple:main Apr 22, 2024
5 checks passed
@DougGregor DougGregor deleted the package-model-syntax branch April 22, 2024 13:25
DougGregor added a commit to DougGregor/swift-package-manager that referenced this pull request Apr 24, 2024
DougGregor added a commit that referenced this pull request Apr 25, 2024
#7494)

* **Explanation**: Implement package manifest editing commands (`swift
package add-dependency`, `swift package add-target`, `swift package
add-product`) described in
[SE-0301](https://github.com/apple/swift-evolution/blob/main/proposals/0301-package-editing-commands.md),
using swift-syntax under the hood to perform the edits.
* **Original PR**:
#7467,
#7476,
#7477
* **Risk**: Very low. All new code for new commands.
* **Reviewed by**: @ahoppen , @bnbarham , @owenv , @MaxDesiatov 
* **Testing**: New tests.

---------

Co-authored-by: Rintaro Ishizaki <rishizaki@apple.com>
Co-authored-by: Saleem Abdulrasool <compnerd@compnerd.org>
hjyamauchi added a commit to hjyamauchi/swift-installer-scripts that referenced this pull request Apr 26, 2024
compnerd pushed a commit to apple/swift-installer-scripts that referenced this pull request Apr 27, 2024
DougGregor pushed a commit to DougGregor/swift that referenced this pull request Apr 28, 2024
This reflects apple/swift-package-manager#7467

(cherry picked from commit 56d91588206c67032c5a0a244918254dba7e3cc3)
furby-tm pushed a commit to wabiverse/swift-package-manager that referenced this pull request May 15, 2024
furby-tm pushed a commit to wabiverse/swift-package-manager that referenced this pull request May 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants