I wrote this tool to do automatic updating of layers managed by the setup-layers script. This addresses some of the functionality I discussed in my OSSNA 2023 talk <https://www.youtube.com/watch?v=MiTwL9xZdW4>.
A detailed explanation of what the following script does is found in the comments, but in a nutshell: * If the --sync-upstream argument is provided, any layers that have remotes called "origin" and "upstream" have upstream changes on their configured branch applied to the origin remote (and local clone). * Updates rev and description for all layers managed by setup-layers after pulling the latest changes on the configured branch. * Leaves the setup-layers.json file in an updated state so git diff will show changes. Changes can then be reviewed and managed based on local project policy. I am looking for comments and feedback before I send this as a proper patch. In particular: * Are there any problems with this general approach? * Should this be managed in OE-Core in the same way setup-layers is? * I made some stylistic changes (e.g. main function, --dry-run, etc) that I plan to also patch into setup-layers if this is accepted and there are no objections. --- #!/usr/bin/env python3 # # Copyright OpenEmbedded Contributors # # SPDX-License-Identifier: MIT # # This script is idempotent and assumes that the layers it is updating have # been managed by setup-layers according to the JSON formatted layers data. # # For each layer repository in the JSON formatted layers data file: # # * If remotes named upstream and origin exist and the --sync-upstream argument # has been provided, merge upstream changes on the configured branch to the # origin remote. # * Pull the latest changes on the configured branch from origin. # * Update the JSON layers data to reflect the latest layer repo HEAD. # * Leave layer repository in an updated state with the configured branch # checked out. # # After reviewing the changes in the JSON layers data file, they can either be # committed or reverted. If they are reverted, it will be necessary to re-run # setup-layers to ensure your layer repositories reflect the intent captured in # the configuration. For the sake of completeness, you may also wish to re-run # setup-layers even if you commit the configuration changes, as this script # leaves the local layer repo clones checked out to the configured branch rather # than the configured rev. The HEAD of the configured branch and the configured # rev are identical at the completion of this script, but the latter will be # more consistent with expectations established by the setup-layers script. # # The term "setup-layers" refers to the script found at # openembedded-core/scripts/oe-setup-layers. Pass the --help argument to this # script for the most up to date usage guidance. import argparse import json import os import subprocess def _do_git_command(repodir, command): print("Running '{}' in {}".format(command, repodir)) if not args.dry_run: subprocess.check_output(command, shell=True, cwd=repodir) def _do_git_checkout_branch(repodir, remote, branch): _do_git_command(repodir, 'git checkout -B {0} --track {1}/{0} --quiet'.format(branch, remote)) def _do_git_pull(repodir, remote, branch): _do_git_fetch(repodir, remote) _do_git_merge(repodir, remote, branch) def _do_git_fetch(repodir, remote): _do_git_command(repodir, 'git fetch {} --prune --prune-tags --quiet'.format(remote)) def _do_git_merge(repodir, remote, branch): _do_git_command(repodir, 'git merge {}/{} --ff-only --quiet'.format(remote, branch)) def _do_git_force_push(repodir, remote, branch): _do_git_command(repodir, 'git push {} heads/{} --force --quiet'.format(remote, branch)) def _get_git_rev(repodir): rev = subprocess.check_output("git rev-parse HEAD", shell=True, cwd=repodir, stderr=subprocess.DEVNULL) return rev.strip().decode("utf-8") def _get_git_desc(repodir): desc = subprocess.check_output("git describe HEAD --always", shell=True, cwd=repodir, stderr=subprocess.DEVNULL) return desc.strip().decode("utf-8") def _do_sync_upstream(repodir, repo_name, repo_data): if not args.sync_upstream: return repo_remote = repo_data['git-remote'] branch = repo_remote['branch'] remotes = repo_remote['remotes'] print("Attempting to sync layer repo {} with upstream.".format(repo_name)) if 'origin' not in remotes or 'upstream' not in remotes: print("Origin and/or upstream remotes not found on {}".format(repo_name)) return _do_git_checkout_branch(repodir, 'origin', branch) _do_git_pull(repodir, 'origin', branch) _do_git_fetch(repodir, 'upstream') _do_git_merge(repodir, 'upstream', branch) _do_git_force_push(repodir, 'origin', branch) def _do_update_layer_json(repodir, repo_name, layer_data): print("Updating layer JSON for {} repo.".format(repo_name)) branch = layer_data['sources'][repo_name]['git-remote']['branch'] _do_git_checkout_branch(repodir, 'origin', branch) _do_git_pull(repodir, 'origin', branch) layer_data['sources'][repo_name]['git-remote']['rev'] = _get_git_rev(repodir) layer_data['sources'][repo_name]['git-remote']['describe'] = _get_git_desc(repodir) def _do_update_layers(layer_data): print("Updating layers...") repos = layer_data['sources'] for repo_name in repos: repo_data = repos[repo_name] repodir = os.path.abspath(os.path.join(args.destdir, repo_data['path'])) if 'contains_this_file' in repo_data.keys(): continue _do_sync_upstream(repodir, repo_name, repo_data) _do_update_layer_json(repodir, repo_name, layer_data) def _do_save_layer_config(layer_data): print("Saving updated layer config.") if args.dry_run: return with open(args.jsondata, 'w') as f: json.dump(layer_data, f, ensure_ascii=False, indent=4) print("\nReview {} for layer changes.".format(args.jsondata)) def _parse_args(): global args try: defaultdest = os.path.dirname(subprocess.check_output('git rev-parse --show-toplevel', universal_newlines=True, shell=True, cwd=os.path.dirname(__file__))) except subprocess.CalledProcessError as e: defaultdest = os.path.abspath(".") parser = argparse.ArgumentParser(description="Updates layer setup JSON in addition to layer repos with upstream remotes.") parser.add_argument('--destdir', default=defaultdest, help='Where to find the layers (default is {defaultdest}).'.format(defaultdest=defaultdest)) parser.add_argument('--jsondata', default=__file__+".json", help='File containing the layer data in json format (default is {defaultjson}).'.format(defaultjson=__file__+".json")) parser.add_argument('--sync-upstream', action='store_true', help="For repositories with origin and upstream remotes, merge any upstream changes to the configured branch.") parser.add_argument('--dry-run', action='store_true', help="Performs a dry run, but does not make any actual changes.") args = parser.parse_args() def _parse_layer_config(): with open(args.jsondata) as f: layer_data = json.load(f) supported_versions = ["1.0"] if layer_data["version"] not in supported_versions: raise Exception("File {} has version {}, which is not in supported versions: {}".format(args.jsondata, layer_data["version"], supported_versions)) return layer_data def main(): _parse_args() layer_data = _parse_layer_config() _do_update_layers(layer_data) _do_save_layer_config(layer_data) if __name__=="__main__": main() -- "Perfection must be reached by degrees; she requires the slow hand of time." - Voltaire
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#184211): https://lists.openembedded.org/g/openembedded-core/message/184211 Mute This Topic: https://lists.openembedded.org/mt/100104869/21656 Group Owner: openembedded-core+ow...@lists.openembedded.org Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [arch...@mail-archive.com] -=-=-=-=-=-=-=-=-=-=-=-