Python 3.12 will not have the distutils package in the standard library. The TextFile class is particularly useful when reading [Mm]akefiles, and recreating its functionality would be painful. Stash a local copy of the last version of distutils.text_file.py and use it. Do not rely on distutils being present or installed.
191 lines
7.6 KiB
Python
191 lines
7.6 KiB
Python
## generate.py
|
|
##
|
|
## Generate the simulator CMakeLists.txt from the top-level makefile.
|
|
##
|
|
## This is the top-level driver: process options, search for the
|
|
## makefile, parse the makefile and walk its dependencies, and,
|
|
## finally, output the CMakeLists.txt(s) and simh-simulators.cmake.
|
|
##
|
|
## Author: B. Scott Michel
|
|
## ("scooter me fecit")
|
|
|
|
import sys
|
|
import os.path
|
|
import argparse
|
|
import re
|
|
|
|
GEN_SCRIPT_DIR = os.path.dirname(__file__)
|
|
GEN_SCRIPT_NAME = os.path.basename(__file__)
|
|
|
|
import pprint
|
|
|
|
import simgen.cmake_container as SCC
|
|
import simgen.parse_makefile as SPM
|
|
import simgen.packaging as SPKG
|
|
|
|
|
|
def process_makefile(makefile_dir, debug=0):
|
|
the_makefile = os.path.join(makefile_dir, "makefile")
|
|
print('{0}: Processing {1}'.format(GEN_SCRIPT_NAME, the_makefile))
|
|
|
|
(defs, rules, actions) = SPM.parse_makefile(the_makefile)
|
|
if debug >= 4:
|
|
pprint.pp(defs)
|
|
|
|
all_rule = rules.get('all')
|
|
if all_rule is None:
|
|
print('{0}: "all" rule not found. Cannot process.'.format(GEN_SCRIPT_NAME))
|
|
|
|
simulators = SCC.CMakeBuildSystem()
|
|
for all_targ in SPM.shallow_expand_vars(all_rule, defs).split():
|
|
print("{0}: all target {1}".format(GEN_SCRIPT_NAME, all_targ))
|
|
walk_target_deps(all_targ, defs, rules, actions, simulators, debug=debug)
|
|
|
|
experimental_rule = rules.get('experimental')
|
|
for experimental_targ in SPM.shallow_expand_vars(experimental_rule, defs).split():
|
|
print("{0}: exp target {1}".format(GEN_SCRIPT_NAME, experimental_targ))
|
|
walk_target_deps(experimental_targ, defs, rules, actions, simulators, debug=debug)
|
|
|
|
simulators.collect_vars(defs, debug=debug)
|
|
return simulators
|
|
|
|
|
|
## Makefile target dependencies to filter out.
|
|
_ignored_deps = [
|
|
'${SIM}',
|
|
'${BUILD_ROMS}'
|
|
]
|
|
|
|
## Simulator compile/link action pattern
|
|
_compile_act_rx = re.compile(r"\$[({]CC[)}]\s*(.*)")
|
|
_test_name_rx = re.compile(r"\$@\s*\$\(call\s+find_test,\s*(.*),(.*)\)\s+\$")
|
|
|
|
def walk_target_deps(target, defs, rules, actions, simulators, depth='', debug=0):
|
|
""" Recursively walk a target's dependencies, i.e., the right hand side of a make rule.
|
|
Descend into each dependency to find something that looks like a simulator's
|
|
source code list. Once source code list is found, extract simulator defines, includes,
|
|
source files and set flags.
|
|
"""
|
|
if debug >= 1:
|
|
print('{0}-- target: {1}'.format(depth, target))
|
|
|
|
target_deps = SPM.target_dep_list(target, rules, defs)
|
|
|
|
has_buildrom = any(filter(lambda dep: dep == '${BUILD_ROMS}', target_deps))
|
|
if debug >= 1:
|
|
print('{0} has_buildrom {1}', has_buildrom)
|
|
|
|
deps = [dep for dep in target_deps if dep not in _ignored_deps]
|
|
targ_actions = actions.get(target)
|
|
if targ_actions:
|
|
depth3 = depth + ' '
|
|
if debug >= 2:
|
|
print('{0}deps {1}'.format(depth3, deps))
|
|
|
|
# Are the dependencies a source code list?
|
|
expanded_deps = [l for slist in [ SPM.shallow_expand_vars(dep, defs).split() for dep in deps ] for l in slist]
|
|
if debug >= 3:
|
|
print('{0}expanded_deps {1}'.format(depth3, expanded_deps))
|
|
|
|
if any(filter(lambda f: f.endswith('.c'), expanded_deps)):
|
|
if debug >= 1:
|
|
print('{0}sim sources {1}'.format(depth3, deps))
|
|
if debug >= 2:
|
|
print('{0}targ_actions {1}'.format(depth3, targ_actions))
|
|
|
|
# The simulators' compile and test actions are very regular and easy to find:
|
|
compile_act = None
|
|
test_name = None
|
|
sim_dir = None
|
|
for act in targ_actions:
|
|
m_cact = _compile_act_rx.match(act)
|
|
m_test = _test_name_rx.match(act)
|
|
if m_cact:
|
|
compile_act = m_cact.group(1)
|
|
elif m_test:
|
|
(sim_dir, test_name) = m_test.group(1, 2)
|
|
|
|
if debug >= 2:
|
|
print('{0}sim_dir {1}'.format(depth3, sim_dir))
|
|
print('{0}compile_act {1}'.format(depth3, compile_act))
|
|
print('{0}test_name {1}'.format(depth3, test_name))
|
|
|
|
if compile_act and test_name and sim_dir:
|
|
sim_name = target.replace("${BIN}", "").replace("${EXE}", "")
|
|
# Just in case there are vestiges of old-style make variables
|
|
sim_name = sim_name.replace("$(BIN)", "").replace("$(EXE)", "")
|
|
if debug >= 2:
|
|
print('{0}sim_name {1}'.format(depth3, sim_name))
|
|
|
|
simulators.extract(compile_act, test_name, sim_dir, sim_name, defs, has_buildrom, debug, depth+' ')
|
|
else:
|
|
# No actions associated with the dependency(ies), which means that the dependency(ies)
|
|
# are meta-targets. Continue to walk.
|
|
for dep in deps:
|
|
walk_target_deps(dep, defs, rules, actions, simulators, depth=depth+' ', debug=debug)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
args = argparse.ArgumentParser(description="SIMH simulator CMakeLists.txt generator.")
|
|
args.add_argument('--debug', nargs='?', const=1, default=0, type=int,
|
|
help='Debug level (0-3, 0 == off)')
|
|
args.add_argument('--srcdir', default=None,
|
|
help='makefile source directory.')
|
|
## args.add_argument('--file', '-f', default=os.path.join(GEN_SCRIPT_DIR, 'simh_makefile.cmake'),
|
|
## help='Output file for "all-in-one" CMakeLists.txt, default is simh_makefile.cmake')
|
|
flags = vars(args.parse_args())
|
|
|
|
debug_level = flags.get('debug')
|
|
makefile_dir = flags.get('srcdir')
|
|
|
|
print('{0}: Expecting to emit {1} simulators.'.format(GEN_SCRIPT_NAME, len(SPKG.package_info.keys())))
|
|
|
|
found_makefile = True
|
|
if makefile_dir is None:
|
|
## Find the makefile, which should be one directory up from this Python
|
|
## module
|
|
makefile_dir = GEN_SCRIPT_DIR
|
|
print('{0}: Looking for makefile, starting in {1}'.format(GEN_SCRIPT_NAME, makefile_dir))
|
|
the_makefile = ''
|
|
while makefile_dir:
|
|
the_makefile = os.path.join(makefile_dir, "makefile")
|
|
if os.path.exists(the_makefile):
|
|
break
|
|
else:
|
|
makefile_dir = os.path.dirname(makefile_dir)
|
|
print('{0}: Looking for makefile, trying {1}'.format(GEN_SCRIPT_NAME, makefile_dir))
|
|
|
|
if not the_makefile:
|
|
found_makefile = False
|
|
else:
|
|
the_makefile = os.path.join(makefile_dir, "makefile")
|
|
if not os.path.exists(the_makefile):
|
|
found_makefile = False
|
|
|
|
if not found_makefile:
|
|
print('{0}: SIMH top-level makefile not found, relative to {1}'.format(GEN_SCRIPT_NAME, GEN_SCRIPT_DIR))
|
|
sys.exit(1)
|
|
|
|
sims = process_makefile(makefile_dir, debug=debug_level)
|
|
|
|
## Sanity check: Make sure that all of the simulators in SPKG.package_info have
|
|
## been encountered
|
|
for simdir in sims.dirs.keys():
|
|
for sim in sims.dirs[simdir].simulators.keys():
|
|
SPKG.package_info[sim].encountered()
|
|
|
|
orphans = [ sim for sim, pkg_info in SPKG.package_info.items() if not pkg_info.was_processed() ]
|
|
if len(orphans) > 0:
|
|
print('{0}: Simulators not extracted from makefile:'.format(GEN_SCRIPT_NAME))
|
|
for orphan in orphans:
|
|
print('{0}{1}'.format(' ' * 4, orphan))
|
|
sys.exit(1)
|
|
|
|
if debug_level >= 1:
|
|
pp = pprint.PrettyPrinter()
|
|
pp.pprint(sims)
|
|
|
|
## Emit all of the individual CMakeLists.txt
|
|
sims.write_simulators(makefile_dir, debug=debug_level)
|
|
## Emit the packaging data
|
|
SPKG.write_packaging(makefile_dir)
|