lookup_branch_head.py (4453B)
1 # This Source Code Form is subject to the terms of the Mozilla Public 2 # License, v. 2.0. If a copy of the MPL was not distributed with this 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 import argparse 5 import json 6 import os 7 import pathlib 8 import sys 9 import urllib.request 10 11 # default cache file location in STATE_DIR location 12 default_cache_path = ".moz-fast-forward/milestone.cache" 13 14 15 def fetch_branch_head_dict(): 16 # The milestone dictionary was found by opening the Mozilla dev 17 # tools network tab and loading page 18 # https://chromiumdash.appspot.com/branches 19 milestone_url = ( 20 "https://chromiumdash.appspot.com/fetch_milestones?only_branched=true" 21 ) 22 uf = urllib.request.urlopen(milestone_url) 23 html = uf.read() 24 milestone_dict = json.loads(html) 25 26 # There is more information in the json dictionary, but we only care 27 # about the milestone (version) to branch "name" (webrtc_branch) 28 # info. For example: 29 # v106 -> 5249 (which translates to branch-heads/5249) 30 # v107 -> 5304 (which translates to branch-heads/5304) 31 # 32 # As returned from web query, milestones are integers and branch 33 # "names" are strings. 34 new_dict = {} 35 for row in milestone_dict: 36 new_dict[row["milestone"]] = row["webrtc_branch"] 37 38 return new_dict 39 40 41 def fetch_branch_schedule_dict(): 42 # The milestone schedule dictionary was found by opening the Mozilla 43 # dev tools network tab and loading page 44 # https://chromiumdash.appspot.com/schedule 45 milestone_schedule_url = ( 46 "https://chromiumdash.appspot.com/fetch_milestone_schedule?offset=-1&n=4" 47 ) 48 uf = urllib.request.urlopen(milestone_schedule_url) 49 html = uf.read() 50 schedule_dict = json.loads(html) 51 52 # There is more information in the json dictionary, but we only care 53 # about the mstone (version) to branch_point date info. For example: 54 # v138 -> 2025-05-26T00:00:00 55 # v139 -> 2025-06-23T00:00:00 56 # v140 -> 2025-08-04T00:00:00 57 # 58 # As returned from web query, milestones are integers and branch_point 59 # dates are strings. 60 new_dict = {} 61 for row in schedule_dict["mstones"]: 62 new_dict[row["mstone"]] = row["branch_point"] 63 64 return new_dict 65 66 67 def get_branch_date(milestone): 68 milestone_dates = {} 69 try: 70 milestone_dates = fetch_branch_schedule_dict() 71 except Exception: 72 pass 73 74 if milestone in milestone_dates: 75 return milestone_dates[milestone] 76 return None 77 78 79 def read_dict_from_cache(cache_path): 80 if cache_path is not None and os.path.exists(cache_path): 81 with open(cache_path) as ifile: 82 return json.loads(ifile.read(), object_hook=jsonKeys2int) 83 return {} 84 85 86 def write_dict_to_cache(cache_path, milestones): 87 with open(cache_path, "w") as ofile: 88 ofile.write(json.dumps(milestones)) 89 90 91 def get_branch_head(milestone, cache_path=default_cache_path): 92 milestones = read_dict_from_cache(cache_path) 93 94 # if the cache didn't exist or is stale, try to fetch using a web query 95 if milestone not in milestones: 96 try: 97 milestones = fetch_branch_head_dict() 98 write_dict_to_cache(cache_path, milestones) 99 except Exception: 100 pass 101 102 if milestone in milestones: 103 return milestones[milestone] 104 return None 105 106 107 # From https://stackoverflow.com/questions/1450957/pythons-json-module-converts-int-dictionary-keys-to-strings 108 def jsonKeys2int(x): 109 if isinstance(x, dict): 110 return {int(k): v for k, v in x.items()} 111 return x 112 113 114 if __name__ == "__main__": 115 parser = argparse.ArgumentParser( 116 description="Get libwebrtc branch-head for given chromium milestone" 117 ) 118 parser.add_argument( 119 "milestone", type=int, help="integer chromium milestone (example: 106)" 120 ) 121 parser.add_argument("-v", "--verbose", action="store_true") 122 parser.add_argument("-c", "--cache", type=pathlib.Path, help="path to cache file") 123 args = parser.parse_args() 124 125 # if the user provided a cache path use it, otherwise use the default 126 local_cache_path = args.cache or default_cache_path 127 128 branch_head = get_branch_head(args.milestone, local_cache_path) 129 if branch_head is None: 130 sys.exit(f"error: chromium milestone '{args.milestone}' is not found.") 131 132 if args.verbose: 133 print(f"chromium milestone {args.milestone} uses branch-heads/{branch_head}") 134 else: 135 print(branch_head)