diff --git a/scripts/release/homebrew/docker/formula_generate.py b/scripts/release/homebrew/docker/formula_generate.py index d524b471752..4775d338621 100644 --- a/scripts/release/homebrew/docker/formula_generate.py +++ b/scripts/release/homebrew/docker/formula_generate.py @@ -11,6 +11,9 @@ import requests import jinja2 from poet.poet import make_graph, RESOURCE_TEMPLATE +from collections import OrderedDict +import bisect +import argparse TEMPLATE_FILE_NAME='formula_template.txt' CLI_VERSION=os.environ['CLI_VERSION'] @@ -21,6 +24,24 @@ def main(): print('Generate formular for Azure CLI homebrew release.') + parser = argparse.ArgumentParser(prog='formula_generator.py') + parser.set_defaults(func=generate_formula) + parser.add_argument('-b', dest='build_method', choices=['update_existing', 'use_template'], help='The build method, default is update_existing, the other option is use_template.') + args = parser.parse_args() + args.func(**vars(args)) + +def generate_formula(build_method: str, **_): + content = '' + if build_method is None or build_method == 'update_existing': + content = update_formula() + elif build_method == 'use_template': + content = generate_formula_with_template() + with open('azure-cli.rb', mode='w') as fq: + fq.write(content) + + +def generate_formula_with_template() -> str: + """Generate a brew formula by using a template""" template_path = os.path.join(os.path.dirname(__file__), TEMPLATE_FILE_NAME) with open(template_path, mode='r') as fq: template_content = fq.read() @@ -35,9 +56,8 @@ def main(): ) if not content.endswith('\n'): content += '\n' + return content - with open('azure-cli.rb', mode='w') as fq: - fq.write(content) def compute_sha256(resource_url: str) -> str: import hashlib @@ -45,7 +65,7 @@ def compute_sha256(resource_url: str) -> str: resp = requests.get(resource_url) resp.raise_for_status() sha256.update(resp.content) - + return sha256.hexdigest() @@ -60,6 +80,12 @@ def collect_resources() -> str: return '\n\n'.join(nodes_render) +def collect_resources_dict() -> dict: + nodes = make_graph('azure-cli') + filtered_nodes = {nodes[node_name]['name']: nodes[node_name] for node_name in sorted(nodes) if resource_filter(node_name)} + return filtered_nodes + + def resource_filter(name: str) -> bool: # TODO remove need for any filters and delete this method. return not name.startswith('azure-cli') and name not in ('futures', 'jeepney', 'entrypoints') @@ -83,9 +109,63 @@ def last_bottle_hash(): if 'bottle do' in content: start = idx look_for_end = True - + return '\n'.join(lines[start: end + 1]) +def update_formula() -> str: + """Generate a brew formula by updating the existing one""" + nodes = collect_resources_dict() + + resp = requests.get(HOMEBREW_FORMULAR_LATEST) + resp.raise_for_status() + text = resp.text + + # update url, version and sha256 of azure-cli + text = re.sub('url ".*"', 'url "{}"'.format(HOMEBREW_UPSTREAM_URL), text, 1) + text = re.sub('version ".*"', 'version "{}"'.format(CLI_VERSION), text, 1) + upstream_sha = compute_sha256(HOMEBREW_UPSTREAM_URL) + text = re.sub('sha256 ".*"', 'sha256 "{}"'.format(upstream_sha), text, 1) + text = re.sub('.*revision.*\n', '', text, 1) # remove revision for previous version if exists + pack = None + packs_to_remove = set() + lines = text.split('\n') + node_index_dict = OrderedDict() + for idx, line in enumerate(lines): + if line.strip().startswith("resource"): + m = re.search(r'resource "(.*)" do', line) + if m is not None: + pack = m.group(1) + node_index_dict[pack] = idx + elif pack is not None: + if line.strip().startswith("url"): + #update the url of package + if pack in nodes.keys(): + lines[idx] = re.sub('url ".*"', 'url "{}"'.format(nodes[pack]['url']), line, 1) + else: + packs_to_remove.add(pack) + elif line.strip().startswith("sha256"): + #update the sha256 of package + if pack in nodes.keys(): + lines[idx] = re.sub('sha256 ".*"', 'sha256 "{}"'.format(nodes[pack]['checksum']), line, 1) + del nodes[pack] + pack = None + elif line.strip().startswith('def install'): + if nodes: + # add new dependency packages + for node_name, node in nodes.items(): + # find the right place to insert the new resource per alphabetic order + i = bisect.bisect_left(list(node_index_dict.keys()), node_name) + line_idx = list(node_index_dict.items())[i][1] + resource = RESOURCE_TEMPLATE.render(resource=node) + lines[line_idx] = resource + '\n\n' + lines[line_idx] + new_text = "\n".join(lines) + + # remove dependency packages that are no longer needed + for pack in packs_to_remove: + new_text = re.sub(r'resource "{}" do.*?\n end\n\s+'.format(pack), '', new_text, flags=re.DOTALL) + return new_text + + if __name__ == '__main__': main()