summaryrefslogtreecommitdiffstats
path: root/util/cmake/configurejson2cmake.py
diff options
context:
space:
mode:
Diffstat (limited to 'util/cmake/configurejson2cmake.py')
-rwxr-xr-xutil/cmake/configurejson2cmake.py1105
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()