Skip to content

Commit

Permalink
Allow trigger try workfows using labels
Browse files Browse the repository at this point in the history
This change adds and alternate method for triggering try changes.
Instead of comments, changes are triggered via applying labels to pull
requests. The action will remove the label from the request and start
the requested jobs.

This will require creating at least a few labels:

 - T-full
 - T-linux-wpt-2013
 - T-linux-wpt-2020
 - T-macos
 - T-windows

More labels can be added as we support more configurations.

The good thing about this change is that try jobs against the actual
branch in the pull request instead of the master branch. This means
that changes to CI can be tested (unlike for comment processing).

One bit caveat with this change is that when adding multiple labels, a
CI job is triggered for each. Only one real build will run for each
label, but whether or more try jobs is triggered is a race condition.
The first CI job to successfully remove the label will actually trigger
the job. If the same job removes two compatible labels, then they can
share a build (for instance two types of WPT linux jobs). If not there
will be two. Note that this is at least as efficient as the current
behavior.
  • Loading branch information
mrobinson committed Oct 18, 2023
1 parent 2d7dfb0 commit 9cba38d
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 58 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ jobs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
if: github.event_name != 'issue_comment'
if: github.event_name != 'issue_comment' && github.event_name != 'pull_request_target'
with:
fetch-depth: 2
# This is necessary to checkout the pull request if this run was triggered
# via an `issue_comment` action on a pull request.
- uses: actions/checkout@v3
if: github.event_name == 'issue_comment'
if: github.event_name == 'issue_comment' || github.event_name == 'pull_request_target'
with:
ref: refs/pull/${{ github.event.issue.number }}/head
fetch-depth: 2
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/mac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ jobs:
runs-on: macos-13
steps:
- uses: actions/checkout@v3
if: github.event_name != 'issue_comment'
if: github.event_name != 'issue_comment' && github.event_name != 'pull_request_target'
with:
fetch-depth: 2
# This is necessary to checkout the pull request if this run was triggered
# via an `issue_comment` action on a pull request.
- uses: actions/checkout@v3
if: github.event_name == 'issue_comment'
if: github.event_name == 'issue_comment' || github.event_name == 'pull_request_target'
with:
ref: refs/pull/${{ github.event.issue.number }}/head
fetch-depth: 2
Expand Down
36 changes: 23 additions & 13 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,15 @@ on:
types: [checks_requested]
workflow_call:
inputs:
platform:
required: true
type: string
layout:
configuration:
required: true
type: string
unit-tests:
required: true
type: boolean
workflow_dispatch:
inputs:
platform:
required: false
type: choice
options: ["none", "linux", "windows", "macos", "all", "sync"]
options: ["linux", "windows", "macos", "all"]
layout:
required: false
type: choice
Expand All @@ -48,6 +42,14 @@ jobs:
uses: actions/github-script@v6
with:
script: |
// If this is a workflow call with a configuration object,
// then just return it immediately.
let configuration = ${{ inputs.configuration || '""' }};
if (configuration != "") {
console.log("Using configuration: " + JSON.stringify(configuration));
return configuration;
}
// We need to pick defaults if the inputs are not provided. Unprovided inputs
// are empty strings in this template.
let platform = "${{ inputs.platform }}" || "linux";
Expand All @@ -61,34 +63,42 @@ jobs:
unit_tests = true;
}
let platforms = [];
if (platform == "all") {
platforms = [ "linux", "windows", "macos" ];
} else {
platforms = [ platform ];
}
let returnValue = {
platform,
platforms,
layout,
unit_tests,
};
console.log("Using configuration: " + JSON.stringify(returnValue));
return returnValue;
build-win:
name: Windows
needs: ["decision"]
if: ${{ contains(fromJson('["windows", "all"]'), fromJson(needs.decision.outputs.configuration).platform) }}
if: ${{ contains(fromJson(needs.decision.outputs.configuration).platforms, 'windows') }}
uses: ./.github/workflows/windows.yml
with:
unit-tests: ${{ fromJson(needs.decision.outputs.configuration).unit_tests }}

