Skip to content

Commit

Permalink
perf: installation will be saved in cache (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
soumyamahunt authored Sep 8, 2023
1 parent 32576cd commit 5bd49ae
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 112 deletions.
8 changes: 5 additions & 3 deletions __tests__/installer/linux.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,15 @@ describe('linux toolchain installation verification', () => {
const cached = path.resolve('tool', 'cached', 'path')
const swiftPath = path.join(cached, 'usr', 'bin')
jest.spyOn(toolCache, 'find').mockReturnValue(cached)
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue(cached)
jest.spyOn(cache, 'saveCache').mockResolvedValue(1)
jest.spyOn(core, 'getBooleanInput').mockReturnValue(true)
jest.spyOn(exec, 'exec').mockResolvedValue(0)
const downloadSpy = jest.spyOn(toolCache, 'downloadTool')
const extractSpy = jest.spyOn(toolCache, 'extractTar')
const cacheSpy = jest.spyOn(toolCache, 'cacheDir')
jest.spyOn(exec, 'exec').mockResolvedValue(0)
await installer.install()
expect(process.env.PATH?.includes(swiftPath)).toBeTruthy()
for (const spy of [downloadSpy, extractSpy, cacheSpy]) {
for (const spy of [downloadSpy, extractSpy]) {
expect(spy).not.toHaveBeenCalled()
}
})
Expand Down
29 changes: 3 additions & 26 deletions __tests__/installer/windows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,32 +58,6 @@ describe('windows toolchain installation verification', () => {
])
})

