Skip to content

Commit 5bd49ae

Browse files
authored
perf: installation will be saved in cache (#48)
1 parent 32576cd commit 5bd49ae

File tree

6 files changed

+99
-112
lines changed

6 files changed

+99
-112
lines changed

__tests__/installer/linux.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,15 @@ describe('linux toolchain installation verification', () => {
9393
const cached = path.resolve('tool', 'cached', 'path')
9494
const swiftPath = path.join(cached, 'usr', 'bin')
9595
jest.spyOn(toolCache, 'find').mockReturnValue(cached)
96+
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue(cached)
97+
jest.spyOn(cache, 'saveCache').mockResolvedValue(1)
98+
jest.spyOn(core, 'getBooleanInput').mockReturnValue(true)
99+
jest.spyOn(exec, 'exec').mockResolvedValue(0)
96100
const downloadSpy = jest.spyOn(toolCache, 'downloadTool')
97101
const extractSpy = jest.spyOn(toolCache, 'extractTar')
98-
const cacheSpy = jest.spyOn(toolCache, 'cacheDir')
99-
jest.spyOn(exec, 'exec').mockResolvedValue(0)
100102
await installer.install()
101103
expect(process.env.PATH?.includes(swiftPath)).toBeTruthy()
102-
for (const spy of [downloadSpy, extractSpy, cacheSpy]) {
104+
for (const spy of [downloadSpy, extractSpy]) {
103105
expect(spy).not.toHaveBeenCalled()
104106
}
105107
})

__tests__/installer/windows.test.ts

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -58,32 +58,6 @@ describe('windows toolchain installation verification', () => {
5858
])
5959
})
6060

