#!/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) 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)) 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"} # 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", "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 ", # "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", "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 = "" compileOptions = "" 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" elif details["qmake"] == "QMAKE_CXXFLAGS += -fstack-protector-strong": compileOptions = details["qmake"][18:] 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") 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") 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": {"condition": "Debug STREQUAL CMAKE_BUILD_TYPE OR Debug IN_LIST CMAKE_CONFIGURATION_TYPES"}, "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, "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! "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"}, "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-sqlite": None, "system-xcb": 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()