diff --git a/.github/workflows/nightly-pipeline.yml b/.github/workflows/nightly-pipeline.yml index ce793b713..54114ab6c 100644 --- a/.github/workflows/nightly-pipeline.yml +++ b/.github/workflows/nightly-pipeline.yml @@ -13,6 +13,35 @@ on: - cron: '0 3 * * *' jobs: + # The publish builds main, which the pull requests into rc do not change, so + # it runs first and has the runners before the pull request builds do. + Publish: + name: Nightly Publish + uses: 51Degrees/common-ci/.github/workflows/nightly-publish.yml@main + with: + repo-name: ${{ github.event.repository.name }} + org-name: ${{ github.event.repository.owner.login }} + dryrun: ${{ inputs.dryrun || false }} + common-ci-ref: ${{ inputs.common-ci-ref || '' }} + cache-assets: true + secrets: + token: ${{ secrets.ACCESS_TOKEN }} + DeviceDetection: ${{ secrets.DEVICE_DETECTION_KEY }} + DeviceDetectionUrl: ${{ secrets.DEVICE_DETECTION_URL }} + IpIntelligence: ${{ secrets.IP_INTELLIGENCE_KEY }} + IpIntelligenceUrl: ${{ secrets.IPI_DATA_FILE_URL }} + TestResourceKey: ${{ secrets._51DEGREES_RESOURCE_KEY_BESPOKE }} + MavenSettings: ${{ secrets.MAVEN_SETTINGS }} + JavaGpgKeyPassphrase: ${{ secrets.JAVA_GPG_KEY_PASSPHRASE }} + JavaPGP: ${{ secrets.JAVA_KEY_PGP_FILE }} + CodeSigningKeyVaultName: ${{ secrets.CODE_SIGNING_KEY_VAULT_NAME }} + CodeSigningKeyVaultUrl: ${{ secrets.CODE_SIGNING_KEY_VAULT_URL }} + CodeSigningKeyVaultClientId: ${{ secrets.CODE_SIGNING_KEY_VAULT_CLIENT_ID }} + CodeSigningKeyVaultTenantId: ${{ secrets.CODE_SIGNING_KEY_VAULT_TENANT_ID }} + CodeSigningKeyVaultClientSecret: ${{ secrets.CODE_SIGNING_KEY_VAULT_CLIENT_SECRET }} + CodeSigningKeyVaultCertificateName: ${{ secrets.CODE_SIGNING_KEY_VAULT_CERTIFICATE_NAME }} + CodeSigningKeyVaultCertificateData: ${{ secrets.CODE_SIGNING_KEY_VAULT_CERTIFICATE_DATA }} + DataFileChange: name: Nightly Data File Change uses: 51Degrees/common-ci/.github/workflows/nightly-data-file-change.yml@main @@ -50,7 +79,7 @@ jobs: PullRequests: if: ${{ !cancelled() }} - needs: [DataFileChange, PackageUpdate, SubmoduleUpdate] + needs: [Publish, DataFileChange, PackageUpdate, SubmoduleUpdate] name: Nightly Pull Requests uses: 51Degrees/common-ci/.github/workflows/nightly-pull-requests.yml@main with: @@ -59,6 +88,8 @@ jobs: dryrun: ${{ inputs.dryrun || false }} common-ci-ref: ${{ inputs.common-ci-ref || '' }} cache-assets: true + # One configuration that fails does not cancel the others. + fail-fast: false secrets: token: ${{ secrets.ACCESS_TOKEN }} DeviceDetection: ${{ secrets.DEVICE_DETECTION_KEY }} @@ -66,32 +97,3 @@ jobs: IpIntelligence: ${{ secrets.IP_INTELLIGENCE_KEY }} IpIntelligenceUrl: ${{ secrets.IPI_DATA_FILE_URL }} TestResourceKey: ${{ secrets._51DEGREES_RESOURCE_KEY_BESPOKE }} - - Publish: - if: ${{ !cancelled() }} - needs: PullRequests - name: Nightly Publish - uses: 51Degrees/common-ci/.github/workflows/nightly-publish.yml@main - with: - repo-name: ${{ github.event.repository.name }} - org-name: ${{ github.event.repository.owner.login }} - dryrun: ${{ inputs.dryrun || false }} - common-ci-ref: ${{ inputs.common-ci-ref || '' }} - cache-assets: true - secrets: - token: ${{ secrets.ACCESS_TOKEN }} - DeviceDetection: ${{ secrets.DEVICE_DETECTION_KEY }} - DeviceDetectionUrl: ${{ secrets.DEVICE_DETECTION_URL }} - IpIntelligence: ${{ secrets.IP_INTELLIGENCE_KEY }} - IpIntelligenceUrl: ${{ secrets.IPI_DATA_FILE_URL }} - TestResourceKey: ${{ secrets._51DEGREES_RESOURCE_KEY_BESPOKE }} - MavenSettings: ${{ secrets.MAVEN_SETTINGS }} - JavaGpgKeyPassphrase: ${{ secrets.JAVA_GPG_KEY_PASSPHRASE }} - JavaPGP: ${{ secrets.JAVA_KEY_PGP_FILE }} - CodeSigningKeyVaultName: ${{ secrets.CODE_SIGNING_KEY_VAULT_NAME }} - CodeSigningKeyVaultUrl: ${{ secrets.CODE_SIGNING_KEY_VAULT_URL }} - CodeSigningKeyVaultClientId: ${{ secrets.CODE_SIGNING_KEY_VAULT_CLIENT_ID }} - CodeSigningKeyVaultTenantId: ${{ secrets.CODE_SIGNING_KEY_VAULT_TENANT_ID }} - CodeSigningKeyVaultClientSecret: ${{ secrets.CODE_SIGNING_KEY_VAULT_CLIENT_SECRET }} - CodeSigningKeyVaultCertificateName: ${{ secrets.CODE_SIGNING_KEY_VAULT_CERTIFICATE_NAME }} - CodeSigningKeyVaultCertificateData: ${{ secrets.CODE_SIGNING_KEY_VAULT_CERTIFICATE_DATA }} diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index c1fda910b..5a8462654 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -9,6 +9,10 @@ on: common-ci-ref: type: string default: '' + full-matrix: + description: 'Build every configuration, not only those the changes need' + type: boolean + default: false jobs: PullRequests: @@ -20,6 +24,12 @@ jobs: dryrun: ${{ inputs.dryrun || false }} common-ci-ref: ${{ inputs.common-ci-ref || '' }} cache-assets: true + # Each pull request is built on the configurations its changes need, + # as ci/select-options.ps1 chooses, and one that fails does not cancel + # the others. With nothing eligible, the nightly builds rc instead. + full-matrix: ${{ inputs.full-matrix || false }} + fail-fast: false + build-without-pull-requests: false secrets: token: ${{ secrets.ACCESS_TOKEN }} DeviceDetection: ${{ secrets.DEVICE_DETECTION_KEY }} diff --git a/.github/workflows/release-pull-request.yml b/.github/workflows/release-pull-request.yml new file mode 100644 index 000000000..4dfeb08f9 --- /dev/null +++ b/.github/workflows/release-pull-request.yml @@ -0,0 +1,31 @@ +name: Release Pull Request + +# Builds and tests the release pull request, rc into main, on every entry in +# ci/options.json, and never merges it. A pull request into rc is built only on +# the entries its changes need, so this is the full build of a release before +# it reaches main. See common-ci .github/workflows/release-pull-request.yml. +on: + pull_request: + branches: [main] + +# A push to rc while the release is open builds it again, in place of the +# build of the older rc. +concurrency: + group: release-pull-request-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + Release: + name: Release Pull Request + uses: 51Degrees/common-ci/.github/workflows/release-pull-request.yml@main + with: + repo-name: ${{ github.event.repository.name }} + org-name: ${{ github.event.repository.owner.login }} + cache-assets: true + secrets: + token: ${{ secrets.ACCESS_TOKEN }} + DeviceDetection: ${{ secrets.DEVICE_DETECTION_KEY }} + DeviceDetectionUrl: ${{ secrets.DEVICE_DETECTION_URL }} + IpIntelligence: ${{ secrets.IP_INTELLIGENCE_KEY }} + IpIntelligenceUrl: ${{ secrets.IPI_DATA_FILE_URL }} + TestResourceKey: ${{ secrets._51DEGREES_RESOURCE_KEY_BESPOKE }} diff --git a/ci/README.md b/ci/README.md index 1041809a8..b80695746 100644 --- a/ci/README.md +++ b/ci/README.md @@ -20,6 +20,15 @@ repositories used to do for one package tree each. request tests the examples against the libraries it changes. The publish workflow instead tests the examples against the version it built, through [run-integration-tests.ps1](run-integration-tests.ps1). +- [select-options.ps1](select-options.ps1) chooses the entries a pull request + runs. The standard set is JDK 17 and JDK 8 on Linux, without the three IP + Intelligence tests in [standard-excludes.txt](standard-excludes.txt) and + with the Selenium contract on JDK 17 alone. A change to a native module, IP + Intelligence, a `pom.xml` or a file outside the package trees adds to it. + macOS and performance run only when `rc` itself or the release pull request + is built, which + [release-pull-request.yml](../.github/workflows/release-pull-request.yml) + does on every entry. - [fetch-assets.ps1](fetch-assets.ps1) links every data file it downloads into `cxx/device-detection-data` or `cxx/ip-intelligence-data`, where the tests and examples find them. diff --git a/ci/run-integration-tests.ps1 b/ci/run-integration-tests.ps1 index ae705f41a..6b90781ed 100644 --- a/ci/run-integration-tests.ps1 +++ b/ci/run-integration-tests.ps1 @@ -7,7 +7,10 @@ param( [string]$TestResourceKey, [string]$DeviceDetection, [string]$IpIntelligence, - [string]$IpIntelligenceUrl + [string]$IpIntelligenceUrl, + # False, from select-options.ps1, on every entry of a pull request but + # one, so the browser contract runs once. + [bool]$SeleniumContract = $true ) $ErrorActionPreference = "Stop" # Each part's exit code is checked below, so one failing part does not stop @@ -124,7 +127,9 @@ function Invoke-ContractTests { } Write-Host "::group::Selenium contract tests" -if (-not $TestResourceKey) { +if (-not $SeleniumContract) { + Write-Host "Another entry runs the Selenium contract tests for this pull request." +} elseif (-not $TestResourceKey) { Write-Host "::warning title=Selenium skipped::No resource key, so the Selenium contract tests were not run against the cloud example." } elseif ($IsLinux -and [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { Write-Host "::warning title=Selenium skipped::Selenium contract skipped on linux-arm64, which has no selenium-manager build. It runs on x64 and macOS arm64." diff --git a/ci/run-unit-tests.ps1 b/ci/run-unit-tests.ps1 index 562f29486..da9afd154 100644 --- a/ci/run-unit-tests.ps1 +++ b/ci/run-unit-tests.ps1 @@ -2,7 +2,11 @@ param( [Parameter(Mandatory)][string]$RepoName, [string]$Name, [string]$TestResourceKey, - [string]$DeviceDetection + [string]$DeviceDetection, + # How deeply a pull request is tested, as select-options.ps1 chose. + # standard leaves out the tests listed in standard-excludes.txt. Empty, for + # the branch itself or a release, runs every test. + [string]$Depth ) $PSNativeCommandUseErrorActionPreference = $false @@ -13,11 +17,14 @@ $PSNativeCommandUseErrorActionPreference = $false # Detection examples tests, and LicenseKey by the Device Detection data file # update example. The IP Intelligence update example's test is ignored, so it # does not need a key of its own. +[string[]]$excludes = if ($Depth -eq 'standard') { + "-Dsurefire.excludesFile=$PSScriptRoot/standard-excludes.txt" +} Write-Host "Entering '$RepoName'" Push-Location $RepoName try { Write-Host "Testing '$Name'" - mvn surefire:test @MavenOptions -Pdev -DfailIfNoTests=false ` + mvn surefire:test @MavenOptions -Pdev -DfailIfNoTests=false @excludes ` "-DTestResourceKey=$TestResourceKey" ` "-DSuperResourceKey=$TestResourceKey" ` "-DLicenseKey=$DeviceDetection" diff --git a/ci/select-options.ps1 b/ci/select-options.ps1 new file mode 100644 index 000000000..66e0e1620 --- /dev/null +++ b/ci/select-options.ps1 @@ -0,0 +1,70 @@ +param( + # Every entry of options.json. + [Parameter(Mandatory)][object[]]$Options, + # The files the pull request changes, less documentation. + [string[]]$ChangedFiles, + # Those of the changed files whose change touches only comments. + [string[]]$CommentOnlyFiles +) +# Chooses the options.json entries a pull request is built on, for common-ci's +# steps/select-options.ps1. The branch itself and a release build every entry. +# +# The standard set is JDK 17 and JDK 8 on Linux, the main JDK and the oldest, +# without the three IP Intelligence tests that load the Enterprise data file, +# and with the Selenium contract on JDK 17 alone. A change in more than its +# comments to a native module or the cxx submodule adds Windows and Linux ARM, +# where the native library is built differently. A change to IP Intelligence +# or the cxx submodule runs the three tests, and a change to a pom.xml adds +# JDK 11 and 21. A change to a file outside the package trees, such as ci, +# counts for every entry. Only five macOS jobs run at once across the +# organisation, and the releases need them, so macOS is left to the branch and +# release builds, as is performance. + +$Standard = @('Ubuntu_Java_17', 'Ubuntu_Java_8') +$Contract = 'Ubuntu_Java_17' +$Native = @('Windows_Java_17', 'Ubuntu_ARM_Java_17') +$Jdk = @('Ubuntu_Java_11', 'Ubuntu_Java_21') + +$PackageTrees = @('pipeline/', 'device-detection/', 'ip-intelligence/', + 'location/', 'examples/') +$NativePaths = @('device-detection/device-detection.hash.engine.on-premise/', + 'ip-intelligence/ip-intelligence.engine.on-premise/') +$IpIntelligencePaths = @('ip-intelligence/', 'examples/ip-intelligence/') + +function Test-Under([string]$Path, [string[]]$Prefixes) { + return @($Prefixes | Where-Object { $Path.StartsWith($_) }).Count -gt 0 +} + +$code = @($ChangedFiles | Where-Object { $CommentOnlyFiles -notcontains $_ }) +$cxx = @($code | Where-Object { $_ -eq 'cxx' }).Count -gt 0 +$native = $cxx -or @($code | Where-Object { Test-Under $_ $NativePaths }).Count -gt 0 +$ipi = $cxx -or + @($code | Where-Object { Test-Under $_ $IpIntelligencePaths }).Count -gt 0 +$poms = @($code | Where-Object { $_ -match '(^|/)pom\.xml$' }).Count -gt 0 +$other = @($code | Where-Object { + $_ -ne 'cxx' -and $_ -notmatch '(^|/)pom\.xml$' -and + !(Test-Under $_ $PackageTrees) +}).Count -gt 0 +Write-Host ("Native: $native. IP Intelligence: $ipi. pom.xml: $poms. " + + "Outside the package trees: $other.") + +foreach ($entry in $Options) { + $name = $entry.Name + if ($entry.Image -like 'macos*') { + Write-Host "$name is left to the branch and release builds" + continue + } + $why = if ($Standard -contains $name) { 'the standard set' } + elseif ($other) { 'a change outside the package trees' } + elseif ($native -and $Native -contains $name) { 'a native change' } + elseif ($poms -and $Jdk -contains $name) { 'a pom.xml change' } + if (!$why) { + Write-Host "$name is not needed for this change" + continue + } + $entry.Depth = ($ipi -or $other) ? 'deep' : 'standard' + $entry.SeleniumContract = $name -eq $Contract + $entry.RunPerformance = $false + Write-Host "$name runs for $why, at depth $($entry.Depth)" + $entry +} diff --git a/ci/setup-environment.ps1 b/ci/setup-environment.ps1 index ac900bbd4..23e2d11a9 100644 --- a/ci/setup-environment.ps1 +++ b/ci/setup-environment.ps1 @@ -1,6 +1,8 @@ param( [Parameter(Mandatory)][string]$RepoName, - [Parameter(Mandatory)][string]$JavaSDKEnvVar + [Parameter(Mandatory)][string]$JavaSDKEnvVar, + # standard, from select-options.ps1, leaves out the tests WMIC is for. + [string]$Depth ) $ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true @@ -11,7 +13,9 @@ $PSNativeCommandUseErrorActionPreference = $true # Without WMIC a forked test JVM on Windows can end itself part way through # the long running data file tests. The script explains why. -& "$PSScriptRoot/install-wmic.ps1" +if ($Depth -ne 'standard') { + & "$PSScriptRoot/install-wmic.ps1" +} # The native builds run PreBuild.sh, which has to be executable whatever the # checkout did to its mode. diff --git a/ci/standard-excludes.txt b/ci/standard-excludes.txt new file mode 100644 index 000000000..03d3b4b92 --- /dev/null +++ b/ci/standard-excludes.txt @@ -0,0 +1,8 @@ +# Tests that the standard set of a pull request leaves out, for +# run-unit-tests.ps1. Each loads the multi-gigabyte IP Intelligence Enterprise +# data file. +**/fiftyone/ipintelligence/IPIntelligenceTests.* +**/fiftyone/ipintelligence/examples/console/MetadataOnPremTest.* +**/fiftyone/ipintelligence/examples/console/CompareOnPremTest.* +# Surefire's own default, which a file of excludes replaces. +**/*$*