diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index acb9c4f05a3..1deafed2378 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,80 +11,106 @@ on: branches: ["**"] merge_group: types: [checks_requested] + workflow_call: + inputs: + platform: + required: true + type: string + layout: + 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"] + layout: + required: false + type: choice + options: ["none", "2013", "2020", "all"] + unit-tests: + required: false + type: boolean jobs: decision: name: Decision runs-on: ubuntu-20.04 outputs: - skipped: ${{ steps.skipDecision.outputs.result }} - platforms: ${{ steps.platformDecision.outputs.result }} + configuration: ${{ steps.configuration.outputs.result }} steps: - - name: Skip Decision - id: skipDecision + - name: Configuration + id: configuration uses: actions/github-script@v6 with: - result-encoding: string script: | - // Never skip workflow runs for pull requests or merge groups, which might - // need to actually run / retry WPT tests. - if (context.eventName == "pull_request" || context.eventName == "merge_group") { - return "run"; + // Never skip workflow runs for pull requests, merge groups, or manually triggered + // workfows / try jobs, which might need to actually run / retry WPT tests. + if (!['pull_request', 'merge_group', 'workflow_run', 'workflow_call'].includes(context.eventName)) { + // Skip the run if an identical run already exists. This helps to avoid running + // the workflow over and over again for the same commit hash. + if ((await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: "main.yml", + head_sha: context.sha, + status: "success", + })).data.workflow_runs.length > 0) { + console.log("Skipping workflow, because of duplicate job."); + return { platform: "none" }; + } } - // Skip the run if an identical run already exists. This helps to avoid running - // the workflow over and over again for the same commit hash. - if ((await github.rest.actions.listWorkflowRuns({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: "main.yml", - head_sha: context.sha, - status: "success", - })).data.workflow_runs.length > 0) { - return "skip" - } else { - return "run" - } - - name: Platform Decision - id: platformDecision - uses: actions/github-script@v6 - with: - result-encoding: string - script: | - if ("${{ steps.skipDecision.outputs.result }}" == "skip") { - return "none"; - } - if (context.eventName == "push" || context.eventName == "merge_group") { - return "all"; - } - return "linux" + // We need to pick defaults if the inputs are not provided. Unprovided inputs + // are empty strings in this template. + let platform = "${{ inputs.platform }}" || "linux"; + let layout = "${{ inputs.layout }}" || "none"; + let unit_tests = Boolean(${{ inputs.unit-tests }}) + + // Merge queue runs and pushes to master should always trigger a full build and test. + if (["push", "merge_group"].includes(context.eventName)) { + platform = "all"; + layout = "all"; + unit_tests = true; + } + + let returnValue = { + platform, + layout, + unit_tests, + }; + console.log("Using configuration: " + JSON.stringify(returnValue)); + return returnValue; build-win: name: Windows needs: ["decision"] - if: ${{ needs.decision.outputs.platforms == 'all' }} + if: ${{ contains(fromJson('["windows", "all"]'), fromJson(needs.decision.outputs.configuration).platform) }} uses: ./.github/workflows/windows.yml with: - unit-tests: true + unit-tests: ${{ fromJson(needs.decision.outputs.configuration).unit_tests }} build-mac: name: Mac needs: ["decision"] - if: ${{ needs.decision.outputs.platforms == 'all' }} + if: ${{ contains(fromJson('["macos", "all"]'), fromJson(needs.decision.outputs.configuration).platform) }} uses: ./.github/workflows/mac.yml with: - unit-tests: true + unit-tests: ${{ fromJson(needs.decision.outputs.configuration).unit_tests }} build-linux: name: Linux needs: ["decision"] - if: ${{ needs.decision.outputs.platforms == 'all' || needs.decision.outputs.platforms == 'linux' }} + if: ${{ contains(fromJson('["linux", "all"]'), fromJson(needs.decision.outputs.configuration).platform) }} uses: ./.github/workflows/linux.yml with: wpt: 'test' - layout: ${{ (github.event_name == 'push' || github.event_name == 'merge_group') && 'all' || 'none' }} - unit-tests: ${{ github.event_name == 'push' || github.event_name == 'merge_group' }} + layout: ${{ fromJson(needs.decision.outputs.configuration).layout }} + unit-tests: ${{ fromJson(needs.decision.outputs.configuration).unit_tests }} build-result: name: Result @@ -99,7 +125,7 @@ jobs: steps: - name: Mark skipped jobs as successful - if: ${{ needs.decision.outputs.skipped == 'skip' }} + if: ${{ fromJson(needs.decision.outputs.configuration).platform == 'none' }} run: exit 0 - name: Mark the job as successful if: ${{ !contains(join(needs.*.result, ','), 'failure') && !contains(join(needs.*.result, ','), 'cancelled') }} diff --git a/.github/workflows/try.yml b/.github/workflows/try.yml new file mode 100644 index 00000000000..336f2935b93 --- /dev/null +++ b/.github/workflows/try.yml @@ -0,0 +1,121 @@ +on: issue_comment +name: Try + +jobs: + parse-comment: + name: Process Comment + if: ${{ github.event.issue.pull_request }} + runs-on: ubuntu-latest + outputs: + configuration: ${{ steps.configuration.outputs.result }} + steps: + - uses: actions/github-script@v6 + id: configuration + with: + script: | + function makeComment(body) { + console.log(body); + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body + }) + } + + let tokens = "${{ github.event.comment.body }}".split(" "); + let tagIndex = tokens.indexOf("@bors-servo"); + if (tagIndex == -1 || tagIndex + 1 >= tokens.length) { + return { try: false }; + } + + 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; + } + + if (returnValue.try) { + let result = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: "${{ github.event.sender.login }}" + }); + if (!result.data.user.permissions.push) { + makeComment('🔒 User @${{ github.event.sender.login }} does not have permission to trigger try jobs.'); + return { try: false }; + } + } + + 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; + + run-try: + name: Run Try + needs: ["parse-comment"] + if: ${{ fromJson(needs.parse-comment.outputs.configuration).try}} + 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 }} + + results: + name: Results + needs: ["parse-comment", "run-try"] + runs-on: ubuntu-latest + if: ${{ always() && fromJson(needs.parse-comment.outputs.configuration).try}} + steps: + - name: Success + if: ${{ !contains(join(needs.*.result, ','), 'failure') }} + uses: actions/github-script@v6 + with: + script: | + const url = context.serverUrl + + "/" + context.repo.owner + + "/" + context.repo.repo + + "/actions/runs/" + context.runId; + const formattedURL = "[#" + context.runId + "](" + url + ")"; + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "✨ Try run (" + formattedURL + ") " + "succeeded.", + }); + - name: Failure + if: ${{ contains(join(needs.*.result, ','), 'failure') }} + uses: actions/github-script@v6 + with: + script: | + const url = context.serverUrl + + "/" + context.repo.owner + + "/" + context.repo.repo + + "/actions/runs/" + context.runId; + const formattedURL = "[#" + context.runId + "](" + url + ")"; + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "⚠️ Try run (" + formattedURL + ") " + "failed.", + }); + + diff --git a/etc/ci/report_aggregated_expected_results.py b/etc/ci/report_aggregated_expected_results.py index 177e9266bd6..832fc6f0ba8 100755 --- a/etc/ci/report_aggregated_expected_results.py +++ b/etc/ci/report_aggregated_expected_results.py @@ -156,28 +156,24 @@ def get_github_run_url() -> Optional[str]: return f"[#{run_id}](https://github.com/{repository}/actions/runs/{run_id})" -def is_pr_open(pr_number: str) -> bool: - return b"open" == subprocess.check_output( - ["gh", "api", f"/repos/servo/servo/pulls/{pr_number}", "--template", "{{.state}}"]) - - def get_pr_number() -> Optional[str]: github_context = json.loads(os.environ.get("GITHUB_CONTEXT", "{}")) if "event" not in github_context: return None - if "head_commit" not in github_context["event"]: - return None - commit_title = github_context["event"]["head_commit"]["message"] - match = re.match(r"^Auto merge of #(\d+)", commit_title) - if not match: - return None + # If we have a 'merge_group' in the context, this was triggered by + # the merge queue. + if "merge_group" in github_context["event"]: + commit_title = github_context["event"]["merge_group"]["head_commit"]["message"] + match = re.match(r"\(#(\d+)\)$", commit_title) + return match.group(1) if match else None - # Only return a PR number if the PR is open. bors will often push old merges - # onto the HEAD of try branches and we don't want to return results for these - # old PRs. - number = match.group(1) - return number if is_pr_open(number) else None + # If we have an 'issue' in the context, this was triggered by a try comment + # on a PR. + if "issue" in github_context["event"]: + return str(github_context["event"]["issue"]["number"]) + + return None def create_check_run(body: str, tag: str = ""): @@ -246,7 +242,7 @@ def main(): if pr_number: process = subprocess.Popen( ['gh', 'pr', 'comment', pr_number, '-F', '-'], stdin=subprocess.PIPE) - process.communicate(input=html_string.encode("utf-8"))[0] + print(process.communicate(input=html_string.encode("utf-8"))[0]) else: print("Could not find PR number in environment. Not making GitHub comment.")