From c82d2cbe2ff69d752e09652de34d65b0567f5957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 13 Aug 2024 12:07:13 +0200 Subject: [PATCH] ci: merge several workflows (#1031) - For external PRs from forks, this will only require one approval, as opposed to four. - It also allows centralizing the logic for checking privileges. - Overall, this reduces the number of workflow files. --- .github/workflows/downstream_benchmarks.yml | 127 ------- .github/workflows/downstream_enterprise.yml | 103 ------ .../downstream_python_enterprise.yml | 125 ------- .github/workflows/downstream_quickstarts.yml | 67 ---- .github/workflows/pull_request.yml | 82 ++++- .github/workflows/pull_request_native.yml | 42 --- .github/workflows/pull_request_python.yml | 61 ---- ...tarts.yml => pull_request_quickstarts.yml} | 65 +++- .github/workflows/pull_request_secure.yml | 311 ++++++++++++++++++ .github/workflows/sonarcloud.yml | 97 ------ 10 files changed, 446 insertions(+), 634 deletions(-) delete mode 100644 .github/workflows/downstream_benchmarks.yml delete mode 100644 .github/workflows/downstream_enterprise.yml delete mode 100644 .github/workflows/downstream_python_enterprise.yml delete mode 100644 .github/workflows/downstream_quickstarts.yml delete mode 100644 .github/workflows/pull_request_native.yml delete mode 100644 .github/workflows/pull_request_python.yml rename .github/workflows/{downstream_python_quickstarts.yml => pull_request_quickstarts.yml} (55%) create mode 100644 .github/workflows/pull_request_secure.yml delete mode 100644 .github/workflows/sonarcloud.yml diff --git a/.github/workflows/downstream_benchmarks.yml b/.github/workflows/downstream_benchmarks.yml deleted file mode 100644 index 8a8be2cfbb..0000000000 --- a/.github/workflows/downstream_benchmarks.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: Downstream - Integration Tests - -on: - # Enables the workflow to run on PRs from forks. - # CI will only run for trusted users, to prevent stealing of secrets. - pull_request_target: - branches: [main] # Benchmarks aren't branched, so they will only ever work against current main. - types: - - opened - - reopened - - synchronize - paths-ignore: - - 'LICENSE*' - - '.gitignore' - - '**.md' - - '**.adoc' - - '*.txt' - -jobs: - # Check if the user is a member of the organization; if so, allow the PR to sail through. - known_user: - runs-on: ubuntu-latest - outputs: - is_member_of_org: ${{ steps.auth_check.outputs.authorized }} - steps: - - id: auth_check - env: - GH_TOKEN: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Release account is a Solver Gatekeeper. - shell: bash - run: | - # -g to allow actors such as dependabot[bot] - ORG_MEMBERSHIP=`curl -g -L -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $GH_TOKEN" "https://api.github.com/orgs/TimefoldAI/memberships/${{ github.actor }}" | jq -r '.state == "active"'` - echo "authorized=$ORG_MEMBERSHIP" >> "$GITHUB_OUTPUT" - - id: validation - shell: bash - run: | - echo "Authorized user: ${{ steps.auth_check.outputs.authorized }}" - # If the user is not a member, require a member to approve the PR. - approval_required: - needs: known_user - environment: - ${{ - github.event_name == 'pull_request_target' && - github.event.pull_request.head.repo.full_name != github.repository && - (needs.known_user.outputs.is_member_of_org != 'true' || github.actor == 'dependabot[bot]') && - 'external' || 'internal' - }} - runs-on: ubuntu-latest - steps: - - run: true - build: - needs: approval_required - runs-on: ubuntu-latest - concurrency: - group: pr-${{ github.event_name }}-${{ github.head_ref }} - cancel-in-progress: true - steps: - # Clone timefold-solver - # No need to check for stale repo, as Github merges the main repo into the fork automatically. - - name: Checkout timefold-solver - uses: actions/checkout@v4 - with: - path: ./timefold-solver - ref: ${{ github.event.pull_request.head.sha }} # The GHA event will pull the main branch by default, and we must specify the PR reference version - - - name: Setup Temurin 21 and Maven - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - cache: 'maven' - - - name: Quickly build timefold-solver - working-directory: ./timefold-solver - shell: bash - run: mvn -B -Dquickly clean install - - # Clone timefold-solver-enterprise - - name: Checkout timefold-solver-enterprise (PR) # Checkout the PR branch first, if it exists - id: checkout-solver-enterprise - uses: actions/checkout@v4 - continue-on-error: true - with: - repository: TimefoldAI/timefold-solver-enterprise - ref: ${{ github.head_ref }} - token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. - path: ./timefold-solver-enterprise - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Checkout timefold-solver-enterprise (main) # Checkout the main branch if the PR branch does not exist - if: steps.checkout-solver-enterprise.outcome != 'success' - uses: actions/checkout@v4 - with: - repository: TimefoldAI/timefold-solver-enterprise - ref: main - token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. - path: ./timefold-solver-enterprise - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - - name: Quickly build timefold-solver-enterprise - working-directory: ./timefold-solver-enterprise - shell: bash - run: mvn -B -Dquickly clean install - - # Clone timefold-solver-benchmarks - - name: Checkout timefold-solver-benchmarks (PR) # Checkout the PR branch first, if it exists - if: github.head_ref # Only true if this is a PR. - id: checkout-solver-benchmarks-pr - uses: actions/checkout@v4 - continue-on-error: true - with: - repository: TimefoldAI/timefold-solver-benchmarks - ref: ${{ github.head_ref }} - path: ./timefold-solver-benchmarks - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Checkout timefold-solver-benchmarks (main) # Checkout the main branch if the PR branch does not exist - if: ${{ steps.checkout-solver-benchmarks-pr.outcome != 'success' }} - uses: actions/checkout@v4 - with: - repository: TimefoldAI/timefold-solver-benchmarks - ref: main - path: ./timefold-solver-benchmarks - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - - name: Build and test timefold-solver-benchmarks - working-directory: ./timefold-solver-benchmarks - shell: bash - run: mvn -B -DskipJMH clean verify \ No newline at end of file diff --git a/.github/workflows/downstream_enterprise.yml b/.github/workflows/downstream_enterprise.yml deleted file mode 100644 index 99e950a653..0000000000 --- a/.github/workflows/downstream_enterprise.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Downstream - Timefold Solver Enterprise Edition - -on: - # Enables the workflow to run on PRs from forks. - # CI will only run for trusted users, to prevent stealing of secrets. - pull_request_target: - branches: [main, '*.x'] - types: - - opened - - reopened - - synchronize - paths-ignore: - - 'LICENSE*' - - '.gitignore' - - '**.md' - - '**.adoc' - - '*.txt' - -jobs: - # Check if the user is a member of the organization; if so, allow the PR to sail through. - known_user: - runs-on: ubuntu-latest - outputs: - is_member_of_org: ${{ steps.auth_check.outputs.authorized }} - steps: - - id: auth_check - env: - GH_TOKEN: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Release account is a Solver Gatekeeper. - shell: bash - run: | - # -g to allow actors such as dependabot[bot] - ORG_MEMBERSHIP=`curl -g -L -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $GH_TOKEN" "https://api.github.com/orgs/TimefoldAI/memberships/${{ github.actor }}" | jq -r '.state == "active"'` - echo "authorized=$ORG_MEMBERSHIP" >> "$GITHUB_OUTPUT" - - id: validation - shell: bash - run: | - echo "Authorized user: ${{ steps.auth_check.outputs.authorized }}" - # If the user is not a member, require a member to approve the PR. - approval_required: - needs: known_user - environment: - ${{ - github.event_name == 'pull_request_target' && - github.event.pull_request.head.repo.full_name != github.repository && - (needs.known_user.outputs.is_member_of_org != 'true' || github.actor == 'dependabot[bot]') && - 'external' || 'internal' - }} - runs-on: ubuntu-latest - steps: - - run: true - build: - needs: approval_required - runs-on: ubuntu-latest - concurrency: - group: downstream-enterprise-${{ github.event_name }}-${{ github.head_ref }} - cancel-in-progress: true - timeout-minutes: 120 - steps: - # Clone timefold-solver - # No need to check for stale repo, as Github merges the main repo into the fork automatically. - - name: Checkout timefold-solver - uses: actions/checkout@v4 - with: - path: ./timefold-solver - ref: ${{ github.event.pull_request.head.sha }} # The GHA event will pull the main branch by default, and we must specify the PR reference version - - # Clone timefold-solver-enterprise - # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. - - name: Checkout timefold-solver-enterprise (PR) # Checkout the PR branch first, if it exists - id: checkout-solver-enterprise - uses: actions/checkout@v4 - continue-on-error: true - with: - repository: TimefoldAI/timefold-solver-enterprise - ref: ${{ github.head_ref }} - token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. - path: ./timefold-solver-enterprise - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Checkout timefold-solver-enterprise (main) # Checkout the main branch if the PR branch does not exist - if: steps.checkout-solver-enterprise.outcome != 'success' - uses: actions/checkout@v4 - with: - repository: TimefoldAI/timefold-solver-enterprise - ref: main - token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. - path: ./timefold-solver-enterprise - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - # Build and test - - name: Setup Temurin 17 and Maven - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: 'maven' - - name: Quickly build timefold-solver - working-directory: ./timefold-solver - shell: bash - run: mvn -B -Dquickly clean install - - name: Build and test timefold-solver-enterprise - working-directory: ./timefold-solver-enterprise - shell: bash - run: mvn -B clean verify diff --git a/.github/workflows/downstream_python_enterprise.yml b/.github/workflows/downstream_python_enterprise.yml deleted file mode 100644 index 99e3fa7688..0000000000 --- a/.github/workflows/downstream_python_enterprise.yml +++ /dev/null @@ -1,125 +0,0 @@ -# Tests PRs on multiple operating systems and Python/Java versions -name: Downstream - Timefold Solver Enterprise for Python - -on: - # Enables the workflow to run on PRs from forks. - # CI will only run for trusted users, to prevent stealing of secrets. - pull_request_target: - branches: [ main, '*.x' ] - types: - - opened - - reopened - - synchronize - paths-ignore: - - 'LICENSE*' - - '.gitignore' - - '**.md' - - '**.adoc' - - '*.txt' - -defaults: - run: - shell: bash - -jobs: - # Check if the user is a member of the organization; if so, allow the PR to sail through. - known_user: - runs-on: ubuntu-latest - outputs: - is_member_of_org: ${{ steps.auth_check.outputs.authorized }} - steps: - - id: auth_check - env: - GH_TOKEN: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Release account is a Solver Gatekeeper. - shell: bash - run: | - # -g to allow actors such as dependabot[bot] - ORG_MEMBERSHIP=`curl -g -L -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $GH_TOKEN" "https://api.github.com/orgs/TimefoldAI/memberships/${{ github.actor }}" | jq -r '.state == "active"'` - echo "authorized=$ORG_MEMBERSHIP" >> "$GITHUB_OUTPUT" - - id: validation - shell: bash - run: | - echo "Authorized user: ${{ steps.auth_check.outputs.authorized }}" - # If the user is not a member, require a member to approve the PR. - approval_required: - needs: known_user - environment: - ${{ - github.event_name == 'pull_request_target' && - github.event.pull_request.head.repo.full_name != github.repository && - (needs.known_user.outputs.is_member_of_org != 'true' || github.actor == 'dependabot[bot]') && - 'external' || 'internal' - }} - runs-on: ubuntu-latest - steps: - - run: true - build: - needs: approval_required - concurrency: - group: downstream-enterprise-python-${{ github.event_name }}-${{ github.head_ref }} - cancel-in-progress: true - timeout-minutes: 120 - runs-on: ubuntu-latest - steps: - - name: Check out repository code - uses: actions/checkout@v4 - with: - path: './timefold-solver' - ref: ${{ github.event.pull_request.head.sha }} # The GHA event will pull the main branch by default, and we must specify the PR reference version - - # Clone timefold-solver-enterprise - # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. - - name: Checkout timefold-solver-enterprise (PR) # Checkout the PR branch first, if it exists - id: checkout-solver-enterprise - uses: actions/checkout@v4 - continue-on-error: true - with: - repository: TimefoldAI/timefold-solver-enterprise - ref: ${{ github.head_ref }} - token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. - path: ./timefold-solver-enterprise - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - - name: Checkout timefold-solver-enterprise (main) # Checkout the main branch if the PR branch does not exist - if: steps.checkout-solver-enterprise.outcome != 'success' - uses: actions/checkout@v4 - with: - repository: TimefoldAI/timefold-solver-enterprise - ref: main - token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. - path: ./timefold-solver-enterprise - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - # Build and test - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: 17 - distribution: 'temurin' - cache: 'maven' - - # Need to install all Python versions in the same run for tox - - name: Python 3.10, Python 3.11, Python 3.12 Setup - uses: actions/setup-python@v5 - with: - python-version: | - 3.10 - 3.11 - 3.12 - cache: 'pip' - cache-dependency-path: | - **/setup.py - - - name: Install tox - run: - pip install tox build - - - name: Build Timefold Solver for Python - working-directory: ./timefold-solver - run: python -m build - - - name: Run tox on Timefold Solver Enterprise for Python test suite - working-directory: ./timefold-solver-enterprise - env: - PIP_FIND_LINKS: ${{ github.workspace }}/timefold-solver/dist - run: tox diff --git a/.github/workflows/downstream_quickstarts.yml b/.github/workflows/downstream_quickstarts.yml deleted file mode 100644 index 3da2dbe3fa..0000000000 --- a/.github/workflows/downstream_quickstarts.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Downstream - Timefold Quickstarts - -on: - pull_request: - branches: [main, '*.x'] - types: - - opened - - reopened - - synchronize - paths-ignore: - - 'LICENSE*' - - '.gitignore' - - '**.md' - - '**.adoc' - - '*.txt' - -jobs: - build: - runs-on: ubuntu-latest - concurrency: - group: downstream-quickstarts-${{ github.event_name }}-${{ github.head_ref }} - cancel-in-progress: true - timeout-minutes: 120 - steps: - # Clone timefold-solver - # No need to check for stale repo, as Github merges the main repo into the fork automatically. - - name: Checkout timefold-solver - uses: actions/checkout@v4 - with: - path: ./timefold-solver - - # Clone timefold-quickstarts - # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. - - name: Checkout timefold-quickstarts (PR) # Checkout the PR branch first, if it exists - if: github.head_ref # Only true if this is a PR. - id: checkout-quickstarts-pr - uses: actions/checkout@v4 - continue-on-error: true - with: - repository: ${{ github.actor }}/timefold-quickstarts - ref: ${{ github.head_ref }} - path: ./timefold-quickstarts - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - name: Checkout timefold-quickstarts (development) # Checkout the development branch if the PR branch does not exist - if: ${{ steps.checkout-quickstarts-pr.outcome != 'success' }} - uses: actions/checkout@v4 - with: - repository: TimefoldAI/timefold-quickstarts - ref: development - path: ./timefold-quickstarts - fetch-depth: 0 # Otherwise merge will fail on account of not having history. - - # Build and test - - name: Setup Temurin 17 and Maven - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: 'maven' - - name: Quickly build timefold-solver - working-directory: ./timefold-solver - shell: bash - run: mvn -B -Dquickly clean install - - name: Build and test timefold-quickstarts - working-directory: ./timefold-quickstarts - shell: bash - run: mvn -B clean verify diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 587f07f1e5..5be96f44c5 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,4 +1,4 @@ -name: JVM and OS matrix +name: "Base Workflow" on: push: @@ -13,7 +13,8 @@ on: - '*.txt' jobs: - build: + java: + name: "Java Solver" concurrency: group: pull_request-${{ github.event_name }}-${{ github.head_ref }}-${{ matrix.os }}-${{ matrix.java-version }} cancel-in-progress: true @@ -39,3 +40,80 @@ jobs: - name: Build and test timefold-solver run: mvn -B -Dfull verify + python: + name: "Python Solver" + concurrency: + group: pull_request-python-${{ github.event_name }}-${{ github.head_ref }}-${{ matrix.os }}-${{ matrix.java-version }} + cancel-in-progress: true + runs-on: ${{matrix.os}} + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + java-version: [ 21 ] # Latest LTS if not Ubuntu + include: + - os: ubuntu-latest + java-version: 17 + - os: ubuntu-latest + java-version: 22 + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + java-version: ${{matrix.java-version}} + distribution: 'temurin' + cache: 'maven' + + # Need to install all Python versions in the same run for tox + - name: Python 3.10, Python 3.11, Python 3.12 Setup + uses: actions/setup-python@v5 + with: + python-version: | + 3.10 + 3.11 + 3.12 + cache: 'pip' + cache-dependency-path: | + **/setup.py + + - name: Install tox + run: + pip install tox build + + - name: Run tox on Timefold Solver for Python test suite + run: python -m tox + + - name: Run tox on jpyinterpreter test suite + working-directory: ./python/jpyinterpreter + run: python -m tox + native: + name: "Native Image" + concurrency: + group: pull_request_native-${{ github.event_name }}-${{ github.head_ref }}-${{matrix.os}}-${{ matrix.module }}-${{ matrix.java-version }} + cancel-in-progress: true + runs-on: ${{matrix.os}} + strategy: + matrix: + os: [ ubuntu-latest ] # Windows doesn't work, Mac is not a deploy OS. + module: ["spring-integration", "quarkus-integration"] + java-version: [ 17, 21, 22 ] # LTS + latest. + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + + - uses: graalvm/setup-graalvm@v1 + with: + java-version: ${{matrix.java-version}} + distribution: 'graalvm-community' + components: 'native-image' + github-token: ${{ secrets.GITHUB_TOKEN }} + cache: 'maven' + + - name: Quickly build timefold-solver + run: mvn -B -Dquickly clean install + + - name: Test timefold-solver in Native mode + run: | + cd ${{matrix.module}} + mvn -B -Dnative verify \ No newline at end of file diff --git a/.github/workflows/pull_request_native.yml b/.github/workflows/pull_request_native.yml deleted file mode 100644 index 2c2905e2f7..0000000000 --- a/.github/workflows/pull_request_native.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: GraalVM Native - -on: - pull_request: - branches: [main] - paths-ignore: - - 'LICENSE*' - - '.gitignore' - - '**.md' - - '**.adoc' - - '*.txt' - -jobs: - native: - concurrency: - group: pull_request_native-${{ github.event_name }}-${{ github.head_ref }}-${{matrix.os}}-${{ matrix.module }}-${{ matrix.java-version }} - cancel-in-progress: true - runs-on: ${{matrix.os}} - strategy: - matrix: - os: [ ubuntu-latest ] # Windows doesn't work, Mac is not a deploy OS. - module: ["spring-integration", "quarkus-integration"] - java-version: [ 17, 21, 22 ] # LTS + latest. - timeout-minutes: 120 - steps: - - uses: actions/checkout@v4 - - - uses: graalvm/setup-graalvm@v1 - with: - java-version: ${{matrix.java-version}} - distribution: 'graalvm-community' - components: 'native-image' - github-token: ${{ secrets.GITHUB_TOKEN }} - cache: 'maven' - - - name: Quickly build timefold-solver - run: mvn -B -Dquickly clean install - - - name: Test timefold-solver in Native mode - run: | - cd ${{matrix.module}} - mvn -B -Dnative verify \ No newline at end of file diff --git a/.github/workflows/pull_request_python.yml b/.github/workflows/pull_request_python.yml deleted file mode 100644 index 063d8bfe56..0000000000 --- a/.github/workflows/pull_request_python.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Python JVM and OS matrix - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - paths-ignore: - - 'LICENSE*' - - '.gitignore' - - '**.md' - - '**.adoc' - - '*.txt' - -jobs: - build: - concurrency: - group: pull_request-python-${{ github.event_name }}-${{ github.head_ref }}-${{ matrix.os }}-${{ matrix.java-version }} - cancel-in-progress: true - runs-on: ${{matrix.os}} - strategy: - matrix: - os: [ ubuntu-latest, macos-latest, windows-latest ] - java-version: [ 21 ] # Latest LTS if not Ubuntu - include: - - os: ubuntu-latest - java-version: 17 - - os: ubuntu-latest - java-version: 22 - timeout-minutes: 120 - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-java@v4 - with: - java-version: ${{matrix.java-version}} - distribution: 'temurin' - cache: 'maven' - - # Need to install all Python versions in the same run for tox - - name: Python 3.10, Python 3.11, Python 3.12 Setup - uses: actions/setup-python@v5 - with: - python-version: | - 3.10 - 3.11 - 3.12 - cache: 'pip' - cache-dependency-path: | - **/setup.py - - - name: Install tox - run: - pip install tox build - - - name: Run tox on Timefold Solver for Python test suite - run: python -m tox - - - name: Run tox on jpyinterpreter test suite - working-directory: ./python/jpyinterpreter - run: python -m tox diff --git a/.github/workflows/downstream_python_quickstarts.yml b/.github/workflows/pull_request_quickstarts.yml similarity index 55% rename from .github/workflows/downstream_python_quickstarts.yml rename to .github/workflows/pull_request_quickstarts.yml index d4b051be43..25bd50a4fc 100644 --- a/.github/workflows/downstream_python_quickstarts.yml +++ b/.github/workflows/pull_request_quickstarts.yml @@ -1,28 +1,73 @@ -# Tests PRs on multiple operating systems and Python/Java versions -name: Downstream - Timefold Solver for Python Quickstarts +name: Quickstarts Workflow on: pull_request: + branches: [main, '*.x'] types: - opened - reopened - synchronize - branches: - - main paths-ignore: - 'LICENSE*' - '.gitignore' - '**.md' - '**.adoc' - '*.txt' - - '.ci/**' - -defaults: - run: - shell: bash jobs: - build: + java: + name: "Java Quickstarts" + runs-on: ubuntu-latest + concurrency: + group: downstream-quickstarts-${{ github.event_name }}-${{ github.head_ref }} + cancel-in-progress: true + timeout-minutes: 120 + steps: + # Clone timefold-solver + # No need to check for stale repo, as Github merges the main repo into the fork automatically. + - name: Checkout timefold-solver + uses: actions/checkout@v4 + with: + path: ./timefold-solver + + # Clone timefold-quickstarts + # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. + - name: Checkout timefold-quickstarts (PR) # Checkout the PR branch first, if it exists + if: github.head_ref # Only true if this is a PR. + id: checkout-quickstarts-pr + uses: actions/checkout@v4 + continue-on-error: true + with: + repository: ${{ github.actor }}/timefold-quickstarts + ref: ${{ github.head_ref }} + path: ./timefold-quickstarts + fetch-depth: 0 # Otherwise merge will fail on account of not having history. + - name: Checkout timefold-quickstarts (development) # Checkout the development branch if the PR branch does not exist + if: ${{ steps.checkout-quickstarts-pr.outcome != 'success' }} + uses: actions/checkout@v4 + with: + repository: TimefoldAI/timefold-quickstarts + ref: development + path: ./timefold-quickstarts + fetch-depth: 0 # Otherwise merge will fail on account of not having history. + + # Build and test + - name: Setup Temurin 21 and Maven + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + - name: Quickly build timefold-solver + working-directory: ./timefold-solver + shell: bash + run: mvn -B -Dquickly clean install + - name: Build and test timefold-quickstarts + working-directory: ./timefold-quickstarts + shell: bash + run: mvn -B clean verify + python: + name: "Python Quickstarts" concurrency: group: pull_request_python_quickstarts-${{ github.event_name }}-${{ github.head_ref }}-${{ matrix.os }}-${{ matrix.java-version }}-${{ matrix.python-version }} cancel-in-progress: true diff --git a/.github/workflows/pull_request_secure.yml b/.github/workflows/pull_request_secure.yml new file mode 100644 index 0000000000..5a45153f3c --- /dev/null +++ b/.github/workflows/pull_request_secure.yml @@ -0,0 +1,311 @@ +# Jobs in this workflow deal with secrets. +# Since they may be executed from forks by untrusted users, +# we need to ensure that the user is a member of the organization +# or that there is explicit approval for their jobs to run. +name: Secured Workflow + +on: + # There are two differences to "pull_request" here: + # - The workflow will receive secrets, even in PRs from forks. + # - The workflow will be executed automatically, without requiring a manual approval. + # Therefore the workflow needs to be explicitly secured; see "known_user" and "approval_required" jobs below. + pull_request_target: + branches: [main] # Benchmarks aren't branched, so they will only ever work against current main. + types: + - opened + - reopened + - synchronize + paths-ignore: + - 'LICENSE*' + - '.gitignore' + - '**.md' + - '**.adoc' + - '*.txt' + +jobs: + # Check if the user is a member of the organization; if so, allow the PR to sail through. + known_user: + runs-on: ubuntu-latest + outputs: + is_member_of_org: ${{ steps.auth_check.outputs.authorized }} + steps: + - id: auth_check + env: + GH_TOKEN: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Release account is a Solver Gatekeeper. + shell: bash + run: | + # -g to allow actors such as dependabot[bot] + ORG_MEMBERSHIP=`curl -g -L -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $GH_TOKEN" "https://api.github.com/orgs/TimefoldAI/memberships/${{ github.actor }}" | jq -r '.state == "active"'` + echo "authorized=$ORG_MEMBERSHIP" >> "$GITHUB_OUTPUT" + - id: validation + shell: bash + run: | + echo "Authorized user: ${{ steps.auth_check.outputs.authorized }}" + # If the user is not a member, require a member to approve the PR. + approval_required: + needs: known_user + environment: + ${{ + github.event_name == 'pull_request_target' && + github.event.pull_request.head.repo.full_name != github.repository && + (needs.known_user.outputs.is_member_of_org != 'true' || github.actor == 'dependabot[bot]') && + 'external' || 'internal' + }} + runs-on: ubuntu-latest + steps: + - run: true + integration-tests: + needs: approval_required + name: Integration Tests + runs-on: ubuntu-latest + concurrency: + group: pr-${{ github.event_name }}-${{ github.head_ref }} + cancel-in-progress: true + steps: + # Clone timefold-solver + # No need to check for stale repo, as Github merges the main repo into the fork automatically. + - name: Checkout timefold-solver + uses: actions/checkout@v4 + with: + path: ./timefold-solver + ref: ${{ github.event.pull_request.head.sha }} # The GHA event will pull the main branch by default, and we must specify the PR reference version + + - name: Setup Temurin 21 and Maven + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + + - name: Quickly build timefold-solver + working-directory: ./timefold-solver + shell: bash + run: mvn -B -Dquickly clean install + + # Clone timefold-solver-enterprise + - name: Checkout timefold-solver-enterprise (PR) # Checkout the PR branch first, if it exists + id: checkout-solver-enterprise + uses: actions/checkout@v4 + continue-on-error: true + with: + repository: TimefoldAI/timefold-solver-enterprise + ref: ${{ github.head_ref }} + token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. + path: ./timefold-solver-enterprise + fetch-depth: 0 # Otherwise merge will fail on account of not having history. + - name: Checkout timefold-solver-enterprise (main) # Checkout the main branch if the PR branch does not exist + if: steps.checkout-solver-enterprise.outcome != 'success' + uses: actions/checkout@v4 + with: + repository: TimefoldAI/timefold-solver-enterprise + ref: main + token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. + path: ./timefold-solver-enterprise + fetch-depth: 0 # Otherwise merge will fail on account of not having history. + + - name: Quickly build timefold-solver-enterprise + working-directory: ./timefold-solver-enterprise + shell: bash + run: mvn -B -Dquickly clean install + + # Clone timefold-solver-benchmarks + - name: Checkout timefold-solver-benchmarks (PR) # Checkout the PR branch first, if it exists + if: github.head_ref # Only true if this is a PR. + id: checkout-solver-benchmarks-pr + uses: actions/checkout@v4 + continue-on-error: true + with: + repository: TimefoldAI/timefold-solver-benchmarks + ref: ${{ github.head_ref }} + path: ./timefold-solver-benchmarks + fetch-depth: 0 # Otherwise merge will fail on account of not having history. + - name: Checkout timefold-solver-benchmarks (main) # Checkout the main branch if the PR branch does not exist + if: ${{ steps.checkout-solver-benchmarks-pr.outcome != 'success' }} + uses: actions/checkout@v4 + with: + repository: TimefoldAI/timefold-solver-benchmarks + ref: main + path: ./timefold-solver-benchmarks + fetch-depth: 0 # Otherwise merge will fail on account of not having history. + + - name: Build and test timefold-solver-benchmarks + working-directory: ./timefold-solver-benchmarks + shell: bash + run: mvn -B -DskipJMH clean verify + enterprise-java: + needs: approval_required + name: Enterprise Edition (Java) + runs-on: ubuntu-latest + concurrency: + group: downstream-enterprise-${{ github.event_name }}-${{ github.head_ref }} + cancel-in-progress: true + timeout-minutes: 120 + steps: + # Clone timefold-solver + # No need to check for stale repo, as Github merges the main repo into the fork automatically. + - name: Checkout timefold-solver + uses: actions/checkout@v4 + with: + path: ./timefold-solver + ref: ${{ github.event.pull_request.head.sha }} # The GHA event will pull the main branch by default, and we must specify the PR reference version + + # Clone timefold-solver-enterprise + # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. + - name: Checkout timefold-solver-enterprise (PR) # Checkout the PR branch first, if it exists + id: checkout-solver-enterprise + uses: actions/checkout@v4 + continue-on-error: true + with: + repository: TimefoldAI/timefold-solver-enterprise + ref: ${{ github.head_ref }} + token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. + path: ./timefold-solver-enterprise + fetch-depth: 0 # Otherwise merge will fail on account of not having history. + - name: Checkout timefold-solver-enterprise (main) # Checkout the main branch if the PR branch does not exist + if: steps.checkout-solver-enterprise.outcome != 'success' + uses: actions/checkout@v4 + with: + repository: TimefoldAI/timefold-solver-enterprise + ref: main + token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. + path: ./timefold-solver-enterprise + fetch-depth: 0 # Otherwise merge will fail on account of not having history. + + # Build and test + - name: Setup Temurin 21 and Maven + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + - name: Quickly build timefold-solver + working-directory: ./timefold-solver + shell: bash + run: mvn -B -Dquickly clean install + - name: Build and test timefold-solver-enterprise + working-directory: ./timefold-solver-enterprise + shell: bash + run: mvn -B clean verify + enterprise-python: + needs: approval_required + name: Enterprise Edition (Python) + concurrency: + group: downstream-enterprise-python-${{ github.event_name }}-${{ github.head_ref }} + cancel-in-progress: true + timeout-minutes: 120 + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + with: + path: './timefold-solver' + ref: ${{ github.event.pull_request.head.sha }} # The GHA event will pull the main branch by default, and we must specify the PR reference version + + # Clone timefold-solver-enterprise + # Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it. + - name: Checkout timefold-solver-enterprise (PR) # Checkout the PR branch first, if it exists + id: checkout-solver-enterprise + uses: actions/checkout@v4 + continue-on-error: true + with: + repository: TimefoldAI/timefold-solver-enterprise + ref: ${{ github.head_ref }} + token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. + path: ./timefold-solver-enterprise + fetch-depth: 0 # Otherwise merge will fail on account of not having history. + + - name: Checkout timefold-solver-enterprise (main) # Checkout the main branch if the PR branch does not exist + if: steps.checkout-solver-enterprise.outcome != 'success' + uses: actions/checkout@v4 + with: + repository: TimefoldAI/timefold-solver-enterprise + ref: main + token: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Safe; only used to clone the repo and not stored in the fork. + path: ./timefold-solver-enterprise + fetch-depth: 0 # Otherwise merge will fail on account of not having history. + + # Build and test + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: 'temurin' + cache: 'maven' + + # Need to install all Python versions in the same run for tox + - name: Python 3.10, Python 3.11, Python 3.12 Setup + uses: actions/setup-python@v5 + with: + python-version: | + 3.10 + 3.11 + 3.12 + cache: 'pip' + cache-dependency-path: | + **/setup.py + + - name: Install tox + run: + pip install tox build + + - name: Build Timefold Solver for Python + working-directory: ./timefold-solver + run: python -m build + + - name: Run tox on Timefold Solver Enterprise for Python test suite + working-directory: ./timefold-solver-enterprise + env: + PIP_FIND_LINKS: ${{ github.workspace }}/timefold-solver/dist + run: tox + sonarcloud: + needs: approval_required + name: SonarCloud + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + ref: ${{ github.event.pull_request.head.sha }} # The GHA event will pull the main branch by default, and we must specify the PR reference version + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: 21 + distribution: 'temurin' + cache: 'maven' + - name: Python 3.10, Python 3.11, Python 3.12 Setup + uses: actions/setup-python@v5 + with: + python-version: | + 3.10 + 3.11 + 3.12 + cache: 'pip' + cache-dependency-path: | + **/setup.py + - name: Install tox + run: + pip install tox coverage pytest pytest-cov + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Build with Maven to measure code coverage # The ENV variables are limited to the scope of the current step. Avoid adding sensitive ENV variables here as the tests could leak them. + run: mvn -B clean install -Prun-code-coverage + + - name: Get JaCoCo Agent + run: mvn org.apache.maven.plugins:maven-dependency-plugin:2.8:get -Dartifact=org.jacoco:org.jacoco.agent:0.8.11:jar:runtime -Ddest=target/jacocoagent.jar + + - name: Run tox to measure timefold solver python code coverage from Python tests + run: python -m tox -- --cov=timefold --cov-report=xml:target/coverage.xml --cov-config=tox.ini --cov-branch --cov-append --jacoco-agent=./target/jacocoagent.jar + + - name: Run tox to measure jpyinterpreter code coverage from Python tests + working-directory: ./python/jpyinterpreter + run: python -m tox -- --cov=jpyinterpreter --cov-report=xml:target/coverage.xml --cov-config=tox.ini --cov-branch --cov-append --jacoco-agent=../../target/jacocoagent.jar --jacoco-output=../../target/jacoco.exec + + - name: Run analysis + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Needed to run the SonarCloud analysis + run: mvn -B -Psonarcloud-analysis validate org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.organization=timefold -Dsonar.projectKey=ai.timefold:timefold-solver -Dsonar.host.url=https://sonarcloud.io -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} -Dsonar.scm.revision=${{ github.event.pull_request.head.sha }} \ No newline at end of file diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml deleted file mode 100644 index d58144ad7b..0000000000 --- a/.github/workflows/sonarcloud.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: SonarCloud -on: - push: - branches: - - main - # Enables the workflow to run on PRs from forks. - # CI will only run for trusted users, to prevent stealing of secrets. - pull_request_target: - types: - - opened - - reopened - - synchronize - - labeled -jobs: - # Check if the user is a member of the organization; if so, allow the PR to sail through. - known_user: - runs-on: ubuntu-latest - outputs: - is_member_of_org: ${{ steps.auth_check.outputs.authorized }} - steps: - - id: auth_check - env: - GH_TOKEN: ${{ secrets.JRELEASER_GITHUB_TOKEN }} # Release account is a Solver Gatekeeper. - shell: bash - run: | - # -g to allow actors such as dependabot[bot] - ORG_MEMBERSHIP=`curl -g -L -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $GH_TOKEN" "https://api.github.com/orgs/TimefoldAI/memberships/${{ github.actor }}" | jq -r '.state == "active"'` - echo "authorized=$ORG_MEMBERSHIP" >> "$GITHUB_OUTPUT" - - id: validation - shell: bash - run: | - echo "Authorized user: ${{ steps.auth_check.outputs.authorized }}" - # If the user is not a member, require a member to approve the PR. - approval_required: - needs: known_user - environment: - ${{ - github.event_name == 'pull_request_target' && - github.event.pull_request.head.repo.full_name != github.repository && - (needs.known_user.outputs.is_member_of_org != 'true' || github.actor == 'dependabot[bot]') && - 'external' || 'internal' - }} - runs-on: ubuntu-latest - steps: - - run: true - build: - needs: approval_required - name: Build and analyze - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - ref: ${{ github.event.pull_request.head.sha }} # The GHA event will pull the main branch by default, and we must specify the PR reference version - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: 17 - distribution: 'temurin' - cache: 'maven' - - name: Python 3.10, Python 3.11, Python 3.12 Setup - uses: actions/setup-python@v5 - with: - python-version: | - 3.10 - 3.11 - 3.12 - cache: 'pip' - cache-dependency-path: | - **/setup.py - - name: Install tox - run: - pip install tox coverage pytest pytest-cov - - name: Cache SonarCloud packages - uses: actions/cache@v4 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Build with Maven to measure code coverage # The ENV variables are limited to the scope of the current step. Avoid adding sensitive ENV variables here as the tests could leak them. - run: mvn -B clean install -Prun-code-coverage - - - name: Get JaCoCo Agent - run: mvn org.apache.maven.plugins:maven-dependency-plugin:2.8:get -Dartifact=org.jacoco:org.jacoco.agent:0.8.11:jar:runtime -Ddest=target/jacocoagent.jar - - - name: Run tox to measure timefold solver python code coverage from Python tests - run: python -m tox -- --cov=timefold --cov-report=xml:target/coverage.xml --cov-config=tox.ini --cov-branch --cov-append --jacoco-agent=./target/jacocoagent.jar - - - name: Run tox to measure jpyinterpreter code coverage from Python tests - working-directory: ./python/jpyinterpreter - run: python -m tox -- --cov=jpyinterpreter --cov-report=xml:target/coverage.xml --cov-config=tox.ini --cov-branch --cov-append --jacoco-agent=../../target/jacocoagent.jar --jacoco-output=../../target/jacoco.exec - - - name: Run analysis - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Needed to run the SonarCloud analysis - run: mvn -B -Psonarcloud-analysis validate org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.organization=timefold -Dsonar.projectKey=ai.timefold:timefold-solver -Dsonar.host.url=https://sonarcloud.io -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} -Dsonar.scm.revision=${{ github.event.pull_request.head.sha }} \ No newline at end of file