diff --git a/.eslintrc.json b/.eslintrc.json index f07f74f..d93bd9e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,7 +5,7 @@ "parserOptions": { "ecmaVersion": 9, "sourceType": "module", - "project": "./tsconfig.json" + "project": [ "./tsconfig.base.json", "./tsconfig.json"] }, "rules": { "i18n-text/no-en": "off", diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/pull_request_template.md diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6d939fe..c700109 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -169,6 +169,10 @@ jobs: - os: windows-latest swift: '5.3' development: false + - os: ubuntu-22.04 + swift: ${{ fromJSON(vars.SETUPSWIFT_CUSTOM_TOOLCHAINS).ubuntu2204 }} + development: true + noverify: true steps: - name: Checkout repository @@ -201,10 +205,11 @@ jobs: cache-snapshot: ${{ !matrix.development }} - name: Verify Swift version in macos - if: runner.os == 'macOS' + if: runner.os == 'macOS' && matrix.noverify != 'true' run: xcrun --toolchain ${{ env.TOOLCHAINS || '""' }} swift --version | grep ${{ steps.setup-swift.outputs.swift-version }} || exit 1 - name: Verify Swift version + if: matrix.noverify != 'true' run: swift --version | grep ${{ steps.setup-swift.outputs.swift-version }} || exit 1 dry-run: diff --git a/README.md b/README.md index f81c870..3c9e5e2 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,16 @@ In other words specifying... - `"4"` will resolve to latest minor and patch version (aka `4.2.4`) - `"4.0.0"` will resolve to version `4.0` +
+ Additionally, to use custom toolchains, download URL can be provided. The download URL must point to a `tar` archive for `Linux`, `pkg` file for `macOS` and `exe` file for `Windows`. + + i.e. for `macOS`: https://github.com/swiftwasm/swift/releases/download/swift-wasm-5.10-SNAPSHOT-2024-03-30-a/swift-wasm-5.10-SNAPSHOT-2024-03-30-a-macos_x86_64.pkg + for `Linux`: https://github.com/swiftwasm/swift/releases/download/swift-wasm-5.10-SNAPSHOT-2024-03-30-a/swift-wasm-5.10-SNAPSHOT-2024-03-30-a-ubuntu22.04_x86_64.tar.gz + + > [!IMPORTANT] + > When using custom toolchains, please ensure that the toolchain can be installed and used on the GitHub runner, this action won't be able to validate this for custom toolchains. +
+ ### Caveats YAML interprets eg. `4.0` as a float, this action will then interpret that as `4` which will result in eg. Swift `4.2.4` being resolved. Quote your inputs! Thus surround version input with quotations: diff --git a/__tests__/installer/linux.test.ts b/__tests__/installer/linux.test.ts index 56d66d0..cb07e2c 100644 --- a/__tests__/installer/linux.test.ts +++ b/__tests__/installer/linux.test.ts @@ -1,11 +1,16 @@ +import os from 'os' import * as path from 'path' import {promises as fs} from 'fs' +// @ts-ignore +import {__setos as setos} from 'getos' import * as core from '@actions/core' import * as exec from '@actions/exec' import * as cache from '@actions/cache' import * as toolCache from '@actions/tool-cache' import {coerce as parseSemVer} from 'semver' import {LinuxToolchainInstaller} from '../../src/installer/linux' +import {ToolchainVersion} from '../../src/version' +import {Platform} from '../../src/platform' describe('linux toolchain installation verification', () => { const env = process.env @@ -17,7 +22,8 @@ describe('linux toolchain installation verification', () => { dir: 'swift-5.8-RELEASE', docker: '5.8-jammy', platform: 'ubuntu2204', - branch: 'swift-5.8-release' + branch: 'swift-5.8-release', + preventCaching: false } beforeEach(() => { @@ -32,7 +38,7 @@ describe('linux toolchain installation verification', () => { it('tests download', async () => { const installer = new LinuxToolchainInstaller(toolchain) expect(installer['version']).toStrictEqual(parseSemVer('5.8')) - expect(installer['baseUrl']).toBe( + expect(installer['baseUrl'].href).toBe( 'https://download.swift.org/swift-5.8-release/ubuntu2204/swift-5.8-RELEASE' ) @@ -127,4 +133,35 @@ describe('linux toolchain installation verification', () => { const devVersion = await installer.installedSwiftVersion() expect(devVersion).toBe('5.9-dev') }) + + it('tests custom swift tool caching', async () => { + setos({os: 'linux', dist: 'Ubuntu', release: '22.04'}) + jest.spyOn(os, 'arch').mockReturnValue('x64') + const swiftwasm = 'https://github.com/swiftwasm/swift/releases/download' + const name = 'swift-wasm-5.10-SNAPSHOT-2024-03-30-a' + const resource = `${name}-ubuntu22.04_x86_64.tar.gz` + const toolchainUrl = `${swiftwasm}/${name}/${resource}` + const cVer = ToolchainVersion.create(toolchainUrl, false) + const download = path.resolve('tool', 'download', 'path') + const extracted = path.resolve('tool', 'extracted', 'path') + const cached = path.resolve('tool', 'cached', 'path') + jest.spyOn(core, 'getBooleanInput').mockReturnValue(true) + jest.spyOn(cache, 'restoreCache').mockResolvedValue(undefined) + jest.spyOn(toolCache, 'find').mockReturnValue('') + jest.spyOn(fs, 'cp').mockResolvedValue() + jest.spyOn(toolCache, 'downloadTool').mockResolvedValue(download) + jest.spyOn(toolCache, 'extractTar').mockResolvedValue(extracted) + jest.spyOn(toolCache, 'cacheDir').mockResolvedValue(cached) + jest.spyOn(exec, 'exec').mockResolvedValue(0) + const cacheSpy = jest.spyOn(cache, 'saveCache') + const installer = await Platform.install(cVer) + expect(cacheSpy).not.toHaveBeenCalled() + expect(installer.data.baseUrl?.href).toBe(path.posix.dirname(toolchainUrl)) + expect(installer.data.preventCaching).toBe(true) + expect(installer.data.name).toBe('Swift Custom Snapshot') + expect(installer.data.platform).toBe('ubuntu2204') + expect(installer.data.download).toBe(resource) + expect(installer.data.dir).toBe(name) + expect(installer.data.branch).toBe('swiftwasm') + }) }) diff --git a/__tests__/installer/windows.test.ts b/__tests__/installer/windows.test.ts index f3aaaa0..a8c19e3 100644 --- a/__tests__/installer/windows.test.ts +++ b/__tests__/installer/windows.test.ts @@ -19,7 +19,8 @@ describe('windows toolchain installation verification', () => { dir: 'swift-5.8-RELEASE', platform: 'windows10', branch: 'swift-5.8-release', - windows: true + windows: true, + preventCaching: false } const visualStudio = VisualStudio.createFromJSON({ installationPath: path.join('C:', 'Visual Studio'), @@ -61,7 +62,7 @@ describe('windows toolchain installation verification', () => { it('tests download without caching', async () => { const installer = new WindowsToolchainInstaller(toolchain) expect(installer['version']).toStrictEqual(parseSemVer('5.8')) - expect(installer['baseUrl']).toBe( + expect(installer['baseUrl'].href).toBe( 'https://download.swift.org/swift-5.8-release/windows10/swift-5.8-RELEASE' ) diff --git a/__tests__/installer/xcode.test.ts b/__tests__/installer/xcode.test.ts index e8782f7..885f984 100644 --- a/__tests__/installer/xcode.test.ts +++ b/__tests__/installer/xcode.test.ts @@ -16,12 +16,13 @@ describe('macOS toolchain installation verification', () => { name: 'Xcode Swift 5.8.1', date: new Date('2023-05-31T18:30:00.000Z'), download: 'swift-5.8.1-RELEASE-osx.pkg', - symbols: 'swift-5.8.1-RELEASE-osx-symbols.pkg', + debug_info: 'swift-5.8.1-RELEASE-osx-symbols.pkg', dir: 'swift-5.8.1-RELEASE', xcode: '14.3.1', platform: 'xcode', branch: 'swift-5.8.1-release', - xcodePath: '/Applications/Xcode_14.3.1.app' + xcodePath: '/Applications/Xcode_14.3.1.app', + preventCaching: false } beforeEach(() => { @@ -56,7 +57,7 @@ describe('macOS toolchain installation verification', () => { it('tests download', async () => { const installer = new XcodeToolchainInstaller(toolchain) expect(installer['version']).toStrictEqual(parseSemVer('5.8.1')) - expect(installer['baseUrl']).toBe( + expect(installer['baseUrl'].href).toBe( 'https://download.swift.org/swift-5.8.1-release/xcode/swift-5.8.1-RELEASE' ) diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index c0f964b..d29167a 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -20,7 +20,8 @@ describe('setup-swift run validation', () => { dir: 'swift-5.8-RELEASE', docker: '5.8-jammy', platform: 'ubuntu2204', - branch: 'swift-5.8-release' + branch: 'swift-5.8-release', + preventCaching: false } it('tests dry run', async () => { diff --git a/__tests__/snapshot/linux.test.ts b/__tests__/snapshot/linux.test.ts index 51bbdd8..6aff2ef 100644 --- a/__tests__/snapshot/linux.test.ts +++ b/__tests__/snapshot/linux.test.ts @@ -1,4 +1,5 @@ import os from 'os' +import {posix} from 'path' // @ts-ignore import {__setos as setos} from 'getos' import {ToolchainVersion} from '../../src/version' @@ -30,6 +31,7 @@ describe('fetch linux tool data based on options', () => { 'swift-4.2.4-RELEASE-ubuntu18.04.tar.gz.sig' ) expect(lTool.docker).toBeUndefined() + expect(lTool.preventCaching).toBe(false) }) it('fetches ubuntu 18.04 latest swift 5.0 tool', async () => { @@ -46,6 +48,7 @@ describe('fetch linux tool data based on options', () => { 'swift-5.0.3-RELEASE-ubuntu18.04.tar.gz.sig' ) expect(lTool.docker).toBe('5.0.3-bionic') + expect(lTool.preventCaching).toBe(false) }) it('fetches ubuntu 18.04 latest swift 5.5.0 tool', async () => { @@ -62,6 +65,7 @@ describe('fetch linux tool data based on options', () => { 'swift-5.5-RELEASE-ubuntu18.04.tar.gz.sig' ) expect(lTool.docker).toBe('5.5-bionic') + expect(lTool.preventCaching).toBe(false) }) it('fetches ubuntu 20.04 arm64 latest swift 5.6.0 tool', async () => { @@ -79,6 +83,7 @@ describe('fetch linux tool data based on options', () => { 'swift-5.6-RELEASE-ubuntu20.04-aarch64.tar.gz.sig' ) expect(lTool.docker).toBe('5.6-focal') + expect(lTool.preventCaching).toBe(false) }) it('fetches ubuntu 18.04 latest swift 5.5 tool', async () => { @@ -95,6 +100,7 @@ describe('fetch linux tool data based on options', () => { 'swift-5.5.3-RELEASE-ubuntu18.04.tar.gz.sig' ) expect(lTool.docker).toBe('5.5.3-bionic') + expect(lTool.preventCaching).toBe(false) }) it('fetches ubuntu 18.04 latest swift 5.5 tool including dev snapshot', async () => { @@ -111,6 +117,7 @@ describe('fetch linux tool data based on options', () => { 'swift-5.5.3-RELEASE-ubuntu18.04.tar.gz.sig' ) expect(lTool.docker).toBe('5.5.3-bionic') + expect(lTool.preventCaching).toBe(false) }) it('fetches ubuntu 18.04 latest swift tool', async () => { @@ -124,6 +131,7 @@ describe('fetch linux tool data based on options', () => { expect(lTool.platform).toBeTruthy() expect(lTool.branch).toBeTruthy() expect(lTool.download_signature).toBeTruthy() + expect(lTool.preventCaching).toBe(false) }) it('fetches ubuntu 18.04 latest swift tool including dev snapshot', async () => { @@ -138,6 +146,7 @@ describe('fetch linux tool data based on options', () => { expect(lTool.branch).toBeTruthy() expect(lTool.download_signature).toBeTruthy() expect(lTool.docker).toBeTruthy() + expect(lTool.preventCaching).toBe(false) }) it('handles swift tool version not present by returning undefined', async () => { @@ -163,6 +172,7 @@ describe('fetch linux tool data based on options', () => { 'swift-5.6.1-RELEASE-ubuntu20.04.tar.gz.sig' ) expect(lTool.docker).toBe('5.6.1-focal') + expect(lTool.preventCaching).toBe(false) }) it('fetches ubuntu 20.04 latest swift 5.2 tool', async () => { @@ -180,6 +190,7 @@ describe('fetch linux tool data based on options', () => { 'swift-5.2.5-RELEASE-ubuntu20.04.tar.gz.sig' ) expect(lTool.docker).toBe('5.2.5-focal') + expect(lTool.preventCaching).toBe(false) }) it('fetches centos 7 latest swift tool', async () => { @@ -194,6 +205,7 @@ describe('fetch linux tool data based on options', () => { expect(lTool.branch).toBeTruthy() expect(lTool.download_signature).toBeTruthy() expect(lTool.docker).toBeTruthy() + expect(lTool.preventCaching).toBe(false) }) it('fetches ubuntu 22.04 named swift tool', async () => { @@ -213,6 +225,7 @@ describe('fetch linux tool data based on options', () => { expect(lTool.download_signature).toBe( 'swift-DEVELOPMENT-SNAPSHOT-2023-09-02-a-ubuntu22.04.tar.gz.sig' ) + expect(lTool.preventCaching).toBe(false) }) it('fetches ubuntu 22.04 named versioned swift tool', async () => { @@ -232,6 +245,7 @@ describe('fetch linux tool data based on options', () => { expect(lTool.download_signature).toBe( 'swift-5.9-DEVELOPMENT-SNAPSHOT-2023-09-01-a-ubuntu22.04.tar.gz.sig' ) + expect(lTool.preventCaching).toBe(false) }) it('fetches ubuntu 18.04 latest swift 5.5 tools', async () => { @@ -271,4 +285,24 @@ describe('fetch linux tool data based on options', () => { const tools = await Platform.toolchains(ver5_2) expect(tools.length).toBe(2) }) + + it('fetches ubuntu 22.04 custom swift tools', async () => { + setos({os: 'linux', dist: 'Ubuntu', release: '22.04'}) + jest.spyOn(os, 'arch').mockReturnValue('x64') + const swiftwasm = 'https://github.com/swiftwasm/swift/releases/download' + const name = 'swift-wasm-5.10-SNAPSHOT-2024-03-30-a' + const resource = `${name}-ubuntu22.04_x86_64.tar.gz` + const toolchainUrl = `${swiftwasm}/${name}/${resource}` + const cVer = ToolchainVersion.create(toolchainUrl, false) + const tools = await Platform.toolchains(cVer) + expect(tools.length).toBe(1) + const tool = tools[0] + expect(tool.baseUrl?.href).toBe(posix.dirname(toolchainUrl)) + expect(tool.preventCaching).toBe(true) + expect(tool.name).toBe('Swift Custom Snapshot') + expect(tool.platform).toBe('ubuntu2204') + expect(tool.download).toBe(resource) + expect(tool.dir).toBe(name) + expect(tool.branch).toBe('swiftwasm') + }) }) diff --git a/__tests__/snapshot/windows.test.ts b/__tests__/snapshot/windows.test.ts index d0eac82..78dc882 100644 --- a/__tests__/snapshot/windows.test.ts +++ b/__tests__/snapshot/windows.test.ts @@ -1,4 +1,5 @@ import os from 'os' +import {posix} from 'path' // @ts-ignore import {__setos as setos} from 'getos' import {ToolchainVersion} from '../../src/version' @@ -27,6 +28,7 @@ describe('fetch windows tool data based on options', () => { expect(wTool.platform).toBe('windows10') expect(wTool.branch).toBeTruthy() expect(wTool.download_signature).toBeTruthy() + expect(wTool.preventCaching).toBe(false) }) it('fetches windows 10 latest swift 5.0 tool', async () => { @@ -47,6 +49,7 @@ describe('fetch windows tool data based on options', () => { expect(wTool.platform).toBe('windows10') expect(wTool.branch).toBe('swift-5.5-release') expect(wTool.download_signature).toBe('swift-5.5-RELEASE-windows10.exe.sig') + expect(wTool.preventCaching).toBe(false) }) it('fetches windows 10 latest swift 5.5 tool', async () => { @@ -62,6 +65,7 @@ describe('fetch windows tool data based on options', () => { expect(wTool.download_signature).toBe( 'swift-5.5.3-RELEASE-windows10.exe.sig' ) + expect(wTool.preventCaching).toBe(false) }) it('fetches windows 10 latest swift 5.5 tool including dev snapshot', async () => { @@ -77,6 +81,7 @@ describe('fetch windows tool data based on options', () => { expect(wTool.download_signature).toBe( 'swift-5.5.3-RELEASE-windows10.exe.sig' ) + expect(wTool.preventCaching).toBe(false) }) it('fetches windows 10 latest swift tool', async () => { @@ -91,6 +96,7 @@ describe('fetch windows tool data based on options', () => { expect(wTool.platform).toBeTruthy() expect(wTool.branch).toBeTruthy() expect(wTool.download_signature).toBeTruthy() + expect(wTool.preventCaching).toBe(false) }) it('fetches windows 10 latest swift tool including dev snapshot', async () => { @@ -103,6 +109,7 @@ describe('fetch windows tool data based on options', () => { expect(wTool.dir).toBeTruthy() expect(wTool.platform).toBeTruthy() expect(wTool.branch).toBeTruthy() + expect(wTool.preventCaching).toBe(false) }) it('fetches windows 10 named swift tool', async () => { @@ -112,16 +119,17 @@ describe('fetch windows tool data based on options', () => { const version = ToolchainVersion.create(name, false) const tool = await Platform.toolchain(version) expect(tool).toBeTruthy() - const lTool = tool as WindowsToolchainSnapshot - expect(lTool.download).toBe( + const wTool = tool as WindowsToolchainSnapshot + expect(wTool.download).toBe( 'swift-DEVELOPMENT-SNAPSHOT-2023-08-10-a-windows10.exe' ) - expect(lTool.dir).toBe('swift-DEVELOPMENT-SNAPSHOT-2023-08-10-a') - expect(lTool.platform).toBe('windows10') - expect(lTool.branch).toBe('development') - expect(lTool.download_signature).toBe( + expect(wTool.dir).toBe('swift-DEVELOPMENT-SNAPSHOT-2023-08-10-a') + expect(wTool.platform).toBe('windows10') + expect(wTool.branch).toBe('development') + expect(wTool.download_signature).toBe( 'swift-DEVELOPMENT-SNAPSHOT-2023-08-10-a-windows10.exe.sig' ) + expect(wTool.preventCaching).toBe(false) }) it('fetches windows 10 named versioned swift tool', async () => { @@ -131,16 +139,17 @@ describe('fetch windows tool data based on options', () => { const version = ToolchainVersion.create(name, false) const tool = await Platform.toolchain(version) expect(tool).toBeTruthy() - const lTool = tool as WindowsToolchainSnapshot - expect(lTool.download).toBe( + const wTool = tool as WindowsToolchainSnapshot + expect(wTool.download).toBe( 'swift-5.9-DEVELOPMENT-SNAPSHOT-2023-05-11-a-windows10.exe' ) - expect(lTool.dir).toBe('swift-5.9-DEVELOPMENT-SNAPSHOT-2023-05-11-a') - expect(lTool.platform).toBe('windows10') - expect(lTool.branch).toBe('swift-5.9-branch') - expect(lTool.download_signature).toBe( + expect(wTool.dir).toBe('swift-5.9-DEVELOPMENT-SNAPSHOT-2023-05-11-a') + expect(wTool.platform).toBe('windows10') + expect(wTool.branch).toBe('swift-5.9-branch') + expect(wTool.download_signature).toBe( 'swift-5.9-DEVELOPMENT-SNAPSHOT-2023-05-11-a-windows10.exe.sig' ) + expect(wTool.preventCaching).toBe(false) }) it('fetches windows 10 latest swift 5.5 tools', async () => { @@ -156,4 +165,24 @@ describe('fetch windows tool data based on options', () => { const tools = await Platform.toolchains(dev5_5) expect(tools.length).toBe(34) }) + + it('fetches windows 10 custom swift tools', async () => { + setos({os: 'win32', dist: 'Windows', release: '10.0.17063'}) + jest.spyOn(os, 'arch').mockReturnValue('x64') + const swiftwasm = 'https://github.com/swiftwasm/swift/releases/download' + const name = 'swift-wasm-5.10-SNAPSHOT-2024-03-30-a' + const resource = `${name}-windows10.exe` + const toolchainUrl = `${swiftwasm}/${name}/${resource}` + const cVer = ToolchainVersion.create(toolchainUrl, false) + const tools = await Platform.toolchains(cVer) + expect(tools.length).toBe(1) + const tool = tools[0] + expect(tool.baseUrl?.href).toBe(posix.dirname(toolchainUrl)) + expect(tool.preventCaching).toBe(true) + expect(tool.name).toBe('Swift Custom Snapshot') + expect(tool.platform).toBe('windows10') + expect(tool.download).toBe(resource) + expect(tool.dir).toBe(name) + expect(tool.branch).toBe('swiftwasm') + }) }) diff --git a/__tests__/snapshot/xcode.test.ts b/__tests__/snapshot/xcode.test.ts index 115ec44..2581239 100644 --- a/__tests__/snapshot/xcode.test.ts +++ b/__tests__/snapshot/xcode.test.ts @@ -1,4 +1,5 @@ import os from 'os' +import {posix} from 'path' import {glob} from 'glob' // @ts-ignore import {__setos as setos} from 'getos' @@ -28,6 +29,7 @@ describe('fetch macos tool data based on options', () => { expect(xTool.platform).toBe('xcode') expect(xTool.branch).toBe('swift-4.2.4-release') expect(xTool.xcode).toBe('10.1') + expect(xTool.preventCaching).toBe(false) }) it('fetches macOS latest swift 5.0 tool', async () => { @@ -41,6 +43,7 @@ describe('fetch macos tool data based on options', () => { expect(xTool.platform).toBe('xcode') expect(xTool.branch).toBe('swift-5.0.3-release') expect(xTool.xcode).toBe('10.2.1') + expect(xTool.preventCaching).toBe(false) }) it('fetches macOS latest swift 5.5.0 tool', async () => { @@ -54,6 +57,7 @@ describe('fetch macos tool data based on options', () => { expect(xTool.platform).toBe('xcode') expect(xTool.branch).toBe('swift-5.5-release') expect(xTool.xcode).toBe('13') + expect(xTool.preventCaching).toBe(false) }) it('fetches macOS latest swift 5.5 tool', async () => { @@ -67,6 +71,7 @@ describe('fetch macos tool data based on options', () => { expect(xTool.platform).toBe('xcode') expect(xTool.branch).toBe('swift-5.5.3-release') expect(xTool.xcode).toBe('13.2') + expect(xTool.preventCaching).toBe(false) }) it('fetches macOS latest swift 5.5 tool including dev snapshot', async () => { @@ -80,6 +85,7 @@ describe('fetch macos tool data based on options', () => { expect(xTool.platform).toBe('xcode') expect(xTool.branch).toBe('swift-5.5.3-release') expect(xTool.xcode).toBe('13.2') + expect(xTool.preventCaching).toBe(false) }) it('fetches macOS latest swift tool', async () => { @@ -93,6 +99,7 @@ describe('fetch macos tool data based on options', () => { expect(xTool.platform).toBeTruthy() expect(xTool.branch).toBeTruthy() expect(xTool.xcode).toBeTruthy() + expect(xTool.preventCaching).toBe(false) }) it('fetches macOS latest swift tool including dev snapshot', async () => { @@ -105,6 +112,7 @@ describe('fetch macos tool data based on options', () => { expect(xTool.dir).toBeTruthy() expect(xTool.platform).toBeTruthy() expect(xTool.branch).toBeTruthy() + expect(xTool.preventCaching).toBe(false) }) it('fetches macOS latest swift beta version tool', async () => { @@ -135,6 +143,8 @@ describe('fetch macos tool data based on options', () => { ToolchainVersion.create(betaVersion, false) ) expect(tool).toBeTruthy() + const xTool = tool as XcodeToolchainSnapshot + expect(xTool.preventCaching).toBe(false) } }) @@ -145,13 +155,14 @@ describe('fetch macos tool data based on options', () => { const version = ToolchainVersion.create(name, false) const tool = await Platform.toolchain(version) expect(tool).toBeTruthy() - const lTool = tool as XcodeToolchainSnapshot - expect(lTool.download).toBe( + const xTool = tool as XcodeToolchainSnapshot + expect(xTool.download).toBe( 'swift-DEVELOPMENT-SNAPSHOT-2023-09-02-a-osx.pkg' ) - expect(lTool.dir).toBe('swift-DEVELOPMENT-SNAPSHOT-2023-09-02-a') - expect(lTool.platform).toBe('xcode') - expect(lTool.branch).toBe('development') + expect(xTool.dir).toBe('swift-DEVELOPMENT-SNAPSHOT-2023-09-02-a') + expect(xTool.platform).toBe('xcode') + expect(xTool.branch).toBe('development') + expect(xTool.preventCaching).toBe(false) }) it('fetches macOS named versioned swift tool', async () => { @@ -161,13 +172,14 @@ describe('fetch macos tool data based on options', () => { const version = ToolchainVersion.create(name, false) const tool = await Platform.toolchain(version) expect(tool).toBeTruthy() - const lTool = tool as XcodeToolchainSnapshot - expect(lTool.download).toBe( + const xTool = tool as XcodeToolchainSnapshot + expect(xTool.download).toBe( 'swift-5.9-DEVELOPMENT-SNAPSHOT-2023-09-01-a-osx.pkg' ) - expect(lTool.dir).toBe('swift-5.9-DEVELOPMENT-SNAPSHOT-2023-09-01-a') - expect(lTool.platform).toBe('xcode') - expect(lTool.branch).toBe('swift-5.9-branch') + expect(xTool.dir).toBe('swift-5.9-DEVELOPMENT-SNAPSHOT-2023-09-01-a') + expect(xTool.platform).toBe('xcode') + expect(xTool.branch).toBe('swift-5.9-branch') + expect(xTool.preventCaching).toBe(false) }) it('detects earliest toolchains', async () => { @@ -181,6 +193,7 @@ describe('fetch macos tool data based on options', () => { expect(earliestTool.platform).toBe('xcode') expect(earliestTool.branch).toBe('swift-2.2-release') expect(earliestTool.download).toBe('swift-2.2-RELEASE-osx.pkg') + expect(earliestTool.preventCaching).toBe(false) }) it('fetches macOS latest swift 5.5 tools', async () => { @@ -196,4 +209,24 @@ describe('fetch macos tool data based on options', () => { const tools = await Platform.toolchains(dev5_5) expect(tools.length).toBe(103) }) + + it('fetches macOS custom swift tools', async () => { + setos({os: 'darwin', dist: 'macOS', release: '21'}) + jest.spyOn(os, 'arch').mockReturnValue('x64') + const swiftwasm = 'https://github.com/swiftwasm/swift/releases/download' + const name = 'swift-wasm-5.10-SNAPSHOT-2024-03-30-a' + const resource = `${name}-macos_arm64.pkg` + const toolchainUrl = `${swiftwasm}/${name}/${resource}` + const cVer = ToolchainVersion.create(toolchainUrl, false) + const tools = await Platform.toolchains(cVer) + expect(tools.length).toBe(1) + const tool = tools[0] + expect(tool.baseUrl?.href).toBe(posix.dirname(toolchainUrl)) + expect(tool.preventCaching).toBe(true) + expect(tool.name).toBe('Swift Custom Snapshot') + expect(tool.platform).toBe('xcode') + expect(tool.download).toBe(resource) + expect(tool.dir).toBe(name) + expect(tool.branch).toBe('swiftwasm') + }) }) diff --git a/__tests__/swiftorg.test.ts b/__tests__/swiftorg.test.ts index 86d0968..361e205 100644 --- a/__tests__/swiftorg.test.ts +++ b/__tests__/swiftorg.test.ts @@ -95,6 +95,23 @@ describe('swiftorg sync validation', () => { ]) }) + it('tests without latest sync success with matadata fetch', async () => { + execSpy.mockResolvedValue(0) + process.env.SETUPSWIFT_SWIFTORG_METADATA = undefined + const commit = '74caef941bc8ed6a01b9572ab6149e1d1f8a2d69' + const swiftorg = new Swiftorg(false) + nock(SWIFTORG_METADATA) + .get(/.*/) + .reply(200, {commit}, {'content-type': 'application/json'}) + await swiftorg.update() + expect(execSpy).toHaveBeenCalledTimes(3) + expect(execSpy.mock.calls[1]).toStrictEqual([ + 'git', + ['fetch', SWIFTORG_ORIGIN, commit, '--depth=1', '--no-tags'], + {cwd: path.join(MODULE_DIR, SWIFTORG)} + ]) + }) + it('tests without latest sync failure with matadata fetch failure', async () => { execSpy.mockResolvedValue(0) process.env.SETUPSWIFT_SWIFTORG_METADATA = undefined @@ -109,7 +126,7 @@ describe('swiftorg sync validation', () => { expect(execSpy).toHaveBeenCalledTimes(0) }) - it('tests without latest sync failure with invalid matadata', async () => { + it('tests without latest sync failure with invalid matadata content type', async () => { execSpy.mockResolvedValue(0) process.env.SETUPSWIFT_SWIFTORG_METADATA = undefined const contentType = 'application/txt' @@ -122,4 +139,15 @@ describe('swiftorg sync validation', () => { ) expect(execSpy).toHaveBeenCalledTimes(0) }) + + it('tests without latest sync failure with invalid matadata content', async () => { + execSpy.mockResolvedValue(0) + process.env.SETUPSWIFT_SWIFTORG_METADATA = undefined + const swiftorg = new Swiftorg(false) + nock(SWIFTORG_METADATA) + .get(/.*/) + .reply(200, 'invalid', {'content-type': 'application/json'}) + await expect(swiftorg.update()).rejects.toBeInstanceOf(SyntaxError) + expect(execSpy).toHaveBeenCalledTimes(0) + }) }) diff --git a/__tests__/utils/gpg.test.ts b/__tests__/utils/gpg.test.ts index ae53dea..b4c3583 100644 --- a/__tests__/utils/gpg.test.ts +++ b/__tests__/utils/gpg.test.ts @@ -14,7 +14,7 @@ describe('gpg setup validation', () => { const execSpy = jest.spyOn(exec, 'exec') execSpy.mockRejectedValueOnce(new Error()) execSpy.mockResolvedValue(0) - await expect(gpg.setupKeys()).resolves + await gpg.setupKeys() }) it('tests keys refresh retry', async () => { @@ -22,7 +22,7 @@ describe('gpg setup validation', () => { execSpy.mockResolvedValueOnce(0) execSpy.mockRejectedValueOnce(new Error()) execSpy.mockResolvedValue(0) - await expect(gpg.setupKeys()).resolves + await gpg.setupKeys() }) it('tests keys refresh error', async () => { diff --git a/__tests__/version.test.ts b/__tests__/version.test.ts index 2c5c5e2..a27cfea 100644 --- a/__tests__/version.test.ts +++ b/__tests__/version.test.ts @@ -2,7 +2,8 @@ import { ToolchainVersion, SemanticToolchainVersion, LatestToolchainVersion, - ToolchainSnapshotName + ToolchainSnapshotName, + ToolchainSnapshotLocation } from '../src/version' describe('parse version from provided string', () => { @@ -10,11 +11,13 @@ describe('parse version from provided string', () => { const version = ToolchainVersion.create('latest', false) expect(version).toBeInstanceOf(LatestToolchainVersion) expect(version.dev).toBe(false) + expect(version.requiresSwiftOrg).toBe(true) expect(`${version}`).toBe('latest version') const devVersion = ToolchainVersion.create('latest', true) expect(devVersion).toBeInstanceOf(LatestToolchainVersion) expect(devVersion.dev).toBe(true) + expect(version.requiresSwiftOrg).toBe(true) expect(`${devVersion}`).toBe('latest dev version') }) @@ -22,11 +25,13 @@ describe('parse version from provided string', () => { const version = ToolchainVersion.create('current', false) expect(version).toBeInstanceOf(LatestToolchainVersion) expect(version.dev).toBe(false) + expect(version.requiresSwiftOrg).toBe(true) expect(`${version}`).toBe('latest version') const devVersion = ToolchainVersion.create('current', true) expect(devVersion).toBeInstanceOf(LatestToolchainVersion) expect(devVersion.dev).toBe(true) + expect(version.requiresSwiftOrg).toBe(true) expect(`${devVersion}`).toBe('latest dev version') }) @@ -34,6 +39,7 @@ describe('parse version from provided string', () => { const version = ToolchainVersion.create('5', false) expect(version).toBeInstanceOf(SemanticToolchainVersion) expect(version.dev).toBe(false) + expect(version.requiresSwiftOrg).toBe(true) expect(`${version}`).toBe('version: 5.0.0, dev: false') const sVersion = version as SemanticToolchainVersion expect(sVersion['dirGlob']).toBe('swift-5*') @@ -44,6 +50,7 @@ describe('parse version from provided string', () => { const version = ToolchainVersion.create('5.0', false) expect(version).toBeInstanceOf(SemanticToolchainVersion) expect(version.dev).toBe(false) + expect(version.requiresSwiftOrg).toBe(true) const sVersion = version as SemanticToolchainVersion expect(sVersion['dirGlob']).toBe('swift-5_0*') expect(sVersion['dirRegex']).toStrictEqual(/swift-5.0/) @@ -53,6 +60,7 @@ describe('parse version from provided string', () => { const version = ToolchainVersion.create('5.0.0', false) expect(version).toBeInstanceOf(SemanticToolchainVersion) expect(version.dev).toBe(false) + expect(version.requiresSwiftOrg).toBe(true) const sVersion = version as SemanticToolchainVersion expect(sVersion['dirGlob']).toBe('swift-5_0-*') expect(sVersion['dirRegex']).toStrictEqual(/swift-5.0-/) @@ -62,6 +70,7 @@ describe('parse version from provided string', () => { const version = ToolchainVersion.create('5.5', false) expect(version).toBeInstanceOf(SemanticToolchainVersion) expect(version.dev).toBe(false) + expect(version.requiresSwiftOrg).toBe(true) const sVersion = version as SemanticToolchainVersion expect(sVersion['dirGlob']).toBe('swift-5_5*') expect(sVersion['dirRegex']).toStrictEqual(/swift-5.5/) @@ -71,6 +80,7 @@ describe('parse version from provided string', () => { const version = ToolchainVersion.create('5.5.0', false) expect(version).toBeInstanceOf(SemanticToolchainVersion) expect(version.dev).toBe(false) + expect(version.requiresSwiftOrg).toBe(true) const sVersion = version as SemanticToolchainVersion expect(sVersion['dirGlob']).toBe('swift-5_5-*') expect(sVersion['dirRegex']).toStrictEqual(/swift-5.5-/) @@ -80,6 +90,7 @@ describe('parse version from provided string', () => { const version = ToolchainVersion.create('5.5.1', false) expect(version).toBeInstanceOf(SemanticToolchainVersion) expect(version.dev).toBe(false) + expect(version.requiresSwiftOrg).toBe(true) const sVersion = version as SemanticToolchainVersion expect(sVersion['dirGlob']).toBe('swift-5_5_1*') expect(sVersion['dirRegex']).toStrictEqual(/swift-5.5.1/) @@ -90,6 +101,7 @@ describe('parse version from provided string', () => { const version = ToolchainVersion.create(name, false) expect(version).toBeInstanceOf(ToolchainSnapshotName) expect(version.dev).toBe(true) + expect(version.requiresSwiftOrg).toBe(true) const lVersion = version as ToolchainSnapshotName expect(lVersion['dirGlob']).toBe('*') expect(lVersion['dirRegex']).toStrictEqual(new RegExp(name)) @@ -101,11 +113,25 @@ describe('parse version from provided string', () => { const version = ToolchainVersion.create(input, false) expect(version).toBeInstanceOf(ToolchainSnapshotName) expect(version.dev).toBe(true) + expect(version.requiresSwiftOrg).toBe(true) const lVersion = version as ToolchainSnapshotName expect(lVersion['dirGlob']).toBe('swift-5_9-*') expect(lVersion['dirRegex']).toStrictEqual(new RegExp(name)) }) + it('parses toolchain URL', async () => { + const swiftwasm = 'https://github.com/swiftwasm/swift/releases/download' + const name = 'swift-wasm-5.10-SNAPSHOT-2024-03-30-a' + const toolchainUrl = `${swiftwasm}/${name}/${name}-ubuntu22.04_x86_64.tar.gz` + const version = ToolchainVersion.create(toolchainUrl, false) + expect(version).toBeInstanceOf(ToolchainSnapshotLocation) + const lVersion = version as ToolchainSnapshotLocation + expect(lVersion['dirGlob']).toBe('') + expect(lVersion['dirRegex']).toStrictEqual(/a^/) + expect(lVersion.requiresSwiftOrg).toBe(false) + expect(lVersion.url.href).toBe(toolchainUrl) + }) + it('parses invalid input', async () => { const creation = () => ToolchainVersion.create('invalid', false) expect(creation).toThrow() diff --git a/action.yml b/action.yml index 370997e..9f8009f 100644 --- a/action.yml +++ b/action.yml @@ -9,6 +9,9 @@ inputs: i.e. configures 5.1.1 for 5.1, configures 4.2.4 for 4. Provide exact semantic version to configure the same version, i.e. configures 5.1 for 5.1.0, configures 5.0 for 5.0.0. + Provide toolchain download URL to configure custom toolchains, + i.e. https://github.com/swiftwasm/swift/releases/download/swift-wasm-5.10-SNAPSHOT-2024-03-30-a/swift-wasm-5.10-SNAPSHOT-2024-03-30-a-ubuntu22.04_x86_64.tar.gz, + https://github.com/swiftwasm/swift/releases/download/swift-wasm-5.10-SNAPSHOT-2024-03-30-a/swift-wasm-5.10-SNAPSHOT-2024-03-30-a-macos_x86_64.pkg required: false default: 'latest' development: diff --git a/dist/index.js b/dist/index.js index 1f9c5fe..75513c3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -81,6 +81,7 @@ exports.ToolchainInstaller = void 0; const os = __importStar(__nccwpck_require__(2037)); const path = __importStar(__nccwpck_require__(1017)); const fs_1 = __nccwpck_require__(7147); +const url_1 = __nccwpck_require__(7310); const core = __importStar(__nccwpck_require__(2186)); const exec_1 = __nccwpck_require__(1514); const cache = __importStar(__nccwpck_require__(7799)); @@ -96,7 +97,12 @@ class ToolchainInstaller { return match && match.length > 1 ? (0, semver_1.coerce)(match[1]) : undefined; } get baseUrl() { - return `https://download.swift.org/${this.data.branch}/${this.data.platform}/${this.data.dir}`; + const data = this.data; + if (data.baseUrl) { + return data.baseUrl; + } + const base = 'https://download.swift.org'; + return new url_1.URL(path.posix.join(base, data.branch, data.platform, data.dir)); } swiftVersionCommand() { return { @@ -138,7 +144,9 @@ class ToolchainInstaller { tool = yield toolCache.cacheDir(tool, key, version, arch); core.debug(`Added to tool cache at "${tool}"`); } - if (core.getBooleanInput('cache-snapshot') && !cacheHit) { + if (core.getBooleanInput('cache-snapshot') && + !cacheHit && + !this.data.preventCaching) { yield fs_1.promises.cp(tool, restore, { recursive: true }); yield cache.saveCache([restore], key); core.debug(`Saved to cache with key "${key}"`); @@ -148,7 +156,8 @@ class ToolchainInstaller { } download() { return __awaiter(this, void 0, void 0, function* () { - const url = `${this.baseUrl}/${this.data.download}`; + var _a; + const url = path.posix.join((_a = this.baseUrl) === null || _a === void 0 ? void 0 : _a.href, this.data.download); core.debug(`Downloading snapshot from "${url}"`); return yield toolCache.downloadTool(url); }); @@ -400,6 +409,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.VerifyingToolchainInstaller = void 0; +const path_1 = __nccwpck_require__(1017); const core = __importStar(__nccwpck_require__(2186)); const toolCache = __importStar(__nccwpck_require__(7784)); const gpg = __importStar(__nccwpck_require__(9787)); @@ -407,18 +417,19 @@ const base_1 = __nccwpck_require__(5686); class VerifyingToolchainInstaller extends base_1.ToolchainInstaller { get signatureUrl() { const signature = this.data.download_signature; - return signature ? `${this.baseUrl}/${signature}` : undefined; + return signature ? path_1.posix.join(this.baseUrl.href, signature) : undefined; } downloadSignature() { return __awaiter(this, void 0, void 0, function* () { try { - if (this.signatureUrl) { - return yield toolCache.downloadTool(this.signatureUrl); - } + return this.signatureUrl + ? yield toolCache.downloadTool(this.signatureUrl) + : undefined; } catch (error) { if (error instanceof toolCache.HTTPError && error.httpStatusCode === 404) { + core.warning(`No signature at "${this.signatureUrl}"`); return undefined; } throw error; @@ -541,16 +552,24 @@ class WindowsToolchainInstaller extends verify_1.VerifyingToolchainInstaller { return __awaiter(this, void 0, void 0, function* () { function env() { return __awaiter(this, void 0, void 0, function* () { - const { stdout } = yield (0, exec_1.getExecOutput)('cmd', ['/c', 'set'], { - failOnStdErr: true - }); - return stdout; + for (const target of ['Machine', 'User']) { + const { stdout } = yield (0, exec_1.getExecOutput)('powershell', [ + '-NoProfile', + '-Command', + `& {[Environment]::GetEnvironmentVariables('${target}') | ConvertTo-Json}` + ], { failOnStdErr: true }); + core.debug(`${target} variables: "${stdout}"`); + } }); } core.debug(`Installing toolchain from "${exe}"`); - core.debug(`Environment variables before installation: ${yield env()}`); + core.startGroup('Environment variables before installation'); + yield env(); + core.endGroup(); yield (0, exec_1.exec)(`"${exe}"`, ['-q']); - core.debug(`Environment variables after installation: ${yield env()}`); + core.startGroup('Environment variables after installation'); + yield env(); + core.endGroup(); const installation = yield installation_1.Installation.detect(); return installation.location; }); @@ -903,11 +922,13 @@ function run() { const requestedVersion = (_a = core.getInput('swift-version')) !== null && _a !== void 0 ? _a : 'latest'; const development = core.getBooleanInput('development'); const version = version_1.ToolchainVersion.create(requestedVersion, development); - core.startGroup('Syncing swift.org data'); - const checkLatest = core.getInput('check-latest'); - const submodule = new swiftorg_1.Swiftorg(checkLatest); - yield submodule.update(); - core.endGroup(); + if (version.requiresSwiftOrg) { + core.startGroup('Syncing swift.org data'); + const checkLatest = core.getInput('check-latest'); + const submodule = new swiftorg_1.Swiftorg(checkLatest); + yield submodule.update(); + core.endGroup(); + } const dryRun = core.getBooleanInput('dry-run'); let snapshot; let installedVersion; @@ -1035,6 +1056,10 @@ class Platform { } tools(version) { return __awaiter(this, void 0, void 0, function* () { + const verSnapshot = version.toolchainSnapshot(this.file); + if (verSnapshot) { + return [this.snapshotFor(verSnapshot)]; + } const snapshots = yield this.releasedTools(version); if (snapshots.length && !version.dev) { return this.sortSnapshots(snapshots); @@ -1055,7 +1080,7 @@ class Platform { const devSnapshots = snapshotsCollection .flatMap(collection => { return collection.data.map(data => { - return Object.assign(Object.assign({}, data), { platform: collection.platform, branch: collection.branch }); + return Object.assign(Object.assign({}, data), { platform: collection.platform, branch: collection.branch, preventCaching: false }); }); }) .filter(item => version.satisfiedBy(item.dir)); @@ -1251,6 +1276,9 @@ class LinuxPlatform extends versioned_1.VersionedPlatform { return content; }); } + snapshotFor(snapshot) { + return Object.assign(Object.assign({}, snapshot), { download_signature: `${snapshot.download}.sig` }); + } tools(version) { const _super = Object.create(null, { tools: { get: () => super.tools } @@ -1457,7 +1485,8 @@ class VersionedPlatform extends base_1.Platform { platform: pName + this.archSuffix, branch: release.tag.toLocaleLowerCase(), docker: platform.docker, - windows: pName.startsWith('windows') + windows: pName.startsWith('windows'), + preventCaching: false } : []; }); @@ -1507,6 +1536,9 @@ class WindowsPlatform extends versioned_1.VersionedPlatform { get downloadExtension() { return 'exe'; } + snapshotFor(snapshot) { + return Object.assign(Object.assign({}, snapshot), { download_signature: `${snapshot.download}.sig`, windows: true }); + } install(data) { return __awaiter(this, void 0, void 0, function* () { const installer = new installer_1.WindowsToolchainInstaller(data); @@ -1525,6 +1557,29 @@ exports.WindowsPlatform = WindowsPlatform; "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { @@ -1536,6 +1591,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.XcodePlatform = void 0; +const path = __importStar(__nccwpck_require__(1017)); const base_1 = __nccwpck_require__(1568); const installer_1 = __nccwpck_require__(8979); class XcodePlatform extends base_1.Platform { @@ -1552,6 +1608,11 @@ class XcodePlatform extends base_1.Platform { get fileGlob() { return this.name; } + snapshotFor(snapshot) { + const fileExt = path.extname(snapshot.download); + const fileName = path.basename(snapshot.download, fileExt); + return Object.assign(Object.assign({}, snapshot), { debug_info: `${fileName}-symbols.${fileExt}` }); + } releasedTools(version) { return __awaiter(this, void 0, void 0, function* () { const releases = yield this.releases(); @@ -1563,11 +1624,12 @@ class XcodePlatform extends base_1.Platform { name: `Xcode Swift ${release.name}`, date: release.date, download: `${release.tag}-osx.pkg`, - symbols: `${release.tag}-osx-symbols.pkg`, + debug_info: `${release.tag}-osx-symbols.pkg`, dir: release.tag, xcode: xMatch && xMatch.length > 1 ? xMatch[1] : undefined, platform: this.name, - branch: release.tag.toLocaleLowerCase() + branch: release.tag.toLocaleLowerCase(), + preventCaching: false }; }); }); @@ -1916,7 +1978,7 @@ class VisualStudio { this.catalog = catalog; this.properties = properties; } - // eslint-disable-next-line no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any static createFromJSON(json) { return new VisualStudio(json.installationPath, json.installationVersion, json.catalog, json.properties); } @@ -2295,12 +2357,20 @@ const glob_1 = __nccwpck_require__(8211); const const_1 = __nccwpck_require__(6695); exports.SWIFT_RELEASE_REGEX = /swift-(.*)-release/; class ToolchainVersion { + get requiresSwiftOrg() { + return true; + } constructor(dev) { this.dev = dev; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + toolchainSnapshot(platform) { + return undefined; + } toolFiles(fileGlob) { return __awaiter(this, void 0, void 0, function* () { - const pattern = `swiftorg/_data/builds/${this.dirGlob}/${fileGlob}.yml`; + const builds = 'swiftorg/_data/builds'; + const pattern = path.posix.join(builds, this.dirGlob, `${fileGlob}.yml`); core.debug(`Searching for glob "${pattern}"`); let files = yield (0, glob_1.glob)(pattern, { absolute: true, cwd: const_1.MODULE_DIR }); core.debug(`Retrieved files "${files}" for glob "${pattern}"`); @@ -2356,13 +2426,22 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", ({ value: true })); +const url_1 = __nccwpck_require__(7310); const core = __importStar(__nccwpck_require__(2186)); const semver_1 = __nccwpck_require__(1383); const base_1 = __nccwpck_require__(2120); const latest_1 = __nccwpck_require__(8363); const semver_2 = __nccwpck_require__(916); const name_1 = __nccwpck_require__(3987); +const location_1 = __nccwpck_require__(9785); base_1.ToolchainVersion.create = (requested, dev = false) => { + try { + const toolchainUrl = new url_1.URL(requested); + return new location_1.ToolchainSnapshotLocation(toolchainUrl, dev); + } + catch (_a) { + core.debug(`Input "${requested}" not an URL`); + } if (requested === 'latest' || requested === 'current') { core.debug(`Using latest ${dev ? 'development ' : ''}toolchain requirement`); return new latest_1.LatestToolchainVersion(dev); @@ -2391,6 +2470,7 @@ __exportStar(__nccwpck_require__(2120), exports); __exportStar(__nccwpck_require__(8363), exports); __exportStar(__nccwpck_require__(916), exports); __exportStar(__nccwpck_require__(3987), exports); +__exportStar(__nccwpck_require__(9785), exports); /***/ }), @@ -2417,6 +2497,57 @@ class LatestToolchainVersion extends base_1.ToolchainVersion { exports.LatestToolchainVersion = LatestToolchainVersion; +/***/ }), + +/***/ 9785: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ToolchainSnapshotLocation = void 0; +const url_1 = __nccwpck_require__(7310); +const path_1 = __nccwpck_require__(1017); +const base_1 = __nccwpck_require__(2120); +class ToolchainSnapshotLocation extends base_1.ToolchainVersion { + constructor(url, dev) { + super(dev); + this.url = url; + } + get dirGlob() { + return ''; + } + get dirRegex() { + return /a^/; + } + get requiresSwiftOrg() { + return false; + } + toolchainSnapshot(platform) { + try { + const baseUrl = path_1.posix.dirname(this.url.href); + return { + name: 'Swift Custom Snapshot', + date: new Date(), + download: path_1.posix.basename(this.url.href), + dir: path_1.posix.basename(baseUrl), + platform, + branch: this.url.pathname.split(path_1.posix.sep)[1], + baseUrl: new url_1.URL(baseUrl), + preventCaching: true + }; + } + catch (e) { + throw new Error(`Swift resource: "${this.url}" failed with error ${e}`); + } + } + toString() { + return `url: "${this.url}", dev: ${this.dev}`; + } +} +exports.ToolchainSnapshotLocation = ToolchainSnapshotLocation; + + /***/ }), /***/ 3987: @@ -74588,35 +74719,43 @@ const coerce = (version, options) => { let match = null if (!options.rtl) { - match = version.match(re[t.COERCE]) + match = version.match(options.includePrerelease ? re[t.COERCEFULL] : re[t.COERCE]) } else { // Find the right-most coercible string that does not share // a terminus with a more left-ward coercible string. // Eg, '1.2.3.4' wants to coerce '2.3.4', not '3.4' or '4' + // With includePrerelease option set, '1.2.3.4-rc' wants to coerce '2.3.4-rc', not '2.3.4' // // Walk through the string checking with a /g regexp // Manually set the index so as to pick up overlapping matches. // Stop when we get a match that ends at the string end, since no // coercible string can be more right-ward without the same terminus. + const coerceRtlRegex = options.includePrerelease ? re[t.COERCERTLFULL] : re[t.COERCERTL] let next - while ((next = re[t.COERCERTL].exec(version)) && + while ((next = coerceRtlRegex.exec(version)) && (!match || match.index + match[0].length !== version.length) ) { if (!match || next.index + next[0].length !== match.index + match[0].length) { match = next } - re[t.COERCERTL].lastIndex = next.index + next[1].length + next[2].length + coerceRtlRegex.lastIndex = next.index + next[1].length + next[2].length } // leave it in a clean state - re[t.COERCERTL].lastIndex = -1 + coerceRtlRegex.lastIndex = -1 } if (match === null) { return null } - return parse(`${match[2]}.${match[3] || '0'}.${match[4] || '0'}`, options) + const major = match[2] + const minor = match[3] || '0' + const patch = match[4] || '0' + const prerelease = options.includePrerelease && match[5] ? `-${match[5]}` : '' + const build = options.includePrerelease && match[6] ? `+${match[6]}` : '' + + return parse(`${major}.${minor}.${patch}${prerelease}${build}`, options) } module.exports = coerce @@ -75308,12 +75447,17 @@ createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`) // Coercion. // Extract anything that could conceivably be a part of a valid semver -createToken('COERCE', `${'(^|[^\\d])' + +createToken('COERCEPLAIN', `${'(^|[^\\d])' + '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + - `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?`) +createToken('COERCE', `${src[t.COERCEPLAIN]}(?:$|[^\\d])`) +createToken('COERCEFULL', src[t.COERCEPLAIN] + + `(?:${src[t.PRERELEASE]})?` + + `(?:${src[t.BUILD]})?` + `(?:$|[^\\d])`) createToken('COERCERTL', src[t.COERCE], true) +createToken('COERCERTLFULL', src[t.COERCEFULL], true) // Tilde ranges. // Meaning is "reasonably at or greater than" diff --git a/src/installer/base.ts b/src/installer/base.ts index fa18418..098c3cd 100644 --- a/src/installer/base.ts +++ b/src/installer/base.ts @@ -1,6 +1,7 @@ import * as os from 'os' import * as path from 'path' import {promises as fs} from 'fs' +import {URL} from 'url' import * as core from '@actions/core' import {getExecOutput} from '@actions/exec' import * as cache from '@actions/cache' @@ -23,7 +24,12 @@ export abstract class ToolchainInstaller { } protected get baseUrl() { - return `https://download.swift.org/${this.data.branch}/${this.data.platform}/${this.data.dir}` + const data = this.data + if (data.baseUrl) { + return data.baseUrl + } + const base = 'https://download.swift.org' + return new URL(path.posix.join(base, data.branch, data.platform, data.dir)) } protected swiftVersionCommand() { @@ -67,7 +73,11 @@ export abstract class ToolchainInstaller { tool = await toolCache.cacheDir(tool, key, version, arch) core.debug(`Added to tool cache at "${tool}"`) } - if (core.getBooleanInput('cache-snapshot') && !cacheHit) { + if ( + core.getBooleanInput('cache-snapshot') && + !cacheHit && + !this.data.preventCaching + ) { await fs.cp(tool, restore, {recursive: true}) await cache.saveCache([restore], key) core.debug(`Saved to cache with key "${key}"`) @@ -76,7 +86,7 @@ export abstract class ToolchainInstaller { } protected async download() { - const url = `${this.baseUrl}/${this.data.download}` + const url = path.posix.join(this.baseUrl?.href, this.data.download) core.debug(`Downloading snapshot from "${url}"`) return await toolCache.downloadTool(url) } diff --git a/src/installer/verify.ts b/src/installer/verify.ts index bec8b25..1153508 100644 --- a/src/installer/verify.ts +++ b/src/installer/verify.ts @@ -1,3 +1,4 @@ +import {posix} from 'path' import * as core from '@actions/core' import * as toolCache from '@actions/tool-cache' import * as gpg from '../utils/gpg' @@ -9,19 +10,20 @@ export abstract class VerifyingToolchainInstaller< > extends ToolchainInstaller { private get signatureUrl() { const signature = this.data.download_signature - return signature ? `${this.baseUrl}/${signature}` : undefined + return signature ? posix.join(this.baseUrl.href, signature) : undefined } private async downloadSignature() { try { - if (this.signatureUrl) { - return await toolCache.downloadTool(this.signatureUrl) - } + return this.signatureUrl + ? await toolCache.downloadTool(this.signatureUrl) + : undefined } catch (error) { if ( error instanceof toolCache.HTTPError && error.httpStatusCode === 404 ) { + core.warning(`No signature at "${this.signatureUrl}"`) return undefined } throw error diff --git a/src/installer/windows/index.ts b/src/installer/windows/index.ts index 9cf222f..226ef45 100644 --- a/src/installer/windows/index.ts +++ b/src/installer/windows/index.ts @@ -41,15 +41,27 @@ export class WindowsToolchainInstaller extends VerifyingToolchainInstaller + private sortSnapshots(snapshots: SnapshotForInstaller[]) { return snapshots.sort((item1, item2) => { const t1 = item1 as ToolchainSnapshot @@ -65,6 +69,11 @@ export abstract class Platform< async tools( version: ToolchainVersion ): Promise[]> { + const verSnapshot = version.toolchainSnapshot(this.file) + if (verSnapshot) { + return [this.snapshotFor(verSnapshot)] + } + const snapshots = await this.releasedTools(version) if (snapshots.length && !version.dev) { return this.sortSnapshots(snapshots) @@ -92,7 +101,8 @@ export abstract class Platform< return { ...data, platform: collection.platform, - branch: collection.branch + branch: collection.branch, + preventCaching: false } as SnapshotForInstaller }) }) diff --git a/src/platform/linux.ts b/src/platform/linux.ts index 0ea5c18..c67ae8f 100644 --- a/src/platform/linux.ts +++ b/src/platform/linux.ts @@ -2,7 +2,7 @@ import * as path from 'path' import {promises as fs} from 'fs' import {VersionedPlatform} from './versioned' import {ToolchainVersion, SWIFT_BRANCH_REGEX} from '../version' -import {LinuxToolchainSnapshot} from '../snapshot' +import {LinuxToolchainSnapshot, ToolchainSnapshot} from '../snapshot' import {LinuxToolchainInstaller} from '../installer' import {MODULE_DIR} from '../const' @@ -17,6 +17,10 @@ export class LinuxPlatform extends VersionedPlatform { return content } + snapshotFor(snapshot: ToolchainSnapshot) { + return {...snapshot, download_signature: `${snapshot.download}.sig`} + } + async tools(version: ToolchainVersion) { let html: string const tools = await super.tools(version) diff --git a/src/platform/versioned.ts b/src/platform/versioned.ts index ac8344e..212722c 100644 --- a/src/platform/versioned.ts +++ b/src/platform/versioned.ts @@ -119,7 +119,8 @@ export abstract class VersionedPlatform< platform: pName + this.archSuffix, branch: release.tag.toLocaleLowerCase(), docker: platform.docker, - windows: pName.startsWith('windows') + windows: pName.startsWith('windows'), + preventCaching: false } as ToolchainSnapshot) : [] }) diff --git a/src/platform/windows.ts b/src/platform/windows.ts index e7a847b..8417991 100644 --- a/src/platform/windows.ts +++ b/src/platform/windows.ts @@ -1,12 +1,20 @@ import {VersionedPlatform} from './versioned' import {WindowsToolchainInstaller} from '../installer' -import {WindowsToolchainSnapshot} from '../snapshot' +import {WindowsToolchainSnapshot, ToolchainSnapshot} from '../snapshot' export class WindowsPlatform extends VersionedPlatform { protected get downloadExtension() { return 'exe' } + snapshotFor(snapshot: ToolchainSnapshot) { + return { + ...snapshot, + download_signature: `${snapshot.download}.sig`, + windows: true + } + } + async install(data: WindowsToolchainSnapshot) { const installer = new WindowsToolchainInstaller(data) await installer.install(this.arch) diff --git a/src/platform/xcode.ts b/src/platform/xcode.ts index 7c79028..2b041f9 100644 --- a/src/platform/xcode.ts +++ b/src/platform/xcode.ts @@ -1,6 +1,7 @@ +import * as path from 'path' import {Platform} from './base' import {ToolchainVersion} from '../version' -import {XcodeToolchainSnapshot} from '../snapshot' +import {XcodeToolchainSnapshot, ToolchainSnapshot} from '../snapshot' import {XcodeToolchainInstaller} from '../installer' export class XcodePlatform extends Platform { @@ -18,6 +19,15 @@ export class XcodePlatform extends Platform { return this.name } + snapshotFor(snapshot: ToolchainSnapshot) { + const fileExt = path.extname(snapshot.download) + const fileName = path.basename(snapshot.download, fileExt) + return { + ...snapshot, + debug_info: `${fileName}-symbols.${fileExt}` + } + } + protected async releasedTools(version: ToolchainVersion) { const releases = await this.releases() return releases @@ -28,11 +38,12 @@ export class XcodePlatform extends Platform { name: `Xcode Swift ${release.name}`, date: release.date, download: `${release.tag}-osx.pkg`, - symbols: `${release.tag}-osx-symbols.pkg`, + debug_info: `${release.tag}-osx-symbols.pkg`, dir: release.tag, xcode: xMatch && xMatch.length > 1 ? xMatch[1] : undefined, platform: this.name, - branch: release.tag.toLocaleLowerCase() + branch: release.tag.toLocaleLowerCase(), + preventCaching: false } as XcodeToolchainSnapshot }) } diff --git a/src/snapshot/base.ts b/src/snapshot/base.ts index 07bb2db..6042f7b 100644 --- a/src/snapshot/base.ts +++ b/src/snapshot/base.ts @@ -1,3 +1,5 @@ +import {URL} from 'url' + export interface ToolchainSnapshot { readonly name: string readonly date: Date @@ -5,4 +7,6 @@ export interface ToolchainSnapshot { readonly dir: string readonly platform: string readonly branch: string + readonly baseUrl?: URL + readonly preventCaching: boolean } diff --git a/src/snapshot/xcode.ts b/src/snapshot/xcode.ts index 4d66d2b..30cc427 100644 --- a/src/snapshot/xcode.ts +++ b/src/snapshot/xcode.ts @@ -1,6 +1,6 @@ import {ToolchainSnapshot} from './base' export interface XcodeToolchainSnapshot extends ToolchainSnapshot { - readonly symbols?: string + readonly debug_info?: string readonly xcode?: string } diff --git a/src/utils/visual_studio/base.ts b/src/utils/visual_studio/base.ts index a2598ce..c456690 100644 --- a/src/utils/visual_studio/base.ts +++ b/src/utils/visual_studio/base.ts @@ -10,7 +10,7 @@ export class VisualStudio { readonly properties: VisualStudioProperties ) {} - // eslint-disable-next-line no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any static createFromJSON(json: any) { return new VisualStudio( json.installationPath, diff --git a/src/version/base.ts b/src/version/base.ts index 2f69e87..d45586c 100644 --- a/src/version/base.ts +++ b/src/version/base.ts @@ -2,6 +2,7 @@ import * as path from 'path' import * as core from '@actions/core' import {glob} from 'glob' import {MODULE_DIR} from '../const' +import {ToolchainSnapshot} from '../snapshot' export const SWIFT_RELEASE_REGEX = /swift-(.*)-release/ @@ -9,10 +10,20 @@ export abstract class ToolchainVersion { protected abstract readonly dirGlob: string protected abstract readonly dirRegex: RegExp + get requiresSwiftOrg() { + return true + } + constructor(readonly dev: boolean) {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars + toolchainSnapshot(platform: string): ToolchainSnapshot | undefined { + return undefined + } + async toolFiles(fileGlob: string) { - const pattern = `swiftorg/_data/builds/${this.dirGlob}/${fileGlob}.yml` + const builds = 'swiftorg/_data/builds' + const pattern = path.posix.join(builds, this.dirGlob, `${fileGlob}.yml`) core.debug(`Searching for glob "${pattern}"`) let files = await glob(pattern, {absolute: true, cwd: MODULE_DIR}) core.debug(`Retrieved files "${files}" for glob "${pattern}"`) diff --git a/src/version/index.ts b/src/version/index.ts index c01e8a9..a928e96 100644 --- a/src/version/index.ts +++ b/src/version/index.ts @@ -1,9 +1,11 @@ +import {URL} from 'url' import * as core from '@actions/core' import {SemVer, coerce as parseSemVer} from 'semver' import {ToolchainVersion} from './base' import {LatestToolchainVersion} from './latest' import {SemanticToolchainVersion} from './semver' import {ToolchainSnapshotName, DEVELOPMENT_SNAPSHOT} from './name' +import {ToolchainSnapshotLocation} from './location' declare module './base' { // eslint-disable-next-line no-shadow @@ -13,6 +15,13 @@ declare module './base' { } ToolchainVersion.create = (requested: string, dev = false) => { + try { + const toolchainUrl = new URL(requested) + return new ToolchainSnapshotLocation(toolchainUrl, dev) + } catch { + core.debug(`Input "${requested}" not an URL`) + } + if (requested === 'latest' || requested === 'current') { core.debug(`Using latest ${dev ? 'development ' : ''}toolchain requirement`) return new LatestToolchainVersion(dev) @@ -47,3 +56,4 @@ export * from './base' export * from './latest' export * from './semver' export * from './name' +export * from './location' diff --git a/src/version/location.ts b/src/version/location.ts new file mode 100644 index 0000000..c02e8b4 --- /dev/null +++ b/src/version/location.ts @@ -0,0 +1,46 @@ +import {URL} from 'url' +import {posix} from 'path' +import {ToolchainVersion} from './base' + +export class ToolchainSnapshotLocation extends ToolchainVersion { + constructor( + readonly url: URL, + dev: boolean + ) { + super(dev) + } + + protected get dirGlob() { + return '' + } + + protected get dirRegex() { + return /a^/ + } + + get requiresSwiftOrg() { + return false + } + + toolchainSnapshot(platform: string) { + try { + const baseUrl = posix.dirname(this.url.href) + return { + name: 'Swift Custom Snapshot', + date: new Date(), + download: posix.basename(this.url.href), + dir: posix.basename(baseUrl), + platform, + branch: this.url.pathname.split(posix.sep)[1], + baseUrl: new URL(baseUrl), + preventCaching: true + } + } catch (e) { + throw new Error(`Swift resource: "${this.url}" failed with error ${e}`) + } + } + + toString() { + return `url: "${this.url}", dev: ${this.dev}` + } +} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..e7634c5 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "nodenext", + "moduleResolution": "nodenext", + "strict": true, + "noImplicitAny": true, + "esModuleInterop": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noImplicitReturns": true, + "lib": [ + "ES2021.String", + ] + }, + "exclude": [ + "node_modules" + ] +} diff --git a/tsconfig.json b/tsconfig.json index 3baa27a..8516854 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,19 +1,10 @@ { + "extends": "./tsconfig.base.json", "compilerOptions": { - "target": "es6", - "module": "nodenext", - "moduleResolution": "nodenext", "outDir": "lib", - "rootDir": "src", - "strict": true, - "noImplicitAny": true, - "esModuleInterop": true, - "lib": [ - "ES2021.String", - ] + "rootDir": "src" }, "exclude": [ - "node_modules", "**/*.test.ts", "**/__mocks__/*", "lib",