Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,22 +157,25 @@ jobs:
development: [false, true]
include:
- os: macos-latest
swift: '5.0.0'
swift: '5.0.0' # oldest
development: false
- os: macos-13
swift: '5.9'
swift: 'latest' # action caching architecture mismatch
development: false
- os: macos-13
swift: '5.9' # Xcode toolchain inclusion optimization
development: false
- os: windows-latest
swift: '5.9'
swift: '5.9' # 2nd installation approach
development: false
- os: ubuntu-latest
swift: '5.3.0'
swift: '5.3.0' # oldest
development: false
- os: windows-latest
swift: '5.3'
swift: '5.3' # 1st installation approach
development: false
- os: ubuntu-22.04
swift: ${{ fromJSON(vars.SETUPSWIFT_CUSTOM_TOOLCHAINS).ubuntu2204 }}
swift: ${{ fromJSON(vars.SETUPSWIFT_CUSTOM_TOOLCHAINS).ubuntu2204 }} # custom toolchain
development: true

steps:
Expand Down Expand Up @@ -223,8 +226,10 @@ jobs:
with:
script: |
const os = require('os');
const path = require('path');
const fs = require('fs/promises');
const toolCache = require('@actions/tool-cache');
const {exec: actionExec} = require('@actions/exec');
let arch = '';
switch (os.arch()) {
case 'x64':
Expand All @@ -242,22 +247,25 @@ jobs:
core.debug(`Toolcache not set`);
return;
}
const tools = toolCache.findAllVersions(key, arch);
if (!tools.length) {
core.debug(`No tools found for "${key}" with arch ${arch}`);
const versions = toolCache.findAllVersions(key, arch);
if (!versions.length) {
core.debug(`No versions found for "${key}" with arch ${arch}`);
return;
} else {
core.debug(`Found tools "${tools.join(', ')}" for "${key}" with arch ${arch}`);
core.debug(`Found versions "${versions.join(', ')}" for "${key}" with arch ${arch}`);
}
const tool = tools[0];
const tool = toolCache.find(key, versions[0], arch).trim();
await fs.access(tool);
return { key: key, tool: tool };
const tmpDir = process.env.RUNNER_TEMP || os.tmpdir();
const zip = path.join(tmpDir, `${key}.zip`);
await actionExec('zip', ['-r', zip, tool, '-x', '*.DS_Store']);
return { key: key, tool: zip };

- name: Upload cached installation as artifact
if: always() && steps.get-tool.outputs.result != ''
uses: actions/upload-artifact@v4
with:
name: ${{ fromJson(steps.get-tool.outputs.result).key }}-tool
name: ${{ fromJson(steps.get-tool.outputs.result).key }}-${{ matrix.os }}-tool
path: ${{ fromJson(steps.get-tool.outputs.result).tool }}

dry-run:
Expand Down
66 changes: 42 additions & 24 deletions __tests__/installer/linux.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,30 +71,48 @@ describe('linux toolchain installation verification', () => {
expect(process.env.PATH?.includes(swiftPath)).toBeTruthy()
})

it('tests installation with download', async () => {
const installer = new LinuxToolchainInstaller(toolchain)
const download = path.resolve('tool', 'download', 'path')
const extracted = path.resolve('tool', 'extracted', 'path')
const cached = path.resolve('tool', 'cached', 'path')
const swiftPath = path.join(cached, 'usr', 'bin')
jest.spyOn(core, 'getBooleanInput').mockReturnValue(true)
jest.spyOn(cache, 'restoreCache').mockResolvedValue(undefined)
jest.spyOn(cache, 'saveCache').mockResolvedValue(1)
jest.spyOn(toolCache, 'find').mockReturnValue('')
jest.spyOn(fs, 'cp').mockResolvedValue()
const downloadSpy = jest.spyOn(toolCache, 'downloadTool')
downloadSpy.mockResolvedValue(download)
const extractSpy = jest.spyOn(toolCache, 'extractTar')
extractSpy.mockResolvedValue(extracted)
const cacheSpy = jest.spyOn(toolCache, 'cacheDir')
cacheSpy.mockResolvedValue(cached)
jest.spyOn(exec, 'exec').mockResolvedValue(0)
await installer.install()
expect(process.env.PATH?.includes(swiftPath)).toBeTruthy()
for (const spy of [downloadSpy, extractSpy, cacheSpy]) {
expect(spy).toHaveBeenCalled()
it.each(['aarch64', 'x86_64'])(
'tests installation with download for arch %s',
async arch => {
const installer = new LinuxToolchainInstaller(toolchain)
const download = path.resolve('tool', 'download', 'path')
const extracted = path.resolve('tool', 'extracted', 'path')
const cached = path.resolve('tool', 'cached', 'path')
const swiftPath = path.join(cached, 'usr', 'bin')
jest.spyOn(core, 'getBooleanInput').mockReturnValue(true)
jest.spyOn(cache, 'restoreCache').mockResolvedValue(undefined)
jest.spyOn(toolCache, 'find').mockReturnValue('')
jest.spyOn(fs, 'cp').mockResolvedValue()
const downloadSpy = jest.spyOn(toolCache, 'downloadTool')
downloadSpy.mockResolvedValue(download)
const extractSpy = jest.spyOn(toolCache, 'extractTar')
extractSpy.mockResolvedValue(extracted)
const toolCacheSpy = jest.spyOn(toolCache, 'cacheDir')
toolCacheSpy.mockResolvedValue(cached)
const actionCacheSpy = jest.spyOn(cache, 'saveCache')
actionCacheSpy.mockResolvedValue(1)
jest.spyOn(exec, 'exec').mockResolvedValue(0)
await installer.install(arch)
expect(process.env.PATH?.includes(swiftPath)).toBeTruthy()
for (const spy of [
downloadSpy,
extractSpy,
toolCacheSpy,
actionCacheSpy
]) {
expect(spy).toHaveBeenCalled()
}
const toolCacheKey = `${toolchain.dir}-${toolchain.platform}`
const actionCacheKey = `${toolCacheKey}-${arch}`
const toolDir = path.basename(toolchain.download, '.tar.gz')
const cachedTool = path.join(extracted, toolDir)
expect(toolCacheSpy.mock.calls[0]?.[0]).toBe(cachedTool)
expect(toolCacheSpy.mock.calls[0]?.[1]).toBe(toolCacheKey)
expect(toolCacheSpy.mock.calls[0]?.[2]).toBe('5.8.0')
expect(toolCacheSpy.mock.calls[0]?.[3]).toBe(arch)
expect(actionCacheSpy.mock.calls[0]?.[1]).toBe(actionCacheKey)
}
})
)

it('tests installation with cache', async () => {
const installer = new LinuxToolchainInstaller(toolchain)
Expand All @@ -107,7 +125,7 @@ describe('linux toolchain installation verification', () => {
jest.spyOn(exec, 'exec').mockResolvedValue(0)
const downloadSpy = jest.spyOn(toolCache, 'downloadTool')
const extractSpy = jest.spyOn(toolCache, 'extractTar')
await installer.install()
await installer.install('aarch64')
expect(process.env.PATH?.includes(swiftPath)).toBeTruthy()
for (const spy of [downloadSpy, extractSpy]) {
expect(spy).not.toHaveBeenCalled()
Expand Down
2 changes: 1 addition & 1 deletion __tests__/installer/windows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ describe('windows toolchain installation verification', () => {
stdout: vsEnvs.join(os.EOL),
stderr: ''
})
await installer.install()
await installer.install('x86_64')
expect(setupSpy).toHaveBeenCalled()
expect(process.env.PATH?.includes(swiftPath)).toBeTruthy()
expect(process.env.PATH?.includes(swiftDev)).toBeTruthy()
Expand Down
90 changes: 55 additions & 35 deletions __tests__/installer/xcode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ describe('macOS toolchain installation verification', () => {
const installationNeededSpy = jest.spyOn(installer, 'isInstallationNeeded')
const downloadSpy = jest.spyOn(toolCache, 'downloadTool')
const extractSpy = jest.spyOn(toolCache, 'extractXar')
await installer.install()
await installer.install('x86_64')
await installer.install('aarch64')
for (const spy of [downloadSpy, extractSpy]) {
expect(spy).not.toHaveBeenCalled()
}
Expand Down Expand Up @@ -96,39 +97,58 @@ describe('macOS toolchain installation verification', () => {
expect(process.env.TOOLCHAINS).toBe(identifier)
})

it('tests installation with download', async () => {
const installer = new XcodeToolchainInstaller(toolchain)
const download = path.resolve('tool', 'download', 'path')
const extracted = path.resolve('tool', 'extracted', 'path')
const deployed = path.resolve('tool', 'deployed', 'path')
const cached = path.resolve('tool', 'cached', 'path')
const swiftPath = path.join(cached, 'usr', 'bin')
const identifier = 'org.swift.581202305171a'
jest.spyOn(installer, 'isInstallationNeeded').mockResolvedValue(true)
jest.spyOn(core, 'getBooleanInput').mockReturnValue(true)
jest.spyOn(cache, 'restoreCache').mockResolvedValue(undefined)
jest.spyOn(cache, 'saveCache').mockResolvedValue(1)
jest.spyOn(toolCache, 'find').mockReturnValue('')
jest.spyOn(exec, 'exec').mockResolvedValue(0)
jest.spyOn(fs, 'cp').mockResolvedValue()
const downloadSpy = jest.spyOn(toolCache, 'downloadTool')
downloadSpy.mockResolvedValue(download)
const extractSpy = jest.spyOn(toolCache, 'extractXar')
extractSpy.mockResolvedValue(extracted)
const deploySpy = jest.spyOn(toolCache, 'extractTar')
deploySpy.mockResolvedValue(deployed)
const cacheSpy = jest.spyOn(toolCache, 'cacheDir')
cacheSpy.mockResolvedValue(cached)
jest.spyOn(fs, 'access').mockResolvedValue()
jest.spyOn(fs, 'readFile').mockResolvedValue('')
jest.spyOn(plist, 'parse').mockReturnValue({CFBundleIdentifier: identifier})
await installer.install()
expect(process.env.PATH?.includes(swiftPath)).toBeTruthy()
expect(process.env.TOOLCHAINS).toBe(identifier)
for (const spy of [downloadSpy, extractSpy, deploySpy, cacheSpy]) {
expect(spy).toHaveBeenCalled()
it.each(['aarch64', 'x86_64'])(
'tests installation with download for %s',
async arch => {
const installer = new XcodeToolchainInstaller(toolchain)
const download = path.resolve('tool', 'download', 'path')
const extracted = path.resolve('tool', 'extracted', 'path')
const deployed = path.resolve('tool', 'deployed', 'path')
const cached = path.resolve('tool', 'cached', 'path')
const swiftPath = path.join(cached, 'usr', 'bin')
const identifier = 'org.swift.581202305171a'
jest.spyOn(installer, 'isInstallationNeeded').mockResolvedValue(true)
jest.spyOn(core, 'getBooleanInput').mockReturnValue(true)
jest.spyOn(cache, 'restoreCache').mockResolvedValue(undefined)
jest.spyOn(toolCache, 'find').mockReturnValue('')
jest.spyOn(exec, 'exec').mockResolvedValue(0)
jest.spyOn(fs, 'cp').mockResolvedValue()
const downloadSpy = jest.spyOn(toolCache, 'downloadTool')
downloadSpy.mockResolvedValue(download)
const extractSpy = jest.spyOn(toolCache, 'extractXar')
extractSpy.mockResolvedValue(extracted)
const deploySpy = jest.spyOn(toolCache, 'extractTar')
deploySpy.mockResolvedValue(deployed)
const toolCacheSpy = jest.spyOn(toolCache, 'cacheDir')
toolCacheSpy.mockResolvedValue(cached)
const actionCacheSpy = jest.spyOn(cache, 'saveCache')
actionCacheSpy.mockResolvedValue(1)
jest.spyOn(fs, 'access').mockResolvedValue()
jest.spyOn(fs, 'readFile').mockResolvedValue('')
jest
.spyOn(plist, 'parse')
.mockReturnValue({CFBundleIdentifier: identifier})
await installer.install(arch)
expect(process.env.PATH?.includes(swiftPath)).toBeTruthy()
expect(process.env.TOOLCHAINS).toBe(identifier)
for (const spy of [
downloadSpy,
extractSpy,
deploySpy,
toolCacheSpy,
actionCacheSpy
]) {
expect(spy).toHaveBeenCalled()
}
const toolCacheKey = `${toolchain.dir}-${toolchain.platform}`
const actionCacheKey = `${toolCacheKey}-${arch}`
expect(toolCacheSpy.mock.calls[0]?.[0]).toBe(deployed)
expect(toolCacheSpy.mock.calls[0]?.[1]).toBe(toolCacheKey)
expect(toolCacheSpy.mock.calls[0]?.[2]).toBe('5.8.1')
expect(toolCacheSpy.mock.calls[0]?.[3]).toBe(arch)
expect(actionCacheSpy.mock.calls[0]?.[1]).toBe(actionCacheKey)
}
})
)

it('tests installation with cache', async () => {
const installer = new XcodeToolchainInstaller(toolchain)
Expand All @@ -152,7 +172,7 @@ describe('macOS toolchain installation verification', () => {
jest.spyOn(fs, 'access').mockResolvedValue()
jest.spyOn(fs, 'readFile').mockResolvedValue('')
jest.spyOn(plist, 'parse').mockReturnValue({CFBundleIdentifier: identifier})
await installer.install()
await installer.install('aarch64')
expect(process.env.PATH?.includes(swiftPath)).toBeTruthy()
expect(process.env.TOOLCHAINS).toBe(identifier)
for (const spy of [downloadSpy, extractSpy, deploySpy]) {
Expand All @@ -170,7 +190,7 @@ describe('macOS toolchain installation verification', () => {
stdout: `swift-driver version: 1.75.2 Apple Swift version 5.8.1 (swiftlang-5.8.0.124.5 clang-1403.0.22.11.100)\nTarget: arm64-apple-macosx13.0`,
stderr: ''
})
await expect(installer.install()).resolves
await expect(installer.install('aarch64')).resolves
expect(process.env.DEVELOPER_DIR).toBe(toolchain.xcodePath)
})

Expand Down
21 changes: 11 additions & 10 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 14 additions & 11 deletions src/installer/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,26 @@
}
}

async install(arch?: string) {
const key = `${this.data.dir}-${this.data.platform}`
async install(arch: string) {
const toolCacheKey = `${this.data.dir}-${this.data.platform}`
const actionCacheKey = arch ? `${toolCacheKey}-${arch}` : toolCacheKey
const version = this.version?.raw
let tool: string | undefined
let cacheHit = false
if (version) {
core.debug(
`Finding tool with key: "${key}", version: "${version}" and arch: "${arch}" in tool cache`
`Finding tool with key: "${toolCacheKey}", version: "${version}" and arch: "${arch}" in tool cache`
)
tool = toolCache.find(key, version, arch).trim()
tool = toolCache.find(toolCacheKey, version, arch).trim()
}

const tmpDir = process.env.RUNNER_TEMP || os.tmpdir()
const restore = path.join(tmpDir, 'setup-swift', key)
const restore = path.join(tmpDir, 'setup-swift', toolCacheKey)
if (!tool?.length) {
if (await cache.restoreCache([restore], key)) {
core.debug(`Restored snapshot at "${restore}" from key "${key}"`)
if (await cache.restoreCache([restore], actionCacheKey)) {
core.debug(

Check warning on line 59 in src/installer/base.ts

View check run for this annotation

Codecov / codecov/patch

src/installer/base.ts#L59

Added line #L59 was not covered by tests
`Restored snapshot at "${restore}" from key "${actionCacheKey}"`
)
tool = restore
cacheHit = true
} else {
Expand All @@ -70,9 +73,9 @@
}

if (tool && version) {
tool = await toolCache.cacheDir(tool, key, version, arch)
tool = await toolCache.cacheDir(tool, toolCacheKey, version, arch)
if (core.isDebug()) {
core.exportVariable('SWIFT_SETUP_TOOL_KEY', key)
core.exportVariable('SWIFT_SETUP_TOOL_KEY', toolCacheKey)
}
core.debug(`Added to tool cache at "${tool}"`)
}
Expand All @@ -83,8 +86,8 @@
!this.data.preventCaching
) {
await fs.cp(tool, restore, {recursive: true})
await cache.saveCache([restore], key)
core.debug(`Saved to cache with key "${key}"`)
await cache.saveCache([restore], actionCacheKey)
core.debug(`Saved to cache with key "${actionCacheKey}"`)
}
await this.add(tool)
}
Expand Down
Loading
Loading