From 03ebe2d2e9a3cccc6c1b08f6fe171fc47d8df215 Mon Sep 17 00:00:00 2001 From: Jeroen van Straten Date: Tue, 31 May 2022 13:00:02 +0200 Subject: [PATCH] chore: add version updating script in preparation for auto-release CI --- .github/workflows/misc.yml | 8 ++ RELEASE.md | 5 ++ ci/README.md | 5 ++ ci/version | 1 + ci/version-diff-template | 132 +++++++++++++++++++++++++++ ci/version.py | 178 +++++++++++++++++++++++++++++++++++++ 6 files changed, 329 insertions(+) create mode 100644 ci/README.md create mode 100644 ci/version create mode 100644 ci/version-diff-template create mode 100755 ci/version.py diff --git a/.github/workflows/misc.yml b/.github/workflows/misc.yml index dbd1083d..b0d1f0cb 100644 --- a/.github/workflows/misc.yml +++ b/.github/workflows/misc.yml @@ -21,3 +21,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: editorconfig-checker/action-editorconfig-checker@v1 + + check-version-patch: + name: Check version update patch file + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Check version update patch file + run: python3 ci/version.py check diff --git a/RELEASE.md b/RELEASE.md index 6b064cc1..fc01c2b3 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -25,6 +25,11 @@ important: by corrosion, but good to synchronize with the version of the main crate. - `tests/Cargo.toml`: can be ignored. +You (or CI) can use `ci/version.py` to update the version automatically, but +this automation is based on a patchfile that may go out of date. You may have +to regenerate it (using the same tool) if you change a file that includes the +version number. + Relation of `substrait-validator` crate version to the Substrait specification version is TBD. diff --git a/ci/README.md b/ci/README.md new file mode 100644 index 00000000..972202d2 --- /dev/null +++ b/ci/README.md @@ -0,0 +1,5 @@ +CI-related scripts +================== + +This directory contains some assorted scripts related to CI and the release +process. diff --git a/ci/version b/ci/version new file mode 100644 index 00000000..8a9ecc2e --- /dev/null +++ b/ci/version @@ -0,0 +1 @@ +0.0.1 \ No newline at end of file diff --git a/ci/version-diff-template b/ci/version-diff-template new file mode 100644 index 00000000..822f310b --- /dev/null +++ b/ci/version-diff-template @@ -0,0 +1,132 @@ +diff --git a/c/Cargo.toml b/c/Cargo.toml +index 0e9b476..7165025 100644 +--- a/c/Cargo.toml ++++ b/c/Cargo.toml +@@ -1,6 +1,6 @@ + [package] + name = "substrait-validator-c" +-version = "{frm}" ++version = "{to}" + edition = "2021" + license = "Apache-2.0" + +@@ -12,6 +12,6 @@ doc = false + cbindgen = "0.20.0" + + [dependencies] +-substrait-validator = {{ path = "../rs", version = "{frm}" }} ++substrait-validator = {{ path = "../rs", version = "{to}" }} + libc = "0.2" + thiserror = "1.0" +diff --git a/derive/Cargo.toml b/derive/Cargo.toml +index 7a8af00..539e170 100644 +--- a/derive/Cargo.toml ++++ b/derive/Cargo.toml +@@ -4,7 +4,7 @@ description = "Procedural macros for substrait-validator" + homepage = "https://substrait.io/" + repository = "https://github.com/substrait-io/substrait" + readme = "README.md" +-version = "{frm}" ++version = "{to}" + edition = "2021" + license = "Apache-2.0" + +diff --git a/py/Cargo.toml b/py/Cargo.toml +index c095a2d..32108ad 100644 +--- a/py/Cargo.toml ++++ b/py/Cargo.toml +@@ -1,6 +1,6 @@ + [package] + name = "substrait-validator-py" +-version = "{frm}" ++version = "{to}" + edition = "2018" + license = "Apache-2.0" + include = [ +@@ -29,7 +29,7 @@ name = "substrait_validator" + doc = false + + [dependencies] +-substrait-validator = {{ path = "../rs", version = "{frm}" }} ++substrait-validator = {{ path = "../rs", version = "{to}" }} + pyo3 = {{ version = "0.15.1", features = ["extension-module"] }} + + [build-dependencies] +diff --git a/py/pyproject.toml b/py/pyproject.toml +index 8481d2c..60d0453 100644 +--- a/py/pyproject.toml ++++ b/py/pyproject.toml +@@ -5,7 +5,7 @@ backend-path = ["."] + + [project] + name = "substrait-validator" +-version = "{frm}" ++version = "{to}" + description = "Validator for Substrait query plans" + readme = "README.md" + license = {{file = "LICENSE"}} +diff --git a/rs/Cargo.toml b/rs/Cargo.toml +index 6dbf6a8..d7e0c22 100644 +--- a/rs/Cargo.toml ++++ b/rs/Cargo.toml +@@ -4,7 +4,7 @@ description = "Substrait validator" + homepage = "https://substrait.io/" + repository = "https://github.com/substrait-io/substrait" + readme = "README.md" +-version = "{frm}" ++version = "{to}" + edition = "2021" + license = "Apache-2.0" + include = ["src", "build.rs", "README.md"] +@@ -17,7 +17,7 @@ prost-types = "0.10" + + # Prost doesn't generate any introspection stuff, so we hack that stuff in with + # our own procedural macros. +-substrait-validator-derive = {{ path = "../derive", version = "{frm}" }} ++substrait-validator-derive = {{ path = "../derive", version = "{to}" }} + + # Google/protobuf has a funny idea about case conventions (it converts them all + # over the place) and prost remaps to Rust's conventions to boot. So, to +diff --git a/rs/README.md b/rs/README.md +index 1ea785f..553fd82 100644 +--- a/rs/README.md ++++ b/rs/README.md +@@ -6,7 +6,7 @@ plans. + + ``` + [dependencies] +-substrait-validator = "{frm}" ++substrait-validator = "{to}" + ``` + + YAML file resolution +@@ -20,7 +20,7 @@ dependency: + + ``` + [dependencies] +-substrait-validator = {{ version = "{frm}", features = ["curl"] }} ++substrait-validator = {{ version = "{to}", features = ["curl"] }} + ``` + + This adds the `substrait_validator::Config::add_curl_yaml_uri_resolver()` +diff --git a/tests/Cargo.toml b/tests/Cargo.toml +index ae095fd..a341e1c 100644 +--- a/tests/Cargo.toml ++++ b/tests/Cargo.toml +@@ -1,6 +1,6 @@ + [package] + name = "test-runner" +-version = "{frm}" ++version = "{to}" + edition = "2018" + license = "Apache-2.0" + default-run = "runner" +@@ -14,7 +14,7 @@ name = "find_protoc" + path = "src/find_protoc.rs" + + [dependencies] +-substrait-validator = {{ path = "../rs", version = "{frm}" }} ++substrait-validator = {{ path = "../rs", version = "{to}" }} + serde = {{ version = "1.0", features = ["derive"] }} + serde_json = "1.0" + walkdir = "2" diff --git a/ci/version.py b/ci/version.py new file mode 100755 index 00000000..59018bc4 --- /dev/null +++ b/ci/version.py @@ -0,0 +1,178 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: Apache-2.0 + +import subprocess +import sys +import os + + +def set_version(current_version, new_version, dry_run=False): + """Tries to update the version from current_version to new_version.""" + print(f"Modifying version from {current_version} to {new_version}...") + if dry_run: + print(" (dry run, won't actually make the change)") + + # Read the template. + with open("version-diff-template", "r") as f: + template = f.read() + + # Fill out the template. + patch = template.format(frm=current_version, to=new_version).encode("utf-8") + + # Try to apply the patch. + output = subprocess.run(["git", "apply", "--check"], cwd="..", input=patch) + if output.returncode: + print("Patch is invalid. Something is out of sync: check the version") + print("file and try regenerating the patch template.") + sys.exit(1) + print("Generated patch seems valid.") + + # Stop here if this is a dry run. + if dry_run: + return + + # Apply the patch. + subprocess.run(["git", "apply"], cwd="..", input=patch, check=True) + + # Update the current version file. + with open("version", "w") as f: + f.write(new_version) + + print("Version modified.") + + +def escape(s): + """Escapes { and } sequences in a diff template file.""" + return s.replace("{", "{{").replace("}", "}}") + + +if __name__ == "__main__": + + # The rest of the script assumes that we're running from the script + # directory, so make sure that's where we are. + os.chdir(os.path.dirname(os.path.realpath(__file__))) + + # Read the current version. + with open("version", "r") as f: + current_version = f.read().strip() + + if len(sys.argv) == 2 and sys.argv[1] == "get": + print(current_version) + sys.exit(0) + + if len(sys.argv) == 3 and sys.argv[1] == "set": + new_version = sys.argv[2] + if new_version == current_version: + print("Error: that's already the current version.") + sys.exit(1) + set_version(current_version, new_version) + sys.exit(0) + + if len(sys.argv) == 2 and sys.argv[1] == "check": + test_version = "3.1.4" + if current_version == test_version: + test_version = "2.7.1" + set_version(current_version, test_version, dry_run=True) + sys.exit(0) + + if len(sys.argv) == 2 and sys.argv[1] == "update": + print("Change all the version numbers in the repository, EXCEPT the") + print("current_version file in this directory, such that git diff") + print("will give a valid diff (make sure the repo is clean before") + print("doing this). The new version doesn't matter; it just serves") + print("as a marker and will be reverted. When done, enter the version") + print("you changed the current version into here.") + print() + dummy_version = input("New/dummy version: ").strip() + print() + if dummy_version == current_version: + print("Current and new/dummy version must differ") + sys.exit(1) + + # Ask git for the diff, expected to correspond to a version change from + # current_version to dummy_version. + output = subprocess.run(["git", "diff"], capture_output=True) + if output.returncode: + error = output.stderr.decode("utf-8") + print(f"Failed to get diff:\n{error}") + sys.exit(1) + diff = output.stdout.decode("utf-8") + + # Parse the diff and convert it to a format-style template. + lines = diff.strip().split("\n") + template_lines = [] + num_changes = 0 + skip = False + for index, line in enumerate(lines): + if skip: + skip = False + continue + if line.startswith("-") and not line.startswith("---"): + if index == len(lines) - 1: + print("Error: - line at end of `git diff`") + sys.exit(1) + frm = line[1:] + if not lines[index + 1].startswith("+"): + print( + f"Error: expecting -+ line pairs on lines {index+1} " + f"and {index+2} of `git diff`" + ) + sys.exit(1) + to = lines[index + 1][1:] + if ( + frm.replace(current_version, dummy_version) != to + or to.replace(dummy_version, current_version) != frm + ): + print( + f"Error: diff at lines {index+1} and {index+2} of git " + f"diff is not just a change from {current_version} to " + f"{dummy_version}" + ) + sys.exit(1) + template_lines.append( + "-" + escape(frm).replace(current_version, "{frm}") + ) + template_lines.append("+" + escape(to).replace(dummy_version, "{to}")) + skip = True + num_changes += 1 + else: + template_lines.append(escape(line)) + template = "\n".join(template_lines) + "\n" + print(f"Found {num_changes} version instances.") + + # If no lines changed, the user probably didn't understand how this + # works. + if not num_changes: + print("Error: `git diff` returned empty or invalid diff.") + print( + "Did you manually change the version from " + f"{current_version} to {dummy_version}?" + ) + sys.exit(1) + + # Write the template. + with open("version-diff-template", "w") as f: + f.write(template) + print("Wrote updated template.") + + # Try to apply the reverse diff. + set_version(dummy_version, current_version) + sys.exit(0) + + # Arguments invalid, print usage. + me = sys.argv[0] if sys.argv else "version.py" + print("Usage:") + print() + print(f"{me} get") + print(" Returns the current version.") + print() + print(f"{me} set ") + print(" Updates the version to the given value.") + print() + print(f"{me} check") + print(" Checks patch template consistency.") + print() + print(f"{me} update") + print(" Interactively updates the patchfile template used to update") + print(" the version") + sys.exit(2)