2019-09-09 10:27:23 -07:00
// Load tempDirectory before it gets wiped by tool-cache
import * as core from '@actions/core' ;
import * as exec from '@actions/exec' ;
import * as io from '@actions/io' ;
2022-09-27 14:47:12 +02:00
import * as hc from '@actions/http-client' ;
2019-09-09 10:27:23 -07:00
import { chmodSync } from 'fs' ;
2022-09-29 17:45:25 +02:00
import { readdir } from 'fs/promises' ;
2022-09-27 14:47:12 +02:00
import path from 'path' ;
import semver from 'semver' ;
2022-09-29 17:45:25 +02:00
import { IS_LINUX , IS_WINDOWS , IS_MAC } from './utils' ;
2022-09-27 14:47:12 +02:00
import { QualityOptions } from './setup-dotnet' ;
export interface DotnetVersion {
type : string ;
value : string ;
qualityFlag : boolean ;
}
2019-09-09 10:27:23 -07:00
2022-09-27 14:47:12 +02:00
export class DotnetVersionResolver {
private inputVersion : string ;
private resolvedArgument : DotnetVersion ;
2020-04-04 19:23:59 +02:00
2019-09-09 10:27:23 -07:00
constructor ( version : string ) {
2022-09-27 14:47:12 +02:00
this . inputVersion = version . trim ();
this . resolvedArgument = { type : '' , value : '' , qualityFlag : false };
2020-04-04 18:05:12 +02:00
}
2022-09-27 14:47:12 +02:00
private async resolveVersionInput () : Promise < void > {
if ( ! semver . validRange ( this . inputVersion )) {
throw new Error (
`'dotnet-version' was supplied in invalid format: ${ this . inputVersion } ! Supported syntax: A.B.C, A.B, A.B.x, A, A.x`
);
}
if ( semver . valid ( this . inputVersion )) {
this . resolvedArgument . type = 'version' ;
this . resolvedArgument . value = this . inputVersion ;
} else {
const [ major , minor ] = this . inputVersion . split ( '.' );
if ( this . isNumericTag ( major )) {
this . resolvedArgument . type = 'channel' ;
if ( this . isNumericTag ( minor )) {
this . resolvedArgument . value = ` ${ major } . ${ minor } ` ;
} else {
const httpClient = new hc . HttpClient ( 'actions/setup-dotnet' , [], {
allowRetries : true ,
maxRetries : 3
});
this . resolvedArgument . value = await this . getLatestVersion (
httpClient ,
[ major , minor ]
);
}
}
this . resolvedArgument . qualityFlag = + major >= 6 ? true : false ;
2020-04-05 15:37:29 +02:00
}
}
2022-09-27 14:47:12 +02:00
private isNumericTag ( versionTag ) : boolean {
return /^\d+$/ . test ( versionTag );
2019-09-09 10:27:23 -07:00
}
2022-09-27 14:47:12 +02:00
public async createDotNetVersion () : Promise < {
type : string ;
value : string ;
qualityFlag : boolean ;
} > {
await this . resolveVersionInput ();
if ( ! this . resolvedArgument . type ) {
return this . resolvedArgument ;
2019-09-09 10:27:23 -07:00
}
if ( IS_WINDOWS ) {
2022-09-27 14:47:12 +02:00
this . resolvedArgument . type =
this . resolvedArgument . type === 'channel' ? '-Channel' : '-Version' ;
2019-09-09 10:27:23 -07:00
} else {
2022-09-27 14:47:12 +02:00
this . resolvedArgument . type =
this . resolvedArgument . type === 'channel' ? '--channel' : '--version' ;
}
return this . resolvedArgument ;
}
2020-09-15 09:36:09 -07:00
2022-09-27 14:47:12 +02:00
private async getLatestVersion (
httpClient : hc.HttpClient ,
versionParts : string []
) : Promise < string > {
const response = await httpClient . getJson < any >(
DotnetVersionResolver . DotNetCoreIndexUrl
);
const result = response . result || {};
let releasesInfo : any [] = result [ 'releases-index' ];
2019-09-09 10:27:23 -07:00
2022-09-27 14:47:12 +02:00
let releaseInfo = releasesInfo . find ( info => {
let sdkParts : string [] = info [ 'channel-version' ]. split ( '.' );
return sdkParts [ 0 ] === versionParts [ 0 ];
});
2020-09-15 09:36:09 -07:00
2022-09-27 14:47:12 +02:00
if ( ! releaseInfo ) {
throw new Error (
`Could not find info for version ${ versionParts . join ( '.' ) } at ${
DotnetVersionResolver . DotNetCoreIndexUrl
} `
);
2019-09-09 10:27:23 -07:00
}
2022-09-27 14:47:12 +02:00
return releaseInfo [ 'channel-version' ];
2021-11-23 05:03:56 -05:00
}
2022-09-27 14:47:12 +02:00
static DotNetCoreIndexUrl : string =
'https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/releases-index.json' ;
}
export class DotnetCoreInstaller {
private version : string ;
private quality : QualityOptions ;
private static readonly installationDirectoryWindows = path . join (
process . env [ 'PROGRAMFILES' ] + '' ,
'dotnet'
);
private static readonly installationDirectoryLinux = '/usr/share/dotnet' ;
2022-09-29 17:45:25 +02:00
private static readonly installationDirectoryMac = path . join (
process . env [ 'HOME' ] + '' ,
'.dotnet'
);
2022-09-27 14:47:12 +02:00
2021-11-23 05:03:56 -05:00
static addToPath() {
2020-09-15 09:36:09 -07:00
if ( process . env [ 'DOTNET_INSTALL_DIR' ]) {
core . addPath ( process . env [ 'DOTNET_INSTALL_DIR' ]);
2020-09-24 08:26:00 -07:00
core . exportVariable ( 'DOTNET_ROOT' , process . env [ 'DOTNET_INSTALL_DIR' ]);
2020-09-15 09:36:09 -07:00
} else {
if ( IS_WINDOWS ) {
2022-09-27 14:47:12 +02:00
core . addPath ( DotnetCoreInstaller . installationDirectoryWindows );
core . exportVariable (
'DOTNET_ROOT' ,
DotnetCoreInstaller . installationDirectoryWindows
2020-09-15 09:36:09 -07:00
);
2022-09-27 14:47:12 +02:00
} else if ( IS_LINUX ) {
core . addPath ( DotnetCoreInstaller . installationDirectoryLinux );
2020-09-15 09:36:09 -07:00
core . exportVariable (
'DOTNET_ROOT' ,
2022-09-27 14:47:12 +02:00
DotnetCoreInstaller . installationDirectoryLinux
2020-09-15 09:36:09 -07:00
);
} else {
// This is the default set in install-dotnet.sh
2022-09-29 17:45:25 +02:00
core . addPath ( DotnetCoreInstaller . installationDirectoryMac );
2020-09-24 08:26:00 -07:00
core . exportVariable (
'DOTNET_ROOT' ,
2022-09-29 17:45:25 +02:00
DotnetCoreInstaller . installationDirectoryMac
2020-09-24 08:26:00 -07:00
);
2020-09-15 09:36:09 -07:00
}
2019-09-09 10:27:23 -07:00
}
2022-09-27 14:47:12 +02:00
}
2019-09-09 10:27:23 -07:00
2022-09-27 14:47:12 +02:00
constructor ( version : string , quality : QualityOptions ) {
this . version = version ;
this . quality = quality ;
2019-09-09 10:27:23 -07:00
}
2022-09-27 14:47:12 +02:00
private setQuality (
dotnetVersion : DotnetVersion ,
scriptArguments : string []
) : void {
const option = IS_WINDOWS ? '-Quality' : '--quality' ;
if ( dotnetVersion . qualityFlag ) {
scriptArguments . push ( option , this . quality );
} else {
core . warning (
`'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${ this . version } . 'dotnet-quality' input is ignored.`
);
2019-09-09 10:27:23 -07:00
}
2022-09-27 14:47:12 +02:00
}
2019-09-09 10:27:23 -07:00
2022-09-29 17:45:25 +02:00
public async installDotnet () : Promise < string > {
2022-09-27 14:47:12 +02:00
const windowsDefaultOptions = [
'-NoLogo' ,
'-Sta' ,
'-NoProfile' ,
'-NonInteractive' ,
'-ExecutionPolicy' ,
'Unrestricted' ,
'-Command'
];
const scriptName = IS_WINDOWS ? 'install-dotnet.ps1' : 'install-dotnet.sh' ;
const escapedScript = path
. join ( __dirname , '..' , 'externals' , scriptName )
. replace ( /'/g , "''" );
let scriptArguments : string [];
let scriptPath = '' ;
const versionResolver = new DotnetVersionResolver ( this . version );
const dotnetVersion = await versionResolver . createDotNetVersion ();
2019-11-08 17:15:28 +01:00
2022-09-27 14:47:12 +02:00
if ( IS_WINDOWS ) {
scriptArguments = [ '&' , `' ${ escapedScript } '` ];
2019-09-09 10:27:23 -07:00
2022-09-27 14:47:12 +02:00
if ( dotnetVersion . type ) {
scriptArguments . push ( dotnetVersion . type , dotnetVersion . value );
}
2020-04-04 19:23:59 +02:00
2022-09-27 14:47:12 +02:00
if ( this . quality ) {
this . setQuality ( dotnetVersion , scriptArguments );
}
2020-04-04 19:23:59 +02:00
2022-09-27 14:47:12 +02:00
if ( process . env [ 'https_proxy' ] != null ) {
scriptArguments . push ( `-ProxyAddress ${ process . env [ 'https_proxy' ] } ` );
}
// This is not currently an option
if ( process . env [ 'no_proxy' ] != null ) {
scriptArguments . push ( `-ProxyBypassList ${ process . env [ 'no_proxy' ] } ` );
}
2020-04-04 18:05:12 +02:00
2022-09-27 14:47:12 +02:00
scriptArguments . push (
2022-09-29 17:45:25 +02:00
'-InstallDir' ,
`' ${ DotnetCoreInstaller . installationDirectoryWindows } '`
2021-03-17 12:38:57 +03:00
);
2022-09-27 14:47:12 +02:00
// process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used
scriptPath =
( await io . which ( 'pwsh' , false )) || ( await io . which ( 'powershell' , true ));
2022-09-29 17:45:25 +02:00
scriptArguments = windowsDefaultOptions . concat ( scriptArguments );
2022-09-27 14:47:12 +02:00
} else {
chmodSync ( escapedScript , '777' );
scriptPath = await io . which ( escapedScript , true );
scriptArguments = [];
2019-09-09 10:27:23 -07:00
2022-09-27 14:47:12 +02:00
if ( dotnetVersion . type ) {
scriptArguments . push ( dotnetVersion . type , dotnetVersion . value );
}
2020-09-24 08:26:00 -07:00
2022-09-27 14:47:12 +02:00
if ( this . quality ) {
this . setQuality ( dotnetVersion , scriptArguments );
2019-11-08 17:15:28 +01:00
}
2020-09-24 08:26:00 -07:00
2022-09-27 14:47:12 +02:00
if ( IS_LINUX ) {
scriptArguments . push (
'--install-dir' ,
DotnetCoreInstaller . installationDirectoryLinux
);
}
2022-09-29 17:45:25 +02:00
if ( IS_MAC ) {
scriptArguments . push (
'--install-dir' ,
DotnetCoreInstaller . installationDirectoryMac
);
}
2019-11-08 17:15:28 +01:00
}
2022-09-27 14:47:12 +02:00
const { exitCode , stdout } = await exec . getExecOutput (
`" ${ scriptPath } "` ,
scriptArguments ,
{ ignoreReturnCode : true }
);
if ( exitCode ) {
throw new Error ( `Failed to install dotnet ${ exitCode } . ${ stdout } ` );
2021-11-23 10:58:49 -03:00
}
2022-09-29 17:45:25 +02:00
return this . outputDotnetVersion (
dotnetVersion . value ,
scriptArguments [ scriptArguments . length - 1 ]
);
}
private async outputDotnetVersion (
version ,
installationPath
) : Promise < string > {
let versionsOnRunner : string [] = await readdir (
path . join ( installationPath . replace ( /'/g , '' ), 'sdk' )
);
let installedVersion = semver . maxSatisfying ( versionsOnRunner , version , {
includePrerelease : true
}) ! ;
return installedVersion ;
2019-09-09 10:27:23 -07:00
}
}