#!/usr/bin/python3.9 # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2021 Aurelien Larcher # import argparse import os import re import sys import json from bass.component import Component from bass.makefiles import Item from bass.makefiles import Keywords from bass.makefiles import Makefile as MK # Refactoring rules #----------------------------------------------------------------------------- # They should be called in-order to avoid unsatisfied assumptions. def format_component(path, verbose): mk = MK(path) kw = Keywords() refactor000(mk) refactor001(mk) refactor002(mk) mk.write() #----------------------------------------------------------------------------- # 000: Use WS_* variables instead $(WS_TOP)/* # If $(WS_TOP)/make-rules is found in an include then replace with the # variable $(WS_RULES). Do the same for other variables. def refactor000(mk): for i in iter(mk.includes): r = re.match(r"^\$\(WS_TOP\)\/(.*)\/(.*).mk", i.value()) if r is not None: subdir = r.group(1) mkfile = r.group(2) print("000: Fix include " + i.value()) i.set_value(os.path.join(MK.directory_variable(subdir), mkfile+".mk")) mk.contents[i.line()] = i.include_line() mk.update() #----------------------------------------------------------------------------- # 001: Use common.mk # If common.mk is not included then: # 1. infer the build system and set the BUILD_STYLE. # 2. set the BUILD_BITS from the existing targets. # 3. erase default target and keep the custom ones. # 4. fix known target typos def refactor001(mk): kw = Keywords() if mk.has_variable('BUILD_STYLE') or mk.has_mk_include('common'): return # Build style build_style = None for i in iter(mk.includes): r = re.match(r"^\$\(WS_MAKE_RULES\)/(.*).mk$", i.value()) if r is not None: build_style = r.group(1) if r.group(1) in kw.variables['BUILD_STYLE'] else None if build_style is not None: mk.set_variable('BUILD_STYLE', build_style) break if build_style is None: raise ValueError("Variable BUILD_STYLE cannot be defined") else: print("001: Setting build style to '" + build_style + "'") build_style = mk.variable('BUILD_STYLE').value() # Build bits mk_bits = mk.run("print-value-MK_BITS")[0] if mk_bits not in kw.variables["MK_BITS"]: raise ValueError("Variable MK_BITS cannot be defined") else: print("001: Setting make bits to '" + mk_bits + "'") # Check targets new_mk_bits = None new_targets = {} for t, u in iter(mk.targets.items()): # We do not know how to handle target with defined steps yet if len(u.str) > 1: continue # Amend typos if t == 'test' and u.value() == MK.value('NO_TEST'): print("001: Fix typo $(NO_TEST) -> $(NO_TESTS)") u.set_value(MK.value('NO_TESTS')) # Process target found = False for v in kw.targets[t]: v = MK.value(v.replace(MK.value("MK_BITS"), mk_bits)) # If the target dependency is one of the default values if u.value() == v: found = True w = MK.target_value(t, mk_bits) #print(w) if v == w: print("001: Use default target '"+t+"'") u.str = None else: print("001: Define target '"+t+"': "+u.value()) new_targets[t] = u break if not found: # Some Python/Perl makefiles actually use NO_ARCH target with MK_BITS=32, or BITS was not set if mk_bits == '32' or mk_bits == '64': ok_bits = ( 'NO_ARCH', '64', '32_and_64', '64_and_32' ) for b in ok_bits: if u.value() == MK.target_value(t, b): if not new_mk_bits: new_mk_bits = b elif b != new_mk_bits: raise ValueError("001: Inconsistent target '"+t+"': "+u.value()) u.str = None break else: raise ValueError("001: Unknown target '"+t+"' bitness: "+u.value()) if new_mk_bits: print("001: Changing make bits from "+mk_bits+" to '"+new_mk_bits+"'") mk_bits = new_mk_bits # Collect items rem_lines = set() rem_includes = [ MK.makefile_path("prep"), MK.makefile_path("ips")] new_includes = [] include_shared_mk = None include_common_mk = None for i in iter(mk.includes): if i.value() not in rem_includes: if i.value() == MK.makefile_path(build_style): i.set_value(MK.makefile_path("common")) include_common_mk = i elif re.match(r".*/shared-macros.mk$", i.value()): include_shared_mk = i new_includes.append(i) else: rem_lines.add(i.line()) mk.includes = new_includes if include_common_mk is None: raise ValueError("Include directive of common.mk not found") if include_shared_mk is None: raise ValueError("Include directive of shared-macros.mk not found") # Add lines to skip for default targets for u in mk.targets.values(): if u.str is None: rem_lines.add(u.line()) # Update content contents = mk.contents[0:include_shared_mk.line()] # Add build macros contents.append(Keywords.assignment('BUILD_STYLE', build_style)) contents.append(Keywords.assignment('BUILD_BITS', mk_bits)) # Write metadata lines for idx, line in enumerate(mk.contents[include_shared_mk.line():include_common_mk.line()]): if (include_shared_mk.line() + idx) in rem_lines: continue contents.append(line) # Write new targets for t in ["build", "install", "test"]: if t in new_targets.keys(): contents.append(Keywords.target_variable_assignment(t, new_targets[t].value())) rem_lines.add(new_targets[t].line()) # Add common include contents.append(include_common_mk.include_line()) # Write lines after common.mk for idx, line in enumerate(mk.contents[include_common_mk.line()+1:]): if (include_common_mk.line()+1+idx) in rem_lines: continue contents.append(line) mk.update(contents) #----------------------------------------------------------------------------- # 002: Indent COMPONENT_ variables def refactor002(mk): for k,i in iter(mk.variables.items()): if re.match("^COMPONENT_", k): idx = i.line() lines = i.variable_assignment(k) for i in range(0, i.length()): mk.contents[idx + i] = lines[i] mk.update() # Update rules #----------------------------------------------------------------------------- # U000: Update to default OpenSSL # If openssl is a dependency and the openssl package version is not set # 1. update the dependency to the next openssl X.Y # 2. add macros USE_OPENSSLXY to the makefile def update000(mk): curr_version = '1.0' next_version = '1.1' curr_macro = 'USE_OPENSSL'+curr_version.replace('.','') next_macro = 'USE_OPENSSL'+next_version.replace('.','') curr_openssl_pkg = 'library/security/openssl' next_openssl_pkg = 'library/security/openssl-11' reqs = mk.required_packages() has_openssl_deps=False for p in reqs.split(): if p == curr_openssl_pkg: has_openssl_deps=True if not has_openssl_deps: return # Check whether current version is enforced for line in iter(mk.contents): if re.match("^"+curr_macro+"[\s]*=[\s]*yes", line): return print("U000: update to next openssl") # Replace dependency for idx, line in enumerate(mk.contents): if re.match(r"REQUIRED_PACKAGES(.*)"+curr_openssl_pkg+"[\s]*$", line): mk.contents[idx] = line.replace(curr_openssl_pkg, next_openssl_pkg) break # Add macro before shared-macros include_shared_macros_mk = mk.get_mk_include('shared-macros') if not include_shared_macros_mk: raise ValueError('include shared_macros.mk not found') mk.set_variable(next_macro, 'yes', include_shared_macros_mk.line()) mk.update() #----------------------------------------------------------------------------- # Update component makefile for revision or version bump def update_component(path, version, verbose): format_component(path, verbose) # Nothing to bump, just update the Makefile to current format if version is None: return mk = MK(path) # Apply default update rules update000(mk) # Check current version if not mk.has_variable('COMPONENT_VERSION'): raise ValueError('COMPONENT_VERSION not found') newvers = str(version) current = mk.variable('COMPONENT_VERSION').value() version_has_changed = False # Bump revision only if newvers == '0' or newvers == current: print("Bump COMPONENT_REVISION") if mk.has_variable('COMPONENT_REVISION'): try: component_revision = int(mk.variable('COMPONENT_REVISION').value()) except ValueError: print('COMPONENT_REVISION field malformed: {}'.format(component_revision)) # Change value mk.set_variable('COMPONENT_REVISION', str(component_revision+1)) else: # Add value set to 1 after COMPONENT_VERSION mk.set_variable('COMPONENT_REVISION', str(1), line=mk.variable('COMPONENT_VERSION').line()+1) # Update to given version and remove revision else: if newvers == 'latest': if mk.build_style() == 'setup.py': print("Trying to get latest version from PyPI") js = mk.get_pypi_data() try: newvers = js['info']['version'] except KeyError: print("Unable to find version") return None print("Bump COMPONENT_VERSION to " + newvers) version_has_changed = True mk.set_variable('COMPONENT_VERSION', newvers) if mk.has_variable('COMPONENT_REVISION'): mk.remove_variable('COMPONENT_REVISION') # Update makefile mk.write() if not version_has_changed: return # Try to update archive checksum if mk.uses_pypi(): print("Trying to get checksum from PyPI") js = mk.get_pypi_data() try: verblock = js['releases'][newvers] except KeyError: print("Unknown version '%s'" % newvers) return None # Index 0 is for the pypi package and index 1 for the source archive sha256 = verblock[1]['digests']['sha256'] print("Found: "+str(sha256)) mk.set_archive_hash(sha256) # Update makefile mk.write() def main(): parser = argparse.ArgumentParser() parser.add_argument('--path', default='components', help='Directory holding components') parser.add_argument('--bump', nargs='?', default=None, const=0, help='Bump component to given version') parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Verbose output') args = parser.parse_args() path = args.path version = args.bump verbose = args.verbose update_component(path=path, version=version, verbose=verbose) if __name__ == '__main__': main()