Andreas Wacknitz
2024-04-04 5e5f64b64ea30050f76f29d5ad52fa5d0e089301
commit | author | age
de89cf 1 #!/usr/bin/python3.9
27cdce 2
AL 3 #
4 # This file and its contents are supplied under the terms of the
5 # Common Development and Distribution License ("CDDL"), version 1.0.
6 # You may only use this file in accordance with the terms of version
7 # 1.0 of the CDDL.
8 #
9 # A full copy of the text of the CDDL should have accompanied this
10 # source.  A copy of the CDDL is also available via the Internet at
11 # http://www.illumos.org/license/CDDL.
12 #
13
14 #
15 # Copyright 2021 Aurelien Larcher
16 #
17
18 import argparse
19 import os
20 import re
21 import sys
22 import json
23
24 from bass.component import Component
25 from bass.makefiles import Item
26 from bass.makefiles import Keywords
27 from bass.makefiles import Makefile as MK
28
29 # Refactoring rules
30 #-----------------------------------------------------------------------------
31 # They should be called in-order to avoid unsatisfied assumptions.
32 def format_component(path, verbose):
33     mk = MK(path)
34     kw = Keywords()
35     refactor000(mk)
36     refactor001(mk)
37     refactor002(mk)
38     mk.write()
39
40
41 #-----------------------------------------------------------------------------
42 # 000:  Use WS_* variables instead $(WS_TOP)/* 
43 #       If $(WS_TOP)/make-rules is found in an include then replace with the
44 #       variable $(WS_RULES). Do the same for other variables.
45 def refactor000(mk):
46     for i in iter(mk.includes):
b688b7 47         r = re.match(r"^\$\(WS_TOP\)\/(.*)\/(.*).mk", i.value())
27cdce 48         if r is not None:
AL 49             subdir = r.group(1)
50             mkfile = r.group(2)
b688b7 51             print("000: Fix include " + i.value())
27cdce 52             i.set_value(os.path.join(MK.directory_variable(subdir), mkfile+".mk"))
AL 53             mk.contents[i.line()] = i.include_line()
4bf6ad 54     mk.update()
27cdce 55
AL 56
57 #-----------------------------------------------------------------------------
58 # 001:  Use common.mk
59 #       If common.mk is not included then:
60 #           1. infer the build system and set the BUILD_STYLE.
61 #           2. set the BUILD_BITS from the existing targets.
62 #           3. erase default target and keep the custom ones.
b688b7 63 #           4. fix known target typos
27cdce 64 def refactor001(mk):
AL 65     kw = Keywords()
b688b7 66     if mk.has_variable('BUILD_STYLE') or mk.has_mk_include('common'):
27cdce 67         return
AL 68     # Build style
69     build_style = None
70     for i in iter(mk.includes):
71         r = re.match(r"^\$\(WS_MAKE_RULES\)/(.*).mk$", i.value())
72         if r is not None:
73             build_style = r.group(1) if r.group(1) in kw.variables['BUILD_STYLE'] else None
74             if build_style is not None:
75                 mk.set_variable('BUILD_STYLE', build_style)
76                 break
77     if build_style is None:
78         raise ValueError("Variable BUILD_STYLE cannot be defined")
79     else:
80         print("001: Setting build style to '" + build_style + "'")
81     build_style = mk.variable('BUILD_STYLE').value()
82     # Build bits
83     mk_bits = mk.run("print-value-MK_BITS")[0]
84     if mk_bits not in kw.variables["MK_BITS"]:
85         raise ValueError("Variable MK_BITS cannot be defined")
86     else:
87         print("001: Setting make bits to '" + mk_bits + "'")
88     # Check targets
b688b7 89     new_mk_bits = None
27cdce 90     new_targets = {}
AL 91     for t, u in iter(mk.targets.items()):
92         # We do not know how to handle target with defined steps yet
93         if len(u.str) > 1:
94             continue
b688b7 95         # Amend typos
AL 96         if t == 'test' and u.value() == MK.value('NO_TEST'): 
97             print("001: Fix typo $(NO_TEST) -> $(NO_TESTS)")
98             u.set_value(MK.value('NO_TESTS'))
27cdce 99         # Process target
AL 100         found = False
101         for v in kw.targets[t]:
102             v = MK.value(v.replace(MK.value("MK_BITS"), mk_bits))
103             # If the target dependency is one of the default values
b688b7 104             if u.value() == v:
27cdce 105                 found = True
AL 106                 w = MK.target_value(t, mk_bits)
b688b7 107                 #print(w)
27cdce 108                 if v == w:
AL 109                     print("001: Use default target '"+t+"'")
110                     u.str = None 
111                 else:
b688b7 112                     print("001: Define target '"+t+"': "+u.value())
27cdce 113                     new_targets[t] = u
AL 114                 break
115         if not found:
871dfc 116             # Some Python/Perl makefiles actually use NO_ARCH target with MK_BITS=32, or BITS was not set
AL 117             if mk_bits == '32' or mk_bits == '64':
b688b7 118                 ok_bits = ( 'NO_ARCH', '64', '32_and_64', '64_and_32' )
AL 119                 for b in ok_bits:
120                     if u.value() == MK.target_value(t, b):
121                         if not new_mk_bits:
122                             new_mk_bits = b
123                         elif b != new_mk_bits:
124                             raise ValueError("001: Inconsistent target '"+t+"': "+u.value())
125                         u.str = None
126                         break
27cdce 127             else:
b688b7 128                 raise ValueError("001: Unknown target '"+t+"' bitness: "+u.value())
0d28f0 129     if new_mk_bits:
AL 130         print("001: Changing make bits from "+mk_bits+" to '"+new_mk_bits+"'")
131         mk_bits = new_mk_bits
27cdce 132     # Collect items
AL 133     rem_lines = set()
134     rem_includes = [ MK.makefile_path("prep"), MK.makefile_path("ips")]
135     new_includes = []
136     include_shared_mk = None
137     include_common_mk = None
138     for i in iter(mk.includes): 
139         if i.value() not in rem_includes:
140             if i.value() == MK.makefile_path(build_style):
141                 i.set_value(MK.makefile_path("common"))
142                 include_common_mk = i
b688b7 143             elif re.match(r".*/shared-macros.mk$", i.value()):
27cdce 144                 include_shared_mk = i
AL 145             new_includes.append(i)
146         else:
147             rem_lines.add(i.line())
148     mk.includes = new_includes
149     if include_common_mk is None:
150         raise ValueError("Include directive of common.mk not found")
151     if include_shared_mk is None:
152         raise ValueError("Include directive of shared-macros.mk not found")
153     # Add lines to skip for default targets 
154     for u in mk.targets.values():
155         if u.str is None:
156             rem_lines.add(u.line())
157     # Update content 
158     contents = mk.contents[0:include_shared_mk.line()]
159     # Add build macros
160     contents.append(Keywords.assignment('BUILD_STYLE', build_style))
161     contents.append(Keywords.assignment('BUILD_BITS', mk_bits))
162     # Write metadata lines 
163     for idx, line in enumerate(mk.contents[include_shared_mk.line():include_common_mk.line()]):
164         if (include_shared_mk.line() + idx) in rem_lines:
165             continue
166         contents.append(line)
167     # Write new targets
168     for t  in ["build", "install", "test"]:
169         if t in new_targets.keys():
b688b7 170             contents.append(Keywords.target_variable_assignment(t, new_targets[t].value()))
27cdce 171             rem_lines.add(new_targets[t].line())
AL 172     # Add common include
173     contents.append(include_common_mk.include_line())
174     # Write lines after common.mk 
175     for idx, line in enumerate(mk.contents[include_common_mk.line()+1:]):
176         if (include_common_mk.line()+1+idx) in rem_lines:
177             continue
178         contents.append(line)
179     mk.update(contents)
180
181
182 #-----------------------------------------------------------------------------
183 # 002:  Indent COMPONENT_ variables
184 def refactor002(mk):
185     for k,i in iter(mk.variables.items()):
186         if re.match("^COMPONENT_", k):
187             idx = i.line()
188             lines = i.variable_assignment(k)
189             for i in range(0, i.length()):
190                 mk.contents[idx + i] = lines[i] 
191     mk.update()
192
193
4bf6ad 194 # Update rules
AL 195 #-----------------------------------------------------------------------------
196 # U000: Update to default OpenSSL
197 #       If openssl is a dependency and the openssl package version is not set
198 #           1. update the dependency to the next openssl X.Y
199 #           2. add macros USE_OPENSSLXY to the makefile 
200 def update000(mk):
201     curr_version = '1.0'
202     next_version = '1.1'
203     curr_macro = 'USE_OPENSSL'+curr_version.replace('.','')
204     next_macro = 'USE_OPENSSL'+next_version.replace('.','')
205     curr_openssl_pkg = 'library/security/openssl'
206     next_openssl_pkg = 'library/security/openssl-11'
207     reqs = mk.required_packages()
208     has_openssl_deps=False
209     for p in reqs.split():
210         if p == curr_openssl_pkg:
211             has_openssl_deps=True
212     if not has_openssl_deps:
213         return
214     # Check whether current version is enforced
215     for line in iter(mk.contents):
216         if re.match("^"+curr_macro+"[\s]*=[\s]*yes", line):
217             return
218     print("U000: update to next openssl")
219     # Replace dependency
220     for idx, line in enumerate(mk.contents):
221         if re.match(r"REQUIRED_PACKAGES(.*)"+curr_openssl_pkg+"[\s]*$", line):
222             mk.contents[idx] = line.replace(curr_openssl_pkg, next_openssl_pkg)
223             break
224     # Add macro before shared-macros
225     include_shared_macros_mk = mk.get_mk_include('shared-macros')
226     if not include_shared_macros_mk:
227         raise ValueError('include shared_macros.mk not found')
228     mk.set_variable(next_macro, 'yes', include_shared_macros_mk.line())
229     mk.update()
230
231
27cdce 232 #-----------------------------------------------------------------------------
AL 233 # Update component makefile for revision or version bump 
234 def update_component(path, version, verbose):
235     format_component(path, verbose)
236     # Nothing to bump, just update the Makefile to current format
237     if version is None:
238         return
239     mk = MK(path)
4bf6ad 240     # Apply default update rules
AL 241     update000(mk)
242     # Check current version
27cdce 243     if not mk.has_variable('COMPONENT_VERSION'):
AL 244         raise ValueError('COMPONENT_VERSION not found')
245     newvers = str(version) 
246     current = mk.variable('COMPONENT_VERSION').value()
81e725 247     version_has_changed = False
27cdce 248     # Bump revision only
AL 249     if newvers == '0' or newvers == current:
250         print("Bump COMPONENT_REVISION")
251         if mk.has_variable('COMPONENT_REVISION'):
252             try:
253                 component_revision = int(mk.variable('COMPONENT_REVISION').value())
254             except ValueError:
255                 print('COMPONENT_REVISION field malformed: {}'.format(component_revision))
256             # Change value
257             mk.set_variable('COMPONENT_REVISION', str(component_revision+1))
258         else:
259             # Add value set to 1 after COMPONENT_VERSION
260             mk.set_variable('COMPONENT_REVISION', str(1), line=mk.variable('COMPONENT_VERSION').line()+1)
261     # Update to given version and remove revision
262     else:
81e725 263         if newvers == 'latest':
AL 264             if mk.build_style() == 'setup.py':
265                 print("Trying to get latest version from PyPI")
266                 js = mk.get_pypi_data()
267                 try:
268                     newvers = js['info']['version']
269                 except KeyError:
270                     print("Unable to find version")
271                     return None
27cdce 272         print("Bump COMPONENT_VERSION to " + newvers)
81e725 273         version_has_changed = True
27cdce 274         mk.set_variable('COMPONENT_VERSION', newvers)
AL 275         if mk.has_variable('COMPONENT_REVISION'):
276             mk.remove_variable('COMPONENT_REVISION')
277     # Update makefile
278     mk.write()
279
81e725 280     if not version_has_changed:
AL 281         return
282
283     # Try to update archive checksum
284     if mk.uses_pypi():
285         print("Trying to get checksum from PyPI")
286         js = mk.get_pypi_data()
287         try:
288               verblock = js['releases'][newvers]
289         except KeyError:
290             print("Unknown version '%s'" % newvers)
291             return None
292         # Index 0 is for the pypi package and index 1 for the source archive
293         sha256 = verblock[1]['digests']['sha256']
294         print("Found: "+str(sha256))
295         mk.set_archive_hash(sha256)
296         # Update makefile
297         mk.write()
298
27cdce 299
AL 300 def main():
301     parser = argparse.ArgumentParser()
302     parser.add_argument('--path', default='components',
303                         help='Directory holding components')
304     parser.add_argument('--bump', nargs='?', default=None, const=0,
305                         help='Bump component to given version')
306     parser.add_argument('-v', '--verbose', action='store_true',
307                         default=False, help='Verbose output')
308     args = parser.parse_args()
309
310     path = args.path
311     version = args.bump
312     verbose = args.verbose
313
314     update_component(path=path, version=version, verbose=verbose)
315
316
317 if __name__ == '__main__':
318     main()