2023-03-09 14:43:05 +02: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' ;
import * as hc from '@actions/http-client' ;
import { chmodSync } from 'fs' ;
import { readdir } from 'fs/promises' ;
import path from 'path' ;
import os from 'os' ;
import semver from 'semver' ;
2023-05-12 14:07:46 +02:00
import { IS_WINDOWS , getPlatform } from './utils' ;
2023-03-09 14:43:05 +02:00
import { QualityOptions } from './setup-dotnet' ;
export interface DotnetVersion {
type : string ;
value : string ;
qualityFlag : boolean ;
}
export class DotnetVersionResolver {
private inputVersion : string ;
private resolvedArgument : DotnetVersion ;
constructor ( version : string ) {
this . inputVersion = version . trim ();
this . resolvedArgument = { type : '' , value : '' , qualityFlag : false };
}
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 ;
}
}
private isNumericTag ( versionTag ) : boolean {
return /^\d+$/ . test ( versionTag );
}
2023-05-12 16:28:16 +02:00
public async createDotnetVersion () : Promise < {
2023-03-09 14:43:05 +02:00
type : string ;
value : string ;
qualityFlag : boolean ;
} > {
await this . resolveVersionInput ();
if ( ! this . resolvedArgument . type ) {
return this . resolvedArgument ;
}
if ( IS_WINDOWS ) {
this . resolvedArgument . type =
this . resolvedArgument . type === 'channel' ? '-Channel' : '-Version' ;
} else {
this . resolvedArgument . type =
this . resolvedArgument . type === 'channel' ? '--channel' : '--version' ;
}
return this . resolvedArgument ;
}
private async getLatestVersion (
httpClient : hc.HttpClient ,
versionParts : string []
) : Promise < string > {
const response = await httpClient . getJson < any >(
DotnetVersionResolver . DotNetCoreIndexUrl
);
const result = response . result || {};
const releasesInfo : any [] = result [ 'releases-index' ];
const releaseInfo = releasesInfo . find ( info => {
const sdkParts : string [] = info [ 'channel-version' ]. split ( '.' );
return sdkParts [ 0 ] === versionParts [ 0 ];
});
if ( ! releaseInfo ) {
throw new Error (
`Could not find info for version ${ versionParts . join ( '.' ) } at ${
DotnetVersionResolver . DotNetCoreIndexUrl
} `
);
}
return releaseInfo [ 'channel-version' ];
}
static DotNetCoreIndexUrl =
'https://dotnetcli.azureedge.net/dotnet/release-metadata/releases-index.json' ;
}
2023-05-12 16:28:16 +02:00
export class DotnetInstallScript {
private scriptName = IS_WINDOWS ? 'install-dotnet.ps1' : 'install-dotnet.sh' ;
private escapedScript : string ;
private scriptArguments : string [] = [];
private scriptPath = '' ;
private scriptReady : Promise < void >;
2023-03-09 14:43:05 +02:00
2023-05-12 16:28:16 +02:00
constructor () {
this . escapedScript = path
. join ( __dirname , '..' , 'externals' , this . scriptName )
. replace ( /'/g , "''" );
2023-05-24 15:22:01 +02:00
2023-05-12 16:28:16 +02:00
this . scriptReady = IS_WINDOWS
? this . setupScriptPowershell ()
: this . setupScriptBash ();
}
private async setupScriptPowershell() {
this . scriptArguments = [
'-NoLogo' ,
'-Sta' ,
'-NoProfile' ,
'-NonInteractive' ,
'-ExecutionPolicy' ,
'Unrestricted' ,
'-Command'
];
this . scriptArguments . push ( '&' , `' ${ this . escapedScript } '` );
if ( process . env [ 'https_proxy' ] != null ) {
this . scriptArguments . push ( `-ProxyAddress ${ process . env [ 'https_proxy' ] } ` );
}
// This is not currently an option
if ( process . env [ 'no_proxy' ] != null ) {
this . scriptArguments . push ( `-ProxyBypassList ${ process . env [ 'no_proxy' ] } ` );
}
2023-05-24 15:22:01 +02:00
this . scriptPath =
( await io . which ( 'pwsh' , false )) || ( await io . which ( 'powershell' , true ));
2023-05-12 16:28:16 +02:00
}
private async setupScriptBash() {
chmodSync ( this . escapedScript , '777' );
this . scriptPath = await io . which ( this . escapedScript , true );
2023-03-09 14:43:05 +02:00
}
2023-05-12 16:28:16 +02:00
public useArguments (... args : string []) {
this . scriptArguments . push (... args );
return this ;
2023-03-09 14:43:05 +02:00
}
2023-05-12 16:28:16 +02:00
public useVersion ( dotnetVersion : DotnetVersion , quality? : QualityOptions ) {
if ( dotnetVersion . type ) {
this . useArguments ( dotnetVersion . type , dotnetVersion . value );
}
if ( quality && ! dotnetVersion . qualityFlag ) {
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: ${ dotnetVersion . value } . 'dotnet-quality' input is ignored.`
);
return this ;
}
if ( quality ) {
this . useArguments ( IS_WINDOWS ? '-Quality' : '--quality' , quality );
}
return this ;
}
public async execute() {
const getExecOutputOptions = {
ignoreReturnCode : true ,
env : process.env as { string : string }
};
await this . scriptReady ;
return exec . getExecOutput (
`" ${ this . scriptPath } "` ,
this . scriptArguments ,
2023-05-24 15:22:01 +02:00
getExecOutputOptions
);
2023-05-12 16:28:16 +02:00
}
}
export abstract class DotnetInstallDir {
private static readonly default = {
linux : '/usr/share/dotnet' ,
mac : path.join ( process . env [ 'HOME' ] + '' , '.dotnet' ),
windows : path.join ( process . env [ 'PROGRAMFILES' ] + '' , 'dotnet' )
2023-05-24 15:22:01 +02:00
};
2023-05-12 16:28:16 +02:00
public static readonly path = process . env [ 'DOTNET_INSTALL_DIR' ]
2023-05-24 15:22:01 +02:00
? DotnetInstallDir . convertInstallPathToAbsolute (
process . env [ 'DOTNET_INSTALL_DIR' ]
)
: DotnetInstallDir . default [ getPlatform ()];
2023-05-12 16:28:16 +02:00
2023-03-09 14:43:05 +02:00
private static convertInstallPathToAbsolute ( installDir : string ) : string {
2023-05-12 17:34:18 +02:00
if ( path . isAbsolute ( installDir )) return path . normalize ( installDir );
const transformedPath = installDir . startsWith ( '~' )
? path . join ( os . homedir (), installDir . slice ( 1 ))
: path . join ( process . cwd (), installDir );
2023-03-09 14:43:05 +02:00
return path . normalize ( transformedPath );
}
2023-05-12 16:28:16 +02:00
public static addToPath() {
2023-03-09 14:43:05 +02:00
core . addPath ( process . env [ 'DOTNET_INSTALL_DIR' ] ! );
core . exportVariable ( 'DOTNET_ROOT' , process . env [ 'DOTNET_INSTALL_DIR' ]);
}
2023-05-12 16:28:16 +02:00
public static initialize() {
process . env [ 'DOTNET_INSTALL_DIR' ] = DotnetInstallDir . path ;
2023-03-09 14:43:05 +02:00
}
2023-05-12 16:28:16 +02:00
}
2023-03-09 14:43:05 +02:00
2023-05-12 16:28:16 +02:00
export class DotnetCoreInstaller {
static addToPath = DotnetInstallDir . addToPath ;
2023-03-09 14:43:05 +02:00
2023-05-12 16:28:16 +02:00
static {
2023-05-12 17:10:53 +02:00
DotnetInstallDir . initialize ();
2023-05-12 16:28:16 +02:00
}
2023-03-09 14:43:05 +02:00
2023-05-24 15:22:01 +02:00
constructor ( private version : string , private quality : QualityOptions ) {}
2023-03-09 14:43:05 +02:00
2023-05-12 16:28:16 +02:00
public async installDotnet () : Promise < string > {
const versionResolver = new DotnetVersionResolver ( this . version );
const dotnetVersion = await versionResolver . createDotnetVersion ();
2023-03-09 14:43:05 +02:00
2023-05-12 16:28:16 +02:00
const installScript = new DotnetInstallScript ()
2023-05-24 15:22:01 +02:00
. useArguments (
IS_WINDOWS ? '-SkipNonVersionedFiles' : '--skip-non-versioned-files'
)
2023-05-12 16:28:16 +02:00
. useVersion ( dotnetVersion , this . quality );
2023-03-09 14:43:05 +02:00
2023-05-12 16:28:16 +02:00
const { exitCode , stderr } = await installScript . execute ();
2023-03-09 14:43:05 +02:00
if ( exitCode ) {
throw new Error (
`Failed to install dotnet, exit code: ${ exitCode } . ${ stderr } `
);
}
return this . outputDotnetVersion ( dotnetVersion . value );
}
private async outputDotnetVersion ( version ) : Promise < string > {
const installationPath = process . env [ 'DOTNET_INSTALL_DIR' ] ! ;
const versionsOnRunner : string [] = await readdir (
path . join ( installationPath . replace ( /'/g , '' ), 'sdk' )
);
const installedVersion = semver . maxSatisfying ( versionsOnRunner , version , {
includePrerelease : true
}) ! ;
return installedVersion ;
}
}