Skip to content
Merged
Changes from 5 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e4efbeb
DRAFT: Swiftly proxies
cmcgee1024 Aug 10, 2024
3b5c404
Add a swiftly install workflow where the version comes from the .swif…
cmcgee1024 Aug 10, 2024
956256f
update design to make auto-installation an error instead
cmcgee1024 Aug 20, 2024
375df29
provide a mechanism to find the currently in-use toolchain physical l…
cmcgee1024 Aug 20, 2024
ed68a9e
add more details about the selector prefix, and methods to escape
cmcgee1024 Aug 21, 2024
c745f4c
Restructure the PR to move the selector syntax from the proxies to a …
cmcgee1024 Aug 29, 2024
560236d
Implement proxy mechanism with dynamic toolchain selection
cmcgee1024 Sep 4, 2024
ae29e88
Merge branch 'main' of github.com:cmcgee1024/swiftly into proxies-design
cmcgee1024 Sep 5, 2024
ce0d27a
Rewrite the select toolchain function with a type for the selection r…
cmcgee1024 Sep 5, 2024
66dd459
Update the documentation
cmcgee1024 Sep 5, 2024
2df7357
Create a swiftly run command
cmcgee1024 Sep 6, 2024
8976bba
Fix empty command case with a single ++++
cmcgee1024 Sep 7, 2024
3caab66
Write run command and proxy tests
cmcgee1024 Sep 9, 2024
31f4327
Regenerate the cli reference documentation
cmcgee1024 Sep 9, 2024
16caf80
Fix design document discrepancies and add install proxy argument tests
cmcgee1024 Sep 9, 2024
b2aa165
Update the list command to decorate default, and in-use toolchains
cmcgee1024 Sep 10, 2024
7504dd3
Update list tests to check for in use and default labels
cmcgee1024 Sep 10, 2024
0e7b661
Make the version argument optional in the install subcommand
cmcgee1024 Sep 11, 2024
43d620a
Fix case of empty bin directory when checking for overwrite
cmcgee1024 Sep 12, 2024
2e3c59d
Remove +install selector option from swift run in favour of regular `…
cmcgee1024 Sep 20, 2024
9dd640c
Import GPG keys on every install to get new signing keys from swift.org
cmcgee1024 Sep 17, 2024
5e615ea
Make recommended documentation changes.
cmcgee1024 Oct 18, 2024
10a0856
Merge branch 'main' of github.com:cmcgee1024/swiftly into proxies-design
cmcgee1024 Oct 18, 2024
6d6050e
Provide a better error message on swiftly install with no version
cmcgee1024 Oct 19, 2024
6989796
Update README, and add documentation for the new run subcommand
cmcgee1024 Oct 20, 2024
52d081f
Prompt before updating the `.swift-version` file.
cmcgee1024 Oct 23, 2024
2f6d701
Merge branch 'main' of github.com:cmcgee1024/swiftly into proxies-design
cmcgee1024 Oct 23, 2024
cb0e923
Create proxies on toolchain installation, creating only the necessary…
cmcgee1024 Nov 5, 2024
bb36de0
Fix the design document
cmcgee1024 Nov 5, 2024
7269128
Merge branch 'main' of github.com:cmcgee1024/swiftly into proxies-design
cmcgee1024 Nov 8, 2024
35cb5c5
Merge branch 'main' of github.com:cmcgee1024/swiftly into proxies-design
cmcgee1024 Nov 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 49 additions & 21 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,28 @@ This document contains the high level design of swiftly. Not all features have b

## Index

- [Swiftly's purpose](#swiftlys-purpose)
- [Installation of swiftly](#installation-of-switly)
- [Linux](#linux)
- [Installation of swiftly](#installation-of-swiftly)
- [Installation of a Swift toolchain](#installation-of-a-swift-toolchain)
- [macOS](#macos)
- [Installation of swiftly](#installation-of-swiftly-1)
- [Installation of a Swift toolchain](#installation-of-a-swift-toolchain-1)
- [Interface](#interface)
- [Toolchain names and versions](#toolchain-names-and-versions)
- [Commands](#commands)
- [Toolchain selection](#toolchain-selection)
- [Detailed design](#detailed-design)
- [Implementation sketch - Core](#implementation-sketch---core)
- [Implementation sketch - Ubuntu 20.04](#implementation-sketch---ubuntu-2004)
- [Implementation sketch - macOS](#implementation-sketch---macos)
- [`config.json` schema](#configjson-schema)

## Swiftly's purpose

Swiftly helps you to easily install different Swift toolchains locally on your account. It also provides a single path where you can run the tools in the currently selected toolchain. Toolchain selection is [configurable](#toolchain-selection) using different mechanisms.

Note that swiftly is *not* a virtual toolchain in itself since there are cases where it cannot behave as a self-contained Swift toolchain. For example, there can be external dependencies on specific files, such as headers or libraries, far too many and variable between toolchain versions to be managed by swiftly. Also, for long-lived processes, there is no way to gracefully restart them without help from the client.
Copy link
Member

Choose a reason for hiding this comment

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

This sentence needs some tweaking I think

Suggested change
Note that swiftly is *not* a virtual toolchain in itself since there are cases where it cannot behave as a self-contained Swift toolchain. For example, there can be external dependencies on specific files, such as headers or libraries, far too many and variable between toolchain versions to be managed by swiftly. Also, for long-lived processes, there is no way to gracefully restart them without help from the client.
Note that swiftly is *not* a virtual toolchain in itself since there are cases where it cannot behave as a self-contained Swift toolchain. For example, there can be external dependencies on specific files, such as headers or libraries, far too many variables between toolchain versions to be managed by swiftly. Also, for long-lived processes, there is no way to gracefully restart them without help from the client.


## Installation of swiftly

The installation of swiftly is divided into two phases: delivery and initialization. Delivery of the swiftly binary can be accomplished using different methods:
Expand Down Expand Up @@ -60,7 +67,7 @@ A simple setup for managing the toolchains could look like this:

The toolchains (i.e. the contents of a given Swift download tarball) would be contained in the toolchains directory, each named according to the major/minor/patch version. `config.json` would contain any required metadata (e.g. the latest Swift version, which toolchain is selected, etc.). If pulling in Foundation to use `JSONEncoder`/`JSONDecoder` (or some other JSON tool) would be a problem, we could also use something simpler.

The `~/.local/bin` directory would include symlinks pointing to the `bin` directory of the "active" toolchain, if any.
The `~/.local/bin` directory would include symlinks pointing to swiftly itself. When these binaries are run swiftly proxies them to the requested toolchain, or a default.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be good to more clearly explain what "these binaries" refer to in this sentence.


This is all very similar to how rustup does things, but I figure there's no need to reinvent the wheel here.

Expand All @@ -78,7 +85,7 @@ The contents of `~/Library/Application Support/swiftly` would look like this:
– env
```

