diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3bda10f4..138078a6 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,9 +2,6 @@ "image": "mcr.microsoft.com/devcontainers/universal:2", "customizations": { "vscode": { - "settings": { - "jest.runMode": "on-demand" - }, "extensions": [ "eamodio.gitlens", "ms-vscode.vscode-typescript-next", diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 29cce524..d59996a2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -327,6 +327,7 @@ jobs: uses: ./ with: check-latest: ${{ needs.ci.outputs.check_latest }} + sdks: static-sdk dry-run: true - name: Verify Swift version @@ -381,7 +382,8 @@ jobs: with: { 'swift-version': 'latest', 'check-latest': '${{ needs.ci.outputs.check_latest }}', - 'visual-studio-components': '${{ matrix.os }}'.includes('arm') ? 'Microsoft.VisualStudio.Component.Windows11SDK.22000' : '' + 'visual-studio-components': '${{ matrix.os }}'.includes('arm') ? 'Microsoft.VisualStudio.Component.Windows11SDK.22000' : '', + 'sdks': '${{ runner.os }}' != 'Windows' ? 'static-sdk' : '' } } ] @@ -414,6 +416,10 @@ jobs: if: runner.os == 'Windows' run: which link | grep "Microsoft Visual Studio" || exit 1 + - name: Verify Swift SDKs + if: runner.os != 'Windows' + run: swift sdk list | grep ${{ steps.setup-swift.outputs.swift-version }}-RELEASE_static-linux || exit 1 + - name: Test Swift package run: | swift package init --type library --name SetupLib diff --git a/.vscode/settings.json b/.vscode/settings.json index bf087cc3..30340641 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "jest.jestCommandLine": "npm run test -- ", + "jest.runMode": "on-demand", "files.exclude": { "**/node_modules": true }, diff --git a/README.md b/README.md index c7a7a3c9..5d209104 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This action supports the following functionalities: - Works on Linux, macOS and Windows across all architectures. - Supports [installing latest major/minor/patch](#specifying-version). +- Supports installing official Swift SDKs from cross platform developments. - Provides snapshots as soon as published in `swift.org`. - Verifies toolchain snapshots before installation (`gpg` for Linux and Windows, `pkgutil` for macOS) . - Allows development snapshots by enabling `development` flag and optional version. @@ -43,6 +44,14 @@ Or use the latest development snapshots by enabling the `development` flag: development: true ``` +Install additional SDKs as part of toolchain setup, i.e. install static Linux SDK: + +```yml +- uses: SwiftyLab/setup-swift@latest + with: + sdks: static-sdk +``` + After the environment is configured you can run swift and xcode commands using the standard [`run`](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsrun) step: ```yml diff --git a/__tests__/installer/linux.test.ts b/__tests__/installer/linux.test.ts index dbcfe3d1..0bf6d341 100644 --- a/__tests__/installer/linux.test.ts +++ b/__tests__/installer/linux.test.ts @@ -198,7 +198,7 @@ describe('linux toolchain installation verification', () => { jest.spyOn(toolCache, 'cacheDir').mockResolvedValue(cached) jest.spyOn(exec, 'exec').mockResolvedValue(0) const cacheSpy = jest.spyOn(cache, 'saveCache') - const installer = await Platform.install(cVer) + 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) diff --git a/__tests__/installer/windows.test.ts b/__tests__/installer/windows.test.ts index c8bfe05f..87060425 100644 --- a/__tests__/installer/windows.test.ts +++ b/__tests__/installer/windows.test.ts @@ -330,8 +330,115 @@ describe('windows toolchain installation verification', () => { const installer = new WindowsToolchainInstaller(toolchain) const installation = path.resolve('tool', 'installed', 'path') jest.spyOn(VisualStudio, 'setup').mockResolvedValue(visualStudio) - jest.spyOn(fs, 'access').mockRejectedValueOnce(new Error()) - jest.spyOn(fs, 'access').mockResolvedValue() + jest + .spyOn(fs, 'access') + .mockRejectedValueOnce(new Error()) + .mockResolvedValue() + jest.spyOn(fs, 'copyFile').mockResolvedValue() + jest.spyOn(exec, 'exec').mockResolvedValue(0) + jest.spyOn(exec, 'getExecOutput').mockResolvedValue({ + exitCode: 0, + stdout: vsEnvs.join(os.EOL), + stderr: '' + }) + const toolPath = path.join( + installation, + 'Developer', + 'Toolchains', + 'unknown-Asserts-development.xctoolchain' + ) + const sdkroot = path.join( + installation, + 'Developer', + 'Platforms', + 'Windows.platform', + 'Developer', + 'SDKs', + 'Windows.sdk' + ) + const swiftLibs = path.join(sdkroot, 'usr', 'lib', 'swift') + const swiftPath = path.join(toolPath, 'usr', 'bin') + const swiftDev = path.join(installation, 'Swift-development', 'bin') + const icu67 = path.join(installation, 'icu-67', 'usr', 'bin') + await installer['add'](installation, 'x86_64') + expect(process.env.PATH?.includes(swiftPath)).toBeTruthy() + expect(process.env.PATH?.includes(swiftDev)).toBeTruthy() + expect(process.env.PATH?.includes(icu67)).toBeTruthy() + expect(process.env.SDKROOT).toBe(sdkroot) + expect(process.env.SWIFTFLAGS).toContain(`-sdk ${sdkroot}`) + expect(process.env.SWIFTFLAGS).toContain(`-I ${swiftLibs}`) + expect(process.env.SWIFTFLAGS).toContain( + `-L ${path.join(swiftLibs, 'windows')}` + ) + }) + + it('tests add to PATH with fallback SDK copying', async () => { + const installer = new WindowsToolchainInstaller(toolchain) + const installation = path.resolve('tool', 'installed', 'path') + jest.spyOn(VisualStudio, 'setup').mockResolvedValue(visualStudio) + jest + .spyOn(fs, 'access') + .mockRejectedValueOnce(new Error()) + .mockImplementation(async p => { + if (typeof p === 'string' && p.endsWith('vcruntime.modulemap')) { + return Promise.reject(new Error()) + } + return Promise.resolve() + }) + jest.spyOn(fs, 'copyFile').mockResolvedValue() + jest.spyOn(exec, 'exec').mockResolvedValue(0) + jest.spyOn(exec, 'getExecOutput').mockResolvedValue({ + exitCode: 0, + stdout: vsEnvs.join(os.EOL), + stderr: '' + }) + const toolPath = path.join( + installation, + 'Developer', + 'Toolchains', + 'unknown-Asserts-development.xctoolchain' + ) + const sdkroot = path.join( + installation, + 'Developer', + 'Platforms', + 'Windows.platform', + 'Developer', + 'SDKs', + 'Windows.sdk' + ) + const swiftLibs = path.join(sdkroot, 'usr', 'lib', 'swift') + const swiftPath = path.join(toolPath, 'usr', 'bin') + const swiftDev = path.join(installation, 'Swift-development', 'bin') + const icu67 = path.join(installation, 'icu-67', 'usr', 'bin') + await installer['add'](installation, 'x86_64') + expect(process.env.PATH?.includes(swiftPath)).toBeTruthy() + expect(process.env.PATH?.includes(swiftDev)).toBeTruthy() + expect(process.env.PATH?.includes(icu67)).toBeTruthy() + expect(process.env.SDKROOT).toBe(sdkroot) + expect(process.env.SWIFTFLAGS).toContain(`-sdk ${sdkroot}`) + expect(process.env.SWIFTFLAGS).toContain(`-I ${swiftLibs}`) + expect(process.env.SWIFTFLAGS).toContain( + `-L ${path.join(swiftLibs, 'windows')}` + ) + }) + + it('tests add to PATH without SDK copying', async () => { + const installer = new WindowsToolchainInstaller(toolchain) + const installation = path.resolve('tool', 'installed', 'path') + jest.spyOn(VisualStudio, 'setup').mockResolvedValue(visualStudio) + jest + .spyOn(fs, 'access') + .mockRejectedValueOnce(new Error()) + .mockImplementation(async p => { + if ( + typeof p === 'string' && + (p.endsWith('ucrt.modulemap') || p.endsWith('winsdk.modulemap')) + ) { + return Promise.reject(new Error()) + } + return Promise.resolve() + }) jest.spyOn(fs, 'copyFile').mockResolvedValue() jest.spyOn(exec, 'exec').mockResolvedValue(0) jest.spyOn(exec, 'getExecOutput').mockResolvedValue({ diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index d29167a9..0d5f028d 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -2,7 +2,8 @@ import * as core from '@actions/core' import * as main from '../src/main' import {Swiftorg} from '../src/swiftorg' import {Platform} from '../src/platform' -import {LinuxToolchainInstaller} from '../src/installer' +import {SdkSupportedVersion} from '../src/version' +import {LinuxToolchainInstaller, SdkToolchainInstaller} from '../src/installer' describe('setup-swift run validation', () => { const swiftorgSpy = jest @@ -23,11 +24,27 @@ describe('setup-swift run validation', () => { branch: 'swift-5.8-release', preventCaching: false } + const sdkToolchains = [ + { + name: 'Static SDK', + date: new Date('2023-03-30 10:28:49.000000000 -05:00'), + download: 'swift-5.8-RELEASE_static-linux-0.0.1.artifactbundle.tar.gz', + checksum: + 'df0b40b9b582598e7e3d70c82ab503fd6fbfdff71fd17e7f1ab37115a0665b3b', + dir: 'swift-5.8-RELEASE', + platform: 'static-sdk', + branch: 'swift-5.8-release', + preventCaching: true + } + ] it('tests dry run', async () => { toolchainSpy.mockResolvedValue(toolchain) jest.spyOn(core, 'getBooleanInput').mockReturnValue(true) jest.spyOn(core, 'getInput').mockReturnValue('latest') + jest + .spyOn(SdkSupportedVersion.prototype, 'sdkSnapshots') + .mockResolvedValue(sdkToolchains) await main.run() expect(failedSpy).not.toHaveBeenCalled() expect(installSpy).not.toHaveBeenCalled() @@ -45,13 +62,26 @@ describe('setup-swift run validation', () => { expect(obj).toStrictEqual(toolchain) break } + case 'sdks': { + const objs = JSON.parse(call[1]) + for (let i = 0; i < objs.length; i++) { + const obj = objs[i] + const sdkToolchain = sdkToolchains[i] + obj.date = new Date(obj.date) + expect(obj).toStrictEqual(sdkToolchain) + } + break + } } } }) it('tests install', async () => { const installer = new LinuxToolchainInstaller(toolchain) - installSpy.mockResolvedValue(installer) + const sdkInstallers = sdkToolchains.map( + toolchain => new SdkToolchainInstaller(toolchain) + ) + installSpy.mockResolvedValue({installer, sdkInstallers}) jest.spyOn(core, 'getBooleanInput').mockReturnValue(false) jest.spyOn(core, 'getInput').mockReturnValue('latest') jest.spyOn(installer, 'installedSwiftVersion').mockResolvedValue('5.8') @@ -72,6 +102,16 @@ describe('setup-swift run validation', () => { expect(obj).toStrictEqual(toolchain) break } + case 'sdks': { + const objs = JSON.parse(call[1]) + for (let i = 0; i < objs.length; i++) { + const obj = objs[i] + const sdkToolchain = sdkToolchains[i] + obj.date = new Date(obj.date) + expect(obj).toStrictEqual(sdkToolchain) + } + break + } } } }) diff --git a/__tests__/snapshot/linux.test.ts b/__tests__/snapshot/linux.test.ts index e219852d..7a752611 100644 --- a/__tests__/snapshot/linux.test.ts +++ b/__tests__/snapshot/linux.test.ts @@ -304,4 +304,203 @@ describe('fetch linux tool data based on options', () => { expect(tool.dir).toBe(name) expect(tool.branch).toBe('swiftwasm') }) + + it.each([ + 'static-sdk', + 'static-sdk:0.0.1', + 'static-linux', + 'static-linux:0.0.1' + ])('fetches ubuntu 22.04 swift tool with SDK %s', async sdk => { + setos({os: 'linux', dist: 'Ubuntu', release: '22.04'}) + jest.spyOn(os, 'arch').mockReturnValue('x64') + const version = ToolchainVersion.create('6.1.1', false, [sdk]) + const tool = await Platform.toolchain(version) + expect(tool).toBeTruthy() + const lTool = tool as LinuxToolchainSnapshot + expect(lTool.download).toBe('swift-6.1.1-RELEASE-ubuntu22.04.tar.gz') + expect(lTool.dir).toBe('swift-6.1.1-RELEASE') + expect(lTool.platform).toBe('ubuntu2204') + expect(lTool.branch).toBe('swift-6.1.1-release') + expect(lTool.download_signature).toBe( + 'swift-6.1.1-RELEASE-ubuntu22.04.tar.gz.sig' + ) + expect(lTool.preventCaching).toBe(false) + if (!tool) { + return + } + + const sdkSnapshots = await version.sdkSnapshots(tool) + expect(sdkSnapshots.length).toBe(1) + const sdkSnapshot = sdkSnapshots[0] + expect(sdkSnapshot.platform).toBe('static-sdk') + expect(sdkSnapshot.dir).toBe('swift-6.1.1-RELEASE') + expect(sdkSnapshot.branch).toBe('swift-6.1.1-release') + expect(sdkSnapshot.download).toBe( + 'swift-6.1.1-RELEASE_static-linux-0.0.1.artifactbundle.tar.gz' + ) + expect(sdkSnapshot.checksum).toBe( + '8a69753e181e40c202465f03bcafcc898070a86817ca0f39fc808f76638e90c2' + ) + }) + + it.each(['wasm-sdk', 'wasm'])( + 'fetches ubuntu 22.04 swift tool with SDK %s', + async sdk => { + setos({os: 'linux', dist: 'Ubuntu', release: '22.04'}) + jest.spyOn(os, 'arch').mockReturnValue('x64') + const version = ToolchainVersion.create('6.2.0', false, [sdk]) + const tool = await Platform.toolchain(version) + expect(tool).toBeTruthy() + const lTool = tool as LinuxToolchainSnapshot + expect(lTool.download).toBe('swift-6.2-RELEASE-ubuntu22.04.tar.gz') + expect(lTool.dir).toBe('swift-6.2-RELEASE') + expect(lTool.platform).toBe('ubuntu2204') + expect(lTool.branch).toBe('swift-6.2-release') + expect(lTool.download_signature).toBe( + 'swift-6.2-RELEASE-ubuntu22.04.tar.gz.sig' + ) + expect(lTool.preventCaching).toBe(false) + if (!tool) { + return + } + + const sdkSnapshots = await version.sdkSnapshots(tool) + expect(sdkSnapshots.length).toBe(1) + const sdkSnapshot = sdkSnapshots[0] + expect(sdkSnapshot.platform).toBe('wasm') + expect(sdkSnapshot.dir).toBe('swift-6.2-RELEASE') + expect(sdkSnapshot.branch).toBe('swift-6.2-release') + expect(sdkSnapshot.download).toBe( + 'swift-6.2-RELEASE_wasm.artifactbundle.tar.gz' + ) + expect(sdkSnapshot.checksum).toBe( + 'fe4e8648309fce86ea522e9e0d1dc48e82df6ba6e5743dbf0c53db8429fb5224' + ) + } + ) + + it('fetches ubuntu 22.04 named swift tool with Linux SDK', async () => { + setos({os: 'linux', dist: 'Ubuntu', release: '22.04'}) + jest.spyOn(os, 'arch').mockReturnValue('x64') + const name = 'swift-6.0-DEVELOPMENT-SNAPSHOT-2024-09-17-a' + const version = ToolchainVersion.create(name, false, ['static-sdk']) + const tool = await Platform.toolchain(version) + expect(tool).toBeTruthy() + const lTool = tool as LinuxToolchainSnapshot + expect(lTool.download).toBe( + 'swift-6.0-DEVELOPMENT-SNAPSHOT-2024-09-17-a-ubuntu22.04.tar.gz' + ) + expect(lTool.dir).toBe('swift-6.0-DEVELOPMENT-SNAPSHOT-2024-09-17-a') + expect(lTool.platform).toBe('ubuntu2204') + expect(lTool.branch).toBe('swift-6.0-branch') + expect(lTool.download_signature).toBe( + 'swift-6.0-DEVELOPMENT-SNAPSHOT-2024-09-17-a-ubuntu22.04.tar.gz.sig' + ) + expect(lTool.preventCaching).toBe(false) + if (!tool) { + return + } + + const sdkSnapshots = await version.sdkSnapshots(tool) + expect(sdkSnapshots.length).toBe(1) + const sdkSnapshot = sdkSnapshots[0] + expect(sdkSnapshot.platform).toBe('static-sdk') + expect(sdkSnapshot.dir).toBe('swift-6.0-DEVELOPMENT-SNAPSHOT-2024-09-17-a') + expect(sdkSnapshot.branch).toBe('swift-6.0-branch') + expect(sdkSnapshot.download).toBe( + 'swift-6.0-DEVELOPMENT-SNAPSHOT-2024-09-17-a_static-linux-0.0.1.artifactbundle.tar.gz' + ) + expect(sdkSnapshot.checksum).toBe( + '83a88650cd0675552ce2cf8159c31966fde73418f49493c1644073fffe8be9f4' + ) + }) + + it('fetches ubuntu 22.04 named swift tool with WASM SDK', async () => { + setos({os: 'linux', dist: 'Ubuntu', release: '22.04'}) + jest.spyOn(os, 'arch').mockReturnValue('x64') + const name = 'swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a' + const version = ToolchainVersion.create(name, false, ['wasm-sdk']) + const tool = await Platform.toolchain(version) + expect(tool).toBeTruthy() + const lTool = tool as LinuxToolchainSnapshot + expect(lTool.download).toBe( + 'swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a-ubuntu22.04.tar.gz' + ) + expect(lTool.dir).toBe('swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a') + expect(lTool.platform).toBe('ubuntu2204') + expect(lTool.branch).toBe('swift-6.2-branch') + expect(lTool.download_signature).toBe( + 'swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a-ubuntu22.04.tar.gz.sig' + ) + expect(lTool.preventCaching).toBe(false) + if (!tool) { + return + } + + const sdkSnapshots = await version.sdkSnapshots(tool) + expect(sdkSnapshots.length).toBe(1) + const sdkSnapshot = sdkSnapshots[0] + expect(sdkSnapshot.platform).toBe('wasm') + expect(sdkSnapshot.dir).toBe('swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a') + expect(sdkSnapshot.branch).toBe('swift-6.2-branch') + expect(sdkSnapshot.download).toBe( + 'swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a_wasm.artifactbundle.tar.gz' + ) + expect(sdkSnapshot.checksum).toBe( + '40f3c780d4a8f3d369c203615330e1b00441b6f8b7023535bebc16bf4dd5f84a' + ) + }) + + it('fetches ubuntu 22.04 named swift tool with Linux and WASM SDK', async () => { + setos({os: 'linux', dist: 'Ubuntu', release: '22.04'}) + jest.spyOn(os, 'arch').mockReturnValue('x64') + const name = 'swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a' + const version = ToolchainVersion.create(name, false, [ + 'static-sdk', + 'wasm-sdk' + ]) + const tool = await Platform.toolchain(version) + expect(tool).toBeTruthy() + const lTool = tool as LinuxToolchainSnapshot + expect(lTool.download).toBe( + 'swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a-ubuntu22.04.tar.gz' + ) + expect(lTool.dir).toBe('swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a') + expect(lTool.platform).toBe('ubuntu2204') + expect(lTool.branch).toBe('swift-6.2-branch') + expect(lTool.download_signature).toBe( + 'swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a-ubuntu22.04.tar.gz.sig' + ) + expect(lTool.preventCaching).toBe(false) + if (!tool) { + return + } + + const sdkSnapshots = await version.sdkSnapshots(tool) + expect(sdkSnapshots.length).toBe(2) + for (let i = 0; i < 2; i++) { + const sdkSnapshot = sdkSnapshots[i] + expect(sdkSnapshot.dir).toBe( + 'swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a' + ) + expect(sdkSnapshot.branch).toBe('swift-6.2-branch') + if (i == 0) { + expect(sdkSnapshot.platform).toBe('static-sdk') + expect(sdkSnapshot.download).toBe( + 'swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a_static-linux-0.0.1.artifactbundle.tar.gz' + ) + expect(sdkSnapshot.checksum).toBe( + '9e4065031461c00a88912e94d0c7d847e701a28667f58facad49dc636de77b6f' + ) + } else { + expect(sdkSnapshot.platform).toBe('wasm') + expect(sdkSnapshot.download).toBe( + 'swift-6.2-DEVELOPMENT-SNAPSHOT-2025-08-01-a_wasm.artifactbundle.tar.gz' + ) + expect(sdkSnapshot.checksum).toBe( + '40f3c780d4a8f3d369c203615330e1b00441b6f8b7023535bebc16bf4dd5f84a' + ) + } + } + }) }) diff --git a/__tests__/utils/visual_studio/setup.test.ts b/__tests__/utils/visual_studio/setup.test.ts index 26e7c2c4..4f4fd511 100644 --- a/__tests__/utils/visual_studio/setup.test.ts +++ b/__tests__/utils/visual_studio/setup.test.ts @@ -127,7 +127,7 @@ describe('visual studio setup validation', () => { stderr: '' }) await visualStudio.env() - const arg = execSpy.mock.calls.at(0) + const arg = execSpy.mock.calls[0] expect(arg).toBeTruthy() expect(arg?.[0]).toBe('cmd') expect(arg?.[1]).toStrictEqual([ diff --git a/action.yml b/action.yml index a0d9bf89..49f80e8a 100644 --- a/action.yml +++ b/action.yml @@ -54,11 +54,20 @@ inputs: i.e. Enable this option for installing static SDK: https://www.swift.org/documentation/articles/static-linux-getting-started.html required: false default: 'false' + sdks: + description: >- + Semi-colon separated list of Swift SDKs to install along with the main toolchain. + Available SDKs are based on platforms available for the specified version. + Examples: "static-sdk", "static-sdk;wasm-sdk", "static-sdk:0.0.1;wasm-sdk". + required: false + default: '' outputs: swift-version: description: The actual Swift version that was configured. toolchain: description: JSON formatted toolchain snapshot metadata that was configured. + sdks: + description: JSON formatted SDK snapshots metadata that were configured. runs: using: node20 main: dist/index.js diff --git a/dist/index.js b/dist/index.js index 50d35007..8bc099ae 100644 --- a/dist/index.js +++ b/dist/index.js @@ -114,7 +114,7 @@ class ToolchainInstaller { return data.baseUrl; } const base = 'https://download.swift.org'; - return new url_1.URL(path.posix.join(base, data.branch, data.platform, data.dir)); + return new url_1.URL(path.posix.join('/', data.branch, data.platform, data.dir), base); } swiftVersionCommand() { return { @@ -174,9 +174,9 @@ class ToolchainInstaller { await this.add(tool, arch); } async download(arch) { - const url = path.posix.join(this.baseUrl?.href, this.data.download); + const url = new url_1.URL(path.posix.join(this.baseUrl.pathname, this.data.download), this.baseUrl); core.debug(`Downloading snapshot from "${url}" for architecture ${arch}`); - return await toolCache.downloadTool(url); + return await toolCache.downloadTool(url.href); } async installedSwiftVersion(command) { const { bin, args } = command ?? this.swiftVersionCommand(); @@ -219,6 +219,7 @@ __exportStar(__nccwpck_require__(6298), exports); __exportStar(__nccwpck_require__(1949), exports); __exportStar(__nccwpck_require__(4121), exports); __exportStar(__nccwpck_require__(4426), exports); +__exportStar(__nccwpck_require__(9239), exports); /***/ }), @@ -360,6 +361,76 @@ class PackageManager { exports.PackageManager = PackageManager; +/***/ }), + +/***/ 9239: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"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 () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.SdkToolchainInstaller = void 0; +const path = __importStar(__nccwpck_require__(6928)); +const core = __importStar(__nccwpck_require__(7484)); +const exec_1 = __nccwpck_require__(5236); +const base_1 = __nccwpck_require__(8690); +class SdkToolchainInstaller extends base_1.ToolchainInstaller { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async unpack(resource, arch) { + throw new Error('Method not implemented.'); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async add(installation, arch) { + throw new Error('Method not implemented.'); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async install(arch) { + const url = new URL(path.posix.join(this.baseUrl.pathname, this.data.download), this.baseUrl); + core.startGroup(`Installing SDK ${url}`); + const args = ['sdk', 'install', url.href]; + if (this.data.checksum) { + args.push('--checksum', this.data.checksum); + } + await (0, exec_1.exec)('swift', args); + core.endGroup(); + } +} +exports.SdkToolchainInstaller = SdkToolchainInstaller; + + /***/ }), /***/ 6298: @@ -1140,7 +1211,9 @@ async function run() { try { const requestedVersion = core.getInput('swift-version') ?? 'latest'; const development = core.getBooleanInput('development'); - const version = version_1.ToolchainVersion.create(requestedVersion, development); + const sdksStr = core.getInput('sdks'); + const sdks = sdksStr ? sdksStr.split(';') : []; + const version = version_1.ToolchainVersion.create(requestedVersion, development, sdks); if (version.requiresSwiftOrg) { core.startGroup('Syncing swift.org data'); const checkLatest = core.getInput('check-latest'); @@ -1150,6 +1223,7 @@ async function run() { } const dryRun = core.getBooleanInput('dry-run'); let snapshot; + let sdkSnapshots; let installedVersion; if (dryRun) { const toolchain = await platform_1.Platform.toolchain(version); @@ -1166,14 +1240,17 @@ async function run() { else { installedVersion = requestedVersion; } + sdkSnapshots = await version.sdkSnapshots(toolchain); } else { - const installer = await platform_1.Platform.install(version); + const { installer, sdkInstallers } = await platform_1.Platform.install(version); snapshot = installer.data; + sdkSnapshots = sdkInstallers.map(installer => installer.data); installedVersion = await installer.installedSwiftVersion(); } core.setOutput('swift-version', installedVersion); core.setOutput('toolchain', JSON.stringify(snapshot)); + core.setOutput('sdks', JSON.stringify(sdkSnapshots)); } catch (error) { if (error instanceof Error) @@ -1236,14 +1313,12 @@ const core = __importStar(__nccwpck_require__(7484)); const yaml = __importStar(__nccwpck_require__(4281)); const semver_1 = __importDefault(__nccwpck_require__(2088)); const version_1 = __nccwpck_require__(191); -const const_1 = __nccwpck_require__(6575); -const RELEASE_FILE = path.join(const_1.MODULE_DIR, 'swiftorg', '_data', 'builds', 'swift_releases.yml'); class Platform { async toolFiles(version) { return await version.toolFiles(this.fileGlob); } async releases() { - const data = await fs_1.promises.readFile(RELEASE_FILE, 'utf-8'); + const data = await fs_1.promises.readFile(version_1.SWIFT_RELEASE_FILE, 'utf-8'); return yaml.load(data); } sortSnapshots(snapshots) { @@ -1301,7 +1376,10 @@ class Platform { }; }); }) - .filter(item => version.satisfiedBy(item.dir)); + .filter(item => { + const snapshot = item; + return version.satisfiedBy(snapshot.dir); + }); snapshots.push(...devSnapshots); return this.sortSnapshots(snapshots); } @@ -1363,6 +1441,7 @@ const base_1 = __nccwpck_require__(673); const xcode_1 = __nccwpck_require__(8775); const linux_1 = __nccwpck_require__(2136); const windows_1 = __nccwpck_require__(5687); +const installer_1 = __nccwpck_require__(1135); base_1.Platform.currentPlatform = async () => { const arch = os.arch(); const _os = await new Promise((resolve, reject) => { @@ -1412,11 +1491,15 @@ base_1.Platform.install = async (version) => { } const toolchain = toolchains[0]; core.startGroup(`Installing Swift toolchain snapshot ${toolchain.dir}`); - const installer = await platform.install(toolchains[0]); - core.endGroup(); - core.startGroup(`Getting installed Swift version ${toolchain.dir}`); + const installer = await platform.install(toolchain); core.endGroup(); - return installer; + const sdkInstallers = []; + for (const sdkSnapshot of await version.sdkSnapshots(toolchain)) { + const sdkInstaller = new installer_1.SdkToolchainInstaller(sdkSnapshot); + await sdkInstaller.install(toolchain.platform); + sdkInstallers.push(sdkInstaller); + } + return { installer, sdkInstallers }; }; __exportStar(__nccwpck_require__(673), exports); __exportStar(__nccwpck_require__(7507), exports); @@ -1674,7 +1757,7 @@ class VersionedPlatform extends base_1.Platform { async releasedTools(version) { const releases = await this.releases(); const allReleasedTools = releases - .filter(release => version.satisfiedBy(release.tag)) + .filter(release => version.satisfiedBy(release)) .flatMap(release => { return release.platforms.flatMap(platform => { const pName = platform.dir ?? @@ -1844,7 +1927,7 @@ class XcodePlatform extends base_1.Platform { async releasedTools(version) { const releases = await this.releases(); return releases - .filter(release => version.satisfiedBy(release.tag)) + .filter(release => version.satisfiedBy(release)) .map(release => { const xMatch = /Xcode\s+(.*)/.exec(release.xcode); return { @@ -2230,8 +2313,7 @@ class VisualStudio { }) .filter(match => { return match && match.length > 1; - }) - .at(0); + })[0]; if (sdkComponentMatch) { args.push(`-winsdk=10.0.${sdkComponentMatch[1]}.0`); } @@ -2465,10 +2547,20 @@ base_1.VisualStudio.prototype.update = async function (sdkroot) { const winsdk = path.join(universalCRTSdkDir, 'Include', uCRTVersion); const vcToolsInclude = path.join(vCToolsInstallDir, 'include'); const vcModulemap = path.join(vcToolsInclude, 'module.modulemap'); - const uCRTmap = path.join(sdkshare, 'ucrt.modulemap'); + const uCRTMapInstalled = path.join(sdkshare, 'ucrt.modulemap'); const winsdkMap = path.join(sdkshare, 'winsdk.modulemap'); - await fs_1.promises.copyFile(uCRTmap, path.join(winsdk, 'ucrt', 'module.modulemap')); - await fs_1.promises.copyFile(winsdkMap, path.join(winsdk, 'um', 'module.modulemap')); + const uCRTMapSdk = path.join(winsdk, 'ucrt', 'module.modulemap'); + const umMap = path.join(winsdk, 'um', 'module.modulemap'); + try { + await fs_1.promises.access(uCRTMapInstalled); + await fs_1.promises.access(winsdkMap); + } + catch (error) { + core.warning(`Skipping modulemap copies: "${error}"`); + return; + } + await fs_1.promises.copyFile(uCRTMapInstalled, uCRTMapSdk); + await fs_1.promises.copyFile(winsdkMap, umMap); try { const modulemap = path.join(sdkshare, 'vcruntime.modulemap'); const runtimenotes = 'vcruntime.apinotes'; @@ -2671,11 +2763,13 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.ToolchainVersion = exports.SWIFT_RELEASE_REGEX = void 0; +exports.ToolchainVersion = exports.SWIFT_RELEASE_REGEX = exports.SWIFT_RELEASE_FILE = exports.SWIFT_BUILDS_DIR = void 0; const path = __importStar(__nccwpck_require__(6928)); const core = __importStar(__nccwpck_require__(7484)); const glob_1 = __nccwpck_require__(1363); const const_1 = __nccwpck_require__(6575); +exports.SWIFT_BUILDS_DIR = path.join(const_1.MODULE_DIR, 'swiftorg', '_data', 'builds'); +exports.SWIFT_RELEASE_FILE = path.join(exports.SWIFT_BUILDS_DIR, 'swift_releases.yml'); exports.SWIFT_RELEASE_REGEX = /swift-(.*)-release/; class ToolchainVersion { dev; @@ -2690,8 +2784,7 @@ class ToolchainVersion { return undefined; } async toolFiles(fileGlob) { - const builds = 'swiftorg/_data/builds'; - const pattern = path.posix.join(builds, this.dirGlob, `${fileGlob}.yml`); + const pattern = path.posix.join(exports.SWIFT_BUILDS_DIR, this.dirGlob, `${fileGlob}.yml`); core.debug(`Searching for glob "${pattern}"`); let files = await (0, glob_1.glob)(pattern, { absolute: true, cwd: const_1.MODULE_DIR }); core.debug(`Retrieved files "${files}" for glob "${pattern}"`); @@ -2705,8 +2798,14 @@ class ToolchainVersion { } return files; } - satisfiedBy(dir) { - return this.dirRegex.exec(dir); + satisfiedBy(release) { + const tag = typeof release === 'string' ? release : release.tag; + return this.dirRegex.exec(tag) != null; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async sdkSnapshots(toolchain) { + core.info('No SDKs to install'); + return []; } } exports.ToolchainVersion = ToolchainVersion; @@ -2764,43 +2863,57 @@ const latest_1 = __nccwpck_require__(6764); const semver_2 = __nccwpck_require__(7263); const name_1 = __nccwpck_require__(9524); const location_1 = __nccwpck_require__(8192); -base_1.ToolchainVersion.create = (requested, dev = false) => { +const sdk_1 = __nccwpck_require__(4640); +base_1.ToolchainVersion.create = (requested, dev = false, sdks) => { + let baseVersion; try { const toolchainUrl = new url_1.URL(requested); - return new location_1.ToolchainSnapshotLocation(toolchainUrl, dev); + baseVersion = new location_1.ToolchainSnapshotLocation(toolchainUrl, dev); } catch { core.debug(`Input "${requested}" not an URL`); + if (requested === 'latest' || requested === 'current') { + core.debug(`Using latest ${dev ? 'development ' : ''}toolchain requirement`); + baseVersion = new latest_1.LatestToolchainVersion(dev); + } + else if (requested.includes(name_1.DEVELOPMENT_SNAPSHOT) || + requested.startsWith('swift-')) { + const version = new name_1.ToolchainSnapshotName(requested); + core.debug(`Using as toolchain name "${version}"`); + baseVersion = version; + } + else { + let semver; + try { + semver = new semver_1.SemVer(requested); + } + catch (error) { + const ver = (0, semver_1.coerce)(requested); + if (!ver) { + throw error; + } + semver = ver; + } + core.debug(`Using latest ${requested}${dev ? '-dev' : ''} toolchain requirement`); + baseVersion = new semver_2.SemanticToolchainVersion(requested, semver, dev); + } } - if (requested === 'latest' || requested === 'current') { - core.debug(`Using latest ${dev ? 'development ' : ''}toolchain requirement`); - return new latest_1.LatestToolchainVersion(dev); - } - if (requested.includes(name_1.DEVELOPMENT_SNAPSHOT) || - requested.startsWith('swift-')) { - const version = new name_1.ToolchainSnapshotName(requested); - core.debug(`Using as toolchain name "${version}"`); - return version; - } - let semver; - try { - semver = new semver_1.SemVer(requested); - } - catch (error) { - const ver = (0, semver_1.coerce)(requested); - if (!ver) { - throw error; + // If SDKs are requested, wrap the base version with SdkSupportedVersion + if (sdks && sdks.length > 0) { + const filteredSdks = sdks.filter(sdk => sdk.trim().length > 0); + if (filteredSdks.length > 0) { + core.debug(`Wrapping version with SDK support for: ${filteredSdks.join(', ')}`); + return new sdk_1.SdkSupportedVersion(baseVersion, filteredSdks, dev); } - semver = ver; } - core.debug(`Using latest ${requested}${dev ? '-dev' : ''} toolchain requirement`); - return new semver_2.SemanticToolchainVersion(requested, semver, dev); + return baseVersion; }; __exportStar(__nccwpck_require__(1634), exports); __exportStar(__nccwpck_require__(6764), exports); __exportStar(__nccwpck_require__(7263), exports); __exportStar(__nccwpck_require__(9524), exports); __exportStar(__nccwpck_require__(8192), exports); +__exportStar(__nccwpck_require__(4640), exports); /***/ }), @@ -2928,6 +3041,261 @@ class ToolchainSnapshotName extends base_1.ToolchainVersion { exports.ToolchainSnapshotName = ToolchainSnapshotName; +/***/ }), + +/***/ 4640: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"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 () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.SdkSupportedVersion = void 0; +const path = __importStar(__nccwpck_require__(6928)); +const fs_1 = __nccwpck_require__(9896); +const core = __importStar(__nccwpck_require__(7484)); +const yaml = __importStar(__nccwpck_require__(4281)); +const base_1 = __nccwpck_require__(1634); +const requirement_1 = __nccwpck_require__(4912); +class SdkSupportedVersion extends base_1.ToolchainVersion { + baseVersion; + requestedSdks; + constructor(baseVersion, requestedSdks, dev) { + super(dev || baseVersion.dev); + this.baseVersion = baseVersion; + this.requestedSdks = requestedSdks.map(requirement => requirement_1.SdkRequirement.create(requirement)); + } + get dirGlob() { + return this.baseVersion['dirGlob']; + } + get dirRegex() { + return this.baseVersion['dirRegex']; + } + get requiresSwiftOrg() { + return this.baseVersion.requiresSwiftOrg; + } + toolchainSnapshot(platform) { + return this.baseVersion.toolchainSnapshot(platform); + } + async toolFiles(fileGlob) { + const baseFiles = await this.baseVersion.toolFiles(fileGlob); + const baseFilesPromises = baseFiles.map(async (file) => { + const requestedSdksPromises = this.requestedSdks.map(async (requestedSdk) => { + const dir = path.dirname(file); + try { + try { + const sdkPath = path.join(dir, requestedSdk.file); + await fs_1.promises.access(sdkPath); + } + catch { + core.debug(`Retryng to find sdk config by replacing "-" with "_"`); + const sdkPath = path.join(dir, requestedSdk.file.replaceAll('-', '_')); + await fs_1.promises.access(sdkPath); + } + return true; + } + catch { + core.debug(`No SDK file found for ${requestedSdk} in ${dir}`); + return false; + } + }); + const requestedSdksResults = await Promise.all(requestedSdksPromises); + return { + value: file, + include: requestedSdksResults.every(result => result) + }; + }); + const baseFilesWithIncludes = await Promise.all(baseFilesPromises); + return baseFilesWithIncludes + .filter(file => file.include) + .map(file => file.value); + } + satisfiedBy(release) { + const baseSatisfied = this.baseVersion.satisfiedBy(release); + if (!baseSatisfied || typeof release === 'string') { + return baseSatisfied; + } + return this.requestedSdks.every(requestedSdk => { + return release.platforms.some(platform => platform.platform === requestedSdk.platform); + }); + } + async getDevelopmentSdkSnapshots(toolchain) { + return await Promise.all(this.requestedSdks.map(async (requestedSdk) => { + let data; + try { + const yml = path.join(base_1.SWIFT_BUILDS_DIR, toolchain.branch.replaceAll('.', '_'), requestedSdk.file); + data = await fs_1.promises.readFile(yml, 'utf-8'); + } + catch { + core.debug(`Retryng to find sdk config data by replacing "-" with "_"`); + const yml = path.join(base_1.SWIFT_BUILDS_DIR, toolchain.branch.replaceAll('.', '_'), requestedSdk.file.replaceAll('-', '_')); + data = await fs_1.promises.readFile(yml, 'utf-8'); + } + let snapshots = yaml.load(data); + snapshots = snapshots.filter(snapshot => snapshot.dir === toolchain.dir); + return { + ...snapshots[0], + name: requestedSdk.platform, + platform: requestedSdk.platform, + branch: toolchain.branch, + baseUrl: toolchain.baseUrl, + preventCaching: true + }; + })); + } + async getReleasedSdkSnapshots(toolchain) { + const data = await fs_1.promises.readFile(base_1.SWIFT_RELEASE_FILE, 'utf-8'); + const releases = yaml.load(data); + const release = releases.find(release => release.tag === toolchain.dir); + const snapshots = []; + for (const platform of release?.platforms ?? []) { + const requestedSdk = this.requestedSdks.find(requestedSdk => requestedSdk.platform == platform.platform); + if (!requestedSdk) { + continue; + } + snapshots.push({ + name: platform.name, + date: toolchain.date, + download: `${toolchain.dir}_${requestedSdk.download}.artifactbundle.tar.gz`, + dir: toolchain.dir, + platform: platform.platform, + branch: toolchain.branch, + baseUrl: toolchain.baseUrl, + preventCaching: true, + checksum: platform.checksum + }); + } + return snapshots; + } + async sdkSnapshots(toolchain) { + return base_1.SWIFT_RELEASE_REGEX.exec(toolchain.branch) != null + ? await this.getReleasedSdkSnapshots(toolchain) + : await this.getDevelopmentSdkSnapshots(toolchain); + } + toString() { + return `${this.baseVersion.toString()}, sdks: [${this.requestedSdks.join(', ')}]`; + } +} +exports.SdkSupportedVersion = SdkSupportedVersion; + + +/***/ }), + +/***/ 31: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.DefaultSdk = exports.Wasm = exports.StaticLinux = exports.SdkRequirement = void 0; +class SdkRequirement { + platform; + version; + constructor(platform, version) { + this.platform = platform; + this.version = version; + } + toString() { + return this.version ? `${this.platform}:${this.version}` : this.platform; + } +} +exports.SdkRequirement = SdkRequirement; +class StaticLinux extends SdkRequirement { + constructor(version) { + super('static-sdk', version); + } + get download() { + const version = this.version && this.version.length > 0 ? this.version : '0.0.1'; + return `static-linux-${version}`; + } + get file() { + return 'static-sdk.yml'; + } +} +exports.StaticLinux = StaticLinux; +class Wasm extends SdkRequirement { + constructor(version) { + super('wasm', version); + } + get download() { + const versionSuffix = this.version && this.version.length > 0 ? `-${this.version}` : ''; + return `wasm${versionSuffix}`; + } + get file() { + return 'wasm-sdk.yml'; + } +} +exports.Wasm = Wasm; +class DefaultSdk extends SdkRequirement { + get download() { + return this.platform; + } + get file() { + return `${this.platform}.yml`; + } +} +exports.DefaultSdk = DefaultSdk; + + +/***/ }), + +/***/ 4912: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.SdkRequirement = void 0; +const base_1 = __nccwpck_require__(31); +base_1.SdkRequirement.create = function (requirement) { + const [platform, version] = requirement.split(':', 2); + switch (platform) { + case 'static-sdk': + case 'static-linux': + return new base_1.StaticLinux(version); + case 'wasm-sdk': + case 'wasm': + return new base_1.Wasm(version); + default: + return new base_1.DefaultSdk(platform, version); + } +}; +var base_2 = __nccwpck_require__(31); +Object.defineProperty(exports, "SdkRequirement", ({ enumerable: true, get: function () { return base_2.SdkRequirement; } })); + + /***/ }), /***/ 7263: diff --git a/src/installer/base.ts b/src/installer/base.ts index a8efb9ec..61bf9d26 100644 --- a/src/installer/base.ts +++ b/src/installer/base.ts @@ -28,8 +28,12 @@ export abstract class ToolchainInstaller { 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)) + return new URL( + path.posix.join('/', data.branch, data.platform, data.dir), + base + ) } protected swiftVersionCommand() { @@ -99,9 +103,12 @@ export abstract class ToolchainInstaller { } protected async download(arch: string) { - const url = path.posix.join(this.baseUrl?.href, this.data.download) + const url = new URL( + path.posix.join(this.baseUrl.pathname, this.data.download), + this.baseUrl + ) core.debug(`Downloading snapshot from "${url}" for architecture ${arch}`) - return await toolCache.downloadTool(url) + return await toolCache.downloadTool(url.href) } protected abstract unpack(resource: string, arch: string): Promise diff --git a/src/installer/index.ts b/src/installer/index.ts index 5a87d294..9439c0d0 100644 --- a/src/installer/index.ts +++ b/src/installer/index.ts @@ -3,3 +3,4 @@ export * from './verify' export * from './linux' export * from './windows' export * from './xcode' +export * from './sdk' diff --git a/src/installer/sdk.ts b/src/installer/sdk.ts new file mode 100644 index 00000000..3d73d439 --- /dev/null +++ b/src/installer/sdk.ts @@ -0,0 +1,32 @@ +import * as path from 'path' +import * as core from '@actions/core' +import {exec} from '@actions/exec' +import {ToolchainInstaller} from './base' +import {SdkSnapshot} from '../snapshot' + +export class SdkToolchainInstaller extends ToolchainInstaller { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected async unpack(resource: string, arch: string): Promise { + throw new Error('Method not implemented.') + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected async add(installation: string, arch: string): Promise { + throw new Error('Method not implemented.') + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async install(arch: string) { + const url = new URL( + path.posix.join(this.baseUrl.pathname, this.data.download), + this.baseUrl + ) + core.startGroup(`Installing SDK ${url}`) + const args = ['sdk', 'install', url.href] + if (this.data.checksum) { + args.push('--checksum', this.data.checksum) + } + await exec('swift', args) + core.endGroup() + } +} diff --git a/src/main.ts b/src/main.ts index a471e853..1ca87f70 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,14 +1,16 @@ import * as core from '@actions/core' import {ToolchainVersion, SWIFT_BRANCH_REGEX} from './version' import {Swiftorg} from './swiftorg' -import {ToolchainSnapshot} from './snapshot' +import {SdkSnapshot, ToolchainSnapshot} from './snapshot' import {Platform} from './platform' export async function run() { try { const requestedVersion = core.getInput('swift-version') ?? 'latest' const development = core.getBooleanInput('development') - const version = ToolchainVersion.create(requestedVersion, development) + const sdksStr = core.getInput('sdks') + const sdks = sdksStr ? sdksStr.split(';') : [] + const version = ToolchainVersion.create(requestedVersion, development, sdks) if (version.requiresSwiftOrg) { core.startGroup('Syncing swift.org data') @@ -20,6 +22,7 @@ export async function run() { const dryRun = core.getBooleanInput('dry-run') let snapshot: ToolchainSnapshot + let sdkSnapshots: SdkSnapshot[] let installedVersion: string if (dryRun) { const toolchain = await Platform.toolchain(version) @@ -34,13 +37,16 @@ export async function run() { } else { installedVersion = requestedVersion } + sdkSnapshots = await version.sdkSnapshots(toolchain) } else { - const installer = await Platform.install(version) + const {installer, sdkInstallers} = await Platform.install(version) snapshot = installer.data + sdkSnapshots = sdkInstallers.map(installer => installer.data) installedVersion = await installer.installedSwiftVersion() } core.setOutput('swift-version', installedVersion) core.setOutput('toolchain', JSON.stringify(snapshot)) + core.setOutput('sdks', JSON.stringify(sdkSnapshots)) } catch (error) { if (error instanceof Error) core.setFailed(error.message) } diff --git a/src/platform/base.ts b/src/platform/base.ts index 4381fdf7..4d30566d 100644 --- a/src/platform/base.ts +++ b/src/platform/base.ts @@ -3,18 +3,13 @@ import {promises as fs} from 'fs' import * as core from '@actions/core' import * as yaml from 'js-yaml' import semver from 'semver' -import {ToolchainVersion, SWIFT_BRANCH_REGEX} from '../version' +import { + ToolchainVersion, + SWIFT_RELEASE_FILE, + SWIFT_BRANCH_REGEX +} from '../version' import {ToolchainSnapshot, SwiftRelease} from '../snapshot' import {ToolchainInstaller, SnapshotForInstaller} from '../installer' -import {MODULE_DIR} from '../const' - -const RELEASE_FILE = path.join( - MODULE_DIR, - 'swiftorg', - '_data', - 'builds', - 'swift_releases.yml' -) export abstract class Platform< Installer extends ToolchainInstaller> @@ -29,7 +24,7 @@ export abstract class Platform< } protected async releases() { - const data = await fs.readFile(RELEASE_FILE, 'utf-8') + const data = await fs.readFile(SWIFT_RELEASE_FILE, 'utf-8') return yaml.load(data) as SwiftRelease[] } @@ -106,7 +101,10 @@ export abstract class Platform< } as SnapshotForInstaller }) }) - .filter(item => version.satisfiedBy((item as ToolchainSnapshot).dir)) + .filter(item => { + const snapshot = item as ToolchainSnapshot + return version.satisfiedBy(snapshot.dir) + }) snapshots.push(...devSnapshots) return this.sortSnapshots(snapshots) } diff --git a/src/platform/index.ts b/src/platform/index.ts index d465196e..81bf6914 100644 --- a/src/platform/index.ts +++ b/src/platform/index.ts @@ -7,7 +7,7 @@ import {XcodePlatform} from './xcode' import {LinuxPlatform} from './linux' import {WindowsPlatform} from './windows' import {ToolchainSnapshot} from '../snapshot' -import {ToolchainInstaller} from '../installer' +import {ToolchainInstaller, SdkToolchainInstaller} from '../installer' declare module './base' { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -21,9 +21,10 @@ declare module './base' { export function toolchain( version: ToolchainVersion ): Promise - export function install( - version: ToolchainVersion - ): Promise> + export function install(version: ToolchainVersion): Promise<{ + installer: ToolchainInstaller + sdkInstallers: SdkToolchainInstaller[] + }> } } @@ -82,11 +83,16 @@ Platform.install = async (version: ToolchainVersion) => { } const toolchain = toolchains[0] core.startGroup(`Installing Swift toolchain snapshot ${toolchain.dir}`) - const installer = await platform.install(toolchains[0]) - core.endGroup() - core.startGroup(`Getting installed Swift version ${toolchain.dir}`) + const installer = await platform.install(toolchain) core.endGroup() - return installer + + const sdkInstallers: SdkToolchainInstaller[] = [] + for (const sdkSnapshot of await version.sdkSnapshots(toolchain)) { + const sdkInstaller = new SdkToolchainInstaller(sdkSnapshot) + await sdkInstaller.install(toolchain.platform) + sdkInstallers.push(sdkInstaller) + } + return {installer, sdkInstallers} } export * from './base' diff --git a/src/platform/versioned.ts b/src/platform/versioned.ts index 3ab662c3..cc633ab5 100644 --- a/src/platform/versioned.ts +++ b/src/platform/versioned.ts @@ -101,7 +101,7 @@ export abstract class VersionedPlatform< protected async releasedTools(version: ToolchainVersion) { const releases = await this.releases() const allReleasedTools = releases - .filter(release => version.satisfiedBy(release.tag)) + .filter(release => version.satisfiedBy(release)) .flatMap(release => { return release.platforms.flatMap(platform => { const pName = diff --git a/src/platform/xcode.ts b/src/platform/xcode.ts index 8bdd19a1..56b8e214 100644 --- a/src/platform/xcode.ts +++ b/src/platform/xcode.ts @@ -41,7 +41,7 @@ export class XcodePlatform extends Platform { protected async releasedTools(version: ToolchainVersion) { const releases = await this.releases() return releases - .filter(release => version.satisfiedBy(release.tag)) + .filter(release => version.satisfiedBy(release)) .map(release => { const xMatch = /Xcode\s+(.*)/.exec(release.xcode) return { diff --git a/src/snapshot/index.ts b/src/snapshot/index.ts index 89f329cc..b1084c9b 100644 --- a/src/snapshot/index.ts +++ b/src/snapshot/index.ts @@ -4,3 +4,4 @@ export * from './linux' export * from './windows' export * from './xcode' export * from './release' +export * from './sdk' diff --git a/src/snapshot/release.ts b/src/snapshot/release.ts index 26bb0c08..317ab057 100644 --- a/src/snapshot/release.ts +++ b/src/snapshot/release.ts @@ -12,8 +12,10 @@ export interface SwiftRelease { export namespace SwiftRelease { export interface Platform { readonly name: string + readonly platform: string readonly archs: string[] readonly dir?: string readonly docker?: string + readonly checksum?: string } } diff --git a/src/snapshot/sdk.ts b/src/snapshot/sdk.ts new file mode 100644 index 00000000..1100f716 --- /dev/null +++ b/src/snapshot/sdk.ts @@ -0,0 +1,5 @@ +import {ToolchainSnapshot} from './base' + +export interface SdkSnapshot extends ToolchainSnapshot { + readonly checksum?: string +} diff --git a/src/utils/visual_studio/base.ts b/src/utils/visual_studio/base.ts index 9dbd4cbe..1f1f5e4c 100644 --- a/src/utils/visual_studio/base.ts +++ b/src/utils/visual_studio/base.ts @@ -58,8 +58,7 @@ export class VisualStudio { }) .filter(match => { return match && match.length > 1 - }) - .at(0) + })[0] if (sdkComponentMatch) { args.push(`-winsdk=10.0.${sdkComponentMatch[1]}.0`) } diff --git a/src/utils/visual_studio/update.ts b/src/utils/visual_studio/update.ts index a501063e..1d62f6fd 100644 --- a/src/utils/visual_studio/update.ts +++ b/src/utils/visual_studio/update.ts @@ -30,10 +30,22 @@ VisualStudio.prototype.update = async function (sdkroot: string) { const winsdk = path.join(universalCRTSdkDir, 'Include', uCRTVersion) const vcToolsInclude = path.join(vCToolsInstallDir, 'include') const vcModulemap = path.join(vcToolsInclude, 'module.modulemap') - const uCRTmap = path.join(sdkshare, 'ucrt.modulemap') + const uCRTMapInstalled = path.join(sdkshare, 'ucrt.modulemap') const winsdkMap = path.join(sdkshare, 'winsdk.modulemap') - await fs.copyFile(uCRTmap, path.join(winsdk, 'ucrt', 'module.modulemap')) - await fs.copyFile(winsdkMap, path.join(winsdk, 'um', 'module.modulemap')) + const uCRTMapSdk = path.join(winsdk, 'ucrt', 'module.modulemap') + const umMap = path.join(winsdk, 'um', 'module.modulemap') + + try { + await fs.access(uCRTMapInstalled) + await fs.access(winsdkMap) + } catch (error) { + core.warning(`Skipping modulemap copies: "${error}"`) + return + } + + await fs.copyFile(uCRTMapInstalled, uCRTMapSdk) + await fs.copyFile(winsdkMap, umMap) + try { const modulemap = path.join(sdkshare, 'vcruntime.modulemap') const runtimenotes = 'vcruntime.apinotes' diff --git a/src/version/base.ts b/src/version/base.ts index d45586c4..6dd58ed0 100644 --- a/src/version/base.ts +++ b/src/version/base.ts @@ -2,8 +2,18 @@ import * as path from 'path' import * as core from '@actions/core' import {glob} from 'glob' import {MODULE_DIR} from '../const' -import {ToolchainSnapshot} from '../snapshot' +import {ToolchainSnapshot, SwiftRelease, SdkSnapshot} from '../snapshot' +export const SWIFT_BUILDS_DIR = path.join( + MODULE_DIR, + 'swiftorg', + '_data', + 'builds' +) +export const SWIFT_RELEASE_FILE = path.join( + SWIFT_BUILDS_DIR, + 'swift_releases.yml' +) export const SWIFT_RELEASE_REGEX = /swift-(.*)-release/ export abstract class ToolchainVersion { @@ -22,8 +32,11 @@ export abstract class ToolchainVersion { } async toolFiles(fileGlob: string) { - const builds = 'swiftorg/_data/builds' - const pattern = path.posix.join(builds, this.dirGlob, `${fileGlob}.yml`) + const pattern = path.posix.join( + SWIFT_BUILDS_DIR, + 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}"`) @@ -38,7 +51,14 @@ export abstract class ToolchainVersion { return files } - satisfiedBy(dir: string) { - return this.dirRegex.exec(dir) + satisfiedBy(release: SwiftRelease | string) { + const tag = typeof release === 'string' ? release : release.tag + return this.dirRegex.exec(tag) != null + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async sdkSnapshots(toolchain: ToolchainSnapshot): Promise { + core.info('No SDKs to install') + return [] } } diff --git a/src/version/index.ts b/src/version/index.ts index 25a2c3ed..131038d2 100644 --- a/src/version/index.ts +++ b/src/version/index.ts @@ -6,50 +6,70 @@ import {LatestToolchainVersion} from './latest' import {SemanticToolchainVersion} from './semver' import {ToolchainSnapshotName, DEVELOPMENT_SNAPSHOT} from './name' import {ToolchainSnapshotLocation} from './location' +import {SdkSupportedVersion} from './sdk' declare module './base' { // eslint-disable-next-line @typescript-eslint/no-namespace export namespace ToolchainVersion { - function create(requested: string, dev: boolean): ToolchainVersion + function create( + requested: string, + dev: boolean, + sdks?: string[] + ): ToolchainVersion } } -ToolchainVersion.create = (requested: string, dev = false) => { +ToolchainVersion.create = (requested: string, dev = false, sdks?: string[]) => { + let baseVersion: ToolchainVersion + try { const toolchainUrl = new URL(requested) - return new ToolchainSnapshotLocation(toolchainUrl, dev) + baseVersion = 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) - } - if ( - requested.includes(DEVELOPMENT_SNAPSHOT) || - requested.startsWith('swift-') - ) { - const version = new ToolchainSnapshotName(requested) - core.debug(`Using as toolchain name "${version}"`) - return version + if (requested === 'latest' || requested === 'current') { + core.debug( + `Using latest ${dev ? 'development ' : ''}toolchain requirement` + ) + baseVersion = new LatestToolchainVersion(dev) + } else if ( + requested.includes(DEVELOPMENT_SNAPSHOT) || + requested.startsWith('swift-') + ) { + const version = new ToolchainSnapshotName(requested) + core.debug(`Using as toolchain name "${version}"`) + baseVersion = version + } else { + let semver: SemVer + try { + semver = new SemVer(requested) + } catch (error) { + const ver = parseSemVer(requested) + if (!ver) { + throw error + } + semver = ver + } + core.debug( + `Using latest ${requested}${dev ? '-dev' : ''} toolchain requirement` + ) + baseVersion = new SemanticToolchainVersion(requested, semver, dev) + } } - let semver: SemVer - try { - semver = new SemVer(requested) - } catch (error) { - const ver = parseSemVer(requested) - if (!ver) { - throw error + // If SDKs are requested, wrap the base version with SdkSupportedVersion + if (sdks && sdks.length > 0) { + const filteredSdks = sdks.filter(sdk => sdk.trim().length > 0) + if (filteredSdks.length > 0) { + core.debug( + `Wrapping version with SDK support for: ${filteredSdks.join(', ')}` + ) + return new SdkSupportedVersion(baseVersion, filteredSdks, dev) } - semver = ver } - core.debug( - `Using latest ${requested}${dev ? '-dev' : ''} toolchain requirement` - ) - return new SemanticToolchainVersion(requested, semver, dev) + + return baseVersion } export * from './base' @@ -57,3 +77,4 @@ export * from './latest' export * from './semver' export * from './name' export * from './location' +export * from './sdk' diff --git a/src/version/sdk/index.ts b/src/version/sdk/index.ts new file mode 100644 index 00000000..881fc045 --- /dev/null +++ b/src/version/sdk/index.ts @@ -0,0 +1,177 @@ +import * as path from 'path' +import {promises as fs} from 'fs' +import * as core from '@actions/core' +import * as yaml from 'js-yaml' +import { + SWIFT_BUILDS_DIR, + SWIFT_RELEASE_FILE, + SWIFT_RELEASE_REGEX, + ToolchainVersion +} from '../base' +import {SdkSnapshot, SwiftRelease, ToolchainSnapshot} from '../../snapshot' +import {SdkRequirement} from './requirement' + +export class SdkSupportedVersion< + T extends ToolchainVersion +> extends ToolchainVersion { + readonly requestedSdks: SdkRequirement[] + + constructor( + readonly baseVersion: T, + requestedSdks: string[], + dev: boolean + ) { + super(dev || baseVersion.dev) + this.requestedSdks = requestedSdks.map(requirement => + SdkRequirement.create(requirement) + ) + } + + protected get dirGlob() { + return this.baseVersion['dirGlob'] + } + + protected get dirRegex() { + return this.baseVersion['dirRegex'] + } + + get requiresSwiftOrg() { + return this.baseVersion.requiresSwiftOrg + } + + toolchainSnapshot(platform: string) { + return this.baseVersion.toolchainSnapshot(platform) + } + + async toolFiles(fileGlob: string) { + const baseFiles = await this.baseVersion.toolFiles(fileGlob) + const baseFilesPromises = baseFiles.map(async file => { + const requestedSdksPromises = this.requestedSdks.map( + async requestedSdk => { + const dir = path.dirname(file) + try { + try { + const sdkPath = path.join(dir, requestedSdk.file) + await fs.access(sdkPath) + } catch { + core.debug(`Retryng to find sdk config by replacing "-" with "_"`) + const sdkPath = path.join( + dir, + requestedSdk.file.replaceAll('-', '_') + ) + await fs.access(sdkPath) + } + return true + } catch { + core.debug(`No SDK file found for ${requestedSdk} in ${dir}`) + return false + } + } + ) + + const requestedSdksResults = await Promise.all(requestedSdksPromises) + return { + value: file, + include: requestedSdksResults.every(result => result) + } + }) + const baseFilesWithIncludes = await Promise.all(baseFilesPromises) + return baseFilesWithIncludes + .filter(file => file.include) + .map(file => file.value) + } + + satisfiedBy(release: SwiftRelease | string) { + const baseSatisfied = this.baseVersion.satisfiedBy(release) + if (!baseSatisfied || typeof release === 'string') { + return baseSatisfied + } + + return this.requestedSdks.every(requestedSdk => { + return release.platforms.some( + platform => platform.platform === requestedSdk.platform + ) + }) + } + + private async getDevelopmentSdkSnapshots( + toolchain: ToolchainSnapshot + ): Promise { + return await Promise.all( + this.requestedSdks.map(async requestedSdk => { + let data: string + try { + const yml = path.join( + SWIFT_BUILDS_DIR, + toolchain.branch.replaceAll('.', '_'), + requestedSdk.file + ) + data = await fs.readFile(yml, 'utf-8') + } catch { + core.debug( + `Retryng to find sdk config data by replacing "-" with "_"` + ) + const yml = path.join( + SWIFT_BUILDS_DIR, + toolchain.branch.replaceAll('.', '_'), + requestedSdk.file.replaceAll('-', '_') + ) + data = await fs.readFile(yml, 'utf-8') + } + + let snapshots = yaml.load(data) as SdkSnapshot[] + snapshots = snapshots.filter(snapshot => snapshot.dir === toolchain.dir) + return { + ...snapshots[0], + name: requestedSdk.platform, + platform: requestedSdk.platform, + branch: toolchain.branch, + baseUrl: toolchain.baseUrl, + preventCaching: true + } + }) + ) + } + + private async getReleasedSdkSnapshots( + toolchain: ToolchainSnapshot + ): Promise { + const data = await fs.readFile(SWIFT_RELEASE_FILE, 'utf-8') + const releases = yaml.load(data) as SwiftRelease[] + const release = releases.find(release => release.tag === toolchain.dir) + + const snapshots: SdkSnapshot[] = [] + for (const platform of release?.platforms ?? []) { + const requestedSdk = this.requestedSdks.find( + requestedSdk => requestedSdk.platform == platform.platform + ) + + if (!requestedSdk) { + continue + } + + snapshots.push({ + name: platform.name, + date: toolchain.date, + download: `${toolchain.dir}_${requestedSdk.download}.artifactbundle.tar.gz`, + dir: toolchain.dir, + platform: platform.platform, + branch: toolchain.branch, + baseUrl: toolchain.baseUrl, + preventCaching: true, + checksum: platform.checksum + }) + } + return snapshots + } + + async sdkSnapshots(toolchain: ToolchainSnapshot): Promise { + return SWIFT_RELEASE_REGEX.exec(toolchain.branch) != null + ? await this.getReleasedSdkSnapshots(toolchain) + : await this.getDevelopmentSdkSnapshots(toolchain) + } + + toString() { + return `${this.baseVersion.toString()}, sdks: [${this.requestedSdks.join(', ')}]` + } +} diff --git a/src/version/sdk/requirement/base.ts b/src/version/sdk/requirement/base.ts new file mode 100644 index 00000000..6197b330 --- /dev/null +++ b/src/version/sdk/requirement/base.ts @@ -0,0 +1,55 @@ +export abstract class SdkRequirement { + constructor( + readonly platform: string, + readonly version: string | undefined + ) {} + + abstract get download(): string + abstract get file(): string + + toString(): string { + return this.version ? `${this.platform}:${this.version}` : this.platform + } +} + +export class StaticLinux extends SdkRequirement { + constructor(version: string | undefined) { + super('static-sdk', version) + } + + get download(): string { + const version = + this.version && this.version.length > 0 ? this.version : '0.0.1' + return `static-linux-${version}` + } + + get file(): string { + return 'static-sdk.yml' + } +} + +export class Wasm extends SdkRequirement { + constructor(version: string | undefined) { + super('wasm', version) + } + + get download(): string { + const versionSuffix = + this.version && this.version.length > 0 ? `-${this.version}` : '' + return `wasm${versionSuffix}` + } + + get file(): string { + return 'wasm-sdk.yml' + } +} + +export class DefaultSdk extends SdkRequirement { + get download(): string { + return this.platform + } + + get file(): string { + return `${this.platform}.yml` + } +} diff --git a/src/version/sdk/requirement/index.ts b/src/version/sdk/requirement/index.ts new file mode 100644 index 00000000..e567800b --- /dev/null +++ b/src/version/sdk/requirement/index.ts @@ -0,0 +1,25 @@ +import {SdkRequirement, StaticLinux, Wasm, DefaultSdk} from './base' + +declare module './base' { + // eslint-disable-next-line no-shadow, @typescript-eslint/no-namespace + export namespace SdkRequirement { + export function create(requirement: string): SdkRequirement + } +} + +SdkRequirement.create = function (requirement: string) { + const [platform, version] = requirement.split(':', 2) + + switch (platform) { + case 'static-sdk': + case 'static-linux': + return new StaticLinux(version) + case 'wasm-sdk': + case 'wasm': + return new Wasm(version) + default: + return new DefaultSdk(platform, version) + } +} + +export {SdkRequirement} from './base'