diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af94bdc..8c0ace4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,6 +76,7 @@ jobs: with: swift-version: ${{ matrix.swift }} windows-toolchain-variant: noasserts + windows-minimal: true - name: Check the +NoAsserts toolchain was selected, and compile env: VERSION: ${{ steps.setup.outputs.version }} diff --git a/README.md b/README.md index d11f864..4620ec7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ see [How verification works](#how-verification-works)). It never runs a pre-exis | `swift-sdks` | no | | Cross-compilation Swift SDKs to install after the toolchain: `static-linux`, `wasm`, `android` (comma or space separated). | | `use-preinstalled` | no | `false` | **Unverified opt-in.** If `swift` on `PATH` already reports exactly `swift-version`, run and use it instead of installing. By default no pre-existing binary is ever executed. | | `windows-toolchain-variant` | no | `asserts` | Windows only. `asserts` keeps the installer's default `+Asserts` toolchain; `noasserts` installs the `+NoAsserts` one instead. Use `noasserts` on win-arm64, where the `+Asserts` `swift-package.exe` fails to start ([swiftlang/swift#92379](https://github.com/swiftlang/swift/issues/92379)). | -| `cache` | no | `true` | Cache the downloaded Linux toolchain archive and its `.sig` with `actions/cache`. The archive is re-verified with GPG on every run and extracted into a clean per-version directory; `false` reuses nothing on disk. | +| `windows-minimal` | no | `false` | Windows only. `true` leaves out what a CI build does not use: the debugger (`OptionsInstallDBG`), its embedded Python (`OptionsInstallPy`) and the Android platform (`OptionsInstallAndroidPlatform`). The IDE feature stays: without it `swift.exe` fails to start (`ERROR_SXS_CANT_GEN_ACTCTX`). | +| `cache` | no | `true` | Cache the downloaded Linux toolchain archive and its `.sig` with `actions/cache`. The archive is re-verified with GPG on every run and extracted into a clean per-version directory; `false` reuses nothing on disk. The macOS `.pkg` and the Windows installer are not cached: restoring them from the Actions cache measured slower than downloading them from swift.org. | ## Outputs @@ -60,7 +61,7 @@ A cache entry that fails verification fails the job instead of being re-download `RUNNER_TEMP` is private to one runner instance and emptied for every job on GitHub-hosted runners, so earlier or concurrent jobs cannot plant or wipe the toolchain. Self-hosted runners must not share `RUNNER_TEMP` between concurrently running jobs, and must run jobs of different trust levels on separate runner instances. Missing build prerequisites (`libcurl4-openssl-dev`, -`libxml2-dev`, the GCC dev packages, ...) are installed with `apt-get` only if absent. +`libxml2-dev`, the GCC dev packages, ...) are installed with `apt-get` only if absent - from the package lists the runner image ships, refreshed only if that fails, and, apart from `curl` and `gnupg2`, in the background while the archive is fetched, verified and extracted. The archive is extracted with `pigz` when the runner has it. macOS: the package is installed into the user domain (`~/Library/Developer/Toolchains`), falling back to `sudo installer -target /`. `TOOLCHAINS` is exported with the toolchain's bundle identifier and its diff --git a/action.yml b/action.yml index fe46e25..4fe4eb6 100644 --- a/action.yml +++ b/action.yml @@ -26,10 +26,19 @@ inputs: (https://github.com/swiftlang/swift/issues/92379); the +NoAsserts one does not. required: false default: "asserts" + windows-minimal: + description: >- + Windows only: leave out what a CI build does not use - the debugger, its embedded Python and the + Android platform. Shortens the install; default false installs everything the installer + installs by default. + required: false + default: "false" cache: description: >- Cache the downloaded Linux toolchain archive and its signature with actions/cache. The archive is - re-verified with GPG on every run; with false nothing on disk is ever reused. + re-verified with GPG on every run; with false nothing on disk is ever reused. The macOS .pkg and + the Windows installer are not cached: restoring them from the Actions cache measured slower than + downloading them from swift.org. required: false default: "true" outputs: @@ -111,6 +120,7 @@ runs: env: SWIFT_VERSION: ${{ inputs.swift-version }} WINDOWS_TOOLCHAIN_VARIANT: ${{ inputs.windows-toolchain-variant }} + WINDOWS_MINIMAL: ${{ inputs.windows-minimal }} run: '& "$env:GITHUB_ACTION_PATH/install-windows.ps1"' - name: Report Swift version diff --git a/install-linux.sh b/install-linux.sh index 55c9d5a..e540fdb 100755 --- a/install-linux.sh +++ b/install-linux.sh @@ -31,15 +31,37 @@ for pkg in curl gnupg2 binutils git libcurl4-openssl-dev libedit2 libsqlite3-0 l libncurses-dev pkg-config tzdata uuid-dev zlib1g-dev "libgcc-${gcc_ver}-dev" "libstdc++-${gcc_ver}-dev"; do dpkg -s "$pkg" > /dev/null 2>&1 || missing+=("$pkg") done -if [ "${#missing[@]}" -gt 0 ]; then - echo "Installing missing packages: ${missing[*]}" - "${sudo_cmd[@]}" apt-get update -qq - "${sudo_cmd[@]}" env DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends "${missing[@]}" +# The package lists a runner image ships are tried first, and refreshed only when that fails: the +# refresh is most of what installing a couple of packages costs. +apt_install() { + "${sudo_cmd[@]}" env DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends "$@" \ + || { "${sudo_cmd[@]}" apt-get update -qq \ + && "${sudo_cmd[@]}" env DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends "$@"; } +} + +# curl and gnupg2 download and verify the toolchain, so they have to be there first. Everything else +# is needed only to run it, and is installed in the background while the archive is fetched, +# verified and extracted, then waited for at the end. +needed_now=() +later=() +for pkg in "${missing[@]}"; do + case "$pkg" in curl | gnupg2) needed_now+=("$pkg") ;; *) later+=("$pkg") ;; esac +done +if [ "${#needed_now[@]}" -gt 0 ]; then + echo "Installing missing packages: ${needed_now[*]}" + apt_install "${needed_now[@]}" fi scratch="$(mktemp -d "${RUNNER_TEMP:-/tmp}/setup-swift.XXXXXX")" trap 'rm -r -f "$scratch"' EXIT +apt_pid="" +if [ "${#later[@]}" -gt 0 ]; then + echo "Installing missing packages in the background: ${later[*]}" + apt_install "${later[@]}" > "$scratch/apt.log" 2>&1 & + apt_pid=$! +fi + download() { # url dest curl --fail --silent --show-error --location --retry 5 --retry-delay 5 --retry-all-errors \ --connect-timeout 30 --output "$2" "$1" \ @@ -107,9 +129,20 @@ assert_no_symlinks "$dest" rm -r -f -- "$dest" mkdir "$dest" assert_no_symlinks "$dest" -tar -xzf "$snap/$file" -C "$dest" --strip-components=1 --no-same-owner +# pigz where the runner has it: inflation stays single-threaded, but pigz reads, writes and +# checksums on threads of its own. +if command -v pigz > /dev/null; then + tar -I pigz -xf "$snap/$file" -C "$dest" --strip-components=1 --no-same-owner +else + tar -xzf "$snap/$file" -C "$dest" --strip-components=1 --no-same-owner +fi rm -r -f -- "$snap" [ -x "$dest/usr/bin/swift" ] || { echo "::error::$dest/usr/bin/swift is missing after extraction"; exit 1; } +if [ -n "$apt_pid" ] && ! wait "$apt_pid"; then + cat "$scratch/apt.log" + echo "::error::Installing the packages the toolchain needs failed: ${later[*]}" + exit 1 +fi if [ -n "${GITHUB_PATH:-}" ]; then echo "$dest/usr/bin" >> "$GITHUB_PATH"; fi if [ -n "${GITHUB_OUTPUT:-}" ]; then echo "source=$source" >> "$GITHUB_OUTPUT"; fi echo "Installed Swift toolchain at $dest" diff --git a/install-windows.ps1 b/install-windows.ps1 index 207829f..67d5f2d 100644 --- a/install-windows.ps1 +++ b/install-windows.ps1 @@ -17,6 +17,16 @@ switch ($variant) { } default { Write-Host "::error::windows-toolchain-variant must be asserts or noasserts, got '$variant'"; exit 1 } } +# windows-minimal: the installer features a CI build does not use - the debugger, its Python and the +# Android platform, a second SDK. Not the IDE feature: without it swift.exe fails to start with +# ERROR_SXS_CANT_GEN_ACTCTX (14001), measured on 6.4.1 with each feature left out on its own. +switch ($env:WINDOWS_MINIMAL) { + { $_ -in @('', 'false') } { } + 'true' { + $installerOptions += @('OptionsInstallDBG=0', 'OptionsInstallPy=0', 'OptionsInstallAndroidPlatform=0') + } + default { Write-Host "::error::windows-minimal must be true or false, got '$env:WINDOWS_MINIMAL'"; exit 1 } +} # Publisher the installer must be signed by, see README.md. swift.org's Windows installers are signed # with an Apple Inc. certificate whose OU (an Apple internal group id) is deliberately not pinned. $ExpectedSubject = '^CN=Apple Inc\., (OU=[^,]+, )?O=Apple Inc\., L=Cupertino, S=California, C=US$'