-
Ulf Hermann authoredUlf Hermann authored
- QML CMake API review meeting agenda
- Should we preserve existing plugin target names or change them to use the default?
- Discussion about the behavior of target creation by default
- Should we rename NO_GENERATE_SOURCE to NO_GENERATE_PLUGIN_SOURCE in qt_add_qml_plugin()?
- Should we always enable AUTOMOC for qml modules?
- What should be the default output directory of qml modules? How does it impact tooling or running an app?
- What's our expectation on linking to a backing library without a plugin, and ensure that it works?
- Do we want to restrict qt_target_qml_sources() to require that the target given to it must be a QML module?
- We now run qmllint on the files in the source directory. There won't be any qmldir files there. Is that a problem?
QML CMake API review meeting agenda
Should we preserve existing plugin target names or change them to use the default?
There are certain repos that specify a custom PLUGIN_TARGET with the previously added plugin name, instead of using the automatically generated name. Is there a good reason to keep those? At least for the Qt build itself, we don't need to set the PLUGIN_TARGET names, the auto-generated name should be ok. The advantage of removing, is the code won't be cargo culted into other projects. The advantage of keeping, is user projects can see the explicit target name for deployment purposes, but that's a bit far-fetched.
Just a proposal
qt_add_qml_module(Qml
# When not specified, the default target name will be QmlPlugin, so backing lib name + Plugin suffix
PLUGIN_TARGET QmlPlugin,
# The output name of the plugin when not specified should be lower case target name + snake case _plugin suffix.
FICTIONAL_PLUGIN_OUTPUT_NAME qml_plugin -> # libqml_plugin.so
Ulf: I don't really care about the exact name, but looking at other Qt plugins, we generally seem to use no uppercase letters in plugin names. So, maybe just lowercase whatever it currently does and call it a day.
Decision: TBD
Discussion about the behavior of target creation by default
-
qt_add_qml_module()
callsqt_add_qml_plugin()
by default. Plugin target will be created if it doesn't already exist. -
qt_add_qml_module()
+NO_CREATE_PLUGIN_TARGET
means -> won't callqt_add_qml_plugin()
, user project will do it. You still need to specifyPLUGIN_TARGET
, even though the project will create it later.
All three of the following examples work:
# CASE 1: Pre-create plugin target, NO_CREATE_PLUGIN_TARGET not needed since auto-detected
qt_add_qml_plugin(FooPlugin)
qt_add_qml_module(Foo PLUGIN_TARGET FooPlugin)
# CASE 2: Pre-create plugin target, NO_CREATE_PLUGIN_TARGET explicitly given but not necessary
qt_add_qml_plugin(FooPlugin)
qt_add_qml_module(Foo NO_CREATE_PLUGIN_TARGET PLUGIN_TARGET FooPlugin)
# CASE 3: Prevent plugin target creation, do it later after the call
qt_add_qml_module(Foo NO_CREATE_PLUGIN_TARGET PLUGIN_TARGET FooPlugin)
qt_add_qml_plugin(FooPlugin)
NOTE: qt_internal_add_qml_module()
currently relies on being able to pre-create the plugin target before it calls qt_add_qml_module()
(case 1). It may be possible to modify it to match case 2 if we wanted to eliminate case 1. User projects should be able to do at least cases 2 and 3.
- Are we happy with supporting all of the above cases?
- Should we add an explicit
NO_CREATE_TARGET
option too, which must be provided if passing in an existing target for the backing target?- Would have to think about the case where the plugin and backing target are merged.
Ulf: I'm fine with either way. The only thing out of those options that I see as strictly necessary for users is case 1. If the others are also supported, that's fine with me. There are a few related cases that may or may not interfer here, but which are rather important:
# CASE 4: No plugin at all; executable needs to link or be identical to the backing target
qt_add_qml_module(Foo NO_CREATE_PLUGIN_TARGET)
# CASE 5: Plugin target same as backing target; that is, the whole module lives in the plugin
qt_add_qml_module(Foo PLUGIN_TARGET Foo NO_PLUGIN_OPTIONAL)
Decisions: TBD
Should we rename NO_GENERATE_SOURCE to NO_GENERATE_PLUGIN_SOURCE in qt_add_qml_plugin()?
JIRA: QTBUG-95090
This would make the keyword the same as the associated keyword in qt_add_qml_module()
. Potential counter-argument: the qt_add_qml_plugin()
command name is already about plugins.
Decision: TBD
Should we always enable AUTOMOC for qml modules?
I think we tacitly agreed the answer is yes, but have we fully thought through the consequences of this? Type registration does have code paths for AUTOMOC being off, so it might not be
Decision: TBD
What should be the default output directory of qml modules? How does it impact tooling or running an app?
We previously decided to change the default to ${CMAKE_BINARY_DIR}/qml
. We then discarded the /qml
part, but this made everything get dumped into the top of the build tree. We then decided to put the default back to ${CMAKE_CURRENT_BINARY_DIR}
, but keep the support for a QT_QML_OUTPUT_DIRECTORY
variable to specify the base below which QML modules are output based on their TARGET_PATH
.
- To be confirmed: will qml hot-reloading work? What are the requirements?
Consider an app whose executable is a QML module, and it uses a couple of other QML modules. The main app's URI might be BasicApp
, while QML module it uses might be TimeExample
and OtherStuff
. The main.qml
will likely want to do import TimeExample
, but this won't work at run time using just the resources without adding something to the import path.
Source directory structure:
+-- main.qml
+-- Foo.qml
+-- TimeExample
| +-- Clock.qml
+-- OtherStuff
+-- blah.qml
Build directory structure should be the same as the source directory structure, but will also have qmldir
files. But the resources will be different because they are based on a QML module's TARGET_PATH, which is based on the module's URI. Since we made the app a QML module as well, it has a URI and therefore it has a non-empty TARGET_PATH:
Resources structure:
+-- BasicApp
| +-- main.qml
| +-- Foo.qml
| +-- qmldir
+-- TimeExample
| +-- Clock.qml
| +-- qmldir
+-- OtherStuff
+-- blah.qml
+-- qmldir
Running qmllint
on any of the BasicApp
qml files will find TimeExample
via the implicit import path. But let's say that TimeExample
does an import OtherStuff
and we run qmllint
on TimeExample
's qml files. In order for qmllint
to find OtherStuff
, we would likely need to add the root of the above directory tree to qmllint
's import path. If QT_QML_OUTPUT_DIRECTORY
is set, then that is easy because that variable is precisely the path we would need to add. If that variable is not set, we would have to walk up the directory tree from TimeExample
and add the base dir, then assume all other QML modules will also be under that base location.
- Do we require projects to follow this rigid directory structure?
- It still won't work if some QML modules come from other projects. When installed, things might all be under a coherent directory structure, but at build time, some QML modules might be in separate places.
- Projects sometimes have their own constraints that require them to structure things differently (e.g. conformance to rigid coding standards that dictate directory structures). These constraints are not always reasonable, but the developers might not have any freedom to change them.
- Maybe we need to give projects a way to add import paths to be used only during the build and only for tooling like
qmllint
,qmlcachegen
, etc.- The
IMPORT_PATH
option toqt_add_qml_module()
is for run time, so it can't be used for this.
- The
- Could tooling like
qmllint
,qmlcachegen
, etc. be taught to walk up theTARGET_PATH
of the QML module they are processing and search for imports below its base point automatically? Or do they do this already? It still wouldn't help for QML modules that are in separate parts of the file system (e.g. provided by third party packages).
NOTE: May merge the discussion of the next topic into this one too.
Ulf:
- The
IMPORT_PATH
option toqt_add_qml_module()
is actually not for run time, if I get this right. It's explicitly for qmllint and qmlcachegen(plus). However, we should strive to generate the -I options to qmllint etc automatically. - External projects are generally expected to install their QML modules in the system QML import path, or in some predetermined location that you can just add via engine->addImportPath(). We cannot install into those locations at build time.
- The rigid structure would be there to get you started quickly without messing with import paths. We do provide all the knobs and switches to invent your own project structure if you don't like it.
- Being able to quickly set up a basic project is important. In Qt5, you rarely had to use import paths because you rarely created proper QML modules. So, this part is new to our users. If you cannot get anything started without setting import paths then people will perceive it as broken.
- The case of loading the starting point from the resource file system is important, but separate. Currently you do have to set up an import path for this, and you do have to link all the modules together, and all the plugins need to be optional. That's quite a few extra requirements.
- We might merge those cases and add the extra requirements to the rigid structure. I'm actually not opposed to this, but it should be an explicit step.
- The argument about linting a file from
TimeExample
is important. We still need an import path in the build directory even if we load the starting point from the resource file system by default. For finding the root of the import path a few things have been proposed:- Remove TARGET_PATH from the end of CMAKE_CURRENT_BINARY_DIR. If it doesn't match, warn. Otherwise that's the import path. The upside of this is that it's very simple. The downside is that it dictates a project structure where all the modules are in the same import path (unless externally provided)
- Mark some directories as import path with some CMake construct. Warn if URIs of QML modules below them don't match the target paths. This has the upside of being precise and extensible, but the downside of requiring a manual setting. I don't know if it actually works. We might just automatically mark any directory that contains an executable as that is potentially an application binary dir. If so, we need a way to override it.
- The tools themselves could automatically deduce the import path like the build system would in option 1. This has the upside of requiring no change to the build system and the downside of adding overhead and complexity to the tools. It also might produce false positives for cases where you invent your own project structure.
- Whatever way we choose for finding the import path, we still have the problem of placing the
BasicApp
module in a suitable directory. The following aspects need to be considered:- Typically, the module contained in an executable won't be used by any other module (which you can override, though). Therefore, it does not need to reside in the import path or follow any rules about URI and TARGET_PATH.
- If we derive the import path from the module location, the
BasicApp
module needs to be in a subdirectoryBasicApp
, so that we can compute the import path from its location. That requires us to ditch the concept of building modules into CMAKE_CURRENT_BINARY_DIR ... Or we make special rules for such "starting point" modules. - If we determine the import path independently of the module location, we can build modules into CMAKE_CURRENT_BINARY_DIR without further special casing.
- We need to be able to import the other modules with minimal effort at run time. In the best case this means not setting any explicit import path. This seems to be related to the location of
BasicApp
but is actually not. It's a function of the locations of the executable and the other modules, and of whether we load from the host file system or the resource file system.
Decisions: TBD
What's our expectation on linking to a backing library without a plugin, and ensure that it works?
This absorbs/replaces the following two questions from the first API review meeting:
- Do we want to support static-backing-lib-but-shared-plugin use case?
- How do we support multiple qml modules in a single executable?
Possible scenario is an executable that links to multiple static backing libs (i.e. uses multiple QML modules). Runtime loading is better, assuming we can solve the problem of the linker discarding global initializers. Unclear what the role of shared plugins is in this scenario, needs clarification.
Build dir layout shouldn't matter, the use case involves an executable that links directly to all QML modules' backing libraries and only that executable is installed (if the backing libraries are shared libraries, those would need to be installed too). Nothing else gets installed, everything should be loaded from resources and compiled-in stuff at run time. Assume that none of the QML modules are singletons and all plugins are optional for this particular scenario.
It's a valid use case, but it might need some additional fixes in the qml engine (to be confirmed). Projects would have to add the resource import path under which all its QML modules can be found (e.g. qrc:/
).
Open questions:
- Is there anything we still need to do to support this use case?
- Do we want to document how to set up this scenario for 6.2, or should we delay doing that until we have a robust solution to the problem of preventing the linker from discarding global constructors that register our modules, etc. (affects both shared and static libraries and plugins)? Projects would still be free to try it for 6.2 if they want to handle the linker issues their own way.
- Would a lot of our problems in the previous topic go away if we said executables cannot be QML modules? Executables can link to static libraries as QML modules and not really lose any functionality. The executables would then typically just be a simple
main.qml
, added as an ordinary resource.
Ulf:
- I've invented static-backing-lib/shared-plugin case in order to make it possible to have modules fully contained in their plugins. I've since learned that there is a better way to do this. We can therefore drop this case.
- Multiple QML modules in a single executable. This remains a valid case.
- We could use it to cut down on the number of shared libraries we need to ship for QtQuick.Controls for example. The QML modules can contain singletons in this case. We just need to make the qmldir and qmltypes files available in the right locations. Therefore, build dir layout and conformance to import paths (see above) does matter.
qmllint
etc need to find the qmldir and qmltypes files. The fact that the binary that contains the types is elsewhere doesn't change anything about the logical location of the module. - Optimally, a plugin for such a module would link to the combined dynamic library that contains all the modules (if it is a library and not an executable). However, restricting the case to modules without plugins for now would be fine (and not solve the qqc2 case, but I guess we can hack that somehow).
- I still don't know how a user should set up the project so that the initializers don't get discarded by the linker. Just linking the static backing targets doesn't work, does it? Object libraries are frowned upon, AFAIU. So, how would it work?
- We could use it to cut down on the number of shared libraries we need to ship for QtQuick.Controls for example. The QML modules can contain singletons in this case. We just need to make the qmldir and qmltypes files available in the right locations. Therefore, build dir layout and conformance to import paths (see above) does matter.
Decisions: TBD
qt_target_qml_sources()
to require that the target given to it must be a QML module?
Do we want to restrict Certain simple use cases can use an ordinary target that isn't a QML module and still run qmllint
and qmlcachegen
successfully. QML team has raised concerns that this won't work in general for all but simple cases.
- If we add this constraint, do we still support a non-QML executable with a simple
main.qml
that uses other QML modules? - If so, how would we expect them to add that
main.qml
file to the app resources?-
qt_add_resources()
would work, but seems to go against our "use the providedqt_target_qml_sources()
command for adding qml files" mantra.
-
Decision: TBD
We now run qmllint on the files in the source directory. There won't be any qmldir files there. Is that a problem?
qmllint
gets a set of .qrc
files passed to it. One of those will contain the qmldir
of the module/target. But qmldir
files of any other QML modules won't be found without adding an import path for the build directory. But then you will get a mix of source and build directory files for any imported QML modules. If any warning messages need to refer to those other-QML-module files, you're back to potentially seeing the build directory copies mentioned instead of the source directory files.
Decision: TBD