2023-03-09 19:43:05 +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' ;
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' ;
import { IS_LINUX , IS_WINDOWS } from './utils' ;
import { QualityOptions } from './setup-dotnet' ;
export interface DotnetVersion {
2023-04-10 21:58:35 +07:00
type : string ;
2023-03-09 19:43:05 +07:00
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 > {
2023-04-10 21:23:29 +07:00
const isLatestPatchSyntax = /^\d+\.\d+\.\d{1}x{2}$/ . test ( this . inputVersion ) ;
if ( ! semver . validRange ( this . inputVersion ) && ! isLatestPatchSyntax ) {
2023-03-09 19:43:05 +07:00
throw new Error (
2023-04-10 21:23:29 +07:00
` 'dotnet-version' was supplied in invalid format: ${ this . inputVersion } ! Supported syntax: A.B.C, A.B, A.B.x, A, A.x, A.B.Cxx `
2023-03-09 19:43:05 +07:00
) ;
}
if ( semver . valid ( this . inputVersion ) ) {
this . resolvedArgument . type = 'version' ;
this . resolvedArgument . value = this . inputVersion ;
} else {
2023-04-10 21:23:29 +07:00
this . resolvedArgument . type = 'channel' ;
2023-03-09 19:43:05 +07:00
const [ major , minor ] = this . inputVersion . split ( '.' ) ;
2023-04-10 21:23:29 +07:00
if ( isLatestPatchSyntax ) {
this . resolvedArgument . value = this . inputVersion ;
} else if ( this . isNumericTag ( major ) && this . isNumericTag ( minor ) ) {
this . resolvedArgument . value = ` ${ major } . ${ minor } ` ;
} else {
2023-04-11 18:20:34 +07:00
this . resolvedArgument . value = await this . getLatestByMajorTag ( major ) ;
2023-03-09 19:43:05 +07:00
}
this . resolvedArgument . qualityFlag = + major >= 6 ? true : false ;
}
}
private isNumericTag ( versionTag ) : boolean {
return /^\d+$/ . test ( versionTag ) ;
}
2023-04-10 21:23:29 +07:00
public async createDotNetVersion ( ) : Promise < DotnetVersion > {
2023-03-09 19:43:05 +07:00
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 ;
}
2023-04-11 18:20:34 +07:00
private async getLatestByMajorTag ( majorTag : string ) : Promise < string > {
const httpClient = new hc . HttpClient ( 'actions/setup-dotnet' , [ ] , {
allowRetries : true ,
maxRetries : 3
} ) ;
2023-03-09 19:43:05 +07:00
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 ( '.' ) ;
2023-04-11 18:20:34 +07:00
return sdkParts [ 0 ] === majorTag ;
2023-03-09 19:43:05 +07:00
} ) ;
if ( ! releaseInfo ) {
throw new Error (
2023-04-11 18:23:50 +07:00
` Could not find info for version with major tag: ${ majorTag } at ${ DotnetVersionResolver . DotNetCoreIndexUrl } `
2023-03-09 19:43:05 +07:00
) ;
}
return releaseInfo [ 'channel-version' ] ;
}
static DotNetCoreIndexUrl =
'https://dotnetcli.azureedge.net/dotnet/release-metadata/releases-index.json' ;
}
export class DotnetCoreInstaller {
private version : string ;
private quality : QualityOptions ;
static {
const installationDirectoryWindows = path . join (
process . env [ 'PROGRAMFILES' ] + '' ,
'dotnet'
) ;
const installationDirectoryLinux = '/usr/share/dotnet' ;
const installationDirectoryMac = path . join (
process . env [ 'HOME' ] + '' ,
'.dotnet'
) ;
const dotnetInstallDir : string | undefined =
process . env [ 'DOTNET_INSTALL_DIR' ] ;
if ( dotnetInstallDir ) {
process . env [ 'DOTNET_INSTALL_DIR' ] =
this . convertInstallPathToAbsolute ( dotnetInstallDir ) ;
} else {
if ( IS_WINDOWS ) {
process . env [ 'DOTNET_INSTALL_DIR' ] = installationDirectoryWindows ;
} else {
process . env [ 'DOTNET_INSTALL_DIR' ] = IS_LINUX
? installationDirectoryLinux
: installationDirectoryMac ;
}
}
}
constructor ( version : string , quality : QualityOptions ) {
this . version = version ;
this . quality = quality ;
}
private static convertInstallPathToAbsolute ( installDir : string ) : string {
let transformedPath ;
if ( path . isAbsolute ( installDir ) ) {
transformedPath = installDir ;
} else {
transformedPath = installDir . startsWith ( '~' )
? path . join ( os . homedir ( ) , installDir . slice ( 1 ) )
: ( transformedPath = path . join ( process . cwd ( ) , installDir ) ) ;
}
return path . normalize ( transformedPath ) ;
}
static addToPath() {
core . addPath ( process . env [ 'DOTNET_INSTALL_DIR' ] ! ) ;
core . exportVariable ( 'DOTNET_ROOT' , process . env [ 'DOTNET_INSTALL_DIR' ] ) ;
}
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. `
) ;
}
}
public async installDotnet ( ) : Promise < string > {
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 ( ) ;
if ( IS_WINDOWS ) {
scriptArguments = [ '&' , ` ' ${ escapedScript } ' ` ] ;
if ( dotnetVersion . type ) {
scriptArguments . push ( dotnetVersion . type , dotnetVersion . value ) ;
}
if ( this . quality ) {
this . setQuality ( dotnetVersion , scriptArguments ) ;
}
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' ] } ` ) ;
}
scriptPath =
( await io . which ( 'pwsh' , false ) ) || ( await io . which ( 'powershell' , true ) ) ;
scriptArguments = windowsDefaultOptions . concat ( scriptArguments ) ;
} else {
chmodSync ( escapedScript , '777' ) ;
scriptPath = await io . which ( escapedScript , true ) ;
scriptArguments = [ ] ;
if ( dotnetVersion . type ) {
scriptArguments . push ( dotnetVersion . type , dotnetVersion . value ) ;
}
if ( this . quality ) {
this . setQuality ( dotnetVersion , scriptArguments ) ;
}
}
// process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used
const getExecOutputOptions = {
ignoreReturnCode : true ,
env : process.env as { string : string }
} ;
const { exitCode , stderr } = await exec . getExecOutput (
` " ${ scriptPath } " ` ,
scriptArguments ,
getExecOutputOptions
) ;
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 ;
}
}