1import subprocess 2import re 3import argparse 4import os 5import shutil 6import sys 7 8# v10.0.0 -> fail if release/v10.0 is not there 9# v10.0.1 -> update release/v10.0 10# v10.1.0 -> create release/v10.1 from release/v10.0 and update it 11# v10.1.1 -> update release/v10.1 12 13LOG = "[release_branch_updater.py]" 14 15def main(): 16 17 arg_parser = argparse.ArgumentParser() 18 arg_parser.add_argument("--port-clone-tmpdir", default="port_tmpdir") 19 arg_parser.add_argument("--port-urls-path", default=os.path.join(os.path.dirname(__file__), "release_branch_updater_port_urls.txt")) 20 arg_parser.add_argument("--lvgl-path", default=os.path.join(os.path.dirname(__file__), "..")) 21 arg_parser.add_argument("--dry-run", action="store_true") 22 arg_parser.add_argument("--oldest-major", type=int) 23 args = arg_parser.parse_args() 24 25 port_clone_tmpdir = args.port_clone_tmpdir 26 port_urls_path = args.port_urls_path 27 lvgl_path = args.lvgl_path 28 dry_run = args.dry_run 29 oldest_major = args.oldest_major 30 31 lvgl_release_branches = get_release_branches(lvgl_path) 32 print(LOG, "LVGL release branches:", ", ".join(fmt_release(br) for br in lvgl_release_branches) or "(none)") 33 if oldest_major is not None: 34 lvgl_release_branches = [br for br in lvgl_release_branches if br[0] >= oldest_major] 35 print(LOG, 'LVGL release branches after "oldest-major" filter:', 36 ", ".join(fmt_release(br) for br in lvgl_release_branches) or "(none)") 37 38 with open(port_urls_path) as f: 39 urls = f.read() 40 urls = [url for url in map(str.strip, urls.splitlines()) if url] 41 42 # ensure this script creates the directory i.e. it doesn't belong to the user since it will rm -rf at the end 43 assert not os.path.exists(port_clone_tmpdir), "the port clone tmpdir should not exist yet" 44 45 for url in urls: 46 print(LOG, "working with port:", url) 47 48 subprocess.check_call(("git", "clone", url, port_clone_tmpdir)) 49 50 port_release_branches = get_release_branches(port_clone_tmpdir) 51 print(LOG, "port release branches:", ", ".join(fmt_release(br) for br in port_release_branches) or "(none)") 52 53 # we want to 54 # 1. create (if necessary) the port's release branch 55 # 2. update the LVGL submodule to match the LVGL's release branch version 56 # 3. update the lv_conf.h based on the lv_conf.defaults 57 58 # from oldest to newest release... 59 for lvgl_branch in lvgl_release_branches: 60 print(LOG, f"attempting to update release branch {fmt_release(lvgl_branch)} ...") 61 62 port_does_not_have_the_branch = False 63 port_submodule_was_updated = False 64 port_lv_conf_h_was_updated = False 65 66 # if the branch does not exist in the port, create it from 67 # the closest minor of the same major. 68 if lvgl_branch in port_release_branches: 69 print(LOG, "... this port has a matching release branch.") 70 subprocess.check_call(("git", "-C", port_clone_tmpdir, "branch", "--track", 71 fmt_release(lvgl_branch), 72 f"origin/{fmt_release(lvgl_branch)}")) 73 else: 74 print(LOG, "... this port does not have this release branch minor ...") 75 port_does_not_have_the_branch = True 76 77 # get the port branch with this major and the next smallest minor 78 create_from = next(( 79 br 80 for br in reversed(port_release_branches) # reverse it to get the newest (largest) minor 81 if br[0] == lvgl_branch[0] # same major 82 and br[1] < lvgl_branch[1] # smaller minor because exact minor does not exist 83 ), None) 84 if create_from is None: 85 # there are no branches in the port that are this major 86 # version. One must be created manually. 87 print(LOG, "... this port has no major from which to create the minor. one must be created manually. continuing to next.") 88 continue 89 90 print(LOG, f"... creating the new branch {fmt_release(lvgl_branch)} " 91 f"from {fmt_release(create_from)}") 92 subprocess.check_call(("git", "-C", port_clone_tmpdir, "branch", 93 fmt_release(lvgl_branch), # new branch name 94 fmt_release(create_from))) # start point 95 96 port_release_branches.append(lvgl_branch) 97 port_release_branches.sort() 98 99 # checkout the same release in both LVGL and the port 100 subprocess.check_call(("git", "-C", lvgl_path, "checkout", f"origin/{fmt_release(lvgl_branch)}")) 101 subprocess.check_call(("git", "-C", port_clone_tmpdir, "checkout", fmt_release(lvgl_branch))) 102 103 # update the submodule in the port if it exists 104 out = subprocess.check_output(("git", "-C", port_clone_tmpdir, "config", "--file", 105 ".gitmodules", "--get-regexp", "path")) 106 port_lvgl_submodule_path = next(( 107 line.partition("lvgl.path ")[2] 108 for line 109 in out.decode().strip().splitlines() 110 if "lvgl.path " in line 111 ), None) 112 if port_lvgl_submodule_path is None: 113 print(LOG, "this port has no LVGL submodule") 114 else: 115 print(LOG, "lvgl submodule found in port at:", port_lvgl_submodule_path) 116 117 # get the SHA of LVGL in this release of LVGL 118 out = subprocess.check_output(("git", "-C", lvgl_path, "rev-parse", "--verify", "--quiet", "HEAD")) 119 lvgl_sha = out.decode().strip() 120 print(LOG, "the SHA of LVGL in this release should be:", lvgl_sha) 121 122 # get the SHA of LVGL this port wants to use in this release 123 out = subprocess.check_output(("git", "-C", port_clone_tmpdir, "rev-parse", 124 "--verify", "--quiet", f"HEAD:{port_lvgl_submodule_path}")) 125 port_lvgl_submodule_sha = out.decode().strip() 126 print(LOG, "the SHA of LVGL in the submodule of this port is:", port_lvgl_submodule_sha) 127 128 if lvgl_sha == port_lvgl_submodule_sha: 129 print(LOG, "the submodule's version of LVGL is already up to date") 130 else: 131 print(LOG, "the submodule's version of LVGL is NOT up to date") 132 port_submodule_was_updated = True 133 134 # update the version of the submodule in the index. no need to `git submodule update --init` it. 135 # also no need to `git add .` afterwards because it stages the change. 136 # 160000 is a git file mode which means submodule. 137 subprocess.check_call(("git", "-C", port_clone_tmpdir, "update-index", "--cacheinfo", 138 f"160000,{lvgl_sha},{port_lvgl_submodule_path}")) 139 140 # update the lv_conf.h if there's an lv_conf.defaults 141 out = subprocess.check_output(("find", ".", "-name", "lv_conf.defaults", "-print", "-quit"), cwd=port_clone_tmpdir) 142 port_lv_conf_defaults = next(iter(out.decode().strip().splitlines()), None) 143 if port_lv_conf_defaults is None: 144 print(LOG, "this port has no lv_conf.defaults") 145 else: 146 out = subprocess.check_output(("find", ".", "-name", "lv_conf.h", "-print", "-quit"), cwd=port_clone_tmpdir) 147 port_lv_conf_h = next(iter(out.decode().strip().splitlines()), None) 148 if port_lv_conf_h is None: 149 print(LOG, "this port has an lv_conf.defaults but no lv_conf.h") 150 else: 151 subprocess.check_call((sys.executable, os.path.join(lvgl_path, "scripts/generate_lv_conf.py"), 152 "--defaults", os.path.abspath(os.path.join(port_clone_tmpdir, port_lv_conf_defaults)), 153 "--config", os.path.abspath(os.path.join(port_clone_tmpdir, port_lv_conf_h)), )) 154 155 # check if lv_conf.h actually changed. it will not detect the submodule change as a false positive. 156 out = subprocess.check_output(("git", "-C", port_clone_tmpdir, "diff")) 157 diff = out.decode().strip() 158 if not diff: 159 print(LOG, "this port's lv_conf.h did NOT change") 160 else: 161 print(LOG, "this port's lv_conf.h changed") 162 port_lv_conf_h_was_updated = True 163 subprocess.check_call(("git", "-C", port_clone_tmpdir, "add", port_lv_conf_h)) 164 out = subprocess.check_output(("git", "-C", port_clone_tmpdir, "diff")) 165 diff = out.decode().strip() 166 assert not diff 167 168 if port_does_not_have_the_branch or port_submodule_was_updated or port_lv_conf_h_was_updated: 169 print(LOG, "changes were made. ready to push.") 170 # keep it brief for commit message 50 character limit suggestion. 171 # max length will be 50 characters in this case: "CI release edit: new branch. submodule. lv_conf.h." 172 commit_msg = ("CI release edit:" 173 + (" new branch." if port_does_not_have_the_branch else "") 174 + (" submodule." if port_submodule_was_updated else "") 175 + (" lv_conf.h." if port_lv_conf_h_was_updated else "") 176 ) 177 print(LOG, f"commit message: '{commit_msg}'") 178 subprocess.check_call(("git", "-C", port_clone_tmpdir, "commit", "-m", commit_msg)) 179 if dry_run: 180 print(LOG, "this is a dry run so nothing will be pushed") 181 else: 182 subprocess.check_call(("git", "-C", port_clone_tmpdir, "push", 183 *(("-u", "origin") if port_does_not_have_the_branch else ()), 184 fmt_release(lvgl_branch), 185 )) 186 print(LOG, "the changes were pushed.") 187 else: 188 print(LOG, "nothing to push for this release. it is up to date.") 189 190 shutil.rmtree(port_clone_tmpdir) 191 192 print(LOG, "port update complete:", url) 193 194def get_release_branches(working_dir): 195 196 out = subprocess.check_output(("git", "-C", working_dir, "branch", "--quiet", "--format", "%(refname)", "--all")) 197 branches = out.decode().strip().splitlines() 198 199 release_versions = [] 200 for branch_name in branches: 201 release_branch = re.fullmatch(r"refs/remotes/origin/release/v([0-9]+)\.([0-9]+)", branch_name) 202 if release_branch is None: 203 continue 204 release_versions.append((int(release_branch[1]), int(release_branch[2]))) 205 206 release_versions.sort() 207 208 return release_versions 209 210def fmt_release(release_tuple): 211 return f"release/v{release_tuple[0]}.{release_tuple[1]}" 212 213if __name__ == "__main__": 214 main() 215