build-mac:
name: Mac
needs: ["decision"]
if: ${{ contains(fromJson('["macos", "all"]'), fromJson(needs.decision.outputs.configuration).platform) }}
if: ${{ contains(fromJson(needs.decision.outputs.configuration).platforms, 'macos') }}
uses: ./.github/workflows/mac.yml
with:
unit-tests: ${{ fromJson(needs.decision.outputs.configuration).unit_tests }}

build-linux:
name: Linux
needs: ["decision"]
if: ${{ contains(fromJson('["linux", "all"]'), fromJson(needs.decision.outputs.configuration).platform) }}
if: ${{ contains(fromJson(needs.decision.outputs.configuration).platforms, 'linux') }}
uses: ./.github/workflows/linux.yml
with:
wpt: 'test'
Expand All @@ -108,7 +118,7 @@ jobs:

steps:
- name: Mark skipped jobs as successful
if: ${{ fromJson(needs.decision.outputs.configuration).platform == 'none' }}
if: ${{ fromJson(needs.decision.outputs.configuration).platforms[0] != null }}
run: exit 0
- name: Mark the job as successful
if: ${{ !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
Expand Down
162 changes: 123 additions & 39 deletions .github/workflows/try.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
name: Try

on:
pull_request_target:
types: [labeled]
issue_comment:
types: [created]

permissions:
pull-requests: write

jobs:
parse-comment:
name: Process Comment
if: ${{ github.event.issue.pull_request }}
if: ${{ github.event_name == 'pull_request_target' || github.event.issue.pull_request }}
runs-on: ubuntu-latest
concurrency: try-${{ github.event.issue.number }}
outputs:
configuration: ${{ steps.configuration.outputs.result }}
steps:
Expand All @@ -26,62 +32,140 @@ jobs:
})
}
let tokens = context.payload.comment.body.split(/\s+/);
let tagIndex = tokens.indexOf("@bors-servo");
if (tagIndex == -1 || tagIndex + 1 >= tokens.length) {
return { try: false };
function combineWPTLayoutOptions(layout, newLayout) {
let has2013 = layout == "2013" || layout == "all";
let has2020 = layout == "2020" || layout == "all";
let adding2013 = newLayout == "2013";
let adding2020 = newLayout == "2020";
if ((adding2020 && has2020) || (adding2013 && has2013)) {
return layout;
}
if (adding2020) {
return has2013 ? "all" : "2020";
}
if (adding2013) {
return has2020 ? "all" : "2013";
}
return layout;
}
let tryString = tokens[tagIndex + 1];
console.log("Found try string: '" + tryString + "'");
let returnValue = { try: false };
if (tryString == "try") {
returnValue = { try: true, platform: 'all', layout: 'all', unit_tests: true, };
} else if (tryString == "try=wpt") {
returnValue = { try: true, platform: 'linux', layout: '2013', unit_tests: false };
} else if (tryString == "try=wpt-2020") {
returnValue = { try: true, platform: 'linux', layout: '2020', unit_tests: false };
} else if (tryString == "try=linux") {
returnValue = { try: true, platform: 'linux', layout: 'none', unit_tests: true };
} else if (tryString == "try=mac") {
returnValue = { try: true, platform: 'macos', layout: 'none', unit_tests: true };
} else if (tryString == "try=windows") {
returnValue = { try: true, platform: 'windows', layout: 'none', unit_tests: true };
} else {
makeComment("🤔 Unknown try string '" + tryString + "'");
return returnValue;
function addPlatformToConfiguration(platform, configuration) {
if (!configuration.platforms.includes(platform)) {
configuration.platforms.push(platform);
}
}
if (returnValue.try) {
let username = context.payload.comment.user.login;
let result = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username
});
if (!result.data.user.permissions.push) {
makeComment('🔒 User @' + username + ' does not have permission to trigger try jobs.');
return { try: false };
function updateConfigurationFromString(tryString, configuration) {
if (tryString.includes("full")) {
configuration.platforms = ["linux", "macos", "windows"];
configuration.unit_tests = true;
configuration.layout = "all";
return configuration;
}
if (tryString.includes("linux")) {
addPlatformToConfiguration("linux", configuration);
configuration.unit_tests = true;
} else if (tryString.includes("macos")) {
addPlatformToConfiguration("macos", configuration);
configuration.unit_tests = true;
} else if (tryString.includes("win")) {
addPlatformToConfiguration("windows", configuration);
configuration.unit_tests = true;
}
if (tryString.includes("wpt")) {
addPlatformToConfiguration("linux", configuration);
if (tryString.includes("2020")) {
configuration.layout = combineWPTLayoutOptions(configuration.layout, "2020");
} else {
configuration.layout = combineWPTLayoutOptions(configuration.layout, "2013");
}
}
}
let configuration = {
platforms: [],
layout: "none",
unit_tests: false,
};
if (context.eventName == "pull_request_target") {
let try_labels = [];
for (const label of context.payload.pull_request.labels) {
if (!label.name.startsWith("T-")) {
continue;
}
// Try to remove the label. If tht fails, it's likely that another
// workflow has already processed it or a user has removed it.
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: label.name,
});
} catch (exception) {
console.log("Assuming '" + label.name + "' is already removed: " + exception);
continue;
}
console.log("Found label: " + label.name);
updateConfigurationFromString(label.name, configuration);
}
} else {
let tokens = context.payload.comment.body.split(/\s+/);
let tagIndex = tokens.indexOf("@bors-servo");
if (tagIndex == -1 || tagIndex + 1 >= tokens.length) {
return { platforms: [] };
}
let tryString = tokens[tagIndex + 1];
console.log("Found try string: '" + tryString + "'");
if (tryString == "try") {
updateConfigurationFromString("full", configuration);
} else {
updateConfigurationFromString(tryString, configuration);
}
}
console.log(JSON.stringify(configuration));
if (configuration.platforms.length == 0) {
return { platforms: [] };
}
let username = context.payload.sender.login;
let result = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username
});
if (!result.data.user.permissions.push) {
makeComment('🔒 User @' + username + ' does not have permission to trigger try jobs.');
return { platforms: [] };
}
const url = context.serverUrl +
"/" + context.repo.owner +
"/" + context.repo.repo +
"/actions/runs/" + context.runId;
const formattedURL = "[#" + context.runId + "](" + url + ")";
makeComment("🔨 Triggering try run (" + formattedURL + ") with platform=" + returnValue.platform + " and layout=" + returnValue.layout);
return returnValue;
let platformsString = configuration.platforms.toString();
makeComment("🔨 Triggering try run (" + formattedURL + ") with platforms=" +
platformsString + " and layout=" + configuration.layout);
return configuration;
run-try:
name: Run Try
needs: ["parse-comment"]
if: ${{ fromJson(needs.parse-comment.outputs.configuration).try}}
if: ${{ fromJson(needs.parse-comment.outputs.configuration).platforms[0] != null }}
uses: ./.github/workflows/main.yml
with:
platform: ${{ fromJson(needs.parse-comment.outputs.configuration).platform }}
layout: ${{ fromJson(needs.parse-comment.outputs.configuration).layout }}
unit-tests: ${{ fromJson(needs.parse-comment.outputs.configuration).unit_tests }}
configuration: ${{ needs.parse-comment.outputs.configuration }}

results:
name: Results
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ jobs:
runs-on: windows-2019
steps:
- uses: actions/checkout@v3
if: github.event_name != 'issue_comment'
if: github.event_name != 'issue_comment' && github.event_name != 'pull_request_target'
with:
fetch-depth: 2
# This is necessary to checkout the pull request if this run was triggered
# via an `issue_comment` action on a pull request.
- uses: actions/checkout@v3
if: github.event_name == 'issue_comment'
if: github.event_name == 'issue_comment' || github.event_name == 'pull_request_target'
with:
ref: refs/pull/${{ github.event.issue.number }}/head
fetch-depth: 2
Expand Down

0 comments on commit 9cba38d

Please sign in to comment.