diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml
index 907475adb0..866f684564 100644
--- a/.github/workflows/bazel.yml
+++ b/.github/workflows/bazel.yml
@@ -18,6 +18,8 @@ on:
- "**/*.java"
- "**/BUILD.bazel"
- "MODULE.bazel"
+ - "pom.xml"
+ - "scripts/sync_bazel_dependencies.py"
- ".bazelversion"
- ".bazelrc"
- ".github/workflows/bazel.yml"
@@ -27,6 +29,8 @@ on:
- "**/*.java"
- "**/BUILD.bazel"
- "MODULE.bazel"
+ - "pom.xml"
+ - "scripts/sync_bazel_dependencies.py"
- ".bazelversion"
- ".bazelrc"
- ".github/workflows/bazel.yml"
@@ -74,11 +78,14 @@ jobs:
path: |
~/.cache/bazel-disk-cache
~/.cache/bazel/cache/repos/v1
- key: bazel-${{ runner.os }}-java${{ matrix.java }}-${{ hashFiles('MODULE.bazel', '.bazelversion', 'maven_install.json') }}
+ key: bazel-${{ runner.os }}-java${{ matrix.java }}-${{ hashFiles('MODULE.bazel', 'pom.xml', 'scripts/sync_bazel_dependencies.py', '.bazelversion', 'maven_install.json') }}
restore-keys: |
bazel-${{ runner.os }}-java${{ matrix.java }}-
bazel-${{ runner.os }}-
+ - name: Verify Bazel dependency sync
+ run: python3 scripts/sync_bazel_dependencies.py --check
+
# Re-generate the Maven lock file so it is always consistent with
# MODULE.bazel. The __INPUT_ARTIFACTS_HASH in the committed file is
# intentionally set to -1 to signal that the file needs to be regenerated;
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4274c74da3..73e36ef669 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -20,5 +20,15 @@ If you have any problem with the package or any suggestions, please file an [iss
3. Submit a pull request.
4. The bot will automatically assign someone to review your PR. Check the full list of bot commands [here](https://prow.k8s.io/command-help).
+### Sync Bazel dependency versions
+
+The root `pom.xml` is the source of truth for Maven and Bazel dependency versions.
+If you update a managed dependency version there, also regenerate the Bazel dependency
+block and lock file before sending your PR:
+
+1. Run `python3 scripts/sync_bazel_dependencies.py`
+2. Run `REPIN=1 bazel run @maven//:pin`
+3. Commit the updated `MODULE.bazel` and `maven_install.json`
+
### Contact
You can reach the maintainers of this project at [SIG API Machinery](https://github.com/kubernetes/community/tree/master/sig-api-machinery) or on the [#kubernetes-client](https://kubernetes.slack.com/messages/kubernetes-client) channel on the Kubernetes slack.
diff --git a/MODULE.bazel b/MODULE.bazel
index 26f7670721..91728b2e60 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -25,59 +25,60 @@ bazel_dep(name = "contrib_rules_jvm", version = "0.27.0")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
+# BEGIN: generated by scripts/sync_bazel_dependencies.py
+# Generated from pom.xml by scripts/sync_bazel_dependencies.py.
+# Do not edit this block by hand; update pom.xml and rerun the script instead.
+# Artifact order follows pom.xml dependencyManagement order within each section.
maven.install(
artifacts = [
# ---- core ----
- # Versions kept in sync with the property values in the root pom.xml.
- "jakarta.annotation:jakarta.annotation-api:3.0.0",
- "io.swagger:swagger-annotations:1.6.16",
- "com.squareup.okhttp3:okhttp:5.3.2",
- "com.squareup.okhttp3:okhttp-jvm:5.3.2",
- "com.squareup.okhttp3:logging-interceptor:5.3.2",
- "com.squareup.okio:okio:3.16.4",
- "com.squareup.okio:okio-jvm:3.16.4",
- "com.google.code.gson:gson:2.14.0",
- "com.fasterxml.jackson.core:jackson-databind:2.21.2",
- "com.fasterxml.jackson.core:jackson-core:2.21.2",
- "com.fasterxml.jackson.core:jackson-annotations:2.21",
- "io.gsonfire:gson-fire:1.9.0",
"org.apache.commons:commons-lang3:3.20.0",
"org.apache.commons:commons-collections4:4.5.0",
+ "org.yaml:snakeyaml:2.6",
+ "commons-codec:commons-codec:1.22.0",
"org.apache.commons:commons-compress:1.28.0",
"commons-io:commons-io:2.22.0",
- "commons-codec:commons-codec:1.22.0",
- "org.yaml:snakeyaml:2.6",
- "org.slf4j:slf4j-api:2.0.17",
+ "com.github.ben-manes.caffeine:caffeine:3.0.0",
+ "org.slf4j:slf4j-api:2.0.18",
"org.bouncycastle:bcpkix-jdk18on:1.84",
"org.bouncycastle:bcprov-jdk18on:1.84",
+ "software.amazon.awssdk:sts:2.44.8",
+ "software.amazon.awssdk:auth:2.44.8",
+ "software.amazon.awssdk:http-auth-aws:2.44.8",
+ "software.amazon.awssdk:http-auth-spi:2.44.8",
+ "software.amazon.awssdk:http-client-spi:2.44.8",
+ "software.amazon.awssdk:utils:2.44.8",
"com.google.protobuf:protobuf-java:4.34.1",
"org.bitbucket.b_c:jose4j:0.9.6",
- "com.google.auth:google-auth-library-oauth2-http:1.46.0",
- "software.amazon.awssdk:auth:2.43.0",
- "software.amazon.awssdk:http-auth-aws:2.43.0",
- "software.amazon.awssdk:http-auth-spi:2.43.0",
- "software.amazon.awssdk:http-client-spi:2.43.0",
- "software.amazon.awssdk:sts:2.43.0",
- "software.amazon.awssdk:utils:2.43.0",
+ "com.bucket4j:bucket4j-core:8.10.1",
"io.prometheus:simpleclient:0.16.0",
"io.prometheus:simpleclient_httpserver:0.16.0",
- "com.bucket4j:bucket4j-core:8.10.1",
- "com.github.ben-manes.caffeine:caffeine:3.0.0",
- "com.flipkart.zjsonpatch:zjsonpatch:0.4.16",
+ "com.google.code.gson:gson:2.14.0",
+ "com.fasterxml.jackson.core:jackson-databind:2.21.3",
+ "com.fasterxml.jackson.core:jackson-annotations:2.21",
+ "com.fasterxml.jackson.core:jackson-core:2.21.3",
+ "io.gsonfire:gson-fire:1.9.0",
+ "com.squareup.okhttp3:okhttp:5.3.2",
+ "com.squareup.okhttp3:logging-interceptor:5.3.2",
+ "io.swagger:swagger-annotations:1.6.16",
+ "jakarta.annotation:jakarta.annotation-api:3.0.0",
+ "com.google.auth:google-auth-library-oauth2-http:1.47.0",
"org.jetbrains:annotations:26.1.0",
"org.reflections:reflections:0.10.2",
+ "com.squareup.okhttp3:okhttp-jvm:5.3.2",
+ "com.squareup.okio:okio:3.16.4",
+ "com.squareup.okio:okio-jvm:3.16.4",
+ "com.flipkart.zjsonpatch:zjsonpatch:0.4.16",
# ---- spring (Java 17+ modules) ----
- # spring-boot 4.0.6 transitively brings in spring-framework 7.0.x, but the
- # root pom.xml pins spring.version=6.2.12. Explicitly declare all
- # spring-framework modules at 6.2.12 and use maven.artifact() with
- # exclusions on the spring-boot artifacts so Coursier does not upgrade them.
- "org.springframework:spring-aop:6.2.12",
- "org.springframework:spring-beans:6.2.12",
- "org.springframework:spring-context:6.2.12",
- "org.springframework:spring-core:6.2.12",
- "org.springframework:spring-expression:6.2.12",
- "org.springframework:spring-test:6.2.12",
+ "org.springframework:spring-core:6.2.8",
+ "org.springframework:spring-aop:6.2.8",
+ "org.springframework:spring-beans:6.2.8",
+ "org.springframework:spring-context:6.2.8",
+ "org.springframework:spring-expression:6.2.8",
+ "org.springframework:spring-test:6.2.8",
# ---- test ----
+ "ch.qos.logback:logback-classic:1.5.32",
+ "ch.qos.logback:logback-core:1.5.32",
"org.junit.jupiter:junit-jupiter-api:5.13.4",
"org.junit.jupiter:junit-jupiter-engine:5.13.4",
"org.junit.jupiter:junit-jupiter-params:5.13.4",
@@ -90,10 +91,8 @@ maven.install(
"uk.org.webcompere:system-stubs-jupiter:2.1.8",
"uk.org.webcompere:system-stubs-core:2.1.8",
"org.wiremock:wiremock:3.13.2",
- "org.awaitility:awaitility:4.3.0",
"org.assertj:assertj-core:3.27.7",
- "ch.qos.logback:logback-classic:1.5.32",
- "ch.qos.logback:logback-core:1.5.32",
+ "org.awaitility:awaitility:4.3.0",
],
repositories = [
"https://repo1.maven.org/maven2",
@@ -103,14 +102,13 @@ maven.install(
lock_file = "//:maven_install.json",
)
-# Spring Boot 4.0.6 artifacts declared with exclusions to pin spring-framework at
-# 6.2.12 (matching pom.xml spring.version) instead of the 7.0.x that spring-boot
-# 4.0.x normally pulls in.
+# Spring Boot 4.0.6 artifacts declared with exclusions to keep
+# spring-framework pinned to 6.2.8.
_SPRING_FRAMEWORK_EXCLUSIONS = [
+ "org.springframework:spring-core",
"org.springframework:spring-aop",
"org.springframework:spring-beans",
"org.springframework:spring-context",
- "org.springframework:spring-core",
"org.springframework:spring-expression",
"org.springframework:spring-test",
]
@@ -143,4 +141,6 @@ maven.artifact(
exclusions = _SPRING_FRAMEWORK_EXCLUSIONS,
)
+# END: generated by scripts/sync_bazel_dependencies.py
+
use_repo(maven, "maven")
diff --git a/pom.xml b/pom.xml
index ce8a92b966..9eb97e24cb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,7 +52,7 @@
3.0.0
4.34.1
5.13.4
- 5.13.4
+ 1.13.4
${junit-jupiter.version}
8.10.1
1.84
@@ -73,6 +73,18 @@
6.2.8
0.16.0
0.10.2
+ 2.44.8
+ 1.47.0
+ 0.9.6
+ 26.1.0
+ 1.5.32
+ 5.23.0
+ 3.16.4
+ 3.27.7
+ 4.3.0
+ 2.1.8
+ 3.13.2
+ 0.4.16
org.apache.commons
commons-lang3
@@ -161,10 +174,40 @@
bcpkix-jdk18on
${bouncycastle.version}
+
+ org.bouncycastle
+ bcprov-jdk18on
+ ${bouncycastle.version}
+
software.amazon.awssdk
sts
- 2.44.8
+ ${aws.sdk.version}
+
+
+ software.amazon.awssdk
+ auth
+ ${aws.sdk.version}
+
+
+ software.amazon.awssdk
+ http-auth-aws
+ ${aws.sdk.version}
+
+
+ software.amazon.awssdk
+ http-auth-spi
+ ${aws.sdk.version}
+
+
+ software.amazon.awssdk
+ http-client-spi
+ ${aws.sdk.version}
+
+
+ software.amazon.awssdk
+ utils
+ ${aws.sdk.version}
com.google.protobuf
@@ -174,7 +217,7 @@
org.bitbucket.b_c
jose4j
- 0.9.6
+ ${jose4j.version}
com.bucket4j
@@ -196,11 +239,26 @@
spring-core
${spring.version}
+
+ org.springframework
+ spring-aop
+ ${spring.version}
+
+
+ org.springframework
+ spring-beans
+ ${spring.version}
+
org.springframework
spring-context
${spring.version}
+
+ org.springframework
+ spring-expression
+ ${spring.version}
+
org.springframework.boot
spring-boot-autoconfigure
@@ -274,19 +332,57 @@
com.google.auth
google-auth-library-oauth2-http
- 1.47.0
+ ${google.auth.version}
org.jetbrains
annotations
- 26.1.0
+ ${jetbrains.annotations.version}
+
+
+ org.reflections
+ reflections
+ ${reflections.version}
+
+
+ com.squareup.okhttp3
+ okhttp-jvm
+ ${okhttp3.version}
+
+
+ com.squareup.okio
+ okio
+ ${okio.version}
+
+
+ com.squareup.okio
+ okio-jvm
+ ${okio.version}
ch.qos.logback
logback-classic
- 1.5.32
+ ${logback.version}
+ test
+
+
+ ch.qos.logback
+ logback-core
+ ${logback.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit-jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-jupiter.version}
test
@@ -301,22 +397,58 @@
${junit-jupiter.version}
test
+
+ org.junit.platform
+ junit-platform-launcher
+ ${junit-platform.version}
+ test
+
+
+ org.junit.platform
+ junit-platform-commons
+ ${junit-platform.version}
+ test
+
+
+ org.junit.platform
+ junit-platform-engine
+ ${junit-platform.version}
+ test
+
+
+ org.junit.platform
+ junit-platform-reporting
+ ${junit-platform.version}
+ test
+
+
+ org.mockito
+ mockito-core
+ ${mockito.version}
+ test
+
org.mockito
mockito-junit-jupiter
- 5.23.0
+ ${mockito.version}
test
uk.org.webcompere
system-stubs-jupiter
- 2.1.8
+ ${system-stubs.version}
+ test
+
+
+ uk.org.webcompere
+ system-stubs-core
+ ${system-stubs.version}
test
org.wiremock
wiremock
- 3.13.2
+ ${wiremock.version}
test
@@ -334,7 +466,7 @@
com.flipkart.zjsonpatch
zjsonpatch
- 0.4.16
+ ${zjsonpatch.version}
com.fasterxml.jackson.core
@@ -345,13 +477,13 @@
org.assertj
assertj-core
- 3.27.7
+ ${assertj.version}
test
org.awaitility
awaitility
- 4.3.0
+ ${awaitility.version}
test
diff --git a/scripts/sync_bazel_dependencies.py b/scripts/sync_bazel_dependencies.py
new file mode 100644
index 0000000000..bbef5a7281
--- /dev/null
+++ b/scripts/sync_bazel_dependencies.py
@@ -0,0 +1,284 @@
+#!/usr/bin/env python3
+
+from __future__ import annotations
+
+import argparse
+from dataclasses import dataclass
+import re
+import sys
+import xml.etree.ElementTree as ET
+from pathlib import Path
+
+
+GENERATED_START = "# BEGIN: generated by scripts/sync_bazel_dependencies.py"
+GENERATED_END = "# END: generated by scripts/sync_bazel_dependencies.py"
+MAX_PROPERTY_RESOLUTION_DEPTH = 20
+SECTION_CORE = "core"
+SECTION_SPRING = "spring (Java 17+ modules)"
+SECTION_TEST = "test"
+SCOPE_TEST = "test"
+SPRING_BOOT_GROUP = "org.springframework.boot"
+SPRING_FRAMEWORK_GROUP = "org.springframework"
+SPRING_BOOT_COORDINATE = "org.springframework.boot:spring-boot"
+SPRING_CORE_COORDINATE = "org.springframework:spring-core"
+EXCLUDED_BAZEL_COORDINATES = {
+ "commons-cli:commons-cli",
+ "com.google.code.findbugs:jsr305",
+ "org.junit.jupiter:junit-jupiter",
+}
+
+
+@dataclass(frozen=True)
+class ManagedDependency:
+ group_id: str
+ artifact_id: str
+ version: str
+ scope: str | None = None
+
+ @property
+ def coordinate(self) -> str:
+ return f"{self.group_id}:{self.artifact_id}"
+
+ @property
+ def bazel_artifact(self) -> str:
+ return f"{self.coordinate}:{self.version}"
+
+
+def parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ description="Sync Bazel dependency declarations from the root pom.xml."
+ )
+ parser.add_argument(
+ "--root",
+ type=Path,
+ default=Path(__file__).resolve().parents[1],
+ help="Repository root containing pom.xml and MODULE.bazel.",
+ )
+ parser.add_argument(
+ "--check",
+ action="store_true",
+ help="Fail if MODULE.bazel is not up to date.",
+ )
+ return parser.parse_args()
+
+
+def local_name(tag: str) -> str:
+ return tag.split("}", 1)[-1]
+
+
+def parse_managed_dependencies(pom_path: Path) -> list[ManagedDependency]:
+ root = ET.parse(pom_path).getroot()
+ namespace = {}
+ if root.tag.startswith("{"):
+ namespace["m"] = root.tag[1:].split("}", 1)[0]
+ prefix = "m:"
+ else:
+ prefix = ""
+
+ properties_element = root.find(f"{prefix}properties", namespace)
+ properties = {}
+ if properties_element is not None:
+ for child in properties_element:
+ properties[local_name(child.tag)] = (child.text or "").strip()
+
+ project_version = (root.findtext(f"{prefix}version", default="", namespaces=namespace) or "").strip()
+ if project_version:
+ properties.setdefault("project.version", project_version)
+ properties.setdefault("pom.version", project_version)
+ properties.setdefault("version", project_version)
+
+ def resolve(value: str) -> str:
+ resolved = value
+ for _ in range(MAX_PROPERTY_RESOLUTION_DEPTH):
+ updated = re.sub(
+ r"\$\{([^}]+)\}",
+ lambda match: properties.get(match.group(1), match.group(0)),
+ resolved,
+ )
+ if updated == resolved:
+ return updated
+ resolved = updated
+ raise ValueError(f"Could not fully resolve property expression: {value}")
+
+ managed_dependencies = []
+ dependency_path = f"{prefix}dependencyManagement/{prefix}dependencies/{prefix}dependency"
+ for dependency in root.findall(dependency_path, namespace):
+ group_id = dependency.findtext(f"{prefix}groupId", default="", namespaces=namespace).strip()
+ artifact_id = dependency.findtext(
+ f"{prefix}artifactId", default="", namespaces=namespace
+ ).strip()
+ version = dependency.findtext(f"{prefix}version", default="", namespaces=namespace).strip()
+ scope = dependency.findtext(f"{prefix}scope", default="", namespaces=namespace).strip() or None
+ if group_id and artifact_id and version:
+ managed_dependencies.append(
+ ManagedDependency(
+ group_id=group_id,
+ artifact_id=artifact_id,
+ version=resolve(version),
+ scope=scope,
+ )
+ )
+ return managed_dependencies
+
+
+def classify_dependency(dependency: ManagedDependency) -> str:
+ # Keep all org.springframework modules together so the generated spring
+ # section also doubles as the exclusion list for the spring-boot artifacts.
+ # The group_id check intentionally runs before the scope check so spring-test
+ # stays in the spring section even though its Maven scope is test.
+ if dependency.group_id == SPRING_FRAMEWORK_GROUP:
+ return SECTION_SPRING
+ if dependency.scope == SCOPE_TEST:
+ return SECTION_TEST
+ return SECTION_CORE
+
+
+def partition_dependencies(
+ managed_dependencies: list[ManagedDependency],
+) -> tuple[dict[str, list[ManagedDependency]], list[ManagedDependency]]:
+ """Split dependencyManagement entries into Bazel install sections and spring boot artifacts."""
+ install_sections = {
+ SECTION_CORE: [],
+ SECTION_SPRING: [],
+ SECTION_TEST: [],
+ }
+ spring_boot_dependencies = []
+
+ for dependency in managed_dependencies:
+ if dependency.group_id == SPRING_BOOT_GROUP:
+ spring_boot_dependencies.append(dependency)
+ continue
+ if dependency.coordinate in EXCLUDED_BAZEL_COORDINATES:
+ continue
+ install_sections[classify_dependency(dependency)].append(dependency)
+
+ if not spring_boot_dependencies:
+ raise ValueError("No org.springframework.boot dependencies found in dependencyManagement.")
+
+ return install_sections, spring_boot_dependencies
+
+
+def find_dependency(
+ dependencies: list[ManagedDependency], coordinate: str
+) -> ManagedDependency:
+ """Return the dependency matching a group/artifact coordinate or raise ValueError."""
+ for dependency in dependencies:
+ if dependency.coordinate == coordinate:
+ return dependency
+ raise ValueError(f"Missing expected dependencyManagement entry: {coordinate}")
+
+
+def render_generated_block(managed_dependencies: list[ManagedDependency]) -> str:
+ install_sections, spring_boot_dependencies = partition_dependencies(managed_dependencies)
+ spring_framework_dependencies = install_sections[SECTION_SPRING]
+ if not spring_framework_dependencies:
+ raise ValueError("No spring-framework dependencies found in dependencyManagement.")
+ spring_framework_exclusions = [
+ dependency.coordinate for dependency in spring_framework_dependencies
+ ]
+
+ lines = [
+ GENERATED_START,
+ "# Generated from pom.xml by scripts/sync_bazel_dependencies.py.",
+ "# Do not edit this block by hand; update pom.xml and rerun the script instead.",
+ "# Artifact order follows pom.xml dependencyManagement order within each section.",
+ "maven.install(",
+ " artifacts = [",
+ ]
+ for section, dependencies in install_sections.items():
+ lines.append(f" # ---- {section} ----")
+ for dependency in dependencies:
+ lines.append(f' "{dependency.bazel_artifact}",')
+ spring_boot_version = find_dependency(
+ spring_boot_dependencies, SPRING_BOOT_COORDINATE
+ ).version
+ spring_framework_version = find_dependency(
+ spring_framework_dependencies, SPRING_CORE_COORDINATE
+ ).version
+ lines.extend(
+ [
+ " ],",
+ " repositories = [",
+ ' "https://repo1.maven.org/maven2",',
+ ' "https://repo.spring.io/milestone",',
+ " ],",
+ " fetch_sources = False,",
+ ' lock_file = "//:maven_install.json",',
+ ")",
+ "",
+ f"# Spring Boot {spring_boot_version} artifacts declared with exclusions to keep",
+ f"# spring-framework pinned to {spring_framework_version}.",
+ "_SPRING_FRAMEWORK_EXCLUSIONS = [",
+ ]
+ )
+ for exclusion in spring_framework_exclusions:
+ lines.append(f' "{exclusion}",')
+ lines.extend(["]", ""])
+
+ for dependency in spring_boot_dependencies:
+ lines.extend(
+ [
+ "maven.artifact(",
+ f' artifact = "{dependency.artifact_id}",',
+ ' group = "org.springframework.boot",',
+ f' version = "{dependency.version}",',
+ " exclusions = _SPRING_FRAMEWORK_EXCLUSIONS,",
+ ")",
+ "",
+ ]
+ )
+
+ spring_boot_versions = {dependency.version for dependency in spring_boot_dependencies}
+ if len(spring_boot_versions) != 1:
+ raise ValueError(
+ "Spring Boot artifact versions diverged in dependencyManagement: "
+ + ", ".join(sorted(spring_boot_versions))
+ )
+
+ lines.append(GENERATED_END)
+ return "\n".join(lines) + "\n"
+
+
+def sync_module(module_path: Path, generated_block: str, check_only: bool) -> bool:
+ module_text = module_path.read_text()
+ pattern = re.compile(
+ rf"{re.escape(GENERATED_START)}\n.*?{re.escape(GENERATED_END)}\n?",
+ re.DOTALL,
+ )
+ match = pattern.search(module_text)
+ if not match:
+ raise ValueError(
+ f"Could not find generated block markers in {module_path}. "
+ f"Expected {GENERATED_START!r} and {GENERATED_END!r}."
+ )
+
+ updated_text = module_text[: match.start()] + generated_block + module_text[match.end() :]
+ changed = updated_text != module_text
+ if changed and not check_only:
+ module_path.write_text(updated_text)
+ return changed
+
+
+def main() -> int:
+ args = parse_args()
+ root = args.root.resolve()
+ pom_path = root / "pom.xml"
+ module_path = root / "MODULE.bazel"
+
+ managed_dependencies = parse_managed_dependencies(pom_path)
+ generated_block = render_generated_block(managed_dependencies)
+ changed = sync_module(module_path, generated_block, args.check)
+
+ if args.check and changed:
+ print(f"{module_path} is out of date. Run {Path(__file__).name} to regenerate it.")
+ return 1
+
+ if not args.check and changed:
+ print(f"Updated {module_path}")
+ elif not args.check:
+ print(f"{module_path} is already up to date")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())