From a5b2fc08adb545094d621f36faafd0f4a8f3db52 Mon Sep 17 00:00:00 2001 From: Static <210101922+Static-Codes@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:55:42 -0400 Subject: [PATCH 1/6] Fixed comment for clarity --- bundle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle.py b/bundle.py index ba6c72b..1536ed4 100644 --- a/bundle.py +++ b/bundle.py @@ -20,7 +20,7 @@ # - ^\s* - Removes optional leading whitespace. # - (import|'from') - Removes import keywords # - ({"|".join(INTERNAL_MODULES)}) - This will skip lines containing internal modules. -# - The \b (word boundary) ensures we don't match 'constants_test' if we only want 'constants' +# - \b - The word boundary ensures test modules are ignored; for example: 'constants_test' when 'constants' is desired. IMPORT_PATTERN = re.compile( fr'^\s*(import|from)\s+({"|".join(INTERNAL_MODULES)})\b', re.IGNORECASE From ab0ee5babe3e06474b98d311a73c7515f6b2882a Mon Sep 17 00:00:00 2001 From: Static <210101922+Static-Codes@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:35:05 -0400 Subject: [PATCH 2/6] Added MIT license notifications that were removed from source files --- bundle.py | 20 ++++++++++++++++++++ classes.py | 19 +++++++++++++++++++ main.py | 19 +++++++++++++++++++ processing.py | 20 +++++++++++++++++++- utils.py | 20 +++++++++++++++++++- variables.py | 20 ++++++++++++++++++++ 6 files changed, 116 insertions(+), 2 deletions(-) diff --git a/bundle.py b/bundle.py index 1536ed4..2e157ea 100644 --- a/bundle.py +++ b/bundle.py @@ -1,3 +1,23 @@ +# Copyright (c) 2026 Ferenc Deak & Static Codes +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + # Instead of cramming all 2000+ lines of auto2cmake in a single file, this bundler serves as a good middleground. # The portability factor of the project remains untouched for end-users, and maintainers gain much needed organization! import os diff --git a/classes.py b/classes.py index aaf81db..a8cf23c 100644 --- a/classes.py +++ b/classes.py @@ -1,3 +1,22 @@ +# Copyright (c) 2026 Ferenc Deak & Static Codes +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. import variables import sys from enum import Enum diff --git a/main.py b/main.py index 6df8d2d..520b79c 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,22 @@ +# Copyright (c) 2026 Ferenc Deak & Static Codes +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. # Disabling bytecode generation. import sys sys.dont_write_bytecode = True diff --git a/processing.py b/processing.py index 012b237..62c9ebc 100644 --- a/processing.py +++ b/processing.py @@ -1,4 +1,22 @@ -# processing.py +# Copyright (c) 2026 Ferenc Deak & Static Codes +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. import sys import os import re diff --git a/utils.py b/utils.py index 2025b25..82e303b 100644 --- a/utils.py +++ b/utils.py @@ -1,4 +1,22 @@ -# utils.py +# Copyright (c) 2026 Ferenc Deak & Static Codes +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. import os import sys import re diff --git a/variables.py b/variables.py index fa151ed..21d9677 100644 --- a/variables.py +++ b/variables.py @@ -1,3 +1,23 @@ +# Copyright (c) 2026 Ferenc Deak & Static Codes +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + cpp_extensions = [".c", ".cpp", ".cxx", ".c++", ".cc"] header_extensions = [".h", ".hpp", ".hxx", ".h++", ".hh"] qrc_extensions = [".qrc"] From c90cbdca64b41c60f40721433a7a35bc26974de2 Mon Sep 17 00:00:00 2001 From: Static <210101922+Static-Codes@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:36:51 -0400 Subject: [PATCH 3/6] Refactored process_a_define() and added TODO comments in processing.py --- processing.py | 272 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 216 insertions(+), 56 deletions(-) diff --git a/processing.py b/processing.py index 62c9ebc..439d31b 100644 --- a/processing.py +++ b/processing.py @@ -32,7 +32,7 @@ filelist_to_string, find_wildcard_file, moc_header ) - +# TODO: Refactor variable names for clarity and add comments documenting this workflow. def process_argument(line): s = line[len("AC_ARG_ENABLE("):].strip() arg_name = "" @@ -69,6 +69,7 @@ def process_argument(line): variables.options[arg_name].set_status(is_enabled) +# Currently works as intended no work is required here as of 2026-07-21. def processable_line(line): possible_starts = ["AC_ARG_ENABLE(", "AM_CONDITIONAL(", "AC_DEFINE(", "AC_CONFIG_FILES("] for start in possible_starts: @@ -77,6 +78,7 @@ def processable_line(line): return "" +# TODO: Refactor for clarity def process_conditional(line): stripped_line = line[len("AM_CONDITIONAL("):].strip() define_name = "" @@ -86,71 +88,83 @@ def process_conditional(line): define_name += c bound_option = "" stage = 1 # 1 - skipping, 2 - adding + for c in stripped_line: - if (c == '"' or c == ' ' or c == '=') and stage == 2: - break - if stage == 2: - bound_option += c - if c == '$': - stage = 2 + if (c == '"' or c == ' ' or c == '=') and stage == 2: break + if stage == 2: bound_option += c + if c == '$': stage = 2 bound_option = bound_option.replace("-", "_") if bound_option in variables.options: variables.options[bound_option].set_define(define_name) + else: variables.options[bound_option] = Option(bound_option, "", "", define_name, "", "") +# Refactored on 2026-07-21 to address an conditional formatting bug. def process_a_define(line): + if not line.startswith("AC_DEFINE("): + return + stripped_line = line[len("AC_DEFINE("):].strip() define_string = "" defined_to_value = "" define_description = "" - stage = 1 # 1 - parsing the define name, 2 parsing the define value, 3 - parsing the define description - sqp = 0 - roup = 1 + stage = 1 # 1: name, 2: value, 3: description + sqp = 0 # The depth of the square bracket + roup = 0 # The depth of the round parenthesis depth (This starts at 0 since '(' will be stripped below.) + for c in stripped_line: - if c == '[': - sqp += 1 - if c == ']': - sqp -= 1 - if c == '(': - roup += 1 - if c == ')': - roup -= 1 + if c == '[': sqp += 1 + elif c == ']': sqp -= 1 + elif c == '(': roup += 1 + elif c == ')': if roup == 0: break - if c == ',' and sqp == 0: + roup -= 1 + + elif c == ',' and sqp == 0 and roup == 0: stage += 1 - if stage == 4: - break - if stage == 1 and c != ',': - define_string += c - elif stage == 2 and c != ',': - defined_to_value += c - elif stage == 3: - define_description += c - + continue # Skips the inclusion of a comma for seperation. + + if stage == 1: define_string += c + elif stage == 2: defined_to_value += c + elif stage == 3: define_description += c + + # Sanitizing values to remove any leftover whitespace from the raw slicing operations. + define_string = define_string.strip() + defined_to_value = defined_to_value.strip() + define_description = define_description.strip() + + # Extracting the name of the option from its value if the name contains a shell variable (example: '$var') variable_name = "" - stage = 1 # 1 - skipping, 2 - adding - for c in stripped_line: - if c == '"' and stage == 2: - break - if stage == 2: - variable_name += c - if c == '$': - stage = 2 - - variables.temp_defines[define_string] = {} - variables.temp_defines[define_string]["name"] = define_string - variables.temp_defines[define_string]["option_name"] = variable_name.upper() - variables.temp_defines[define_string]["description"] = define_description - variables.temp_defines[define_string]["value"] = defined_to_value - variables.temp_defines[define_string]["used"] = 0 - + in_var = False + for c in defined_to_value: + + if in_var: + + if c.isalnum() or c == '_': + variable_name += c + + else: + break + + elif c == '$': + in_var = True + + variables.temp_defines[define_string] = { + "name": define_string, + "option_name": variable_name.upper(), + "description": define_description, + "value": defined_to_value, + "used": 0 + } + +# TODO: Refactor for clarity and break this into multiple smaller helper functions. def process_makefile_am(file): if not os.path.isfile(file): warning("File not found:", file) @@ -342,23 +356,28 @@ def process_makefile_am(file): variables.required_directories.append(current_directory + "/" + subdir) variables.extra_content[current_directory] = extra_dir - +# TODO: Refactor for clarity and break this into multiple smaller helper functions. def process_libraries(): for library in variables.libraries: current_content = "" added_files = [] + if variables.generate_comments: current_content += "# Generating the library " + library.name + "\n" + current_content += "set(project \"" + library.referred_name + "\")\n\n" current_content += "set(${project} \"\")\n" condition_required = "" for condition in library.conditional_appends: conditional_append = library.conditional_appends[condition] + if condition: used_condition = False + for opt_name in variables.options: option = variables.options[opt_name] + if option.get_define() == condition: used_condition = True current_content += "\nif(" + option.get_name() + ")\n" @@ -366,15 +385,19 @@ def process_libraries(): condition_required = option.get_name() for cond_append in conditional_append: + if '$' in cond_append: nice_var_name = remove_garbage(cond_append) + if nice_var_name in library.just_variables: + l = [item for sublist in library.just_variables[nice_var_name] for item in sublist] unfolded_conditionals = filelist_to_string(l, library.directory, 8) if unfolded_conditionals: current_content += " list(APPEND ${project}_SOURCES" + unfolded_conditionals added_files.append(unfolded_conditionals) + else: file_list = "\n ".join(conditional_append) current_content += " list(APPEND ${project}_SOURCES\n " + file_list @@ -394,13 +417,16 @@ def process_libraries(): add_regardless = [] unfolded_conditionals = "" for cond_append in conditional_append: + if '$' in cond_append: nice_var_name = remove_garbage(cond_append) + if nice_var_name in library.just_variables: l = [item for sublist in library.just_variables[nice_var_name] for item in sublist] unfolded_conditionals = filelist_to_string(l, library.directory, 8) else: add_regardless.append(cond_append) + unfolded_conditionals += filelist_to_string(add_regardless, library.directory, 8) current_content += "list(APPEND ${project}_SOURCES" + unfolded_conditionals added_files.append(unfolded_conditionals) @@ -410,6 +436,7 @@ def process_libraries(): condition_used = False for option in variables.options: + if variables.options[option].get_define() == library.condition: current_content += "if (" + variables.options[option].get_name() + ")\n" condition_required = variables.options[option].get_name() @@ -420,6 +447,7 @@ def process_libraries(): if not condition_used: new_condition = "" + for c in library.condition: new_condition += c @@ -429,30 +457,43 @@ def process_libraries(): filelist = filelist_to_string(library.filelist, library.directory) current_content += " list(APPEND ${project}_SOURCES\n " + filelist + ")\nendif()\n\n" added_files.append(filelist) + else: filelist = filelist_to_string(library.filelist, library.directory) work_list = filelist.strip() current_content += "list(APPEND ${project}_SOURCES\n " + work_list + "\n)\n" added_files.append(work_list) + # Do not remove the else clause here. + # This will reintroduce a bug that causes: + # - add_executable and add_library to be wrapped in a conditional. + # - Compilation issues due to incorrect syntax. + if library.condition: condition_required = library.condition + else: + condition_required = "" if condition_required: current_content += "if (" + condition_required + ")\n" - + has_libraries = False + for file in added_files: lines = file.split('\n') + for raw_line in lines: line = raw_line.strip() + if line and not line.startswith('#'): has_libraries = True break + if has_libraries: break if has_libraries: + if library.target_type == TargetType.LIBRARY: current_content += "add_library ( " +library.referred_name + \ " " + library.type + " " + "${${project}_SOURCES} )\n" @@ -465,9 +506,12 @@ def process_libraries(): final_flags = "" to_work_with_flags = [] + for flag in flags: + if not '$' in flag and not '@' in flag: final_flags += replace_quotes(flag) + " " + else: to_work_with_flags.append(flag) @@ -478,17 +522,24 @@ def process_libraries(): final_flags = [] done = False + while not done: + for flag in to_work_with_flags: + if '$' in flag: - m = re.search(r"\$\(.*\)", flag) - if m: - desired_var = remove_garbage(m.group(0)) + match = re.search(r"\$\(.*\)", flag) + + if match: + desired_var = remove_garbage(match.group(0)) + if desired_var == "top_srcdir": to_work_with_flags.append("{CMAKE_SOURCE_DIR}") + elif desired_var in variables.config_ac_variables: for v in variables.config_ac_variables[desired_var]["value"]: final_flags.append(v) + if flag in to_work_with_flags: to_work_with_flags.remove(flag) @@ -508,37 +559,51 @@ def process_libraries(): if include_directories: current_content += "\ntarget_include_directories( " + library.referred_name + " PRIVATE" + for i_d in include_directories: current_content += "\n " + i_d.replace("-I", "") + current_content += "\n)\n" if library.link_with_libs: final_link_list = "\ntarget_link_libraries( " + library.referred_name + for link_name in library.link_with_libs: target_link_lib = make_nice_library_name(link_name) + if target_link_lib.startswith("$"): clean_tll_name = remove_garbage(target_link_lib) + if clean_tll_name in library.just_variables: + for more_link_names_list in library.just_variables[clean_tll_name]: + for real_link in more_link_names_list: final_link_list += "\n " + make_nice_library_name(real_link) else: + if target_link_lib.startswith("@"): canname = target_link_lib.replace("@", '') + if canname in variables.config_ac_variables: libs = variables.config_ac_variables[canname]["value"] + for lib in "".join(libs).split(): link_lib_name = make_nice_library_name(lib) + if not link_lib_name.startswith("-L"): final_link_list += "\n " + link_lib_name else: final_link_list += "\n# " + target_link_lib + " # <-- FIX THIS" warning ("WARNING: ", target_link_lib, " in ", library.directory + "/CMakeLists.txt", " was not indentifiable, fix it manually") + else: final_link_list += "\n " + target_link_lib + final_link_list += "\n)\n" current_content += final_link_list + else: warning("No source files found for ", library.referred_name, ". Skipping target generation." ) @@ -552,17 +617,19 @@ def process_libraries(): cmake_file_holder.contained_libraries_content.append(current_content) cmake_file_holder.libraries.append(library) - +# TODO: Refactor variable names in this function for added clarity. def process_config_files(line): s = line[len("AC_CONFIG_FILES("):].strip() s = remove_garbage(s) vec = s.split() + for file in vec: makefile_am = variables.working_directory + "/" + file + ".am" + if os.path.isfile(makefile_am): process_makefile_am(makefile_am) - +# TODO: Refactor variable names for clarity and break this into multiple smaller helper functions. def process_configure_ac(fname): """Processes the configure.ac and creates some options for the outgoing CmakeLists.txt""" with open(fname) as f: @@ -576,17 +643,24 @@ def process_configure_ac(fname): current_line = "" previous_line = "" line_distance = 0 + for i in range(len(content)): + if len(current_line) > 1 and '$' in current_line: previous_line = current_line line_distance = 0 + else: + if line_distance < 3: + if '$' in current_line: previous_line = current_line line_distance = 0 + else: line_distance += 1 + else: previous_line = "" line_distance += 1 @@ -597,63 +671,81 @@ def process_configure_ac(fname): continue if '=' in current_line: + if current_line[0].isalpha(): j = 0 varname = "" + while current_line[j].isalnum() or current_line[j] == '_': varname += current_line[j] j += 1 + while current_line[j].isspace(): j += 1 + if current_line[j] == '=': var_value = "" j += 1 + while j < len(current_line): var_value += current_line[j] j += 1 + if not varname in variables.config_ac_variables: variables.config_ac_variables[varname] = {} variables.config_ac_variables[varname]["value"] = [] + variables.config_ac_variables[varname]["value"].append(var_value) method = processable_line(current_line) if method: full_line = "" while True: + current_line = content[i].strip() full_line += current_line + " " parco = count_parentheses(full_line) + if parco == 0: break + else: i += 1 + if method == "AC_DEFINE": full_line += previous_line + parameters = {'line': full_line} function_list[method](**parameters) for option_name in variables.options: option = variables.options[option_name] + for temp_define_name in variables.temp_defines: temp_define = variables.temp_defines[temp_define_name] enter = False + if option.get_define() == temp_define["name"]: option.set_define_description(temp_define["description"]) option.set_define_value(temp_define["value"]) temp_define["used"] = 1 enter = True + if option.get_name() == temp_define["option_name"]: option.set_define(temp_define["name"]) option.set_define_description(temp_define["description"]) option.set_define_value(temp_define["value"]) temp_define["used"] = 1 enter = True + if enter: break for temp_define_name in variables.temp_defines: temp_define = variables.temp_defines[temp_define_name] + if temp_define["used"] == 0: + for option_name in variables.options: option = variables.options[option_name] td_upper = temp_define_name @@ -661,11 +753,12 @@ def process_configure_ac(fname): opt_upper = option_name opt_upper= opt_upper.upper() sim_v = similar(td_upper, opt_upper) + if (sim_v > 0.5) or (td_upper in opt_upper) or (opt_upper in td_upper): option.add_extra_define(temp_define_name) temp_define["used"] = 1 - +# TODO: Add comments documenting this workflow. def generate_default_cmake(req_dir): """Generates default CMakeLists.txt in the given directory with content of source files""" projname = req_dir.split("/")[-1] @@ -673,11 +766,15 @@ def generate_default_cmake(req_dir): sources += "set(${project}_SOURCES\n" has_code = False files = glob.glob(req_dir + "/*.c*") + if files: has_code = True + for f in files: sources += "\t${CMAKE_CURRENT_SOURCE_DIR}/" + f.split("/")[-1] + "\n" + files = glob.glob(req_dir + "/*.h*") + for f in files: sources += "\t${CMAKE_CURRENT_SOURCE_DIR}/" + f.split("/")[-1] + "\n" @@ -685,22 +782,26 @@ def generate_default_cmake(req_dir): r_cmake_file = open(req_dir + "/CMakeLists.txt", "w") r_cmake_file.write(f"cmake_minimum_required(VERSION {variables.cmake_minimum_version})\n") + if has_code: r_cmake_file.write(sources) r_cmake_file.write("add_library(${project} STATIC ${${project}_SOURCES} )") - r_cmake_file.close() + r_cmake_file.close() +# TODO: Add comments documenting this workflow. def process_cmake_file_directories(): """Adds extra content to the correct cmake file""" for dirname in variables.extra_content: extra_c = variables.extra_content[dirname] + if not dirname in variables.cmake_files: variables.cmake_files[dirname] = CMakeFile(dirname) + c_cmake_file = variables.cmake_files[dirname] c_cmake_file.extra_content = extra_c - +# TODO: Refactor variable names for clarity and add comments documenting this workflow. def create_cmakefile(path, cpps, headers, module): cpps_found = False headers_found = False @@ -732,24 +833,30 @@ def create_cmakefile(path, cpps, headers, module): if headers: headers_found = True f.write("set(${project}_HEADERS\n") + for fn in headers: + if not moc_header(pjoin(path,fn)): f.write(" ${CMAKE_CURRENT_SOURCE_DIR}/" + fn + "\n") + else: moc_headers.append(fn) + f.write(")\n\n") if moc_headers: mocs_found = True f.write("set(${project}_MOC_HEADERS\n") + for fn in moc_headers: f.write(" ${CMAKE_CURRENT_SOURCE_DIR}/" + fn + "\n") + f.write(")\n\n") f.close() return cpps_found, headers_found, mocs_found, full_module - +# TODO: Add comments for clarity and break this into multiple smaller helper functions. def convert_sourcetree_to_cmake(start_path): print("Converting: {}".format(start_path)) @@ -771,6 +878,7 @@ def convert_sourcetree_to_cmake(start_path): for source_tree_entry in source_tree_entries: full_path = pjoin(start_path, source_tree_entry) + if os.path.isfile(full_path): file_name, file_extension = os.path.splitext(source_tree_entry) file_extension = file_extension.lower() @@ -807,8 +915,10 @@ def convert_sourcetree_to_cmake(start_path): modules.append(sub_module) if mocs_found: + if not variables.cmake_automoc: f.write("qt_wrap_cpp(${project}_MOC_SOURCES ${${project}_MOC_HEADERS})\n") + else: f.write("set(CMAKE_INCLUDE_CURRENT_DIR ON)\n") f.write("set(CMAKE_AUTOMOC ON)\n") @@ -842,6 +952,7 @@ def convert_sourcetree_to_cmake(start_path): return used_module +# TODO: Add comments and rename variables for clarity also break this into multiple smaller helper functions. def convert_qmake_project(dir, fn): print("\nQMake project started in {} for {}".format(dir, fn)) with open(fn) as f: @@ -850,9 +961,12 @@ def convert_qmake_project(dir, fn): new_content = [] i = 0 + while i < len(content): cl = content[i] + if cl: + if cl[0] == '#': i = i + 1 continue @@ -870,6 +984,7 @@ def convert_qmake_project(dir, fn): i = j new_content.append(cl) + i = i + 1 for l in new_content: @@ -888,15 +1003,19 @@ def convert_qmake_project(dir, fn): print(clp) if clp and len(clp) > 1: + if clp[0].upper() == "TEMPLATE": qmake_proj_type = clp[1] + elif clp[0].upper() == "TARGET": qmake_target_name = clp[1] + elif clp[0].upper().startswith("QT"): if clp[0].endswith('+'): clp1s = clp[1].split() for c in clp1s: qmake_required_qt.add(c) + elif clp[0].endswith('-'): clp1s = clp[1].split() for c in clp1s: @@ -927,8 +1046,10 @@ def convert_qmake_project(dir, fn): if qmake_required_qt: req_qt_comps = "find_package(Qt5 COMPONENTS " + for comp in qmake_required_qt: req_qt_comps += comp.capitalize() + " " + req_qt_comps += "REQUIRED)\n" cmake_file.write(req_qt_comps) @@ -942,6 +1063,7 @@ def convert_qmake_project(dir, fn): cmake_file.write("\nset(${project}_SOURCES\n") for src in srcs: cmake_file.write("\t" + "${CMAKE_CURRENT_SOURCE_DIR}/" + src.strip() + "\n") + cmake_file.write(")\n\n") headers = [] @@ -955,42 +1077,58 @@ def convert_qmake_project(dir, fn): if headers: cmake_file.write("\nset(${project}_HEADERS\n") for header in headers: + if header.startswith('$$'): varname = header[2:] found = False + for vn in qvars.keys(): + if vn.startswith(varname): vv = qvars[vn] found = True + for v in vv: fn = v.strip() + if not moc_header(pjoin(variables.working_directory, fn)): cmake_file.write("\t" + "${CMAKE_CURRENT_SOURCE_DIR}/" + fn + "\n") + else: + if not variables.cmake_automoc: moc_headers.append(fn) + else: cmake_file.write("\t" + "${CMAKE_CURRENT_SOURCE_DIR}/" + fn + "\n") + break + if not found: print("Error in qmake: var {} not found".format(vn)) + else: cmake_file.write("\t" + "${CMAKE_CURRENT_SOURCE_DIR}/" + header.strip() + "\n") + cmake_file.write(")\n\n") if not variables.cmake_automoc: cmake_file.write("set(${project}_MOC_HEADERS\n") + for mh in moc_headers: fn = mh.strip() cmake_file.write("\t" + "${CMAKE_CURRENT_SOURCE_DIR}/" + fn + "\n") + cmake_file.write(")\n") cmake_file.write("qt_wrap_cpp( ${project}_MOC_SOURCES ${${project}_MOC_HEADERS} )\n") + else: cmake_file.write("set(CMAKE_INCLUDE_CURRENT_DIR ON)\n") cmake_file.write("set(CMAKE_AUTOMOC ON)\n") resources = [] for k in qvars.keys(): + if k.startswith("RESOURCES"): resources = qvars[k] break @@ -998,6 +1136,7 @@ def convert_qmake_project(dir, fn): res_to_add = [] if resources: cmake_file.write("\nset(CMAKE_AUTORCC ON)\n") + for resource in resources: res_file = resource res_file_name = os.path.basename(resource) @@ -1008,6 +1147,7 @@ def convert_qmake_project(dir, fn): if qmake_proj_type.upper() == "APP": cmake_file.write("\n\nadd_executable( ${project} ${${project}_SOURCES} ") + if qmake_proj_type.upper() == "LIB": cmake_file.write("\n\nadd_library( ${project} ${${project}_SOURCES} ") @@ -1019,33 +1159,42 @@ def convert_qmake_project(dir, fn): cmake_file.close() sys.exit(0) - +# TODO: Add comments for clarity and break this into multiple smaller helper functions. def convert(): if variables.quick: + if not variables.working_directory: variables.working_directory = os.getcwd() + convert_sourcetree_to_cmake(variables.working_directory) sys.exit() configure_ac = find_file("configure.ac", variables.working_directory) if configure_ac: process_configure_ac(configure_ac) + else: qmake_pro = find_wildcard_file("*.pro", variables.working_directory) + if qmake_pro: + for current_qmake_pro in qmake_pro: convert_qmake_project(variables.working_directory, current_qmake_pro) + sys.exit(0) if variables.recursive: msg_rec = "" + else: msg_rec = "non " + warning(variables.working_directory + "/configure.ac not found. Performing " + msg_rec + "recursive source dump in: " + variables.working_directory) convert_sourcetree_to_cmake(variables.working_directory) sys.exit() cmake_file = open(variables.working_directory + "/CMakeLists.txt", "w") + if variables.generate_comments: cmake_file.write("# Autogenerated by auto2cmake on {0}\n\n# Options\n\n". format(time.strftime("%Y-%m-%d %H:%M:%S"))) @@ -1093,6 +1242,7 @@ def convert(): if len(option[1].get_define()) >= 1: extra = remove_garbage(option[1].get_define_value()) cmake_file.write(" file(APPEND ${{CONFIG_H}} \"#define {0} {1}\\n\\n\")\n".format(option[1].get_define(), replace_quotes(extra))) + else: cmake_file.write(" file(APPEND ${{CONFIG_H}} \"#define HAVE_{0} \\n\\n\")\n".format(option[1].get_name())) @@ -1110,6 +1260,7 @@ def convert(): for temp_define_name in variables.temp_defines: temp_define = variables.temp_defines[temp_define_name] + if temp_define["used"] == 0: extra_value = remove_garbage(temp_define["value"]) extra_description = replace_quotes(remove_garbage(temp_define["description"])) @@ -1117,13 +1268,17 @@ def convert(): cmake_file.write("file(APPEND ${{CONFIG_H}} \"#define {0} {1} \\n\\n \")\n".format(temp_define_name, replace_quotes(extra_value))) cmake_file.write("\n") + if variables.generate_comments: cmake_file.write("# Setting the include directory for the application to find config.h\n") + cmake_file.write("include_directories( ${CMAKE_BINARY_DIR} )") cmake_file.write("\n") + if variables.generate_comments: cmake_file.write("# Since we have created a config.h add a global define for it\n") + cmake_file.write("add_definitions( \"-DHAVE_CONFIG_H\" )") cmake_file.close() @@ -1133,16 +1288,20 @@ def convert(): for cmakefile_name in variables.cmake_files: cfile = variables.cmake_files[cmakefile_name] + if os.path.isfile(cfile.directory + "/CMakeLists.txt"): + if variables.working_directory != cfile.directory: os.remove(cfile.directory + "/CMakeLists.txt") for cmakefile_name in variables.cmake_files: cfile = variables.cmake_files[cmakefile_name] new_cmake_file = open(cfile.directory + "/CMakeLists.txt", "a") + if cfile.directory in variables.required_directories: variables.required_directories.remove(cfile.directory) new_cmake_file.write(cfile.extra_content) + for content in cfile.contained_libraries_content: new_cmake_file.write(content) new_cmake_file.close() @@ -1151,6 +1310,7 @@ def convert(): if final_list: warning("WARNING!!! Creating default CMakeLists.txt in the directories below. Don't forget to fix these later") + for req_dir in final_list: warning("Default CMakeLists.txt in:", req_dir) generate_default_cmake(req_dir) \ No newline at end of file From 47c494904fa222176b6553c9b3f893922674fff8 Mon Sep 17 00:00:00 2001 From: Static <210101922+Static-Codes@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:40:57 -0400 Subject: [PATCH 4/6] add: params recursive_scan, skip_test, keep_test_artifacts + add: CMake compilation tests to workflow --- tests/functions.py | 48 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/tests/functions.py b/tests/functions.py index 15a5125..a8b4879 100644 --- a/tests/functions.py +++ b/tests/functions.py @@ -43,7 +43,12 @@ def run_command(command: str, cwd=cwd, packages_to_check=[], command_should_fail return result -def run_test(repo_url: str, project_name, packages_to_check: List[str], test_number: int, cmake_version: Optional[str], test_info: str = "Not Provided", test_should_fail: bool = False) -> bool: +def run_test(repo_url: str, project_name, packages_to_check: List[str], test_number: int, cmake_version: Optional[str], test_info: str = "Not Provided", test_should_fail: bool = False, keep_test_artifacts: bool = False, skip_test: bool = False, recursive_scan: bool = False) -> bool: + + if skip_test: + print(f"Skipping Test #{test_number}") + return False + print(f"Running Test #{test_number}") print(f"Test Description: {test_info}\n") @@ -61,7 +66,7 @@ def run_test(repo_url: str, project_name, packages_to_check: List[str], test_num print("Please delete the directory manually.") print(f"Error Log: {e}\n") - print(f"Action 1/5: Cloning the repository from: {repo_url}\n") + print(f"Action 1/6: Cloning the repository from: {repo_url}\n") res = run_command(command=f"git clone {repo_url}", packages_to_check=["git"]) @@ -78,7 +83,7 @@ def run_test(repo_url: str, project_name, packages_to_check: List[str], test_num print(f"Error: {project_path} does not exist.") sys.exit(1) - print(f"Action 2/5: Cleaning up existing CMakeLists.txt in {project_name}\n") + print(f"Action 2/6: Cleaning up existing CMakeLists.txt in {project_name}\n") for root, dirs, files in os.walk(project_path): for file in files: if file == "CMakeLists.txt": @@ -90,9 +95,12 @@ def run_test(repo_url: str, project_name, packages_to_check: List[str], test_num # Using -v to specify a CMake version override. test_command += f" -v {cmake_version}" if cmake_version is not None else test_command + + # Using -r if recursive_scan is set to True. + test_command += f" -r" if recursive_scan else test_command - print(f"Action 3/5: Running auto2cmake.py for {project_name}") + print(f"Action 3/6: Running auto2cmake.py for {project_name}") print(f"Command: {test_command}\n") res = run_command(test_command, command_should_fail=test_should_fail) @@ -109,24 +117,44 @@ def run_test(repo_url: str, project_name, packages_to_check: List[str], test_num cmake_version_string = "CMake" + f" {cmake_version}" if cmake_version is not None else "" - print(f"Action 4/5: Attempting to configure {project_name} using {cmake_version_string}\n") + print(f"Action 4/6: Attempting to configure {project_name} using {cmake_version_string}\n") build_dir = os.path.join(cwd, f"{project_name}_build_test") if os.path.exists(build_dir): shutil.rmtree(build_dir) os.makedirs(build_dir) - # Warning are expected, but aslong as the return code is 0, the conversion operation was successful. res = run_command(f"cmake -S {project_path} -B {build_dir}") + # Tracking the status of both the config and build phases + test_passed = False + if res.returncode == 0: print(f"SUCCESS: {project_name} configured with {cmake_version_string}!\n") + + build_cmd = f"cmake --build {build_dir}" + print(f"Action 5/6: Attempting to compile {project_name}\nCommand: {build_cmd}\n") + + # Attempting to compile the newly translate project. + # If this fails, then the translation done in processing.py needs to be refactored further. + build_res = run_command(build_cmd) + + if build_res.returncode == 0: + print(f"SUCCESS: {project_name} compiled successfully!\n") + test_passed = True + else: + print(f"FAILURE: Unable to compile {project_name}, exit code {build_res.returncode}.\n") + else: print(f"FAILURE: Unable to configure {project_name} using {cmake_version_string}.\n") - print("Action 5/5: Removing build artifacts associated with this test.\n") - shutil.rmtree(project_path) - shutil.rmtree(build_dir) + print("Action 6/6: Removing build artifacts associated with this test.\n") + + if not keep_test_artifacts: + shutil.rmtree(project_path) + if os.path.exists(build_dir): + shutil.rmtree(build_dir) + print("SUCCESS: Removed build artifacts associated with this test.\n\n") - return res.returncode == 0 + return test_passed \ No newline at end of file From 179ca8299bc46e7548470d0113ec11f54b0500f7 Mon Sep 17 00:00:00 2001 From: Static <210101922+Static-Codes@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:42:23 -0400 Subject: [PATCH 5/6] add: Intentionally failing test to properly identify and squash remaining bugs --- tests/run_tests.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/tests/run_tests.py b/tests/run_tests.py index 74dee8c..306ad21 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -40,7 +40,9 @@ packages_to_check=["git"], test_number=1, test_info="Testing compatibility with gnupg using a supported legacy version of CMake (3.1)", - cmake_version="3.1" + cmake_version="3.1", + # skip_test=True, + recursive_scan=True ) }, @@ -55,7 +57,10 @@ packages_to_check=["git"], test_number=2, test_info="Testing compatibility with cpython, which caused issues in older releases of auto2cmake.", - cmake_version="3.28.3" + cmake_version="3.28.3", + # skip_test=True, + # keep_test_artifacts=True, + recursive_scan=True ) }, @@ -71,7 +76,9 @@ test_number=3, test_info="Testing the version parsing logic against an unsupported version of CMake (2.7)", cmake_version="2.7", - test_should_fail=True + test_should_fail=True, + # skip_test=True, + recursive_scan=True ) }, @@ -87,7 +94,28 @@ test_number=4, test_info="Testing the version parsing logic against an unreleased version of CMake (6.9)", cmake_version="6.9", - test_should_fail=True + test_should_fail=True, + # skip_test=True, + recursive_scan=True + ) + }, + + { + "args": { + "repo_url": "https://github.com/strongswan/strongswan", + "project_name": "strongswan" + }, + "result": run_test( + repo_url="https://github.com/strongswan/strongswan", + project_name="strongswan", + packages_to_check=["git"], + test_number=5, + test_info="Testing another large project recommended by Autotools users.", + cmake_version="3.28.3", + test_should_fail=False, + # keep_test_artifacts=True, + # skip_test=True, + recursive_scan=True ) }, From 8db0e3b7002fa756e234f7c83f95344528614159 Mon Sep 17 00:00:00 2001 From: Static <210101922+Static-Codes@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:44:58 -0400 Subject: [PATCH 6/6] readded: Important comment with information for the status code of the cmake build command --- tests/functions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functions.py b/tests/functions.py index a8b4879..04d02f6 100644 --- a/tests/functions.py +++ b/tests/functions.py @@ -123,6 +123,7 @@ def run_test(repo_url: str, project_name, packages_to_check: List[str], test_num shutil.rmtree(build_dir) os.makedirs(build_dir) + # Warning are expected, but aslong as the return code is 0, the conversion operation was successful. res = run_command(f"cmake -S {project_path} -B {build_dir}") # Tracking the status of both the config and build phases