From 12a085a69a198c9eb068ec89743853d7c8bb8222 Mon Sep 17 00:00:00 2001 From: Joel Linn Date: Mon, 13 Sep 2021 20:52:57 +0200 Subject: [PATCH] Premake generator: automatically construct filters Based on a set of predefined configs, parse and evaluate Makefiles. Will emit filters if necessary. --- generate_premake.py | 157 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 131 insertions(+), 26 deletions(-) diff --git a/generate_premake.py b/generate_premake.py index 4c97baa310..57c0e00f62 100644 --- a/generate_premake.py +++ b/generate_premake.py @@ -21,12 +21,35 @@ templates['libavcodec'] = template_header.format('libavcodec', '9DB2830C-D326-4 "libavutil", }) """ +class Config(): + def __init__(self, os, arch, config_h, premake_filters): + self.os = os + self.arch = arch + self.config_h = config_h + self.premake_filters = premake_filters + self.key_values = [] + +supported_configs = [ + Config('windows', 'x86_64' , 'config_windows_x86_64.h' , 'platforms:Windows'), + Config('linux' , 'x86_64' , 'config_linux_x86_64.h' , 'platforms:Linux'), + Config('android', 'x86_64' , 'config_android_x86_64.h' , 'platforms:Android_x86_64'), + Config('android', 'aarch64', 'config_android_aarch64.h', 'platforms:Android_ARM64'), +] + +def are_list_items_identical(list_a, list_b): + set_a = set(list_a) + if len(set_a) != len(list_b): + return False + for a in set_a: + if a not in list_b: + return False + return True # gets the config defines from the generated header -def get_conf(): +def parse_config(file_name): conf = {} # platform configs mostly differ in HAVE_*, which we are not interested in - with open('config_win.h', 'r') as c: + with open(file_name, 'r') as c: for line in c: split = line.rstrip().split(' ' , 2) if len(split) != 3: @@ -45,6 +68,10 @@ def get_conf(): conf[key] = val return conf +def parse_configs(): + for config in supported_configs: + config.key_values = parse_config(config.config_h) + # Adapted from distutils.sysconfig.parse_makefile # Regexes needed for parsing Makefile (and similar syntaxes, # like old-style Setup files). @@ -152,45 +179,123 @@ def parse_makefile(fn, conf, g=None): g.update(done) return g - -def premake_files(files): +def premake_files(files, libname): + # .o rule order from ffbuild: + extensions = [ + '.c', + '.cpp', + '.m', + '.S', + '.asm', + '.rc', + ] s = ' files({\n' for f in files: - # will break on asm files - f = re.sub('.o$', '.c', f) + match = re.search('^(.*).o$', f) + if match: + for ext in extensions: + f2 = match.group(1) + ext + if os.path.exists(os.path.join(libname, f2)): + f = f2 + break + if f.endswith('.o'): + print('Error: could not resolve source for OBJ "{}".'.format(f)) s += ' "{}",\n'.format(f) s += ' })\n' return s +def premake_filter(filters = None): + f = ' filter({' + if filters and len(filters): + f += '"' + # Concatenate sorted (for consistency) filters + f += ' or '.join(sorted(filters)) + f += '"' + f += '})\n' + return f -def generate_premake(conf, libname): +def generate_premake(configs, libname): M = 'Makefile' + makefiles = [ - os.path.join(libname, M), - os.path.join(libname, 'aarch64', M), - os.path.join(libname, 'x86', M), + (os.path.join(libname, M) , configs), + # Original Makefiles are always included but since symbols are never used we can ignore them: + (os.path.join(libname, 'aarch64', M), list(filter(lambda config: config.arch == 'aarch64', configs))), + (os.path.join(libname, 'x86' , M) , list(filter(lambda config: config.arch == 'x86_64', configs))), ] + + # Makefile variables that contain source files - conditionals from arch.mak + file_blocks = [ + ('HEADERS' , None), + ('ARCH_HEADERS' , None), + ('BUILT_HEADERS' , None), + ('OBJS' , None), + + ('ARMV5TE-OBJS' , 'HAVE_ARMV5TE'), + ('ARMV6-OBJS' , 'HAVE_ARMV6'), + ('ARMV8-OBJS' , 'HAVE_ARMV8'), + ('VFP-OBJS' , 'HAVE_VFP'), + ('NEON-OBJS' , 'HAVE_NEON'), + + ('MMX-OBJS' , 'HAVE_MMX'), + ('X86ASM-OBJS' , 'HAVE_X86ASM'), + ] + + # Cache tree of parsed makefile variables + ms = {} + for makefile in makefiles: + ms2 = {} + for config in configs: + ms2[config] = parse_makefile(makefile[0], config.key_values) + ms[makefile[0]] = ms2 + with open(os.path.join(libname, 'premake5.lua'), 'w') as premake: premake.write(templates[libname]) for makefile in makefiles: - m = parse_makefile(makefile, conf) - file_blocks = [ - 'HEADERS', - 'ARCH_HEADERS', - 'BUILT_HEADERS', - 'OBJS', - 'MMX-OBJS', - ] + premake.write('\n -- {}:\n'.format(makefile[0].replace('\\', '/'))) for file_block in file_blocks: - for _ in range(2): - if file_block in m: - premake.write(' -- {}:\n'.format(makefile.replace('\\', '/'))) - premake.write(' -- {}:\n'.format(file_block)) - premake.write(premake_files(m[file_block].split())) - file_block += '-yes' + files = {} + for config in makefile[1]: + if file_block[1] and ((not file_block[1] in config.key_values) or (not config.key_values[file_block[1]])): + continue + m = ms[makefile[0]][config] + fb = file_block[0] + for _ in range(2): + if fb in m: + for file in m[fb].split(): + if file not in files: + files[file] = { config } + else: + files[file].add(config) + fb += '-yes' # Evaluated conditionals + if len(files): + # Get unique config groups + config_sets = [] + for file_configs in files.values(): + is_new = True + for config_set in config_sets: + if are_list_items_identical(config_set, file_configs): + is_new = False + break + if is_new: + config_sets.append(file_configs) + # Make common files come first + config_sets = sorted(config_sets, key=lambda x: -len(x)) + + premake.write(' -- {}:\n'.format(file_block[0])) + + # Write file lists with applied filters + filter_used = False + for config_set in config_sets: + if not are_list_items_identical(config_set, configs): # no filter for common files + filter_used = True + premake.write(premake_filter([config.premake_filters for config in config_set])) + premake.write(premake_files([filename for filename, file_configs in files.items() if are_list_items_identical(config_set, file_configs)], libname)) + if filter_used: + premake.write(premake_filter()) if __name__ == '__main__': - conf =get_conf() + parse_configs() for libname in templates: - generate_premake(conf, libname) + generate_premake(supported_configs, libname)