#!/usr/bin/python3 # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import os import re import subprocess import sys # Constants MOBILE_ANDROID_DIR = os.path.abspath(os.path.dirname(__file__)) CHANGELOG_FILE = os.path.join( MOBILE_ANDROID_DIR, "android-components/docs/changelog.md" ) SOURCE_JSON = os.path.join( MOBILE_ANDROID_DIR, "../../services/settings/dumps/main/search-telemetry-v2.json" ) TARGET_JSON = os.path.join( MOBILE_ANDROID_DIR, "android-components/components/feature/search/src/main/assets/search/search_telemetry_v2.json", ) EXPIRED_STRING_VERSION_OFFSET = 3 def check_ripgrep_installed(): """Check if ripgrep (rg) is installed.""" try: subprocess.run(["rg", "--version"], capture_output=True, check=True) except FileNotFoundError: print( "ERROR: ripgrep (rg) is not installed. Please install ripgrep and try again." ) print( "See installation instructions here: https://github.com/BurntSushi/ripgrep?tab=readme-ov-file#installation" ) sys.exit(1) def check_uncommitted_changes(): """Check for uncommitted changes in the git repository.""" result = subprocess.run( ["git", "status", "--porcelain", "--untracked-files=no"], capture_output=True, text=True, check=False, ) if result.stdout.strip(): print("ERROR: Please commit changes before continuing.") sys.exit(1) def get_bug_id(): """Get BUG_ID from script arguments.""" if len(sys.argv) < 2: print("Usage: python script.py BUG_ID") sys.exit(1) return sys.argv[1] def get_previous_version(): """Extract the previous version number from the changelog.""" with open(CHANGELOG_FILE) as file: content = file.read() match = re.search(r"# (\d+)\.0 \(In Development\)", content) if not match: print( "ERROR: Unable to extract the previous version number from the changelog file." ) sys.exit(1) return int(match.group(1)) def update_changelog(previous_version, new_version): """Update the changelog with the new version number.""" with open(CHANGELOG_FILE) as file: content = file.read() updated_content = content.replace( f"# {previous_version}.0 (In Development)", f"# {new_version}.0 (In Development)\n\n# {previous_version}.0", ) with open(CHANGELOG_FILE, "w") as file: file.write(updated_content) def find_expired_strings(expired_string_version): """Find strings to be removed.""" rg_command = [ "rg", "-g", "**/values/**", "-U", f'([\\r\\n\\s]*)?]*moz:removedIn="{expired_string_version}"[^>]*>.*?', MOBILE_ANDROID_DIR, ] result = subprocess.run(rg_command, capture_output=True, text=True, check=False) expired_strings = [] if result.stdout.strip(): for line in result.stdout.splitlines(): match = re.search(r'\\s*)?]*moz:removedIn=\\"{expired_string_version}\\"[^>]*>[^<]*<\\/string>//g"' ) subprocess.run(bash_command, shell=True, check=True, executable="/bin/bash") return True return False def update_json_if_necessary(): """Check if JSON files differ and copy if necessary.""" if os.path.exists(SOURCE_JSON) and os.path.exists(TARGET_JSON): result = subprocess.run(["cmp", "-s", SOURCE_JSON, TARGET_JSON], check=False) if result.returncode != 0: # Files differ subprocess.run(["cp", SOURCE_JSON, TARGET_JSON], check=False) return True return False def search_remaining_occurrences(removed_strings): """Search for remaining occurrences of each removed string.""" remaining_use_message = "" for name in removed_strings: rg_command = [ "rg", "-n", "--pcre2", f"{name}(?![a-zA-Z0-9_-])", MOBILE_ANDROID_DIR, "-g", "!**/strings.xml", ] result = subprocess.run(rg_command, capture_output=True, text=True, check=False) if result.stdout.strip(): lines = result.stdout.strip().splitlines() remaining_use_message += ( f"\n- \033[31m\033[1m{name}\033[0m ({len(lines)}):\n" ) for line in lines: remaining_use_message += f"\t· {line}\n" return remaining_use_message def commit_changes(bug_id, new_version_number, strings_removed, json_updated): """Commit all changes with a constructed commit message.""" commit_message = ( f"Bug {bug_id} - Start the nightly {new_version_number} development cycle.\n\n" ) if strings_removed: commit_message += f"Strings expiring in version {new_version_number - EXPIRED_STRING_VERSION_OFFSET} have been removed\n" if json_updated: commit_message += ( "search_telemetry_v2.json was updated in Android Components, based on the " "content of services/settings/dumps/main/search-telemetry-v2.json\n" ) subprocess.run(["git", "add", "-u"], check=False) subprocess.run(["git", "commit", "--quiet", "-m", commit_message], check=False) def main(): check_ripgrep_installed() check_uncommitted_changes() bug_id = get_bug_id() previous_version = get_previous_version() new_version = previous_version + 1 expired_string_version = new_version - EXPIRED_STRING_VERSION_OFFSET # Update changelog update_changelog(previous_version, new_version) # Find and remove expired strings expired_strings = find_expired_strings(expired_string_version) if expired_strings: strings_removed = remove_expired_strings(expired_string_version) remaining_use_message = search_remaining_occurrences(expired_strings) else: strings_removed = False remaining_use_message = "" # Check JSON update json_updated = update_json_if_necessary() # Commit changes commit_changes(bug_id, new_version, strings_removed, json_updated) # Output final message print(f"✅ Changelog updated to version {new_version}") if strings_removed: print(f"✅ Removed 'moz:removedIn=\"{expired_string_version}\"' entries") else: print(f"ℹ️ No 'moz:removedIn=\"{expired_string_version}\"' entries found") if json_updated: print("✅ search_telemetry_v2.json was updated in Android Components") else: print("ℹ️ search_telemetry_v2.json was already up to date and was not modified") print(f"✅ Changes committed with Bug ID {bug_id}.") if remaining_use_message: print( "\n⚠️ Some of the strings that were removed might still be used in the codebase." ) print( "These are the potential remaining usages. Keep in mind that it is purely indicative and might show false positives." ) print("Please remove the real remaining usages and amend the commit.") print(remaining_use_message) print("\n\033[1mPlease make sure you complete the following steps:\033[0m") if remaining_use_message: print( "☐ Remove the remaining uses of the removed strings and amend the commit." ) print("☐ Review the changes and make sure they are correct") print("☐ Run `moz-phab submit --no-wip`") print( "☐ Run `mach try --preset firefox-android` and add a comment with the try link on the patch" ) if __name__ == "__main__": main()