diff --git a/scripts/azure-pipelines/analyze-test-results.ps1 b/scripts/azure-pipelines/analyze-test-results.ps1 new file mode 100644 index 000000000..9e6d09d20 --- /dev/null +++ b/scripts/azure-pipelines/analyze-test-results.ps1 @@ -0,0 +1,778 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT +# + +<# +.SYNOPSIS +Analyze the test results as output by the CI system. + +.DESCRIPTION +Takes the set of port test results from $logDir, +and the baseline from $baselineFile, and makes certain that the set +of failures we expected are exactly the set of failures we got. +Then, uploads the logs from any unexpected failures. + +.PARAMETER logDir +Directory of xml test logs to analyze. + +.PARAMETER failurelogDir +Path to the failure logs that need to be published to azure for inspection. + +.PARAMETER outputDir +Where to write out the results of the analysis. + +.PARAMETER allResults +Include tests that have no change from the baseline in the output. + +.PARAMETER errorOnRegression +Output an error on test regressions. +This will give a clean message in the build pipeline. + +.PARAMETER noTable +Don't create or upload the markdown table of results + +.PARAMETER triplets +A list of triplets to analyze; defaults to all triplets. + +.PARAMETER baselineFile +The path to the ci.baseline.txt file in the vcpkg repository. +#> +[CmdletBinding()] +Param( + [Parameter(Mandatory = $true)] + [string]$logDir, + [Parameter(Mandatory = $true)] + [string]$failurelogDir, + [Parameter(Mandatory = $true)] + [string]$outputDir, + [switch]$allResults, + [switch]$errorOnRegression, + [switch]$noTable, + [string[]]$triplets = @(), + [Parameter(Mandatory = $true)] + [string]$baselineFile +) + +$ErrorActionPreference = 'Stop' + +if ( -not (Test-Path $logDir) ) { + [System.Console]::Error.WriteLine("Log directory does not exist: $logDir") + exit +} +if ( -not (Test-Path $outputDir) ) { + [System.Console]::Error.WriteLine("output directory does not exist: $outputDir") + exit +} + +if ( $triplets.Count -eq 0 ) { + $triplets = @( + "x64-linux", + "x64-osx", + "arm-uwp", + "arm64-windows", + "x64-osx", + "x64-uwp", + "x64-windows-static", + "x64-windows", + "x86-windows" + ) +} + + +<# +.SYNOPSIS +Creates an object the represents the test run. + +.DESCRIPTION +build_test_results takes an XML file of results from the CI run, +and constructs an object based on that XML file for further +processing. + +.OUTPUTS +An object with the following elements: + assemblyName: + assemblyStartDate: + assemblyStartTime: + assemblyTime: + collectionName: + collectionTime: + allTests: A hashtable with an entry for each port tested + The key is the name of the port + The value is an object with the following elements: + name: Name of the port (Does not include the triplet name) + result: Pass/Fail/Skip result from xunit + time: Test time in seconds + originalResult: Result as defined by Build.h in vcpkg source code + abi_tag: The port hash + features: The features installed + +.PARAMETER xmlFilename +The path to the XML file to parse. +#> +function build_test_results { + [CmdletBinding()] + Param + ( + [string]$xmlFilename + ) + if ( ($xmlFilename.Length -eq 0) -or ( -not( Test-Path $xmlFilename))) { + #write-error "Missing file: $xmlFilename" + return $null + } + + Write-Verbose "building test hash for $xmlFilename" + + [xml]$xmlContents = Get-Content $xmlFilename + + # This currently only supports one collection per assembly, which is the way + # the vcpkg tests are designed to run in the pipeline. + $xmlAssembly = $xmlContents.assemblies.assembly + $assemblyName = $xmlAssembly.name + $assemblyStartDate = $xmlAssembly."run-date" + $assemblyStartTime = $xmlAssembly."run-time" + $assemblyTime = $xmlAssembly.time + $xmlCollection = $xmlAssembly.collection + $collectionName = $xmlCollection.name + $collectionTime = $xmlCollection.time + + $allTestResults = @{ } + foreach ( $test in $xmlCollection.test) { + $name = ($test.name -replace ":.*$") + + # Reconstruct the original BuildResult enumeration (defined in Build.h) + # failure.message - why the test failed (valid only on test failure) + # reason - why the test was skipped (valid only when the test is skipped) + # case BuildResult::POST_BUILD_CHECKS_FAILED: + # case BuildResult::FILE_CONFLICTS: + # case BuildResult::BUILD_FAILED: + # case BuildResult::EXCLUDED: + # case BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES: + $originalResult = "NULLVALUE" + switch ($test.result) { + "Skip" { + $originalResult = $test.reason.InnerText + } + "Fail" { + $originalResult = $test.failure.message.InnerText + } + "Pass" { + $originalResult = "SUCCEEDED" + } + } + + $abi_tag = "" + $features = "" + foreach ( $trait in $test.traits.trait) { + switch ( $trait.name ) { + "abi_tag" { $abi_tag = $trait.value } + "features" { $features = $trait.value } + } + } + + # If additional fields get saved in the XML, then they should be added to this hash + # also consider using a PSCustomObject here instead of a hash + $testHash = @{ name = $name; result = $test.result; time = $test.time; originalResult = $originalResult; abi_tag = $abi_tag; features = $features } + $allTestResults[$name] = $testHash + } + + return @{ + assemblyName = $assemblyName; + assemblyStartDate = $assemblyStartDate; + assemblyStartTime = $assemblyStartTime; + assemblyTime = $assemblyTime; + collectionName = $collectionName; + collectionTime = $collectionTime; + allTests = $allTestResults + } +} + +<# +.SYNOPSIS +Creates an object that represents the baseline expectations. + +.DESCRIPTION +build_baseline_results converts the baseline file to an object representing +the expectations set up by the baseline file. It records four states: + 1) fail + 2) skip + 3) ignore + 4) pass -- this is represented by not being recorded +In other words, if a port is not contained in the object returned by this +cmdlet, expect it to pass. + +.OUTPUTS +An object containing the following fields: + collectionName: the triplet + fail: ports marked as fail + skip: ports marked as skipped + ignore: ports marked as ignore + +.PARAMETER baselineFile +The path to vcpkg's ci.baseline.txt. + +.PARAMETER triplet +The triplet to create the result object for. +#> +function build_baseline_results { + [CmdletBinding()] + Param( + $baselineFile, + $triplet + ) + #read in the file, strip out comments and blank lines and spaces, leave only the current triplet + #remove comments, remove empty lines, remove whitespace, then keep only those lines for $triplet + $baseline_list_raw = Get-Content -Path $baselineFile ` + | Where-Object { -not ($_ -match "\s*#") } ` + | Where-Object { -not ( $_ -match "^\s*$") } ` + | ForEach-Object { $_ -replace "\s" } ` + | Where-Object { $_ -match ":$triplet=" } + + #filter to skipped and trim the triplet + $skip_hash = @{ } + foreach ( $port in $baseline_list_raw | ? { $_ -match "=skip$" } | % { $_ -replace ":.*$" }) { + if ($skip_hash[$port] -ne $null) { + [System.Console]::Error.WriteLine("$($port):$($triplet) has multiple definitions in $baselineFile") + } + $skip_hash[$port] = $true + } + $fail_hash = @{ } + $baseline_list_raw | ? { $_ -match "=fail$" } | % { $_ -replace ":.*$" } | ? { $fail_hash[$_] = $true } | Out-Null + $ignore_hash = @{ } + $baseline_list_raw | ? { $_ -match "=ignore$" } | % { $_ -replace ":.*$" } | ? { $ignore_hash[$_] = $true } | Out-Null + + return @{ + collectionName = $triplet; + skip = $skip_hash; + fail = $fail_hash; + ignore = $ignore_hash + } +} + +<# +.SYNOPSIS +Analyzes the results of the current run against the baseline. + +.DESCRIPTION +combine_results compares the results to the baselie, and generates the results +for the CI -- whether it should pass or fail. + +.OUTPUTS +An object containing the following: +(Note that this is not the same data structure as build_test_results) + assemblyName: + assemblyStartDate: + assemblyStartTime: + assemblyTime: + collectionName: + collectionTime: + allTests: A hashtable of each port with a different status from the baseline + The key is the name of the port + The value is an object with the following data members: + name: The name of the port + result: xunit test result Pass/Fail/Skip + message: Human readable message describing the test result + time: time the current test results took to run. + baselineResult: + currentResult: + features: + ignored: list of ignored tests + +.PARAMETER baseline +The baseline object to use from build_baseline_results. + +.PARAMETER current +The results object to use from build_test_results. +#> +function combine_results { + [CmdletBinding()] + Param + ( + $baseline, + $current + ) + + if ($baseline.collectionName -ne $current.collectionName) { + Write-Warning "Comparing mismatched collections $($baseline.collectionName) and $($current.collectionName)" + } + + $currentTests = $current.allTests + + # lookup table with the results of all of the tests + $allTestResults = @{ } + + $ignoredList = @() + + Write-Verbose "analyzing $($currentTests.count) tests" + + foreach ($key in $currentTests.keys) { + Write-Verbose "analyzing $key" + + $message = $null + $result = $null + $time = $null + $currentResult = $null + $features = $currentTest.features + + $baselineResult = "Pass" + if ($baseline.fail[$key] -ne $null) { + Write-Verbose "$key is failing" + $baselineResult = "Fail" + } + elseif ( $baseline.skip[$key] -ne $null) { + Write-Verbose "$key is skipped" + $baselineResult = "Skip" + } + elseif ( $baseline.ignore[$key] -ne $null) { + $baselineResult = "ignore" + } + + $currentTest = $currentTests[$key] + + if ( $currentTest.result -eq $baselineResult) { + Write-Verbose "$key has no change from baseline" + $currentResult = $currentTest.result + if ($allResults) { + # Only marking regressions as failures but keep the skipped status + if ($currentResult -eq "Skip") { + $result = "Skip" + } + else { + $result = "Pass" + } + $message = "No change from baseline" + $time = $currentTest.time + } + } + elseif ( $baselineResult -eq "ignore") { + if ( $currentTest.result -eq "Fail" ) { + Write-Verbose "ignoring failure on $key" + $ignoredList += $key + } + } + else { + Write-Verbose "$key had a change from the baseline" + + $currentResult = $currentTest.result + # Test exists in both test runs but does not match. Determine if this is a regression + # Pass -> Fail = Fail (Regression) + # Pass -> Skip = Skip + # Fail -> Pass = Fail (need to update baseline) + # Fail -> Skip = Skip + # Skip -> Fail = Fail (Should not happen) + # Skip -> Pass = Fail (should not happen) + + $lookupTable = @{ + 'Pass' = @{ + 'Fail' = @('Fail', "Test passes in baseline but fails in current run. If expected update ci.baseline.txt with '$($key):$($current.collectionName)=fail'"); + 'Skip' = @($null, 'Test was skipped due to missing dependencies') + }; + 'Fail' = @{ + 'Pass' = @('Fail', "Test fails in baseline but now passes. Update ci.baseline.txt with '$($key):$($current.collectionName)=pass'"); + 'Skip' = @($null, 'Test fails in baseline but is skipped in current run') + }; + 'Skip' = @{ + 'Fail' = @('Skip', "Test is skipped in baseline but fails in current run. Results are ignored") + 'Pass' = @('Skip', "Test is skipped in baseline but passes in current run. Results are ignored") + } + } + $resultList = $lookupTable[$baselineResult][$currentResult] + $result = $resultList[0] + $message = $resultList[1] + $time = $currentTest.time + Write-Verbose ">$key $message" + } + + if ($result -ne $null) { + Write-Verbose "Adding $key to result list" + $allTestResults[$key] = @{ name = $key; result = $result; message = $message; time = $time; abi_tag = $currentTest.abi_tag; baselineResult = $baselineResult; currentResult = $currentResult; features = $features } + } + } + + return @{ + assemblyName = $current.assemblyName; + assemblyStartDate = $current.assemblyStartDate; + assemblyStartTime = $current.assemblyStartTime; + assemblyTime = $current.assemblyTime; + collectionName = $current.collectionName; + collectionTime = $current.collectionTime; + allTests = $allTestResults; + ignored = $ignoredList + } +} + +<# +.SYNOPSIS +Takes the combined results object and writes it to an xml file. + +.DESCRIPTION +write_xunit_results takes the results object from combine_results, and writes the +results XML file to the correct location for the CI system to pick it up. + +.PARAMETER combined_results +The results object from combine_results. +#> +function write_xunit_results { + [CmdletBinding()] + Param( + $combined_results + ) + $allTests = $combined_results.allTests + $triplet = $combined_results.collectionName + + $filePath = "$outputDir\$triplet.xml" + if (Test-Path $filePath) { + Write-Verbose "removing old file $filepath" + rm $filePath + } + Write-Verbose "output filename: $filepath" + + $xmlWriter = New-Object System.Xml.XmlTextWriter($filePath, $Null) + $xmlWriter.Formatting = "Indented" + $xmlWriter.IndentChar = "`t" + + $xmlWriter.WriteStartDocument() + $xmlWriter.WriteStartElement("assemblies") + $xmlWriter.WriteStartElement("assembly") + $xmlWriter.WriteAttributeString("name", $combined_results.assemblyName) + $xmlWriter.WriteAttributeString("run-date", $combined_results.assemblyStartDate) + $xmlWriter.WriteAttributeString("run-time", $combined_results.assemblyStartTime) + $xmlWriter.WriteAttributeString("time", $combined_results.assemblyTime) + + $xmlWriter.WriteStartElement("collection") + $xmlWriter.WriteAttributeString("name", $triplet) + $xmlWriter.WriteAttributeString("time", $combined_results.collectionTime) + + foreach ($testName in $allTests.Keys) { + $test = $allTests[$testName] + + $xmlWriter.WriteStartElement("test") + + $fullTestName = "$($testName):$triplet" + $xmlWriter.WriteAttributeString("name", $fullTestName) + $xmlWriter.WriteAttributeString("method", $fullTestName) + $xmlWriter.WriteAttributeString("time", $test.time) + $xmlWriter.WriteAttributeString("result", $test.result) + + switch ($test.result) { + "Pass" { } # Do nothing + "Fail" { + $xmlWriter.WriteStartElement("failure") + $xmlWriter.WriteStartElement("message") + $xmlWriter.WriteCData($test.message) + $xmlWriter.WriteEndElement() #message + $xmlWriter.WriteEndElement() #failure + } + "Skip" { + $xmlWriter.WriteStartElement("reason") + $xmlWriter.WriteCData($test.message) + $xmlWriter.WriteEndElement() #reason + } + } + + $xmlWriter.WriteEndElement() # test + } + + + $xmlWriter.WriteEndElement() # collection + $xmlWriter.WriteEndElement() # assembly + $xmlWriter.WriteEndElement() # assemblies + $xmlWriter.WriteEndDocument() + $xmlWriter.Flush() + $xmlWriter.Close() +} + +<# +.SYNOPSIS +Saves the failure logs, and prints information to the screen for CI. + +.DESCRIPTION +save_failure_logs takes the combined_results object, saves the failure +logs to the correct location for the CI to pick them up, and writes pretty +information to the screen for the CI logs, so that one knows what's wrong. + +.PARAMETER combined_results +The results object from combine_results. +#> +function save_failure_logs { + [CmdletBinding()] + Param( + $combined_results + ) + $triplet = $combined_results.collectionName + $allTests = $combined_results.allTests + + # abi_tags of missing results (if any exist) + $missing_results = @() + + foreach ($testName in $allTests.Keys) { + $test = $allTests[$testName] + if ($test.result -eq "Fail") { + $path_to_failure_Logs = Join-Path "$outputDir" "failureLogs" + if ( -not (Test-Path $path_to_failure_Logs)) { + mkdir $path_to_failure_Logs | Out-Null + } + $path_to_triplet_Logs = Join-Path $path_to_failure_Logs "$triplet" + if ( -not (Test-Path $path_to_triplet_Logs)) { + mkdir $path_to_triplet_Logs | Out-Null + } + + $abi_tag = $test.abi_tag + $sourceDirectory = Join-Path "$failurelogDir" "$($abi_tag.substring(0,2))" + $sourceFilename = Join-Path $sourceDirectory "$abi_tag.zip" + Write-Verbose "searching for $sourceFilename" + + if ( Test-Path $sourceFilename) { + Write-Verbose "found failure log file" + + Write-Verbose "Uncompressing $sourceFilename to $outputDir\failureLogs\$triplet\" + Write-Host "Uncompressing $sourceFilename to $outputDir\failureLogs\$triplet\" + + $destination = Join-Path (Join-Path "$outputDir" "failureLogs") "$triplet" + + Expand-Archive -Path $sourceFilename -Destination $destination -Force + } + elseif ($test.currentState -eq "Pass") { + # The port is building, but is marked as expected to fail. There are no failure logs. + # Write a log with instructions how to fix it. + Write-Verbose "The port is building but marked as expected to fail, adding readme.txt with fixit instructions" + + $out_filename = Join-Path (Join-Path (Join-Path (Join-Path "$outputDir" "failureLogs") "$triplet") "$($test.name)") "readme.txt" + + $message = "Congradulations! The port $($test.name) builds for $triplet!`n" + $message += "For the CI tests to recognize this, please update ci.baseline.txt in your PR.`n" + $message += "Remove the line that looks like this:`n" + $message += " $($test.name):$triplet=fail`n" + $message | Out-File $out_filename -Encoding ascii + } + else { + $missing_results += $test.abi_tag + Write-Verbose "Missing failure logs for $($test.name)" + Join-Path (Join-Path (Join-Path "$outputDir" "failureLogs") "$triplet" ) "$($test.name)" | % { mkdir $_ } | Out-Null + } + + + + if ((Convert-Path "$outputDir\failureLogs\$triplet\$($test.name)" | Get-ChildItem).count -eq 0) { + Write-Verbose "The logs are empty, adding readme.txt" + + $readme_path = Join-Path (Join-Path (Join-Path (Join-Path "$outputDir" "failureLogs") "$triplet") "$($test.name)") "readme.txt" + + $message = "There are no build logs for $($test.name) build.`n" + $message += "This is usually because the build failed early and outside of a task that is logged.`n" + $message += "See the console output logs from vcpkg for more information on the failure.`n" + $message += "If the console output of the $($test.name) is missing you can trigger a rebuild`n" + $message += "in the test system by making any whitespace change to one of the files under`n" + $message += "the ports/$($test.name) directory or by asking a member of the vcpkg team to remove the`n" + $message += "tombstone for abi tag $abi_tag`n" + $message | Out-File $readme_path -Encoding ascii + } + } + } + + if ($missing_results.count -ne 0) { + $missing_tag_filename = Join-Path (Join-Path (Join-Path "$outputDir" "failureLogs") "$triplet") "missing_abi_tags.txt" + $missing_results | Out-File -FilePath $missing_tag_filename -Encoding ascii + } + Write-Verbose "$triplet logs saved: $(Get-ChildItem $outputDir\failureLogs\$triplet\ -ErrorAction Ignore)" + +} + +<# +.SYNOPSIS +Writes a pretty summary table to the CI log. + +.DESCRIPTION +Takes a hashtable which maps triplets to objects returned by the combine_results +cmdlet, and a list of missing triplets, and prints a really pretty summary table +to the CI logs. + +.PARAMETER complete_results +A hashtable which maps triplets to combine_results objects. + +.PARAMETER missing_triplets +A list of missing triplets. +#> +function write_summary_table { + [CmdletBinding()] + Param( + $complete_results, + $missing_triplets + ) + + $table = "" + + foreach ($triplet in $complete_results.Keys) { + $triplet_results = $complete_results[$triplet] + + if ($triplet_results.allTests.count -eq 0) { + $table += "$triplet CI build test results are clean`n`n" + } + else { + $portWidth = $triplet.length + #calculate the width of the first column + foreach ($testName in $triplet_results.allTests.Keys) { + $test = $triplet_results.allTests[$testName] + if ($portWidth -lt $test.name.length) { + $portWidth = $test.name.length + } + } + + # the header + $table += "|{0,-$portWidth}|result|features|notes`n" -f $triplet + $table += "|{0}|----|--------|-----`n" -f ("-" * $portWidth) + + # add each port results + foreach ($testName in $triplet_results.allTests.Keys | Sort-Object) { + $test = $triplet_results.allTests[$testName] + $notes = "" + if ($test.result -eq 'Fail') { + $notes = "**Regression**" + } + elseif ($test.result -eq 'Skip') { + if ($test.currentResult -eq 'Fail') { + $notes = "Previously skipped, not a regression" + } + else { + $notes = "Missing port dependency" + } + } + $notes = $test.message + $table += "|{0,-$portWidth}|{1,-4}|{2}|{3}`n" -f $test.name, $test.currentResult, $test.features, $notes + } + $table += "`n" + } + if ($triplet_results.ignored.Count -ne 0) { + $table += "The following build failures were ignored: $($triplet_results.ignored)`n" + } + } + + # Add list of missing triplets to the table + foreach ($triplet in $missing_triplets.Keys) { + $table += "$triplet results are inconclusive because it is missing logs from test run`n`n" + } + + $table +} + +<# +.SYNOPSIS +Writes short errors to the CI logs. + +.DESCRIPTION +write_errors_for_summary takes a hashtable from triplets to combine_results +objects, and writes short errors to the CI logs. + +.PARAMETER complete_results +A hashtable from triplets to combine_results objects. +#> +function write_errors_for_summary { + [CmdletBinding()] + Param( + $complete_results + ) + + $failure_found = $false + + Write-Verbose "preparing error output for Azure Devops" + + foreach ($triplet in $complete_results.Keys) { + $triplet_results = $complete_results[$triplet] + + Write-Verbose "searching $triplet triplet" + + # add each port results + foreach ($testName in $triplet_results.allTests.Keys) { + $test = $triplet_results.allTests[$testName] + + Write-Verbose "checking $($testName):$triplet $($test.result)" + + if ($test.result -eq 'Fail') { + $failure_found = $true + if ($test.currentResult -eq "pass") { + [System.Console]::Error.WriteLine( ` + "PASSING, REMOVE FROM FAIL LIST: $($test.name):$triplet ($baselineFile)" ` + ) + } + else { + [System.Console]::Error.WriteLine( ` + "REGRESSION: $($test.name):$triplet. If expected, add $($test.name):$triplet=fail to $baselineFile." ` + ) + } + } + } + } +} + + +$complete_results = @{ } +$missing_triplets = @{ } +foreach ( $triplet in $triplets) { + Write-Verbose "looking for $triplet logs" + + # The standard name for logs is: + # .xml + # for example: + # x64-linux.xml + + $current_test_hash = build_test_results( Convert-Path "$logDir\$($triplet).xml" ) + $baseline_results = build_baseline_results -baselineFile $baselineFile -triplet $triplet + + if ($current_test_hash -eq $null) { + [System.Console]::Error.WriteLine("Missing $triplet test results in current test run") + $missing_triplets[$triplet] = "test" + } + else { + Write-Verbose "combining results..." + $complete_results[$triplet] = combine_results -baseline $baseline_results -current $current_test_hash + } +} + +Write-Verbose "done analizing results" + +# If there is only one triplet, add the triplet name to the result table file +if ($triplets.Count -eq 1) { + $result_table_name = $triplets[0] +} +else { + $result_table_name = "" +} + +if (-not $noTable) { + $table_path = Join-Path "$outputDir" "result_table$result_table_name.md" + + write_summary_table -complete_results $complete_results -missing_triplets $missing_triplets | Out-File -FilePath $table_path -Encoding ascii + + Write-Host "" + cat $table_path + + Write-Host "##vso[task.addattachment type=Distributedtask.Core.Summary;name=$result_table_name issue summary;]$table_path" +} + +foreach ( $triplet in $complete_results.Keys) { + $combined_results = $complete_results[$triplet] + if ( $failurelogDir -ne "") { + save_failure_logs -combined_results $combined_results + } + + write_xunit_results -combined_results $combined_results +} + + +# emit error last. Unlike the table output this is going to be seen in the "status" section of the pipeline +# and needs to be formatted for a single line. +if ($errorOnRegression) { + write_errors_for_summary -complete_results $complete_results + + if ($missing_triplets.Count -ne 0) { + $regression_log_directory = Join-Path "$outputDir" "failureLogs" + if ( -not (Test-Path $regression_log_directory)) { + mkdir $regression_log_directory | Out-Null + } + $file_path = Join-Path $regression_log_directory "missing_test_results.txt" + $message = "Test logs are missing for the following triplets: $($hash.Keys | %{"$($_)($($hash[$_]))"})`n" + $message += "Without this information the we are unable to determine if the build has regressions. `n" + $message += "Missing test logs are sometimes the result of failures in the pipeline infrastructure. `n" + $message += "If you beleave this is the case please alert a member of the vcpkg team to investigate. `n" + $message | Out-File $file_path -Encoding ascii + } +} diff --git a/scripts/azure-pipelines/azure-pipelines.yml b/scripts/azure-pipelines/azure-pipelines.yml new file mode 100644 index 000000000..c6c8a49f0 --- /dev/null +++ b/scripts/azure-pipelines/azure-pipelines.yml @@ -0,0 +1,56 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT +# + +variables: + clean-tombstones: 'false' +stages: + - stage: Clean_Tombstones + displayName: 'Clean Tombstones' + pool: + name: PrWin-2020-04-21-1 + jobs: + - job: + displayName: 'Clean Tombstones' + condition: eq(variables['clean-tombstones'], 'true') + timeoutInMinutes: 10 + steps: + - task: PowerShell@2 + displayName: 'Initialize Environment' + inputs: + filePath: 'scripts/azure-pipelines/windows/initialize-environment.ps1' + - powershell: | + Remove-Item archives\fail -Force -Recurse + displayName: 'Delete archives\fail' + + - stage: Build + jobs: + - template: windows/azure-pipelines.yml + parameters: + triplet: x86-windows + jobName: x86_windows + + - template: windows/azure-pipelines.yml + parameters: + triplet: x64-windows + jobName: x64_windows + + - template: windows/azure-pipelines.yml + parameters: + triplet: x64-windows-static + jobName: x64_windows_static + + - template: windows/azure-pipelines.yml + parameters: + triplet: x64-uwp + jobName: x64_uwp + + - template: windows/azure-pipelines.yml + parameters: + triplet: arm64-windows + jobName: arm64_windows + + - template: windows/azure-pipelines.yml + parameters: + triplet: arm-uwp + jobName: arm_uwp diff --git a/scripts/azure-pipelines/generate-skip-list.ps1 b/scripts/azure-pipelines/generate-skip-list.ps1 new file mode 100644 index 000000000..98c868eb9 --- /dev/null +++ b/scripts/azure-pipelines/generate-skip-list.ps1 @@ -0,0 +1,72 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT +# + +<# +.SYNOPSIS +Generates a list of ports to skip in the CI. + +.DESCRIPTION +generate-skip-list takes a triplet, and the path to the ci.baseline.txt +file, and generates a skip list string to pass to vcpkg. + +.PARAMETER Triplet +The triplet to find skipped ports for. + +.PARAMETER BaselineFile +The path to the ci.baseline.txt file. +#> +[CmdletBinding()] +Param( + [string]$Triplet, + [string]$BaselineFile +) + +$ErrorActionPreference = 'Stop' + +if (-not (Test-Path -Path $BaselineFile)) { + Write-Error "Unable to find baseline file $BaselineFile" +} + +#read in the file, strip out comments and blank lines and spaces +$baselineListRaw = Get-Content -Path $BaselineFile ` + | Where-Object { -not ($_ -match "\s*#") } ` + | Where-Object { -not ( $_ -match "^\s*$") } ` + | ForEach-Object { $_ -replace "\s" } + +############################################################### +# This script is running at the beginning of the CI test, so do a little extra +# checking so things can fail early. + +#verify everything has a valid value +$missingValues = $baselineListRaw | Where-Object { -not ($_ -match "=\w") } + +if ($missingValues) { + Write-Error "The following are missing values: $missingValues" +} + +$invalidValues = $baselineListRaw ` + | Where-Object { -not ($_ -match "=(skip|pass|fail|ignore)$") } + +if ($invalidValues) { + Write-Error "The following have invalid values: $invalidValues" +} + +$baselineForTriplet = $baselineListRaw ` + | Where-Object { $_ -match ":$Triplet=" } + +# Verify there are no duplicates (redefinitions are not allowed) +$file_map = @{ } +foreach ($port in $baselineForTriplet | ForEach-Object { $_ -replace ":.*$" }) { + if ($null -ne $file_map[$port]) { + Write-Error ` + "$($port):$($Triplet) has multiple definitions in $baselineFile" + } + $file_map[$port] = $true +} + +# Format the skip list for the command line +$skip_list = $baselineForTriplet ` + | Where-Object { $_ -match "=skip$" } ` + | ForEach-Object { $_ -replace ":.*$" } +[string]::Join(",", $skip_list) diff --git a/scripts/azure-pipelines/windows/azure-pipelines.yml b/scripts/azure-pipelines/windows/azure-pipelines.yml new file mode 100644 index 000000000..1913f0c9b --- /dev/null +++ b/scripts/azure-pipelines/windows/azure-pipelines.yml @@ -0,0 +1,66 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT +# + +jobs: +- job: ${{ parameters.jobName }} + pool: + name: PrWin-2020-04-21-1 + + variables: + triplet: '${{ parameters.triplet }}' + + timeoutInMinutes: 1440 # 1 day + + steps: + - task: PowerShell@2 + displayName: 'Initialize Environment' + inputs: + filePath: 'scripts/azure-pipelines/windows/initialize-environment.ps1' + + - powershell: | + $baselineFile = "$(System.DefaultWorkingDirectory)\scripts\ci.baseline.txt" + $skipList = $(System.DefaultWorkingDirectory)\scripts\azure-pipelines\generate-skip-list.ps1 -Triplet "$(triplet)" -BaselineFile $baselineFile + Write-Host "baseline file: $baselineFile" + Write-Host "skip list: $skipList" + $(System.DefaultWorkingDirectory)\scripts\azure-pipelines\windows\ci-step.ps1 -Triplet "$(triplet)" -ExcludePorts $skipList + Write-Host "CI test script is complete" + errorActionPreference: continue + displayName: '** Build vcpkg and test ports **' + + - powershell: | + $baseName = "$(triplet)" + $outputPathRoot = "$(System.ArtifactsDirectory)\raw xml results" + if(-not (Test-Path $outputPathRoot)) + { + Write-Host "creating $outputPathRoot" + mkdir $outputPathRoot | Out-Null + } + + $xmlPath = "$(System.DefaultWorkingDirectory)\test-full-ci.xml" + $outputXmlPath = "$outputPathRoot\$baseName.xml" + + cp $xmlPath $(Build.ArtifactStagingDirectory) + Move-Item $xmlPath -Destination $outputXmlPath + + # already in DevOps, no need for extra copies + rm $(System.DefaultWorkingDirectory)\console-out.txt -ErrorAction Ignore + + Remove-Item "$(System.DefaultWorkingDirectory)\buildtrees\*" -Recurse -errorAction silentlycontinue + Remove-Item "$(System.DefaultWorkingDirectory)\packages\*" -Recurse -errorAction silentlycontinue + Remove-Item "$(System.DefaultWorkingDirectory)\installed\*" -Recurse -errorAction silentlycontinue + displayName: 'Collect logs and cleanup build' + + - task: PowerShell@2 + displayName: 'Analyze results and prepare test logs' + inputs: + failOnStderr: true + filePath: 'scripts/azure-pipelines/analyze-test-results.ps1' + arguments: '-baselineFile ''$(System.DefaultWorkingDirectory)\scripts\ci.baseline.txt'' -logDir ''$(System.ArtifactsDirectory)\raw xml results'' -failurelogDir ''archives\fail'' -outputDir ''$(Build.ArtifactStagingDirectory)'' -errorOnRegression -triplets ''$(triplet)''' + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: $(triplet) port build failure logs' + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)\failureLogs' + ArtifactName: '$(triplet) port build failure logs' + condition: failed() diff --git a/scripts/azure-pipelines/windows/ci-step.ps1 b/scripts/azure-pipelines/windows/ci-step.ps1 new file mode 100644 index 000000000..0e07895e0 --- /dev/null +++ b/scripts/azure-pipelines/windows/ci-step.ps1 @@ -0,0 +1,163 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT +# + +<# +.SYNOPSIS +Runs the bootstrap and port install parts of the vcpkg CI for Windows + +.DESCRIPTION +There are multiple steps to the vcpkg CI; this is the most important one. +First, it runs `boostrap-vcpkg.bat` in order to build the tool itself; it +then installs either all of the ports specified, or all of the ports excluding +those which are passed in $ExcludePorts. Then, it runs `vcpkg ci` to access the +data, and prints all of the failures and successes to the Azure console. + +.PARAMETER Triplet +The triplet to run the installs for -- one of the triplets known by vcpkg, like +`x86-windows` and `x64-windows`. + +.PARAMETER OnlyIncludePorts +The set of ports to install. + +.PARAMETER ExcludePorts +If $OnlyIncludePorts is not passed, this set of ports is used to exclude ports to +install from the set of all ports. + +.PARAMETER AdditionalVcpkgFlags +Flags to pass to vcpkg in addition to the ports to install, and the triplet. +#> +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)][string]$Triplet, + [string]$OnlyIncludePorts = '', + [string]$ExcludePorts = '', + [string]$AdditionalVcpkgFlags = '' +) + +Set-StrictMode -Version Latest + +$scriptsDir = Split-Path -parent $script:MyInvocation.MyCommand.Definition + +<# +.SYNOPSIS +Gets the first parent directory D of $startingDir such that D/$filename is a file. + +.DESCRIPTION +Get-FileRecursivelyUp Looks for a directory containing $filename, starting in +$startingDir, and then checking each parent directory of $startingDir in turn. +It returns the first directory it finds. +If the file is not found, the empty string is returned - this is likely to be +a bug. + +.PARAMETER startingDir +The directory to start looking for $filename in. + +.PARAMETER filename +The filename to look for. +#> +function Get-FileRecursivelyUp() { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)][string]$startingDir, + [Parameter(Mandatory = $true)][string]$filename + ) + + $currentDir = $startingDir + + while ($currentDir.Length -gt 0 -and -not (Test-Path "$currentDir\$filename")) { + Write-Verbose "Examining $currentDir for $filename" + $currentDir = Split-Path $currentDir -Parent + } + + if ($currentDir.Length -eq 0) { + Write-Warning "None of $startingDir's parent directories contain $filename. This is likely a bug." + } + + Write-Verbose "Examining $currentDir for $filename - Found" + return $currentDir +} + +<# +.SYNOPSIS +Removes a file or directory, with backoff in the directory case. + +.DESCRIPTION +Remove-Item -Recurse occasionally fails spuriously; in order to get around this, +we remove with backoff. At a maximum, we will wait 180s before giving up. + +.PARAMETER Path +The path to remove. +#> +function Remove-VcpkgItem { + [CmdletBinding()] + param([Parameter(Mandatory = $true)][string]$Path) + + if ([string]::IsNullOrEmpty($Path)) { + return + } + + if (Test-Path $Path) { + # Remove-Item -Recurse occasionally fails. This is a workaround + if ((Get-Item $Path) -is [System.IO.DirectoryInfo]) { + Remove-Item $Path -Force -Recurse -ErrorAction SilentlyContinue + for ($i = 0; $i -le 60 -and (Test-Path $Path); $i++) { # ~180s max wait time + Start-Sleep -m (100 * $i) + Remove-Item $Path -Force -Recurse -ErrorAction SilentlyContinue + } + + if (Test-Path $Path) { + Write-Error "$Path was unable to be fully deleted." + throw; + } + } + else { + Remove-Item $Path -Force + } + } +} + +$vcpkgRootDir = Get-FileRecursivelyUp $scriptsDir .vcpkg-root + +Write-Host "Bootstrapping vcpkg ..." +& "$vcpkgRootDir\bootstrap-vcpkg.bat" -Verbose +if (!$?) { throw "bootstrap failed" } +Write-Host "Bootstrapping vcpkg ... done." + +$ciXmlPath = "$vcpkgRootDir\test-full-ci.xml" +$consoleOuputPath = "$vcpkgRootDir\console-out.txt" +Remove-VcpkgItem $ciXmlPath + +$env:VCPKG_FEATURE_FLAGS = "binarycaching" + +if (![string]::IsNullOrEmpty($OnlyIncludePorts)) { + ./vcpkg install --triplet $Triplet $OnlyIncludePorts $AdditionalVcpkgFlags ` + "--x-xunit=$ciXmlPath" | Tee-Object -FilePath "$consoleOuputPath" +} +else { + $exclusions = "" + if (![string]::IsNullOrEmpty($ExcludePorts)) { + $exclusions = "--exclude=$ExcludePorts" + } + + if ( $Triplet -notmatch "x86-windows" -and $Triplet -notmatch "x64-windows" ) { + # WORKAROUND: the x86-windows flavors of these are needed for all + # cross-compilation, but they are not auto-installed. + # Install them so the CI succeeds + ./vcpkg install "protobuf:x86-windows" "boost-build:x86-windows" "sqlite3:x86-windows" + if (-not $?) { throw "Failed to install protobuf & boost-build & sqlite3" } + } + + # Turn all error messages into strings for output in the CI system. + # This is needed due to the way the public Azure DevOps turns error output to pipeline errors, + # even when told to ignore error output. + ./vcpkg ci $Triplet $AdditionalVcpkgFlags "--x-xunit=$ciXmlPath" $exclusions 2>&1 ` + | ForEach-Object { + if ($_ -is [System.Management.Automation.ErrorRecord]) { $_.ToString() } else { $_ } + } + + # Phasing out the console output (it is already saved in DevOps) Create a dummy file for now. + Set-Content -LiteralPath "$consoleOuputPath" -Value '' +} + +Write-Host "CI test is complete" diff --git a/scripts/azure-pipelines/windows/create-vmss.ps1 b/scripts/azure-pipelines/windows/create-vmss.ps1 new file mode 100644 index 000000000..099c7dbfb --- /dev/null +++ b/scripts/azure-pipelines/windows/create-vmss.ps1 @@ -0,0 +1,458 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT +# +# + +<# +.SYNOPSIS +Creates a Windows virtual machine scale set, set up for vcpkg's CI. + +.DESCRIPTION +create-vmss.ps1 creates an Azure Windows VM scale set, set up for vcpkg's CI +system. See https://docs.microsoft.com/en-us/azure/virtual-machine-scale-sets/overview +for more information. + +This script assumes you have installed Azure tools into PowerShell by following the instructions +at https://docs.microsoft.com/en-us/powershell/azure/install-az-ps?view=azps-3.6.1 +or are running from Azure Cloud Shell. +#> + +$Location = 'SouthCentralUS' +$Prefix = 'PrWin-' + (Get-Date -Format 'yyyy-MM-dd') +$VMSize = 'Standard_F16s_v2' +$ProtoVMName = 'PROTOTYPE' +$LiveVMPrefix = 'BUILD' +$WindowsServerSku = '2019-Datacenter' +$InstalledDiskSizeInGB = 1024 +$ErrorActionPreference = 'Stop' + +$ProgressActivity = 'Creating Scale Set' +$TotalProgress = 12 +$CurrentProgress = 1 + +<# +.SYNOPSIS +Returns whether there's a name collision in the resource group. + +.DESCRIPTION +Find-ResourceGroupNameCollision takes a list of resources, and checks if $Test +collides names with any of the resources. + +.PARAMETER Test +The name to test. + +.PARAMETER Resources +The list of resources. +#> +function Find-ResourceGroupNameCollision { + [CmdletBinding()] + Param([string]$Test, $Resources) + + foreach ($resource in $Resources) { + if ($resource.ResourceGroupName -eq $Test) { + return $true + } + } + + return $false +} + +<# +.SYNOPSIS +Attempts to find a name that does not collide with any resources in the resource group. + +.DESCRIPTION +Find-ResourceGroupName takes a set of resources from Get-AzResourceGroup, and finds the +first name in {$Prefix, $Prefix-1, $Prefix-2, ...} such that the name doesn't collide with +any of the resources in the resource group. + +.PARAMETER Prefix +The prefix of the final name; the returned name will be of the form "$Prefix(-[1-9][0-9]*)?" +#> +function Find-ResourceGroupName { + [CmdletBinding()] + Param([string] $Prefix) + + $resources = Get-AzResourceGroup + $result = $Prefix + $suffix = 0 + while (Find-ResourceGroupNameCollision -Test $result -Resources $resources) { + $suffix++ + $result = "$Prefix-$suffix" + } + + return $result +} + +<# +.SYNOPSIS +Creates a randomly generated password. + +.DESCRIPTION +New-Password generates a password, randomly, of length $Length, containing +only alphanumeric characters (both uppercase and lowercase). + +.PARAMETER Length +The length of the returned password. +#> +function New-Password { + Param ([int] $Length = 32) + + $Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + $result = '' + for ($idx = 0; $idx -lt $Length; $idx++) { + # NOTE: this should probably use RNGCryptoServiceProvider + $result += $Chars[(Get-Random -Minimum 0 -Maximum $Chars.Length)] + } + + return $result +} + +<# +.SYNOPSIS +Waits for the shutdown of the specified resource. + +.DESCRIPTION +Wait-Shutdown takes a VM, and checks if there's a 'PowerState/stopped' +code; if there is, it returns. If there isn't, it waits ten seconds and +tries again. + +.PARAMETER ResourceGroupName +The name of the resource group to look up the VM in. + +.PARAMETER Name +The name of the virtual machine to wait on. +#> +function Wait-Shutdown { + [CmdletBinding()] + Param([string]$ResourceGroupName, [string]$Name) + + Write-Host "Waiting for $Name to stop..." + while ($true) { + $Vm = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $Name -Status + $highestStatus = $Vm.Statuses.Count + for ($idx = 0; $idx -lt $highestStatus; $idx++) { + if ($Vm.Statuses[$idx].Code -eq 'PowerState/stopped') { + return + } + } + + Write-Host "... not stopped yet, sleeping for 10 seconds" + Start-Sleep -Seconds 10 + } +} + +<# +.SYNOPSIS +Sanitizes a name to be used in a storage account. + +.DESCRIPTION +Sanitize-Name takes a string, and removes all of the '-'s and +lowercases the string, since storage account names must have no +'-'s and must be completely lowercase alphanumeric. It then makes +certain that the length of the string is not greater than 24, +since that is invalid. + +.PARAMETER RawName +The name to sanitize. +#> +function Sanitize-Name { + [CmdletBinding()] + Param( + [string]$RawName + ) + + $result = $RawName.Replace('-', '').ToLowerInvariant() + if ($result.Length -gt 24) { + Write-Error 'Sanitized name for storage account $result was too long.' + } + + return $result +} + +#################################################################################################### +Write-Progress ` + -Activity $ProgressActivity ` + -Status 'Creating resource group' ` + -PercentComplete (100 / $TotalProgress * $CurrentProgress++) + +$ResourceGroupName = Find-ResourceGroupName $Prefix +$AdminPW = New-Password +New-AzResourceGroup -Name $ResourceGroupName -Location $Location +$AdminPWSecure = ConvertTo-SecureString $AdminPW -AsPlainText -Force +$Credential = New-Object System.Management.Automation.PSCredential ("AdminUser", $AdminPWSecure) + +#################################################################################################### +Write-Progress ` + -Activity $ProgressActivity ` + -Status 'Creating virtual network' ` + -PercentComplete (100 / $TotalProgress * $CurrentProgress++) + +$allowHttp = New-AzNetworkSecurityRuleConfig ` + -Name AllowHTTP ` + -Description 'Allow HTTP(S)' ` + -Access Allow ` + -Protocol Tcp ` + -Direction Outbound ` + -Priority 1008 ` + -SourceAddressPrefix * ` + -SourcePortRange * ` + -DestinationAddressPrefix * ` + -DestinationPortRange @(80, 443) + +$allowDns = New-AzNetworkSecurityRuleConfig ` + -Name AllowDNS ` + -Description 'Allow DNS' ` + -Access Allow ` + -Protocol * ` + -Direction Outbound ` + -Priority 1009 ` + -SourceAddressPrefix * ` + -SourcePortRange * ` + -DestinationAddressPrefix * ` + -DestinationPortRange 53 + +$allowStorage = New-AzNetworkSecurityRuleConfig ` + -Name AllowStorage ` + -Description 'Allow Storage' ` + -Access Allow ` + -Protocol * ` + -Direction Outbound ` + -Priority 1010 ` + -SourceAddressPrefix VirtualNetwork ` + -SourcePortRange * ` + -DestinationAddressPrefix Storage ` + -DestinationPortRange * + +$denyEverythingElse = New-AzNetworkSecurityRuleConfig ` + -Name DenyElse ` + -Description 'Deny everything else' ` + -Access Deny ` + -Protocol * ` + -Direction Outbound ` + -Priority 1011 ` + -SourceAddressPrefix * ` + -SourcePortRange * ` + -DestinationAddressPrefix * ` + -DestinationPortRange * + +$NetworkSecurityGroupName = $ResourceGroupName + 'NetworkSecurity' +$NetworkSecurityGroup = New-AzNetworkSecurityGroup ` + -Name $NetworkSecurityGroupName ` + -ResourceGroupName $ResourceGroupName ` + -Location $Location ` + -SecurityRules @($allowHttp, $allowDns, $allowStorage, $denyEverythingElse) + +$SubnetName = $ResourceGroupName + 'Subnet' +$Subnet = New-AzVirtualNetworkSubnetConfig ` + -Name $SubnetName ` + -AddressPrefix "10.0.0.0/16" ` + -NetworkSecurityGroup $NetworkSecurityGroup + +$VirtualNetworkName = $ResourceGroupName + 'Network' +$VirtualNetwork = New-AzVirtualNetwork ` + -Name $VirtualNetworkName ` + -ResourceGroupName $ResourceGroupName ` + -Location $Location ` + -AddressPrefix "10.0.0.0/16" ` + -Subnet $Subnet + +#################################################################################################### +Write-Progress ` + -Activity $ProgressActivity ` + -Status 'Creating archives storage account' ` + -PercentComplete (100 / $TotalProgress * $CurrentProgress++) + +$StorageAccountName = Sanitize-Name $ResourceGroupName + +New-AzStorageAccount ` + -ResourceGroupName $ResourceGroupName ` + -Location $Location ` + -Name $StorageAccountName ` + -SkuName 'Standard_LRS' ` + -Kind StorageV2 + +$StorageAccountKeys = Get-AzStorageAccountKey ` + -ResourceGroupName $ResourceGroupName ` + -Name $StorageAccountName + +$StorageAccountKey = $StorageAccountKeys[0].Value + +$StorageContext = New-AzStorageContext ` + -StorageAccountName $StorageAccountName ` + -StorageAccountKey $StorageAccountKey + +$ArchivesFiles = New-AzStorageShare -Name 'archives' -Context $StorageContext +Set-AzStorageShareQuota -ShareName 'archives' -Context $StorageContext -Quota 5120 +$LogFiles = New-AzStorageShare -Name 'logs' -Context $StorageContext +Set-AzStorageShareQuota -ShareName 'logs' -Context $StorageContext -Quota 64 + +#################################################################################################### +Write-Progress ` + -Activity 'Creating prototype VM' ` + -PercentComplete (100 / $TotalProgress * $CurrentProgress++) + +$NicName = $ResourceGroupName + 'NIC' +$Nic = New-AzNetworkInterface ` + -Name $NicName ` + -ResourceGroupName $ResourceGroupName ` + -Location $Location ` + -Subnet $VirtualNetwork.Subnets[0] + +$VM = New-AzVMConfig -Name $ProtoVMName -VMSize $VMSize +$VM = Set-AzVMOperatingSystem ` + -VM $VM ` + -Windows ` + -ComputerName $ProtoVMName ` + -Credential $Credential ` + -ProvisionVMAgent ` + -EnableAutoUpdate + +$VM = Add-AzVMNetworkInterface -VM $VM -Id $Nic.Id +$VM = Set-AzVMSourceImage ` + -VM $VM ` + -PublisherName 'MicrosoftWindowsServer' ` + -Offer 'WindowsServer' ` + -Skus $WindowsServerSku ` + -Version latest + +$InstallDiskName = $ProtoVMName + "InstallDisk" +$VM = Add-AzVMDataDisk ` + -Vm $VM ` + -Name $InstallDiskName ` + -Lun 0 ` + -Caching ReadWrite ` + -CreateOption Empty ` + -DiskSizeInGB $InstalledDiskSizeInGB ` + -StorageAccountType 'StandardSSD_LRS' + +$VM = Set-AzVMBootDiagnostic -VM $VM -Disable +New-AzVm ` + -ResourceGroupName $ResourceGroupName ` + -Location $Location ` + -VM $VM + +#################################################################################################### +Write-Progress ` + -Activity $ProgressActivity ` + -Status 'Running provisioning script provision-image.ps1 in VM' ` + -PercentComplete (100 / $TotalProgress * $CurrentProgress++) + +Invoke-AzVMRunCommand ` + -ResourceGroupName $ResourceGroupName ` + -VMName $ProtoVMName ` + -CommandId 'RunPowerShellScript' ` + -ScriptPath "$PSScriptRoot\provision-image.ps1" ` + -Parameter @{AdminUserPassword = $AdminPW; ` + StorageAccountName=$StorageAccountName; ` + StorageAccountKey=$StorageAccountKey;} + +#################################################################################################### +Write-Progress ` + -Activity $ProgressActivity ` + -Status 'Restarting VM' ` + -PercentComplete (100 / $TotalProgress * $CurrentProgress++) + +Restart-AzVM -ResourceGroupName $ResourceGroupName -Name $ProtoVMName + +#################################################################################################### +Write-Progress ` + -Activity $ProgressActivity ` + -Status 'Running provisioning script sysprep.ps1 in VM' ` + -PercentComplete (100 / $TotalProgress * $CurrentProgress++) + +Invoke-AzVMRunCommand ` + -ResourceGroupName $ResourceGroupName ` + -VMName $ProtoVMName ` + -CommandId 'RunPowerShellScript' ` + -ScriptPath "$PSScriptRoot\sysprep.ps1" + +#################################################################################################### +Write-Progress ` + -Activity $ProgressActivity ` + -Status 'Waiting for VM to shut down' ` + -PercentComplete (100 / $TotalProgress * $CurrentProgress++) + +Wait-Shutdown -ResourceGroupName $ResourceGroupName -Name $ProtoVMName + +#################################################################################################### +Write-Progress ` + -Activity $ProgressActivity ` + -Status 'Converting VM to Image' ` + -PercentComplete (100 / $TotalProgress * $CurrentProgress++) + +Stop-AzVM ` + -ResourceGroupName $ResourceGroupName ` + -Name $ProtoVMName ` + -Force + +Set-AzVM ` + -ResourceGroupName $ResourceGroupName ` + -Name $ProtoVMName ` + -Generalized + +$VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $ProtoVMName +$PrototypeOSDiskName = $VM.StorageProfile.OsDisk.Name +$ImageConfig = New-AzImageConfig -Location $Location -SourceVirtualMachineId $VM.ID +$Image = New-AzImage -Image $ImageConfig -ImageName $ProtoVMName -ResourceGroupName $ResourceGroupName + +#################################################################################################### +Write-Progress ` + -Activity $ProgressActivity ` + -Status 'Deleting unused VM and disk' ` + -PercentComplete (100 / $TotalProgress * $CurrentProgress++) + +Remove-AzVM -Id $VM.ID -Force +Remove-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $PrototypeOSDiskName -Force +Remove-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $InstallDiskName -Force + +#################################################################################################### +Write-Progress ` + -Activity $ProgressActivity ` + -Status 'Creating scale set' ` + -PercentComplete (100 / $TotalProgress * $CurrentProgress++) + +$VmssIpConfigName = $ResourceGroupName + 'VmssIpConfig' +$VmssIpConfig = New-AzVmssIpConfig -SubnetId $Nic.IpConfigurations[0].Subnet.Id -Primary -Name $VmssIpConfigName +$VmssName = $ResourceGroupName + 'Vmss' +$Vmss = New-AzVmssConfig ` + -Location $Location ` + -SkuCapacity 6 ` + -SkuName $VMSize ` + -SkuTier 'Standard' ` + -Overprovision $false ` + -UpgradePolicyMode Manual + +$Vmss = Add-AzVmssNetworkInterfaceConfiguration ` + -VirtualMachineScaleSet $Vmss ` + -Primary $true ` + -IpConfiguration $VmssIpConfig ` + -NetworkSecurityGroupId $NetworkSecurityGroup.Id ` + -Name $NicName + +$Vmss = Set-AzVmssOsProfile ` + -VirtualMachineScaleSet $Vmss ` + -ComputerNamePrefix $LiveVMPrefix ` + -AdminUsername 'AdminUser' ` + -AdminPassword $AdminPW ` + -WindowsConfigurationProvisionVMAgent $true ` + -WindowsConfigurationEnableAutomaticUpdate $true + +$Vmss = Set-AzVmssStorageProfile ` + -VirtualMachineScaleSet $Vmss ` + -OsDiskCreateOption 'FromImage' ` + -OsDiskCaching ReadWrite ` + -ImageReferenceId $Image.Id + +New-AzVmss ` + -ResourceGroupName $ResourceGroupName ` + -Name $VmssName ` + -VirtualMachineScaleSet $Vmss + +#################################################################################################### +Write-Progress -Activity $ProgressActivity -Completed +Write-Host "Location: $Location" +Write-Host "Resource group name: $ResourceGroupName" +Write-Host "User name: AdminUser" +Write-Host "Using generated password: $AdminPW" +Write-Host 'Finished!' diff --git a/scripts/azure-pipelines/windows/initialize-environment.ps1 b/scripts/azure-pipelines/windows/initialize-environment.ps1 new file mode 100644 index 000000000..b86006a9c --- /dev/null +++ b/scripts/azure-pipelines/windows/initialize-environment.ps1 @@ -0,0 +1,93 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT +# +<# +.SYNOPSIS +Sets up the environment to run other vcpkg CI steps in an Azure Pipelines job. + +.DESCRIPTION +This script maps network drives from infrastructure and cleans out anything that +might have been leftover from a previous run. + +.PARAMETER ForceAllPortsToRebuildKey +A subdirectory / key to use to force a build without any previous run caching, +if necessary. +#> + +[CmdletBinding()] +Param( + [string]$ForceAllPortsToRebuildKey = '' +) + +$StorageAccountName = $env:StorageAccountName +$StorageAccountKey = $env:StorageAccountKey + +function Remove-DirectorySymlink { + Param([string]$Path) + if (Test-Path $Path) { + [System.IO.Directory]::Delete($Path) + } +} + +Write-Host 'Setting up archives mount' +if (-Not (Test-Path W:)) { + net use W: "\\$StorageAccountName.file.core.windows.net\archives" /u:"AZURE\$StorageAccountName" $StorageAccountKey +} + +Write-Host 'Setting up logs mount' +if (-Not (Test-Path L:)) { + net use L: "\\$StorageAccountName.file.core.windows.net\logs" /u:"AZURE\$StorageAccountName" $StorageAccountKey +} + +Write-Host 'Creating downloads directory' +mkdir D:\downloads -ErrorAction SilentlyContinue + +# Delete entries in the downloads folder, except: +# those in the 'tools' folder +# those last accessed in the last 30 days +Get-ChildItem -Path D:\downloads -Exclude "tools" ` + | Where-Object{ $_.LastAccessTime -lt (get-Date).AddDays(-30) } ` + | ForEach-Object{Remove-Item -Path $_ -Recurse -Force} + +# Msys sometimes leaves a database lock file laying around, especially if there was a failed job +# which causes unrelated failures in jobs that run later on the machine. +# work around this by just removing the vcpkg installed msys2 if it exists +if( Test-Path D:\downloads\tools\msys2 ) +{ + Write-Host "removing previously installed msys2" + Remove-Item D:\downloads\tools\msys2 -Recurse -Force +} + +Write-Host 'Setting up archives path...' +if ([string]::IsNullOrWhiteSpace($ForceAllPortsToRebuildKey)) +{ + $archivesPath = 'W:\' +} +else +{ + $archivesPath = "W:\force\$ForceAllPortsToRebuildKey" + if (-Not (Test-Path $fullPath)) { + Write-Host 'Creating $archivesPath' + mkdir $archivesPath + } +} + +Write-Host "Linking archives => $archivesPath" +Remove-DirectorySymlink archives +cmd /c "mklink /D archives $archivesPath" + +Write-Host 'Linking installed => E:\installed' +Remove-DirectorySymlink installed +Remove-Item E:\installed -Recurse -Force -ErrorAction SilentlyContinue +mkdir E:\installed +cmd /c "mklink /D installed E:\installed" + +Write-Host 'Linking downloads => D:\downloads' +Remove-DirectorySymlink downloads +cmd /c "mklink /D downloads D:\downloads" + +Write-Host 'Cleaning buildtrees' +Remove-Item buildtrees\* -Recurse -Force -errorAction silentlycontinue + +Write-Host 'Cleaning packages' +Remove-Item packages\* -Recurse -Force -errorAction silentlycontinue diff --git a/scripts/azure-pipelines/windows/provision-image.ps1 b/scripts/azure-pipelines/windows/provision-image.ps1 new file mode 100644 index 000000000..9a33461ee --- /dev/null +++ b/scripts/azure-pipelines/windows/provision-image.ps1 @@ -0,0 +1,447 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT + +<# +.SYNOPSIS +Sets up a machine to be an image for a scale set. + +.DESCRIPTION +provision-image.ps1 runs on an existing, freshly provisioned virtual machine, +and sets that virtual machine up as a vcpkg build machine. After this is done, +(outside of this script), we take that machine and make it an image to be copied +for setting up new VMs in the scale set. + +This script must either be run as admin, or one must pass AdminUserPassword; +if the script is run with AdminUserPassword, it runs itself again as an +administrator. + +.PARAMETER AdminUserPassword +The administrator user's password; if this is $null, or not passed, then the +script assumes it's running on an administrator account. + +.PARAMETER StorageAccountName +The name of the storage account. Stored in the environment variable %StorageAccountName%. +Used by the CI system to access the global storage. + +.PARAMETER StorageAccountKey +The key of the storage account. Stored in the environment variable %StorageAccountKey%. +Used by the CI system to access the global storage. +#> +param( + [string]$AdminUserPassword = $null, + [string]$StorageAccountName = $null, + [string]$StorageAccountKey = $null +) + +$ErrorActionPreference = 'Stop' + +<# +.SYNOPSIS +Gets a random file path in the temp directory. + +.DESCRIPTION +Get-TempFilePath takes an extension, and returns a path with a random +filename component in the temporary directory with that extension. + +.PARAMETER Extension +The extension to use for the path. +#> +Function Get-TempFilePath { + Param( + [String]$Extension + ) + + if ([String]::IsNullOrWhiteSpace($Extension)) { + throw 'Missing Extension' + } + + $tempPath = [System.IO.Path]::GetTempPath() + $tempName = [System.IO.Path]::GetRandomFileName() + '.' + $Extension + return Join-Path $tempPath $tempName +} + +if (-not [string]::IsNullOrEmpty($AdminUserPassword)) { + Write-Host "AdminUser password supplied; switching to AdminUser" + $PsExecPath = Get-TempFilePath -Extension 'exe' + Write-Host "Downloading psexec to $PsExecPath" + & curl.exe -L -o $PsExecPath -s -S https://live.sysinternals.com/PsExec64.exe + $PsExecArgs = @( + '-u', + 'AdminUser', + '-p', + $AdminUserPassword, + '-accepteula', + '-h', + 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe', + '-ExecutionPolicy', + 'Unrestricted', + '-File', + $PSCommandPath + ) + + if (-Not ([string]::IsNullOrWhiteSpace($StorageAccountName))) { + $PsExecArgs += '-StorageAccountName' + $PsExecArgs += $StorageAccountName + } + + if (-Not ([string]::IsNullOrWhiteSpace($StorageAccountKey))) { + $PsExecArgs += '-StorageAccountKey' + $PsExecArgs += $StorageAccountKey + } + + Write-Host "Executing $PsExecPath " + @PsExecArgs + + $proc = Start-Process -FilePath $PsExecPath -ArgumentList $PsExecArgs -Wait -PassThru + Write-Host 'Cleaning up...' + Remove-Item $PsExecPath + exit $proc.ExitCode +} + +$VisualStudioBootstrapperUrl = 'https://aka.ms/vs/16/release/vs_enterprise.exe' +$Workloads = @( + 'Microsoft.VisualStudio.Workload.NativeDesktop', + 'Microsoft.VisualStudio.Workload.Universal', + 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', + 'Microsoft.VisualStudio.Component.VC.Tools.ARM', + 'Microsoft.VisualStudio.Component.VC.Tools.ARM64', + 'Microsoft.VisualStudio.Component.VC.ATL', + 'Microsoft.VisualStudio.Component.VC.ATLMFC', + 'Microsoft.VisualStudio.Component.VC.v141.x86.x64.Spectre', + 'Microsoft.VisualStudio.Component.Windows10SDK.18362', + 'Microsoft.Net.Component.4.8.SDK', + 'Microsoft.Component.NetFX.Native' +) + +$MpiUrl = 'https://download.microsoft.com/download/A/E/0/AE002626-9D9D-448D-8197-1EA510E297CE/msmpisetup.exe' + +$CudaUrl = 'https://developer.download.nvidia.com/compute/cuda/10.1/Prod/local_installers/cuda_10.1.243_426.00_win10.exe' +$CudaFeatures = 'nvcc_10.1 cuobjdump_10.1 nvprune_10.1 cupti_10.1 gpu_library_advisor_10.1 memcheck_10.1 ' + ` + 'nvdisasm_10.1 nvprof_10.1 visual_profiler_10.1 visual_studio_integration_10.1 cublas_10.1 cublas_dev_10.1 ' + ` + 'cudart_10.1 cufft_10.1 cufft_dev_10.1 curand_10.1 curand_dev_10.1 cusolver_10.1 cusolver_dev_10.1 cusparse_10.1 ' + ` + 'cusparse_dev_10.1 nvgraph_10.1 nvgraph_dev_10.1 npp_10.1 npp_dev_10.1 nvrtc_10.1 nvrtc_dev_10.1 nvml_dev_10.1 ' + ` + 'occupancy_calculator_10.1 fortran_examples_10.1' + +$BinSkimUrl = 'https://www.nuget.org/api/v2/package/Microsoft.CodeAnalysis.BinSkim/1.6.0' + +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' + +<# +.SYNOPSIS +Writes a message to the screen depending on ExitCode. + +.DESCRIPTION +Since msiexec can return either 0 or 3010 successfully, in both cases +we write that installation succeeded, and which exit code it exited with. +If msiexec returns anything else, we write an error. + +.PARAMETER ExitCode +The exit code that msiexec returned. +#> +Function PrintMsiExitCodeMessage { + Param( + $ExitCode + ) + + # 3010 is probably ERROR_SUCCESS_REBOOT_REQUIRED + if ($ExitCode -eq 0 -or $ExitCode -eq 3010) { + Write-Host "Installation successful! Exited with $ExitCode." + } + else { + Write-Error "Installation failed! Exited with $ExitCode." + } +} + +<# +.SYNOPSIS +Install Visual Studio. + +.DESCRIPTION +InstallVisualStudio takes the $Workloads array, and installs it with the +installer that's pointed at by $BootstrapperUrl. + +.PARAMETER Workloads +The set of VS workloads to install. + +.PARAMETER BootstrapperUrl +The URL of the Visual Studio installer, i.e. one of vs_*.exe. + +.PARAMETER InstallPath +The path to install Visual Studio at. + +.PARAMETER Nickname +The nickname to give the installation. +#> +Function InstallVisualStudio { + Param( + [String[]]$Workloads, + [String]$BootstrapperUrl, + [String]$InstallPath = $null, + [String]$Nickname = $null + ) + + try { + Write-Host 'Downloading Visual Studio...' + [string]$bootstrapperExe = Get-TempFilePath -Extension 'exe' + curl.exe -L -o $bootstrapperExe -s -S $BootstrapperUrl + Write-Host "Installing Visual Studio..." + $args = @('/c', $bootstrapperExe, '--quiet', '--norestart', '--wait', '--nocache') + foreach ($workload in $Workloads) { + $args += '--add' + $args += $workload + } + + if (-not ([String]::IsNullOrWhiteSpace($InstallPath))) { + $args += '--installpath' + $args += $InstallPath + } + + if (-not ([String]::IsNullOrWhiteSpace($Nickname))) { + $args += '--nickname' + $args += $Nickname + } + + $proc = Start-Process -FilePath cmd.exe -ArgumentList $args -Wait -PassThru + PrintMsiExitCodeMessage $proc.ExitCode + } + catch { + Write-Error "Failed to install Visual Studio! $($_.Exception.Message)" + } +} + +<# +.SYNOPSIS +Install a .msi file. + +.DESCRIPTION +InstallMSI takes a url where an .msi lives, and installs that .msi to the system. + +.PARAMETER Name +The name of the thing to install. + +.PARAMETER Url +The URL at which the .msi lives. +#> +Function InstallMSI { + Param( + [String]$Name, + [String]$Url + ) + + try { + Write-Host "Downloading $Name..." + [string]$msiPath = Get-TempFilePath -Extension 'msi' + curl.exe -L -o $msiPath -s -S $Url + Write-Host "Installing $Name..." + $args = @('/i', $msiPath, '/norestart', '/quiet', '/qn') + $proc = Start-Process -FilePath 'msiexec.exe' -ArgumentList $args -Wait -PassThru + PrintMsiExitCodeMessage $proc.ExitCode + } + catch { + Write-Error "Failed to install $Name! $($_.Exception.Message)" + } +} + +<# +.SYNOPSIS +Unpacks a zip file to $Dir. + +.DESCRIPTION +InstallZip takes a URL of a zip file, and unpacks the zip file to the directory +$Dir. + +.PARAMETER Name +The name of the tool being installed. + +.PARAMETER Url +The URL of the zip file to unpack. + +.PARAMETER Dir +The directory to unpack the zip file to. +#> +Function InstallZip { + Param( + [String]$Name, + [String]$Url, + [String]$Dir + ) + + try { + Write-Host "Downloading $Name..." + [string]$zipPath = Get-TempFilePath -Extension 'zip' + curl.exe -L -o $zipPath -s -S $Url + Write-Host "Installing $Name..." + Expand-Archive -Path $zipPath -DestinationPath $Dir -Force + } + catch { + Write-Error "Failed to install $Name! $($_.Exception.Message)" + } +} + +<# +.SYNOPSIS +Installs MPI + +.DESCRIPTION +Downloads the MPI installer located at $Url, and installs it with the +correct flags. + +.PARAMETER Url +The URL of the installer. +#> +Function InstallMpi { + Param( + [String]$Url + ) + + try { + Write-Host 'Downloading MPI...' + [string]$installerPath = Get-TempFilePath -Extension 'exe' + curl.exe -L -o $installerPath -s -S $Url + Write-Host 'Installing MPI...' + $proc = Start-Process -FilePath $installerPath -ArgumentList @('-force', '-unattend') -Wait -PassThru + $exitCode = $proc.ExitCode + if ($exitCode -eq 0) { + Write-Host 'Installation successful!' + } + else { + Write-Error "Installation failed! Exited with $exitCode." + } + } + catch { + Write-Error "Failed to install MPI! $($_.Exception.Message)" + } +} + +<# +.SYNOPSIS +Installs NVIDIA's CUDA Toolkit. + +.DESCRIPTION +InstallCuda installs the CUDA Toolkit with the features specified as a +space separated list of strings in $Features. + +.PARAMETER Url +The URL of the CUDA installer. + +.PARAMETER Features +A space-separated list of features to install. +#> +Function InstallCuda { + Param( + [String]$Url, + [String]$Features + ) + + try { + Write-Host 'Downloading CUDA...' + [string]$installerPath = Get-TempFilePath -Extension 'exe' + curl.exe -L -o $installerPath -s -S $Url + Write-Host 'Installing CUDA...' + $proc = Start-Process -FilePath $installerPath -ArgumentList @('-s ' + $Features) -Wait -PassThru + $exitCode = $proc.ExitCode + if ($exitCode -eq 0) { + Write-Host 'Installation successful!' + } + else { + Write-Error "Installation failed! Exited with $exitCode." + } + } + catch { + Write-Error "Failed to install CUDA! $($_.Exception.Message)" + } +} + +<# +.SYNOPSIS +Partitions a new physical disk. + +.DESCRIPTION +Takes the disk $DiskNumber, turns it on, then partitions it for use with label +$Label and drive letter $Letter. + +.PARAMETER DiskNumber +The number of the disk to set up. + +.PARAMETER Letter +The drive letter at which to mount the disk. + +.PARAMETER Label +The label to give the disk. +#> +Function New-PhysicalDisk { + Param( + [int]$DiskNumber, + [string]$Letter, + [string]$Label + ) + + if ($Letter.Length -ne 1) { + throw "Bad drive letter $Letter, expected only one letter. (Did you accidentially add a : ?)" + } + + try { + Write-Host "Attempting to online physical disk $DiskNumber" + [string]$diskpartScriptPath = Get-TempFilePath -Extension 'txt' + [string]$diskpartScriptContent = + "SELECT DISK $DiskNumber`r`n" + + "ONLINE DISK`r`n" + + Write-Host "Writing diskpart script to $diskpartScriptPath with content:" + Write-Host $diskpartScriptContent + Set-Content -Path $diskpartScriptPath -Value $diskpartScriptContent + Write-Host 'Invoking DISKPART...' + & diskpart.exe /s $diskpartScriptPath + + Write-Host "Provisioning physical disk $DiskNumber as drive $Letter" + [string]$diskpartScriptContent = + "SELECT DISK $DiskNumber`r`n" + + "ATTRIBUTES DISK CLEAR READONLY`r`n" + + "CREATE PARTITION PRIMARY`r`n" + + "FORMAT FS=NTFS LABEL=`"$Label`" QUICK`r`n" + + "ASSIGN LETTER=$Letter`r`n" + Write-Host "Writing diskpart script to $diskpartScriptPath with content:" + Write-Host $diskpartScriptContent + Set-Content -Path $diskpartScriptPath -Value $diskpartScriptContent + Write-Host 'Invoking DISKPART...' + & diskpart.exe /s $diskpartScriptPath + } + catch { + Write-Error "Failed to provision physical disk $DiskNumber as drive $Letter! $($_.Exception.Message)" + } +} + +Write-Host "AdminUser password not supplied; assuming already running as AdminUser" + +New-PhysicalDisk -DiskNumber 2 -Letter 'E' -Label 'install disk' + +Write-Host 'Disabling pagefile...' +wmic computersystem set AutomaticManagedPagefile=False +wmic pagefileset delete + +Write-Host 'Configuring AntiVirus exclusions...' +Add-MPPreference -ExclusionPath C:\ +Add-MPPreference -ExclusionPath D:\ +Add-MPPreference -ExclusionPath E:\ +Add-MPPreference -ExclusionProcess ninja.exe +Add-MPPreference -ExclusionProcess clang-cl.exe +Add-MPPreference -ExclusionProcess cl.exe +Add-MPPreference -ExclusionProcess link.exe +Add-MPPreference -ExclusionProcess python.exe + +InstallVisualStudio -Workloads $Workloads -BootstrapperUrl $VisualStudioBootstrapperUrl -Nickname 'Stable' +InstallMpi -Url $MpiUrl +InstallCuda -Url $CudaUrl -Features $CudaFeatures +InstallZip -Url $BinSkimUrl -Name 'BinSkim' -Dir 'C:\BinSkim' +if (-Not ([string]::IsNullOrWhiteSpace($StorageAccountName))) { + Write-Host 'Storing storage account name to environment' + Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' ` + -Name StorageAccountName ` + -Value $StorageAccountName +} +if (-Not ([string]::IsNullOrWhiteSpace($StorageAccountKey))) { + Write-Host 'Storing storage account key to environment' + Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' ` + -Name StorageAccountKey ` + -Value $StorageAccountKey +} diff --git a/scripts/azure-pipelines/windows/sysprep.ps1 b/scripts/azure-pipelines/windows/sysprep.ps1 new file mode 100644 index 000000000..c0965350d --- /dev/null +++ b/scripts/azure-pipelines/windows/sysprep.ps1 @@ -0,0 +1,17 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT +# + +<# +.SYNOPSIS +Prepares the virtual machine for imaging. + +.DESCRIPTION +Runs the `sysprep` utility to prepare the system for imaging. +See https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/sysprep--system-preparation--overview +for more information. +#> + +$ErrorActionPreference = 'Stop' +Write-Host 'Running sysprep' +& C:\Windows\system32\sysprep\sysprep.exe /oobe /generalize /shutdown diff --git a/scripts/ci.baseline.txt b/scripts/ci.baseline.txt index 386bf5829..7f796173a 100644 --- a/scripts/ci.baseline.txt +++ b/scripts/ci.baseline.txt @@ -45,10 +45,14 @@ 7zip:x64-osx=fail 7zip:x64-uwp=fail abseil:arm-uwp=fail -ace:arm64-windows=fail +# ace is failing because the port's attempt to make yasm available is not succeeding ace:arm-uwp=fail +ace:arm64-windows=fail ace:x64-osx=fail ace:x64-uwp=fail +ace:x64-windows-static=fail +ace:x64-windows=fail +ace:x86-windows=fail activemq-cpp:x64-windows-static=fail akali:x64-uwp=fail akali:arm-uwp=fail @@ -270,7 +274,7 @@ cppcms:x64-osx=fail cppcms:x64-windows-static=fail cppfs:arm-uwp=fail cppfs:x64-uwp=fail -cppgraphqlgen:arm-uwp=fail +cppgraphqlgen:arm-uwp=ignore cppgraphqlgen:x64-uwp=ignore cppkafka:x64-linux=ignore cppmicroservices:arm64-windows=fail @@ -305,8 +309,8 @@ cudnn:x64-windows-static=fail cudnn:x86-windows=fail date:arm64-windows=fail dbow2:x64-osx=fail -dcmtk:arm64-windows=fail dcmtk:arm-uwp=fail +dcmtk:arm64-windows=fail dcmtk:x64-uwp=fail detours:x64-linux=fail detours:x64-osx=fail @@ -337,6 +341,7 @@ dlfcn-win32:x64-linux=fail dlfcn-win32:x64-osx=fail dlfcn-win32:x64-uwp=fail dmlc:arm-uwp=fail +dmlc:arm64-windows=ignore dmlc:x64-uwp=fail dmlc:x64-windows-static=ignore dmlc:x86-windows=ignore @@ -435,11 +440,11 @@ fdlibm:arm-uwp=fail fdlibm:x64-uwp=fail fftw3:arm-uwp=fail fftw3:x64-uwp=fail +# ffmpeg on arm64 is currently failing due to an internal compiler error +ffmpeg:arm64-windows=fail field3d:x64-windows=fail field3d:x64-windows-static=fail field3d:x86-windows=fail -fizz:x64-windows=fail -fizz:x64-windows-static=fail flint:x64-linux=fail flint:x64-osx=fail fltk:arm-uwp=fail @@ -457,11 +462,6 @@ fmilib:x64-uwp=fail fmilib:x64-windows=ignore fmilib:x64-windows-static=ignore fmilib:x86-windows=ignore -# Folly fails due to a compiler bug in MSVC 19.22.27905, fixed in newer releases -folly:arm64-windows=fail -folly:x86-windows=fail -folly:x64-windows=fail -folly:x64-windows-static=fail foonathan-memory:arm64-windows=fail foonathan-memory:arm-uwp=fail foonathan-memory:x64-uwp=fail @@ -602,6 +602,7 @@ hwloc:x64-linux=fail hwloc:x64-osx=fail hwloc:x64-uwp=fail hyperscan:x64-linux=ignore +# hypre has a conflict with 'superlu' port hypre:x64-linux=fail hypre:x64-osx=fail icu:arm64-windows=fail @@ -957,10 +958,15 @@ libuuid:x86-windows=fail libuv:arm64-windows=fail libuv:arm-uwp=fail libuv:x64-uwp=fail -libvpx:arm64-windows=fail +# libvpx is failing because the port's attempt to make yasm available is not succeeding libvpx:arm-uwp=fail +libvpx:arm64-windows=fail libvpx:x64-linux=fail libvpx:x64-osx=fail +libvpx:x64-uwp=fail +libvpx:x64-windows-static=fail +libvpx:x64-windows=fail +libvpx:x86-windows=fail libwandio:x86-windows=fail libwandio:x64-windows=fail libwandio:x64-windows-static=fail @@ -995,9 +1001,6 @@ llvm:arm64-windows=fail llvm:arm-uwp=fail llvm:x64-uwp=fail llvm:x64-linux=ignore -# disable them temporarily and wait for fix -llvm:x64-windows=fail -llvm:x64-windows-static=fail # installing iconv makes building llvm fail; needs to be fixed llvm:x64-osx=ignore lmdb:arm64-windows=fail @@ -1122,8 +1125,12 @@ mozjpeg:x64-uwp = skip mozjpeg:x64-windows = skip mozjpeg:x64-windows-static = skip mozjpeg:x86-windows = skip +# mpg123 is failing because the port's attempt to make yasm available is not succeeding mpg123:arm-uwp=fail mpg123:x64-uwp=fail +mpg123:x64-windows-static=fail +mpg123:x64-windows=fail +mpg123:x86-windows=fail mpir:arm64-windows=fail mpir:arm-uwp=fail mpir:x64-uwp=fail @@ -1413,9 +1420,13 @@ polyhook2:x64-linux=fail polyhook2:x64-uwp=fail polyhook2:x64-osx=fail portable-snippets:arm-uwp=fail -portaudio:arm64-windows=fail +# Portaudio was broken by Ninja 1.9.0 https://github.com/ninja-build/ninja/pull/1406 portaudio:arm-uwp=fail +portaudio:arm64-windows=fail portaudio:x64-uwp=fail +portaudio:x64-windows-static=fail +portaudio:x64-windows=fail +portaudio:x86-windows=fail portmidi:arm64-windows=fail portmidi:arm-uwp=fail portmidi:x64-linux=fail @@ -1435,6 +1446,9 @@ protobuf-c:x64-windows-static=fail protobuf-c:x64-uwp=fail protobuf-c:arm64-windows=fail protobuf-c:arm-uwp=fail +# proxygen fails with "Target 'Windows' not supported by proxygen!" +proxygen:x64-windows=fail +proxygen:x64-windows-static=fail ptex:arm-uwp=fail ptex:x64-linux=fail ptex:x64-osx=fail @@ -1459,8 +1473,40 @@ qhull:x64-uwp=ignore qpid-proton:arm-uwp=fail qpid-proton:x64-uwp=fail qpid-proton:x64-windows-static=fail +#qt5-activeqt is skipped because it conflicts with qt5-declarative: +# Starting package 142/820: qt5-declarative:x86-windows +# Building package qt5-declarative[core]:x86-windows... +# Using cached binary package: C:\agent\_work\1\s\archives\94\9428e63fede20a51ac0631a9d94d8773e593dd06.zip +# Building package qt5-declarative[core]:x86-windows... done +# Installing package qt5-declarative[core]:x86-windows... +# The following files are already installed in C:/agent/_work/1/s/installed/x86-windows and are in conflict with qt5-declarative:x86-windows +# +# Installed by qt5-activeqt:x86-windows +# tools/qt5/bin/Qt5Gui.dll +# tools/qt5/bin/Qt5Widgets.dll +# tools/qt5/bin/bz2.dll +# tools/qt5/bin/freetype.dll +# tools/qt5/bin/glib-2.dll +# tools/qt5/bin/harfbuzz.dll +# tools/qt5/bin/jpeg62.dll +# tools/qt5/bin/libcharset.dll +# tools/qt5/bin/libiconv.dll +# tools/qt5/bin/libintl.dll +# tools/qt5/bin/libpng16.dll +# tools/qt5/bin/pcre.dll +# tools/qt5/bin/plugins/imageformats/qgif.dll +# tools/qt5/bin/plugins/imageformats/qico.dll +# tools/qt5/bin/plugins/imageformats/qjpeg.dll +# tools/qt5/bin/plugins/platforms/qwindows.dll +# tools/qt5/bin/plugins/styles/qwindowsvistastyle.dll +qt5-activeqt:arm-uwp=skip +qt5-activeqt:arm64-windows=skip qt5-activeqt:x64-linux=fail qt5-activeqt:x64-osx=fail +qt5-activeqt:x64-uwp=skip +qt5-activeqt:x64-windows-static=skip +qt5-activeqt:x64-windows=skip +qt5-activeqt:x86-windows=skip qt5-macextras:x64-linux=fail qt5-macextras:x64-windows=fail qt5-macextras:x64-windows-static=fail @@ -1524,7 +1570,6 @@ redis-plus-plus:x64-windows-static=fail redis-plus-plus:arm64-windows=fail replxx:arm-uwp=fail replxx:x64-uwp=fail -replxx:arm64-windows=fail reproc:arm-uwp=fail reproc:x64-uwp=fail restbed:arm-uwp=fail @@ -1561,7 +1606,6 @@ sciter:x64-uwp=fail sciter:x64-windows-static=fail scnlib:arm-uwp=fail scnlib:x64-uwp=fail -scnlib:x86-windows=fail scylla-wrapper:arm64-windows=fail scylla-wrapper:arm-uwp=fail scylla-wrapper:x64-linux=fail @@ -1584,6 +1628,8 @@ sdl2-mixer:x64-uwp=fail sdl2-net:arm-uwp=fail sdl2-net:x64-uwp=fail seal:arm-uwp=fail +# https://github.com/microsoft/vcpkg/issues/10918 +seal:x64-windows-static=fail seal:x64-uwp=fail secp256k1:x64-linux=fail secp256k1:x64-osx=fail @@ -1676,9 +1722,30 @@ stormlib:arm-uwp=fail stormlib:x64-uwp=fail stxxl:arm-uwp=fail stxxl:x64-uwp=fail -superlu:arm64-windows=fail -superlu:arm-uwp=fail -superlu:x64-uwp=fail +# Sundials was broken by Ninja 1.9.0 https://github.com/ninja-build/ninja/pull/1406 +sundials:arm64-windows=fail +sundials:x64-windows=fail +sundials:x86-windows=fail +# Conflicts between ports: +#The following files are already installed in C:/agent/_work/1/s/installed/x64-windows-static +# and are in conflict with superlu:x64-windows-static +# +#Installed by hypre:x64-windows-static +# include/slu_Cnames.h +# include/slu_cdefs.h +# include/slu_dcomplex.h +# include/slu_ddefs.h +# include/slu_scomplex.h +# include/slu_sdefs.h +# include/slu_util.h +# include/slu_zdefs.h +# include/supermatrix.h +superlu:arm-uwp=skip +superlu:arm-windows=skip +superlu:arm64-windows=skip +superlu:x64-uwp=skip +superlu:x64-windows-static=skip +superlu:x64-windows=skip systemc:arm64-windows=fail systemc:arm-uwp=fail systemc:x64-uwp=fail @@ -1907,4 +1974,3 @@ zkpp:arm-uwp=fail c4core:arm-uwp=fail c4core:arm64-windows=fail c4core:x64-osx=fail -