Instead of downloading tarballs containing the toolchains and storing them directly in `~/.local/share/swiftly/toolchains`, we instead install Swift toolchains to `~/Library/Developer/Toolchains` via the `.pkg` files provided for download at swift.org. To select a toolchain for use, we update the symlinks at `~/Library/Application Support/swiftly/bin` to point to the desired toolchain in `~/Library/Developer/Toolchains`. In the env file, we’ll contain a line that looks like `export PATH="$HOME/Library/Application Support/swiftly:$PATH"`, so the version of swift being used will automatically always be from the active toolchain. `config.json` will contain version information about the selected toolchain as well as its actual location on disk.
Instead of downloading tarballs containing the toolchains and storing them directly in `~/.local/share/swiftly/toolchains`, we instead install Swift toolchains to `~/Library/Developer/Toolchains` via the `.pkg` files provided for download at swift.org. In the env file, we’ll add a line that looks like `export PATH="$HOME/Library/Application Support/swiftly:$PATH"`, so that swiftly can proxy toolchain commands to the requested toolchain, or default. `config.json` will contain version information about the selected toolchain as well as its actual location on disk.

This scheme works for ensuring the version of Swift used on the command line can be controlled, but it doesn’t affect the active toolchain used by Xcode, which uses its own mechanisms for that. Xcode, if it is installed, can find the toolchains installed by swiftly.

