diff --git a/.github/workflows/evict_caches.yml b/.github/workflows/evict_caches.yml new file mode 100644 index 0000000..28cbd38 --- /dev/null +++ b/.github/workflows/evict_caches.yml @@ -0,0 +1,138 @@ +# Code generated internal/ci/ci_tool.cue; DO NOT EDIT. + +name: Evict caches +"on": + schedule: + - cron: 0 2 * * * +jobs: + test: + if: ${{github.repository == 'cue-lang/cue-api-java'}} + runs-on: ubuntu-22.04 + defaults: + run: + shell: bash + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - name: Reset git directory modification times + run: touch -t 202211302355 $(find * -type d) + - name: Restore git file modification times + uses: chetan/git-restore-mtime-action@075f9bc9d159805603419d50f794bd9f33252ebe + - id: DispatchTrailer + name: Try to extract Dispatch-Trailer + run: |- + x="$(git log -1 --pretty='%(trailers:key=Dispatch-Trailer,valueonly)')" + if [[ "$x" == "" ]] + then + # Some steps rely on the presence or otherwise of the Dispatch-Trailer. + # We know that we don't have a Dispatch-Trailer in this situation, + # hence we use the JSON value null in order to represent that state. + # This means that GitHub expressions can determine whether a Dispatch-Trailer + # is present or not by checking whether the fromJSON() result of the + # output from this step is the JSON value null or not. + x=null + fi + echo "value<> $GITHUB_OUTPUT + echo "$x" >> $GITHUB_OUTPUT + echo "EOD" >> $GITHUB_OUTPUT + - if: |- + ((github.ref == 'refs/heads/main') && (! (contains(github.event.head_commit.message, ' + Dispatch-Trailer: {"type":"')))) && (contains(github.event.head_commit.message, ' + Dispatch-Trailer: {"type":"')) + name: Check we don't have Dispatch-Trailer on a protected branch + run: |- + echo "github.event.head_commit.message contains Dispatch-Trailer" + echo "github.event.head_commit.message value" + cat < ~/.netrc + machine review.gerrithub.io + login cueckoo + password ${{ secrets.CUECKOO_GERRITHUB_PASSWORD }} + EOD + chmod 600 ~/.netrc + - name: Push tip to trybot + run: |- + mkdir tmpgit + cd tmpgit + git init -b initialbranch + git config user.name cueckoo + git config user.email cueckoo@gmail.com + git config http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n cueckoo:${{ secrets.CUECKOO_GITHUB_PAT }} | base64)" + git remote add origin https://review.gerrithub.io/a/cue-lang/cue-api-java + git remote add trybot https://github.com/cue-lang/cue-api-java-trybot + + git fetch origin "${{ github.ref }}" + + success=false + for try in {1..20}; do + echo "Push to trybot try $try" + if git push -f trybot "FETCH_HEAD:${{ github.ref }}"; then + success=true + break + fi + sleep 1 + done + if ! $success; then + echo "Giving up" + exit 1 + fi diff --git a/.github/workflows/trybot.yml b/.github/workflows/trybot.yml new file mode 100644 index 0000000..a733ec1 --- /dev/null +++ b/.github/workflows/trybot.yml @@ -0,0 +1,112 @@ +# Code generated internal/ci/ci_tool.cue; DO NOT EDIT. + +name: TryBot +"on": + push: + branches: + - ci/test + - main + pull_request: {} + workflow_dispatch: {} +jobs: + test: + runs-on: ubuntu-22.04 + defaults: + run: + shell: bash + if: |- + (contains(github.event.head_commit.message, ' + Dispatch-Trailer: {"type":"trybot"')) || ! (contains(github.event.head_commit.message, ' + Dispatch-Trailer: {"type":"')) + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - name: Reset git directory modification times + run: touch -t 202211302355 $(find * -type d) + - name: Restore git file modification times + uses: chetan/git-restore-mtime-action@075f9bc9d159805603419d50f794bd9f33252ebe + - id: DispatchTrailer + name: Try to extract Dispatch-Trailer + run: |- + x="$(git log -1 --pretty='%(trailers:key=Dispatch-Trailer,valueonly)')" + if [[ "$x" == "" ]] + then + # Some steps rely on the presence or otherwise of the Dispatch-Trailer. + # We know that we don't have a Dispatch-Trailer in this situation, + # hence we use the JSON value null in order to represent that state. + # This means that GitHub expressions can determine whether a Dispatch-Trailer + # is present or not by checking whether the fromJSON() result of the + # output from this step is the JSON value null or not. + x=null + fi + echo "value<> $GITHUB_OUTPUT + echo "$x" >> $GITHUB_OUTPUT + echo "EOD" >> $GITHUB_OUTPUT + - if: |- + ((github.ref == 'refs/heads/main') && (! (contains(github.event.head_commit.message, ' + Dispatch-Trailer: {"type":"')))) && (contains(github.event.head_commit.message, ' + Dispatch-Trailer: {"type":"')) + name: Check we don't have Dispatch-Trailer on a protected branch + run: |- + echo "github.event.head_commit.message contains Dispatch-Trailer" + echo "github.event.head_commit.message value" + cat <", so grab the email with sed. + # For now, we require the sorted lists of author and signer emails to match. + # Note that this also fails if a commit isn't signed-off at all. + # + # In Gerrit we already enable a form of this via https://gerrit-review.googlesource.com/Documentation/project-configuration.html#require-signed-off-by, + # but it does not support co-authors nor can it be used when testing GitHub PRs. + commit_authors="$( + { + git log -1 --pretty='%ae' + git log -1 --pretty='%(trailers:key=Co-authored-by,valueonly)' | sed -ne 's/.* <\(.*\)>/\1/p' + } | sort -u + )" + commit_signers="$( + { + git log -1 --pretty='%(trailers:key=Signed-off-by,valueonly)' | sed -ne 's/.* <\(.*\)>/\1/p' + } | sort -u + )" + if [[ "${commit_authors}" != "${commit_signers}" ]]; then + echo "Error: commit author email addresses do not match signed-off-by trailers" + echo + echo "Authors:" + echo "${commit_authors}" + echo + echo "Signers:" + echo "${commit_signers}" + exit 1 + fi + - name: Re-generate CI + run: |- + cue cmd importjsonschema ./vendor + cue cmd gen + working-directory: ./internal/ci + - name: Check that git is clean at the end of the job + run: test -z "$(git status --porcelain)" || (git status; git diff; false) diff --git a/.github/workflows/trybot_dispatch.yml b/.github/workflows/trybot_dispatch.yml new file mode 100644 index 0000000..c47ba5a --- /dev/null +++ b/.github/workflows/trybot_dispatch.yml @@ -0,0 +1,133 @@ +# Code generated internal/ci/ci_tool.cue; DO NOT EDIT. + +name: Dispatch trybot +"on": + repository_dispatch: {} + push: + branches: + - ci/test +jobs: + trybot: + runs-on: ubuntu-22.04 + defaults: + run: + shell: bash + if: ${{ ((github.ref == 'refs/heads/ci/test') && false) || github.event.client_payload.type == 'trybot' }} + steps: + - name: Write netrc file for cueckoo Gerrithub + run: |- + cat < ~/.netrc + machine review.gerrithub.io + login cueckoo + password ${{ secrets.CUECKOO_GERRITHUB_PASSWORD }} + EOD + chmod 600 ~/.netrc + - id: payload + if: github.repository == 'cue-lang/cue-api-java' && (github.ref == 'refs/heads/ci/test') + name: Write fake payload + run: |- + cat <> $GITHUB_OUTPUT + value<.needs + // keyword. + // Each job runs in a fresh instance of the virtual environment + // specified by runs-on. + // You can run an unlimited number of jobs as long as you are + // within the workflow usage limits. For more information, see + // https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#usage-limits. + jobs: { + {[=~"^[_a-zA-Z][a-zA-Z0-9_-]*$" & !~"^()$"]: #normalJob | #reusableWorkflowCallJob} + } + + // The name for workflow runs generated from the workflow. GitHub + // displays the workflow run name in the list of workflow runs on + // your repository's 'Actions' tab. + "run-name"?: string + permissions?: #permissions + + #architecture: "ARM32" | "x64" | "x86" + + #branch: #globs + + #concurrency: { + // When a concurrent job or workflow is queued, if another job or + // workflow using the same concurrency group in the repository is + // in progress, the queued job or workflow will be pending. Any + // previously pending job or workflow in the concurrency group + // will be canceled. + group: string + + // To cancel any currently running job or workflow in the same + // concurrency group, specify cancel-in-progress: true. + "cancel-in-progress"?: bool | #expressionSyntax + } + + #configuration: string | number | bool | { + [string]: #configuration + } | [...#configuration] + + #container: { + // The Docker image to use as the container to run the action. The + // value can be the Docker Hub image name or a registry name. + image: string + + // If the image's container registry requires authentication to + // pull the image, you can use credentials to set a map of the + // username and password. The credentials are the same values + // that you would provide to the `docker login` command. + credentials?: { + username?: string + password?: string + ... + } + + // Sets an array of environment variables in the container. + env?: #env + + // Sets an array of ports to expose on the container. + ports?: [...number | string] & [_, ...] + + // Sets an array of volumes for the container to use. You can use + // volumes to share data between services or other steps in a + // job. You can specify named Docker volumes, anonymous Docker + // volumes, or bind mounts on the host. + // To specify a volume, you specify the source and destination + // path: : + // The is a volume name or an absolute path on the host + // machine, and is an absolute path in the + // container. + volumes?: [...=~"^[^:]+:[^:]+$"] & [_, ...] + + // Additional Docker container resource options. For a list of + // options, see + // https://docs.docker.com/engine/reference/commandline/create/#options. + options?: string + } + + #defaults: run?: { + shell?: #shell + "working-directory"?: #["working-directory"] + } + + #permissions: "read-all" | "write-all" | #["permissions-event"] + + #: "permissions-event": { + actions?: #["permissions-level"] + checks?: #["permissions-level"] + contents?: #["permissions-level"] + deployments?: #["permissions-level"] + discussions?: #["permissions-level"] + "id-token"?: #["permissions-level"] + issues?: #["permissions-level"] + packages?: #["permissions-level"] + pages?: #["permissions-level"] + "pull-requests"?: #["permissions-level"] + "repository-projects"?: #["permissions-level"] + "security-events"?: #["permissions-level"] + statuses?: #["permissions-level"] + } + + #: "permissions-level": "read" | "write" | "none" + + #env: { + [string]: bool | number | string + } | #stringContainingExpressionSyntax + + #environment: { + // The name of the environment configured in the repo. + name: string + + // A deployment URL + url?: string + } + + #event: "branch_protection_rule" | "check_run" | "check_suite" | "create" | "delete" | "deployment" | "deployment_status" | "discussion" | "discussion_comment" | "fork" | "gollum" | "issue_comment" | "issues" | "label" | "member" | "milestone" | "page_build" | "project" | "project_card" | "project_column" | "public" | "pull_request" | "pull_request_review" | "pull_request_review_comment" | "pull_request_target" | "push" | "registry_package" | "release" | "status" | "watch" | "workflow_call" | "workflow_dispatch" | "workflow_run" | "repository_dispatch" + + #eventObject: null | { + ... + } + + #expressionSyntax: =~""" + ^\\$\\{\\{(.|[\r + ])*\\}\\}$ + """ + + #stringContainingExpressionSyntax: =~""" + ^.*\\$\\{\\{(.|[\r + ])*\\}\\}.*$ + """ + + #globs: [...strings.MinRunes(1)] & [_, ...] + + #machine: "linux" | "macos" | "windows" + + #name: =~"^[_a-zA-Z][a-zA-Z0-9_-]*$" + + #path: #globs + + #ref: null | { + branches?: #branch + "branches-ignore"?: #branch + tags?: #branch + "tags-ignore"?: #branch + paths?: #path + "paths-ignore"?: #path + ... + } + + #shell: (string | ("bash" | "pwsh" | "python" | "sh" | "cmd" | "powershell")) & string + + #types: [_, ...] + + #: "working-directory": string + + #jobNeeds: [...#name] & [_, ...] | #name + + #reusableWorkflowCallJob: { + // The name of the job displayed on GitHub. + name?: string + needs?: #jobNeeds + permissions?: #["permissions-event"] + + // You can use the if conditional to prevent a job from running + // unless a condition is met. You can use any supported context + // and expression to create a conditional. + // Expressions in an if conditional do not require the ${{ }} + // syntax. For more information, see + // https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. + if?: bool | number | string + + // The location and version of a reusable workflow file to run as + // a job, of the form './{path/to}/{localfile}.yml' or + // '{owner}/{repo}/{path}/{filename}@{ref}'. {ref} can be a SHA, + // a release tag, or a branch name. Using the commit SHA is the + // safest for stability and security. + uses: =~"^(.+/)+(.+)\\.(ya?ml)(@.+)?$" + + // A map of inputs that are passed to the called workflow. Any + // inputs that you pass must match the input specifications + // defined in the called workflow. Unlike + // 'jobs..steps[*].with', the inputs you pass with + // 'jobs..with' are not be available as environment + // variables in the called workflow. Instead, you can reference + // the inputs by using the inputs context. + with?: #env + + // When a job is used to call a reusable workflow, you can use + // 'secrets' to provide a map of secrets that are passed to the + // called workflow. Any secrets that you pass must match the + // names defined in the called workflow. + secrets?: #env | "inherit" + + // A strategy creates a build matrix for your jobs. You can define + // different variations of an environment to run each job in. + strategy?: { + // A build matrix is a set of different configurations of the + // virtual environment. For example you might run a job against + // more than one supported version of a language, operating + // system, or tool. Each configuration is a copy of the job that + // runs and reports a status. + // You can specify a matrix by supplying an array for the + // configuration options. For example, if the GitHub virtual + // environment supports Node.js versions 6, 8, and 10 you could + // specify an array of those versions in the matrix. + // When you define a matrix of operating systems, you must set the + // required runs-on keyword to the operating system of the + // current job, rather than hard-coding the operating system + // name. To access the operating system name, you can use the + // matrix.os context parameter to set runs-on. For more + // information, see + // https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. + matrix: ({ + ... + } | #expressionSyntax) & { + {[=~"^(in|ex)clude$" & !~"^()$"]: [...{ + [string]: #configuration + }] & [_, ...]} + {[!~"^(in|ex)clude$" & !~"^()$"]: [...#configuration] & [_, ...] | #expressionSyntax} + } + + // When set to true, GitHub cancels all in-progress jobs if any + // matrix job fails. Default: true + "fail-fast"?: bool | *true + + // The maximum number of jobs that can run simultaneously when + // using a matrix job strategy. By default, GitHub will maximize + // the number of jobs run in parallel depending on the available + // runners on GitHub-hosted virtual machines. + "max-parallel"?: number + } + + // Concurrency ensures that only a single job or workflow using + // the same concurrency group will run at a time. A concurrency + // group can be any string or expression. The expression can use + // any context except for the secrets context. + // You can also specify concurrency at the workflow level. + // When a concurrent job or workflow is queued, if another job or + // workflow using the same concurrency group in the repository is + // in progress, the queued job or workflow will be pending. Any + // previously pending job or workflow in the concurrency group + // will be canceled. To also cancel any currently running job or + // workflow in the same concurrency group, specify + // cancel-in-progress: true. + concurrency?: string | #concurrency + } + + #normalJob: { + // The name of the job displayed on GitHub. + name?: string + needs?: #jobNeeds + permissions?: #permissions + + // The type of machine to run the job on. The machine can be + // either a GitHub-hosted runner, or a self-hosted runner. + "runs-on": "macos-10.15" | "macos-11" | "macos-12" | "macos-latest" | "self-hosted" | "ubuntu-18.04" | "ubuntu-20.04" | "ubuntu-22.04" | "ubuntu-latest" | "windows-2019" | "windows-2022" | "windows-latest" | (["self-hosted", ...string] & [_, ...] | ["self-hosted", #machine, ...string] & [_, _, ...] | ["self-hosted", #architecture, ...string] & [_, _, ...] | ["self-hosted", #machine, #architecture, ...string] & [_, _, _, ...] | ["self-hosted", #architecture, #machine, ...string] & [_, _, _, ...]) & [...] | #stringContainingExpressionSyntax + + // The environment that the job references. + environment?: string | #environment + + // A map of outputs for a job. Job outputs are available to all + // downstream jobs that depend on this job. + outputs?: { + [string]: string + } + + // A map of environment variables that are available to all steps + // in the job. + env?: #env + + // A map of default settings that will apply to all steps in the + // job. + defaults?: #defaults + + // You can use the if conditional to prevent a job from running + // unless a condition is met. You can use any supported context + // and expression to create a conditional. + // Expressions in an if conditional do not require the ${{ }} + // syntax. For more information, see + // https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. + if?: bool | number | string + + // A job contains a sequence of tasks called steps. Steps can run + // commands, run setup tasks, or run an action in your + // repository, a public repository, or an action published in a + // Docker registry. Not all steps run actions, but all actions + // run as a step. Each step runs in its own process in the + // virtual environment and has access to the workspace and + // filesystem. Because steps run in their own process, changes to + // environment variables are not preserved between steps. GitHub + // provides built-in steps to set up and complete a job. + steps?: [...{ + // A unique identifier for the step. You can use the id to + // reference the step in contexts. For more information, see + // https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. + id?: string + + // You can use the if conditional to prevent a step from running + // unless a condition is met. You can use any supported context + // and expression to create a conditional. + // Expressions in an if conditional do not require the ${{ }} + // syntax. For more information, see + // https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. + if?: bool | number | string + + // A name for your step to display on GitHub. + name?: string + + // Selects an action to run as part of a step in your job. An + // action is a reusable unit of code. You can use an action + // defined in the same repository as the workflow, a public + // repository, or in a published Docker container image + // (https://hub.docker.com/). + // We strongly recommend that you include the version of the + // action you are using by specifying a Git ref, SHA, or Docker + // tag number. If you don't specify a version, it could break + // your workflows or cause unexpected behavior when the action + // owner publishes an update. + // - Using the commit SHA of a released action version is the + // safest for stability and security. + // - Using the specific major action version allows you to receive + // critical fixes and security patches while still maintaining + // compatibility. It also assures that your workflow should still + // work. + // - Using the master branch of an action may be convenient, but + // if someone releases a new major version with a breaking + // change, your workflow could break. + // Some actions require inputs that you must set using the with + // keyword. Review the action's README file to determine the + // inputs required. + // Actions are either JavaScript files or Docker containers. If + // the action you're using is a Docker container you must run the + // job in a Linux virtual environment. For more details, see + // https://help.github.com/en/articles/virtual-environments-for-github-actions. + uses?: string + + // Runs command-line programs using the operating system's shell. + // If you do not provide a name, the step name will default to + // the text specified in the run command. + // Commands run using non-login shells by default. You can choose + // a different shell and customize the shell used to run + // commands. For more information, see + // https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#using-a-specific-shell. + // Each run keyword represents a new process and shell in the + // virtual environment. When you provide multi-line commands, + // each line runs in the same shell. + run?: string + "working-directory"?: #["working-directory"] + shell?: #shell + + // A map of the input parameters defined by the action. Each input + // parameter is a key/value pair. Input parameters are set as + // environment variables. The variable is prefixed with INPUT_ + // and converted to upper case. + with?: #env & { + args?: string + entrypoint?: string + ... + } + + // Sets environment variables for steps to use in the virtual + // environment. You can also set environment variables for the + // entire workflow or a job. + env?: #env + + // Prevents a job from failing when a step fails. Set to true to + // allow a job to pass when this step fails. + "continue-on-error"?: bool | #expressionSyntax | *false + + // The maximum number of minutes to run the step before killing + // the process. + "timeout-minutes"?: number + }] & [_, ...] + + // The maximum number of minutes to let a workflow run before + // GitHub automatically cancels it. Default: 360 + "timeout-minutes"?: number | *360 + + // A strategy creates a build matrix for your jobs. You can define + // different variations of an environment to run each job in. + strategy?: { + // A build matrix is a set of different configurations of the + // virtual environment. For example you might run a job against + // more than one supported version of a language, operating + // system, or tool. Each configuration is a copy of the job that + // runs and reports a status. + // You can specify a matrix by supplying an array for the + // configuration options. For example, if the GitHub virtual + // environment supports Node.js versions 6, 8, and 10 you could + // specify an array of those versions in the matrix. + // When you define a matrix of operating systems, you must set the + // required runs-on keyword to the operating system of the + // current job, rather than hard-coding the operating system + // name. To access the operating system name, you can use the + // matrix.os context parameter to set runs-on. For more + // information, see + // https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. + matrix: ({ + ... + } | #expressionSyntax) & { + {[=~"^(in|ex)clude$" & !~"^()$"]: [...{ + [string]: #configuration + }] & [_, ...]} + {[!~"^(in|ex)clude$" & !~"^()$"]: [...#configuration] & [_, ...] | #expressionSyntax} + } + + // When set to true, GitHub cancels all in-progress jobs if any + // matrix job fails. Default: true + "fail-fast"?: bool | *true + + // The maximum number of jobs that can run simultaneously when + // using a matrix job strategy. By default, GitHub will maximize + // the number of jobs run in parallel depending on the available + // runners on GitHub-hosted virtual machines. + "max-parallel"?: number + } + + // Prevents a workflow run from failing when a job fails. Set to + // true to allow a workflow run to pass when this job fails. + "continue-on-error"?: bool | #expressionSyntax + + // A container to run any steps in a job that don't already + // specify a container. If you have steps that use both script + // and container actions, the container actions will run as + // sibling containers on the same network with the same volume + // mounts. + // If you do not set a container, all steps will run directly on + // the host specified by runs-on unless a step refers to an + // action configured to run in a container. + container?: string | #container + + // Additional containers to host services for a job in a workflow. + // These are useful for creating databases or cache services like + // redis. The runner on the virtual machine will automatically + // create a network and manage the life cycle of the service + // containers. + // When you use a service container for a job or your step uses + // container actions, you don't need to set port information to + // access the service. Docker automatically exposes all ports + // between containers on the same network. + // When both the job and the action run in a container, you can + // directly reference the container by its hostname. The hostname + // is automatically mapped to the service name. + // When a step does not use a container action, you must access + // the service using localhost and bind the ports. + services?: { + [string]: #container + } + + // Concurrency ensures that only a single job or workflow using + // the same concurrency group will run at a time. A concurrency + // group can be any string or expression. The expression can use + // any context except for the secrets context. + // You can also specify concurrency at the workflow level. + // When a concurrent job or workflow is queued, if another job or + // workflow using the same concurrency group in the repository is + // in progress, the queued job or workflow will be pending. Any + // previously pending job or workflow in the concurrency group + // will be canceled. To also cancel any currently running job or + // workflow in the same concurrency group, specify + // cancel-in-progress: true. + concurrency?: string | #concurrency + } +} diff --git a/cue.mod/usr/github.com/SchemaStore/schemastore/src/schemas/json/workflow.cue b/cue.mod/usr/github.com/SchemaStore/schemastore/src/schemas/json/workflow.cue new file mode 100644 index 0000000..34711ce --- /dev/null +++ b/cue.mod/usr/github.com/SchemaStore/schemastore/src/schemas/json/workflow.cue @@ -0,0 +1,4 @@ +package json + +#job: ((#Workflow & {}).jobs & {x: _}).x +#step: ((#job & {steps: _}).steps & [_])[0] diff --git a/internal/ci/base/base.cue b/internal/ci/base/base.cue new file mode 100644 index 0000000..e6db33b --- /dev/null +++ b/internal/ci/base/base.cue @@ -0,0 +1,88 @@ +// Copyright 2022 The CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// package base is a collection of workflows, jobs, stes etc that are common to +// CUE projects and the workflows they specify. The package itself needs to be +// instantiated to parameterise many of the exported definitions. +// +// For example a package using base would do something like this: +// +// package workflows +// +// import "cuelang.org/go/internal/ci/base" +// +// // Create an instance of base +// _base: base & core { params: { +// // any values you need to set that are outside of core +// ... +// }} +// +package base + +import ( + "strings" +) + +// Package parameters +githubRepositoryPath: *(URLPath & {#url: githubRepositoryURL, _}) | string +githubRepositoryURL: *("https://github.com/" + githubRepositoryPath) | string +gerritHubHostname: "review.gerrithub.io" +gerritHubRepositoryURL: *("https://\(gerritHubHostname)/a/" + githubRepositoryPath) | string +trybotRepositoryPath: *(githubRepositoryPath + "-" + trybot.key) | string +trybotRepositoryURL: *("https://github.com/" + trybotRepositoryPath) | string + +defaultBranch: *"master" | string +testDefaultBranch: *"ci/test" | _ +protectedBranchPatterns: *[defaultBranch] | [...string] +releaseTagPrefix: *"v" | string +releaseTagPattern: *(releaseTagPrefix + "*") | string + +botGitHubUser: string +botGitHubUserTokenSecretsKey: *(strings.ToUpper(botGitHubUser) + "_GITHUB_PAT") | string +botGitHubUserEmail: string +botGerritHubUser: *botGitHubUser | string +botGerritHubUserPasswordSecretsKey: *(strings.ToUpper(botGitHubUser) + "_GERRITHUB_PASSWORD") | string +botGerritHubUserEmail: *botGitHubUserEmail | string + +workflowFileExtension: ".yml" + +linuxMachine: string + +codeReview: #codeReview & { + github: githubRepositoryURL + gerrit: gerritHubRepositoryURL +} + +// Define some shared keys and human-readable names. +// +// trybot.key and unity.key are shared with +// github.com/cue-sh/tools/cmd/cueckoo. The keys are used across various CUE +// workflows and their consistency in those various locations is therefore +// crucial. As such, we assert specific values for the keys here rather than +// just deriving values from the human-readable names. +// +// trybot.name is by the trybot GitHub workflow and by gerritstatusupdater as +// an identifier in the status updates that are posted as reviews for this +// workflows, but also as the result label key, e.g. "TryBot-Result" would be +// the result label key for the "TryBot" workflow. This name also shows up in +// the CI badge in the top-level README. +trybot: { + key: "trybot" & strings.ToLower(name) + name: "TryBot" +} + +unity: { + key: "unity" & strings.ToLower(name) + name: "Unity" +} diff --git a/internal/ci/base/codereview.cue b/internal/ci/base/codereview.cue new file mode 100644 index 0000000..113aab8 --- /dev/null +++ b/internal/ci/base/codereview.cue @@ -0,0 +1,28 @@ +package base + +// This file contains aspects principally related to git-codereview +// configuration. + +import ( + "strings" +) + +// #codeReview defines the schema of a codereview.cfg file that +// sits at the root of a repository. codereview.cfg is the configuration +// file that drives golang.org/x/review/git-codereview. This config +// file is also used by github.com/cue-sh/tools/cmd/cueckoo. +#codeReview: { + gerrit?: string + github?: string + "cue-unity"?: string +} + +// #toCodeReviewCfg converts a #codeReview instance to +// the key: value +toCodeReviewCfg: { + #input: #codeReview + let parts = [for k, v in #input {k + ": " + v}] + + // Per https://pkg.go.dev/golang.org/x/review/git-codereview#hdr-Configuration + strings.Join(parts, "\n") +} diff --git a/internal/ci/base/gerrithub.cue b/internal/ci/base/gerrithub.cue new file mode 100644 index 0000000..9b0ae9c --- /dev/null +++ b/internal/ci/base/gerrithub.cue @@ -0,0 +1,356 @@ +package base + +// This file contains gerrithub related definitions etc + +import ( + encjson "encoding/json" + "strings" + + "github.com/SchemaStore/schemastore/src/schemas/json" +) + +// trybotWorkflows is a template for trybot-based repos +trybotWorkflows: { + (trybot.key): json.#Workflow & { + on: workflow_dispatch: {} + } + "\(trybot.key)_dispatch": trybotDispatchWorkflow + "push_tip_to_\(trybot.key)": pushTipToTrybotWorkflow + "evict_caches": evictCaches +} + +#dispatch: { + type: string + CL: int + patchset: int + targetBranch: *defaultBranch | string + + let p = strings.Split("\(CL)", "") + let rightMostTwo = p[len(p)-2] + p[len(p)-1] + ref: *"refs/changes/\(rightMostTwo)/\(CL)/\(patchset)" | string +} + +trybotDispatchWorkflow: bashWorkflow & { + #dummyDispatch?: #dispatch + name: "Dispatch \(trybot.key)" + on: { + repository_dispatch: {} + push: { + // To enable testing of the dispatch itself + branches: [testDefaultBranch] + } + } + jobs: [string]: defaults: run: shell: "bash" + jobs: { + (trybot.key): { + "runs-on": linuxMachine + + let goodDummyData = [ if encjson.Marshal(#dummyDispatch) != _|_ {true}, false][0] + + // We set the "on" conditions above, but this would otherwise mean we + // run for all dispatch events. + if: "${{ (\(isTestDefaultBranch) && \(goodDummyData)) || github.event.client_payload.type == '\(trybot.key)' }}" + + // See the comment below about the need for cases + let cases = [ + { + condition: "!=" + expr: "fromJSON(steps.payload.outputs.value)" + nameSuffix: "fake data" + }, + { + condition: "==" + expr: "github.event.client_payload" + nameSuffix: "repository_dispatch payload" + }, + ] + + steps: [ + writeNetrcFile, + + json.#step & { + name: "Write fake payload" + id: "payload" + if: "github.repository == '\(githubRepositoryPath)' && \(isTestDefaultBranch)" + + // Use bash heredocs so that JSON's use of double quotes does + // not get interpreted as shell. Both in the running of the + // command itself, which itself is the echo-ing of a command to + // $GITHUB_OUTPUT. + run: #""" + cat <> $GITHUB_OUTPUT + value< ~/.netrc + machine \(gerritHubHostname) + login \(botGerritHubUser) + password ${{ secrets.\(botGerritHubUserPasswordSecretsKey) }} + EOD + chmod 600 ~/.netrc + """ +} diff --git a/internal/ci/base/github.cue b/internal/ci/base/github.cue new file mode 100644 index 0000000..721b989 --- /dev/null +++ b/internal/ci/base/github.cue @@ -0,0 +1,375 @@ +package base + +// This file contains aspects principally related to GitHub workflows + +import ( + encjson "encoding/json" + "list" + "strings" + "strconv" + + "github.com/SchemaStore/schemastore/src/schemas/json" +) + +bashWorkflow: json.#Workflow & { + jobs: [string]: defaults: run: shell: "bash" +} + +installGo: json.#step & { + name: "Install Go" + uses: "actions/setup-go@v5" + with: { + // We do our own caching in setupGoActionsCaches. + cache: false + "go-version": string + } +} + +checkoutCode: { + #actionsCheckout: json.#step & { + name: "Checkout code" + uses: "actions/checkout@v4" + + // "pull_request" builds will by default use a merge commit, + // testing the PR's HEAD merged on top of the master branch. + // For consistency with Gerrit, avoid that merge commit entirely. + // This doesn't affect builds by other events like "push", + // since github.event.pull_request is unset so ref remains empty. + with: { + ref: "${{ github.event.pull_request.head.sha }}" + "fetch-depth": 0 // see the docs below + } + } + + [ + #actionsCheckout, + + // Restore modified times to work around https://go.dev/issues/58571, + // as otherwise we would get lots of unnecessary Go test cache misses. + // Note that this action requires actions/checkout to use a fetch-depth of 0. + // Since this is a third-party action which runs arbitrary code, + // we pin a commit hash for v2 to be in control of code updates. + // Also note that git-restore-mtime does not update all directories, + // per the bug report at https://github.com/MestreLion/git-tools/issues/47, + // so we first reset all directory timestamps to a static time as a fallback. + // TODO(mvdan): May be unnecessary once the Go bug above is fixed. + json.#step & { + name: "Reset git directory modification times" + run: "touch -t 202211302355 $(find * -type d)" + }, + json.#step & { + name: "Restore git file modification times" + uses: "chetan/git-restore-mtime-action@075f9bc9d159805603419d50f794bd9f33252ebe" + }, + + { + json.#step & { + name: "Try to extract \(dispatchTrailer)" + id: dispatchTrailerStepID + run: """ + x="$(git log -1 --pretty='%(trailers:key=\(dispatchTrailer),valueonly)')" + if [[ "$x" == "" ]] + then + # Some steps rely on the presence or otherwise of the Dispatch-Trailer. + # We know that we don't have a Dispatch-Trailer in this situation, + # hence we use the JSON value null in order to represent that state. + # This means that GitHub expressions can determine whether a Dispatch-Trailer + # is present or not by checking whether the fromJSON() result of the + # output from this step is the JSON value null or not. + x=null + fi + echo "\(_dispatchTrailerDecodeStepOutputVar)<> $GITHUB_OUTPUT + echo "$x" >> $GITHUB_OUTPUT + echo "EOD" >> $GITHUB_OUTPUT + """ + } + }, + + // Safety nets to flag if we ever have a Dispatch-Trailer slip through the + // net and make it to master + json.#step & { + name: "Check we don't have \(dispatchTrailer) on a protected branch" + if: "\(isProtectedBranch) && \(containsDispatchTrailer)" + run: """ + echo "\(_dispatchTrailerVariable) contains \(dispatchTrailer)" + echo "\(_dispatchTrailerVariable) value" + cat <", so grab the email with sed. + # For now, we require the sorted lists of author and signer emails to match. + # Note that this also fails if a commit isn't signed-off at all. + # + # In Gerrit we already enable a form of this via https://gerrit-review.googlesource.com/Documentation/project-configuration.html#require-signed-off-by, + # but it does not support co-authors nor can it be used when testing GitHub PRs. + commit_authors="$( + { + git log -1 --pretty='%ae' + git log -1 --pretty='%(trailers:key=Co-authored-by,valueonly)' | sed -ne 's/.* <\(.*\)>/\1/p' + } | sort -u + )" + commit_signers="$( + { + git log -1 --pretty='%(trailers:key=Signed-off-by,valueonly)' | sed -ne 's/.* <\(.*\)>/\1/p' + } | sort -u + )" + if [[ "${commit_authors}" != "${commit_signers}" ]]; then + echo "Error: commit author email addresses do not match signed-off-by trailers" + echo + echo "Authors:" + echo "${commit_authors}" + echo + echo "Signers:" + echo "${commit_signers}" + exit 1 + fi + """# +} + +curlGitHubAPI: { + #tokenSecretsKey: *botGitHubUserTokenSecretsKey | string + + #""" + curl -s -L -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.\#(#tokenSecretsKey) }}" -H "X-GitHub-Api-Version: 2022-11-28" + """# +} + +setupGoActionsCaches: { + // #readonly determines whether we ever want to write the cache back. The + // writing of a cache back (for any given cache key) should only happen on a + // protected branch. But running a workflow on a protected branch often + // implies that we want to skip the cache to ensure we catch flakes early. + // Hence the concept of clearing the testcache to ensure we catch flakes + // early can be defaulted based on #readonly. In the general case the two + // concepts are orthogonal, hence they are kept as two parameters, even + // though in our case we could get away with a single parameter that + // encapsulates our needs. + #readonly: *false | bool + #cleanTestCache: *!#readonly | bool + #goVersion: string + #additionalCacheDirs: [...string] + #os: string + + let goModCacheDirID = "go-mod-cache-dir" + let goCacheDirID = "go-cache-dir" + + // cacheDirs is a convenience variable that includes + // GitHub expressions that represent the directories + // that participate in Go caching. + let cacheDirs = list.Concat([[ + "${{ steps.\(goModCacheDirID).outputs.dir }}/cache/download", + "${{ steps.\(goCacheDirID).outputs.dir }}", + ], #additionalCacheDirs]) + + let cacheRestoreKeys = "\(#os)-\(#goVersion)" + + let cacheStep = json.#step & { + with: { + path: strings.Join(cacheDirs, "\n") + + // GitHub actions caches are immutable. Therefore, use a key which is + // unique, but allow the restore to fallback to the most recent cache. + // The result is then saved under the new key which will benefit the + // next build. Restore keys are only set if the step is restore. + key: "\(cacheRestoreKeys)-${{ github.run_id }}" + "restore-keys": cacheRestoreKeys + } + } + + let readWriteCacheExpr = "(\(isProtectedBranch) || \(isTestDefaultBranch))" + + // pre is the list of steps required to establish and initialise the correct + // caches for Go-based workflows. + [ + // TODO: once https://github.com/actions/setup-go/issues/54 is fixed, + // we could use `go env` outputs from the setup-go step. + json.#step & { + name: "Get go mod cache directory" + id: goModCacheDirID + run: #"echo "dir=$(go env GOMODCACHE)" >> ${GITHUB_OUTPUT}"# + }, + json.#step & { + name: "Get go build/test cache directory" + id: goCacheDirID + run: #"echo "dir=$(go env GOCACHE)" >> ${GITHUB_OUTPUT}"# + }, + + // Only if we are not running in readonly mode do we want a step that + // uses actions/cache (read and write). Even then, the use of the write + // step should be predicated on us running on a protected branch. Because + // it's impossible for anything else to write such a cache. + if !#readonly { + cacheStep & { + if: readWriteCacheExpr + uses: "actions/cache@v4" + } + }, + + cacheStep & { + // If we are readonly, there is no condition on when we run this step. + // It should always be run, becase there is no alternative. But if we + // are not readonly, then we need to predicate this step on us not + // being on a protected branch. + if !#readonly { + if: "! \(readWriteCacheExpr)" + } + + uses: "actions/cache/restore@v4" + }, + + if #cleanTestCache { + // All tests on protected branches should skip the test cache. The + // canonical way to do this is with -count=1. However, we want the + // resulting test cache to be valid and current so that subsequent CLs + // in the trybot repo can leverage the updated cache. Therefore, we + // instead perform a clean of the testcache. + // + // Critically we only want to do this in the main repo, not the trybot + // repo. + json.#step & { + if: "github.repository == '\(githubRepositoryPath)' && (\(isProtectedBranch) || github.ref == 'refs/heads/\(testDefaultBranch)')" + run: "go clean -testcache" + } + }, + ] +} + +// isProtectedBranch is an expression that evaluates to true if the +// job is running as a result of pushing to one of protectedBranchPatterns. +// It would be nice to use the "contains" builtin for simplicity, +// but array literals are not yet supported in expressions. +isProtectedBranch: { + #trailers: [...string] + "((" + strings.Join([for branch in protectedBranchPatterns { + (_matchPattern & {variable: "github.ref", pattern: "refs/heads/\(branch)"}).expr + }], " || ") + ") && (! \(containsDispatchTrailer)))" +} + +// isTestDefaultBranch is an expression that evaluates to true if +// the job is running on the testDefaultBranch +isTestDefaultBranch: "(github.ref == 'refs/heads/\(testDefaultBranch)')" + +// #isReleaseTag creates a GitHub expression, based on the given release tag +// pattern, that evaluates to true if called in the context of a workflow that +// is part of a release. +isReleaseTag: { + (_matchPattern & {variable: "github.ref", pattern: "refs/tags/\(releaseTagPattern)"}).expr +} + +checkGitClean: json.#step & { + name: "Check that git is clean at the end of the job" + run: "test -z \"$(git status --porcelain)\" || (git status; git diff; false)" +} + +repositoryDispatch: json.#step & { + #githubRepositoryPath: *githubRepositoryPath | string + #botGitHubUserTokenSecretsKey: *botGitHubUserTokenSecretsKey | string + #arg: _ + + _curlGitHubAPI: curlGitHubAPI & {#tokenSecretsKey: #botGitHubUserTokenSecretsKey, _} + + name: string + run: #""" + \#(_curlGitHubAPI) --fail --request POST --data-binary \#(strconv.Quote(encjson.Marshal(#arg))) https://api.github.com/repos/\#(#githubRepositoryPath)/dispatches + """# +} + +workflowDispatch: json.#step & { + #githubRepositoryPath: *githubRepositoryPath | string + #botGitHubUserTokenSecretsKey: *botGitHubUserTokenSecretsKey | string + #workflowID: string + + // params are defined per https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event + #params: *{ + ref: defaultBranch + } | _ + + _curlGitHubAPI: curlGitHubAPI & {#tokenSecretsKey: #botGitHubUserTokenSecretsKey, _} + + name: string + run: #""" + \#(_curlGitHubAPI) --fail --request POST --data-binary \#(strconv.Quote(encjson.Marshal(#params))) https://api.github.com/repos/\#(#githubRepositoryPath)/actions/workflows/\#(#workflowID)/dispatches + """# +} + +// dispatchTrailer is the trailer that we use to pass information in a commit +// when triggering workflow events in other GitHub repos. +// +// NOTE: keep this consistent with gerritstatusupdater parsing logic. +dispatchTrailer: "Dispatch-Trailer" + +// dispatchTrailerStepID is the ID of the step that attempts +// to extract a Dispatch-Trailer value from the commit at HEAD +dispatchTrailerStepID: strings.Replace(dispatchTrailer, "-", "", -1) + +// _dispatchTrailerDecodeStepOutputVar is the name of the output +// variable int he dispatchTrailerStepID step +_dispatchTrailerDecodeStepOutputVar: "value" + +// dispatchTrailerExpr is a GitHub expression that can be dereferenced +// to get values from the JSON-decded Dispatch-Trailer value that +// is extracted during the dispatchTrailerStepID step. +dispatchTrailerExpr: "fromJSON(steps.\(dispatchTrailerStepID).outputs.\(_dispatchTrailerDecodeStepOutputVar))" + +// containsDispatchTrailer returns a GitHub expression that looks at the commit +// message of the head commit of the event that triggered the workflow, an +// expression that returns true if the commit message associated with that head +// commit contains dispatchTrailer. +// +// Note that this logic does not 100% match the answer that would be returned by: +// +// git log --pretty=%(trailers:key=Dispatch-Trailer,valueonly) +// +// GitHub expressions are incredibly limited in their capabilities: +// +// https://docs.github.com/en/actions/learn-github-actions/expressions +// +// There is not even a regular expression matcher. Hence the logic is a best-efforts +// approximation of the logic employed by git log. +containsDispatchTrailer: { + #type?: string + + // If we have a value for #type, then match against that value. + // Otherwise the best we can do is match against: + // + // Dispatch-Trailer: {"type:} + // + let _typeCheck = [if #type != _|_ {#type + "\""}, ""][0] + """ + (contains(\(_dispatchTrailerVariable), '\n\(dispatchTrailer): {"type":"\(_typeCheck)')) + """ +} + +containsTrybotTrailer: containsDispatchTrailer & { + #type: trybot.key + _ +} + +containsUnityTrailer: containsDispatchTrailer & { + #type: unity.key + _ +} + +_dispatchTrailerVariable: "github.event.head_commit.message" diff --git a/internal/ci/base/helpers.cue b/internal/ci/base/helpers.cue new file mode 100644 index 0000000..31321a7 --- /dev/null +++ b/internal/ci/base/helpers.cue @@ -0,0 +1,40 @@ +package base + +// This file contains everything else + +import ( + "list" + "strings" +) + +// _matchPattern returns a GitHub Actions expression which evaluates whether a +// variable matches a globbing pattern. For literal patterns it uses "==", +// and for suffix patterns it uses "startsWith". +// See https://docs.github.com/en/actions/learn-github-actions/expressions. +_matchPattern: { + variable: string + pattern: string + expr: [ + if strings.HasSuffix(pattern, "*") { + let prefix = strings.TrimSuffix(pattern, "*") + "startsWith(\(variable), '\(prefix)')" + }, + { + "\(variable) == '\(pattern)'" + }, + ][0] +} + +doNotEditMessage: { + #generatedBy: string + "Code generated \(#generatedBy); DO NOT EDIT." +} + +// #URLPath is a temporary measure to derive the path part of a URL. +// +// TODO: remove when cuelang.org/issue/1433 lands. +URLPath: { + #url: string + let parts = strings.Split(#url, "/") + strings.Join(list.Slice(parts, 3, len(parts)), "/") +} diff --git a/internal/ci/ci_tool.cue b/internal/ci/ci_tool.cue new file mode 100644 index 0000000..67e7bcc --- /dev/null +++ b/internal/ci/ci_tool.cue @@ -0,0 +1,73 @@ +// Copyright 2024 The CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ci + +import ( + "path" + "encoding/yaml" + "tool/file" + + "github.com/cue-lang/cue-api-java/internal/ci/repo" + "github.com/cue-lang/cue-api-java/internal/ci/github" +) + +// For the commands below, note we use simple yet hacky path resolution, rather +// than anything that might derive the module root using go list or similar, in +// order that we have zero dependencies. This is important because this CUE +// package is "vendored" to an external dependency so that that unity can +// re-run and verify these steps as part of a the test suite that runs against +// new CUE versions. + +_goos: string @tag(os,var=os) + +// gen.workflows regenerates the GitHub workflow Yaml definitions. +// +// See internal/ci/gen.go for details on how this step fits into the sequence +// of generating our CI workflow definitions, and updating various txtar tests +// with files from that process. +command: gen: { + _dir: path.FromSlash("../../.github/workflows", path.Unix) + + workflows: { + remove: { + glob: file.Glob & { + glob: path.Join([_dir, "*.yml"], _goos) + files: [...string] + } + for _, _filename in glob.files { + "delete \(_filename)": file.RemoveAll & { + path: _filename + } + } + } + for _workflowName, _workflow in github.workflows { + let _filename = _workflowName + repo.workflowFileExtension + "generate \(_filename)": file.Create & { + $after: [for v in remove {v}] + filename: path.Join([_dir, _filename], _goos) + let donotedit = repo.doNotEditMessage & {#generatedBy: "internal/ci/ci_tool.cue", _} + contents: "# \(donotedit)\n\n\(yaml.Marshal(_workflow))" + } + } + } +} + +command: gen: codereviewcfg: file.Create & { + _dir: path.FromSlash("../../", path.Unix) + filename: path.Join([_dir, "codereview.cfg"], _goos) + let res = repo.toCodeReviewCfg & {#input: repo.codeReview, _} + let donotedit = repo.doNotEditMessage & {#generatedBy: "internal/ci/ci_tool.cue", _} + contents: "# \(donotedit)\n\n\(res)\n" +} diff --git a/internal/ci/github/repo.cue b/internal/ci/github/repo.cue new file mode 100644 index 0000000..de288c2 --- /dev/null +++ b/internal/ci/github/repo.cue @@ -0,0 +1,28 @@ +// Copyright 2024 The CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package github + +// This file exists to provide a single point of importing +// the repo package. The pattern of using base and repo +// is replicated across a number of CUE repos, and as such +// the import path of repo varies between them. This makes +// spotting differences and applying changes between the +// github/*.cue files noisy. Instead, import the repo package +// in a single file, and that keeps the different in import +// path down to a single file. + +import repo "github.com/cue-lang/cue-api-java/internal/ci/repo" + +_repo: repo diff --git a/internal/ci/github/trybot.cue b/internal/ci/github/trybot.cue new file mode 100644 index 0000000..088720a --- /dev/null +++ b/internal/ci/github/trybot.cue @@ -0,0 +1,87 @@ +// Copyright 2024 The CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package github + +import ( + "list" + + "github.com/SchemaStore/schemastore/src/schemas/json" +) + +// The trybot workflow. +workflows: trybot: _repo.bashWorkflow & { + name: _repo.trybot.name + + on: { + push: { + branches: list.Concat([[_repo.testDefaultBranch], _repo.protectedBranchPatterns]) // do not run PR branches + } + pull_request: {} + } + + jobs: { + test: { + "runs-on": _repo.linuxMachine + + // Only run the trybot workflow if we have the trybot trailer, or + // if we have no special trailers. Note this condition applies + // after and in addition to the "on" condition above. + if: "\(_repo.containsTrybotTrailer) || ! \(_repo.containsDispatchTrailer)" + + steps: [ + for v in _repo.checkoutCode {v}, + + json.#step & { + uses: "cue-lang/setup-cue@v1.0.0" + with: version: "v0.8.0" + }, + + _repo.earlyChecks, + + json.#step & { + name: "Re-generate CI" + run: """ + cue cmd importjsonschema ./vendor + cue cmd gen + """ + "working-directory": "./internal/ci" + }, + + _repo.checkGitClean, + ] + } + } + + _#goGenerate: json.#step & { + name: "Generate" + run: "go generate ./..." + } + + _#goTest: json.#step & { + name: "Test" + run: "go test ./..." + } + + _#goCheck: json.#step & { + // These checks can vary between platforms, as different code can be built + // based on GOOS and GOARCH build tags. + // However, CUE does not have any such build tags yet, and we don't use + // dependencies that vary wildly between platforms. + // For now, to save CI resources, just run the checks on one matrix job. + // TODO: consider adding more checks as per https://github.com/golang/go/issues/42119. + name: "Check" + run: "go vet ./..." + } +} diff --git a/internal/ci/github/workflows.cue b/internal/ci/github/workflows.cue new file mode 100644 index 0000000..ecee317 --- /dev/null +++ b/internal/ci/github/workflows.cue @@ -0,0 +1,48 @@ +// Copyright 2024 The CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// package github declares the workflows for this project. +package github + +// Note: the name of the workflows (and hence the corresponding .yml filenames) +// correspond to the environment variable names for gerritstatusupdater. +// Therefore, this filename must only be change in combination with also +// updating the environment in which gerritstatusupdater is running for this +// repository. +// +// This name is also used by the CI badge in the top-level README. +// +// This name is also used in the evict_caches lookups. +// +// i.e. don't change the names of workflows! +// +// In addition to separately declaring the workflows themselves, we define the +// shape of #workflows here as a cross-check that we don't accidentally change +// the name of workflows without reading this comment. +// +// We explicitly use close() here instead of a definition in order that we can +// cue export the github package as a test. +workflows: close({ + // Adding this constraint here, whilst clear for the reader, + // blows out evaluation time. This will be fixed as part of + // the performance work which is covered under various issues. + // [string]: json.#Workflow + + _repo.trybotWorkflows + trybot_dispatch: #dummyDispatch: dummyDispatch +}) + +dummyDispatch: _repo.#dispatch & { + type: _repo.trybot.key +} diff --git a/internal/ci/repo/repo.cue b/internal/ci/repo/repo.cue new file mode 100644 index 0000000..fd719f7 --- /dev/null +++ b/internal/ci/repo/repo.cue @@ -0,0 +1,21 @@ +// package repo contains data values that are common to all CUE configurations +// in this repo. The list of configurations includes GitHub workflows, but also +// things like gerrit configuration etc. +package repo + +import ( + "github.com/cue-lang/cue-api-java/internal/ci/base" +) + +base + +githubRepositoryPath: "cue-lang/cue-api-java" + +botGitHubUser: "cueckoo" +botGitHubUserEmail: "cueckoo@gmail.com" + +defaultBranch: "main" + +linuxMachine: "ubuntu-22.04" + +latestGo: "1.21.x" diff --git a/internal/ci/vendor/vendor_tool.cue b/internal/ci/vendor/vendor_tool.cue new file mode 100644 index 0000000..0d5c1e0 --- /dev/null +++ b/internal/ci/vendor/vendor_tool.cue @@ -0,0 +1,50 @@ +// Copyright 2024 The CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vendor + +import ( + "path" + + "tool/exec" + "tool/http" +) + +// _cueCmd defines the command that is run to run cmd/cue. +// This is factored out in order that the cue-github-actions +// project which "vendors" the various workflow-related +// packages can specify "cue" as the value so that unity +// tests can specify the cmd/cue binary to use. +_cueCmd: string | *"cue" @tag(cue_cmd) + +// For the commands below, note we use simple yet hacky path resolution, rather +// than anything that might derive the module root using go list or similar, in +// order that we have zero dependencies. + +// importjsonschema vendors a CUE-imported version of the JSONSchema that +// defines GitHub workflows into the main module's cue.mod/pkg. +command: importjsonschema: { + getJSONSchema: http.Get & { + request: body: "" + + // Tip link for humans: + // https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/github-workflow.json + url: "https://raw.githubusercontent.com/SchemaStore/schemastore/5ffe36662a8fcab3c32e8fbca39c5253809e6913/src/schemas/json/github-workflow.json" + } + import: exec.Run & { + _outpath: path.FromSlash("../../cue.mod/pkg/github.com/SchemaStore/schemastore/src/schemas/json/github-workflow.cue", "unix") + stdin: getJSONSchema.response.body + cmd: "\(_cueCmd) import -f -p json -l #Workflow: -o \(_outpath) jsonschema: -" + } +}