it('tests download with caching', async () => {
const installer = new WindowsToolchainInstaller(toolchain)
expect(installer['version']).toStrictEqual(parseSemVer('5.8'))
expect(installer['baseUrl']).toBe(
'https://download.swift.org/swift-5.8-release/windows10/swift-5.8-RELEASE'
)

const download = path.resolve('tool', 'download', 'path')
process.env.VSWHERE_PATH = path.join('C:', 'Visual Studio')
jest.spyOn(fs, 'access').mockResolvedValue()
jest.spyOn(fs, 'rename').mockResolvedValue()
jest.spyOn(core, 'getBooleanInput').mockReturnValue(true)
jest.spyOn(exec, 'exec').mockResolvedValue(0)
jest.spyOn(exec, 'getExecOutput').mockResolvedValue({
exitCode: 0,
stdout: JSON.stringify([visualStudio]),
stderr: ''
})
jest.spyOn(cache, 'restoreCache').mockResolvedValue(undefined)
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`)
expect(cacheSpy).toHaveBeenCalled()
})

it('tests download without caching', async () => {
const installer = new WindowsToolchainInstaller(toolchain)
expect(installer['version']).toStrictEqual(parseSemVer('5.8'))
Expand Down Expand Up @@ -204,7 +178,10 @@ describe('windows toolchain installation verification', () => {
.mockResolvedValue(visualStudio)
jest.spyOn(fs, 'access').mockRejectedValue(new Error())
jest.spyOn(fs, 'copyFile').mockResolvedValue()
jest.spyOn(core, 'getBooleanInput').mockReturnValue(true)
jest.spyOn(toolCache, 'find').mockReturnValue(cached)
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue(cached)
jest.spyOn(cache, 'saveCache').mockResolvedValue(1)
jest.spyOn(exec, 'exec').mockResolvedValue(0)
jest.spyOn(exec, 'getExecOutput').mockResolvedValue({
exitCode: 0,
Expand Down
23 changes: 17 additions & 6 deletions __tests__/installer/xcode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import * as toolCache from '@actions/tool-cache'
import {coerce as parseSemVer} from 'semver'
import * as plist from 'plist'
import {XcodeToolchainInstaller} from '../../src/installer/xcode'
import {NoInstallationNeededError} from '../../src/installer/base'

jest.mock('plist')

Expand Down Expand Up @@ -43,9 +42,14 @@ 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['download']()).rejects.toMatchObject(
new NoInstallationNeededError('Bundled with xcode')
)
const installationNeededSpy = jest.spyOn(installer, 'isInstallationNeeded')
const downloadSpy = jest.spyOn(toolCache, 'downloadTool')
const extractSpy = jest.spyOn(toolCache, 'extractXar')
await installer.install()
for (const spy of [downloadSpy, extractSpy]) {
expect(spy).not.toHaveBeenCalled()
}
expect(installationNeededSpy).toHaveBeenCalled()
expect(process.env.DEVELOPER_DIR).toBe(toolchain.xcodePath)
})

Expand Down Expand Up @@ -131,18 +135,25 @@ describe('macOS toolchain installation verification', () => {
const identifier = 'org.swift.581202305171a'
jest.spyOn(toolCache, 'find').mockReturnValue(cached)
jest.spyOn(exec, 'exec').mockResolvedValue(0)
jest.spyOn(cache, 'saveCache').mockResolvedValue(1)
const downloadSpy = jest.spyOn(toolCache, 'downloadTool')
const extractSpy = jest.spyOn(toolCache, 'extractXar')
const deploySpy = jest.spyOn(toolCache, 'extractTar')
const cacheSpy = jest.spyOn(toolCache, 'cacheDir')
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue(cached)
jest.spyOn(core, 'getBooleanInput').mockReturnValue(true)
jest.spyOn(exec, 'exec').mockResolvedValue(0)
jest.spyOn(exec, 'getExecOutput').mockResolvedValue({
exitCode: 0,
stdout: `Apple Swift version 5.9-dev (LLVM fd38736063c15cd, Swift a533c63d783f5b8)\nTarget: arm64-apple-macosx13.0`,
stderr: ''
})
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]) {
for (const spy of [downloadSpy, extractSpy, deploySpy]) {
expect(spy).not.toHaveBeenCalled()
}
})
Expand Down
79 changes: 39 additions & 40 deletions dist/index.js

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

62 changes: 28 additions & 34 deletions src/installer/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ export type SnapshotForInstaller<Installer> =
? Snapshot
: never

export class NoInstallationNeededError extends Error {}

export abstract class ToolchainInstaller<Snapshot extends ToolchainSnapshot> {
constructor(readonly data: Snapshot) {}

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

async install(arch?: string) {
try {
const key = `${this.data.branch}-${this.data.platform}`
const version = this.version?.raw
let tool: string | undefined
if (version) {
tool = toolCache.find(key, version, arch).trim()
}
if (!tool?.length) {
const key = `${this.data.dir}-${this.data.platform}`
const version = this.version?.raw
let tool: string | undefined
if (version) {
tool = toolCache.find(key, version, arch).trim()
core.debug(`Found tool at "${tool}" in tool cache`)
}
if (!tool?.length) {
const tmpDir = process.env.RUNNER_TEMP || os.tmpdir()
const restore = path.join(tmpDir, 'setup-swift', key)
if (await cache.restoreCache([restore], key)) {
core.debug(`Restored snapshot at "${restore}" from key "${key}"`)
tool = restore
} else {
const resource = await this.download()
const installation = await this.unpack(resource)
if (version) {
tool = await toolCache.cacheDir(installation, key, version, arch)
} else {
core.debug('Proceeding without caching non-versioned snapshot')
tool = installation
}
}
await this.add(tool)
} catch (error) {
if (!(error instanceof NoInstallationNeededError)) {
throw error
core.debug(`Downloaded and installed snapshot at "${installation}"`)
tool = installation
}
}
if (version) {
tool = await toolCache.cacheDir(tool, key, version, arch)
core.debug(`Added to tool cache at "${tool}"`)
}
if (core.getBooleanInput('cache-snapshot')) {
await cache.saveCache([tool], key)
core.debug(`Saved to cache with key "${key}"`)
}
await this.add(tool)
}

protected async download() {
const tmpDir = process.env.RUNNER_TEMP || os.tmpdir()
let resourcePath = path.join(tmpDir, 'setup-swift', this.data.download)
if (!(await cache.restoreCache([resourcePath], this.data.download))) {
const url = `${this.baseUrl}/${this.data.download}`
core.debug(`Downloading snapshot from "${url}"`)
resourcePath = await toolCache.downloadTool(url)
if (core.getBooleanInput('cache-snapshot')) {
await cache.saveCache([resourcePath], this.data.download)
}
} else {
core.debug(`Picked snapshot from cache key "${this.data.download}"`)
}
return resourcePath
const url = `${this.baseUrl}/${this.data.download}`
core.debug(`Downloading snapshot from "${url}"`)
return await toolCache.downloadTool(url)
}

protected abstract unpack(resource: string): Promise<string>
Expand Down
10 changes: 7 additions & 3 deletions src/installer/xcode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as core from '@actions/core'
import {exec} from '@actions/exec'
import * as toolCache from '@actions/tool-cache'
import * as plist from 'plist'
import {ToolchainInstaller, NoInstallationNeededError} from './base'
import {ToolchainInstaller} from './base'
import {XcodeToolchainSnapshot} from '../snapshot'

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

protected async download() {
async install(arch?: string | undefined) {
if (!(await this.isInstallationNeeded())) {
throw new NoInstallationNeededError('Bundled with xcode')
return
}
await super.install(arch)
}

protected async download() {
const toolchain = await super.download()
core.debug(`Checking package signature for "${toolchain}"`)
await exec('pkgutil', ['--check-signature', toolchain])
Expand Down

0 comments on commit 5bd49ae

Please sign in to comment.