#!/usr/bin/env python3 # # Copyright (C) 2018 - 2025 Axivion GmbH # Copyright (C) 2025 The Qt Company GmbH, a subsidiary of The Qt Group # https://www.qt.io/ # # # For local build customization: clone makefiles from a standard build tree # into a new directory to run an analysis build in the new directory. # # - 1st parameter: original build tree (path) # - 2nd parameter (optional): cloned build tree, if omitted, will use # original tree with '.Axivion_LB' appended # # Cloning is incremental: only newer makefiles will be copied. # # Hard-coded commands in the original makefiles can be replaced by custom # commands, e.g. 'gcc' : 'cafeCC', see command_mapping. # # For the definition of makefiles, see get_make_files below. # import glob import os import os.path import shutil import sys # Text to be appended to name of original build dir for the # name of the cloned build tree clone_suffix = '.Axivion_LB' # replace hard-coded commands in original makefiles with analysis build command do_replace_command = False # mapping of original commands to replacement # only active if "do_replace_command == True" # configure this, if you need to post-process the generated Makefiles. Preferably # the Makefiles should contain the correct command or an environment variable already. command_mapping = { 'gcc' : 'cafeCC', 'g++' : 'cafeCC', } # replace commands only if they directly follow a leading tab char, do not # replace any other occurrence of any of command_mapping.keys() # only active if "do_replace_command == True" need_leading_tab = True # remove makefiles from cloned tree, which are not in original tree anymore do_clean = True # remove all makefiles in cloned tree before copying new makefiles do_extra_clean= False # logging def ignore(s): pass error = sys.stderr.write warn = sys.stderr.write info = sys.stdout.write debug = sys.stdout.write trace = sys.stdout.write # 1st parameter: Original build directory, will append clone_suffix text # for cloned build tree if len(sys.argv) < 1 + 1: error('Need the name of the original build dir as 1st parameter\n') sys.exit(1) original_build_tree = sys.argv[1] # optional 2nd parameter: cloned build directory if len(sys.argv) >= 2 + 1: cloned_build_tree = sys.argv[2] else: cloned_build_tree = '%s%s' % (original_build_tree, clone_suffix) # Only 2 parameters if len(sys.argv) > 2 + 1: error('Expect 1 or 2 parameters, ignoring all others\n') sys.exit(3) # Make sure the original build really exists if not os.path.isdir(original_build_tree): error('Did not find original build tree %s, cannot clone build system' % original_build_tree) sys.exit(2) def get_make_files(rootdir): '''Enumerates all makefiles in a build tree''' for dirpath, dirnames, filenames in os.walk(rootdir): for filename in filenames: if filename.lower().endswith('.mk') or filename.lower() == 'makefile': yield os.path.join(dirpath, filename) def transfer_makefile(src, dst): '''Copies the makefile src into the filename dest, replacing commands''' global command_mapping, need_leading_tab with open(src, 'rt') as s: with open(dst, 'wt') as d: for line in s: for old, new in command_mapping.items(): if need_leading_tab: # expect the old command to be after a tab char # at the beginning of a line (Makefile command) old_prefix = '\t%s' % old if line.startswith(old_prefix): line = '\t%s%s' % (new, line[len(old_prefix):]) else: # replace all occurrences of the string old line = line.replace(old, new) d.write(line) # mapping from existing (old) makefiles to their modification time stamps # used to decide which new makefiles shall be copied # and to remove all old makefiles, which have no current counterpart in the # original build old_makefiles = {} # search for old makefiles in previously cloned tree for f in get_make_files(cloned_build_tree): if do_extra_clean: os.unlinkd(f) debug('removed %s\n' % f) else: old_makefiles[f] = os.path.getmtime(f) # find, transform and copy makefiles from original_build_tree for src in get_make_files(original_build_tree): source_path, basename = os.path.split(src) rel_source_dir = os.path.relpath(source_path, original_build_tree) target_dir = os.path.normpath( os.path.join(cloned_build_tree, rel_source_dir)) dst = os.path.join(target_dir, basename) if not os.path.isdir(target_dir): trace('mkdir %s\n' % target_dir) os.makedirs(target_dir) need_to_copy = True if dst in old_makefiles: try: src_modtime = os.path.getmtime(src) need_to_copy = src_modtime > old_makefiles[dst] except os.error: pass # remove the makefile from the list of old makefiles # because it is up-to-date or will be updated del old_makefiles[dst] if need_to_copy: if do_replace_command: transfer_makefile(src=src, dst=dst) debug('transformed %s to %s\n' % (src, dst)) else: shutil.copyfile(src=src, dst=dst) debug('copied %s to %s\n' % (src, dst)) else: trace('skipped %s; not newer than %s\n' % (src, dst)) # optionally remove all makefiles from cloned tree, which do not exist # in the original tree if do_clean: for f in old_makefiles: os.unlink(f) debug('removed %s\n' % f)