From d03d12a74f08973b272266810ee37514aaecbacc Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Tue, 3 Sep 2019 14:32:43 +0200 Subject: [PATCH] Add Qml plugin example support Extend the example writing section to handle qml plugins. Detection is based on whether the plugin config is detected an the target links against Qml. add_qt_qml_module() now uses the the public facing API underneath. Change-Id: I2ccd06548e7b5b18c9dc3338b835bc115fa97809 Reviewed-by: Simon Hausmann --- cmake/QtBuild.cmake | 79 ++++++----- util/cmake/pro2cmake.py | 293 +++++++++++++++++++++++++++++++++------- 2 files changed, 289 insertions(+), 83 deletions(-) diff --git a/cmake/QtBuild.cmake b/cmake/QtBuild.cmake index 9e7db08f9b5..fc30e704189 100644 --- a/cmake/QtBuild.cmake +++ b/cmake/QtBuild.cmake @@ -2035,7 +2035,8 @@ endfunction() function(add_qml_module target) set(qml_module_optional_args - CPP_PLUGIN + DESIGNER_SUPPORTED + DO_NOT_INSTALL ) set(qml_module_single_args @@ -2043,12 +2044,19 @@ function(add_qml_module target) TARGET_PATH VERSION QML_PLUGINDUMP_DEPENDENCIES + CLASSNAME + ) + + set(qml_module_multi_args + IMPORTS + TYPEINFO + DEPENDENCIES ) qt_parse_all_arguments(arg "add_qml_module" "${__add_qt_plugin_optional_args};${qml_module_optional_args}" "${__add_qt_plugin_single_args};${qml_module_single_args}" - "${__add_qt_plugin_multi_args}" ${ARGN}) + "${__add_qt_plugin_multi_args};${qml_module_multi_args}" ${ARGN}) if (NOT arg_URI) message(FATAL_ERROR "add_qml_module called without specifying the module's uri. Please specify one using the URI parameter.") @@ -2081,11 +2089,7 @@ function(add_qml_module target) # If we have no sources, but qml files, create a custom target so the # qml file will be visibile in an IDE. - if (NOT arg_CPP_PLUGIN) - add_custom_target(${target} - SOURCES ${arg_QML_FILES} - ) - else() + if (arg_SOURCES) add_qt_plugin(${target} TYPE qml_plugin @@ -2095,21 +2099,37 @@ function(add_qml_module target) ) endif() - # Set target properties - set_target_properties(${target} - PROPERTIES - QT_QML_MODULE_TARGET_PATH ${arg_TARGET_PATH} - QT_QML_MODULE_URI ${arg_URI} - QT_RESOURCE_PREFIX "/qt-project.org/imports/${arg_TARGET_PATH}" - QT_QML_MODULE_VERSION ${arg_VERSION} + + if (arg_CPP_PLUGIN) + set(no_create_option DO_NOT_CREATE_TARGET) + endif() + + if (arg_CLASSNAME) + set(classname_arg CLASSNAME ${arg_CLASSNAME}) + endif() + + if (arg_DESIGNER_SUPPORTED) + set(designer_supported_arg DESIGNER_SUPPORTED) + endif() + + qt6_add_qml_module(${target} + ${designer_supported_arg} + ${no_create_option} + ${classname_arg} + RESOURCE_PREFIX "/qt-project.org/imports" + TARGET_PATH ${arg_TARGET_PATH} + URI ${arg_URI} + VERSION ${arg_VERSION} + QML_FILES ${arg_QML_FILES} + IMPORTS "${arg_IMPORTS}" + TYPEINFO "${arg_TYPEINFO}" + DO_NOT_INSTALL + DO_NOT_CREATE_TARGET + DEPENDENCIES ${arg_DEPENDENCIES} + RESOURCE_EXPORT "${INSTALL_CMAKE_NAMESPACE}${target}Targets" ) - qt_add_qmltypes_target(${target} - TARGET_PATH "${arg_TARGET_PATH}" - IMPORT_VERSION "${arg_VERSION}" - IMPORT_NAME "${arg_URI}" - QML_PLUGINDUMP_DEPENDENCIES "${arg_QML_PLUGINDUMP_DEPENDENCIES}") - + get_target_property(qmldir_file ${target} QT_QML_MODULE_QMLDIR_FILE) qt_path_join(qml_module_install_dir ${QT_INSTALL_DIR} "${INSTALL_QMLDIR}/${arg_TARGET_PATH}") set(plugin_types "${CMAKE_CURRENT_SOURCE_DIR}/plugins.qmltypes") if (EXISTS ${plugin_types}) @@ -2126,25 +2146,10 @@ function(add_qml_module target) endif() endif() - if (NOT QT_BUILD_SHARED_LIBS) - # only embed qmldir on static builds. Some qml modules may request - # their qml files to be embedded into their binary - string(REPLACE "." "_" qmldir_resource_name ${arg_TARGET_PATH}) - string(REPLACE "/" "_" qmldir_resource_name ${qmldir_resource_name}) - set(qmldir_resource_name "${qmldir_resource_name}_qmldir") - get_target_property(target_resource_prefix ${target} QT_RESOURCE_PREFIX) - set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/qmldir" - PROPERTIES QT_RESOURCE_ALIAS "qmldir" - ) - add_qt_resource(${target} ${qmldir_resource_name} - PREFIX ${target_resource_prefix} - FILES "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" - ) - endif() qt_copy_or_install( FILES - "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" + "${qmldir_file}" DESTINATION "${qml_module_install_dir}" ) @@ -2152,7 +2157,7 @@ function(add_qml_module target) if(QT_WILL_INSTALL) # qmldir should also be copied to the cmake binary dir when doing # prefix builds - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" + file(COPY "${qmldir_file}" DESTINATION "${QT_BUILD_DIR}/${INSTALL_QMLDIR}/${arg_TARGET_PATH}" ) endif() diff --git a/util/cmake/pro2cmake.py b/util/cmake/pro2cmake.py index 4aa9accdd16..b79e1f97cf2 100755 --- a/util/cmake/pro2cmake.py +++ b/util/cmake/pro2cmake.py @@ -364,6 +364,113 @@ def write_add_qt_resource_call( return output +class QmlDirFileInfo: + def __init__(self, file_path: str, type_name: str): + self.file_path = file_path + self.version = "" + self.type_name = type_name + self.internal = False + self.singleton = False + + +class QmlDir: + def __init__(self): + self.module = "" + self.plugin_name = "" + self.plugin_path = "" + self.classname = "" + self.imports = [] # typing.List[str] + self.type_names = {} # typing.Dict[str, QmlDirFileInfo] + self.type_infos = [] # typing.List[str] + self.depends = [] # typing.List[[str,str]] + self.designer_supported = False + + def __str__(self): + str = "module: {}\n".format(self.module) + str += "plugin: {} {}\n".format(self.plugin_name, self.plugin_path) + str += "classname: {}\n".format(self.classname) + str += "type_infos:{}\n".format(" \n".join(self.type_infos)) + str += "imports:{}\n".format(" \n".join(self.imports)) + str += "dependends: \n" + for dep in self.depends: + str += " {} {}\n".format(dep[0], dep[1]) + str += "designer supported: {}\n".format(self.designer_supported) + str += "type_names:\n" + for key in self.type_names: + file_info = self.type_names[key] + str += " type:{} version:{} path:{} internal:{} singleton:{}\n".format( + file_info.type_name, + file_info.version, + file_info.type_name, + file_info.file_path, + file_info.internal, + file_info.singleton, + ) + return str + + def get_or_create_file_info(self, path: str, type_name: str) -> QmlDirFileInfo: + if not path in self.type_names: + self.type_names[path] = QmlDirFileInfo(path, type_name) + qmldir_file = self.type_names[path] + if qmldir_file.type_name != type_name: + raise RuntimeError("Registered qmldir file type_name does not match.") + return qmldir_file + + def handle_file_internal(self, type_name: str, path: str): + qmldir_file = self.get_or_create_file_info(path, type_name) + qmldir_file.internal = True + + def handle_file_singleton(self, type_name: str, version: str, path: str): + qmldir_file = self.handle_file(type_name, version, path) + qmldir_file.singleton = True + + def handle_file(self, type_name: str, version: str, path: str) -> QmlDirFileInfo: + qmldir_file = self.get_or_create_file_info(path, type_name) + qmldir_file.version = version + qmldir_file.type_name = type_name + qmldir_file.path = path + return qmldir_file + + def from_file(self, path: str): + f = open(path, "r") + if not f: + raise RuntimeError("Failed to open qmldir file at: {}".format(str)) + for line in f: + if line.startswith("#"): + continue + line = line.strip().replace("\n", "") + if len(line) == 0: + continue + + entries = line.split(" ") + if len(entries) == 0: + raise RuntimeError("Unexpected QmlDir file line entry") + if entries[0] == "module": + self.module = entries[1] + elif entries[0] == "[singleton]": + self.handle_file_singleton(entries[1], entries[2], entries[3]) + elif entries[0] == "internal": + self.handle_file_internal(entries[1], entries[2]) + elif entries[0] == "plugin": + self.plugin_name = entries[1] + if len(entries) > 2: + self.plugin_path = entries[2] + elif entries[0] == "classname": + self.classname = entries[1] + elif entries[0] == "typeinfo": + self.type_infos.append(entries[1]) + elif entries[0] == "depends": + self.depends.append((entries[1], entries[2])) + elif entries[0] == "designersupported": + self.designer_supported = True + elif entries[0] == "import": + self.imports.append(entries[1]) + elif len(entries) == 3: + self.handle_file(entries[0], entries[1], entries[2]) + else: + raise RuntimeError("Uhandled qmldir entry {}".format(line)) + + def fixup_linecontinuation(contents: str) -> str: # Remove all line continuations, aka a backslash followed by # a newline character with an arbitrary amount of whitespace @@ -2352,9 +2459,6 @@ def write_main_part( write_android_part(cm_fh, name, scopes[0], indent) - if is_qml_plugin: - write_qml_plugin_qml_files(cm_fh, name, scopes[0], indent) - ignored_keys_report = write_ignored_keys(scopes[0], spaces(indent)) if ignored_keys_report: cm_fh.write(ignored_keys_report) @@ -2534,7 +2638,9 @@ def write_find_package_section( cm_fh.write("\n") -def write_example(cm_fh: IO[str], scope: Scope, gui: bool = False, *, indent: int = 0) -> str: +def write_example( + cm_fh: IO[str], scope: Scope, gui: bool = False, *, indent: int = 0, is_plugin: bool = False +) -> str: binary_name = scope.TARGET assert binary_name @@ -2551,9 +2657,53 @@ def write_example(cm_fh: IO[str], scope: Scope, gui: bool = False, *, indent: in (public_libs, private_libs) = extract_cmake_libraries(scope) write_find_package_section(cm_fh, public_libs, private_libs, indent=indent) - add_executable = f'add_{"qt_gui_" if gui else ""}executable({binary_name}' + add_target = "" - write_all_source_file_lists(cm_fh, scope, add_executable, indent=0) + qmldir = None + if is_plugin: + if "qml" in scope.get("QT"): + # Get the uri from the destination directory + dest_dir = scope.expandString("DESTDIR") + if not dest_dir: + dest_dir = "${CMAKE_CURRENT_BINARY_DIR}" + else: + uri = os.path.basename(dest_dir) + dest_dir = "${CMAKE_CURRENT_BINARY_DIR}/" + dest_dir + + add_target = f"qt6_add_qml_module({binary_name}\n" + add_target += f' OUTPUT_DIRECTORY "{dest_dir}"\n' + add_target += " VERSION 1.0\n" + add_target += ' URI "{}"\n'.format(uri) + + qmldir_file_path = scope.get_files("qmldir.files") + if qmldir_file_path: + qmldir_file_path = os.path.join(os.getcwd(), qmldir_file_path[0]) + else: + qmldir_file_path = os.path.join(os.getcwd(), "qmldir") + + if os.path.exists(qmldir_file_path): + qml_dir = QmlDir() + qml_dir.from_file(qmldir_file_path) + if qml_dir.designer_supported: + add_target += " DESIGNER_SUPPORTED\n" + if len(qml_dir.classname) != 0: + add_target += f" CLASSNAME {qml_dir.classname}\n" + if len(qml_dir.imports) != 0: + add_target += " IMPORTS\n{}".format(" \n".join(qml_dir.imports)) + if len(qml_dir.depends) != 0: + add_target += " DEPENDENCIES\n" + for dep in qml_dir.depends: + add_target += f" {dep[0]}/{dep[1]}\n" + + add_target += " INSTALL_LOCATION ${INSTALL_EXAMPLEDIR}\n)\n\n" + add_target += f"target_sources({binary_name} PRIVATE" + else: + add_target = f"add_library({binary_name} MODULE" + + else: + add_target = f'add_{"qt_gui_" if gui else ""}executable({binary_name}' + + write_all_source_file_lists(cm_fh, scope, add_target, indent=0) cm_fh.write(")\n") @@ -2561,14 +2711,18 @@ def write_example(cm_fh: IO[str], scope: Scope, gui: bool = False, *, indent: in cm_fh, scope, f"target_include_directories({binary_name} PUBLIC", indent=0, footer=")" ) write_defines( - cm_fh, scope, f"target_compile_definitions({binary_name} PUBLIC", indent=0, footer=")" + cm_fh, + scope, + "target_compile_definitions({} PUBLIC".format(binary_name), + indent=0, + footer=")", ) write_list( cm_fh, private_libs, "", indent=indent, - header=f"target_link_libraries({binary_name} PRIVATE\n", + header="target_link_libraries({} PRIVATE\n".format(binary_name), footer=")", ) write_list( @@ -2576,21 +2730,24 @@ def write_example(cm_fh: IO[str], scope: Scope, gui: bool = False, *, indent: in public_libs, "", indent=indent, - header=f"target_link_libraries({binary_name} PUBLIC\n", + header="target_link_libraries({} PUBLIC\n".format(binary_name), footer=")", ) write_compile_options( - cm_fh, scope, f"target_compile_options({binary_name}", indent=0, footer=")" + cm_fh, scope, "target_compile_options({}".format(binary_name), indent=0, footer=")" ) write_resources(cm_fh, binary_name, scope, indent=indent, is_example=True) + if qmldir: + write_qml_plugin_epilogue(cm_fh, binary_name, scope, qmldir, indent) + cm_fh.write( - f"\ninstall(TARGETS {binary_name}\n" - ' RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"\n' - ' BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"\n' - ' LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"\n' - ")\n" + "\ninstall(TARGETS {}\n".format(binary_name) + + ' RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"\n' + + ' BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"\n' + + ' LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"\n' + + ")\n" ) return binary_name @@ -2602,6 +2759,7 @@ def write_plugin(cm_fh, scope, *, indent: int = 0) -> str: extra = [] + qmldir = None plugin_type = scope.get_string("PLUGIN_TYPE") is_qml_plugin = any("qml_plugin" == s for s in scope.get("_LOADED")) plugin_function_name = "add_qt_plugin" @@ -2609,11 +2767,11 @@ def write_plugin(cm_fh, scope, *, indent: int = 0) -> str: extra.append(f"TYPE {plugin_type}") elif is_qml_plugin: plugin_function_name = "add_qml_module" - write_qml_plugin(cm_fh, plugin_name, scope, indent=indent, extra_lines=extra) + qmldir = write_qml_plugin(cm_fh, plugin_name, scope, indent=indent, extra_lines=extra) plugin_class_name = scope.get_string("PLUGIN_CLASS_NAME") if plugin_class_name: - extra.append(f"CLASS_NAME {plugin_class_name}") + extra.append("CLASS_NAME {}".format(plugin_class_name)) write_main_part( cm_fh, @@ -2626,6 +2784,10 @@ def write_plugin(cm_fh, scope, *, indent: int = 0) -> str: known_libraries={}, extra_keys=[], ) + + if qmldir: + write_qml_plugin_epilogue(cm_fh, plugin_name, scope, qmldir, indent) + return plugin_name @@ -2634,18 +2796,12 @@ def write_qml_plugin( target: str, scope: Scope, *, - extra_lines: List[str] = [], + extra_lines: typing.List[str] = [], indent: int = 0, - **kwargs: Any, -): + **kwargs: typing.Any, +) -> QmlDir: # Collect other args if available indent += 2 - # scope_config = scope.get('CONFIG') - # is_embedding_qml_files = False - - sources = scope.get_files("SOURCES") - if len(sources) != 0: - extra_lines.append("CPP_PLUGIN") target_path = scope.get_string("TARGETPATH") if target_path: @@ -2675,28 +2831,74 @@ def write_qml_plugin( if plugindump_dep: extra_lines.append(f'QML_PLUGINDUMP_DEPENDENCIES "{plugindump_dep}"') + qmldir_file_path = os.path.join(os.getcwd(), "qmldir") + if os.path.exists(qmldir_file_path): + qml_dir = QmlDir() + qml_dir.from_file(qmldir_file_path) + if qml_dir.designer_supported: + extra_lines.append("DESIGNER_SUPPORTED") + if len(qml_dir.classname) != 0: + extra_lines.append(f"CLASSNAME {qml_dir.classname}") + if len(qml_dir.imports) != 0: + extra_lines.append("IMPORTS\n {}".format("\n ".join(qml_dir.imports))) + if len(qml_dir.depends) != 0: + extra_lines.append("DEPENDENCIES") + for dep in qml_dir.depends: + extra_lines.append(f" {dep[0]}/{dep[1]}") -def write_qml_plugin_qml_files(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0): + return qml_dir + + return None + + +def write_qml_plugin_epilogue( + cm_fh: typing.IO[str], target: str, scope: Scope, qmldir: QmlDir, indent: int = 0 +): qml_files = scope.get_files("QML_FILES", use_vpath=True) if qml_files: + indent_0 = spaces(indent) + indent_1 = spaces(indent + 1) # Quote file paths in case there are spaces. - qml_files = [f'"{f}"' for f in qml_files] + qml_files_quoted = ['"{}"'.format(f) for f in qml_files] - qml_files_line = "\n{spaces(indent+1)}".join(qml_files) - cm_fh.write(f"\n{spaces(indent)}set(qml_files\n{spaces(indent+1)}{qml_files_line}\n)\n") - - target_path = scope.get_string("TARGETPATH", inherit=True) - target_path_mangled = target_path.replace("/", "_") - target_path_mangled = target_path_mangled.replace(".", "_") - resource_name = "qmake_" + target_path_mangled cm_fh.write( - f"\n{spaces(indent)}add_qt_resource({target} {resource_name}\n" - f"{spaces(indent+1)}FILES\n{spaces(indent+2)}${{qml_files}}\n)\n" + "\n{}set(qml_files\n{}{}\n)\n".format( + indent_0, indent_1, "\n{}".format(indent_1).join(qml_files_quoted) + ) ) - cm_fh.write(f"\nqt_install_qml_files({target}\n FILES ${{qml_files}}\n)\n\n") + for qml_file in qml_files: + if qml_file in qmldir.type_names: + qmldir_file_info = qmldir.type_names[qml_file] + cm_fh.write( + "{}set_source_files_properties({} PROPERTIES\n".format(indent_0, qml_file) + ) + cm_fh.write( + '{}QT_QML_SOURCE_VERSION "{}"\n'.format(indent_1, qmldir_file_info.version) + ) + # Only write typename if they are different, CMake will infer + # the name by default + if ( + os.path.splitext(os.path.basename(qmldir_file_info.path))[0] + != qmldir_file_info.type_name + ): + cm_fh.write( + "{}QT_QML_SOURCE_TYPENAME {}\n".format(indent_1, qmldir_file_info.type_name) + ) + cm_fh.write("{}QT_QML_SOURCE_INSTALL TRUE\n".format(indent_1)) + if qmldir_file_info.singleton: + cm_fh.write("{}QT_QML_SINGLETON_TYPE TRUE\n".format(indent_1)) + if qmldir_file_info.internal: + cm_fh.write("{}QT_QML_INTERNAL_TYPE TRUE\n".format(indent_1)) + cm_fh.write("{})\n".format(indent_0)) + + cm_fh.write( + "\n{}qt6_target_qml_files({}\n{}FILES\n{}${{qml_files}}\n)\n".format( + indent_0, target, indent_1, spaces(indent + 2) + ) + ) def handle_app_or_lib( @@ -2711,8 +2913,13 @@ def handle_app_or_lib( any("qt_plugin" == s for s in scope.get("_LOADED")) or is_qml_plugin or "plugin" in config ) target = "" + gui = all( + val not in config for val in ["console", "cmdline"] + ) and "testlib" not in scope.expand("QT") - if is_plugin: + if is_example: + target = write_example(cm_fh, scope, gui, indent=indent, is_plugin=is_plugin) + elif is_plugin: assert not is_example target = write_plugin(cm_fh, scope, indent=indent) elif is_lib or "qt_module" in scope.get("_LOADED"): @@ -2722,17 +2929,11 @@ def handle_app_or_lib( assert not is_example target = write_tool(cm_fh, scope, indent=indent) else: - gui = all( - val not in config for val in ["console", "cmdline"] - ) and "testlib" not in scope.expand("QT") if "testcase" in config or "testlib" in config or "qmltestcase" in config: assert not is_example target = write_test(cm_fh, scope, gui, indent=indent) else: - if is_example: - target = write_example(cm_fh, scope, gui, indent=indent) - else: - target = write_binary(cm_fh, scope, gui, indent=indent) + target = write_binary(cm_fh, scope, gui, indent=indent) # ind = spaces(indent) write_source_file_list(