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 <simon.hausmann@qt.io>
This commit is contained in:
Leander Beernaert 2019-09-03 14:32:43 +02:00
parent f4aa7255e6
commit d03d12a74f
2 changed files with 289 additions and 83 deletions

View File

@ -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()

View File

@ -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,15 +2929,9 @@ 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)