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.cloudgoogle-cloud-monitoring
- 3.91.0
+ 3.93.0-SNAPSHOTcom.google.api.grpcproto-google-cloud-monitoring-v3
- 3.91.0
+ 3.93.0-SNAPSHOTio.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.cloudgoogle-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):