Merge branch 'support-latest-version' of https://github.com/litetex/setup-dotnet-forked into litetex-support-latest-version

This commit is contained in:
Zachary Eisinger 2020-05-29 14:04:29 -07:00
commit 40390722c2
4 changed files with 267 additions and 36 deletions

View File

@ -20,7 +20,7 @@ steps:
- uses: actions/checkout@master - uses: actions/checkout@master
- uses: actions/setup-dotnet@v1 - uses: actions/setup-dotnet@v1
with: with:
dotnet-version: '3.1.100' # SDK Version to use. dotnet-version: '3.1.x' # SDK Version to use; x will use the latest version of the 3.1 channel
- run: dotnet build <my project> - run: dotnet build <my project>
``` ```
@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-16.04 runs-on: ubuntu-16.04
strategy: strategy:
matrix: matrix:
dotnet: [ '2.2.103', '3.0.100', '3.1.100' ] dotnet: [ '2.2.103', '3.0', '3.1.x' ]
name: Dotnet ${{ matrix.dotnet }} sample name: Dotnet ${{ matrix.dotnet }} sample
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@master
@ -49,7 +49,7 @@ steps:
# Authenticates packages to push to GPR # Authenticates packages to push to GPR
- uses: actions/setup-dotnet@v1 - uses: actions/setup-dotnet@v1
with: with:
dotnet-version: '3.1.100' # SDK Version to use. dotnet-version: '3.1.x' # SDK Version to use.
source-url: https://nuget.pkg.github.com/<owner>/index.json source-url: https://nuget.pkg.github.com/<owner>/index.json
env: env:
NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}

View File

