diff --git a/__tests__/installer/linux.test.ts b/__tests__/installer/linux.test.ts index 34babbc..dbcfe3d 100644 --- a/__tests__/installer/linux.test.ts +++ b/__tests__/installer/linux.test.ts @@ -48,7 +48,7 @@ describe('linux toolchain installation verification', () => { jest.spyOn(cache, 'saveCache').mockResolvedValue(1) jest.spyOn(toolCache, 'downloadTool').mockResolvedValue(download) jest.spyOn(exec, 'exec').mockResolvedValue(0) - await expect(installer['download']()).resolves.toBe(download) + await expect(installer['download']('x86_64')).resolves.toBe(download) }) it('tests unpack', async () => { diff --git a/__tests__/installer/windows.test.ts b/__tests__/installer/windows.test.ts index 087aab2..11689b3 100644 --- a/__tests__/installer/windows.test.ts +++ b/__tests__/installer/windows.test.ts @@ -57,7 +57,7 @@ describe('windows toolchain installation verification', () => { 'Microsoft.VisualStudio.Component.VC.ATL;Microsoft.VisualStudio.Component.VC.CMake.Project;Microsoft.VisualStudio.Component.Windows10SDK' ) const installer = new WindowsToolchainInstaller(toolchain) - expect(installer['vsRequirement'].components).toStrictEqual([ + expect(installer['vsRequirement']('x86_64').components).toStrictEqual([ 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', 'Microsoft.VisualStudio.Component.VC.ATL', 'Microsoft.VisualStudio.Component.VC.CMake.Project', @@ -69,16 +69,25 @@ describe('windows toolchain installation verification', () => { it('tests setting up on Windows 10', async () => { jest.spyOn(os, 'release').mockReturnValue('10.0.17063') const installer = new WindowsToolchainInstaller(toolchain) - expect(installer['vsRequirement'].components).toStrictEqual([ + expect(installer['vsRequirement']('x86_64').components).toStrictEqual([ 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', 'Microsoft.VisualStudio.Component.Windows10SDK.17763' ]) }) + it('tests setting up on ARM64 Windows 10', async () => { + jest.spyOn(os, 'release').mockReturnValue('10.0.17063') + const installer = new WindowsToolchainInstaller(toolchain) + expect(installer['vsRequirement']('aarch64').components).toStrictEqual([ + 'Microsoft.VisualStudio.Component.VC.Tools.ARM64', + 'Microsoft.VisualStudio.Component.Windows10SDK.17763' + ]) + }) + it('tests setting up on Windows 11', async () => { jest.spyOn(os, 'release').mockReturnValue('10.0.22621') const installer = new WindowsToolchainInstaller(toolchain) - expect(installer['vsRequirement'].components).toStrictEqual([ + expect(installer['vsRequirement']('x86_64').components).toStrictEqual([ 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', 'Microsoft.VisualStudio.Component.Windows11SDK.22621' ]) @@ -90,12 +99,31 @@ describe('windows toolchain installation verification', () => { .spyOn(core, 'getInput') .mockReturnValue('Microsoft.VisualStudio.Component.Windows11SDK.22621') const installer = new WindowsToolchainInstaller(toolchain) - expect(installer['vsRequirement'].components).toStrictEqual([ + expect(installer['vsRequirement']('x86_64').components).toStrictEqual([ 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', 'Microsoft.VisualStudio.Component.Windows11SDK.22621' ]) }) + it('tests setting up on Windows 10 with Windows 11 SDK', async () => { + jest.spyOn(os, 'release').mockReturnValue('10.0.17063') + const toolchain = { + name: 'Windows 10 Swift Development Snapshot', + date: new Date('2025-04-03 10:10:00-06:00'), + download: 'swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a-windows10.exe', + dir: 'swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a', + platform: 'windows10', + branch: 'development', + windows: true, + preventCaching: false + } + const installer = new WindowsToolchainInstaller(toolchain) + expect(installer['vsRequirement']('aarch64').components).toStrictEqual([ + 'Microsoft.VisualStudio.Component.VC.Tools.ARM64', + 'Microsoft.VisualStudio.Component.Windows11SDK.22000' + ]) + }) + it('tests download without caching', async () => { const installer = new WindowsToolchainInstaller(toolchain) expect(installer['version']).toStrictEqual(parseSemVer('5.8')) @@ -118,7 +146,9 @@ describe('windows toolchain installation verification', () => { const cacheSpy = jest.spyOn(cache, 'saveCache').mockResolvedValue(1) jest.spyOn(toolCache, 'downloadTool').mockResolvedValue(download) jest.spyOn(exec, 'exec').mockResolvedValue(0) - await expect(installer['download']()).resolves.toBe(`${download}.exe`) + await expect(installer['download']('x86_64')).resolves.toBe( + `${download}.exe` + ) expect(cacheSpy).not.toHaveBeenCalled() }) @@ -138,7 +168,7 @@ describe('windows toolchain installation verification', () => { jest.spyOn(fs, 'access').mockResolvedValue() jest.spyOn(fs, 'cp').mockResolvedValue() const toolPath = path.join(process.env.SystemDrive, 'Library') - await expect(installer['unpack'](exe)).resolves.toBe(toolPath) + await expect(installer['unpack'](exe, 'x86_64')).resolves.toBe(toolPath) }) it('tests unpack for development snapshots', async () => { @@ -160,7 +190,7 @@ describe('windows toolchain installation verification', () => { 'Program Files', 'Swift' ) - await expect(installer['unpack'](exe)).resolves.toBe(toolPath) + await expect(installer['unpack'](exe, 'x86_64')).resolves.toBe(toolPath) }) it('tests unpack for failed path matching', async () => { @@ -186,7 +216,7 @@ describe('windows toolchain installation verification', () => { jest.spyOn(fs, 'cp').mockRejectedValue(new Error()) const addPathSpy = jest.spyOn(core, 'addPath') const exportVariableSpy = jest.spyOn(core, 'exportVariable') - await expect(installer['unpack'](exe)).resolves.toBe('') + await expect(installer['unpack'](exe, 'x86_64')).resolves.toBe('') expect(addPathSpy).toHaveBeenCalledTimes(2) expect(exportVariableSpy).toHaveBeenCalledTimes(1) expect(addPathSpy.mock.calls).toStrictEqual([['b'], ['c']]) @@ -198,7 +228,7 @@ describe('windows toolchain installation verification', () => { const updateSpy = jest .spyOn(VisualStudio.prototype, 'update') .mockResolvedValue() - await installer['add']('') + await installer['add']('', 'x86_64') expect(setupSpy).toHaveBeenCalled() expect(updateSpy).toHaveBeenCalledWith('root') }) @@ -236,7 +266,7 @@ describe('windows toolchain installation verification', () => { jest.spyOn(fs, 'cp').mockRejectedValue(new Error()) const addPathSpy = jest.spyOn(core, 'addPath') const exportVariableSpy = jest.spyOn(core, 'exportVariable') - await expect(installer['unpack'](exe)).resolves.toBe('') + await expect(installer['unpack'](exe, 'x86_64')).resolves.toBe('') expect(addPathSpy).toHaveBeenCalledTimes(2) expect(exportVariableSpy).toHaveBeenCalledTimes(1) expect(addPathSpy.mock.calls).toStrictEqual([['b'], ['c']]) @@ -248,7 +278,7 @@ describe('windows toolchain installation verification', () => { const updateSpy = jest .spyOn(VisualStudio.prototype, 'update') .mockResolvedValue() - await installer['add']('') + await installer['add']('', 'x86_64') expect(setupSpy).toHaveBeenCalled() expect(updateSpy).toHaveBeenCalledWith('root') }) @@ -285,7 +315,7 @@ describe('windows toolchain installation verification', () => { 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) + 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() diff --git a/__tests__/installer/xcode.test.ts b/__tests__/installer/xcode.test.ts index 76339b8..23868b6 100644 --- a/__tests__/installer/xcode.test.ts +++ b/__tests__/installer/xcode.test.ts @@ -112,7 +112,7 @@ describe('macOS toolchain installation verification', () => { jest.spyOn(cache, 'saveCache').mockResolvedValue(1) jest.spyOn(toolCache, 'downloadTool').mockResolvedValue(download) jest.spyOn(exec, 'exec').mockResolvedValue(0) - await expect(installer['download']()).resolves.toBe(download) + await expect(installer['download']('x86_64')).resolves.toBe(download) }) it('tests unpack', async () => { @@ -123,7 +123,9 @@ describe('macOS toolchain installation verification', () => { jest.spyOn(toolCache, 'extractXar').mockResolvedValue(extracted) jest.spyOn(toolCache, 'extractTar').mockResolvedValue(deployed) jest.spyOn(exec, 'exec').mockResolvedValue(0) - await expect(installer['unpack'](download)).resolves.toBe(deployed) + await expect(installer['unpack'](download, 'x86_64')).resolves.toBe( + deployed + ) }) it('tests add to PATH', async () => { @@ -135,7 +137,7 @@ describe('macOS toolchain installation verification', () => { jest.spyOn(fs, 'readFile').mockResolvedValue('') jest.spyOn(plist, 'parse').mockReturnValue({CFBundleIdentifier: identifier}) const swiftPath = path.join(deployed, 'usr', 'bin') - await installer['add'](deployed) + await installer['add'](deployed, 'x86_64') expect(process.env.PATH?.includes(swiftPath)).toBeTruthy() expect(process.env.TOOLCHAINS).toBe(identifier) }) diff --git a/dist/index.js b/dist/index.js index e393be3..ca18485 100644 --- a/dist/index.js +++ b/dist/index.js @@ -142,8 +142,8 @@ class ToolchainInstaller { actionCacheHit = true; } else { - const resource = await this.download(); - const installation = await this.unpack(resource); + const resource = await this.download(arch); + const installation = await this.unpack(resource, arch); core.debug(`Downloaded and installed snapshot at "${installation}"`); tool = installation; } @@ -171,11 +171,11 @@ class ToolchainInstaller { await cache.saveCache([restore], actionCacheKey); core.debug(`Saved to cache with key "${actionCacheKey}"`); } - await this.add(tool); + await this.add(tool, arch); } - async download() { + async download(arch) { const url = path.posix.join(this.baseUrl?.href, this.data.download); - core.debug(`Downloading snapshot from "${url}"`); + core.debug(`Downloading snapshot from "${url}" for architecture ${arch}`); return await toolCache.downloadTool(url); } async installedSwiftVersion(command) { @@ -307,10 +307,10 @@ class LinuxToolchainInstaller extends verify_1.VerifyingToolchainInstaller { core.debug(`Dependencies installation failed with "${error}"`); } } - async download() { + async download(arch) { const [, archive] = await Promise.all([ this.installDependencies(), - super.download() + super.download(arch) ]); return archive; } @@ -427,7 +427,7 @@ class VerifyingToolchainInstaller extends base_1.ToolchainInstaller { throw error; } } - async download() { + async download(arch) { const sigUrl = this.signatureUrl; async function setupKeys() { if (sigUrl) { @@ -437,7 +437,7 @@ class VerifyingToolchainInstaller extends base_1.ToolchainInstaller { core.debug(`Downloading snapshot signature from "${sigUrl}"`); const [, toolchain, signature] = await Promise.all([ setupKeys(), - super.download(), + super.download(arch), this.downloadSignature() ]); if (signature) { @@ -501,22 +501,24 @@ const utils_1 = __nccwpck_require__(1750); const installation_1 = __nccwpck_require__(4694); class WindowsToolchainInstaller extends verify_1.VerifyingToolchainInstaller { get winsdk() { - const recommended = '10.0.17763'; const win11Semver = '10.0.22000'; + const recommended = semver.gte(this.version ?? '6.2.0', '6.2.0') + ? win11Semver + : '10.0.17763'; const current = os.release(); const version = semver.gte(current, recommended) ? current : recommended; const major = semver.lt(version, win11Semver) ? semver.major(version) : 11; const minor = semver.patch(version); return `Microsoft.VisualStudio.Component.Windows${major}SDK.${minor}`; } - get vsRequirement() { + vsRequirement(arch) { const componentsStr = core.getInput('visual-studio-components'); const providedComponents = componentsStr ? componentsStr.split(';') : []; const winsdkComponent = providedComponents.find(component => { return ((utils_1.VISUAL_STUDIO_WINSDK_COMPONENT_REGEX.exec(component)?.length ?? 0) > 1); }); const vsComponents = [ - 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', + `Microsoft.VisualStudio.Component.VC.Tools.${arch == 'aarch64' ? 'ARM64' : 'x86.x64'}`, ...providedComponents ]; if (!winsdkComponent) { @@ -528,22 +530,23 @@ class WindowsToolchainInstaller extends verify_1.VerifyingToolchainInstaller { components: vsComponents }; } - async download() { - core.debug(`Using VS requirement ${JSON.stringify(this.vsRequirement)}`); + async download(arch) { + core.debug(`Using VS requirement ${JSON.stringify(this.vsRequirement(arch))}`); const [, toolchain] = await Promise.all([ - utils_1.VisualStudio.setup(this.vsRequirement), - super.download() + utils_1.VisualStudio.setup(this.vsRequirement(arch)), + super.download(arch) ]); const exeFile = `${toolchain}.exe`; await fs_1.promises.rename(toolchain, exeFile); core.debug(`Toolchain installer downloaded at "${exeFile}"`); return exeFile; } - async unpack(exe) { + async unpack(exe, arch) { + core.debug(`Unpacking for architecture "${arch}"`); const installation = await installation_1.Installation.install(exe); return installation instanceof installation_1.Installation ? installation.location : ''; } - async add(installLocation) { + async add(installLocation, arch) { const installation = await installation_1.Installation.get(installLocation); const sdkrootKey = 'SDKROOT'; let sdkroot; @@ -579,7 +582,7 @@ class WindowsToolchainInstaller extends verify_1.VerifyingToolchainInstaller { core.warning(`Failed VS enviroment after installation ${installLocation}`); return; } - const visualStudio = await utils_1.VisualStudio.setup(this.vsRequirement); + const visualStudio = await utils_1.VisualStudio.setup(this.vsRequirement(arch)); await visualStudio.update(sdkroot); const swiftFlags = [ '-sdk', @@ -1002,14 +1005,14 @@ class XcodeToolchainInstaller extends base_1.ToolchainInstaller { } await super.install(arch); } - async download() { - const toolchain = await super.download(); + async download(arch) { + const toolchain = await super.download(arch); core.debug(`Checking package signature for "${toolchain}"`); await (0, exec_1.exec)('pkgutil', ['--check-signature', toolchain]); return toolchain; } - async unpack(pkg) { - core.debug(`Extracting toolchain from "${pkg}"`); + async unpack(pkg, arch) { + core.debug(`Extracting toolchain from "${pkg}" for architecture ${arch}`); const unpackedPath = await toolCache.extractXar(pkg); core.debug(`Toolchain unpacked to "${unpackedPath}"`); const pkgFile = this.data.download; @@ -1019,7 +1022,7 @@ class XcodeToolchainInstaller extends base_1.ToolchainInstaller { core.debug(`Toolchain extracted to "${extractedPath}"`); return extractedPath; } - async add(toolchain) { + async add(toolchain, arch) { const xctoolchains = path.join('/Library', 'Developer', 'Toolchains'); try { await fs_1.promises.access(xctoolchains); @@ -1045,7 +1048,7 @@ class XcodeToolchainInstaller extends base_1.ToolchainInstaller { core.debug(`Adding toolchain "${toolchain}" to path`); const swiftPath = path.join(toolchain, 'usr', 'bin'); core.addPath(swiftPath); - core.debug(`Swift installed at "${swiftPath}"`); + core.debug(`Swift installed for architecture ${arch} at "${swiftPath}"`); const infoPlist = await fs_1.promises.readFile(path.join(toolchain, 'Info.plist'), 'utf-8'); const info = plist.parse(infoPlist).valueOf(); core.debug(`Setting Swift toolchain identifier to "${info.CFBundleIdentifier}"`); @@ -99555,7 +99558,7 @@ const testSet = (set, version, options) => { const debug = __nccwpck_require__(1159) const { MAX_LENGTH, MAX_SAFE_INTEGER } = __nccwpck_require__(5101) -const { safeRe: re, t } = __nccwpck_require__(5471) +const { safeRe: re, safeSrc: src, t } = __nccwpck_require__(5471) const parseOptions = __nccwpck_require__(356) const { compareIdentifiers } = __nccwpck_require__(3348) @@ -99565,7 +99568,7 @@ class SemVer { if (version instanceof SemVer) { if (version.loose === !!options.loose && - version.includePrerelease === !!options.includePrerelease) { + version.includePrerelease === !!options.includePrerelease) { return version } else { version = version.version @@ -99731,6 +99734,20 @@ class SemVer { // preminor will bump the version up to the next minor release, and immediately // down to pre-release. premajor and prepatch work the same way. inc (release, identifier, identifierBase) { + if (release.startsWith('pre')) { + if (!identifier && identifierBase === false) { + throw new Error('invalid increment argument: identifier is empty') + } + // Avoid an invalid semver results + if (identifier) { + const r = new RegExp(`^${this.options.loose ? src[t.PRERELEASELOOSE] : src[t.PRERELEASE]}$`) + const match = `-${identifier}`.match(r) + if (!match || match[1] !== identifier) { + throw new Error(`invalid identifier: ${identifier}`) + } + } + } + switch (release) { case 'premajor': this.prerelease.length = 0 @@ -99761,6 +99778,12 @@ class SemVer { } this.inc('pre', identifier, identifierBase) break + case 'release': + if (this.prerelease.length === 0) { + throw new Error(`version ${this.raw} is not a prerelease`) + } + this.prerelease.length = 0 + break case 'major': // If this is a pre-major version, bump up to the same major version. @@ -99804,10 +99827,6 @@ class SemVer { case 'pre': { const base = Number(identifierBase) ? 1 : 0 - if (!identifier && identifierBase === false) { - throw new Error('invalid increment argument: identifier is empty') - } - if (this.prerelease.length === 0) { this.prerelease = [base] } else { @@ -100066,20 +100085,13 @@ const diff = (version1, version2) => { return 'major' } - // Otherwise it can be determined by checking the high version - - if (highVersion.patch) { - // anything higher than a patch bump would result in the wrong version + // If the main part has no difference + if (lowVersion.compareMain(highVersion) === 0) { + if (lowVersion.minor && !lowVersion.patch) { + return 'minor' + } return 'patch' } - - if (highVersion.minor) { - // anything higher than a minor bump would result in the wrong version - return 'minor' - } - - // bumping major/minor/patch all have same result - return 'major' } // add the `pre` prefix if we are going to a prerelease version @@ -100586,6 +100598,7 @@ exports = module.exports = {} const re = exports.re = [] const safeRe = exports.safeRe = [] const src = exports.src = [] +const safeSrc = exports.safeSrc = [] const t = exports.t = {} let R = 0 @@ -100618,6 +100631,7 @@ const createToken = (name, value, isGlobal) => { debug(name, index, value) t[name] = index src[index] = value + safeSrc[index] = safe re[index] = new RegExp(value, isGlobal ? 'g' : undefined) safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined) } diff --git a/src/installer/base.ts b/src/installer/base.ts index 9d19900..a8efb9e 100644 --- a/src/installer/base.ts +++ b/src/installer/base.ts @@ -63,8 +63,8 @@ export abstract class ToolchainInstaller { tool = restore actionCacheHit = true } else { - const resource = await this.download() - const installation = await this.unpack(resource) + const resource = await this.download(arch) + const installation = await this.unpack(resource, arch) core.debug(`Downloaded and installed snapshot at "${installation}"`) tool = installation } @@ -95,17 +95,17 @@ export abstract class ToolchainInstaller { await cache.saveCache([restore], actionCacheKey) core.debug(`Saved to cache with key "${actionCacheKey}"`) } - await this.add(tool) + await this.add(tool, arch) } - protected async download() { + protected async download(arch: string) { const url = path.posix.join(this.baseUrl?.href, this.data.download) - core.debug(`Downloading snapshot from "${url}"`) + core.debug(`Downloading snapshot from "${url}" for architecture ${arch}`) return await toolCache.downloadTool(url) } - protected abstract unpack(resource: string): Promise - protected abstract add(installation: string): Promise + protected abstract unpack(resource: string, arch: string): Promise + protected abstract add(installation: string, arch: string): Promise async installedSwiftVersion(command?: {bin: string; args: string[]}) { const {bin, args} = command ?? this.swiftVersionCommand() diff --git a/src/installer/linux.ts b/src/installer/linux.ts index f747bb0..074fd67 100644 --- a/src/installer/linux.ts +++ b/src/installer/linux.ts @@ -43,10 +43,10 @@ export class LinuxToolchainInstaller extends VerifyingToolchainInstaller { private get winsdk() { - const recommended = '10.0.17763' const win11Semver = '10.0.22000' + const recommended = semver.gte(this.version ?? '6.2.0', '6.2.0') + ? win11Semver + : '10.0.17763' const current = os.release() const version = semver.gte(current, recommended) ? current : recommended const major = semver.lt(version, win11Semver) ? semver.major(version) : 11 @@ -19,7 +21,7 @@ export class WindowsToolchainInstaller extends VerifyingToolchainInstaller { @@ -29,7 +31,7 @@ export class WindowsToolchainInstaller extends VerifyingToolchainInstaller