Expand Down Expand Up @@ -138,6 +145,14 @@ Installing a specific snapshot from a swift version development branch

`swiftly install 5.5-snapshot-2022-1-28`

##### Installing the version from the `.swift-version` file

A package could have a swift version file that specifies the recommended toolchain version. A swiftly install with no version will search for a version file and install that version.

`swiftly install`

If no swift version file can be found then the installation fails indicating that it couldn't fine the file.
Copy link
Contributor

Choose a reason for hiding this comment

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

Very small nit, but could we use

If no .swift-version file ...

Rather than

swift version file

Just to be consistent, so users don't conflate the "swift version file" with something else in the Swift ecosystem. This way we explicitly mention the file in question. This is how it's written in the "Installing the version from the .swift-version files" section


#### uninstall

Uninstalling versions of Swift should be in a similar form to install. Uninstalling a toolchain that is currently “in use” (see the “use” command section below) will cause swiftly to use the latest Swift release toolchain that is installed. If none are, the latest snapshot will be used. If no snapshots are installed either, then a message will be printed indicating that all Swift versions are uninstalled.
Expand Down Expand Up @@ -178,7 +193,7 @@ To list all the versions of swift installed on your system

#### use

“Using” a toolchain sets it as the active toolchain, meaning it will be the one found via $PATH and invoked via `swift` commands executed in the shell. Only a single toolchain can be used at a given time. Using a toolchain doesn’t uninstall anything; it only updates symlinks so that the requested toolchain can be found by the shell.
“Using” a toolchain sets it as the default toolchain, meaning it will be the default one that is used when running toolchain commands from the shell. Only a single toolchain can be the default at a given time and location. Using a toolchain doesn’t uninstall anything; it only updates the configuration.

To use the toolchain associated with the most up-to-date Swift version, the “latest” version can be specified:

Expand Down Expand Up @@ -208,6 +223,10 @@ To use the latest installed main snapshot, leave off the date:

`swiftly use main-snapshot`

The use subcommand also supports `.swift-version` files. If version file is present in the current working directory, or an ancestory directory, then swiftly will update that file with the new version to use. This can be a useful feature for a team to share and align on toolchain versions with git. As a special case, if swiftly could not find a version file, but it could find a Package.swift file it will create a new version file for you in the package and set that to the requested toolchain version.
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
The use subcommand also supports `.swift-version` files. If version file is present in the current working directory, or an ancestory directory, then swiftly will update that file with the new version to use. This can be a useful feature for a team to share and align on toolchain versions with git. As a special case, if swiftly could not find a version file, but it could find a Package.swift file it will create a new version file for you in the package and set that to the requested toolchain version.
If a .swift-version file

Simple wording change suggestion


Note: The `.swift-version` file mechanisms can be overridden using the `--global-default` flag so that your swiftly installation's default toolchain can be set explicitly.
Copy link
Contributor

Choose a reason for hiding this comment

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

I would rather the default is the other way. ie use edits global settings and you can change local settings with a --local flag.

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 think that there are advantage of having this local as the default. It encourages the sharing of the toolchain revision that the team is working with through discovery of this feature of swiftly. Also, this helps to tie into one of the values of using swiftly itself, which is how easily and flexibly you can switch toolchains.

In terms of precedence, git defaults to local over global for its configuration.

I'm curious what you think are the pitfalls of this approach.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I realised as I was writing this, there is previous art that defaults to local. It is more a preference on my behalf and how I'd use swiftly

Copy link

Choose a reason for hiding this comment

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

pyenv and rbenv call this command local, global or shell depending on whether you're setting the Python/Ruby version for a directory, globally on the machine (for your user), or just in the current shell. The shell setting overrides the local setting, which overrides the global setting.


#### update

