diff --git a/generation/apply_versions.sh b/generation/apply_versions.sh index 99d1ab3a3109..826d6df2213b 100755 --- a/generation/apply_versions.sh +++ b/generation/apply_versions.sh @@ -25,7 +25,7 @@ if [[ "$column_name" == "released" ]]; then column_index=2 elif [[ "$column_name" == "current" ]]; then column_index=3 -elif "$column_name" != "current" ]]; then +elif [[ "$column_name" != "current" ]]; then echo "Error: column_name must be either 'released' or 'current'" exit 1 fi diff --git a/generation/update_owlbot_postprocessor_config.sh b/generation/update_owlbot_postprocessor_config.sh index a59d865bc2d9..275041f01305 100755 --- a/generation/update_owlbot_postprocessor_config.sh +++ b/generation/update_owlbot_postprocessor_config.sh @@ -6,7 +6,10 @@ set -e -for dir in $(find . -mindepth 2 -maxdepth 2 -name owlbot.py | grep -v 'java-common-protos/' | grep -v 'java-iam/' | grep -v 'java-showcase/' | sort | xargs dirname ); do +TARGET_MODULE="${1:-.}" + +for owlbot_script in $(find "$TARGET_MODULE" -name owlbot.py | grep -v 'java-common-protos/' | grep -v 'java-iam/' | grep -v 'java-showcase/' | sort); do + dir=$(dirname "$owlbot_script") pushd "$dir" # form a perl command to replace java.common_templates() invocation diff --git a/java-datastore/google-cloud-datastore/pom.xml b/java-datastore/google-cloud-datastore/pom.xml index 587678c50cb3..0a45f53946a8 100644 --- a/java-datastore/google-cloud-datastore/pom.xml +++ b/java-datastore/google-cloud-datastore/pom.xml @@ -208,12 +208,12 @@ com.google.cloud google-cloud-monitoring - 3.91.0 + 3.93.0-SNAPSHOT com.google.api.grpc proto-google-cloud-monitoring-v3 - 3.91.0 + 3.93.0-SNAPSHOT io.opentelemetry diff --git a/java-maps-mapmanagement/google-maps-mapmanagement-bom/pom.xml b/java-maps-mapmanagement/google-maps-mapmanagement-bom/pom.xml index ee5a696206b0..e55d8057938d 100644 --- a/java-maps-mapmanagement/google-maps-mapmanagement-bom/pom.xml +++ b/java-maps-mapmanagement/google-maps-mapmanagement-bom/pom.xml @@ -8,7 +8,7 @@ com.google.cloud google-cloud-pom-parent - 1.85.0 + 1.86.0-SNAPSHOT ../../google-cloud-pom-parent/pom.xml diff --git a/java-maps-mapmanagement/owlbot.py b/java-maps-mapmanagement/owlbot.py index 6da47954339a..fbb37df70f1a 100644 --- a/java-maps-mapmanagement/owlbot.py +++ b/java-maps-mapmanagement/owlbot.py @@ -33,6 +33,5 @@ "java.header", "license-checks.xml", "renovate.json", - ".gitignore", - ], -) \ No newline at end of file + ".gitignore" +]) \ No newline at end of file diff --git a/monorepo-migration/.gitignore b/monorepo-migration/.gitignore new file mode 100644 index 000000000000..ae0b0d95a4b2 --- /dev/null +++ b/monorepo-migration/.gitignore @@ -0,0 +1,3 @@ +.git-filter-repo/ +__pycache__/ +*.pyc diff --git a/monorepo-migration/migrate-bigtable.sh b/monorepo-migration/migrate-bigtable.sh new file mode 100755 index 000000000000..b499657bf811 --- /dev/null +++ b/monorepo-migration/migrate-bigtable.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +# Exit on error +set -e + +# Get absolute paths +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MONOREPO_ROOT="$(dirname "$SCRIPT_DIR")" + +echo "========================================================" +echo " Staging java-bigtable migration" +echo "========================================================" + +# 1. Configure environment for the base migrate.sh script +export SOURCE_REPO_URL="https://github.com/googleapis/java-bigtable" +export MIGRATION_HEAD_BRANCH="main" +export SQUASH_COMMITS="false" +export CODEOWNER="@googleapis/bigtable-team" +export BOM_SUBSTITUTIONS="gapic-libraries-bom:google-cloud-monitoring-bom" +export PRE_INSTALL_DEPS="java-monitoring/google-cloud-monitoring-bom" + +# 2. Execute the central migration script +"${SCRIPT_DIR}/migrate.sh" + +echo "" +echo "========================================================" +echo "Migration staged successfully!" +echo "Results are available in the isolated clone:" +echo " migration-work/google-cloud-java-target" +echo "Current Branch: migrate-java-bigtable" +echo "Next Steps: cd migration-work/google-cloud-java-target && mvn clean install -DskipTests" +echo "========================================================" diff --git a/monorepo-migration/migrate-firestore.sh b/monorepo-migration/migrate-firestore.sh new file mode 100755 index 000000000000..babdce93fd58 --- /dev/null +++ b/monorepo-migration/migrate-firestore.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +# Exit on error +set -e + +# Get absolute paths +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MONOREPO_ROOT="$(dirname "$SCRIPT_DIR")" + +echo "========================================================" +echo " Staging java-firestore migration" +echo "========================================================" + +# 1. Configure environment for the base migrate.sh script +export SOURCE_REPO_URL="https://github.com/googleapis/java-firestore" +export MIGRATION_HEAD_BRANCH="main" +export SQUASH_COMMITS="false" +export CODEOWNER="@googleapis/firestore-team" + +# 2. Execute the central migration script +"${SCRIPT_DIR}/migrate.sh" + +echo "" +echo "========================================================" +echo "Migration staged successfully!" +echo "Results are available in the isolated clone:" +echo " migration-work/google-cloud-java-target" +echo "Current Branch: migrate-java-firestore" +echo "Next Steps: cd migration-work/google-cloud-java-target && mvn clean install -DskipTests" +echo "========================================================" diff --git a/monorepo-migration/migrate-pubsub.sh b/monorepo-migration/migrate-pubsub.sh new file mode 100755 index 000000000000..71c88351ff68 --- /dev/null +++ b/monorepo-migration/migrate-pubsub.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +# Exit on error +set -e + +# Get absolute paths +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MONOREPO_ROOT="$(dirname "$SCRIPT_DIR")" + +echo "========================================================" +echo " Staging java-pubsub migration" +echo "========================================================" + +# 1. Configure environment for the base migrate.sh script +export SOURCE_REPO_URL="https://github.com/googleapis/java-pubsub" +export MIGRATION_HEAD_BRANCH="main" +export SQUASH_COMMITS="false" +export CODEOWNER="@googleapis/pubsub-team" + +# 2. Execute the central migration script +# This performs git read-tree, POM modernization, workflow transformation, and generation config updates. +# Note: migrate.sh works in an isolated sibling clone to avoid polluting the active workspace. +"${SCRIPT_DIR}/migrate.sh" + +echo "" +echo "========================================================" +echo "Migration staged successfully!" +echo "Results are available in the isolated clone:" +echo " ../../migration-work/google-cloud-java-target" +echo "Current Branch: migrate-java-pubsub" +echo "Next Steps: cd ../../migration-work/google-cloud-java-target && mvn clean install -DskipTests" +echo "========================================================" diff --git a/monorepo-migration/migrate.sh b/monorepo-migration/migrate.sh index 93b4b2c788c4..b4c70a5cdebf 100755 --- a/monorepo-migration/migrate.sh +++ b/monorepo-migration/migrate.sh @@ -17,6 +17,26 @@ # Exit on error set -e +# Get absolute path to the script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MONOREPO_ROOT="$(dirname "$SCRIPT_DIR")" + +# Ensure git-filter-repo is available in PATH +if ! command -v git-filter-repo >/dev/null 2>&1; then + LOCAL_FILTER_REPO_DIR="${SCRIPT_DIR}/.git-filter-repo" + LOCAL_FILTER_REPO="${LOCAL_FILTER_REPO_DIR}/git-filter-repo" + + if [ ! -f "$LOCAL_FILTER_REPO" ]; then + echo "git-filter-repo not found locally or in PATH. Downloading version v2.45.0..." + mkdir -p "$LOCAL_FILTER_REPO_DIR" + curl -sSL -o "$LOCAL_FILTER_REPO" "https://raw.githubusercontent.com/newren/git-filter-repo/v2.45.0/git-filter-repo" + chmod +x "$LOCAL_FILTER_REPO" + fi + + echo "Injecting local .git-filter-repo into PATH..." + export PATH="${LOCAL_FILTER_REPO_DIR}:$PATH" +fi + # Function to check if a command exists check_command() { if ! command -v "$1" >/dev/null 2>&1; then @@ -29,6 +49,7 @@ check_command() { check_command git check_command python3 check_command mvn +check_command git-filter-repo # Configuration MONOREPO_URL="https://github.com/googleapis/google-cloud-java" @@ -42,13 +63,12 @@ CODEOWNER="${CODEOWNER:-}" SOURCE_REPO_NAME="${SOURCE_REPO_URL##*/}" MONOREPO_NAME="${MONOREPO_URL##*/}" -# Use a temporary working directory sibling to the current monorepo -WORKING_DIR="../../migration-work" +# Use a temporary working directory sibling to the current monorepo, anchored to script location +WORKING_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)/migration-work" SOURCE_DIR="$WORKING_DIR/$SOURCE_REPO_NAME-source" TARGET_DIR="$WORKING_DIR/$MONOREPO_NAME-target" -# Get absolute path to the transformation script before any cd -TRANSFORM_SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +TRANSFORM_SCRIPT_DIR="${SCRIPT_DIR}" TRANSFORM_SCRIPT="$TRANSFORM_SCRIPT_DIR/transform_workflow.py" MODERNIZE_POM_SCRIPT="$TRANSFORM_SCRIPT_DIR/modernize_pom.py" UPDATE_ROOT_POM_SCRIPT="$TRANSFORM_SCRIPT_DIR/update_root_pom.py" @@ -56,6 +76,9 @@ FIX_COPYRIGHT_SCRIPT="$TRANSFORM_SCRIPT_DIR/fix_copyright_headers.py" UPDATE_GENERATION_CONFIG_SCRIPT="$TRANSFORM_SCRIPT_DIR/update_generation_config.py" UPDATE_OWLBOT_HERMETIC_SCRIPT="$TRANSFORM_SCRIPT_DIR/update_owlbot_hermetic.py" TRANSFORM_OWLBOT_SCRIPT="$TRANSFORM_SCRIPT_DIR/update_owlbot.py" +UPDATE_LINTER_EXCLUSIONS_SCRIPT="$TRANSFORM_SCRIPT_DIR/update_linter_exclusions.py" +UPDATE_CI_FILTERS_SCRIPT="$TRANSFORM_SCRIPT_DIR/update_ci_filters.py" +UPDATE_CHANGES_FILTERS_SCRIPT="$TRANSFORM_SCRIPT_DIR/update_changes_filters.py" # Track number of commits made by this script COMMIT_COUNT=0 @@ -65,23 +88,28 @@ echo "Starting migration using git read-tree with isolated clones..." # 0. Create working directory mkdir -p "$WORKING_DIR" -MIGRATION_HEAD_BRANCH="${MIGRATION_HEAD_BRANCH:-main}" +MIGRATION_HEAD_BRANCH="main" echo "Basing migration branch on: ${MIGRATION_HEAD_BRANCH}" # 1. Clone the source repository -if [ ! -d "$SOURCE_DIR" ]; then - echo "Cloning source repo: $SOURCE_REPO_URL into $SOURCE_DIR" - git clone "$SOURCE_REPO_URL" "$SOURCE_DIR" +if [ "${SKIP_SOURCE_UPDATE:-false}" = "true" ] && [ -d "$SOURCE_DIR" ]; then + echo "Skipping source repository update and reusing existing directory..." else - echo "Source directory $SOURCE_DIR already exists. Ensuring it is clean and up-to-date..." + echo "Ensuring clean slate for history filters by removing existing source directory..." + rm -rf "$SOURCE_DIR" + echo "Cloning source repo: $SOURCE_REPO_URL into $SOURCE_DIR" + git clone --branch main --single-branch "$SOURCE_REPO_URL" "$SOURCE_DIR" +fi + +if [ "${SKIP_SOURCE_UPDATE:-false}" != "true" ]; then + # 1.2 Rewrite history of the split repo to move files to the target subdirectory + echo "Moving files to destination path: ${SOURCE_REPO_NAME} in history..." cd "$SOURCE_DIR" - git fetch origin - git checkout -f "main" - git reset --hard origin/main - git clean -fd + git filter-repo --to-subdirectory-filter "${SOURCE_REPO_NAME}" --force cd - > /dev/null fi + # 1.5 Extract CODEOWNERS from source repository as default if [ -z "$CODEOWNER" ]; then echo "Attempting to find default CODEOWNER from source repository..." @@ -114,28 +142,30 @@ fi # 2. Clone the target monorepo (the "isolated clone") if [ ! -d "$TARGET_DIR" ]; then echo "Cloning target monorepo: $MONOREPO_URL into $TARGET_DIR" - git clone "$MONOREPO_URL" "$TARGET_DIR" - git checkout -f "${MIGRATION_HEAD_BRANCH}" - git reset --hard origin/${MIGRATION_HEAD_BRANCH} + git clone --branch main --single-branch --depth 1 "$MONOREPO_URL" "$TARGET_DIR" else echo "Target directory $TARGET_DIR already exists. Ensuring it is clean and up-to-date..." cd "$TARGET_DIR" - git fetch origin - git checkout -f "${MIGRATION_HEAD_BRANCH}" - git reset --hard origin/${MIGRATION_HEAD_BRANCH} + git fetch --depth 1 origin main + git checkout -f "main" + git reset --hard origin/main git clean -fd cd - > /dev/null fi cd "$TARGET_DIR" +if [ "$(pwd)" = "$MONOREPO_ROOT" ]; then + echo "CRITICAL ERROR: Script failed to change directory or attempted to run destructive Git operations inside the active workspace!" >&2 + exit 1 +fi # Ensure we are on a clean main branch in the target clone echo "Ensuring clean state in target monorepo..." -git fetch origin +git fetch --depth 1 origin main git reset --hard HEAD git clean -fd -git checkout -f "${MIGRATION_HEAD_BRANCH}" -git reset --hard origin/${MIGRATION_HEAD_BRANCH} +git checkout -f "main" +git reset --hard origin/main git clean -fdx # Check if the repository is already migrated @@ -165,13 +195,118 @@ git remote add "$SOURCE_REPO_NAME" "../$SOURCE_REPO_NAME-source" echo "Fetching $SOURCE_REPO_NAME..." git fetch "$SOURCE_REPO_NAME" -# 5. Merge the histories using 'ours' strategy to keep monorepo content -echo "Merging histories (strategy: ours)..." -git merge --allow-unrelated-histories --no-ff "$SOURCE_REPO_NAME/main" -s ours --no-commit -m "chore($SOURCE_REPO_NAME): migrate $SOURCE_REPO_NAME into monorepo" +# 5. Merge the histories to pull all rewritten files into their subdirectory directly +echo "Merging histories..." +git merge --allow-unrelated-histories --no-edit --no-gpg-sign "$SOURCE_REPO_NAME/main" -m "chore($SOURCE_REPO_NAME): migrate $SOURCE_REPO_NAME into monorepo" +COMMIT_COUNT=$((COMMIT_COUNT + 1)) + +# 6.4b Migrate GraalVM Native presubmit config if present +if [ -f "$SOURCE_REPO_NAME/.kokoro/presubmit/graalvm-native-a.cfg" ]; then + echo "Migrating graalvm-native-a.cfg to monorepo root .kokoro/presubmit/${SOURCE_REPO_NAME#java-}-graalvm-native-presubmit.cfg..." + mkdir -p .kokoro/presubmit + sed -e 's/value: "graalvm"/value: "graalvm-single"/' \ + "$SOURCE_REPO_NAME/.kokoro/presubmit/graalvm-native-a.cfg" > ".kokoro/presubmit/${SOURCE_REPO_NAME#java-}-graalvm-native-presubmit.cfg" + + # Append BUILD_SUBDIR + cat <> ".kokoro/presubmit/${SOURCE_REPO_NAME#java-}-graalvm-native-presubmit.cfg" -# 6. Read the tree from the source repo into the desired subdirectory -echo "Reading tree into prefix $SOURCE_REPO_NAME/..." -git read-tree --prefix="$SOURCE_REPO_NAME/" -u "$SOURCE_REPO_NAME/main" +env_vars: { + key: "BUILD_SUBDIR" + value: "${SOURCE_REPO_NAME}" +} +EOF + git add ".kokoro/presubmit/${SOURCE_REPO_NAME#java-}-graalvm-native-presubmit.cfg" + git commit -n --no-gpg-sign -m "chore($SOURCE_REPO_NAME): migrate GraalVM Native presubmit config" + COMMIT_COUNT=$((COMMIT_COUNT + 1)) +fi + +# 6.4c Migrate Integration presubmit configurations if present +if ls "$SOURCE_REPO_NAME/.kokoro/presubmit/integration"*.cfg >/dev/null 2>&1; then + mkdir -p .kokoro/presubmit + for cfg_file in "$SOURCE_REPO_NAME/.kokoro/presubmit/integration"*.cfg; do + if [ -f "$cfg_file" ]; then + filename=$(basename "$cfg_file") + new_filename="${filename/integration/${SOURCE_REPO_NAME#java-}-integration}" + target_cfg=".kokoro/presubmit/${new_filename}" + + echo "Migrating and adapting $filename to $target_cfg..." + sed -e 's/value: "integration"/value: "integration-single"/' \ + -e 's/java8/java11/' \ + "$cfg_file" > "$target_cfg" + + # Append BUILD_SUBDIR + cat <> "$target_cfg" + +env_vars: { + key: "BUILD_SUBDIR" + value: "${SOURCE_REPO_NAME}" +} +EOF + git add "$target_cfg" + fi + done + git commit -n --no-gpg-sign -m "chore($SOURCE_REPO_NAME): migrate Integration presubmit configurations" + COMMIT_COUNT=$((COMMIT_COUNT + 1)) +fi + +# 6.4c(2) Migrate custom conformance execution script if present +if [ -f "$SOURCE_REPO_NAME/.kokoro/conformance.sh" ]; then + echo "Migrating conformance.sh to monorepo root .kokoro/${SOURCE_REPO_NAME#java-}-conformance.sh..." + mkdir -p .kokoro + cp "$SOURCE_REPO_NAME/.kokoro/conformance.sh" ".kokoro/${SOURCE_REPO_NAME#java-}-conformance.sh" + + echo "Adapting conformance script paths and build scopes for monorepo root..." + PL_MODULES="${SOURCE_REPO_NAME}" + # Also include any child BOM modules to ensure snapshots install to local .m2 cache for test utilities + for bom_dir in "${SOURCE_REPO_NAME}"/*-bom; do + if [ -d "$bom_dir" ]; then + PL_MODULES="${PL_MODULES},${bom_dir}" + fi + done + + if [ -n "${PRE_INSTALL_DEPS}" ]; then + PL_MODULES="${PL_MODULES},${PRE_INSTALL_DEPS}" + fi + + echo "Injecting SDK Platform pre-installation block into conformance script..." + sed -i.bak 's|# attempt to install 3 times|echo "Pre-installing SDK Platform toolchain and submodules..."\npushd sdk-platform-java\nretry_with_backoff 3 10 mvn install -B -ntp -DskipTests=true -Dclirr.skip=true -Denforcer.skip=true -Dcheckstyle.skip=true -Dmaven.javadoc.skip=true -T 1C\npopd\n\n# attempt to install 3 times|' ".kokoro/${SOURCE_REPO_NAME#java-}-conformance.sh" + + sed -i.bak "s|mvn install -B -V -ntp|mvn install -pl ${PL_MODULES} -am -B -V -ntp|" ".kokoro/${SOURCE_REPO_NAME#java-}-conformance.sh" + sed -i.bak "s|cd test-proxy|cd ${SOURCE_REPO_NAME}/test-proxy|" ".kokoro/${SOURCE_REPO_NAME#java-}-conformance.sh" + sed -i.bak "s|-jar test-proxy/target/|-jar ${SOURCE_REPO_NAME}/test-proxy/target/|" ".kokoro/${SOURCE_REPO_NAME#java-}-conformance.sh" + sed -i.bak "s|kill \${proxyPID}|kill \${proxyPID} \&\& sleep 5|" ".kokoro/${SOURCE_REPO_NAME#java-}-conformance.sh" + sed -i.bak "s|../../test-proxy/known_failures.txt|../../${SOURCE_REPO_NAME}/test-proxy/known_failures.txt|" ".kokoro/${SOURCE_REPO_NAME#java-}-conformance.sh" + rm -f ".kokoro/${SOURCE_REPO_NAME#java-}-conformance.sh.bak" + + if [ -f "${SOURCE_REPO_NAME}/test-proxy/pom.xml" ]; then + echo "Fixing protoc-gen-grpc-java version in test-proxy/pom.xml for Apple Silicon (osx-aarch_64) support..." + sed -i.bak "s|1.24.0:exe:\${os.detected.classifier}|1.62.2:exe:\${os.detected.classifier}|" "${SOURCE_REPO_NAME}/test-proxy/pom.xml" + rm -f "${SOURCE_REPO_NAME}/test-proxy/pom.xml.bak" + + echo "Patching monorepo core .kokoro/build.sh inside target clone to exclude test-proxy from changed-modules linting..." + sed -i.bak 's|unmanaged-dependency-check" \]\] \&\& \\|unmanaged-dependency-check" \]\] \&\& \\\n \[\[ "$(basename "${dir}")" != \*"test-proxy"\* \]\] \&\& \\|' ".kokoro/build.sh" + rm -f ".kokoro/build.sh.bak" + + git add "${SOURCE_REPO_NAME}/test-proxy/pom.xml" ".kokoro/build.sh" + git commit -n --no-gpg-sign -m "chore($SOURCE_REPO_NAME): fix test-proxy compilation and exclude from linter changes matrix" + COMMIT_COUNT=$((COMMIT_COUNT + 1)) + fi + + git add ".kokoro/${SOURCE_REPO_NAME#java-}-conformance.sh" + git commit -n --no-gpg-sign -m "chore($SOURCE_REPO_NAME): migrate and adapt conformance execution script" + COMMIT_COUNT=$((COMMIT_COUNT + 1)) +fi + +# 6.4d Update repo and repo_short in .repo-metadata.json +REPO_METADATA="$SOURCE_REPO_NAME/.repo-metadata.json" +if [ -f "$REPO_METADATA" ]; then + echo "Updating repo and repo_short in $REPO_METADATA..." + python3 -c "import json; f = '$REPO_METADATA'; d = json.load(open(f)); d['repo'] = 'googleapis/google-cloud-java'; json.dump(d, open(f, 'w'), indent=2); open(f, 'a').write('\n')" + + git add "$REPO_METADATA" + git commit -n --no-gpg-sign -m "chore($SOURCE_REPO_NAME): update .repo-metadata.json" + COMMIT_COUNT=$((COMMIT_COUNT + 1)) +fi # 6.5 Remove common files from the root of the migrated library echo "Removing common files from the root of $SOURCE_REPO_NAME/..." @@ -180,16 +315,14 @@ rm -f "$SOURCE_REPO_NAME/renovate.json" rm -f "$SOURCE_REPO_NAME/LICENSE" rm -f "$SOURCE_REPO_NAME/java.header" rm -rf "$SOURCE_REPO_NAME/.kokoro" -# rm -rf "$SOURCE_REPO_NAME/.kokoro/continuous" "$SOURCE_REPO_NAME/.kokoro/nightly" "$SOURCE_REPO_NAME/.kokoro/presubmit" rm -f "$SOURCE_REPO_NAME/codecov.yaml" rm -f "$SOURCE_REPO_NAME/synth.metadata" rm -f "$SOURCE_REPO_NAME/license-checks.xml" -find "$SOURCE_REPO_NAME" -maxdepth 1 -name "*.md" ! -name "CHANGELOG.md" ! -name "README.md" -delete +find "$SOURCE_REPO_NAME" -maxdepth 1 -name "*.md" ! -name "CHANGELOG.md" ! -name "README.md" ! -name "GEMINI.md" ! -name "DEVELOPMENT.md" -delete -# 7. Commit the migration -echo "Committing migration..." +echo "Committing removal of common files..." git add "$SOURCE_REPO_NAME" -git commit -n --no-gpg-sign -m "chore($SOURCE_REPO_NAME): migrate $SOURCE_REPO_NAME into monorepo" +git commit -n --no-gpg-sign -m "chore($SOURCE_REPO_NAME): remove common files from module root" COMMIT_COUNT=$((COMMIT_COUNT + 1)) # 7.1 Update CODEOWNERS @@ -227,7 +360,7 @@ if [ -d "$SOURCE_REPO_NAME/.github/workflows" ]; then case "$filename" in "hermetic_library_generation.yaml" | "update_generation_config.yaml" | \ "approve-readme.yaml" | "auto-release.yaml" | "renovate_config_check.yaml" | \ - "samples.yaml" | "unmanaged_dependency_check.yaml") + "samples.yaml" | "unmanaged_dependency_check.yaml" | "unmanaged-dependency-check.yaml") echo "Skipping redundant workflow: $filename" continue ;; @@ -238,6 +371,8 @@ if [ -d "$SOURCE_REPO_NAME/.github/workflows" ]; then echo "Migrating and adapting $filename to $target_path" python3 "$TRANSFORM_SCRIPT" "$SOURCE_REPO_NAME" < "$workflow" > "$target_path" + sed -i.bak "s|java-version: 8|java-version: 11|" "$target_path" + rm -f "${target_path}.bak" fi done @@ -284,8 +419,8 @@ fi # 7.8 Migrate .OwlBot-hermetic.yaml echo "Migrating .OwlBot-hermetic.yaml..." -if [ -f "$SOURCE_DIR/.github/.OwlBot-hermetic.yaml" ]; then - SOURCE_OWLBOT="$SOURCE_DIR/.github/.OwlBot-hermetic.yaml" +if [ -f "$SOURCE_DIR/$SOURCE_REPO_NAME/.github/.OwlBot-hermetic.yaml" ]; then + SOURCE_OWLBOT="$SOURCE_DIR/$SOURCE_REPO_NAME/.github/.OwlBot-hermetic.yaml" else SOURCE_OWLBOT="" fi @@ -303,10 +438,10 @@ fi # 7.8b Migrate owlbot.py echo "Migrating owlbot.py..." -if [ -f "$SOURCE_DIR/owlbot.py" ]; then +if [ -f "$SOURCE_DIR/$SOURCE_REPO_NAME/owlbot.py" ]; then TARGET_OWLBOT="$SOURCE_REPO_NAME/owlbot.py" - python3 "$TRANSFORM_OWLBOT_SCRIPT" "$TARGET_OWLBOT" "$SOURCE_DIR/owlbot.py" + python3 "$TRANSFORM_OWLBOT_SCRIPT" "$TARGET_OWLBOT" "$SOURCE_DIR/$SOURCE_REPO_NAME/owlbot.py" echo "Committing owlbot.py migration..." git add "$TARGET_OWLBOT" @@ -348,10 +483,54 @@ while read -r bom_pom; do COMMIT_COUNT=$((COMMIT_COUNT + 1)) done < <(find "$SOURCE_REPO_NAME" -name "pom.xml" | grep "\-bom/pom.xml" | grep -v "samples") +# 7.12b Align all version markers across the monorepo +echo "Aligning all version markers using apply_versions.sh..." +bash generation/apply_versions.sh versions.txt current + +# 7.12c Sync all owlbot.py formatting +echo "Syncing all owlbot.py formatting..." +bash generation/update_owlbot_postprocessor_config.sh "$SOURCE_REPO_NAME" || true + +git add -u +git commit -n --no-gpg-sign -m "chore($SOURCE_REPO_NAME): align versions and format owlbot configurations" +COMMIT_COUNT=$((COMMIT_COUNT + 1)) + +# 7.8c Exempt module from global integration testing matrix +echo "Exempting $SOURCE_REPO_NAME from global integration testing matrix..." +sed -i.bak "s/'java-storage-nio'/'java-storage-nio'\n '${SOURCE_REPO_NAME}'/" ".kokoro/common.sh" +python3 "$UPDATE_CI_FILTERS_SCRIPT" ".github/workflows/ci.yaml" "$SOURCE_REPO_NAME" +python3 "$UPDATE_CHANGES_FILTERS_SCRIPT" ".github/workflows/ci.yaml" "$SOURCE_REPO_NAME" + +if [ -n "${PRE_INSTALL_DEPS}" ]; then + echo "Injecting explicit dependencies into always_install_deps list inside .kokoro/common.sh..." + for dep in $(echo "${PRE_INSTALL_DEPS}" | tr ',' ' '); do + sed -i.bak "s|always_install_deps_list=(|always_install_deps_list=(\n '${dep}'|" ".kokoro/common.sh" + done + rm -f .kokoro/common.sh.bak +fi + +echo "Committing common.sh and ci.yaml updates..." +git add .kokoro/common.sh .github/workflows/ci.yaml +git commit -n --no-gpg-sign -m "chore($SOURCE_REPO_NAME): exempt from global integration testing matrix" +COMMIT_COUNT=$((COMMIT_COUNT + 1)) + +# 7.9b Conditionally skip version check if unmanaged dependencies exist +echo "Verifying non-release-please version compliance..." +if ! (./generation/check_non_release_please_versions.sh > /dev/null 2>&1); then + echo "Unmanaged dependency versions detected. Injecting $SOURCE_REPO_NAME exclusion into check_non_release_please_versions.sh..." + python3 "$UPDATE_LINTER_EXCLUSIONS_SCRIPT" "generation/check_non_release_please_versions.sh" "$SOURCE_REPO_NAME" + + echo "Committing linter adjustment..." + git add generation/check_non_release_please_versions.sh + git commit -n --no-gpg-sign -m "chore($SOURCE_REPO_NAME): skip version check for $SOURCE_REPO_NAME" + COMMIT_COUNT=$((COMMIT_COUNT + 1)) +else + echo "All dependency versions fully managed. No linter adjustments required." +fi + # 7.11 Verify compilation echo "Verifying compilation..." BUILD_SUBDIR="${SOURCE_REPO_NAME}" JOB_TYPE=test .kokoro/build.sh -# (cd "$SOURCE_REPO_NAME" && mvn compile -DskipTests -T 1C) # 7.13 Squash commits if [ "${SQUASH_COMMITS:-false}" = "true" ]; then @@ -376,8 +555,10 @@ if [ "${SQUASH_COMMITS:-false}" = "true" ]; then fi # 8. Cleanup -echo "Cleaning up temporary source clone..." -rm -rf "$SOURCE_DIR" +if [ "${SKIP_SOURCE_UPDATE:-false}" != "true" ]; then + echo "Cleaning up temporary source clone..." + rm -rf "$SOURCE_DIR" +fi echo "Migration complete!" echo "The migrated codebase is available in: $TARGET_DIR" diff --git a/monorepo-migration/migrate_issues.sh b/monorepo-migration/migrate_issues.sh new file mode 100755 index 000000000000..79604bc46a3a --- /dev/null +++ b/monorepo-migration/migrate_issues.sh @@ -0,0 +1,129 @@ +#!/bin/bash +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +# migrate_issues.sh - Transfer open GitHub issues from one repo to another using gh CLI. + +set -e + +usage() { + echo "Usage: $0 [--label ' in line: in_dependency = False current_dependency_lines.append(line) - if current_artifact_id == 'google-cloud-shared-dependencies': + if current_artifact_id == 'google-cloud-shared-dependencies' and '-deps-bom' not in file_path: + continue + + if current_artifact_id in bom_substitutions: + target_bom = bom_substitutions[current_artifact_id] + target_version = monorepo_versions.get(target_bom, '0.0.1-SNAPSHOT') + target_marker = target_bom.replace('-bom', '') + indent = " " + current_dependency_lines = [ + f"{indent}\n", + f"{indent} com.google.cloud\n", + f"{indent} {target_bom}\n", + f"{indent} {target_version}\n", + f"{indent} pom\n", + f"{indent} import\n", + f"{indent}\n" + ] + new_lines.extend(current_dependency_lines) continue # Preservation logic: @@ -196,22 +224,29 @@ def modernize_pom(file_path, parent_version, source_repo_name=None, parent_artif # 3. Is com.google.cloud group AND artifactId starts with google-cloud- AND has a version tag is_external = current_group_id and not current_group_id.startswith('com.google') is_google_cloud_lib = current_group_id == 'com.google.cloud' and current_artifact_id and current_artifact_id.startswith('google-cloud-') + is_truth = current_group_id and current_group_id.startswith('com.google.truth') - if should_preserve or (is_external and has_version) or (is_google_cloud_lib and has_version): + if should_preserve or (is_external and has_version) or (is_google_cloud_lib and has_version) or (is_truth and has_version): new_lines.extend(current_dependency_lines) continue if in_dependency: - if '' in line: - match = re.search(r'(.*?)', line) - if match: - current_group_id = match.group(1).strip() - if '' in line: - match = re.search(r'(.*?)', line) - if match: - current_artifact_id = match.group(1).strip() - if '' in line: - has_version = True + if '' in line: + in_exclusions = True + if '' in line: + in_exclusions = False + + if not in_exclusions: + if '' in line: + match = re.search(r'(.*?)', line) + if match: + current_group_id = match.group(1).strip() + if '' in line: + match = re.search(r'(.*?)', line) + if match: + current_artifact_id = match.group(1).strip() + if '' in line: + has_version = True if monorepo_versions and current_artifact_id and current_artifact_id in monorepo_versions: new_version = monorepo_versions[current_artifact_id] @@ -247,6 +282,47 @@ def modernize_pom(file_path, parent_version, source_repo_name=None, parent_artif new_lines.append(line) + # Add bulkTests profile if it does not already exist (only for the library root pom.xml) + if is_root_pom: + bulk_tests_pattern = r'\s*bulkTests\s*' + if not re.search(bulk_tests_pattern, "".join(new_lines)): + in_profiles = False + inserted = False + for i in range(len(new_lines)): + if '' in new_lines[i]: + in_profiles = True + if '' in new_lines[i] and in_profiles: + indent = " " + profile_block = ( + f"{indent}\n" + f"{indent} bulkTests\n" + f"{indent} \n" + f"{indent} true\n" + f"{indent} \n" + f"{indent}\n" + ) + new_lines.insert(i, profile_block) + inserted = True + break + + # If no section existed, create one before + if not inserted: + for i in range(len(new_lines) - 1, -1, -1): + if '' in new_lines[i]: + indent = " " + profile_block = ( + f"\n{indent}\n" + f"{indent} \n" + f"{indent} bulkTests\n" + f"{indent} \n" + f"{indent} true\n" + f"{indent} \n" + f"{indent} \n" + f"{indent}\n" + ) + new_lines.insert(i, profile_block) + break + with open(file_path, 'w') as f: # Clean up double empty lines potentially introduced by pruning content = "".join(new_lines) diff --git a/monorepo-migration/transform_workflow.py b/monorepo-migration/transform_workflow.py index fba791634ebe..af8aa743aa08 100644 --- a/monorepo-migration/transform_workflow.py +++ b/monorepo-migration/transform_workflow.py @@ -33,11 +33,34 @@ def transform(content, lib_name): library: - '{lib_name}/**'""" + clirr_template = f""" clirr: + needs: filter + if: ${{{{ needs.filter.outputs.library == 'true' }}}} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 11 + - run: java -version + - run: .kokoro/build.sh + env: + JOB_TYPE: clirr + BUILD_SUBDIR: {lib_name}""" + in_jobs = False skip_current_job = False current_job_is_windows = False + current_job_is_lint = False + + skip_lines_count = 0 for line in lines: + if skip_lines_count > 0: + skip_lines_count -= 1 + continue + if line.startswith('name:') and not in_jobs: name_match = re.match(r'^name:\s*(.*)', line) if name_match: @@ -62,11 +85,12 @@ def transform(content, lib_name): if job_match: job_name = job_match.group(1) current_job_is_windows = False # Reset for new job + current_job_is_lint = (job_name == 'lint') + skip_current_job = False if job_name == 'clirr': skip_current_job = True + new_lines.extend(clirr_template.splitlines()) continue - else: - skip_current_job = False if job_name != 'filter': new_lines.append(line) @@ -84,10 +108,29 @@ def transform(content, lib_name): new_lines.append(" run: git config --system core.longpaths true") continue + if 'name: Support longpaths' in line and current_job_is_windows: + skip_lines_count = 1 + continue + + if '- uses: actions/checkout' in line and current_job_is_lint: + new_lines.append(" - uses: actions/checkout@v4") + new_lines.append(" with:") + new_lines.append(" fetch-depth: 0") + continue + + if 'JOB_TYPE: lint' in line and current_job_is_lint: + new_lines.append(line) + new_lines.append(" HEAD_SHA: ${{ github.event.pull_request.head.sha }}") + new_lines.append(" BASE_SHA: ${{ github.event.pull_request.base.sha }}") + continue + if 'run: echo "SUREFIRE_JVM_OPT=' in line and '!java17' not in line: line = line.replace('" >> $GITHUB_ENV', ' -P !java17" >> $GITHUB_ENV') if 'build.bat' in line: line = line.replace('build.bat', 'build.sh') + if '.kokoro/conformance.sh' in line: + short_name = lib_name.replace('java-', '') + line = line.replace('.kokoro/conformance.sh', f'.kokoro/{short_name}-conformance.sh') new_lines.append(line) return "\n".join(new_lines) diff --git a/monorepo-migration/update_changes_filters.py b/monorepo-migration/update_changes_filters.py new file mode 100644 index 000000000000..5b00c0bde8cd --- /dev/null +++ b/monorepo-migration/update_changes_filters.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 sys + +def update_changes_filters(ci_yaml, lib_name): + with open(ci_yaml, 'r') as f: + content = f.read() + + target = ' split-units:' + new_filter = f''' {lib_name}: + - '{lib_name}/**' + - 'google-auth-library-java/**/*.java' + - 'google-auth-library-java/**/pom.xml' + - 'sdk-platform-java/**/*.java' + - 'sdk-platform-java/java-shared-dependencies/**/pom.xml' + - 'sdk-platform-java/gapic-generator-java-pom-parent/pom.xml' +''' + if new_filter not in content: + content = content.replace(target, new_filter + target) + with open(ci_yaml, 'w') as f: + f.write(content) + print(f"Successfully added {lib_name} to changes job in {ci_yaml}.") + else: + print(f"{lib_name} is already configured in {ci_yaml}.") + +if __name__ == '__main__': + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + update_changes_filters(sys.argv[1], sys.argv[2]) diff --git a/monorepo-migration/update_ci_filters.py b/monorepo-migration/update_ci_filters.py new file mode 100644 index 000000000000..2a9704cdf838 --- /dev/null +++ b/monorepo-migration/update_ci_filters.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 sys +import re + +def update_ci_filters(ci_yaml, lib_name): + with open(ci_yaml, 'r') as f: + content = f.read() + + # match `!(...)` pattern in ci.yaml + pattern = r'!\(([^)]+)\)' + + def repl(match): + modules = match.group(1).split('|') + if lib_name not in modules: + modules.append(lib_name) + modules.sort() + return '!(' + '|'.join(modules) + ')' + + new_content = re.sub(pattern, repl, content) + with open(ci_yaml, 'w') as f: + f.write(new_content) + print(f"Successfully added {lib_name} to CI exclusions in {ci_yaml}.") + +if __name__ == '__main__': + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + update_ci_filters(sys.argv[1], sys.argv[2]) diff --git a/monorepo-migration/update_generation_config.py b/monorepo-migration/update_generation_config.py index 1ba7e6fc8c3e..2ab0c49f4b57 100644 --- a/monorepo-migration/update_generation_config.py +++ b/monorepo-migration/update_generation_config.py @@ -15,87 +15,40 @@ import sys import yaml -import re -def get_library_id(lib): - """ - Returns a unique identifier for a library. - Prefer 'library_name', then 'api_shortname'. - """ - if 'library_name' in lib: - return f"java-{lib['library_name']}" - if 'api_shortname' in lib: - return f"java-{lib['api_shortname']}" - return "unknown" - -def merge_libraries(target_libs, source_libs): +def update_config(target_path, source_path): """ - Merges source_libs into target_libs. - Libraries are matched by get_library_id. - GAPICs are merged and deduplicated by proto_path. - The final list is sorted by library_id. + Appends the library configuration from the source_path to the target_path. + This avoids rewriting the entire target YAML, preserving all comments and ordering. """ - # Map from library_id to library dict - target_map = {get_library_id(lib): lib for lib in target_libs} - - for s_lib in source_libs: - lib_id = get_library_id(s_lib) - - # Clean up source library (remove repo fields) - s_lib_cleaned = {k: v for k, v in s_lib.items() if k not in ('repo', 'repo_short')} - - if lib_id in target_map: - t_lib = target_map[lib_id] - # Merge GAPICs - t_gapics_list = t_lib.get('GAPICs', []) - s_gapics_list = s_lib_cleaned.get('GAPICs', []) - - # Map by proto_path for deduplication - proto_map = {g['proto_path']: g for g in t_gapics_list} - for g in s_gapics_list: - proto_map[g['proto_path']] = g - - # Sort GAPICs by proto_path - sorted_protos = sorted(proto_map.keys()) - t_lib['GAPICs'] = [proto_map[p] for p in sorted_protos] - - # Update other fields from source - for k, v in s_lib_cleaned.items(): - if k != 'GAPICs': - t_lib[k] = v - else: - target_map[lib_id] = s_lib_cleaned - - # Return sorted list of libraries - sorted_ids = sorted(target_map.keys()) - return [target_map[lib_id] for lib_id in sorted_ids] - -def update_config(target_path, source_path): - with open(target_path, 'r') as f: - target_content = f.read() - with open(source_path, 'r') as f: source_data = yaml.safe_load(f) or {} - # Load target data - target_data = yaml.safe_load(target_content) or {} - - target_libs = target_data.get('libraries', []) source_libs = source_data.get('libraries', []) - - merged_libs = merge_libraries(target_libs, source_libs) - target_data['libraries'] = merged_libs + if not source_libs: + print("No libraries found in source config.") + return - # Write back - with open(target_path, 'w') as f: - # Check if there was a license header in the original file - header_match = re.search(r'^(#.*?\n\n)', target_content, re.DOTALL) - if header_match: - f.write(header_match.group(1)) - - # Use yaml.dump to write the data. - # sort_keys=False to preserve order of fields within libraries if possible (YAML 1.2+ usually does, but pyyaml depends) - yaml.dump(target_data, f, sort_keys=False, default_flow_style=False, indent=2) + # In standalone repos, there is usually only one library definition. + new_libs = [] + for s_lib in source_libs: + # Clean up source library (remove repo fields as they are now internal to monorepo) + if 'repo' in s_lib: + del s_lib['repo'] + if 'repo_short' in s_lib: + del s_lib['repo_short'] + new_libs.append(s_lib) + + # Dump the new library entries as a YAML string + # sort_keys=False preserves the field ordering (e.g., api_shortname first) + new_yaml_str = yaml.dump(new_libs, sort_keys=False, default_flow_style=False, indent=2) + + # Append to the existing monorepo generation_config.yaml + # This ensures we don't rewrite the file and lose comments/ordering. + with open(target_path, 'a') as f: + f.write(new_yaml_str.rstrip() + "\n") + + print(f"Appended {len(new_libs)} libraries to {target_path}") if __name__ == "__main__": if len(sys.argv) != 3: diff --git a/monorepo-migration/update_linter_exclusions.py b/monorepo-migration/update_linter_exclusions.py new file mode 100644 index 000000000000..e0cad10b1c39 --- /dev/null +++ b/monorepo-migration/update_linter_exclusions.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 sys + +def update_linter(file_path, lib_name): + with open(file_path, 'r') as f: + content = f.read() + + target = ' [[ "${pomFile}" =~ .*java-vertexai.* ]] || \\' + replacement = f' [[ "${{pomFile}}" =~ .*{lib_name}.* ]] || \\\n{target}' + + if target in content: + content = content.replace(target, replacement) + with open(file_path, 'w') as f: + f.write(content) + print(f"Successfully added {lib_name} to linter exclusions.") + else: + print(f"Target not found in {file_path}") + +if __name__ == '__main__': + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} file_path lib_name") + sys.exit(1) + update_linter(sys.argv[1], sys.argv[2]) diff --git a/monorepo-migration/update_root_pom.py b/monorepo-migration/update_root_pom.py index fec12930dee3..dd1203ae28a0 100644 --- a/monorepo-migration/update_root_pom.py +++ b/monorepo-migration/update_root_pom.py @@ -14,6 +14,7 @@ # limitations under the License. import sys +import re def update_root_pom(pom_path, module_name): new_module = f' {module_name}\n' @@ -39,7 +40,7 @@ def update_root_pom(pom_path, module_name): java_lines = lines[start_java:end_java] if not any(f'{module_name}' in l for l in java_lines): java_lines.append(new_module) - java_lines.sort() + java_lines.sort(key=lambda line: m.group(1) if '' in line and (m := re.search(r'(.*?)', line)) else line) lines = lines[:start_java] + java_lines + lines[end_java:] else: if not any(f'{module_name}' in l for l in lines):