Add azure-pipelines build and test definitions (#20840)
authorSven Boemer <sbomer@gmail.com>
Thu, 8 Nov 2018 16:50:37 +0000 (08:50 -0800)
committerGitHub <noreply@github.com>
Thu, 8 Nov 2018 16:50:37 +0000 (08:50 -0800)
This adds an azure pipeline definition with a matrix of product and test builds, using helix to run tests. The intention is that this definition will eventually be used for both our official build and CI testing.

There is one build job for each OS/platform/arch, and one test job for each OS/platform/arch/priority/R2Rflag. The test job builds tests and then submits them to helix, passing along a number of test run modes. One helix test job will be created for each OS/platform/arch/priority/R2Rflag/helixtargetqueue/testscenario.

There is a lot of work left to be done to get this up to parity with our official builds and CI, which I've tried to call out in comments.

16 files changed:
NuGet.Config [new file with mode: 0644]
azure-pipelines.yml [new file with mode: 0644]
build-test.cmd
dir.common.props
eng/build-job.yml [new file with mode: 0644]
eng/install-native-dependencies.sh [new file with mode: 0644]
eng/kill_tasks.cmd [new file with mode: 0644]
eng/platform-matrix.yml [new file with mode: 0644]
eng/test-job.yml [new file with mode: 0644]
eng/xplat-job.yml [new file with mode: 0644]
src/pal/tools/probe-win.ps1
src/publishwitharcade.proj [new file with mode: 0644]
src/restorearcadepublishtasks.proj [new file with mode: 0644]
tests/helixprep.proj
tests/helixpublishwitharcade.proj [new file with mode: 0644]
tests/runtest_helix.py [new file with mode: 0755]

diff --git a/NuGet.Config b/NuGet.Config
new file mode 100644 (file)
index 0000000..7fa789a
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <packageSources>
+    <clear />
+    <add key="arcade" value="https://dotnetfeed.blob.core.windows.net/dotnet-tools-internal/index.json" />
+    <add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
+    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
+  </packageSources>
+</configuration>
\ No newline at end of file
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644 (file)
index 0000000..5f6b4a4
--- /dev/null
@@ -0,0 +1,178 @@
+resources:
+  repositories:
+  # shared library repository
+  - repository: arcade
+    type: github
+    endpoint: DotNet-Bot GitHub Connection
+    name: dotnet/arcade
+    ref: refs/heads/master
+
+variables:
+  DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+
+
+jobs:
+
+##   The following is the matrix of test runs that we have. This is
+##   duplicated for each os/arch combination in platform-matrix.yml.
+
+##
+##   Product build       Test build              Test run
+##   (Azure DevOps)      (Azure DevOps)          (helix)
+##
+##   ###########################################################################################
+##
+##   Debug
+##
+##   Checked ----------> Pri0 -----------------> plain runtests
+##           |
+##           \---------> Pri1 -----------------> plain runtests
+##           |                \----------------> jitstress
+##           |                \----------------> gcstress
+##           |                \----------------> maybe more (dynamically selected runtest modes)
+##           |
+##           \---------> Pri1 crossgen --------> plain runtests
+##                                     \-------> jitstress
+##                                     \-------> gcstress
+##                                     \-------> maybe more (dynamically selected runtest modes)
+##
+##   Release ----------> Pri1 -----------------> plain runtests
+##           |
+##           \---------> Pri1 crossgen --------> plain runtests
+##
+##
+
+## Each build or test job is defined in Azure DevOps, and will show
+## up in the UI in the order in which they are defined here. The
+## build and test build job matrix is defined statically, but
+## queue-time inputs can be used to control whether a job executes
+## (used to select which jobs run in ci vs for official builds), or
+## to select test modes. This should eventually be used to enable
+## requesting specific test runs from pull requests.
+
+
+##
+## Templates used to define jobs:
+## Please update this if the factoring changes.
+##
+## This file defines the set of jobs in a platform-agnostic manner,
+## using the platform-matrix.yml template. This will create one job
+## for each platform from the passed-in jobTemplate (either a build
+## job or a test job). The build-job.yml and test-job.yml templates
+## use xplat-job.yml to handle some of the common logic for
+## abstracting over platforms. Finally, xplat-job.yml uses the arcade
+## base.yml job template, which sets up telemetry and signing support.
+
+## azure-pipelines.yml -> platform-matrix.yml -------> build-job.yml -------> xplat-job.yml -> base.yml
+##                                            |  (passed-in jobTemplate)  |                    (arcade)
+##                                            \------> test-job.yml ------/
+
+
+
+#
+# Debug build
+#
+
+- template: eng/platform-matrix.yml
+  parameters:
+    jobTemplate: build-job.yml
+    buildConfig: debug
+
+#
+# Checked build
+#
+
+- template: eng/platform-matrix.yml
+  parameters:
+    jobTemplate: build-job.yml
+    buildConfig: checked
+
+#
+# Release build
+#
+
+- template: eng/platform-matrix.yml
+  parameters:
+    jobTemplate: build-job.yml
+    buildConfig: release
+
+#
+# Checked test builds
+#
+
+# Pri0
+- template: eng/platform-matrix.yml
+  parameters:
+    jobTemplate: test-job.yml
+    buildConfig: checked
+    jobParameters:
+      priority: 0
+
+# Pri1
+- template: eng/platform-matrix.yml
+  parameters:
+    jobTemplate: test-job.yml
+    buildConfig: checked
+    jobParameters:
+      priority: 1
+      scenarios: 'normal;jitstress2'
+
+# Pri1 crossgen
+- template: eng/platform-matrix.yml
+  parameters:
+    jobTemplate: test-job.yml
+    buildConfig: checked
+    jobParameters:
+      priority: 1
+      crossgen: true
+      scenarios: 'normal;jitstress2'
+
+#
+# Release test builds
+#
+
+# Pri1
+- template: eng/platform-matrix.yml
+  parameters:
+    jobTemplate: test-job.yml
+    buildConfig: release
+    jobParameters:
+      priority: 1
+
+# Pri1 crossgen
+- template: eng/platform-matrix.yml
+  parameters:
+    jobTemplate: test-job.yml
+    buildConfig: release
+    jobParameters:
+      priority: 1
+      crossgen: true
+
+
+# Publish build information to Build Assets Registry
+
+# This job gathers build assets from the pipeline (from each official
+# product build job), and publishes them to the build assets
+# registry. Its dependencies should be updated to include all of the
+# official builds if we add more platform/arch combinations.
+
+# TODO: Enable publish to BAR
+#- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
+#  - template: /eng/common/templates/phases/publish-build-assets.yml@arcade
+#- phase: publish_bar
+#  displayName: publish to BAR (empty currently)
+#  queue:
+#    name: Hosted VS2017
+#  dependsOn:
+#  - build_Linux_x64_release
+#  - build_OSX_x64_release
+#  - build_Windows_NT_x64_release
+#  - build_Windows_NT_x86_release
+#  - build_Windows_NT_arm_release
+#  - build_Windows_NT_arm64_release
+  # TODO: enable these builds
+  #  - build_rhel_x64_release
+  #  - build_alpine_x64_release
+  #  - build_crossbuild_arm_release
+  #  - build_crossbuild_arm64_release
+
index 29bc779..790b75f 100644 (file)
@@ -527,7 +527,7 @@ set __MsbuildWrn=/flp1:WarningsOnly;LogFile="%__BuildWrn%"
 set __MsbuildErr=/flp2:ErrorsOnly;LogFile="%__BuildErr%"
 set __Logging=-MsBuildLog=!__MsbuildLog! -MsBuildWrn=!__MsbuildWrn! -MsBuildErr=!__MsbuildErr!
 
-call %__ProjectDir%\run.cmd build -Project=%__ProjectDir%\tests\helixprep.proj !__Logging! %__RunArgs% %__BuildAgainstPackagesArg% %RuntimeIdArg% %TargetsWindowsArg% %__CrossgenArg% %__PriorityArg% %__PassThroughArg% %__UnprocessedBuildArgs%
+call %__ProjectDir%\run.cmd build -Project=%__ProjectDir%\tests\helixprep.proj !__Logging! %__RunArgs% %RuntimeIdArg% %TargetsWindowsArg% %__CrossgenArg% %__PriorityArg% %__PassThroughArg% %__UnprocessedBuildArgs%
 if errorlevel 1 (
     echo %__MsgPrefix%Error: build failed. Refer to the build log files for details:
     echo     %__BuildLog%
index f945289..0c0096c 100644 (file)
@@ -22,7 +22,9 @@
     <BuildType Condition="'$(__BuildType)' == 'checked'">Checked</BuildType>
 
     <BuildOS>$(__BuildOS)</BuildOS>
-    <BuildOS Condition="'$(__BuildOS)' == ''">Windows_NT</BuildOS>
+    <BuildOS Condition="'$(__BuildOS)' == '' And '$([MSBuild]::IsOSPlatform(Windows))' == 'true'">Windows_NT</BuildOS>
+    <BuildOS Condition="'$(__BuildOS)' == '' And '$([MSBuild]::IsOSPlatform(Linux))' == 'true'">Linux</BuildOS>
+    <BuildOS Condition="'$(__BuildOS)' == '' And '$([MSBuild]::IsOSPlatform(OSX))' == 'true'">OSX</BuildOS>
 
     <Configuration Condition="'$(Configuration)' == ''">$(BuildType)</Configuration>
     <Platform Condition="'$(Platform)' == ''">$(BuildArch)</Platform>
diff --git a/eng/build-job.yml b/eng/build-job.yml
new file mode 100644 (file)
index 0000000..08f8ac0
--- /dev/null
@@ -0,0 +1,108 @@
+parameters:
+  buildConfig: ''
+  archType: ''
+  osGroup: ''
+
+### Product build
+jobs:
+- template: xplat-job.yml
+  parameters:
+    buildConfig: ${{ parameters.buildConfig }}
+    archType: ${{ parameters.archType }}
+    osGroup: ${{ parameters.osGroup }}
+
+    # Compute job name from template parameters
+    name: ${{ format('build_{0}_{1}_{2}', parameters.osGroup, parameters.archType, parameters.buildConfig) }}
+    displayName: ${{ format('Build {0} {1} {2}', parameters.osGroup, parameters.archType, parameters.buildConfig) }}
+
+    steps:
+
+    # Install native dependencies
+    - ${{ if or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX')) }}:
+      - script: sh eng/install-native-dependencies.sh $(osGroup)
+        displayName: Install native dependencies
+    - ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+      # Necessary to install python
+      - script: eng\common\init-tools-native.cmd -InstallDirectory $(Build.SourcesDirectory)\native-tools -Force
+        displayName: Install native dependencies
+
+
+    # Run init-tools (pre-arcade dependency bootstrapping)
+    # TODO: replace this with an arcade equivalent
+    - ${{ if or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX')) }}:
+      - script: ./init-tools.sh
+        displayName: Init tools
+    - ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+      - script: .\init-tools.cmd
+        displayName: Init tools
+
+
+    # Sync
+    - ${{ if or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX')) }}:
+      - script: ./Tools/dotnetcli/dotnet msbuild build.proj /p:RestoreDuringBuild=true /t:Sync
+        displayName: Sync
+    - ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+      - script: .\Tools\dotnetcli\dotnet.exe msbuild build.proj /p:RestoreDuringBuild=true /t:Sync
+        displayName: Sync
+
+
+    # Build
+    - ${{ if or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX')) }}:
+      - script: ./build.sh $(buildConfig) $(archType) -skipnuget -skiprestore
+        displayName: Build product
+    - ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+      # TODO: IBCOptimize? EnforcePGO? pass an OfficialBuildId? SignType? file logging parameters?
+      - script: set __TestIntermediateDir=int&&build.cmd $(buildConfig) $(archType) -skiptests -skipbuildpackages -skiprestore
+        displayName: Build product
+
+
+    # Upload build as pipeline artifact
+    - ${{ if or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX')) }}:
+      - task: PublishPipelineArtifact@0
+        displayName: Save product build as pipeline artifact
+        inputs:
+          artifactName: ${{ format('{0}_{1}_{2}_build', parameters.osGroup, parameters.archType, parameters.buildConfig) }}
+          targetPath: $(Build.SourcesDirectory)/bin/Product/$(osGroup).$(archType).$(buildConfigUpper)
+    - ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+      - task: PublishPipelineArtifact@0
+        displayName: Save product build as pipeline artifact
+        inputs:
+          artifactName: ${{ format('{0}_{1}_{2}_build', parameters.osGroup, parameters.archType, parameters.buildConfig) }}
+          targetPath: $(Build.SourcesDirectory)\bin\Product\Windows_NT.$(archType).$(buildConfigUpper)
+
+
+    # TODO: Sign
+    - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
+      - script: echo Sign!
+        displayName: Sign Binaries (empty for now)
+
+
+    # Get key vault secrets for publishing
+    - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
+      - task: AzureKeyVault@1
+        inputs:
+          azureSubscription: 'DotNet-Engineering-Services_KeyVault'
+          KeyVaultName: EngKeyVault
+          SecretsFilter: 'dotnetfeed-storage-access-key-1,microsoft-symbol-server-pat,symweb-symbol-server-pat'
+
+
+    # TODO: Build packages and publish official build
+    #- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
+    #    ${{ if or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX')) }}:
+    #      # TODO: ensure that NUGET_PACKAGES path is correctly set
+    #      - script: .dotnet/dotnet msbuild src/restorearcadepublishtasks.proj /t:Restore
+    #        displayName: Restore tasks used to publish the build
+    #      - script: .dotnet/dotnet msbuild src/publishwitharcade.proj /t:PublishPackages /p:AzureFeedUrl=$(AzureFeedUrl) /p:AccountKey=$(AccountKey)
+    #        displayName: Publish official build
+    #    ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+    #      # TODO: pass publish feed url and access token in from the internal pipeline
+    #      - script: .dotnet\dotnet.exe msbuild src\restorearcadepublishtasks.proj /t:Restore
+    #        displayName: Restore tasks used to publish the build
+    #      - script: .dotnet\dotnet.exe msbuild src\publishwitharcade.proj /t:PublishPackages /p:AzureFeedUrl=$(AzureFeedUrl) /p:AccountKey=$(AccountKey)
+
+    # Kill tasks that hold onto files on Windows. Otherwise git clean
+    # may fail for later jobs on the same agent.
+    - ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+      - script: eng/kill_tasks.cmd
+        displayName: Kill tasks that hold on to files
+        condition: always()
diff --git a/eng/install-native-dependencies.sh b/eng/install-native-dependencies.sh
new file mode 100644 (file)
index 0000000..9e80c57
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/env sh
+
+if [ "$1" = "Linux" ]; then
+    sudo apt update
+    if [ "$?" != "0" ]; then
+       exit 1;
+    fi
+    sudo apt install cmake llvm-3.9 clang-3.9 lldb-3.9 liblldb-3.9-dev libunwind8 libunwind8-dev gettext libicu-dev liblttng-ust-dev libcurl4-openssl-dev libssl-dev libkrb5-dev libnuma-dev
+    if [ "$?" != "0"]; then
+        exit 1;
+    fi
+elif [ "$1" = "OSX" ]; then
+    brew install icu4c openssl
+    if [ "$?" != "0" ]; then
+        exit 1;
+    fi
+    brew link --force icu4c
+    if [ "$?" != "0"]; then
+        exit 1;
+    fi
+else
+    echo "Must pass \"Linux\" or \"OSX\" as first argument."
+    exit 1
+fi
+
diff --git a/eng/kill_tasks.cmd b/eng/kill_tasks.cmd
new file mode 100644 (file)
index 0000000..ee7099c
--- /dev/null
@@ -0,0 +1,10 @@
+@if not defined _echo @echo off
+setlocal EnableDelayedExpansion
+
+:: Check if VBCSCompiler.exe is running
+tasklist /fi "imagename eq VBCSCompiler.exe" |find ":" > nul
+:: Compiler is running if errorlevel == 1
+if errorlevel 1 (
+       echo Stop VBCSCompiler.exe execution.
+       for /f "tokens=2 delims=," %%F in ('tasklist /nh /fi "imagename eq VBCSCompiler.exe" /fo csv') do taskkill /f /PID %%~F
+)
diff --git a/eng/platform-matrix.yml b/eng/platform-matrix.yml
new file mode 100644 (file)
index 0000000..7f1a463
--- /dev/null
@@ -0,0 +1,61 @@
+parameters:
+  jobTemplate: ''
+  buildConfig: ''
+  jobParameters: {}
+
+jobs:
+
+# Linux x64
+
+- template: ${{ parameters.jobTemplate }}
+  parameters:
+    buildConfig: ${{ parameters.buildConfig }}
+    archType: x64
+    osGroup: Linux
+    ${{ insert }}: ${{ parameters.jobParameters }}
+
+# macOS x64
+
+- template: ${{ parameters.jobTemplate }}
+  parameters:
+    buildConfig: ${{ parameters.buildConfig }}
+    archType: x64
+    osGroup: OSX
+    ${{ insert }}: ${{ parameters.jobParameters }}
+
+# Windows x64/x86/arm/arm64
+
+- template: ${{ parameters.jobTemplate }}
+  parameters:
+    buildConfig: ${{ parameters.buildConfig }}
+    archType: x64
+    osGroup: Windows_NT
+    ${{ insert }}: ${{ parameters.jobParameters }}
+
+- template: ${{ parameters.jobTemplate }}
+  parameters:
+    buildConfig: ${{ parameters.buildConfig }}
+    archType: x86
+    osGroup: Windows_NT
+    ${{ insert }}: ${{ parameters.jobParameters }}
+
+- template: ${{ parameters.jobTemplate }}
+  parameters:
+    buildConfig: ${{ parameters.buildConfig }}
+    archType: arm
+    osGroup: Windows_NT
+    ${{ insert }}: ${{ parameters.jobParameters }}
+
+- template: ${{ parameters.jobTemplate }}
+  parameters:
+    buildConfig: ${{ parameters.buildConfig }}
+    archType: arm64
+    osGroup: Windows_NT
+    ${{ insert }}: ${{ parameters.jobParameters }}
+
+# TODO for official build:
+# RedHat x64
+# Linux crossbuild arm
+# Linux crossbuild arm64
+# Linux musl x64
+
diff --git a/eng/test-job.yml b/eng/test-job.yml
new file mode 100644 (file)
index 0000000..0a1a9eb
--- /dev/null
@@ -0,0 +1,121 @@
+parameters:
+  buildConfig: ''
+  archType: ''
+  osGroup: ''
+  priority: 0
+  crossgen: false
+  scenarios: ''
+
+### Test job
+
+### Each test job depends on a corresponding build job with the same
+### buildConfig and archType.
+
+jobs:
+- template: xplat-job.yml
+  parameters:
+    buildConfig: ${{ parameters.buildConfig }}
+    archType: ${{ parameters.archType }}
+    osGroup: ${{ parameters.osGroup }}
+
+    # Compute job name from template parameters
+    ${{ if eq(parameters.crossgen, 'false') }}:
+        name: ${{ format('testbuild_pri{0}_{1}_{2}_{3}', parameters.priority, parameters.osGroup, parameters.archType, parameters.buildConfig) }}
+        displayName: ${{ format('Test pri{0} {1} {2} {3}', parameters.priority, parameters.osGroup, parameters.archType, parameters.buildConfig) }}
+    ${{ if eq(parameters.crossgen, 'true') }}:
+        name: ${{ format('testbuild_pri{0}_r2r_{1}_{2}_{3}', parameters.priority, parameters.osGroup, parameters.archType, parameters.buildConfig) }}
+        displayName: ${{ format('Test Pri{0} R2R {1} {2} {3}', parameters.priority, parameters.osGroup, parameters.archType, parameters.buildConfig) }}
+
+    variables:
+      # Map template parameters to command line arguments
+      ${{ if eq(parameters.priority, '1') }}:
+        ${{ if or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX')) }}:
+          priorityArg: 'priority1'
+        ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+          priorityArg: '-priority=1'
+      ${{ if eq(parameters.priority, '0') }}:
+        priorityArg: ''
+
+      ${{ if eq(parameters.crossgen, 'true') }}:
+        crossgenArg: 'crossgen'
+      ${{ if eq(parameters.crossgen, 'false') }}:
+        crossgenArg: ''
+      ${{ if ne(parameters.scenarios, '') }}:
+        scenariosArg: ${{ format('/p:"Scenarios={0}"', parameters.scenarios) }}
+      ${{ if eq(parameters.scenarios, '') }}:
+        scenariosArg: ''
+
+    # TODO: Enable crossgen in build-test.sh. It currently doesn't
+    # accept a crossgen arg, so disable the macos/linux crossgen test
+    # build jobs.
+    ${{ if and(eq(parameters.crossgen, 'true'), or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX'))) }}:
+      condition: false
+
+    # Test job depends on the corresponding build job
+    dependsOn: ${{ format('build_{0}_{1}_{2}', parameters.osGroup, parameters.archType, parameters.buildConfig) }}
+
+    steps:
+
+    # Install test build dependencies
+    - ${{ if or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX')) }}:
+      - script: sh eng/install-native-dependencies.sh $(osGroup)
+        displayName: Install native dependencies
+
+
+    # Download product build from pipeline artifact storage
+    - ${{ if or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX')) }}:
+      - task: DownloadPipelineArtifact@0
+        displayName: Download product build pipeline artifact
+        inputs:
+          artifactName: ${{ format('{0}_{1}_{2}_build', parameters.osGroup, parameters.archType, parameters.buildConfig) }}
+          targetPath: $(Build.SourcesDirectory)/bin/Product/$(osGroup).$(archType).$(buildConfigUpper)
+    - ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+      - task: DownloadPipelineArtifact@0
+        displayName: Download product build pipeline artifact
+        inputs:
+          artifactName: ${{ format('{0}_{1}_{2}_build', parameters.osGroup, parameters.archType, parameters.buildConfig) }}
+          targetPath: $(Build.SourcesDirectory)\bin\Product\Windows_NT.$(archType).$(buildConfigUpper)
+
+      
+    # Build tests
+    - ${{ if or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX')) }}:
+        # TODO: enable crossgen in build-test.sh
+      - script: ./build-test.sh $(buildConfig) $(archType) $(priorityArg) $(crossgenArg)
+        displayName: Build tests
+    - ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+      - script: build-test.cmd $(buildConfig) $(archType) $(priorityArg) $(crossgenArg)
+        displayName: Build tests
+
+
+    # Prepare tests for helix
+    - ${{ if or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX')) }}:
+      - script: ./Tools/dotnetcli/dotnet msbuild tests/helixprep.proj /p:CORE_ROOT=$(Build.SourcesDirectory)/bin/tests/$(osGroup).$(archType).$(buildConfigUpper)/Tests/Core_Root /p:__BuildType=$(buildConfig) /p:__BuildArch=$(archType) /p:UsePython=true
+        # TODO: remove UsePython argument once we've removed generated wrappers in helixprep.proj
+        displayName: Prepare test archives for Helix
+    - ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+        # TODO: remove UsePython argument once we've removed generated wrappers in helixprep.proj
+      - script: .\Tools\dotnetcli\dotnet.exe msbuild tests\helixprep.proj /p:CORE_ROOT=$(Build.SourcesDirectory)\bin\tests\Windows_NT.$(archType).$(buildConfigUpper)\tests\core_root /p:__BuildType=$(buildConfig) /p:__BuildArch=$(archType) /p:UsePython=true
+        displayName: Prepare test archives for Helix
+
+
+    # Send tests to helix
+    - ${{ if or(eq(parameters.osGroup, 'Linux'), eq(parameters.osGroup, 'OSX')) }}:
+      - script: ./Tools/dotnetcli/dotnet msbuild tests/helixpublishwitharcade.proj /t:Test $(scenariosArg)
+        displayName: Send test jobs to Helix
+        env:
+          ${{ if eq(variables['System.TeamProject'], 'internal') }}:
+            # Access token variable for internal project
+            HelixAccessToken: $(HelixApiAccessToken)
+          ${{ if eq(variables['System.TeamProject'], 'public') }}:
+            # Access token variable for public project
+            HelixAccessToken: $(BotAccount-dotnet-github-anon-kaonashi-bot-helix-token)
+    - ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+      - script: .\Tools\dotnetcli\dotnet msbuild tests\helixpublishwitharcade.proj /t:Test $(scenariosArg)
+        displayName: Send test jobs to Helix
+        env:
+          ${{ if eq(variables['System.TeamProject'], 'internal') }}:
+            # Access token variable for internal project
+            HelixAccessToken: $(HelixApiAccessToken)
+          ${{ if eq(variables['System.TeamProject'], 'public') }}:
+            # Access token variable for public project
+            HelixAccessToken: $(BotAccount-dotnet-github-anon-kaonashi-bot-helix-token)
diff --git a/eng/xplat-job.yml b/eng/xplat-job.yml
new file mode 100644 (file)
index 0000000..ba15cc0
--- /dev/null
@@ -0,0 +1,50 @@
+parameters:
+  buildConfig: ''
+  archType: ''
+  osGroup: ''
+  name: ''
+  displayName: ''
+  condition: ''
+  dependsOn: ''
+  variables: {} ## any extra variables to add to the defaults defined below
+
+jobs:
+- template: /eng/common/templates/phases/base.yml@arcade
+  parameters:
+
+    name: ${{ parameters.name }}
+    displayName: ${{ parameters.displayName }}
+
+    condition: ${{ parameters.condition }}
+
+    dependsOn: ${{ parameters.dependsOn }}
+
+    queue:
+      ${{ if eq(parameters.osGroup, 'Linux') }}:
+        name: Hosted Ubuntu 1604
+      ${{ if eq(parameters.osGroup, 'OSX') }}:
+        name: Hosted macOS
+      ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+        name: dotnet-external-temp
+      timeoutInMinutes: 180
+
+    ${{ if eq(parameters.osGroup, 'Linux') }}:
+      agentOs: Ubuntu
+    ${{ if eq(parameters.osGroup, 'OSX') }}:
+      agentOs: MacOS
+    ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
+      agentOs: Windows_NT
+
+    variables:
+      buildConfig: ${{ parameters.buildConfig }}
+      ${{ if eq(parameters.buildConfig, 'checked') }}:
+        buildConfigUpper: 'Checked'
+      ${{ if eq(parameters.buildConfig, 'debug') }}:
+        buildConfigUpper: 'Debug'
+      ${{ if eq(parameters.buildConfig, 'release') }}:
+        buildConfigUpper: 'Release'
+      archType: ${{ parameters.archType }}
+      osGroup: ${{ parameters.osGroup }}
+      ${{insert}}: ${{ parameters.variables }}
+
+    steps: ${{ parameters.steps }}
index fa30d9c..5fc499a 100644 (file)
@@ -46,7 +46,7 @@ function LocateCMake
   $validVersions = @()
   foreach ($regKey in GetCMakeVersions) {
     $info = GetCMakeInfo($regKey)
-    if ($info -ne $null) { 
+    if ($info -ne $null) {
       $validVersions += @($info)
     }
   }
diff --git a/src/publishwitharcade.proj b/src/publishwitharcade.proj
new file mode 100644 (file)
index 0000000..01bc997
--- /dev/null
@@ -0,0 +1,37 @@
+<Project DefaultTargets="PublishProductPackages" Sdk="Microsoft.DotNet.Arcade.Sdk">
+
+  <!-- TODO: move properties imported from here into a common props file -->
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+
+  <Import Project="$(NuGetPackageRoot)microsoft.dotnet.build.tasks.feed\$(MicrosoftDotNetBuildTasksFeedVersion)\build\Microsoft.DotNet.Build.Tasks.Feed.targets" />
+
+  <Target Name="PublishPackages">
+
+    <ItemGroup>
+      <ItemsToPush Remove="*.nupkg" />
+      <ItemsToPush Include="$(PackagesBinDir)pkg\*.nupkg">
+        <ManifestArtifactData>NonShipping=true</ManifestArtifactData> <!-- TODO: how is this metadata used? -->
+      </ItemsToPush>
+    </ItemGroup>
+
+    <Error Condition=" '$(AzureFeedUrl)' == '' " Text="AzureFeedUrl must be set" />
+    <Error Condition=" '$(AccountKey)' == '' " Text="AccountKey must be set" />
+    <Error Condition=" '$(BUILD_REPOSITORY_URI)' == '' " Text="BUILD_REPOSITORY_URI must be set" />
+    <Error Condition=" '$(BUILD_SOURCEBRANCH)' == '' " Text="BUILD_SOURCEBRANCH must be set" />
+    <Error Condition=" '$(BUILD_BUILDNUMBER)' == '' " Text="BUILD_BUILDNUMBER must be set" />
+    <Error Condition=" '$(BUILD_SOURCEVERSION)' == '' " Text="BUILD_SOURCEVERSION must be set" />
+
+    <PushToBlobFeed ExpectedFeedUrl="$(AzureFeedUrl)"
+                    AccountKey="$(AccountKey)"
+                    ItemsToPush="@(ItemsToPush)"
+                    ManifestBuildData="Location=$(AzureFeedUrl)"
+                    ManifestRepoUri="$(BUILD_REPOSITORY_URI)"
+                    ManifestBranch="$(BUILD_SOURCEBRANCH)"
+                    ManifestBuildId="$(BUILD_BUILDNUMBER)"
+                    ManifestCommit="$(BUILD_SOURCEVERSION)" />
+                    <!-- TODO: The arcade sample publishes an asset manifest. Do we want this?
+                    AssetManifestPath="" />
+                    -->
+  </Target>
+
+</Project>
diff --git a/src/restorearcadepublishtasks.proj b/src/restorearcadepublishtasks.proj
new file mode 100644 (file)
index 0000000..21d8c61
--- /dev/null
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.DotNet.Arcade.Sdk">
+
+    <PropertyGroup>
+      <TargetFramework>net462</TargetFramework>
+      <RestoreSources/>
+      <RestoreSources>
+        $(RestoreSources);
+        https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json
+      </RestoreSources>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <PackageReference Include="Microsoft.DotNet.Build.Tasks.Feed" Version="$(MicrosoftDotNetBuildTasksFeedVersion)" />
+    </ItemGroup>
+
+</Project>
index 1cbf0ed..50d7d9b 100644 (file)
@@ -1,23 +1,26 @@
 <Project ToolsVersion="12.0" DefaultTargets="ArchiveAll" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 
-  <UsingTask TaskName="ZipFileCreateFromDirectory" AssemblyFile="$(ToolsDir)\net46\Microsoft.DotNet.Build.Tasks.dll"/>
-  
+  <UsingTask TaskName="ZipFileCreateFromDirectory" AssemblyFile="$(ToolsDir)\net46\Microsoft.DotNet.Build.Tasks.dll"
+             Condition=" '$(MSBuildRuntimeType)' != 'Core' " />
+  <UsingTask TaskName="ZipFileCreateFromDirectory" AssemblyFile="$(ToolsDir)\Microsoft.DotNet.Build.Tasks.dll"
+             Condition=" '$(MSBuildRuntimeType)' == 'Core' " />
+
   <Import Project="dir.props" />
   <Import Project="..\dir.props" />
 
   <PropertyGroup>
     <DiscoveryDirectory>$(TestWorkingDir)</DiscoveryDirectory>
-    <CoreRootDir>$(CORE_ROOT)</CoreRootDir>
     <CoreRootName>Core_Root_$(RuntimeID)</CoreRootName>
+    <UsePython Condition=" '$(UsePython)' == '' ">false</UsePython>
   </PropertyGroup>
 
   <ItemGroup>
-    <TestCmds Include="$(DiscoveryDirectory)\**\*.cmd" ></TestCmds>
     <XunitDlls Include="$(DiscoveryDirectory)\**\*.XUnitWrapper.dll" ></XunitDlls>
     <RequiresSigningFilesToDelete Include="$(DiscoveryDirectory)\**\*.requires_signing" />
   </ItemGroup>
 
   <!-- Build the platform-specific wrapper to run an individual xunit wrapper -->
+  <!-- We should remove the generated wrappers once we have transitioned our official build -->
 
   <Target Name="GenerateWrapperExecutables"
     Inputs="@(XunitDlls)"
              Targets="GenerateWrapperSh" />
   </Target>
 
+  <!-- Copy the wrapper script to the xunit directory -->
+  <Target Name="CopyWrapperScript"
+    Inputs="@(XunitDlls)"
+    Outputs="$(TestWorkingDir)*\runtest_helix.py" >
+
+    <Copy SourceFiles="$(MSBuildThisFileDirectory)\runtest_helix.py"
+          DestinationFolder="%(XunitDlls.RootDir)%(XunitDlls.Directory)" />
+
+  </Target>
+
   <!-- Zip each top-level test folder to send to Helix -->
 
   <Target Name="ArchiveTests"
     Inputs="@(XunitDlls)"
     Outputs="$(TestWorkingDir)archive\**" >
-    
+
     <Copy SourceFiles="$(CORE_ROOT)\xunit.console.dll"
           DestinationFolder="%(XunitDlls.RootDir)%(XunitDlls.Directory)"
-    />
+          Condition=" '$(UsePython)' == 'false' " />
+
     <Message Text="Deleting '.requires_signing' files to avoid file name lengths exceeding MAX_PATH" Importance="Low" />
     <Delete Files="@(RequiresSigningFilesToDelete)" />
     <MSBuild Projects="helixprep.proj"
   <!-- Zip Core_Root & Packages payload to send to Helix -->
 
   <Target Name="ArchiveCoreRoot"
-    Inputs="$(CoreRootDir)"
+    Inputs="$(CORE_ROOT)"
     Outputs="$(TestWorkingDir)archive\Core_Root" >
     <MSBuild Projects="helixprep.proj"
-             Properties="BuildPath=$(CoreRootDir);ProjectName=$(CoreRootName);BuildArchiveDir=$(TestWorkingDir)archive\Core_Root\"
+             Properties="BuildPath=$(CORE_ROOT);ProjectName=$(CoreRootName);BuildArchiveDir=$(TestWorkingDir)archive\Core_Root\"
              Targets="ArchiveBuild" />
 
     <!-- Make dummy packages.zip to upload to Helix -->
@@ -180,8 +194,13 @@ EXIT /B %ERRORLEVEL%
   <!-- Default target to run - builds executables & archives everything needed for Helix run -->
 
   <Target Name="ArchiveAll" >
+    <PropertyGroup>
+      <_ArchiveTargets Condition=" '$(UsePython)' == 'false' ">GenerateWrapperExecutables</_ArchiveTargets>
+      <_ArchiveTargets Condition=" '$(UsePython)' == 'true' ">CopyWrapperScript</_ArchiveTargets>
+      <_ArchiveTargets>$(_ArchiveTargets);ArchiveTests;ArchiveCoreRoot</_ArchiveTargets>
+    </PropertyGroup>
     <MSBuild Projects="helixprep.proj"
-             Targets="GenerateWrapperExecutables;ArchiveTests;ArchiveCoreRoot" />
+             Targets="$(_ArchiveTargets)" />
   </Target>
 
-</Project>
\ No newline at end of file
+</Project>
diff --git a/tests/helixpublishwitharcade.proj b/tests/helixpublishwitharcade.proj
new file mode 100644 (file)
index 0000000..38fb8d2
--- /dev/null
@@ -0,0 +1,93 @@
+<Project Sdk="Microsoft.DotNet.Helix.Sdk">
+
+  <!-- This project uses the helix SDK ,documented at
+       https://github.com/dotnet/arcade/tree/master/src/Microsoft.DotNet.Helix/Sdk,
+       to send test jobs to helix. -->
+
+  <Import Project="..\dir.props" />
+
+  <PropertyGroup>
+    <!-- TODO: pick appropriate helix source and type. -->
+    <HelixSource>pr/coreclr/master</HelixSource>
+    <HelixType>test/stuff</HelixType>
+    <HelixBuild>$(BUILD_BUILDNUMBER)</HelixBuild>
+
+    <!-- TODO: add target queues for rhel and linux-musl -->
+    <!-- TODO: why don't we currently run tests on windows x86? -->
+    <HelixTargetQueues Condition=" '$(BuildOS)' == 'Windows_NT' ">
+      Windows.10.Amd64;
+      Windows.10.Nano.Amd64;
+      Windows.10.Amd64.Core;
+      Windows.7.Amd64;
+      Windows.81.Amd64
+    </HelixTargetQueues>
+    <HelixTargetQueues Condition=" '$(BuildOS)' == 'Linux' ">
+      debian.82.amd64;
+      fedora.27.amd64;
+      fedora.28.amd64;
+      redhat.73.amd64;
+      ubuntu.1404.amd64;
+      ubuntu.1604.amd64;
+      ubuntu.1804.amd64;
+      opensuse.423.amd64;
+      sles.12.amd64
+    </HelixTargetQueues>
+    <HelixTargetQueues Condition=" '$(BuildOS)' == 'OSX' ">
+      osx.1012.amd64;
+      osx.1013.amd64
+    </HelixTargetQueues>
+
+    <EnableXUnitReporter>true</EnableXUnitReporter>
+    <WaitForWorkItemCompletion>true</WaitForWorkItemCompletion>
+    <SourceDirectory>$(MSBuildProjectDirectory)/..</SourceDirectory>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <HelixCorrelationPayload Include="$(SourceDirectory)\bin\tests\*\archive\Core_Root\*.zip">
+      <PayloadArchive>%(Identity)</PayloadArchive>
+    </HelixCorrelationPayload>
+  </ItemGroup>
+
+  <Target Name="SubmitTestsToHelix">
+    <ItemGroup>
+      <Scenarios Include="$(Scenarios)" />
+    </ItemGroup>
+
+    <!-- If no scenario was specified, just run the normal test
+         scenario -->
+    <MSBuild Projects="$(MSBuildProjectFile)" Targets="Test"
+             Condition=" '@(Scenarios->Count())' == '0' " />
+
+    <!-- If scenarios were specified, submit jobs for each
+         scenario. -->
+    <MSBuild Projects="$(MSBuildProjectFile)" Targets="Test"
+             Properties="Scenario=%(Scenarios.Identity)"
+             BuildInParallel="true"
+             Condition=" '@(Scenarios->Count())' != '0' " />
+
+  </Target>
+
+  <Target Name="BuildHelixWorkItem"
+          BeforeTargets="Test">
+    <PropertyGroup>
+      <!-- The "normal" scenario is just a way to include the default
+           (empty) scenario when specifying multiple scenarios at
+           once. From here, on, treat it as the empty scenario so that
+           job names will not have a scenario prefix and the runtest
+           script doesn't have to define a "normal" scenario. -->
+      <Scenario Condition=" '$(Scenario)' == 'normal' "></Scenario>
+      <ScenarioPrefix Condition=" '$(Scenario)' == '' " />
+      <ScenarioPrefix Condition=" '$(Scenario)' != '' ">$(Scenario) </ScenarioPrefix>
+    </PropertyGroup>
+    <ItemGroup>
+      <TestZipFiles Include="$(SourceDirectory)\bin\tests\*\archive\tests\*.zip" />
+      <HelixWorkItem Include="@(TestZipFiles->'$(ScenarioPrefix)%(FileName)')" >
+        <PayloadArchive>%(Identity)</PayloadArchive>
+        <Command Condition=" '$(Scenario)' == '' ">python runtest_helix.py -wrapper %(FileName).dll</Command>
+        <Command Condition=" '$(Scenario)' != '' ">python runtest_helix.py -scenario $(Scenario) -wrapper %(FileName).dll</Command>
+      </HelixWorkItem>
+    </ItemGroup>
+
+  </Target>
+
+</Project>
diff --git a/tests/runtest_helix.py b/tests/runtest_helix.py
new file mode 100755 (executable)
index 0000000..910d9d7
--- /dev/null
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+
+# This script runs tests in helix. It defines a set of test scenarios
+# that enable various combinations of runtime configurations to be set
+# via the test process environment.
+
+# This script calls "corerun xunit.console.dll xunitwrapper.dll",
+# where the xunitwrapper.dll will run a .sh/.cmd script per test in a
+# separate process. This process will have the scenario environment
+# variables set, but the xunit process will not.
+
+# TODO: Factor out logic common with runtest.py
+
+import argparse
+import subprocess
+import os
+import sys
+import tempfile
+
+test_scenarios = {
+    "jitstress2": { "COMPlus_TieredCompilation": "0",
+                    "COMPlus_JitStress": "2" },
+}
+
+if sys.platform == "linux" or sys.platform == "darwin":
+    platform_type = "unix"
+elif sys.platform == "win32":
+    platform_type = "windows"
+else:
+    print("unknown os: %s" % sys.platform)
+    sys.exit(1)
+
+def get_testenv_script(env_dict):
+    if platform_type == "unix":
+        return ''.join([ "export %s=%s%s" % (k, v, os.linesep) for k, v in env_dict.items() ])
+    elif platform_type == "windows":
+        return ''.join([ "set %s=%s%s" % (k, v, os.linesep) for k, v in env_dict.items() ])
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Parse arguments")
+    parser.add_argument("-scenario", dest="scenario", default=None)
+    parser.add_argument("-wrapper", dest="wrapper", default=None, required=True)
+    args = parser.parse_args()
+    scenario = args.scenario
+    wrapper = args.wrapper
+
+    if not "HELIX_CORRELATION_PAYLOAD" in os.environ:
+        print("HELIX_CORRELATION_PAYLOAD must be defined in environment")
+        sys.exit(1)
+
+    if not "HELIX_WORKITEM_PAYLOAD" in os.environ:
+        print("HELIX_WORKITEM_PAYLOAD must be defined in environment")
+        sys.exit(1)
+
+    core_root = os.environ["HELIX_CORRELATION_PAYLOAD"]
+
+    if platform_type == "unix":
+        corerun = os.path.join(core_root, "corerun")
+    else:
+        corerun = os.path.join(core_root, "corerun.exe")
+
+    # Unlike the old test wrapper, this runs xunit.console.dll from
+    # the correlation payload. This removes the need for redundant
+    # copies of the console runner in each test directory.
+    command = [corerun,
+               os.path.join(os.environ["HELIX_CORRELATION_PAYLOAD"], "xunit.console.dll"),
+               os.path.join(os.environ["HELIX_WORKITEM_PAYLOAD"], wrapper),
+               "-noshadow",
+               "-xml", "testResults.xml",
+               "-notrait", "category=outerloop",
+               "-notrait", "category=failing"]
+
+    if scenario is None:
+        print("CORE_ROOT=%s" % core_root)
+        os.environ["CORE_ROOT"] = core_root
+
+        print("BEGIN EXECUTION")
+        print(' '.join(command))
+        proc = subprocess.Popen(command)
+        proc.communicate()
+        print("Finished running tests. Exit code = %d" % proc.returncode)
+        sys.exit(proc.returncode)
+    else:
+        print("scenario: %s" % scenario)
+        with tempfile.NamedTemporaryFile(mode="w") as testenv:
+            testenv.write(get_testenv_script(test_scenarios[scenario]))
+            testenv.flush()
+
+            print("__TestEnv=%s" % testenv.name)
+            os.environ["__TestEnv"] = testenv.name
+
+            with open(testenv.name) as testenv_written:
+                contents = testenv_written.read()
+                print(contents)
+
+            print("CORE_ROOT=%s" % core_root)
+            os.environ["CORE_ROOT"] = core_root
+
+            print("BEGIN EXECUTION")
+            print(' '.join(command))
+            proc = subprocess.Popen(command)
+            proc.communicate()
+            print("Finished running tests. Exit code = %d" % proc.returncode)
+            sys.exit(proc.returncode)