#!/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()
|