@ -4,6 +4,8 @@ import os = require('os');
import path = require('path'); import path = require('path');
import hc = require('@actions/http-client'); import hc = require('@actions/http-client');
import each from 'jest-each';
const toolDir = path.join(__dirname, 'runner', 'tools'); const toolDir = path.join(__dirname, 'runner', 'tools');
const tempDir = path.join(__dirname, 'runner', 'temp'); const tempDir = path.join(__dirname, 'runner', 'temp');
@ -14,6 +16,61 @@ import * as installer from '../src/installer';
const IS_WINDOWS = process.platform === 'win32'; const IS_WINDOWS = process.platform === 'win32';
describe('version tests', () => {
each(['3.1.999', '3.1.101-preview.3']).test(
"Exact version '%s' should be the same",
vers => {
let versInfo = new installer.DotNetVersionInfo(vers);
expect(versInfo.isExactVersion()).toBe(true);
expect(versInfo.version()).toBe(vers);
}
);
each([['3.1.x', '3.1'], ['1.1.*', '1.1'], ['2.0', '2.0']]).test(
"Generic version '%s' should be '%s'",
(vers, resVers) => {
let versInfo = new installer.DotNetVersionInfo(vers);
expect(versInfo.isExactVersion()).toBe(false);
expect(versInfo.version()).toBe(resVers);
}
);
each([
'',
'.',
'..',
' . ',
'. ',
' .',
' . . ',
' .. ',
' . ',
'-1.-1',
'-1',
'-1.-1.-1',
'..3',
'1..3',
'1..',
'.2.3',
'.2.x',
'1',
'2.x',
'*.*.1',
'*.1',
'*.',
'1.2.',
'1.2.-abc',
'a.b',
'a.b.c',
'a.b.c-preview',
' 0 . 1 . 2 '
]).test("Malformed version '%s' should throw", vers => {
expect(() => new installer.DotNetVersionInfo(vers)).toThrow();
});
});
describe('installer tests', () => { describe('installer tests', () => {
beforeAll(async () => { beforeAll(async () => {
await io.rmRF(toolDir); await io.rmRF(toolDir);
@ -29,6 +86,51 @@ describe('installer tests', () => {
} }
}, 100000); }, 100000);
it('Resolving a normal generic version works', async () => {
const dotnetInstaller = new installer.DotnetCoreInstaller('3.1.x');
let versInfo = await dotnetInstaller.resolveInfos(
['win-x64'],
new installer.DotNetVersionInfo('3.1.x')
);
expect(versInfo.resolvedVersion.startsWith('3.1.'));
}, 100000);
it('Resolving a nonexistent generic version fails', async () => {
const dotnetInstaller = new installer.DotnetCoreInstaller('999.1.x');
try {
await dotnetInstaller.resolveInfos(
['win-x64'],
new installer.DotNetVersionInfo('999.1.x')
);
fail();
} catch {
expect(true);
}
}, 100000);
it('Resolving a exact stable version works', async () => {
const dotnetInstaller = new installer.DotnetCoreInstaller('3.1.201');
let versInfo = await dotnetInstaller.resolveInfos(
['win-x64'],
new installer.DotNetVersionInfo('3.1.201')
);
expect(versInfo.resolvedVersion).toBe('3.1.201');
}, 100000);
it('Resolving a exact preview version works', async () => {
const dotnetInstaller = new installer.DotnetCoreInstaller(
'5.0.0-preview.4'
);
let versInfo = await dotnetInstaller.resolveInfos(
['win-x64'],
new installer.DotNetVersionInfo('5.0.0-preview.4')
);
expect(versInfo.resolvedVersion).toBe('5.0.0-preview.4');
}, 100000);
it('Acquires version of dotnet if no matching version is installed', async () => { it('Acquires version of dotnet if no matching version is installed', async () => {
await getDotnet('2.2.205'); await getDotnet('2.2.205');
const dotnetDir = path.join(toolDir, 'dncs', '2.2.205', os.arch()); const dotnetDir = path.join(toolDir, 'dncs', '2.2.205', os.arch());
@ -39,7 +141,7 @@ describe('installer tests', () => {
} else { } else {
expect(fs.existsSync(path.join(dotnetDir, 'dotnet'))).toBe(true); expect(fs.existsSync(path.join(dotnetDir, 'dotnet'))).toBe(true);
} }
}, 100000); }, 400000); //This needs some time to download on "slower" internet connections
it('Acquires version of dotnet if no matching version is installed', async () => { it('Acquires version of dotnet if no matching version is installed', async () => {
const dotnetDir = path.join(toolDir, 'dncs', '2.2.105', os.arch()); const dotnetDir = path.join(toolDir, 'dncs', '2.2.105', os.arch());

View File

@ -6,7 +6,7 @@ branding:
color: green color: green
inputs: inputs:
dotnet-version: dotnet-version:
description: 'SDK version to use. Example: 2.2.104' description: 'SDK version to use. Examples: 2.2.104, 3.1, 3.1.x'
source-url: source-url:
description: 'Optional package source for which to set up authentication. Will consult any existing NuGet.config in the root of the repo and provide a temporary NuGet.config using the NUGET_AUTH_TOKEN environment variable as a ClearTextPassword' description: 'Optional package source for which to set up authentication. Will consult any existing NuGet.config in the root of the repo and provide a temporary NuGet.config using the NUGET_AUTH_TOKEN environment variable as a ClearTextPassword'
owner: owner:

View File

@ -27,31 +27,125 @@ if (!tempDirectory) {
tempDirectory = path.join(baseLocation, 'actions', 'temp'); tempDirectory = path.join(baseLocation, 'actions', 'temp');
} }
/**
* Represents the inputted version information
*/
export class DotNetVersionInfo {
private fullversion: string;
private isExactVersionSet: boolean = false;
constructor(version: string) {
// Check for exact match
if (semver.valid(semver.clean(version) || '') != null) {
this.fullversion = semver.clean(version) as string;
this.isExactVersionSet = true;
return;
}
//Note: No support for previews when using generic
let parts: string[] = version.split('.');
if (parts.length < 2 || parts.length > 3) this.throwInvalidVersionFormat();
if (parts.length == 3 && parts[2] !== 'x' && parts[2] !== '*') {
this.throwInvalidVersionFormat();
}
let major = this.getVersionNumberOrThrow(parts[0]);
let minor = this.getVersionNumberOrThrow(parts[1]);
this.fullversion = major + '.' + minor;
}
private getVersionNumberOrThrow(input: string): number {
try {
if (!input || input.trim() === '') this.throwInvalidVersionFormat();
let number = Number(input);
if (Number.isNaN(number) || number < 0) this.throwInvalidVersionFormat();
return number;
} catch {
this.throwInvalidVersionFormat();
return -1;
}
}
private throwInvalidVersionFormat() {
throw 'Invalid version format! Supported: 1.2.3, 1.2, 1.2.x, 1.2.*';
}
/**
* If true exacatly one version should be resolved
*/
public isExactVersion(): boolean {
return this.isExactVersionSet;
}
public version(): string {
return this.fullversion;
}
}
/**
* Represents a resolved version from the Web-Api
*/
class ResolvedVersionInfo {
downloadUrls: string[];
resolvedVersion: string;
constructor(downloadUrls: string[], resolvedVersion: string) {
if (downloadUrls.length === 0) {
throw 'DownloadUrls can not be empty';
}
if (!resolvedVersion) {
throw 'Resolved version is invalid';
}
this.downloadUrls = downloadUrls;
this.resolvedVersion = resolvedVersion;
}
}
export class DotnetCoreInstaller { export class DotnetCoreInstaller {
constructor(version: string) { constructor(version: string) {
if (semver.valid(semver.clean(version) || '') == null) { this.versionInfo = new DotNetVersionInfo(version);
throw 'Implicit version not permitted';
}
this.version = version;
this.cachedToolName = 'dncs'; this.cachedToolName = 'dncs';
this.arch = 'x64'; this.arch = 'x64';
} }
public async installDotnet() { public async installDotnet() {
// Check cache // Check cache
let toolPath: string; let toolPath: string = '';
let osSuffixes = await this.detectMachineOS(); let osSuffixes = await this.detectMachineOS();
let parts = osSuffixes[0].split('-'); let parts = osSuffixes[0].split('-');
if (parts.length > 1) { if (parts.length > 1) {
this.arch = parts[1]; this.arch = parts[1];
} }
toolPath = this.getLocalTool();
// If version is not generic -> look up cache
if (this.versionInfo.isExactVersion())
toolPath = this.getLocalTool(this.versionInfo.version());
if (!toolPath) { if (!toolPath) {
// download, extract, cache // download, extract, cache
console.log('Getting a download url', this.version); console.log('Getting a download url', this.versionInfo.version());
let downloadUrls = await this.getDownloadUrls(osSuffixes, this.version); let resolvedVersionInfo = await this.resolveInfos(
toolPath = await this.downloadAndInstall(downloadUrls); osSuffixes,
this.versionInfo
);
//Check if cache exists for resolved version
toolPath = this.getLocalTool(resolvedVersionInfo.resolvedVersion);
if (!toolPath) {
//If not exists install it
toolPath = await this.downloadAndInstall(resolvedVersionInfo);
} else {
console.log('Using cached tool');
}
} else { } else {
console.log('Using cached tool'); console.log('Using cached tool');
} }
@ -63,9 +157,9 @@ export class DotnetCoreInstaller {
core.addPath(toolPath); core.addPath(toolPath);
} }
private getLocalTool(): string { private getLocalTool(version: string): string {
console.log('Checking tool cache'); console.log('Checking tool cache', version);
return tc.find(this.cachedToolName, this.version, this.arch); return tc.find(this.cachedToolName, version, this.arch);
} }
private async detectMachineOS(): Promise<string[]> { private async detectMachineOS(): Promise<string[]> {
@ -141,16 +235,16 @@ export class DotnetCoreInstaller {
return osSuffix; return osSuffix;
} }
private async downloadAndInstall(downloadUrls: string[]) { private async downloadAndInstall(resolvedVersionInfo: ResolvedVersionInfo) {
let downloaded = false; let downloaded = false;
let downloadPath = ''; let downloadPath = '';
for (const url of downloadUrls) { for (const url of resolvedVersionInfo.downloadUrls) {
try { try {
downloadPath = await tc.downloadTool(url); downloadPath = await tc.downloadTool(url);
downloaded = true; downloaded = true;
break; break;
} catch (error) { } catch (error) {
console.log('Could Not Download', url, JSON.stringify(error)); console.log('Could not Download', url, JSON.stringify(error));
} }
} }
@ -169,30 +263,29 @@ export class DotnetCoreInstaller {
let cachedDir = await tc.cacheDir( let cachedDir = await tc.cacheDir(
extPath, extPath,
this.cachedToolName, this.cachedToolName,
this.version, resolvedVersionInfo.resolvedVersion,
this.arch this.arch
); );
console.log('Successfully installed', this.version); console.log('Successfully installed', resolvedVersionInfo.resolvedVersion);
return cachedDir; return cachedDir;
} }
// OsSuffixes - The suffix which is a part of the file name ex- linux-x64, windows-x86 // OsSuffixes - The suffix which is a part of the file name ex- linux-x64, windows-x86
// Type - SDK / Runtime // Type - SDK / Runtime
// Version - Version of the SDK/Runtime // versionInfo - versionInfo of the SDK/Runtime
private async getDownloadUrls( async resolveInfos(
osSuffixes: string[], osSuffixes: string[],
version: string versionInfo: DotNetVersionInfo
): Promise<string[]> { ): Promise<ResolvedVersionInfo> {
let downloadUrls: string[] = [];
const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { const httpClient = new hc.HttpClient('actions/setup-dotnet', [], {
allowRetries: true, allowRetries: true,
maxRetries: 3 maxRetries: 3
}); });
const releasesJsonUrl: string = await this.getReleasesJsonUrl( const releasesJsonUrl: string = await this.getReleasesJsonUrl(
httpClient, httpClient,
version.split('.') versionInfo.version().split('.')
); );
const releasesResponse = await httpClient.getJson<any>(releasesJsonUrl); const releasesResponse = await httpClient.getJson<any>(releasesJsonUrl);
@ -200,13 +293,39 @@ export class DotnetCoreInstaller {
let releasesInfo: any[] = releasesResult['releases']; let releasesInfo: any[] = releasesResult['releases'];
releasesInfo = releasesInfo.filter((releaseInfo: any) => { releasesInfo = releasesInfo.filter((releaseInfo: any) => {
return ( return (
releaseInfo['sdk']['version'] === version || semver.satisfies(
releaseInfo['sdk']['version-display'] === version releaseInfo['sdk']['version'],
versionInfo.version()
) ||
semver.satisfies(
releaseInfo['sdk']['version-display'],
versionInfo.version()
)
); );
}); });
// Exclude versions that are newer than the latest if using not exact
if (!versionInfo.isExactVersion()) {
let latestSdk: string = releasesResult['latest-sdk'];
releasesInfo = releasesInfo.filter((releaseInfo: any) =>
semver.lte(releaseInfo['sdk']['version'], latestSdk)
);
}
// Sort for latest version
releasesInfo = releasesInfo.sort((a, b) =>
semver.rcompare(a['sdk']['version'], b['sdk']['version'])
);
let downloadedVersion: string = '';
let downloadUrls: string[] = [];
if (releasesInfo.length != 0) { if (releasesInfo.length != 0) {
let release = releasesInfo[0]; let release = releasesInfo[0];
downloadedVersion = release['sdk']['version'];
let files: any[] = release['sdk']['files']; let files: any[] = release['sdk']['files'];
files = files.filter((file: any) => { files = files.filter((file: any) => {
if (file['rid'] == osSuffixes[0] || file['rid'] == osSuffixes[1]) { if (file['rid'] == osSuffixes[0] || file['rid'] == osSuffixes[1]) {
@ -225,18 +344,28 @@ export class DotnetCoreInstaller {
} }
} else { } else {
console.log( console.log(
`Could not fetch download information for version ${version}` `Could not fetch download information for version ${versionInfo.version()}`
); );
downloadUrls = await this.getFallbackDownloadUrls(version);
if (versionInfo.isExactVersion()) {
console.log('Using fallback');
downloadUrls = await this.getFallbackDownloadUrls(
versionInfo.version()
);
downloadedVersion = versionInfo.version();
} else {
console.log('Unable to use fallback, version is generic!');
}
} }
if (downloadUrls.length == 0) { if (downloadUrls.length == 0) {
throw `Could not construct download URL. Please ensure that specified version ${version} is valid.`; throw `Could not construct download URL. Please ensure that specified version ${versionInfo.version()}/${downloadedVersion} is valid.`;
} }
core.debug(`Got download urls ${downloadUrls}`); core.debug(`Got download urls ${downloadUrls}`);
return downloadUrls; return new ResolvedVersionInfo(downloadUrls, downloadedVersion);
} }
private async getReleasesJsonUrl( private async getReleasesJsonUrl(
@ -361,7 +490,7 @@ export class DotnetCoreInstaller {
return [primaryUrl, legacyUrl]; return [primaryUrl, legacyUrl];
} }
private version: string; private versionInfo: DotNetVersionInfo;
private cachedToolName: string; private cachedToolName: string;
private arch: string; private arch: string;
} }