61-
it('tests download with caching', async () => {
62-
const installer = new WindowsToolchainInstaller(toolchain)
63-
expect(installer['version']).toStrictEqual(parseSemVer('5.8'))
64-
expect(installer['baseUrl']).toBe(
65-
'https://download.swift.org/swift-5.8-release/windows10/swift-5.8-RELEASE'
66-
)
67-
68-
const download = path.resolve('tool', 'download', 'path')
69-
process.env.VSWHERE_PATH = path.join('C:', 'Visual Studio')
70-
jest.spyOn(fs, 'access').mockResolvedValue()
71-
jest.spyOn(fs, 'rename').mockResolvedValue()
72-
jest.spyOn(core, 'getBooleanInput').mockReturnValue(true)
73-
jest.spyOn(exec, 'exec').mockResolvedValue(0)
74-
jest.spyOn(exec, 'getExecOutput').mockResolvedValue({
75-
exitCode: 0,
76-
stdout: JSON.stringify([visualStudio]),
77-
stderr: ''
78-
})
79-
jest.spyOn(cache, 'restoreCache').mockResolvedValue(undefined)
80-
const cacheSpy = jest.spyOn(cache, 'saveCache').mockResolvedValue(1)
81-
jest.spyOn(toolCache, 'downloadTool').mockResolvedValue(download)
82-
jest.spyOn(exec, 'exec').mockResolvedValue(0)
83-
await expect(installer['download']()).resolves.toBe(`${download}.exe`)
84-
expect(cacheSpy).toHaveBeenCalled()
85-
})
86-
8761
it('tests download without caching', async () => {
8862
const installer = new WindowsToolchainInstaller(toolchain)
8963
expect(installer['version']).toStrictEqual(parseSemVer('5.8'))
@@ -204,7 +178,10 @@ describe('windows toolchain installation verification', () => {
204178
.mockResolvedValue(visualStudio)
205179
jest.spyOn(fs, 'access').mockRejectedValue(new Error())
206180
jest.spyOn(fs, 'copyFile').mockResolvedValue()
181+
jest.spyOn(core, 'getBooleanInput').mockReturnValue(true)
207182
jest.spyOn(toolCache, 'find').mockReturnValue(cached)
183+
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue(cached)
184+
jest.spyOn(cache, 'saveCache').mockResolvedValue(1)
208185
jest.spyOn(exec, 'exec').mockResolvedValue(0)
209186
jest.spyOn(exec, 'getExecOutput').mockResolvedValue({
210187
exitCode: 0,

__tests__/installer/xcode.test.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import * as toolCache from '@actions/tool-cache'
77
import {coerce as parseSemVer} from 'semver'
88
import * as plist from 'plist'
99
import {XcodeToolchainInstaller} from '../../src/installer/xcode'
10-
import {NoInstallationNeededError} from '../../src/installer/base'
1110

1211
jest.mock('plist')
1312

@@ -43,9 +42,14 @@ describe('macOS toolchain installation verification', () => {
4342
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`,
4443
stderr: ''
4544
})
46-
await expect(installer['download']()).rejects.toMatchObject(
47-
new NoInstallationNeededError('Bundled with xcode')
48-
)
45+
const installationNeededSpy = jest.spyOn(installer, 'isInstallationNeeded')
46+
const downloadSpy = jest.spyOn(toolCache, 'downloadTool')
47+
const extractSpy = jest.spyOn(toolCache, 'extractXar')
48+
await installer.install()
49+
for (const spy of [downloadSpy, extractSpy]) {
50+
expect(spy).not.toHaveBeenCalled()
51+
}
52+
expect(installationNeededSpy).toHaveBeenCalled()
4953
expect(process.env.DEVELOPER_DIR).toBe(toolchain.xcodePath)
5054
})
5155

@@ -131,18 +135,25 @@ describe('macOS toolchain installation verification', () => {
131135
const identifier = 'org.swift.581202305171a'
132136
jest.spyOn(toolCache, 'find').mockReturnValue(cached)
133137
jest.spyOn(exec, 'exec').mockResolvedValue(0)
138+
jest.spyOn(cache, 'saveCache').mockResolvedValue(1)
134139
const downloadSpy = jest.spyOn(toolCache, 'downloadTool')
135140
const extractSpy = jest.spyOn(toolCache, 'extractXar')
136141
const deploySpy = jest.spyOn(toolCache, 'extractTar')
137-
const cacheSpy = jest.spyOn(toolCache, 'cacheDir')
142+
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue(cached)
143+
jest.spyOn(core, 'getBooleanInput').mockReturnValue(true)
138144
jest.spyOn(exec, 'exec').mockResolvedValue(0)
145+
jest.spyOn(exec, 'getExecOutput').mockResolvedValue({
146+
exitCode: 0,
147+
stdout: `Apple Swift version 5.9-dev (LLVM fd38736063c15cd, Swift a533c63d783f5b8)\nTarget: arm64-apple-macosx13.0`,
148+
stderr: ''
149+
})
139150
jest.spyOn(fs, 'access').mockResolvedValue()
140151
jest.spyOn(fs, 'readFile').mockResolvedValue('')
141152
jest.spyOn(plist, 'parse').mockReturnValue({CFBundleIdentifier: identifier})
142153
await installer.install()
143154
expect(process.env.PATH?.includes(swiftPath)).toBeTruthy()
144155
expect(process.env.TOOLCHAINS).toBe(identifier)
145-
for (const spy of [downloadSpy, extractSpy, deploySpy, cacheSpy]) {
156+
for (const spy of [downloadSpy, extractSpy, deploySpy]) {
146157
expect(spy).not.toHaveBeenCalled()
147158
}
148159
})

dist/index.js

Lines changed: 39 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/installer/base.ts

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ export type SnapshotForInstaller<Installer> =
1212
? Snapshot
1313
: never
1414

15-
export class NoInstallationNeededError extends Error {}
16-
1715
export abstract class ToolchainInstaller<Snapshot extends ToolchainSnapshot> {
1816
constructor(readonly data: Snapshot) {}
1917

@@ -34,45 +32,41 @@ export abstract class ToolchainInstaller<Snapshot extends ToolchainSnapshot> {
3432
}
3533

3634
async install(arch?: string) {
37-
try {
38-
const key = `${this.data.branch}-${this.data.platform}`
39-
const version = this.version?.raw
40-
let tool: string | undefined
41-
if (version) {
42-
tool = toolCache.find(key, version, arch).trim()
43-
}
44-
if (!tool?.length) {
35+
const key = `${this.data.dir}-${this.data.platform}`
36+
const version = this.version?.raw
37+
let tool: string | undefined
38+
if (version) {
39+
tool = toolCache.find(key, version, arch).trim()
40+
core.debug(`Found tool at "${tool}" in tool cache`)
41+
}
42+
if (!tool?.length) {
43+
const tmpDir = process.env.RUNNER_TEMP || os.tmpdir()
44+
const restore = path.join(tmpDir, 'setup-swift', key)
45+
if (await cache.restoreCache([restore], key)) {
46+
core.debug(`Restored snapshot at "${restore}" from key "${key}"`)
47+
tool = restore
48+
} else {
4549
const resource = await this.download()
4650
const installation = await this.unpack(resource)
47-
if (version) {
48-
tool = await toolCache.cacheDir(installation, key, version, arch)
49-
} else {
50-
core.debug('Proceeding without caching non-versioned snapshot')
51-
tool = installation
52-
}
53-
}
54-
await this.add(tool)
55-
} catch (error) {
56-
if (!(error instanceof NoInstallationNeededError)) {
57-
throw error
51+
core.debug(`Downloaded and installed snapshot at "${installation}"`)
52+
tool = installation
5853
}
5954
}
55+
if (version) {
56+
tool = await toolCache.cacheDir(tool, key, version, arch)
57+
core.debug(`Added to tool cache at "${tool}"`)
58+
}
59+
if (core.getBooleanInput('cache-snapshot')) {
60+
await cache.saveCache([tool], key)
61+
core.debug(`Saved to cache with key "${key}"`)
62+
}
63+
await this.add(tool)
6064
}
6165

6266
protected async download() {
63-
const tmpDir = process.env.RUNNER_TEMP || os.tmpdir()
64-
let resourcePath = path.join(tmpDir, 'setup-swift', this.data.download)
65-
if (!(await cache.restoreCache([resourcePath], this.data.download))) {
66-
const url = `${this.baseUrl}/${this.data.download}`
67-
core.debug(`Downloading snapshot from "${url}"`)
68-
resourcePath = await toolCache.downloadTool(url)
69-
if (core.getBooleanInput('cache-snapshot')) {
70-
await cache.saveCache([resourcePath], this.data.download)
71-
}
72-
} else {
73-
core.debug(`Picked snapshot from cache key "${this.data.download}"`)
74-
}
75-
return resourcePath
67+
const url = `${this.baseUrl}/${this.data.download}`
68+
core.debug(`Downloading snapshot from "${url}"`)
69+
return await toolCache.downloadTool(url)
7670
}
7771

7872
protected abstract unpack(resource: string): Promise<string>

src/installer/xcode.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as core from '@actions/core'
44
import {exec} from '@actions/exec'
55
import * as toolCache from '@actions/tool-cache'
66
import * as plist from 'plist'
7-
import {ToolchainInstaller, NoInstallationNeededError} from './base'
7+
import {ToolchainInstaller} from './base'
88
import {XcodeToolchainSnapshot} from '../snapshot'
99

1010
export class XcodeToolchainInstaller extends ToolchainInstaller<XcodeToolchainSnapshot> {
@@ -41,10 +41,14 @@ export class XcodeToolchainInstaller extends ToolchainInstaller<XcodeToolchainSn
4141
return this.data.dir !== `swift-${version}-RELEASE`
4242
}
4343

44-
protected async download() {
44+
async install(arch?: string | undefined) {
4545
if (!(await this.isInstallationNeeded())) {
46-
throw new NoInstallationNeededError('Bundled with xcode')
46+
return
4747
}
48+
await super.install(arch)
49+
}
50+
51+
protected async download() {
4852
const toolchain = await super.download()
4953
core.debug(`Checking package signature for "${toolchain}"`)
5054
await exec('pkgutil', ['--check-signature', toolchain])

0 commit comments

Comments
 (0)