diff --git a/.github/scripts/change_versions.sh b/.github/scripts/change_versions.sh new file mode 100755 index 0000000..cfc9613 --- /dev/null +++ b/.github/scripts/change_versions.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Expects the following environment variables to be set: +# $NEW_VERSION (Example: "1.2.0") +# $NEW_VERSION_PYTHON (Example: "1.2.0a0") + +# This will fail the Maven build if the version is not available. +# Thankfully, this is not the case (yet) in this project. +# If/when it happens, this needs to be replaced by a manually provided version, +# as scanning the text of the POM would be unreliable. +echo " New version: $NEW_VERSION" +echo " New Python Version: $NEW_VERSION_PYTHON" +mvn versions:update-parent "-DparentVersion=[$NEW_VERSION,$NEW_VERSION]" -DallowSnapshots=true -DgenerateBackupPoms=false +( + cd jpyinterpreter || + mvn versions:update-parent "-DparentVersion=[$NEW_VERSION,$NEW_VERSION]" -DallowSnapshots=true -DgenerateBackupPoms=false +) +( + cd timefold-solver-python-code || + mvn versions:update-parent "-DparentVersion=[$NEW_VERSION,$NEW_VERSION]" -DallowSnapshots=true -DgenerateBackupPoms=false +) +sed -i "s/^timefold_solver_python_version.*=.*/timefold_solver_python_version = '$NEW_VERSION_PYTHON'/" setup.py +git commit -am "build: switch to version $NEW_VERSION_PYTHON" \ No newline at end of file diff --git a/.github/workflows/release-changelog-template.md b/.github/workflows/release-changelog-template.md new file mode 100644 index 0000000..9a7c852 --- /dev/null +++ b/.github/workflows/release-changelog-template.md @@ -0,0 +1,28 @@ +**!!! REMOVE THIS !!!** + +**!!! SUMMARIZE THE RELEASE HERE !!!** + +**!!! REMOVE THIS !!!** + +# Changelog + +{{changelogChanges}} +{{changelogContributors}} + +_Timefold Solver Community Edition_ is an open source project, and you are more than welcome to contribute as well! +For more, see [Contributing](https://github.com/TimefoldAI/timefold-solver/blob/main/CONTRIBUTING.adoc). + +Should your business need to scale to truly massive data sets or require enterprise-grade support, +check out [_Timefold Solver Enterprise Edition_](https://timefold.ai/pricing). + +# How to use Timefold Solver in Python + +To see Timefold Solver in action, check out [the quickstarts](https://github.com/TimefoldAI/timefold-quickstarts). + +Add `timefold-solver=={{projectVersion}}` to your `requirements.txt` or `pip install timefold-solver=={{projectVersion}}` file to get started. + +# Additional notes + +The changelog and the list of contributors above are automatically generated. +It excludes contributions to certain areas of the repository, such as CI and build automation. +This is done for the sake of brevity and to make the user-facing changes stand out more. diff --git a/.github/workflows/release-pr-body.md b/.github/workflows/release-pr-body.md new file mode 100644 index 0000000..fa0844c --- /dev/null +++ b/.github/workflows/release-pr-body.md @@ -0,0 +1,13 @@ +At this point, the release of _Timefold Solver Community Edition_ for Python is ready to be published. +Artifacts have been uploaded to PyPI. +Release branch has been created. + +To finish the release of _Timefold Solver Community Edition_ for Python, +please follow the steps below in the given order: + +1. [ ] [Undraft the release](https://github.com/TimefoldAI/timefold-solver-python/releases) on Github. +2. [ ] Merge this PR. +3. [ ] Delete the branch that this PR is based on. (Typically a button appears on this page once the PR is merged.) + +Note: If this is a dry run, +none of the above applies and this PR should not be merged. \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4750a41 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,124 @@ +name: Release +on: + workflow_dispatch: + inputs: + version: + description: 'Community Edition version (e.g. 1.0.0)' + required: true + pythonVersionSuffix: + description: 'What suffix to append to the Python version (ex: a0 for alpha release)' + required: true + default: a0 + sourceBranch: + description: 'Branch to cut the release from' + default: main + required: true + releaseBranch: + description: 'Release branch to create (e.g. 1.0.x for version 1.0.0; once created, branch protection rules apply)' + default: dry_run + required: true + dryRun: + description: 'Do a dry run? (true or false)' + default: true + required: true +jobs: + build: + env: + MAVEN_ARGS: "--no-transfer-progress --batch-mode" + runs-on: ubuntu-latest + steps: + - name: Print inputs to the release workflow + run: echo "${{ toJSON(github.event.inputs) }}" + - name: Checkout the relevant timefold-solver tag + uses: actions/checkout@v4 + with: + repository: "TimefoldAI/timefold-solver" + path: "./timefold-solver" + fetch-depth: 0 + ref: v${{ github.event.inputs.version }} + + - uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: 'maven' + + - name: Set up Maven + uses: stCarolas/setup-maven@v5 + with: + maven-version: 3.9.3 + + - name: Python 3.12 Setup + uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Install Pip and build + run: + python -m pip install --upgrade pip + pip install build + + - name: Checkout timefold-solver-python + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.inputs.sourceBranch }} + + - name: Create release branch and switch to it + run: | + git config user.name "Timefold Release Bot" + git config user.email "release@timefold.ai" + git checkout -b ${{ github.event.inputs.releaseBranch }} + + # We skip tests in dry run, to make the process faster. + # Technically, this goes against the main reason for doing a dry run; to eliminate potential problems. + # But unless something catastrophic happened, PR checks on source branch already ensured that all tests pass. + # We also do not use versions:set, because we'd have to have the SNAPSHOT version built from somewhere, + # and at this point in the release, there is no upstream branch anywhere that would have this version anymore. + - name: Set release version and build release + run: | + export NEW_VERSION=${{ github.event.inputs.version }} + export NEW_VERSION_PYTHON=${{ github.event.inputs.version }}${{ github.event.inputs.pythonVersionSuffix }} + .github/scripts/change_versions.sh + python -m build + + # JReleaser requires the release branch to exist, so we need to push it before releasing. + # Once this is pushed, branch protection rules apply. + # So if any of the subsequent steps should fail, the release branch is there to stay; cannot be deleted. + # To minimize that chance, do a dry run first, with a branch named in a way that the protection rules don't apply. + - name: Push release branch to Git + run: | + git push origin ${{ github.event.inputs.releaseBranch }} + + - name: Run JReleaser + uses: jreleaser/release-action@v2 + env: + JRELEASER_DRY_RUN: ${{ github.event.inputs.dryRun }} + JRELEASER_PROJECT_VERSION: ${{ github.event.inputs.version }}${{ github.event.inputs.pythonVersionSuffix }} + JRELEASER_GITHUB_TOKEN: ${{ secrets.JRELEASER_GITHUB_TOKEN }} + JRELEASER_GPG_PASSPHRASE: ${{ secrets.JRELEASER_GPG_PASSPHRASE }} + JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.JRELEASER_GPG_PUBLIC_KEY }} + JRELEASER_GPG_SECRET_KEY: ${{ secrets.JRELEASER_GPG_SECRET_KEY }} + + - name: JReleaser release output + uses: actions/upload-artifact@v4 + if: always() + with: + name: jreleaser-release + path: | + out/jreleaser/trace.log + out/jreleaser/output.properties + + # TODO: Use https://github.com/marketplace/actions/pypi-publish to publish to PyPI here + + # Pull Request will be created with the changes and a summary of next steps. + - name: Put back the 999-SNAPSHOT version on the release branch + run: | + git checkout -B ${{ github.event.inputs.releaseBranch }}-put-back-999-snapshot + export NEW_VERSION="999-SNAPSHOT" + export NEW_VERSION_PYTHON="999-dev0" + .github/scripts/change_versions.sh + git push origin ${{ github.event.inputs.releaseBranch }}-put-back-999-snapshot + gh pr create --reviewer triceo,Christopher-Chianelli --base ${{ github.event.inputs.releaseBranch }} --head ${{ github.event.inputs.releaseBranch }}-put-back-999-snapshot --title "build: move back to 999-SNAPSHOT" --body-file .github/workflows/release-pr-body.md + env: + GITHUB_TOKEN: ${{ secrets.JRELEASER_GITHUB_TOKEN }} diff --git a/jreleaser.yml b/jreleaser.yml new file mode 100644 index 0000000..366a763 --- /dev/null +++ b/jreleaser.yml @@ -0,0 +1,37 @@ +project: + name: timefold-solver-python + +signing: + active: ALWAYS + armored: true + +release: + github: + commitAuthor: + name: "Timefold Release Bot" + email: "release@timefold.ai" + releaseName: "Timefold Solver Community Edition for Python {{projectVersion}}" + draft: true + overwrite: false + sign: true + milestone: + close: true + name: "v{{projectVersion}}" + changelog: + formatted: ALWAYS + preset: "conventional-commits" + contentTemplate: ".github/workflows/release-changelog-template.md" + contributors: + format: "- {{contributorName}}{{#contributorUsernameAsLink}} ({{.}}){{/contributorUsernameAsLink}}" + hide: + uncategorized: true + categories: + - build + - ci + contributors: + - "Timefold Release Bot" + +files: + globs: + - pattern: "dist/**/*.whl" + - pattern: "dist/**/*.tar.gz" \ No newline at end of file diff --git a/setup.py b/setup.py index 231da47..89cb611 100644 --- a/setup.py +++ b/setup.py @@ -44,10 +44,10 @@ def run(self): command = 'mvnw' if platform.system() == 'Windows': command = 'mvnw.cmd' - self.create_stubs(project_root, command) - subprocess.run([str((project_root / command).absolute()), 'clean', 'install', '-Dasciidoctor.skip', - '-Dassembly.skipAssembly'], + + subprocess.run([str((project_root / command).absolute()), 'clean', 'install'], cwd=project_root, check=True) + self.create_stubs(project_root, command) classpath_jars = [] # Add the main artifact classpath_jars.extend(glob.glob(os.path.join(project_root, 'timefold-solver-python-core', 'target', '*.jar'))) @@ -75,11 +75,11 @@ def find_stub_files(stub_root: str): this_directory = Path(__file__).parent long_description = (this_directory / "README.md").read_text() -version = '999-dev0' +timefold_solver_python_version = '999-dev0' setup( name='timefold-solver', - version=version, + version=timefold_solver_python_version, license='Apache License Version 2.0', license_file='LICENSE', description='An AI constraint solver that optimizes planning and scheduling problems',