Update replaces a given toolchain with a later version of that toolchain. For a stable release, this means updating to a later patch version. For snapshots, this means updating to the most recently available snapshot.
Expand Down Expand Up @@ -266,6 +285,28 @@ This command checks to see if there are new versions of `swiftly` itself and upg

`swiftly self-update`

### Toolchain selection

Swiftly will create a set of symbolic links in its SWIFTLY_BIN_DIR during installation that point to the swiftly binary itself for each of the common toolchain commands, such as swift, swiftc, clang, etc. This mechanism will allows swiftly to proxy those command invocations to a selected toolchain at the time of invocation. A toolchain can be selected in these ways in order of precedence:

* Special toolchain selectors among the regular tool command-line arguments (e.g. `swift build +5.10.1`) with the special '+' prefix
* The presence of a .swift-version file in the current working directory, or ancestor directory, with the required toolchain version
* The swiftly default (in-use) toolchain set in the swftly config.json by `swiftly install` or `swiftly use` commands

If swiftly cannot find an installed toolchain that matches the selection then it fails with an error and instructions how to use `swiftly install` to satisfy the selection next time.

A few notes about the '+' prefix. First, if a literal '+' prefix should be sent directly to the tool as an argument then it is escaped by doubling it with '++'. An argument with only '++++' is ignored entirely, but any additional arguments are sent directly to the tool without any further inspection of their prefixes. This is analogous to the special '--' token that certain argument parsers accept so that they don't interpret anything following that token as command flags or options.

#### Resolve selected toolchain

For cases where the physical toolchain must be located, such as references specific header files, or shared libraries that are not proxied by swiftly there is a method to resolve the currently selected toolchain to its physical location using `swiftly use`.

```
swiftly use --location
Copy link
Contributor

Choose a reason for hiding this comment

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

perhaps --print-location? As it is, it looks like --location affects use in some way.

```

This command will provide the full path to the directory where the selected toolchain is installed to standard output if such a toolchain exists. An external tool can directly navigate to the resources that it requires. For external tools that manage long-lived processes from the toolchain, such as the language server, and lldb, this command can be used in a poll to detect cases where the processes should be restarted.

## Detailed Design

Swiftly itself will be a SPM project consisting of several executable products, one per supported platform, and all of these will share the core module that handles argument parsing, printing help information, and dispatching commands. Each platform’s executable will be built to statically link the stdlib so that they can be run without having installed Swift first.
Expand Down Expand Up @@ -457,15 +498,7 @@ https://download.swift.org/swift-5.5.1-release/ubuntu1604/swift-5.5.1-RELEASE/sw
$ tar -xf <URL> --directory ~/.local/share/swiftly/toolchains
```

It also updates `config.json` to include this toolchain as the latest for the provided version. If installing a new patch release toolchain, the now-outdated one can be deleted (e.g. `5.5.0` can be deleted when `5.5.1` is installed).

Finally, the use implementation executes the following to update the link:

```
$ ln -s ~/.local/share/swiftly/toolchains/<toolchain>/usr/bin/swift ~/.local/bin/swift
```

It also updates `config.json` to include this version as the currently selected one.
It also updates `config.json` to include this toolchain as the latest for the provided version. If installing a new patch release toolchain, the now-outdated one can be deleted (e.g. `5.5.0` can be deleted when `5.5.1` is installed). The `config.json` is updated to include this version as the currently selected (default) one.

### Implementation Sketch - macOS

Expand All @@ -481,18 +514,13 @@ https://download.swift.org/swift-<version>-RELEASE/xcode/swift-<version>-RELEASE

`config.json` is then updated to include this toolchain as the latest for the provided version.

Finally, the use implementation executes the following to update the link:

```
$ ln -s ~/Library/Developer/Toolchains/<toolchain name> ~/.swiftly/active-toolchain
```

It also updates `config.json` to include this version as the currently selected one.
It also updates `config.json` to include this version as the currently selected (default) one.

### `config.json` Schema

```
{
"version": "<version of swiftly that created/updated this config.json file>",
"platform": {
"namePretty": <OS name pretty printed>,
"fullName": <OS name used in toolchain file name>,
Expand Down