From 7ff0b5294a3dbb36b7136c5311d39a8f0abfa822 Mon Sep 17 00:00:00 2001 From: Rashid N H M <95639609+rashidnhm@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:25:59 -0400 Subject: [PATCH] Refactor of dev prerelease auto-update-version workflow (#685) --------- Co-authored-by: ringo-but-quantum Co-authored-by: Lee James O'Riordan --- .github/workflows/dev_version_script.py | 114 ++++++++++++++--------- .github/workflows/update_dev_version.yml | 50 +++++++--- 2 files changed, 107 insertions(+), 57 deletions(-) diff --git a/.github/workflows/dev_version_script.py b/.github/workflows/dev_version_script.py index 14acb3bd6f..033296e33f 100644 --- a/.github/workflows/dev_version_script.py +++ b/.github/workflows/dev_version_script.py @@ -11,74 +11,102 @@ # 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. + +import re import argparse from pathlib import Path -import importlib -import re +try: + from semver import Version +except ImportError as exc: + raise ImportError("Unable to import semver. Install semver by running `pip install semver`") from exc -VERSION_FILE_PATH = "pennylane_lightning/core/_version.py" +DEV_PRERELEASE_TAG_PREFIX = "dev" +DEV_PRERELEASE_TAG_START = "dev0" +VERSION_FILE_PATH = Path("pennylane_lightning/core/_version.py") -rgx_ver = re.compile('^__version__ = "(.*?)"$') +rgx_ver = re.compile(pattern=r"^__version__ = \"(.*)\"$", flags=re.MULTILINE) -rgx_dev_ver = re.compile("^(\d*\.\d*\.\d*)-dev(\d*)$") +def extract_version(repo_root_path: Path) -> Version: + """ + Given the repository root for pennylane-lightning, this function extracts the semver version from + pennylane_lightning/core/_version.py. -def extract_version(package_path): - with package_path.joinpath(VERSION_FILE_PATH).open("r") as f: - for line in f.readlines(): + :param repo_root_path: Path to the repository root. + :return: Extracted version a semver.Version object. + """ + version_file_path = repo_root_path / VERSION_FILE_PATH + if not version_file_path.exists(): + raise FileNotFoundError(f"Unable to find version file at location {version_file_path}") + + with version_file_path.open() as f: + for line in f: if line.startswith("__version__"): - line = line.strip() - m = rgx_ver.match(line) - return m.group(1) + if (m := rgx_ver.match(line.strip())) is not None: + if not m.groups(): + raise ValueError(f"Unable to find valid semver for __version__. Got: '{line}'") + parsed_semver = m.group(1) + if not Version.is_valid(parsed_semver): + raise ValueError(f"Invalid semver for __version__. Got: '{parsed_semver}' from line '{line}'") + return Version.parse(parsed_semver) + raise ValueError(f"Unable to find valid semver for __version__. Got: '{line}'") raise ValueError("Cannot parse version") -def is_dev(version_str): - m = rgx_dev_ver.fullmatch(version_str) - return m is not None +def update_prerelease_version(repo_root_path: Path, version: Version): + """ + Updates the version file within pennylane_lightning/core/_version.py. + :param repo_root_path: Path to the repository root. + :param version: The new version to use within the file. + :return: + """ + version_file_path = repo_root_path / VERSION_FILE_PATH + if not version_file_path.exists(): + raise FileNotFoundError(f"Unable to find version file at location {version_file_path}") -def update_dev_version(package_path, version_str): - m = rgx_dev_ver.fullmatch(version_str) - if m.group(2) == "": - curr_dev_ver = 0 - else: - curr_dev_ver = int(m.group(2)) + with version_file_path.open() as f: + lines = [ + rgx_ver.sub(f"__version__ = \"{str(version)}\"", line) + for line in f + ] - new_version_str = "{}-dev{}".format(m.group(1), str(curr_dev_ver + 1)) - - lines = [] - with package_path.joinpath(VERSION_FILE_PATH).open("r") as f: - for line in f.readlines(): - if not line.startswith("__version__"): - lines.append(line) - else: - lines.append(f'__version__ = "{new_version_str}"\n') - - with package_path.joinpath(VERSION_FILE_PATH).open("w") as f: + with version_file_path.open("w") as f: f.write("".join(lines)) if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("--pr-path", dest="pr", type=str, required=True, help="Path to the PR dir") + parser.add_argument("--pr-path", dest="pr", type=Path, required=True, help="Path to the PR dir") parser.add_argument( - "--master-path", dest="master", type=str, required=True, help="Path to the master dir" + "--master-path", dest="master", type=Path, required=True, help="Path to the master dir" ) args = parser.parse_args() - pr_version = extract_version(Path(args.pr)) - master_version = extract_version(Path(args.master)) + pr_version = extract_version(args.pr) + master_version = extract_version(args.master) - if pr_version == master_version: - if is_dev(pr_version): - print("Automatically update version string.") - update_dev_version(Path(args.pr), pr_version) + print("Got Package Version from 'master' ->", str(master_version)) + print("Got Package Version from 'PR' ->", str(pr_version)) + + # Only attempt to bump the version if the pull_request is: + # - A prerelease, has `X.Y.Z-prerelease` in _version.py + # - The prerelease startswith `dev`. We do not want to auto bump for non-dev prerelease. + if pr_version.prerelease and pr_version.prerelease.startswith(DEV_PRERELEASE_TAG_PREFIX): + # If master branch does not have a prerelease (for any reason) OR does not have an ending number + # Then default to the starting tag + if not master_version.prerelease or master_version.prerelease == DEV_PRERELEASE_TAG_PREFIX: + next_prerelease_version = DEV_PRERELEASE_TAG_START + else: + # Generate the next prerelease version (eg: dev1 -> dev2). Sourcing from master version. + next_prerelease_version = master_version.next_version("prerelease").prerelease + new_version = master_version.replace(prerelease=next_prerelease_version) + if pr_version != new_version: + print(f"Updating PR package version from -> '{pr_version}', to -> {new_version}") + update_prerelease_version(args.pr, new_version) else: - print( - "Even though version of this PR is different from the master, as the PR is not dev, we do nothing." - ) + print(f"PR is on the expected version '{new_version}' ... Nothing to do!") else: - print("Version of this PR is already different from master. Do nothing.") + print("PR is not a dev prerelease ... Nothing to do!") diff --git a/.github/workflows/update_dev_version.yml b/.github/workflows/update_dev_version.yml index 160d9e1e18..a7c08fb40f 100644 --- a/.github/workflows/update_dev_version.yml +++ b/.github/workflows/update_dev_version.yml @@ -1,6 +1,8 @@ name: Update dev version automatically on: - pull_request: + pull_request_target: + branches: + - master jobs: update-dev-version: @@ -14,31 +16,51 @@ jobs: ref: master path: main + - name: Checkout PennyLane-Lightning PR + uses: actions/checkout@v4 + with: + token: ${{ secrets.AUTO_UPDATE_VERSION_RINGO_TOKEN }} + path: pr + + - name: Checkout PR branch + env: + GITHUB_TOKEN: ${{ secrets.AUTO_UPDATE_VERSION_RINGO_TOKEN }} + run: cd pr && gh pr checkout ${{ github.event.pull_request.number }} + - uses: actions/setup-python@v5 name: Install Python with: python-version: '3.9' - - name: Checkout PennyLane-Lightning PR - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - ref: ${{ github.head_ref }} - path: pr + - name: Install Python Dependencies + run: pip install semver + + - name: Capture Current Version in Pull Request + id: old_version + run: echo "version=$(grep -o '__version__ = "[^"]\+[0-9]\+"' pr/pennylane_lightning/core/_version.py | cut -d '"' -f 2)" >> $GITHUB_OUTPUT - name: Run version update script - run: > - python3 pr/.github/workflows/dev_version_script.py - --pr-path "./pr" --master-path "./main" + run: | + python3 \ + main/.github/workflows/dev_version_script.py \ + --pr-path "${{ github.workspace }}/pr" \ + --master-path "${{ github.workspace }}/main" + + - name: Capture Changed version + id: new_version + run: echo "version=$(grep -o '__version__ = "[^"]\+[0-9]\+"' pr/pennylane_lightning/core/_version.py | cut -d '"' -f 2)" >> $GITHUB_OUTPUT - name: Commit and push changes - if: + if: steps.old_version.outputs.version != steps.new_version.outputs.version + env: + OLD_VERSION: ${{ steps.old_version.outputs.version }} + NEW_VERSION: ${{ steps.new_version.outputs.version }} run: | cd ./pr if [[ -n $(git status -s) ]]; then - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - git config --global user.name "Dev version update bot" + git config --global user.email '${{ secrets.AUTO_UPDATE_VERSION_RINGO_EMAIL }}' + git config --global user.name "ringo-but-quantum" git add . - git commit -m 'Auto update version' + git commit -m "Auto update version from '$OLD_VERSION' to '$NEW_VERSION'" git push fi