diff options
Diffstat (limited to 'util/cmake/configurejson2cmake.py')
-rwxr-xr-x | util/cmake/configurejson2cmake.py | 1105 |
1 files changed, 1105 insertions, 0 deletions
diff --git a/util/cmake/configurejson2cmake.py b/util/cmake/configurejson2cmake.py new file mode 100755 index 0000000000..9f93ecafa6 --- /dev/null +++ b/util/cmake/configurejson2cmake.py @@ -0,0 +1,1105 @@ +#!/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 + +from helper import ( + map_qt_library, + featureName, + map_platform, + find_3rd_party_library_mapping, + generate_find_package_info, +) + +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", + } + 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) + + cm_fh.write(generate_find_package_info(newlib, emit_if=emit_if)) + + +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", + "system-xcb": "ON", + "system-freetype": "ON", + "system-pcre2": "ON", + } + + # 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" + + 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", + "harfbuzz", + "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", + "doubleconversion", + "eventfd", + "glib", + "icu", + "inotify", + "journald", + "pcre", + "posix-ipc", + "pps", + "slog2", + "syslog", + "sqlite", + } + + 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 + + +# "tests": { +# "cxx11_future": { +# "label": "C++11 <future>", +# "type": "compile", +# "test": { +# "include": "future", +# "main": [ +# "std::future<int> 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", + "separate_debug_info", # FIXME: see if cmake can do this + "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) + + details = data["test"] + + if isinstance(details, str): + if not ctx["test_dir"]: + print(f" XXXX UNHANDLED TEST SUB-TYPE {details} in test description") + return + + cm_fh.write( + f""" +if(EXISTS "${{CMAKE_CURRENT_SOURCE_DIR}}/{ctx['test_dir']}/{data['test']}/CMakeLists.txt") + qt_config_compile_test("{data['test']}" + LABEL "{data['label']}" + PROJECT_PATH "${{CMAKE_CURRENT_SOURCE_DIR}}/{ctx['test_dir']}/{data['test']}") +endif() +""" + ) + return + + head = details.get("head", "") + if isinstance(head, list): + head = "\n".join(head) + + sourceCode = head + "\n" + + include = details.get("include", "") + if isinstance(include, list): + include = "#include <" + ">\n#include <".join(include) + ">" + elif include: + include = f"#include <{include}>" + + sourceCode += include + "\n" + + tail = details.get("tail", "") + if isinstance(tail, list): + tail = "\n".join(tail) + + sourceCode += tail + "\n" + + sourceCode += "int main(int argc, char **argv)\n" + sourceCode += "{\n" + sourceCode += " (void)argc; (void)argv;\n" + sourceCode += " /* BEGIN TEST: */\n" + + main = details.get("main", "") + if isinstance(main, list): + main = "\n".join(main) + + sourceCode += main + "\n" + + sourceCode += " /* END TEST: */\n" + sourceCode += " return 0;\n" + sourceCode += "}\n" + + sourceCode = sourceCode.replace('"', '\\"') + + librariesCmakeName = "" + languageStandard = "" + qmakeFixme = "" + + cm_fh.write(f"# {test}\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(test)) + "_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(test)) + "_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(test)) + "_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" + else: + qmakeFixme = f"# FIXME: qmake: {details['qmake']}\n" + + library_list = [] + if "use" in data: + for library in data["use"].split(" "): + if len(library) == 0: + continue + + mapped_library = find_3rd_party_library_mapping(library) + if not mapped_library: + qmakeFixme += f"# FIXME: use: unmapped library: {library}\n" + continue + library_list.append(mapped_library.targetName) + + cm_fh.write(f"qt_config_compile_test({featureName(test)}\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") + 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") + + 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 parseFeature(ctx, feature, data, cm_fh): + # 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, + "c11": None, + "c89": None, + "c99": None, + "ccache": None, + "compiler-flags": None, + "cross_compile": None, + "debug_and_release": None, + "debug": None, + "dlopen": {"condition": "UNIX"}, + "doubleconversion": None, + "enable_gdb_index": None, + "enable_new_dtags": None, + "force_debug_info": None, + "framework": {"condition": "APPLE AND BUILD_SHARED_LIBS"}, + "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, + "harfbuzz": {"condition": "harfbuzz_FOUND"}, + "host-dbus": None, + "iconv": { + "condition": "NOT QT_FEATURE_icu AND QT_FEATURE_textcodec AND ( TEST_posix_iconv OR TEST_sun_iconv )" + }, + "incredibuild_xge": None, + "jpeg": {"condition": "QT_FEATURE_imageformatplugin AND JPEG_FOUND"}, + "ltcg": None, + "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 APPLE_WATCHOS AND NOT QT_FEATURE_opengl_desktop AND GLESv2_FOUND )" + }, + "simulator_and_device": {"condition": "APPLE_UIKIT AND NOT QT_UIKIT_SDK"}, + "pkg-config": None, + "posix_fallocate": None, # Only needed for sqlite, which we do not want to build + "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": None, + "profile": None, + "qmakeargs": None, + "qpa_default_platform": None, # Not a bool! + "reduce_relocations": None, + "release": None, + "release_tools": None, + "rpath_dir": None, # rpath related + "rpath": None, + "sanitize_address": None, # sanitizer + "sanitize_memory": None, + "sanitizer": None, + "sanitize_thread": None, + "sanitize_undefined": None, + "separate_debug_info": None, + "shared": None, + "silent": None, + "sql-sqlite": {"condition": "QT_FEATURE_datestring AND SQLite3_FOUND"}, + "stack-protector-strong": None, + "static": None, + "static_runtime": None, + "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", + }, + "system-doubleconversion": None, # No system libraries anymore! + "system-freetype": None, + "system-harfbuzz": None, + "system-jpeg": None, + "system-pcre2": None, + "system-png": None, + "system-sqlite": None, + "system-xcb": None, + "system-zlib": None, + "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 + } + + 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")}"') + 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 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 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["module"] = data.get("module", "global") + ctx["test_dir"] = data.get("testDir", "") + + ctx = processFiles(ctx, data) + + with open(posixpath.join(path, "configure.cmake"), "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) + + 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) + + +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() |