SwiftPM support for Swift scripts

Also, swift build <myscript.swift> to create an executable would be pretty nifty. IIRC, the Swift compiler is not included by default on the Mac (it's part of the SDK which is part of Xcode), but it would be nice to share prebuilt utility apps (written as scripts) with non-developers.

2 Likes

Fab. Few notes from what people wanted from swift-sh:

  1. Omitting version is convenient. Grabbing the url is easy, figuring out versions can be tedious. Personally I write a lot of quick scripts.
  2. Local URLs should work, eg. file://../foo (edit I see this was covered in the proposal)
  3. Creating the resolved file alongside the script makes sense but I also sense it’s not really wanted mostly. Naturally you need one, but you could stick it with build artifacts.
  4. Hopefully editing the scripts will work in Xcode, completion is basically essential (I'm not suggesting the @package will work outside of scripts).
  5. Defining Swift version may be something to consider… or possibly that could be done in the shebang
  6. swift-sh defines SWIFT_PACKAGE for the script and thus the dependencies
  7. Of course, subsequent instantiations for an unchanged file should be almost immediate, swift-sh adds 10ms for the second instantiation versus running the binary directly
  8. You may want to add something to the proposal on how a user might “update” their script’s dependencies, swift-sh doesn’t offer this and honestly I never thought up anything particularly good.

People are mixed on if their scripts are short or long lived, and right—we all have some of both. Scripting is naturally for both these things and the tooling should be aware of that when designed.

22 Likes

First of all this is an amazing proposal, I really hope that it'll be implemented in the future.
Designing the API is always hard. Here's what I could imagine as a possible solution for this feature:

// import a single package from remote
import Yams from .package(url: "https://github.com/jpsim/Yams.git", from: "2.0.0")
// import a local package 
import MyPackage from .package(path: "../MyPackage") 



// loads everything inside the [] as a dependency...
import * [
    .package(url: "https://github.com/jpsim/Yams.git", from: "2.0.0"),
    .package(path: "../MyPackage")  
]
//... but you still can choose what you need, using regular import
import Yams
import MyPackage


// or alternatively you can specify the packages in a list
import Yams, MyPackage from [
    .package(url: "https://github.com/jpsim/Yams.git", from: "2.0.0"),
    .package(path: "../MyPackage")
]



// +1:  support for the most pouplar git hosting platforms (github, gitlab, bitbucket)...
import Yams from .github("jpsim/Yams", from: "2.0.0")

What do you think about this?

9 Likes

I think that's a great idea/pitch :+1:

Such things have been around for Groovy[1] and Scala[2] for a while, if you'd like to research some prior art. Generally though, it seels like the annotating an import could work well here, esp with the common case of the product name == module name that we want to import :slight_smile:

[1] Dependency management with Grape
[2] Ammonite

As @Aciid mentioned in SwiftPM support for Swift scripts - #12 by Aciid, it would be nice if we can embed the @/#package attribute into the import statement.

This would avoid duplicate, potentially inconsistent @/#packages, and also force the declaration to stay close to the import statement itself :+1:

Like everyone else here, I very welcome this pitch, +1!

Can't wait to use something like this. Great job in putting forwards the pitch.

I would like to echo the sentiment expressed by a few others though about enhancing the import statement rather than using a separate @package attribute. By enhancing import we don't have to remember to do two separate operations to do essentially one - "import X from X". I think @tiborbodecs has a good starting example of this .

1 Like

Where users would use multiple scripts on the same machine, confusion or pollution might arise. In a larger context, the concept of combining a virtual environment with the package manager like in Python (+pip) would allow one to keep dependencies “configurable” and tied consistently with the script. Here, I would encourage some form of overridable configuration option, for example by using an environment variable.

See Python venv

1 Like

Thanks for making this evolution proposal.

FWIW script with Swift don’t really exist because when you use swift-sh or this proposal capabilities what you enable is one file Swift programming.

After thinking more about it the @packageurl(url:...) should be on the top of the file or at every import.

I might have been lazy in my previous post and did not explain clearly why I think. What I was trying to explain is that we should add small things to greatly improve the user experience of writing one file program in Swift. Writing them is not great today because you can’t write them in Xcode.

Ignoring the shebang and adding way to circumvent the top level declaration maybe with a define, will allow better experience of writing one file Swift program in Xcode without modifying Xcode.

I think in order to have a great experience of writing a one file Swift program you should be able to build them, test them within the editor and of course have a good auto-completion support.

What do you means by short-lived ?

Swift script are used everyday in our workflow(validating git commit messages, allow ci to merge if the pr as been accepted by my colleagues, get the simulator platform from xcrun simctl, update the pbxproj, binding slack usernames and github mail for the ci to contact dev in case of issues...).

This allow us to replace all other kind of scripting(shell, go, ruby, python) in our CI and project.

Same here! I’m very excited to see this proposal, +1 for @import statement should be consistent. As said how to deal with different Swift versions? For sure, it's not part of the current proposal but something like Python's virtual environment would be super useful.

1 Like

Very big fan of this proposal. I can also see this being useful in cases like notebooks, which currently have their own solution for this.

I recently started to use Swift scripts via the excellent swift-sh quite extensively (to drive the backend of SwiftPM Catalog). I also have written quite extensive bash scripts suites, e.g. for SwiftXcode. So a few comments on this.

First of all, I'd love to see that kind of functionality bundled on every Swift system (instead of having to do the brew install). It is a missing piece to replace interpreted systems.
As an example have a look at this, it gives you a tiny HTTP server from within just a script, w/o any of the usual bulk overhead: httpd-hellowolrd.

at-package ... will be rejected if used during compilation of a regular Swift module or the Swift REPL

That would be unfortunate. I usually start a script in Xcode and then run it on the shell when it is mostly ready. Being able to do this is one of the big advantages of Swift scripts over other systems. Works just fine w/ swift-sh.

Scripts are often short-lived

In my experience this is not the case, almost the reverse is true. Scripts on Unix systems often have a way longer livetime than even daemon binaries.
REPL is the thing for short lived stuff.

Package resolution

There should in no case be a .resolved file alongside the script. The execution environment quite likely doesn't even have the permissions to create that.

It could live in /tmp or /var/lib/cache or whatever is appropriate for the specific platform. I think it should be re-resolved if the timestamp of the script is newer (so to force an update, a user can just touch the script, as many are used to from Makefile's)

Build products

This is related to the package resolution. IMO this has to be dealt with in SPM itself. One of the worst aspects of SPM is that has to refetch dependency repositories for every tiny little project, and rebuild the dependencies all the time.
SPM needs a proper fragment system. Once it has that, a lot of stuff will come for free to the scripts. (SwiftXcode has this via its "images" feature)

What's the plan for rebuilding? Hopefully not on every launch. Hash the file?

(no, this is not what is mentioned in "Global package dependencies", though that also sounds quite useful)

Environment

This has quite a few aspects, and I just ran into this in swift-sh (issue 101). What's the Unix environment, whats the argv, whats the cwd going to be. Are you going to log SPM resolution output, if so, where.

Libraries should be able to detect as well that they are being run as part of the script, because that can affect how they are doing lookup of resources.

In general I would suggest to have a look at each and every issue filed against swift-sh, and have an answer for them ;-): Issues ¡ mxcl/swift-sh ¡ GitHub

4 Likes

Overall a fine idea - just one remark regarding the details:

Why that? This behavior breaks with macOS standards (which puts such things into a subfolder of ~/Library).
You may argue that Swift is not Mac-only, but the reality is that other platforms are not nearly as important now (and I don't see this changing in the near future). Afaik, Ubuntu is the only other OS with official support, so this would adopt the standard of a small minority in favor of what the majority is used to.

For me, attention for tiny things like the layout of the filesystem is a major part of what made Apple great, and imho we shouldn't adopt concepts that are inferior.
I really wouldn't mind if Swift on Linux would stick with Mac conventions, but it's not even necessary:
Some people already realized that dotfile creep is a real issue, so there are solutions like XDG Base Directory Specification.

2 Likes

Totally +1 for this. I wouldn't go into changing import syntax a lot for that as was proposed in some comments, except maybe allowing specifying multiple modules from the same package separated with a comma. This might be handy in general for Swift, not just in context of scripts and SPM.

3 Likes

This is really cool and adding functionality like this would be great. However, my immediate response to the post is the proposed syntax is simply too heavy. In a scripting context, you naturally want stuff to be terser. I think mirroring the Package.swift declaration is not the best thing to aim for. Or, at least, you need to offer convenience shorthands on top of that.

IMO, you should be able to write import @package("https://github.com/jpsim/Yams.git") and that would automatically infer the module name from the last path component of the URL, and equivalent to the latest released package version. Specifying the product would also ideally be similarly short, something like import @package(TSCUtility, "https://github.com/apple/swift-tools-support-core.git").

12 Likes

EDIT: I wanted to mention that I am +1 for the overall idea. I've done a bit of scripting in Swift and would love to have this flexibility to do more.

A few questions / comments:
Could the @package attribute just be ignored if that file is compiled in a non-scripting context? Or potentially even used as a hint to tools like Xcode to ask the user to add a dependency?

It seems like there would be certain packages that would be used very often in scripts, a command-line parsing package for instance. It seems unfortunate that if I had 50 scripts using command-line parsing that I need 50 different clones and 50 different builds of the same package (if I understand the proposal correctly). Could identical package declarations potentially rendezvous on the same package clone? The syntax in the script would still be the same, remaining portable.

2 Likes

Since writing my previous posts, I’ve come around a bit to the idea of somehow allowing editing and running within Xcode for the convenience of that. Also for code completion which is sorely missed when writing scripts right now. I still think it’s unfortunate to have to build the shebang into swift’s syntax even if just to ignore it but maybe that’s a relatively minor thing or maybe editors will need to ignore that line so the compiler doesn’t need to.

I meant that it is common to write scripts are used only once or for a very short time. For that case, it isn't really necessary to deal with all the version management concerns that a long-lived project needs, so we should make some of it optional to support this use case.

1 Like

I'm also +1 on this proposal, and really like @James_Dempsey suggestion for the compiler ignoring the declaration during compilation (and leaving it to the possibility of suggesting what packages to get/pull and install for tooling such as Xcode)

I think for many small projects/libraries this would make such scripts incredibly useful as sample code and simple examples that could just as easily be copy pasted in and edited, to make it easy to use those libraries as well without the "compilation error" speed bump.

1 Like

One thing I like about the @package attribute approach is that it keeps the syntax of import declarations simple, especially since using import without a package specification is likely to be an incredibly common use case. The attribute approach strongly implies it is something to be used only in particular use cases.

With either approach a developer could provide conflicting package specifications across multiple import statements.

I agree with comments that the SPM package specification syntax itself could potentially be simplified, dropping the url: and path: labels. Looking at the API, it’s clear from the parameter name which is which, and reading code at the call site, it’s obvious which strings are URLs and which are paths. And, that would simplify the syntax here.

A shorter string instead of the common https://github.com prefix also seems like a useful SPM package enhancement overall, but especially nice for scripting.

I do think that adding a way to just specify the URL of a package and not have to specify a version would be useful in writing scripts. Maybe if no version is specified, the @package attribute would request a package from SPM as if (Int.max).0.0 was the ‘from:’ argument.

EDITED a bit for clarity.

1 Like

Amazing! Big +1, this is a big step in order to make swift more suitable for scripting.

Just a question, how does this will work with sourcekit-lsp? as far as I can remember sourcekit-lsp doesn't work for single .swift files.

2 Likes