SwiftPM support for Swift scripts

# I propose to add a `swift-script` tool which is similar to `swift-package`

+1 for the swift script tool, but we could add it as a tool in SwiftPM instead of it's own thing. I'd say this kind of syntax feels the most fitting and these could be some sub-commands to manage the cache, run scripts etc.

swift script [filename] [options, eg: -update, -clean] // Runs a script and installs packages
swift script list // Lists all packages in ~/Library/Developer/SwiftPM/Cache
swift script clean // Cleans cache
swift script remove [repository] // Removes repository from cache

Hello to all the Swift fellows!

As a GSoC applicant who decided to pick up this idea, I’ve drafted a revised proposal which adds lots of details to the original one. Here are a few highlights for the new parts:

  • The new swift-script tool for managing scripts and caches;
  • Design details on how a script is built and run;
  • Some alternative designs.

Welcome to check it out!

4 Likes

Hi Folks,

This is a promising effort and great discussion. As a distant observer my urge is to simplify and ship!

Why not just have a .swift file declare a PackageDependencies.Script object with a bootstrap static package member and a main entry point for SwiftPM tooling to build/run?

Easy scripting could really help expand the Swift ecosystem. This could have HUGE benefits to the community.

But proliferating swift syntax and alternate build systems can really complicate the ecosystem. Scripting should add near-zero complexity or confusion.

I prefer a single source of truth for configuration, and would not want to see @package url's in Doit.swift code conflicting with Package.swift statements. I also don't see package-imports as the only problem now with scripts (hash-bang's? shared build locations? reusing scripts?), so I don't see language tweaks converging in a summer on something viable (assuming we prefer shipping over blogging :)

So, perhaps reduce requirements to minimum-viable and strengthen constraints...

Goal is to write the smallest tool extension to build+run a smallish single-file script with only package dependencies.

So, instead of

  MyPackage/
    Sources/
      Source.swift
      main.swift
    Package.swift

We have

  SomeScripts/
     Dothis.swift
     Dothat.buildrun.swift
     ...

Some constraints:

  • No syntax changes in the language. sourcekit-lsp should handle scripts without modification, and scripts should compile like any other .swift file (if moved to an appropriate package).

  • Scripts use exactly the same build SwiftPM caching and build logic as swift packages. No new ./built-here locations (but see below).

  • (Scripts should not be run automatically during builds. I shouldn't be running untrusted code just by building a package I import.)

As for implementation, I would add type Script to PackageDependencies; Script would declare the package and main entry point. Then new buildrun tool mode (i.e., your SwiftPM script) could bootstrap:

If the swift file only declares a single Script object, swift buildrun .../Doit.swift will interpret the Script static member package: Package declaration to bootstrap the build for the script object, and optionally run the declared main entry point.

Then it's just a matter of API design and coding conventions for a Script implementation to reduce the package declaration and CLI using reasonable defaults/conventions, and some tooling shims (or script wrappers) to implement build-run.

Notice in v1 that scripts are leaves of a dependency tree:

  • Nothing depends on script code.
  • Script code only depends on packages, not on other swift files.

This I think reduces scripts to small CLI controllers assembling functionality from normal packages; anything larger should just be in a regular package (or deferred to v-1+). (Thus the dev cycle: hack a script, if anything reusable evolves, throw it into a package, iterate...)

Swift tooling would need to support this bootstrapping, and the .build directory layout updated to handle multiple nested scripts, e.g., if scripts are in SomeScripts/, then SomeScripts/.build/scripts/Doit/{normal package layout} (or an option for a user-wide build directory). swift buildrun might support a base directory for local packages (preferably relative to the jailed script directory tree in v1).

Simple IDE launch support might depend on using an extended suffix, e.g., Doit.buildrun.swift. For v1 CLI, users would deploy shell mechanisms (e.g. aliases/functions) to avoid a hash-bang at the top of the file, perhaps based on the same Doit.buildrun.swift convention.

As for script boundaries:

  • Script package names are not visible outside the script and might not be consistent across builds.

  • There is no sharing between script build environments (at least in v1), resulting in significant duplication of commonly-imported packages.

  • When migrating the script code into a package, the excess package declaration is ignored. I.e., it is an error to include the script file in a regular package, unless the regular package dependencies are sufficient. In this case, no attempt is made to detect deltas between the script-internal package declaration and the external package declaration.

If this could be implemented in a summer, it would be a great first step!

Wes

what is the Proposal number? That link is broken

"NNNN" is normally used as a placeholder. This proposal has not been officially proposed yet, so it doesn't have a number.

Ah thanks for clarifying.

I'm very interested in this proposal. How can I help officially propose it?

@stevapple (along with this pitch's original authors) started a new pitch as a project for this year's Google Summer of Code. I think you can probably contact them to see how or what you can help with.