setup-go/src/installer.ts
Ivan Trubach dcc74252e8 Add support for major release selection
This commit adds 'latest' version alias that resolves to the
latest available release.

The getPossibleVersions function filters available releases using
startsWith(version), so calling getLatestVersion(version) with an
empty string will return the latest release.

Closes #31
2019-12-15 00:12:27 +03:00

227 lines
6.1 KiB
TypeScript

// Load tempDirectory before it gets wiped by tool-cache
let tempDirectory = process.env['RUNNER_TEMPDIRECTORY'] || '';
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import * as os from 'os';
import * as path from 'path';
import * as util from 'util';
import * as semver from 'semver';
import * as restm from 'typed-rest-client/RestClient';
let osPlat: string = os.platform();
let osArch: string = os.arch();
if (!tempDirectory) {
let baseLocation;
if (process.platform === 'win32') {
// On windows use the USERPROFILE env variable
baseLocation = process.env['USERPROFILE'] || 'C:\\';
} else {
if (process.platform === 'darwin') {
baseLocation = '/Users';
} else {
baseLocation = '/home';
}
}
tempDirectory = path.join(baseLocation, 'actions', 'temp');
}
export async function getGo(version: string) {
const selected = await determineVersion(version);
if (selected) {
version = selected;
}
// check cache
let toolPath: string;
toolPath = tc.find('go', normalizeVersion(version));
if (!toolPath) {
// download, extract, cache
toolPath = await acquireGo(version);
core.debug('Go tool is cached under ' + toolPath);
}
setGoEnvironmentVariables(toolPath);
toolPath = path.join(toolPath, 'bin');
//
// prepend the tools path. instructs the agent to prepend for future tasks
//
core.addPath(toolPath);
}
async function acquireGo(version: string): Promise<string> {
//
// Download - a tool installer intimately knows how to get the tool (and construct urls)
//
let fileName: string = getFileName(version);
let downloadUrl: string = getDownloadUrl(fileName);
core.debug('Downloading Go from: ' + downloadUrl);
let downloadPath: string | null = null;
try {
downloadPath = await tc.downloadTool(downloadUrl);
} catch (error) {
core.debug(error);
throw `Failed to download version ${version}: ${error}`;
}
//
// Extract
//
let extPath: string = tempDirectory;
if (!extPath) {
throw new Error('Temp directory not set');
}
if (osPlat == 'win32') {
extPath = await tc.extractZip(downloadPath);
} else {
extPath = await tc.extractTar(downloadPath);
}
//
// Install into the local tool cache - node extracts with a root folder that matches the fileName downloaded
//
const toolRoot = path.join(extPath, 'go');
version = normalizeVersion(version);
return await tc.cacheDir(toolRoot, 'go', version);
}
function getFileName(version: string): string {
const arches: {[arch: string]: string} = {
x64: 'amd64',
arm: 'armv6l',
arm64: 'arm64',
default: '386'
};
const platform: string = osPlat == 'win32' ? 'windows' : osPlat;
const arch: string = arches[osArch] || arches['default'];
const ext: string = osPlat == 'win32' ? 'zip' : 'tar.gz';
const filename: string = util.format(
'go%s.%s-%s.%s',
version,
platform,
arch,
ext
);
return filename;
}
function getDownloadUrl(filename: string): string {
return util.format('https://storage.googleapis.com/golang/%s', filename);
}
function setGoEnvironmentVariables(goRoot: string) {
core.exportVariable('GOROOT', goRoot);
const goPath: string = process.env['GOPATH'] || '';
const goBin: string = process.env['GOBIN'] || '';
// set GOPATH and GOBIN as user value
if (goPath) {
core.exportVariable('GOPATH', goPath);
}
if (goBin) {
core.exportVariable('GOBIN', goBin);
}
}
// This function is required to convert the version 1.10 to 1.10.0.
// Because caching utility accept only sementic version,
// which have patch number as well.
function normalizeVersion(version: string): string {
const versionPart = version.split('.');
if (versionPart[1] == null) {
//append minor and patch version if not available
return version.concat('.0.0');
} else {
// handle beta and rc: 1.10beta1 => 1.10.0-beta1, 1.10rc1 => 1.10.0-rc1
if (versionPart[1].includes('beta') || versionPart[1].includes('rc')) {
versionPart[1] = versionPart[1]
.replace('beta', '.0-beta')
.replace('rc', '.0-rc');
return versionPart.join('.');
}
}
if (versionPart[2] == null) {
//append patch version if not available
return version.concat('.0');
} else {
// handle beta and rc: 1.8.5beta1 => 1.8.5-beta1, 1.8.5rc1 => 1.8.5-rc1
if (versionPart[2].includes('beta') || versionPart[2].includes('rc')) {
versionPart[2] = versionPart[2]
.replace('beta', '-beta')
.replace('rc', '-rc');
return versionPart.join('.');
}
}
return version;
}
async function determineVersion(version: string): Promise<string> {
if (version == 'latest') {
return await getLatestVersion('');
}
if (!version.endsWith('.x')) {
const versionPart = version.split('.');
if (versionPart[1] == null || versionPart[2] == null) {
return await getLatestVersion(version.concat('.x'));
} else {
return version;
}
}
return await getLatestVersion(version);
}
async function getLatestVersion(version: string): Promise<string> {
// clean .x syntax: 1.10.x -> 1.10
const trimmedVersion = version.slice(0, version.length - 2);
const versions = await getPossibleVersions(trimmedVersion);
core.debug(`evaluating ${versions.length} versions`);
if (versions.length === 0) {
throw new Error('unable to get latest version');
}
core.debug(`matched: ${versions[0]}`);
return versions[0];
}
interface IGoRef {
version: string;
}
async function getAvailableVersions(): Promise<string[]> {
let rest: restm.RestClient = new restm.RestClient('setup-go');
let tags: IGoRef[] =
(await rest.get<IGoRef[]>('https://golang.org/dl/?mode=json&include=all'))
.result || [];
return tags.map(tag => tag.version.replace('go', ''));
}
async function getPossibleVersions(version: string): Promise<string[]> {
const versions = await getAvailableVersions();
const possibleVersions = versions.filter(v => v.startsWith(version));
const versionMap = new Map();
possibleVersions.forEach(v => versionMap.set(normalizeVersion(v), v));
return Array.from(versionMap.keys())
.sort(semver.rcompare)
.map(v => versionMap.get(v));
}