make-rules/component.mk | ●●●●● patch | view | raw | blame | history | |
make-rules/shared-macros.mk | ●●●●● patch | view | raw | blame | history | |
tools/bass/makefiles.py | ●●●●● patch | view | raw | blame | history | |
tools/userland-component | ●●●●● patch | view | raw | blame | history |
make-rules/component.mk
New file @@ -0,0 +1,30 @@ # A simple rule to print the value of any macro. Ex: # $ gmake print-REQUIRED_PACKAGES # Note that some macros are set on a per target basis, so what you see # is not always what you get. print-%: @echo '$(subst ','\'',$*=$($*)) (origin: $(origin $*), flavor: $(flavor $*))' # A simple rule to print only the value of any macro. print-value-%: @echo '$(subst ','\'',$($*))' # Provide default print package targets for components that do not rely on IPS. # Define them implicitly so that the definitions do not collide with ips.mk define print-package-rule echo $(strip $(PACKAGE_$(1))) | tr ' ' '\n' endef COMPONENT_TOOL = $(WS_TOOLS)/userland-component format: @$(COMPONENT_TOOL) --path $(COMPONENT_DIR); update: @if [ "$(VERSION)X" = "X" ]; \ then $(COMPONENT_TOOL) --path $(COMPONENT_DIR) --bump; \ else $(COMPONENT_TOOL) --path $(COMPONENT_DIR) --bump $(VERSION); \ fi; make-rules/shared-macros.mk
@@ -1376,24 +1376,4 @@ include $(WS_MAKE_RULES)/environment.mk include $(WS_MAKE_RULES)/depend.mk # A simple rule to print the value of any macro. Ex: # $ gmake print-REQUIRED_PACKAGES # Note that some macros are set on a per target basis, so what you see # is not always what you get. print-%: @echo '$(subst ','\'',$*=$($*)) (origin: $(origin $*), flavor: $(flavor $*))' # A simple rule to print only the value of any macro. print-value-%: @echo '$(subst ','\'',$($*))' # Provide default print package targets for components that do not rely on IPS. # Define them implicitly so that the definitions do not collide with ips.mk define print-package-rule echo $(strip $(PACKAGE_$(1))) | tr ' ' '\n' endef print-package-%: @$(call print-package-rule,$(shell tr '[a-z]' '[A-Z]' <<< $*)) include $(WS_MAKE_RULES)/component.mk tools/bass/makefiles.py
New file @@ -0,0 +1,361 @@ #!/usr/bin/python3.5 # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # This is all very naive and will hurt pythonists' eyes. # import os import re import subprocess import warnings from .component import Component class Keywords(object): def __init__(self): self.variables = { "BUILD_BITS": ["NO_ARCH", "32", "64", "32_and_64", "64_and_32"], "BUILD_STYLE": ["ant", "attpackagemake", "cmake", "configure", "gem", "justmake", "makemaker", "meson", "ocaml", "setup.py", "waf"], "MK_BITS": ["NO_ARCH", "32", "64", "32_and_64"], "COMPONENT_NAME": [], "COMPONENT_VERSION": [], "COMPONENT_REVISION": [], "COMPONENT_FMRI": [], "COMPONENT_CLASSIFICATION": [], "COMPONENT_SUMMARY": [], "COMPONENT_PROJECT_URL": [], "COMPONENT_SRC": ["$(COMPONENT_NAME)-$(COMPONENT_VERSION)"], "COMPONENT_ARCHIVE": [], "COMPONENT_ARCHIVE_URL": [], "COMPONENT_ARCHIVE_HASH": [], "COMPONENT_LICENSE": [], "COMPONENT_LICENSE_FILE": [] } self.targets = { "build": [ "BUILD_$(MK_BITS)"], "install": ["INSTALL_$(MK_BITS)"], "test": ["TEST_$(MK_BITS)", "NO_TESTS"], "system-test": ["SYSTEM_TEST_$(MK_BITS)", "SYSTEM_TESTS_NOT_IMPLEMENTED"] } @staticmethod def assignment(name, value): return name + "=" + value + "\n" @staticmethod def target_variable_assignment(name, value): return Keywords.assignment(name.upper()+"_TARGET", value) class Item(object): def __init__(self, line=None, content=[]): self.idx = line self.str = content for l in iter(self.str): l = l.strip() def append(self, line): self.str.append(line.strip()) def extend(self, content): for l in content: self.append(l) def line(self): return self.idx def length(self): return len(self.str) def value(self): return "".join(self.str).replace("\n","").strip() def set_value(self, value): self.str = [ i.strip()+"\n" for i in value.split("\n") ] def include_line(self): return "include "+self.str[0] def variable_assignment(self, variable): if self.length() == 1: return ["{0:<24}{1}".format(variable+"=",self.str[0])] # Handle continuation lines lines = ["{0:<24}{1}".format(variable+"=",self.str[0][:-2])] for l in self.str[1:]: lines[-1] += "\\\n" lines.append("\t"+l[:-2]) lines[-1] += "\n" return lines def target_definition(self, target): lines = ["{0:<24}{1}".format(target+":", self.str[0])] for l in self.str[1:]: lines.append("\t"+l) return lines class Makefile(object): def __init__(self, path=None, debug=False): self.debug = debug self.path = path self.component = Component() self.includes = [] self.variables = {} self.targets = {} makefile = os.path.join(path, 'Makefile') with open(makefile, 'r') as f: self.contents = f.readlines() self.update() def update(self, contents=None): self.includes = [] self.variables = {} self.targets = {} if contents is not None: self.contents = contents # Construct list of keywords kw = Keywords() # Variable is set m = None # Target is set t = None # Rule definition d = None for idx, line in enumerate(self.contents): # Continuation of target line if t is not None: r = re.match(r"^[\s]*(.*)[\s]*([\\]?)[\s]*$", line) # Concatenate self.targets[t].str[0] += "\\".join(r.group(1)) # Check for continuation or move to definition if not r.group(2): d = t t = None continue if d is not None: # Concatenate r = re.match(r"^[\t][\s]*(.*)[\s]*$", line) # End of definition if r is None: d = None continue self.targets[d].append(r.group(1)) # Continuation line of variable if m is not None: r = re.match(r"^[\s]*(.*)[\s]*([\\]?)[\s]*$", line) self.variables[m].append(r.group(1)) if not r.group(2): m = None continue if re.match(r"^#", line): continue # Check for include r = re.match(r"^include[\s]+(.*)", line) if r is not None: self.includes.append(Item(idx, [r.group(1)])) else: found = False # Collect known variables for k in list(kw.variables.keys()): r = re.match( r"^[\s]*("+k+r")[\s]*=[\s]*([^\\]*)[\s]*([\\]?)[\s]*$", line) if r is not None: found = True v = r.group(2) if v in self.variables.keys(): warnings.warn("Variable '"+v+"' redefined line "+idx) self.variables[k] = Item(idx, [v]) if r.group(3): m = k break if found is True: continue # Collect known targets for k in list(kw.targets.keys()): r = re.match( "^"+k+r"[\s]*:[\s]*(.*)[\s]*([\\]?)[\s]*$", line) if r is not None: found = True v = r.group(1) if v in self.targets.keys(): warnings.warn("Target '"+v+"' redefined line "+idx) self.targets[k] = Item(idx, [v]) if r.group(2): t = k d = None else: t = None d = k break if found is True: continue def write(self): with open(os.path.join(self.path, "Makefile"), 'w') as f: for line in self.contents: f.write(line) def display(self): print(self.path) print('-' * 78) if self.includes: print("includes:") print("---------") for i in iter(self.includes): print("{0:>3}: {1}".format(i.line(), i.value())) print("") if self.variables: print("variables:") print("----------") for k,i in iter(sorted(self.variables.items())): print("{0:>3}: {1:<24}= {2}".format(i.line(), k, i.value())) print("") if self.targets: print("targets:") print("--------") for k,i in iter(sorted(self.targets.items())): print("{0:>3}: {1:<24}= {2}".format(i.line(), k, i.value())) print("") print('-' * 78) def run(self, targets): path = self.path result = [] if self.debug: logger.debug('Executing \'gmake %s\' in %s', targets, path) proc = subprocess.Popen(['gmake', '-s', targets], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=path, universal_newlines=True) stdout, stderr = proc.communicate() for out in stdout.splitlines(): result.append(out.rstrip()) if self.debug: if proc.returncode != 0: logger.debug('exit: %d, %s', proc.returncode, stderr) return result def has_variable(self, variable): return variable in self.variables def variable(self, variable): return self.variables[variable] def remove_variable(self, variable): idx = self.variable(variable).line() del self.contents[idx] self.update() def set_variable(self, variable, value, line=None): if not self.has_variable(variable): self.variables[variable] = Item(None) self.variables[variable].set_value(str(value)) if line is not None: contents = self.contents[0:line] contents.extend(self.variable_assignment(variable)) contents.extend(self.contents[line:]) self.update(contents) return True else: idx = self.variables[variable].line() oln = self.variables[variable].length() self.variables[variable].set_value(str(value)) nln = self.variables[variable].length() if idx is not None: if line is None: line = idx if line == idx and nln == 1 and oln == 1: self.contents[idx] = self.variable_assignment(variable)[0] elif line <= idx: contents = self.contents[0:line] contents.extend(self.variable_assignment(variable)) contents.extend(self.contents[line:idx]) contents.extend(self.contents[idx+oln:]) self.update(contents) else: contents = self.contents[0:idx] contents.extend(self.contents[idx+oln:line]) contents.extend(self.variable_assignment(variable)) contents.extend(self.contents[line:]) self.update(contents) # Add variable at given line elif line is not None: contents = self.contents[0:line] contents.extend(self.variable_assignment(variable)) contents.extend(self.contents[line:]) self.update(contents) def variable_assignment(self, variable): return self.variables[variable].variable_assignment(variable) def has_target(self, target): return target in self.targets def target(self, target): return self.targets[target] def target_definition(self, target): return self.targets[target].target_definition(target) @staticmethod def value(variable): return "$("+variable+")" @staticmethod def directory_variable(subdir): return self.value("WS_"+subdir.upper().replace("-","_")) @staticmethod def makefile_path(name): return os.path.join("$(WS_MAKE_RULES)", name+".mk") @staticmethod def target_value(name, bits): return Makefile.value(name.upper()+"_"+bits) tools/userland-component
New file @@ -0,0 +1,234 @@ #!/usr/bin/python3.5 # # 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.str[0]) if r is not None: subdir = r.group(1) mkfile = r.group(2) print("000: Fix include " + i.str[0]) i.set_value(os.path.join(MK.directory_variable(subdir), mkfile+".mk")) mk.contents[i.line()] = i.include_line() #----------------------------------------------------------------------------- # 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. def refactor001(mk): kw = Keywords() if mk.has_variable('BUILD_STYLE'): 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 mk_bits_32_no_arch = False 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 # 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.str[0] == v: found = True w = MK.target_value(t, mk_bits) if v == w: print("001: Use default target '"+t+"'") u.str = None else: print("001: Define target '"+t+"': "+u.str[0]) new_targets[t] = u break if not found: # Some Python/Perl makefiles actually use NO_ARCH target with MK_BITS=32 if mk_bits == '32' and u.str[0] == MK.value(t.upper()+"_NO_ARCH"): if not mk_bits_32_no_arch: print("001: Changing make bits from '32' to 'NO_ARCH'") mk_bits_32_no_arch = True u.str = None else: raise ValueError("001: Inconsistent target '"+t+"': "+u.str[0]) if mk_bits_32_no_arch: mk_bits = "NO_ARCH" # 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.str[0]): 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].str[0])) 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 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) if not mk.has_variable('COMPONENT_VERSION'): raise ValueError('COMPONENT_VERSION not found') newvers = str(version) current = mk.variable('COMPONENT_VERSION').value() # 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: print("Bump COMPONENT_VERSION to " + newvers) mk.set_variable('COMPONENT_VERSION', newvers) if mk.has_variable('COMPONENT_REVISION'): mk.remove_variable('COMPONENT_REVISION') # 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()