#!/usr/bin/env python3 ############################################################################# ## ## Copyright (C) 2018 The Qt Company Ltd. ## Contact: https://www.qt.io/licensing/ ## ## This file is part of the plugins of the Qt Toolkit. ## ## $QT_BEGIN_LICENSE:GPL-EXCEPT$ ## Commercial License Usage ## Licensees holding valid commercial Qt licenses may use this file in ## accordance with the commercial license agreement provided with the ## Software or, alternatively, in accordance with the terms contained in ## a written agreement between you and The Qt Company. For licensing terms ## and conditions see https://www.qt.io/terms-conditions. For further ## information use the contact form at https://www.qt.io/contact-us. ## ## GNU General Public License Usage ## Alternatively, this file may be used under the terms of the GNU ## General Public License version 3 as published by the Free Software ## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ## included in the packaging of this file. Please review the following ## information to ensure the GNU General Public License requirements will ## be met: https://www.gnu.org/licenses/gpl-3.0.html. ## ## $QT_END_LICENSE$ ## ############################################################################# import json_parser import posixpath import re import sys from typing import Optional, Set from textwrap import dedent import os from special_case_helper import SpecialCaseHandler from helper import ( map_qt_library, featureName, map_platform, find_3rd_party_library_mapping, generate_find_package_info, get_compile_test_dependent_library_mapping, ) knownTests = set() # type: Set[str] class LibraryMapping: def __init__(self, package: str, resultVariable: str, appendFoundSuffix: bool = True) -> None: self.package = package self.resultVariable = resultVariable self.appendFoundSuffix = appendFoundSuffix def map_tests(test: str) -> Optional[str]: testmap = { "c99": "c_std_99 IN_LIST CMAKE_C_COMPILE_FEATURES", "c11": "c_std_11 IN_LIST CMAKE_C_COMPILE_FEATURES", "x86SimdAlways": "ON", # FIXME: Make this actually do a compile test. "aesni": "TEST_subarch_aes", "avx": "TEST_subarch_avx", "avx2": "TEST_subarch_avx2", "avx512f": "TEST_subarch_avx512f", "avx512cd": "TEST_subarch_avx512cd", "avx512dq": "TEST_subarch_avx512dq", "avx512bw": "TEST_subarch_avx512bw", "avx512er": "TEST_subarch_avx512er", "avx512pf": "TEST_subarch_avx512pf", "avx512vl": "TEST_subarch_avx512vl", "avx512ifma": "TEST_subarch_avx512ifma", "avx512vbmi": "TEST_subarch_avx512vbmi", "avx512vbmi2": "TEST_subarch_avx512vbmi2", "avx512vpopcntdq": "TEST_subarch_avx512vpopcntdq", "avx5124fmaps": "TEST_subarch_avx5124fmaps", "avx5124vnniw": "TEST_subarch_avx5124vnniw", "bmi": "TEST_subarch_bmi", "bmi2": "TEST_subarch_bmi2", "cx16": "TEST_subarch_cx16", "f16c": "TEST_subarch_f16c", "fma": "TEST_subarch_fma", "fma4": "TEST_subarch_fma4", "fsgsbase": "TEST_subarch_fsgsbase", "gfni": "TEST_subarch_gfni", "ibt": "TEST_subarch_ibt", "libclang": "TEST_libclang", "lwp": "TEST_subarch_lwp", "lzcnt": "TEST_subarch_lzcnt", "mmx": "TEST_subarch_mmx", "movbe": "TEST_subarch_movbe", "mpx": "TEST_subarch_mpx", "no-sahf": "TEST_subarch_no_shaf", "pclmul": "TEST_subarch_pclmul", "popcnt": "TEST_subarch_popcnt", "prefetchwt1": "TEST_subarch_prefetchwt1", "prfchw": "TEST_subarch_prfchw", "pdpid": "TEST_subarch_rdpid", "rdpid": "TEST_subarch_rdpid", "rdseed": "TEST_subarch_rdseed", "rdrnd": "TEST_subarch_rdseed", # FIXME: Is this the right thing? "rtm": "TEST_subarch_rtm", "shani": "TEST_subarch_sha", "shstk": "TEST_subarch_shstk", "sse2": "TEST_subarch_sse2", "sse3": "TEST_subarch_sse3", "ssse3": "TEST_subarch_ssse3", "sse4a": "TEST_subarch_sse4a", "sse4_1": "TEST_subarch_sse4_1", "sse4_2": "TEST_subarch_sse4_2", "tbm": "TEST_subarch_tbm", "xop": "TEST_subarch_xop", "neon": "TEST_subarch_neon", "iwmmxt": "TEST_subarch_iwmmxt", "crc32": "TEST_subarch_crc32", "vis": "TEST_subarch_vis", "vis2": "TEST_subarch_vis2", "vis3": "TEST_subarch_vis3", "dsp": "TEST_subarch_dsp", "dspr2": "TEST_subarch_dspr2", "altivec": "TEST_subarch_altivec", "spe": "TEST_subarch_spe", "vsx": "TEST_subarch_vsx", "posix-iconv": "TEST_posix_iconv", "sun-iconv": "TEST_sun_iconv", "openssl11": '(OPENSSL_VERSION VERSION_GREATER_EQUAL "1.1.0")', "reduce_exports": "CMAKE_CXX_COMPILE_OPTIONS_VISIBILITY", "libinput_axis_api": "ON", "xlib": "X11_FOUND", "wayland-scanner": "WaylandScanner_FOUND", "3rdparty-hunspell": "VKB_HAVE_3RDPARTY_HUNSPELL", "t9write-alphabetic": "VKB_HAVE_T9WRITE_ALPHA", "t9write-cjk": "VKB_HAVE_T9WRITE_CJK", } if test in testmap: return testmap.get(test, None) if test in knownTests: return f"TEST_{featureName(test)}" return None def cm(ctx, *output): txt = ctx["output"] if txt != "" and not txt.endswith("\n"): txt += "\n" txt += "\n".join(output) ctx["output"] = txt return ctx def readJsonFromDir(path: str) -> str: path = posixpath.join(path, "configure.json") print(f"Reading {path}...") assert posixpath.exists(path) parser = json_parser.QMakeSpecificJSONParser() return parser.parse(path) def processFiles(ctx, data): print(" files:") if "files" in data: for (k, v) in data["files"].items(): ctx[k] = v return ctx def parseLib(ctx, lib, data, cm_fh, cmake_find_packages_set): newlib = find_3rd_party_library_mapping(lib) if not newlib: print(f' XXXX Unknown library "{lib}".') return if newlib.packageName is None: print(f' **** Skipping library "{lib}" -- was masked.') return print(f" mapped library {lib} to {newlib.targetName}.") # Avoid duplicate find_package calls. if newlib.targetName in cmake_find_packages_set: return # If certain libraries are used within a feature, but the feature # is only emitted conditionally with a simple condition (like # 'on Windows' or 'on Linux'), we should enclose the find_package # call for the library into the same condition. emit_if = newlib.emit_if # Only look through features if a custom emit_if wasn't provided. if not emit_if: for feature in data["features"]: feature_data = data["features"][feature] if ( "condition" in feature_data and f"libs.{lib}" in feature_data["condition"] and "emitIf" in feature_data and "config." in feature_data["emitIf"] ): emit_if = feature_data["emitIf"] break if emit_if: emit_if = map_condition(emit_if) cmake_find_packages_set.add(newlib.targetName) find_package_kwargs = {"emit_if": emit_if} if newlib.is_bundled_with_qt: # If a library is bundled with Qt, it has 2 FindFoo.cmake # modules: WrapFoo and WrapSystemFoo. # FindWrapSystemFoo.cmake will try to find the 'Foo' library in # the usual CMake locations, and will create a # WrapSystemFoo::WrapSystemFoo target pointing to the library. # # FindWrapFoo.cmake will create a WrapFoo::WrapFoo target which # will link either against the WrapSystemFoo or QtBundledFoo # target depending on certain feature values. # # Because the following qt_find_package call is for # configure.cmake consumption, we make the assumption that # configure.cmake is interested in finding the system library # for the purpose of enabling or disabling a system_foo feature. find_package_kwargs["use_system_package_name"] = True cm_fh.write(generate_find_package_info(newlib, **find_package_kwargs)) run_library_test = False mapped_library = find_3rd_party_library_mapping(lib) if mapped_library: run_library_test = mapped_library.run_library_test if run_library_test and "test" in data["libraries"][lib]: test = data["libraries"][lib]["test"] write_compile_test( ctx, lib, test, data, cm_fh, manual_library_list=[lib], is_library_test=True ) def lineify(label, value, quote=True): if value: if quote: escaped_value = value.replace('"', '\\"') return f' {label} "{escaped_value}"\n' return f" {label} {value}\n" return "" def map_condition(condition): # Handle NOT: if isinstance(condition, list): condition = "(" + ") AND (".join(condition) + ")" if isinstance(condition, bool): if condition: return "ON" else: return "OFF" assert isinstance(condition, str) mapped_features = {"gbm": "gbm_FOUND"} # Turn foo != "bar" into (NOT foo STREQUAL 'bar') condition = re.sub(r"([^ ]+)\s*!=\s*('.*?')", "(! \\1 == \\2)", condition) condition = condition.replace("!", "NOT ") condition = condition.replace("&&", " AND ") condition = condition.replace("||", " OR ") condition = condition.replace("==", " STREQUAL ") # explicitly handle input.sdk == '': condition = re.sub(r"input\.sdk\s*==\s*''", "NOT INPUT_SDK", condition) last_pos = 0 mapped_condition = "" has_failed = False for match in re.finditer(r"([a-zA-Z0-9_]+)\.([a-zA-Z0-9_+-]+)", condition): substitution = None # appendFoundSuffix = True if match.group(1) == "libs": libmapping = find_3rd_party_library_mapping(match.group(2)) if libmapping and libmapping.packageName: substitution = libmapping.packageName if libmapping.resultVariable: substitution = libmapping.resultVariable if libmapping.appendFoundSuffix: substitution += "_FOUND" # Assume that feature conditions are interested whether # a system library is found, rather than the bundled one # which we always know we can build. if libmapping.is_bundled_with_qt: substitution = substitution.replace("Wrap", "WrapSystem") elif match.group(1) == "features": feature = match.group(2) if feature in mapped_features: substitution = mapped_features.get(feature) else: substitution = f"QT_FEATURE_{featureName(match.group(2))}" elif match.group(1) == "subarch": substitution = f"TEST_arch_{'${TEST_architecture_arch}'}_subarch_{match.group(2)}" elif match.group(1) == "call": if match.group(2) == "crossCompile": substitution = "CMAKE_CROSSCOMPILING" elif match.group(1) == "tests": substitution = map_tests(match.group(2)) elif match.group(1) == "input": substitution = f"INPUT_{featureName(match.group(2))}" elif match.group(1) == "config": substitution = map_platform(match.group(2)) elif match.group(1) == "module": substitution = f"TARGET {map_qt_library(match.group(2))}" elif match.group(1) == "arch": if match.group(2) == "i386": # FIXME: Does this make sense? substitution = "(TEST_architecture_arch STREQUAL i386)" elif match.group(2) == "x86_64": substitution = "(TEST_architecture_arch STREQUAL x86_64)" elif match.group(2) == "arm": # FIXME: Does this make sense? substitution = "(TEST_architecture_arch STREQUAL arm)" elif match.group(2) == "arm64": # FIXME: Does this make sense? substitution = "(TEST_architecture_arch STREQUAL arm64)" elif match.group(2) == "mips": # FIXME: Does this make sense? substitution = "(TEST_architecture_arch STREQUAL mips)" if substitution is None: print(f' XXXX Unknown condition "{match.group(0)}"') has_failed = True else: mapped_condition += condition[last_pos : match.start(1)] + substitution last_pos = match.end(2) mapped_condition += condition[last_pos:] # Space out '(' and ')': mapped_condition = mapped_condition.replace("(", " ( ") mapped_condition = mapped_condition.replace(")", " ) ") # Prettify: condition = re.sub("\\s+", " ", mapped_condition) condition = condition.strip() # Special case for WrapLibClang in qttools condition = condition.replace("TEST_libclang.has_clangcpp", "TEST_libclang") if has_failed: condition += " OR FIXME" return condition def parseInput(ctx, sinput, data, cm_fh): skip_inputs = { "prefix", "hostprefix", "extprefix", "archdatadir", "bindir", "datadir", "docdir", "examplesdir", "external-hostbindir", "headerdir", "hostbindir", "hostdatadir", "hostlibdir", "importdir", "libdir", "libexecdir", "plugindir", "qmldir", "settingsdir", "sysconfdir", "testsdir", "translationdir", "android-arch", "android-ndk", "android-ndk-host", "android-ndk-platform", "android-sdk", "android-toolchain-version", "android-style-assets", "appstore-compliant", "avx", "avx2", "avx512", "c++std", "ccache", "commercial", "compile-examples", "confirm-license", "dbus", "dbus-runtime", "debug", "debug-and-release", "developer-build", "device", "device-option", "f16c", "force-asserts", "force-debug-info", "force-pkg-config", "framework", "gc-binaries", "gdb-index", "gcc-sysroot", "gcov", "gnumake", "gui", "headersclean", "incredibuild-xge", "libudev", "ltcg", "make", "make-tool", "mips_dsp", "mips_dspr2", "mp", "nomake", "opensource", "optimize-debug", "optimize-size", "optimized-qmake", "optimized-tools", "pch", "pkg-config", "platform", "plugin-manifests", "profile", "qreal", "reduce-exports", "reduce-relocations", "release", "rpath", "sanitize", "sdk", "separate-debug-info", "shared", "silent", "qdbus", "sse2", "sse3", "sse4.1", "sse4.2", "ssse3", "static", "static-runtime", "strip", "syncqt", "sysroot", "testcocoon", "use-gold-linker", "warnings-are-errors", "Werror", "widgets", "xplatform", "zlib", "eventfd", "glib", "icu", "inotify", "journald", "pcre", "posix-ipc", "pps", "slog2", "syslog", } if sinput in skip_inputs: print(f" **** Skipping input {sinput}: masked.") return dtype = data if isinstance(data, dict): dtype = data["type"] if dtype == "boolean": print(f" **** Skipping boolean input {sinput}: masked.") return if dtype == "enum": values_line = " ".join(data["values"]) cm_fh.write(f"# input {sinput}\n") cm_fh.write(f'set(INPUT_{featureName(sinput)} "undefined" CACHE STRING "")\n') cm_fh.write( f"set_property(CACHE INPUT_{featureName(sinput)} PROPERTY STRINGS undefined {values_line})\n\n" ) return print(f" XXXX UNHANDLED INPUT TYPE {dtype} in input description") return def get_library_usage_for_compile_test(library): result = {} mapped_library = find_3rd_party_library_mapping(library) if not mapped_library: result["fixme"] = f"# FIXME: use: unmapped library: {library}\n" return result if mapped_library.test_library_overwrite: target_name = mapped_library.test_library_overwrite else: target_name = mapped_library.targetName result["target_name"] = target_name result["package_name"] = mapped_library.packageName result["extra"] = mapped_library.extra return result # Handles config.test/foo/foo.pro projects. def write_standalone_compile_test(cm_fh, ctx, data, config_test_name, is_library_test): rel_test_project_path = f"{ctx['test_dir']}/{config_test_name}" if posixpath.exists(f"{ctx['project_dir']}/{rel_test_project_path}/CMakeLists.txt"): label = "" libraries = [] packages = [] if "label" in data: label = data["label"] if is_library_test and config_test_name in data["libraries"]: if "label" in data["libraries"][config_test_name]: label = data["libraries"][config_test_name]["label"] # If a library entry in configure.json has a test, and # the test uses a config.tests standalone project, we # need to get the package and target info for the # library, and pass it to the test so compiling and # linking succeeds. library_usage = get_library_usage_for_compile_test(config_test_name) if "target_name" in library_usage: libraries.append(library_usage["target_name"]) if "package_name" in library_usage: find_package_arguments = [] find_package_arguments.append(library_usage["package_name"]) if "extra" in library_usage: find_package_arguments.extend(library_usage["extra"]) package_line = "PACKAGE " + " ".join(find_package_arguments) packages.append(package_line) cm_fh.write( f""" qt_config_compile_test("{config_test_name}" LABEL "{label}" PROJECT_PATH "${{CMAKE_CURRENT_SOURCE_DIR}}/{rel_test_project_path}" """ ) if libraries: libraries_string = " ".join(libraries) cm_fh.write(f" LIBRARIES {libraries_string}\n") if packages: packages_string = " ".join(packages) cm_fh.write(f" PACKAGES {packages_string}") cm_fh.write(f")\n") def write_compile_test( ctx, name, details, data, cm_fh, manual_library_list=None, is_library_test=False ): if manual_library_list is None: manual_library_list = [] inherited_test_name = details["inherit"] if "inherit" in details else None inherit_details = None if inherited_test_name and is_library_test: inherit_details = data["libraries"][inherited_test_name]["test"] if not inherit_details: print(f" XXXX Failed to locate inherited library test {inherited_test_name}") if isinstance(details, str): write_standalone_compile_test(cm_fh, ctx, data, details, is_library_test) return def resolve_head(detail): head = detail.get("head", "") if isinstance(head, list): head = "\n".join(head) return head head = "" if inherit_details: head += resolve_head(inherit_details) head += resolve_head(details) sourceCode = head + "\n" def resolve_include(detail, keyword): include = detail.get(keyword, "") if isinstance(include, list): include = "#include <" + ">\n#include <".join(include) + ">" elif include: include = f"#include <{include}>" return include include = "" if is_library_test: if inherit_details: inherited_lib_data = data["libraries"][inherited_test_name] include += resolve_include(inherited_lib_data, "headers") this_lib_data = data["libraries"][name] include += resolve_include(this_lib_data, "headers") else: if inherit_details: include += resolve_include(inherit_details, "include") include += resolve_include(details, "include") sourceCode += include + "\n" def resolve_tail(detail): tail = detail.get("tail", "") if isinstance(tail, list): tail = "\n".join(tail) return tail tail = "" if inherit_details: tail += resolve_tail(inherit_details) tail += resolve_tail(details) sourceCode += tail + "\n" sourceCode += "int main(int argc, char **argv)\n" sourceCode += "{\n" sourceCode += " (void)argc; (void)argv;\n" sourceCode += " /* BEGIN TEST: */\n" def resolve_main(detail): main = detail.get("main", "") if isinstance(main, list): main = "\n".join(main) return main main = "" if inherit_details: main += resolve_main(inherit_details) main += resolve_main(details) sourceCode += main + "\n" sourceCode += " /* END TEST: */\n" sourceCode += " return 0;\n" sourceCode += "}\n" sourceCode = sourceCode.replace('"', '\\"') librariesCmakeName = "" languageStandard = "" compileOptions = "" qmakeFixme = "" cm_fh.write(f"# {name}\n") if "qmake" in details: # We don't really have many so we can just enumerate them all if details["qmake"] == "unix:LIBS += -lpthread": librariesCmakeName = format(featureName(name)) + "_TEST_LIBRARIES" cm_fh.write("if (UNIX)\n") cm_fh.write(" set(" + librariesCmakeName + " pthread)\n") cm_fh.write("endif()\n") elif details["qmake"] == "linux: LIBS += -lpthread -lrt": librariesCmakeName = format(featureName(name)) + "_TEST_LIBRARIES" cm_fh.write("if (LINUX)\n") cm_fh.write(" set(" + librariesCmakeName + " pthread rt)\n") cm_fh.write("endif()\n") elif details["qmake"] == "!winrt: LIBS += runtimeobject.lib": librariesCmakeName = format(featureName(name)) + "_TEST_LIBRARIES" cm_fh.write("if (NOT WINRT)\n") cm_fh.write(" set(" + librariesCmakeName + " runtimeobject)\n") cm_fh.write("endif()\n") elif details["qmake"] == "CONFIG += c++11": # do nothing we're always in c++11 mode pass elif details["qmake"] == "CONFIG += c++11 c++14": languageStandard = "CXX_STANDARD 14" elif details["qmake"] == "CONFIG += c++11 c++14 c++17": languageStandard = "CXX_STANDARD 17" elif details["qmake"] == "CONFIG += c++11 c++14 c++17 c++2a": languageStandard = "CXX_STANDARD 20" elif details["qmake"] == "QMAKE_CXXFLAGS += -fstack-protector-strong": compileOptions = details["qmake"][18:] else: qmakeFixme = f"# FIXME: qmake: {details['qmake']}\n" library_list = [] test_libraries = manual_library_list if "use" in data: test_libraries += data["use"].split(" ") for library in test_libraries: if len(library) == 0: continue adjusted_library = get_compile_test_dependent_library_mapping(name, library) library_usage = get_library_usage_for_compile_test(adjusted_library) if "fixme" in library_usage: qmakeFixme += library_usage["fixme"] continue else: library_list.append(library_usage["target_name"]) cm_fh.write(f"qt_config_compile_test({featureName(name)}\n") cm_fh.write(lineify("LABEL", data.get("label", ""))) if librariesCmakeName != "" or len(library_list) != 0: cm_fh.write(" LIBRARIES\n") if librariesCmakeName != "": cm_fh.write(lineify("", "${" + librariesCmakeName + "}")) if len(library_list) != 0: cm_fh.write(" ") cm_fh.write("\n ".join(library_list)) cm_fh.write("\n") if compileOptions != "": cm_fh.write(f" COMPILE_OPTIONS {compileOptions}\n") cm_fh.write(" CODE\n") cm_fh.write('"' + sourceCode + '"') if qmakeFixme != "": cm_fh.write(qmakeFixme) if languageStandard != "": cm_fh.write(f"\n {languageStandard}\n") cm_fh.write(")\n\n") # "tests": { # "cxx11_future": { # "label": "C++11 ", # "type": "compile", # "test": { # "include": "future", # "main": [ # "std::future f = std::async([]() { return 42; });", # "(void)f.get();" # ], # "qmake": "unix:LIBS += -lpthread" # } # }, def parseTest(ctx, test, data, cm_fh): skip_tests = { "c11", "c99", "gc_binaries", "posix-iconv", "sun-iconv", "precomile_header", "reduce_exports", "gc_binaries", "libinput_axis_api", "wayland-scanner", "xlib", } if test in skip_tests: print(f" **** Skipping features {test}: masked.") return if data["type"] == "compile": knownTests.add(test) if "test" in data: details = data["test"] else: details = test write_compile_test(ctx, test, details, data, cm_fh) elif data["type"] == "libclang": knownTests.add(test) cm_fh.write(f"# {test}\n") lib_clang_lib = find_3rd_party_library_mapping("libclang") cm_fh.write(generate_find_package_info(lib_clang_lib)) cm_fh.write( dedent( """ if(TARGET WrapLibClang::WrapLibClang) set(TEST_libclang "ON" CACHE BOOL "Required libclang version found." FORCE) endif() """ ) ) cm_fh.write("\n") elif data["type"] == "x86Simd": knownTests.add(test) label = data["label"] cm_fh.write(f"# {test}\n") cm_fh.write(f'qt_config_compile_test_x86simd({test} "{label}")\n') cm_fh.write("\n") # "features": { # "android-style-assets": { # "label": "Android Style Assets", # "condition": "config.android", # "output": [ "privateFeature" ], # "comment": "This belongs into gui, but the license check needs it here already." # }, else: print(f" XXXX UNHANDLED TEST TYPE {data['type']} in test description") def get_feature_mapping(): # This is *before* the feature name gets normalized! So keep - and + chars, etc. feature_mapping = { "alloc_h": None, # handled by alloc target "alloc_malloc_h": None, "alloc_stdlib_h": None, "build_all": None, "ccache": { "autoDetect": "1", "condition": "QT_USE_CCACHE" }, "compiler-flags": None, "cross_compile": {"condition": "CMAKE_CROSSCOMPILING"}, "debug_and_release": { "autoDetect": "1", # Setting this to None has weird effects... "condition": "QT_GENERATOR_IS_MULTI_CONFIG", }, "debug": { "condition": "CMAKE_BUILD_TYPE STREQUAL Debug OR Debug IN_LIST CMAKE_CONFIGURATION_TYPES" }, "dlopen": {"condition": "UNIX"}, "enable_gdb_index": None, "enable_new_dtags": None, "force_debug_info": { "autoDetect": "CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo OR RelWithDebInfo IN_LIST CMAKE_CONFIGURATION_TYPES" }, "framework": { "condition": "APPLE AND BUILD_SHARED_LIBS AND NOT CMAKE_BUILD_TYPE STREQUAL Debug" }, "gc_binaries": None, "gcc-sysroot": None, "gcov": None, "gnu-libiconv": { "condition": "NOT WIN32 AND NOT QNX AND NOT ANDROID AND NOT APPLE AND TEST_posix_iconv AND NOT TEST_iconv_needlib", "enable": "TEST_posix_iconv AND NOT TEST_iconv_needlib", "disable": "NOT TEST_posix_iconv OR TEST_iconv_needlib", }, "GNUmake": None, "host-dbus": None, "iconv": { "condition": "NOT QT_FEATURE_icu AND QT_FEATURE_textcodec AND ( TEST_posix_iconv OR TEST_sun_iconv )" }, "incredibuild_xge": None, "ltcg": { "autoDetect": "1", "condition": "CMAKE_INTERPROCEDURAL_OPTIMIZATION" }, "msvc_mp": None, "optimize_debug": None, "optimize_size": None, # special case to enable implicit feature on WIN32, until ANGLE is ported "opengl-desktop": {"autoDetect": ""}, # special case to disable implicit feature on WIN32, until ANGLE is ported "opengl-dynamic": {"autoDetect": "OFF"}, "opengles2": { # special case to disable implicit feature on WIN32, until ANGLE is ported "condition": "NOT WIN32 AND ( NOT WATCHOS AND NOT QT_FEATURE_opengl_desktop AND GLESv2_FOUND )" }, "simulator_and_device": {"condition": "UIKIT AND NOT QT_UIKIT_SDK"}, "pkg-config": {"condition": "PKG_CONFIG_FOUND"}, "posix-libiconv": { "condition": "NOT WIN32 AND NOT QNX AND NOT ANDROID AND NOT APPLE AND TEST_posix_iconv AND TEST_iconv_needlib", "enable": "TEST_posix_iconv AND TEST_iconv_needlib", "disable": "NOT TEST_posix_iconv OR NOT TEST_iconv_needlib", }, "precompile_header": {"condition": "BUILD_WITH_PCH"}, "profile": None, "qmakeargs": None, "qpa_default_platform": None, # Not a bool! "release": None, "release_tools": None, "rpath": { "autoDetect": "1", "condition": "BUILD_SHARED_LIBS AND UNIX AND NOT WIN32 AND NOT ANDROID", }, "sanitize_address": None, # sanitizer "sanitize_memory": None, "sanitizer": None, "sanitize_thread": None, "sanitize_undefined": None, "shared": { "condition": "BUILD_SHARED_LIBS", "output": [ "publicFeature", "publicQtConfig", "publicConfig", { "type": "define", "name": "QT_STATIC", "prerequisite": "!defined(QT_SHARED) && !defined(QT_STATIC)", "negative": True, }, ], }, "silent": None, "sql-sqlite": {"condition": "QT_FEATURE_datestring"}, "stl": None, # Do we really need to test for this in 2018?! "strip": None, "sun-libiconv": { "condition": "NOT WIN32 AND NOT QNX AND NOT ANDROID AND NOT APPLE AND TEST_sun_iconv", "enable": "TEST_sun_iconv", "disable": "NOT TEST_sun_iconv", }, "tiff": {"condition": "QT_FEATURE_imageformatplugin AND TIFF_FOUND"}, "use_gold_linker": None, "verifyspec": None, # qmake specific... "warnings_are_errors": None, # FIXME: Do we need these? "webp": {"condition": "QT_FEATURE_imageformatplugin AND WrapWebP_FOUND"}, "xkbcommon-system": None, # another system library, just named a bit different from the rest } return feature_mapping def parseFeature(ctx, feature, data, cm_fh): feature_mapping = get_feature_mapping() mapping = feature_mapping.get(feature, {}) if mapping is None: print(f" **** Skipping features {feature}: masked.") return handled = { "autoDetect", "comment", "condition", "description", "disable", "emitIf", "enable", "label", "output", "purpose", "section", } label = mapping.get("label", data.get("label", "")) purpose = mapping.get("purpose", data.get("purpose", data.get("description", label))) autoDetect = map_condition(mapping.get("autoDetect", data.get("autoDetect", ""))) condition = map_condition(mapping.get("condition", data.get("condition", ""))) output = mapping.get("output", data.get("output", [])) comment = mapping.get("comment", data.get("comment", "")) section = mapping.get("section", data.get("section", "")) enable = map_condition(mapping.get("enable", data.get("enable", ""))) disable = map_condition(mapping.get("disable", data.get("disable", ""))) emitIf = map_condition(mapping.get("emitIf", data.get("emitIf", ""))) for k in [k for k in data.keys() if k not in handled]: print(f" XXXX UNHANDLED KEY {k} in feature description") if not output: # feature that is only used in the conditions of other features output = ["internalFeature"] publicFeature = False # #define QT_FEATURE_featurename in public header privateFeature = False # #define QT_FEATURE_featurename in private header negativeFeature = False # #define QT_NO_featurename in public header internalFeature = False # No custom or QT_FEATURE_ defines publicDefine = False # #define MY_CUSTOM_DEFINE in public header publicConfig = False # add to CONFIG in public pri file privateConfig = False # add to CONFIG in private pri file publicQtConfig = False # add to QT_CONFIG in public pri file for o in output: outputType = o if isinstance(o, dict): outputType = o["type"] if outputType in ["varAssign", "varAppend", "varRemove"]: continue elif outputType == "define": publicDefine = True elif outputType == "feature": negativeFeature = True elif outputType == "publicFeature": publicFeature = True elif outputType == "privateFeature": privateFeature = True elif outputType == "internalFeature": internalFeature = True elif outputType == "publicConfig": publicConfig = True elif outputType == "privateConfig": privateConfig = True elif outputType == "publicQtConfig": publicQtConfig = True else: print(f" XXXX UNHANDLED OUTPUT TYPE {outputType} in feature {feature}.") continue if not any( [ publicFeature, privateFeature, internalFeature, publicDefine, negativeFeature, publicConfig, privateConfig, publicQtConfig, ] ): print(f" **** Skipping feature {feature}: Not relevant for C++.") return normalized_feature_name = featureName(feature) def writeFeature( name, publicFeature=False, privateFeature=False, labelAppend="", superFeature=None, autoDetect="", ): if comment: cm_fh.write(f"# {comment}\n") cm_fh.write(f'qt_feature("{name}"') if publicFeature: cm_fh.write(" PUBLIC") if privateFeature: cm_fh.write(" PRIVATE") cm_fh.write("\n") cm_fh.write(lineify("SECTION", section)) cm_fh.write(lineify("LABEL", label + labelAppend)) if purpose != label: cm_fh.write(lineify("PURPOSE", purpose)) cm_fh.write(lineify("AUTODETECT", autoDetect, quote=False)) if superFeature: feature_condition = f"QT_FEATURE_{superFeature}" else: feature_condition = condition cm_fh.write(lineify("CONDITION", feature_condition, quote=False)) cm_fh.write(lineify("ENABLE", enable, quote=False)) cm_fh.write(lineify("DISABLE", disable, quote=False)) cm_fh.write(lineify("EMIT_IF", emitIf, quote=False)) cm_fh.write(")\n") # Write qt_feature() calls before any qt_feature_definition() calls # Default internal feature case. featureCalls = {} featureCalls[feature] = {"name": feature, "labelAppend": "", "autoDetect": autoDetect} # Go over all outputs to compute the number of features that have to be declared for o in output: outputType = o name = feature # The label append is to provide a unique label for features that have more than one output # with different names. labelAppend = "" if isinstance(o, dict): outputType = o["type"] if "name" in o: name = o["name"] labelAppend = f": {o['name']}" if outputType not in ["feature", "publicFeature", "privateFeature"]: continue if name not in featureCalls: featureCalls[name] = {"name": name, "labelAppend": labelAppend} if name != feature: featureCalls[name]["superFeature"] = normalized_feature_name if outputType in ["feature", "publicFeature"]: featureCalls[name]["publicFeature"] = True elif outputType == "privateFeature": featureCalls[name]["privateFeature"] = True elif outputType == "publicConfig": featureCalls[name]["publicConfig"] = True elif outputType == "privateConfig": featureCalls[name]["privateConfig"] = True elif outputType == "publicQtConfig": featureCalls[name]["publicQtConfig"] = True # Write the qt_feature() calls from the computed feature map for _, args in featureCalls.items(): writeFeature(**args) # Write qt_feature_definition() calls for o in output: outputType = o outputArgs = {} if isinstance(o, dict): outputType = o["type"] outputArgs = o # Map negative feature to define: if outputType == "feature": outputType = "define" outputArgs = { "name": f"QT_NO_{normalized_feature_name.upper()}", "negative": True, "value": 1, "type": "define", } if outputType != "define": continue if outputArgs.get("name") is None: print(f" XXXX DEFINE output without name in feature {feature}.") continue out_name = outputArgs.get("name") cm_fh.write(f'qt_feature_definition("{feature}" "{out_name}"') if outputArgs.get("negative", False): cm_fh.write(" NEGATE") if outputArgs.get("value") is not None: cm_fh.write(f' VALUE "{outputArgs.get("value")}"') if outputArgs.get("prerequisite") is not None: cm_fh.write(f' PREREQUISITE "{outputArgs.get("prerequisite")}"') cm_fh.write(")\n") # Write qt_feature_config() calls for o in output: outputType = o name = feature modified_name = name outputArgs = {} if isinstance(o, dict): outputType = o["type"] outputArgs = o if "name" in o: modified_name = o["name"] if outputType not in ["publicConfig", "privateConfig", "publicQtConfig"]: continue config_type = "" if outputType == "publicConfig": config_type = "QMAKE_PUBLIC_CONFIG" elif outputType == "privateConfig": config_type = "QMAKE_PRIVATE_CONFIG" elif outputType == "publicQtConfig": config_type = "QMAKE_PUBLIC_QT_CONFIG" if not config_type: print(" XXXX config output without type in feature {}.".format(feature)) continue cm_fh.write('qt_feature_config("{}" {}'.format(name, config_type)) if outputArgs.get("negative", False): cm_fh.write("\n NEGATE") if modified_name != name: cm_fh.write("\n") cm_fh.write(lineify("NAME", modified_name, quote=True)) cm_fh.write(")\n") def processSummaryHelper(ctx, entries, cm_fh): for entry in entries: if isinstance(entry, str): name = entry cm_fh.write(f'qt_configure_add_summary_entry(ARGS "{name}")\n') elif "type" in entry and entry["type"] in [ "feature", "firstAvailableFeature", "featureList", ]: function_args = [] entry_type = entry["type"] if entry_type in ["firstAvailableFeature", "featureList"]: feature_mapping = get_feature_mapping() unhandled_feature = False for feature_name, value in feature_mapping.items(): # Skip entries that mention a feature which is # skipped by configurejson2cmake in the feature # mapping. This is not ideal, but prevents errors at # CMake configuration time. if not value and f"{feature_name}" in entry["args"]: unhandled_feature = True break if unhandled_feature: print(f" XXXX UNHANDLED FEATURE in SUMMARY TYPE {entry}.") continue if entry_type != "feature": function_args.append(lineify("TYPE", entry_type)) if "args" in entry: args = entry["args"] function_args.append(lineify("ARGS", args)) if "message" in entry: message = entry["message"] function_args.append(lineify("MESSAGE", message)) if "condition" in entry: condition = map_condition(entry["condition"]) function_args.append(lineify("CONDITION", condition, quote=False)) entry_args_string = "".join(function_args) cm_fh.write(f"qt_configure_add_summary_entry(\n{entry_args_string})\n") elif "type" in entry and entry["type"] == "buildTypeAndConfig": cm_fh.write(f"qt_configure_add_summary_build_type_and_config()\n") elif "type" in entry and entry["type"] == "buildMode": message = entry["message"] cm_fh.write(f"qt_configure_add_summary_build_mode({message})\n") elif "type" in entry and entry["type"] == "buildParts": message = entry["message"] cm_fh.write(f'qt_configure_add_summary_build_parts("{message}")\n') elif "section" in entry: section = entry["section"] cm_fh.write(f'qt_configure_add_summary_section(NAME "{section}")\n') processSummaryHelper(ctx, entry["entries"], cm_fh) cm_fh.write(f'qt_configure_end_summary_section() # end of "{section}" section\n') else: print(f" XXXX UNHANDLED SUMMARY TYPE {entry}.") report_condition_mapping = { "(features.rpath || features.rpath_dir) && !features.shared": "(features.rpath || QT_EXTRA_RPATHS) && !features.shared", "(features.rpath || features.rpath_dir) && var.QMAKE_LFLAGS_RPATH == ''": None } def processReportHelper(ctx, entries, cm_fh): feature_mapping = get_feature_mapping() for entry in entries: if isinstance(entry, dict): entry_args = [] if "type" not in entry: print(f" XXXX UNHANDLED REPORT TYPE missing type in {entry}.") continue report_type = entry["type"] if report_type not in ["note", "warning", "error"]: print(f" XXXX UNHANDLED REPORT TYPE unknown type in {entry}.") continue report_type = report_type.upper() entry_args.append(lineify("TYPE", report_type, quote=False)) message = entry["message"] # Replace semicolons, qt_parse_all_arguments can't handle # them due to an escaping bug in CMake regarding escaping # macro arguments. # https://gitlab.kitware.com/cmake/cmake/issues/19972 message = message.replace(";", ",") entry_args.append(lineify("MESSAGE", message)) # Need to overhaul everything to fix conditions. if "condition" in entry: condition = entry["condition"] unhandled_condition = False for feature_name, value in feature_mapping.items(): # Skip reports that mention a feature which is # skipped by configurejson2cmake in the feature # mapping. This is not ideal, but prevents errors at # CMake configuration time. if not value and f"features.{feature_name}" in condition: unhandled_condition = True break if unhandled_condition: print(f" XXXX UNHANDLED CONDITION in REPORT TYPE {entry}.") continue if isinstance(condition, str) and condition in report_condition_mapping: new_condition = report_condition_mapping[condition] if new_condition is None: continue else: condition = new_condition condition = map_condition(condition) entry_args.append(lineify("CONDITION", condition, quote=False)) entry_args_string = "".join(entry_args) cm_fh.write(f"qt_configure_add_report_entry(\n{entry_args_string})\n") else: print(f" XXXX UNHANDLED REPORT TYPE {entry}.") def processInputs(ctx, data, cm_fh): print(" inputs:") if "commandline" not in data: return commandLine = data["commandline"] if "options" not in commandLine: return for input_option in commandLine["options"]: parseInput(ctx, input_option, commandLine["options"][input_option], cm_fh) def processTests(ctx, data, cm_fh): print(" tests:") if "tests" not in data: return for test in data["tests"]: parseTest(ctx, test, data["tests"][test], cm_fh) def processFeatures(ctx, data, cm_fh): print(" features:") if "features" not in data: return for feature in data["features"]: parseFeature(ctx, feature, data["features"][feature], cm_fh) def processLibraries(ctx, data, cm_fh): cmake_find_packages_set = set() print(" libraries:") if "libraries" not in data: return for lib in data["libraries"]: parseLib(ctx, lib, data, cm_fh, cmake_find_packages_set) def processReports(ctx, data, cm_fh): if "summary" in data: print(" summary:") processSummaryHelper(ctx, data["summary"], cm_fh) if "report" in data: print(" report:") processReportHelper(ctx, data["report"], cm_fh) if "earlyReport" in data: print(" earlyReport:") processReportHelper(ctx, data["earlyReport"], cm_fh) def processSubconfigs(path, ctx, data): assert ctx is not None if "subconfigs" in data: for subconf in data["subconfigs"]: subconfDir = posixpath.join(path, subconf) subconfData = readJsonFromDir(subconfDir) subconfCtx = ctx processJson(subconfDir, subconfCtx, subconfData) def processJson(path, ctx, data): ctx["project_dir"] = path ctx["module"] = data.get("module", "global") ctx["test_dir"] = data.get("testDir", "config.tests") ctx = processFiles(ctx, data) destination = posixpath.join(path, "configure.cmake") generated_file = destination + '.gen' with open(generated_file, "w") as cm_fh: cm_fh.write("\n\n#### Inputs\n\n") processInputs(ctx, data, cm_fh) cm_fh.write("\n\n#### Libraries\n\n") processLibraries(ctx, data, cm_fh) cm_fh.write("\n\n#### Tests\n\n") processTests(ctx, data, cm_fh) cm_fh.write("\n\n#### Features\n\n") processFeatures(ctx, data, cm_fh) processReports(ctx, data, cm_fh) if ctx.get("module") == "global": cm_fh.write( '\nqt_extra_definition("QT_VERSION_STR" "\\"${PROJECT_VERSION}\\"" PUBLIC)\n' ) cm_fh.write('qt_extra_definition("QT_VERSION_MAJOR" ${PROJECT_VERSION_MAJOR} PUBLIC)\n') cm_fh.write('qt_extra_definition("QT_VERSION_MINOR" ${PROJECT_VERSION_MINOR} PUBLIC)\n') cm_fh.write('qt_extra_definition("QT_VERSION_PATCH" ${PROJECT_VERSION_PATCH} PUBLIC)\n') # do this late: processSubconfigs(path, ctx, data) handler = SpecialCaseHandler( os.path.abspath(destination), os.path.abspath(generated_file), os.path.abspath(path), convertingProFiles=False, debug=False, ) if handler.handle_special_cases(): os.replace(generated_file, destination) def main(): if len(sys.argv) != 2: print("This scripts needs one directory to process!") quit(1) directory = sys.argv[1] print(f"Processing: {directory}.") data = readJsonFromDir(directory) processJson(directory, {}, data) if __name__ == "__main__": main()