tools.sh 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. #!/usr/bin/env bash
  2. # Initialize variables if they aren't already defined.
  3. # CI mode - set to true on CI server for PR validation build or official build.
  4. ci=${ci:-false}
  5. # Set to true to use the pipelines logger which will enable Azure logging output.
  6. # https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md
  7. # This flag is meant as a temporary opt-opt for the feature while validate it across
  8. # our consumers. It will be deleted in the future.
  9. if [[ "$ci" == true ]]; then
  10. pipelines_log=${pipelines_log:-true}
  11. else
  12. pipelines_log=${pipelines_log:-false}
  13. fi
  14. # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names.
  15. configuration=${configuration:-'Debug'}
  16. # Set to true to opt out of outputting binary log while running in CI
  17. exclude_ci_binary_log=${exclude_ci_binary_log:-false}
  18. if [[ "$ci" == true && "$exclude_ci_binary_log" == false ]]; then
  19. binary_log_default=true
  20. else
  21. binary_log_default=false
  22. fi
  23. # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build.
  24. binary_log=${binary_log:-$binary_log_default}
  25. # Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes).
  26. prepare_machine=${prepare_machine:-false}
  27. # True to restore toolsets and dependencies.
  28. restore=${restore:-true}
  29. # Adjusts msbuild verbosity level.
  30. verbosity=${verbosity:-'minimal'}
  31. # Set to true to reuse msbuild nodes. Recommended to not reuse on CI.
  32. if [[ "$ci" == true ]]; then
  33. node_reuse=${node_reuse:-false}
  34. else
  35. node_reuse=${node_reuse:-true}
  36. fi
  37. # Configures warning treatment in msbuild.
  38. warn_as_error=${warn_as_error:-true}
  39. # True to attempt using .NET Core already that meets requirements specified in global.json
  40. # installed on the machine instead of downloading one.
  41. use_installed_dotnet_cli=${use_installed_dotnet_cli:-true}
  42. # Enable repos to use a particular version of the on-line dotnet-install scripts.
  43. # default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh
  44. dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'}
  45. # True to use global NuGet cache instead of restoring packages to repository-local directory.
  46. if [[ "$ci" == true ]]; then
  47. use_global_nuget_cache=${use_global_nuget_cache:-false}
  48. else
  49. use_global_nuget_cache=${use_global_nuget_cache:-true}
  50. fi
  51. # Used when restoring .NET SDK from alternative feeds
  52. runtime_source_feed=${runtime_source_feed:-''}
  53. runtime_source_feed_key=${runtime_source_feed_key:-''}
  54. # Resolve any symlinks in the given path.
  55. function ResolvePath {
  56. local path=$1
  57. while [[ -h $path ]]; do
  58. local dir="$( cd -P "$( dirname "$path" )" && pwd )"
  59. path="$(readlink "$path")"
  60. # if $path was a relative symlink, we need to resolve it relative to the path where the
  61. # symlink file was located
  62. [[ $path != /* ]] && path="$dir/$path"
  63. done
  64. # return value
  65. _ResolvePath="$path"
  66. }
  67. # ReadVersionFromJson [json key]
  68. function ReadGlobalVersion {
  69. local key=$1
  70. if command -v jq &> /dev/null; then
  71. _ReadGlobalVersion="$(jq -r ".[] | select(has(\"$key\")) | .\"$key\"" "$global_json_file")"
  72. elif [[ "$(cat "$global_json_file")" =~ \"$key\"[[:space:]\:]*\"([^\"]+) ]]; then
  73. _ReadGlobalVersion=${BASH_REMATCH[1]}
  74. fi
  75. if [[ -z "$_ReadGlobalVersion" ]]; then
  76. Write-PipelineTelemetryError -category 'Build' "Error: Cannot find \"$key\" in $global_json_file"
  77. ExitWithExitCode 1
  78. fi
  79. }
  80. function InitializeDotNetCli {
  81. if [[ -n "${_InitializeDotNetCli:-}" ]]; then
  82. return
  83. fi
  84. local install=$1
  85. # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism
  86. export DOTNET_MULTILEVEL_LOOKUP=0
  87. # Disable first run since we want to control all package sources
  88. export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
  89. # Disable telemetry on CI
  90. if [[ $ci == true ]]; then
  91. export DOTNET_CLI_TELEMETRY_OPTOUT=1
  92. fi
  93. # LTTNG is the logging infrastructure used by Core CLR. Need this variable set
  94. # so it doesn't output warnings to the console.
  95. export LTTNG_HOME="$HOME"
  96. # Source Build uses DotNetCoreSdkDir variable
  97. if [[ -n "${DotNetCoreSdkDir:-}" ]]; then
  98. export DOTNET_INSTALL_DIR="$DotNetCoreSdkDir"
  99. fi
  100. # Find the first path on $PATH that contains the dotnet.exe
  101. if [[ "$use_installed_dotnet_cli" == true && $global_json_has_runtimes == false && -z "${DOTNET_INSTALL_DIR:-}" ]]; then
  102. local dotnet_path=`command -v dotnet`
  103. if [[ -n "$dotnet_path" ]]; then
  104. ResolvePath "$dotnet_path"
  105. export DOTNET_INSTALL_DIR=`dirname "$_ResolvePath"`
  106. fi
  107. fi
  108. ReadGlobalVersion "dotnet"
  109. local dotnet_sdk_version=$_ReadGlobalVersion
  110. local dotnet_root=""
  111. # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version,
  112. # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues.
  113. if [[ $global_json_has_runtimes == false && -n "${DOTNET_INSTALL_DIR:-}" && -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then
  114. dotnet_root="$DOTNET_INSTALL_DIR"
  115. else
  116. dotnet_root="$repo_root/.dotnet"
  117. export DOTNET_INSTALL_DIR="$dotnet_root"
  118. if [[ ! -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then
  119. if [[ "$install" == true ]]; then
  120. InstallDotNetSdk "$dotnet_root" "$dotnet_sdk_version"
  121. else
  122. Write-PipelineTelemetryError -category 'InitializeToolset' "Unable to find dotnet with SDK version '$dotnet_sdk_version'"
  123. ExitWithExitCode 1
  124. fi
  125. fi
  126. fi
  127. # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom
  128. # build steps from using anything other than what we've downloaded.
  129. Write-PipelinePrependPath -path "$dotnet_root"
  130. Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0"
  131. Write-PipelineSetVariable -name "DOTNET_SKIP_FIRST_TIME_EXPERIENCE" -value "1"
  132. # return value
  133. _InitializeDotNetCli="$dotnet_root"
  134. }
  135. function InstallDotNetSdk {
  136. local root=$1
  137. local version=$2
  138. local architecture="unset"
  139. if [[ $# -ge 3 ]]; then
  140. architecture=$3
  141. fi
  142. InstallDotNet "$root" "$version" $architecture 'sdk' 'true' $runtime_source_feed $runtime_source_feed_key
  143. }
  144. function InstallDotNet {
  145. local root=$1
  146. local version=$2
  147. GetDotNetInstallScript "$root"
  148. local install_script=$_GetDotNetInstallScript
  149. local installParameters=(--version $version --install-dir "$root")
  150. if [[ -n "${3:-}" ]] && [ "$3" != 'unset' ]; then
  151. installParameters+=(--architecture $3)
  152. fi
  153. if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then
  154. installParameters+=(--runtime $4)
  155. fi
  156. if [[ "$#" -ge "5" ]] && [[ "$5" != 'false' ]]; then
  157. installParameters+=(--skip-non-versioned-files)
  158. fi
  159. local variations=() # list of variable names with parameter arrays in them
  160. local public_location=("${installParameters[@]}")
  161. variations+=(public_location)
  162. local dotnetbuilds=("${installParameters[@]}" --azure-feed "https://dotnetbuilds.azureedge.net/public")
  163. variations+=(dotnetbuilds)
  164. if [[ -n "${6:-}" ]]; then
  165. variations+=(private_feed)
  166. local private_feed=("${installParameters[@]}" --azure-feed $6)
  167. if [[ -n "${7:-}" ]]; then
  168. # The 'base64' binary on alpine uses '-d' and doesn't support '--decode'
  169. # '-d'. To work around this, do a simple detection and switch the parameter
  170. # accordingly.
  171. decodeArg="--decode"
  172. if base64 --help 2>&1 | grep -q "BusyBox"; then
  173. decodeArg="-d"
  174. fi
  175. decodedFeedKey=`echo $7 | base64 $decodeArg`
  176. private_feed+=(--feed-credential $decodedFeedKey)
  177. fi
  178. fi
  179. local installSuccess=0
  180. for variationName in "${variations[@]}"; do
  181. local name="$variationName[@]"
  182. local variation=("${!name}")
  183. echo "Attempting to install dotnet from $variationName."
  184. bash "$install_script" "${variation[@]}" && installSuccess=1
  185. if [[ "$installSuccess" -eq 1 ]]; then
  186. break
  187. fi
  188. echo "Failed to install dotnet from $variationName."
  189. done
  190. if [[ "$installSuccess" -eq 0 ]]; then
  191. Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to install dotnet SDK from any of the specified locations."
  192. ExitWithExitCode 1
  193. fi
  194. }
  195. function with_retries {
  196. local maxRetries=5
  197. local retries=1
  198. echo "Trying to run '$@' for maximum of $maxRetries attempts."
  199. while [[ $((retries++)) -le $maxRetries ]]; do
  200. "$@"
  201. if [[ $? == 0 ]]; then
  202. echo "Ran '$@' successfully."
  203. return 0
  204. fi
  205. timeout=$((3**$retries-1))
  206. echo "Failed to execute '$@'. Waiting $timeout seconds before next attempt ($retries out of $maxRetries)." 1>&2
  207. sleep $timeout
  208. done
  209. echo "Failed to execute '$@' for $maxRetries times." 1>&2
  210. return 1
  211. }
  212. function GetDotNetInstallScript {
  213. local root=$1
  214. local install_script="$root/dotnet-install.sh"
  215. local install_script_url="https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh"
  216. if [[ ! -a "$install_script" ]]; then
  217. mkdir -p "$root"
  218. echo "Downloading '$install_script_url'"
  219. # Use curl if available, otherwise use wget
  220. if command -v curl > /dev/null; then
  221. # first, try directly, if this fails we will retry with verbose logging
  222. curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script" || {
  223. if command -v openssl &> /dev/null; then
  224. echo "Curl failed; dumping some information about dotnet.microsoft.com for later investigation"
  225. echo | openssl s_client -showcerts -servername dotnet.microsoft.com -connect dotnet.microsoft.com:443
  226. fi
  227. echo "Will now retry the same URL with verbose logging."
  228. with_retries curl "$install_script_url" -sSL --verbose --retry 10 --create-dirs -o "$install_script" || {
  229. local exit_code=$?
  230. Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')."
  231. ExitWithExitCode $exit_code
  232. }
  233. }
  234. else
  235. with_retries wget -v -O "$install_script" "$install_script_url" || {
  236. local exit_code=$?
  237. Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')."
  238. ExitWithExitCode $exit_code
  239. }
  240. fi
  241. fi
  242. # return value
  243. _GetDotNetInstallScript="$install_script"
  244. }
  245. function InitializeBuildTool {
  246. if [[ -n "${_InitializeBuildTool:-}" ]]; then
  247. return
  248. fi
  249. InitializeDotNetCli $restore
  250. # return values
  251. _InitializeBuildTool="$_InitializeDotNetCli/dotnet"
  252. _InitializeBuildToolCommand="msbuild"
  253. _InitializeBuildToolFramework="net7.0"
  254. }
  255. # Set RestoreNoCache as a workaround for https://github.com/NuGet/Home/issues/3116
  256. function GetNuGetPackageCachePath {
  257. if [[ -z ${NUGET_PACKAGES:-} ]]; then
  258. if [[ "$use_global_nuget_cache" == true ]]; then
  259. export NUGET_PACKAGES="$HOME/.nuget/packages"
  260. else
  261. export NUGET_PACKAGES="$repo_root/.packages"
  262. export RESTORENOCACHE=true
  263. fi
  264. fi
  265. # return value
  266. _GetNuGetPackageCachePath=$NUGET_PACKAGES
  267. }
  268. function InitializeNativeTools() {
  269. if [[ -n "${DisableNativeToolsetInstalls:-}" ]]; then
  270. return
  271. fi
  272. if grep -Fq "native-tools" $global_json_file
  273. then
  274. local nativeArgs=""
  275. if [[ "$ci" == true ]]; then
  276. nativeArgs="--installDirectory $tools_dir"
  277. fi
  278. "$_script_dir/init-tools-native.sh" $nativeArgs
  279. fi
  280. }
  281. function InitializeToolset {
  282. if [[ -n "${_InitializeToolset:-}" ]]; then
  283. return
  284. fi
  285. GetNuGetPackageCachePath
  286. ReadGlobalVersion "Microsoft.DotNet.Arcade.Sdk"
  287. local toolset_version=$_ReadGlobalVersion
  288. local toolset_location_file="$toolset_dir/$toolset_version.txt"
  289. if [[ -a "$toolset_location_file" ]]; then
  290. local path=`cat "$toolset_location_file"`
  291. if [[ -a "$path" ]]; then
  292. # return value
  293. _InitializeToolset="$path"
  294. return
  295. fi
  296. fi
  297. if [[ "$restore" != true ]]; then
  298. Write-PipelineTelemetryError -category 'InitializeToolset' "Toolset version $toolset_version has not been restored."
  299. ExitWithExitCode 2
  300. fi
  301. local proj="$toolset_dir/restore.proj"
  302. local bl=""
  303. if [[ "$binary_log" == true ]]; then
  304. bl="/bl:$log_dir/ToolsetRestore.binlog"
  305. fi
  306. echo '<Project Sdk="Microsoft.DotNet.Arcade.Sdk"/>' > "$proj"
  307. MSBuild-Core "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file"
  308. local toolset_build_proj=`cat "$toolset_location_file"`
  309. if [[ ! -a "$toolset_build_proj" ]]; then
  310. Write-PipelineTelemetryError -category 'Build' "Invalid toolset path: $toolset_build_proj"
  311. ExitWithExitCode 3
  312. fi
  313. # return value
  314. _InitializeToolset="$toolset_build_proj"
  315. }
  316. function ExitWithExitCode {
  317. if [[ "$ci" == true && "$prepare_machine" == true ]]; then
  318. StopProcesses
  319. fi
  320. exit $1
  321. }
  322. function StopProcesses {
  323. echo "Killing running build processes..."
  324. pkill -9 "dotnet" || true
  325. pkill -9 "vbcscompiler" || true
  326. return 0
  327. }
  328. function MSBuild {
  329. local args=$@
  330. if [[ "$pipelines_log" == true ]]; then
  331. InitializeBuildTool
  332. InitializeToolset
  333. if [[ "$ci" == true ]]; then
  334. export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20
  335. export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20
  336. Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20"
  337. Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20"
  338. # https://github.com/dotnet/arcade/issues/11369 - disable new MSBuild server feature on linux
  339. # This feature is new and can result in build failures from connection timeout errors.
  340. export DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER=1
  341. Write-PipelineSetVariable -name "DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER" -value "1"
  342. fi
  343. local toolset_dir="${_InitializeToolset%/*}"
  344. # new scripts need to work with old packages, so we need to look for the old names/versions
  345. local selectedPath=
  346. local possiblePaths=()
  347. possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.ArcadeLogging.dll" )
  348. possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.Arcade.Sdk.dll" )
  349. possiblePaths+=( "$toolset_dir/netcoreapp2.1/Microsoft.DotNet.ArcadeLogging.dll" )
  350. possiblePaths+=( "$toolset_dir/netcoreapp2.1/Microsoft.DotNet.Arcade.Sdk.dll" )
  351. possiblePaths+=( "$toolset_dir/netcoreapp3.1/Microsoft.DotNet.ArcadeLogging.dll" )
  352. possiblePaths+=( "$toolset_dir/netcoreapp3.1/Microsoft.DotNet.Arcade.Sdk.dll" )
  353. for path in "${possiblePaths[@]}"; do
  354. if [[ -f $path ]]; then
  355. selectedPath=$path
  356. break
  357. fi
  358. done
  359. if [[ -z "$selectedPath" ]]; then
  360. Write-PipelineTelemetryError -category 'Build' "Unable to find arcade sdk logger assembly."
  361. ExitWithExitCode 1
  362. fi
  363. args+=( "-logger:$selectedPath" )
  364. fi
  365. MSBuild-Core ${args[@]}
  366. }
  367. function MSBuild-Core {
  368. if [[ "$ci" == true ]]; then
  369. if [[ "$binary_log" != true && "$exclude_ci_binary_log" != true ]]; then
  370. Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build, or explicitly opted-out from with the -noBinaryLog switch."
  371. ExitWithExitCode 1
  372. fi
  373. if [[ "$node_reuse" == true ]]; then
  374. Write-PipelineTelemetryError -category 'Build' "Node reuse must be disabled in CI build."
  375. ExitWithExitCode 1
  376. fi
  377. fi
  378. InitializeBuildTool
  379. local warnaserror_switch=""
  380. if [[ $warn_as_error == true ]]; then
  381. warnaserror_switch="/warnaserror"
  382. fi
  383. function RunBuildTool {
  384. export ARCADE_BUILD_TOOL_COMMAND="$_InitializeBuildTool $@"
  385. "$_InitializeBuildTool" "$@" || {
  386. local exit_code=$?
  387. # We should not Write-PipelineTaskError here because that message shows up in the build summary
  388. # The build already logged an error, that's the reason it failed. Producing an error here only adds noise.
  389. echo "Build failed with exit code $exit_code. Check errors above."
  390. if [[ "$ci" == "true" ]]; then
  391. Write-PipelineSetResult -result "Failed" -message "msbuild execution failed."
  392. # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error
  393. # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error
  394. ExitWithExitCode 0
  395. else
  396. ExitWithExitCode $exit_code
  397. fi
  398. }
  399. }
  400. RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@"
  401. }
  402. function GetDarc {
  403. darc_path="$temp_dir/darc"
  404. version="$1"
  405. if [[ -n "$version" ]]; then
  406. version="--darcversion $version"
  407. fi
  408. "$eng_root/common/darc-init.sh" --toolpath "$darc_path" $version
  409. }
  410. ResolvePath "${BASH_SOURCE[0]}"
  411. _script_dir=`dirname "$_ResolvePath"`
  412. . "$_script_dir/pipeline-logging-functions.sh"
  413. eng_root=`cd -P "$_script_dir/.." && pwd`
  414. repo_root=`cd -P "$_script_dir/../.." && pwd`
  415. repo_root="${repo_root}/"
  416. artifacts_dir="${repo_root}artifacts"
  417. toolset_dir="$artifacts_dir/toolset"
  418. tools_dir="${repo_root}.tools"
  419. log_dir="$artifacts_dir/log/$configuration"
  420. temp_dir="$artifacts_dir/tmp/$configuration"
  421. global_json_file="${repo_root}global.json"
  422. # determine if global.json contains a "runtimes" entry
  423. global_json_has_runtimes=false
  424. if command -v jq &> /dev/null; then
  425. if jq -er '. | select(has("runtimes"))' "$global_json_file" &> /dev/null; then
  426. global_json_has_runtimes=true
  427. fi
  428. elif [[ "$(cat "$global_json_file")" =~ \"runtimes\"[[:space:]\:]*\{ ]]; then
  429. global_json_has_runtimes=true
  430. fi
  431. # HOME may not be defined in some scenarios, but it is required by NuGet
  432. if [[ -z $HOME ]]; then
  433. export HOME="${repo_root}artifacts/.home/"
  434. mkdir -p "$HOME"
  435. fi
  436. mkdir -p "$toolset_dir"
  437. mkdir -p "$temp_dir"
  438. mkdir -p "$log_dir"
  439. Write-PipelineSetVariable -name "Artifacts" -value "$artifacts_dir"
  440. Write-PipelineSetVariable -name "Artifacts.Toolset" -value "$toolset_dir"
  441. Write-PipelineSetVariable -name "Artifacts.Log" -value "$log_dir"
  442. Write-PipelineSetVariable -name "Temp" -value "$temp_dir"
  443. Write-PipelineSetVariable -name "TMP" -value "$temp_dir"
  444. # Import custom tools configuration, if present in the repo.
  445. if [ -z "${disable_configure_toolset_import:-}" ]; then
  446. configure_toolset_script="$eng_root/configure-toolset.sh"
  447. if [[ -a "$configure_toolset_script" ]]; then
  448. . "$configure_toolset_script"
  449. fi
  450. fi
  451. # TODO: https://github.com/dotnet/arcade/issues/1468
  452. # Temporary workaround to avoid breaking change.
  453. # Remove once repos are updated.
  454. if [[ -n "${useInstalledDotNetCli:-}" ]]; then
  455. use_installed_dotnet_cli="$useInstalledDotNetCli"
  456. fi