3af9be6157f42cbb5637bcbe8b2b7e8e5c97c9ea
[platform/upstream/coreclr.git] / eng / common / tools.sh
1 #!/usr/bin/env bash
2
3 # Initialize variables if they aren't already defined.
4
5 # CI mode - set to true on CI server for PR validation build or official build.
6 ci=${ci:-false}
7
8 # Set to true to use the pipelines logger which will enable Azure logging output.
9 # https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md
10 # This flag is meant as a temporary opt-opt for the feature while validate it across
11 # our consumers. It will be deleted in the future.
12 if [[ "$ci" == true ]]; then
13   pipelines_log=${pipelines_log:-true}
14 else
15   pipelines_log=${pipelines_log:-false}
16 fi
17
18 # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names.
19 configuration=${configuration:-'Debug'}
20
21 # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build.
22 # Binary log must be enabled on CI.
23 binary_log=${binary_log:-$ci}
24
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
28 # True to restore toolsets and dependencies.
29 restore=${restore:-true}
30
31 # Adjusts msbuild verbosity level.
32 verbosity=${verbosity:-'minimal'}
33
34 # Set to true to reuse msbuild nodes. Recommended to not reuse on CI.
35 if [[ "$ci" == true ]]; then
36   node_reuse=${node_reuse:-false}
37 else
38   node_reuse=${node_reuse:-true}
39 fi
40
41 # Configures warning treatment in msbuild.
42 warn_as_error=${warn_as_error:-true}
43
44 # True to attempt using .NET Core already that meets requirements specified in global.json 
45 # installed on the machine instead of downloading one.
46 use_installed_dotnet_cli=${use_installed_dotnet_cli:-true}
47
48 # Enable repos to use a particular version of the on-line dotnet-install scripts.
49 #    default URL: https://dot.net/v1/dotnet-install.sh
50 dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'}
51
52 # True to use global NuGet cache instead of restoring packages to repository-local directory.
53 if [[ "$ci" == true ]]; then
54   use_global_nuget_cache=${use_global_nuget_cache:-false}
55 else
56   use_global_nuget_cache=${use_global_nuget_cache:-true}
57 fi
58
59 # Resolve any symlinks in the given path.
60 function ResolvePath {
61   local path=$1
62
63   while [[ -h $path ]]; do
64     local dir="$( cd -P "$( dirname "$path" )" && pwd )"
65     path="$(readlink "$path")"
66
67     # if $path was a relative symlink, we need to resolve it relative to the path where the
68     # symlink file was located
69     [[ $path != /* ]] && path="$dir/$path"
70   done
71
72   # return value
73   _ResolvePath="$path"
74 }
75
76 # ReadVersionFromJson [json key]
77 function ReadGlobalVersion {
78   local key=$1
79
80   local line=`grep -m 1 "$key" "$global_json_file"`
81   local pattern="\"$key\" *: *\"(.*)\""
82
83   if [[ ! $line =~ $pattern ]]; then
84     Write-PipelineTelemetryError -category 'InitializeToolset' "Error: Cannot find \"$key\" in $global_json_file"
85     ExitWithExitCode 1
86   fi
87
88   # return value
89   _ReadGlobalVersion=${BASH_REMATCH[1]}
90 }
91
92 function InitializeDotNetCli {
93   if [[ -n "${_InitializeDotNetCli:-}" ]]; then
94     return
95   fi
96
97   local install=$1
98
99   # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism
100   export DOTNET_MULTILEVEL_LOOKUP=0
101
102   # Disable first run since we want to control all package sources
103   export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
104
105   # Disable telemetry on CI
106   if [[ $ci == true ]]; then
107     export DOTNET_CLI_TELEMETRY_OPTOUT=1
108   fi
109
110   # LTTNG is the logging infrastructure used by Core CLR. Need this variable set
111   # so it doesn't output warnings to the console.
112   export LTTNG_HOME="$HOME"
113
114   # Source Build uses DotNetCoreSdkDir variable
115   if [[ -n "${DotNetCoreSdkDir:-}" ]]; then
116     export DOTNET_INSTALL_DIR="$DotNetCoreSdkDir"
117   fi
118
119   # Find the first path on $PATH that contains the dotnet.exe
120   if [[ "$use_installed_dotnet_cli" == true && $global_json_has_runtimes == false && -z "${DOTNET_INSTALL_DIR:-}" ]]; then
121     local dotnet_path=`command -v dotnet`
122     if [[ -n "$dotnet_path" ]]; then
123       ResolvePath "$dotnet_path"
124       export DOTNET_INSTALL_DIR=`dirname "$_ResolvePath"`
125     fi
126   fi
127
128   ReadGlobalVersion "dotnet"
129   local dotnet_sdk_version=$_ReadGlobalVersion
130   local dotnet_root=""
131
132   # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version,
133   # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues.
134   if [[ $global_json_has_runtimes == false && -n "${DOTNET_INSTALL_DIR:-}" && -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then
135     dotnet_root="$DOTNET_INSTALL_DIR"
136   else
137     dotnet_root="$repo_root/.dotnet"
138
139     export DOTNET_INSTALL_DIR="$dotnet_root"
140
141     if [[ ! -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then
142       if [[ "$install" == true ]]; then
143         InstallDotNetSdk "$dotnet_root" "$dotnet_sdk_version"
144       else
145         Write-PipelineTelemetryError -category 'InitializeToolset' "Unable to find dotnet with SDK version '$dotnet_sdk_version'"
146         ExitWithExitCode 1
147       fi
148     fi
149   fi
150
151   # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom
152   # build steps from using anything other than what we've downloaded.
153   Write-PipelinePrependPath -path "$dotnet_root"
154
155   Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0"
156   Write-PipelineSetVariable -name "DOTNET_SKIP_FIRST_TIME_EXPERIENCE" -value "1"
157
158   # return value
159   _InitializeDotNetCli="$dotnet_root"
160 }
161
162 function InstallDotNetSdk {
163   local root=$1
164   local version=$2
165   local architecture=""
166   if [[ $# == 3 ]]; then
167     architecture=$3
168   fi
169   InstallDotNet "$root" "$version" $architecture
170 }
171
172 function InstallDotNet {
173   local root=$1
174   local version=$2
175  
176   GetDotNetInstallScript "$root"
177   local install_script=$_GetDotNetInstallScript
178
179   local archArg=''
180   if [[ -n "${3:-}" ]]; then
181     archArg="--architecture $3"
182   fi
183   local runtimeArg=''
184   if [[ -n "${4:-}" ]]; then
185     runtimeArg="--runtime $4"
186   fi
187
188   local skipNonVersionedFilesArg=""
189   if [[ "$#" -ge "5" ]]; then
190     skipNonVersionedFilesArg="--skip-non-versioned-files"
191   fi
192   bash "$install_script" --version $version --install-dir "$root" $archArg $runtimeArg $skipNonVersionedFilesArg || {
193     local exit_code=$?
194     Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to install dotnet SDK (exit code '$exit_code')."
195     ExitWithExitCode $exit_code
196   }
197 }
198
199 function GetDotNetInstallScript {
200   local root=$1
201   local install_script="$root/dotnet-install.sh"
202   local install_script_url="https://dot.net/$dotnetInstallScriptVersion/dotnet-install.sh"
203
204   if [[ ! -a "$install_script" ]]; then
205     mkdir -p "$root"
206
207     echo "Downloading '$install_script_url'"
208
209     # Use curl if available, otherwise use wget
210     if command -v curl > /dev/null; then
211       curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script"
212     else
213       wget -q -O "$install_script" "$install_script_url"
214     fi
215   fi
216
217   # return value
218   _GetDotNetInstallScript="$install_script"
219 }
220
221 function InitializeBuildTool {
222   if [[ -n "${_InitializeBuildTool:-}" ]]; then
223     return
224   fi
225   
226   InitializeDotNetCli $restore
227
228   # return values
229   _InitializeBuildTool="$_InitializeDotNetCli/dotnet"  
230   _InitializeBuildToolCommand="msbuild"
231   _InitializeBuildToolFramework="netcoreapp2.1"
232 }
233
234 function GetNuGetPackageCachePath {
235   if [[ -z ${NUGET_PACKAGES:-} ]]; then
236     if [[ "$use_global_nuget_cache" == true ]]; then
237       export NUGET_PACKAGES="$HOME/.nuget/packages"
238     else
239       export NUGET_PACKAGES="$repo_root/.packages"
240     fi
241   fi
242
243   # return value
244   _GetNuGetPackageCachePath=$NUGET_PACKAGES
245 }
246
247 function InitializeNativeTools() {
248   if grep -Fq "native-tools" $global_json_file
249   then
250     local nativeArgs=""
251     if [[ "$ci" == true ]]; then
252       nativeArgs="--installDirectory $tools_dir"
253     fi
254     "$_script_dir/init-tools-native.sh" $nativeArgs
255   fi
256 }
257
258 function InitializeToolset {
259   if [[ -n "${_InitializeToolset:-}" ]]; then
260     return
261   fi
262
263   GetNuGetPackageCachePath
264
265   ReadGlobalVersion "Microsoft.DotNet.Arcade.Sdk"
266
267   local toolset_version=$_ReadGlobalVersion
268   local toolset_location_file="$toolset_dir/$toolset_version.txt"
269
270   if [[ -a "$toolset_location_file" ]]; then
271     local path=`cat "$toolset_location_file"`
272     if [[ -a "$path" ]]; then
273       # return value
274       _InitializeToolset="$path"
275       return
276     fi
277   fi
278
279   if [[ "$restore" != true ]]; then
280     Write-PipelineTelemetryError -category 'InitializeToolset' "Toolset version $toolset_version has not been restored."
281     ExitWithExitCode 2
282   fi
283
284   local proj="$toolset_dir/restore.proj"
285
286   local bl=""
287   if [[ "$binary_log" == true ]]; then
288     bl="/bl:$log_dir/ToolsetRestore.binlog"
289   fi
290   
291   echo '<Project Sdk="Microsoft.DotNet.Arcade.Sdk"/>' > "$proj"
292   MSBuild-Core "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file"
293
294   local toolset_build_proj=`cat "$toolset_location_file"`
295
296   if [[ ! -a "$toolset_build_proj" ]]; then
297     Write-PipelineTelemetryError -category 'InitializeToolset' "Invalid toolset path: $toolset_build_proj"
298     ExitWithExitCode 3
299   fi
300
301   # return value
302   _InitializeToolset="$toolset_build_proj"
303 }
304
305 function ExitWithExitCode {
306   if [[ "$ci" == true && "$prepare_machine" == true ]]; then
307     StopProcesses
308   fi
309   exit $1
310 }
311
312 function StopProcesses {
313   echo "Killing running build processes..."
314   pkill -9 "dotnet" || true
315   pkill -9 "vbcscompiler" || true
316   return 0
317 }
318
319 function MSBuild {
320   local args=$@
321   if [[ "$pipelines_log" == true ]]; then
322     InitializeBuildTool
323     InitializeToolset
324     local toolset_dir="${_InitializeToolset%/*}"
325     local logger_path="$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.Arcade.Sdk.dll"
326     args=( "${args[@]}" "-logger:$logger_path" )
327   fi
328
329   MSBuild-Core ${args[@]}
330 }
331
332 function MSBuild-Core {
333   if [[ "$ci" == true ]]; then
334     if [[ "$binary_log" != true ]]; then
335       Write-PipelineTaskError "Binary log must be enabled in CI build."
336       ExitWithExitCode 1
337     fi
338
339     if [[ "$node_reuse" == true ]]; then
340       Write-PipelineTaskError "Node reuse must be disabled in CI build."
341       ExitWithExitCode 1
342     fi
343   fi
344
345   InitializeBuildTool
346
347   local warnaserror_switch=""
348   if [[ $warn_as_error == true ]]; then
349     warnaserror_switch="/warnaserror"
350   fi
351
352   "$_InitializeBuildTool" "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" || {
353     local exit_code=$?
354     Write-PipelineTaskError "Build failed (exit code '$exit_code')."
355     ExitWithExitCode $exit_code
356   }
357 }
358
359 ResolvePath "${BASH_SOURCE[0]}"
360 _script_dir=`dirname "$_ResolvePath"`
361
362 . "$_script_dir/pipeline-logging-functions.sh"
363
364 eng_root=`cd -P "$_script_dir/.." && pwd`
365 repo_root=`cd -P "$_script_dir/../.." && pwd`
366 artifacts_dir="$repo_root/artifacts"
367 toolset_dir="$artifacts_dir/toolset"
368 tools_dir="$repo_root/.tools"
369 log_dir="$artifacts_dir/log/$configuration"
370 temp_dir="$artifacts_dir/tmp/$configuration"
371
372 global_json_file="$repo_root/global.json"
373 # determine if global.json contains a "runtimes" entry
374 global_json_has_runtimes=false
375 dotnetlocal_key=`grep -m 1 "runtimes" "$global_json_file"` || true
376 if [[ -n "$dotnetlocal_key" ]]; then
377   global_json_has_runtimes=true
378 fi
379
380 # HOME may not be defined in some scenarios, but it is required by NuGet
381 if [[ -z $HOME ]]; then
382   export HOME="$repo_root/artifacts/.home/"
383   mkdir -p "$HOME"
384 fi
385
386 mkdir -p "$toolset_dir"
387 mkdir -p "$temp_dir"
388 mkdir -p "$log_dir"
389
390 Write-PipelineSetVariable -name "Artifacts" -value "$artifacts_dir"
391 Write-PipelineSetVariable -name "Artifacts.Toolset" -value "$toolset_dir"
392 Write-PipelineSetVariable -name "Artifacts.Log" -value "$log_dir"
393 Write-PipelineSetVariable -name "Temp" -value "$temp_dir"
394 Write-PipelineSetVariable -name "TMP" -value "$temp_dir"