diff options
-rw-r--r-- | ciimage/Dockerfile | 1 | ||||
-rw-r--r-- | docs/markdown/Qt5-module.md | 14 | ||||
-rw-r--r-- | docs/markdown/Subprojects.md | 87 | ||||
-rw-r--r-- | mesonbuild/dependencies/ui.py | 4 | ||||
-rw-r--r-- | mesonbuild/interpreterbase.py | 5 | ||||
-rw-r--r-- | mesonbuild/mesonlib.py | 43 | ||||
-rw-r--r-- | mesonbuild/modules/qt.py | 28 | ||||
-rw-r--r-- | test cases/common/16 configure file/config6.h.in | 19 | ||||
-rw-r--r-- | test cases/common/16 configure file/meson.build | 11 | ||||
-rw-r--r-- | test cases/common/16 configure file/prog6.c | 11 | ||||
-rw-r--r-- | test cases/frameworks/4 qt/meson.build | 2 | ||||
-rw-r--r-- | test cases/frameworks/4 qt/q5core.cpp | 18 | ||||
-rw-r--r-- | test cases/frameworks/4 qt/qt4core_fr.ts | 12 | ||||
-rw-r--r-- | test cases/frameworks/4 qt/qt5core_fr.ts | 12 |
14 files changed, 204 insertions, 63 deletions
diff --git a/ciimage/Dockerfile b/ciimage/Dockerfile index a2e3433..05e679e 100644 --- a/ciimage/Dockerfile +++ b/ciimage/Dockerfile @@ -12,4 +12,5 @@ RUN apt-get -y update && apt-get -y upgrade \ && apt-get -y install fpga-icestorm arachne-pnr yosys \ && apt-get -y install gtk-sharp2 gtk-sharp2-gapi libglib2.0-cil-dev \ && apt-get -y install libwmf-dev \ +&& apt-get -y install qt4-linguist-tools qttools5-dev-tools \ && python3 -m pip install hotdoc codecov diff --git a/docs/markdown/Qt5-module.md b/docs/markdown/Qt5-module.md index aea2ae1..b5393a8 100644 --- a/docs/markdown/Qt5-module.md +++ b/docs/markdown/Qt5-module.md @@ -1,7 +1,7 @@ # Qt5 module The Qt5 module provides tools to automatically deal with the various -tools and steps required for Qt. The module has one method. +tools and steps required for Qt. The module has two methods. ## preprocess @@ -12,6 +12,14 @@ This method takes the following keyword arguments: It returns an opaque object that should be passed to a main build target. +## compile_translations (since v0.44.0) + +This method generates the necessary targets to build translation files with lrelease, it takes the following keyword arguments: + - `ts_files`, the list of input translation files produced by Qt's lupdate tool. + - `install` when true, this target is installed during the install step (optional). + - `install_dir` directory to install to (optional). + - `build_by_default` when set to true, to have this target be built by default, that is, when invoking plain ninja; the default value is false (optional). + A simple example would look like this: ```meson @@ -21,6 +29,7 @@ inc = include_directories('includes') moc_files = qt5.preprocess(moc_headers : 'myclass.h', moc_extra_arguments: ['-DMAKES_MY_MOC_HEADER_COMPILE'], include_directories: inc) +translations = qt5.compile_translations(ts_files : 'myTranslation_fr.ts', build_by_default : true) executable('myprog', 'main.cpp', 'myclass.cpp', moc_files, include_directories: inc, dependencies : qt5_dep) @@ -28,5 +37,4 @@ executable('myprog', 'main.cpp', 'myclass.cpp', moc_files, The 'modules' argument is used to include Qt modules in the project. -See the Qt documentation for the [list of -modules](http://doc.qt.io/qt-5/qtmodules.html). +See the Qt documentation for the [list of modules](http://doc.qt.io/qt-5/qtmodules.html). diff --git a/docs/markdown/Subprojects.md b/docs/markdown/Subprojects.md index 923b6a3..14f01d4 100644 --- a/docs/markdown/Subprojects.md +++ b/docs/markdown/Subprojects.md @@ -4,69 +4,76 @@ short-description: Using meson projects as subprojects within other meson projec # Subprojects -Some platforms do not provide a native packaging system. In these cases it is common to bundle all third party libraries in your source tree. This is usually frowned upon because it makes it hard to add these kinds of projects into e.g. those Linux distributions that forbid bundled libraries. - -Meson tries to solve this problem by making it extremely easy to provide both at the same time. The way this is done is that Meson allows you to take any other Meson project and make it a part of your build without (in the best case) any changes to its Meson setup. It becomes a transparent part of the project. The basic idiom goes something like this. +Some platforms do not provide a native packaging system. In these +cases it is common to bundle all third party libraries in your source +tree. This is usually frowned upon because it makes it hard to add +these kinds of projects into e.g. those Linux distributions that +forbid bundled libraries. + +Meson tries to solve this problem by making it extremely easy to +provide both at the same time. The way this is done is that Meson +allows you to take any other Meson project and make it a part of your +build without (in the best case) any changes to its Meson setup. It +becomes a transparent part of the project. The basic idiom goes +something like this. ```meson -dep = dependency('foo', required : false) -if dep.found() - # set up project using external dependency -else - subproject('foo') - # set up rest of project as if foo was provided by this project -endif +dep = dependency('foo', fallback : [subproject_name, variable_name] ``` -All Meson features of the subproject, such as project options keep working and can be set in the master project. There are a few limitations, the most important being that global compiler arguments must be set in the main project before calling subproject. Subprojects must not set global arguments because there is no way to do that reliably over multiple subprojects. To check whether you are running as a subproject, use the `is_subproject` function. - -As an example, suppose we have a simple project that provides a shared library. +As an example, suppose we have a simple project that provides a shared +library. It would be set up like this. ```meson project('simple', 'c') i = include_directories('include') l = shared_library('simple', 'simple.c', include_directories : i, install : true) +simple_dep = declare_dependency(include_directories : i, + link_with : l) ``` -Then we could use that from a master project. First we generate a subdirectory called `subprojects` in the root of the master directory. Then we create a subdirectory called `simple` and put the subproject in that directory. Now the subproject can be used like this. +Then we could use that from a master project. First we generate a +subdirectory called `subprojects` in the root of the master +directory. Then we create a subdirectory called `simple` and put the +subproject in that directory. Now the subproject can be used like +this. ```meson project('master', 'c') -dep = dependency('simple', required : false) -if dep.found() - i = [] - l = [] -else - sp = subproject('simple') # This is a name of a subdirectory in subprojects. - i = sp.get_variable('i') - l = sp.get_variable('l') -endif -exe = executable('prog', 'prog.c', include_directories : i, link_with : l, +dep = dependency('simple', fallback : ['simple', 'simple_dep'] +exe = executable('prog', 'prog.c', dependencies : dep, install : true) ``` -With this setup the system dependency is used when it is available, otherwise we fall back on the bundled version. - -It should be noted that this only works for subprojects that are built with Meson. It can not be used with any other build system. The reason is the simple fact that there is no possible way to do this reliably with mixed build systems. - -Subprojects can use other subprojects, but all subprojects must reside in the top level `subprojects` directory. Recursive use of subprojects is not allowed, though, so you can't have subproject `a` that uses subproject `b` and have `b` also use `a`. - -## Subprojects and dependencies - -A common use case is to use subprojects to provide dependencies on platforms that do not provide them out of the box. This is especially common on Windows. Meson makes this easy while at the same time using system dependencies if are available. The way to do this is to set up a subproject that builds the dependency and has an internal dependency declared like this: +With this setup the system dependency is used when it is available, +otherwise we fall back on the bundled version. If you wish to always +use the embedded version, then you would declare it like this: ```meson -proj_dep = declare_dependency(...) +simple_sp = subproject('simple') +dep = simple_sp.get_variable('simple_dep') ``` -Then you can use the subproject in the master project like this: +All Meson features of the subproject, such as project options keep +working and can be set in the master project. There are a few +limitations, the most important being that global compiler arguments +must be set in the main project before calling subproject. Subprojects +must not set global arguments because there is no way to do that +reliably over multiple subprojects. To check whether you are running +as a subproject, use the `is_subproject` function. -```meson -sp_dep = dependency('subproj_pkgconfig_name', fallback : ['subproj_name', 'proj_dep']) -``` +It should be noted that this only works for subprojects that are built +with Meson. It can not be used with any other build system. The reason +is the simple fact that there is no possible way to do this reliably +with mixed build systems. -This uses the system dependency when available and the self built version if not. If you want to always use the subproject, that is also possible, just use `subproject` and `get_variable` as discussed above to get the dependency object. +Subprojects can use other subprojects, but all subprojects must reside +in the top level `subprojects` directory. Recursive use of subprojects +is not allowed, though, so you can't have subproject `a` that uses +subproject `b` and have `b` also use `a`. # Obtaining subprojects -Meson ships with a dependency system to automatically obtain dependency subprojects. It is documented in the [Wrap dependency system manual](Wrap-dependency-system-manual.md). +Meson ships with a dependency system to automatically obtain +dependency subprojects. It is documented in the [Wrap dependency +system manual](Wrap-dependency-system-manual.md). diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index 3412dc6..837149c 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -207,13 +207,15 @@ class QtBaseDependency(ExternalDependency): moc = ExternalProgram(os.path.join(self.bindir, 'moc'), silent=True) uic = ExternalProgram(os.path.join(self.bindir, 'uic'), silent=True) rcc = ExternalProgram(os.path.join(self.bindir, 'rcc'), silent=True) + lrelease = ExternalProgram(os.path.join(self.bindir, 'lrelease'), silent=True) else: # We don't accept unsuffixed 'moc', 'uic', and 'rcc' because they # are sometimes older, or newer versions. moc = ExternalProgram('moc-' + self.name, silent=True) uic = ExternalProgram('uic-' + self.name, silent=True) rcc = ExternalProgram('rcc-' + self.name, silent=True) - return moc, uic, rcc + lrelease = ExternalProgram('lrelease-' + self.name, silent=True) + return moc, uic, rcc, lrelease def _pkgconfig_detect(self, mods, kwargs): # We set the value of required to False so that we can try the diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index cb82e56..7ccc8b2 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -449,6 +449,11 @@ class InterpreterBase: return obj % 2 != 0 else: raise InterpreterException('int.is_odd() must have no arguments.') + elif method_name == 'to_string': + if not posargs: + return str(obj) + else: + raise InterpreterException('int.to_string() must have no arguments.') else: raise InterpreterException('Unknown method "%s" for an integer.' % method_name) diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 56d347e..686f7d9 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -32,6 +32,13 @@ def detect_meson_py_location(): # $ meson <args> (gets run from /usr/bin/meson) in_path_exe = shutil.which(c_fname) if in_path_exe: + # Special case: when run like "./meson.py <opts>" and user has + # period in PATH, we need to expand it out, because, for example, + # "ninja test" will be run from a different directory. + if '.' in os.environ['PATH'].split(':'): + p, f = os.path.split(in_path_exe) + if p == '' or p == '.': + return os.path.join(os.getcwd(), f) return in_path_exe # $ python3 ./meson.py <args> if os.path.exists(c): @@ -52,7 +59,6 @@ else: python_command = [sys.executable] meson_command = python_command + [detect_meson_py_location()] - # Put this in objects that should not get dumped to pickle files # by accident. import threading @@ -428,19 +434,28 @@ def do_replacement(regex, line, confdata): missing_variables = set() def variable_replace(match): - varname = match.group(1) - if varname in confdata: - (var, desc) = confdata.get(varname) - if isinstance(var, str): - pass - elif isinstance(var, int): - var = str(var) - else: - raise RuntimeError('Tried to replace a variable with something other than a string or int.') + # Pairs of escape characters before '@' or '\@' + if match.group(0).endswith('\\'): + num_escapes = match.end(0) - match.start(0) + return '\\' * (num_escapes // 2) + # Single escape character and '@' + elif match.group(0) == '\\@': + return '@' + # Template variable to be replaced else: - missing_variables.add(varname) - var = '' - return var + varname = match.group(1) + if varname in confdata: + (var, desc) = confdata.get(varname) + if isinstance(var, str): + pass + elif isinstance(var, int): + var = str(var) + else: + raise RuntimeError('Tried to replace a variable with something other than a string or int.') + else: + missing_variables.add(varname) + var = '' + return var return re.sub(regex, variable_replace, line), missing_variables def do_mesondefine(line, confdata): @@ -473,7 +488,7 @@ def do_conf_file(src, dst, confdata): raise MesonException('Could not read input file %s: %s' % (src, str(e))) # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define # Also allow escaping '@' with '\@' - regex = re.compile(r'(?<!\\)@([-a-zA-Z0-9_]+)@') + regex = re.compile(r'(?:\\\\)+(?=\\?@)|\\@|@([-a-zA-Z0-9_]+)@') result = [] missing_variables = set() for line in data: diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index 285169b..463bf01 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -43,13 +43,13 @@ class QtBaseModule: kwargs = {'required': 'true', 'modules': 'Core', 'silent': 'true', 'method': method} qt = _QT_DEPS_LUT[self.qt_version](env, kwargs) # Get all tools and then make sure that they are the right version - self.moc, self.uic, self.rcc = qt.compilers_detect() + self.moc, self.uic, self.rcc, self.lrelease = qt.compilers_detect() # Moc, uic and rcc write their version strings to stderr. # Moc and rcc return a non-zero result when doing so. # What kind of an idiot thought that was a good idea? - for compiler, compiler_name in ((self.moc, "Moc"), (self.uic, "Uic"), (self.rcc, "Rcc")): + for compiler, compiler_name in ((self.moc, "Moc"), (self.uic, "Uic"), (self.rcc, "Rcc"), (self.lrelease, "lrelease")): if compiler.found(): - stdout, stderr = Popen_safe(compiler.get_command() + ['-v'])[1:3] + stdout, stderr = Popen_safe(compiler.get_command() + ['-version'])[1:3] stdout = stdout.strip() stderr = stderr.strip() if 'Qt {}'.format(self.qt_version) in stderr: @@ -62,7 +62,7 @@ class QtBaseModule: raise MesonException('{name} preprocessor is not for Qt {version}. Output:\n{stdo}\n{stderr}'.format( name=compiler_name, version=self.qt_version, stdo=stdout, stderr=stderr)) mlog.log(' {}:'.format(compiler_name.lower()), mlog.green('YES'), '({path}, {version})'.format( - path=self.moc.get_path(), version=compiler_ver.split()[-1])) + path=compiler.get_path(), version=compiler_ver.split()[-1])) else: mlog.log(' {}:'.format(compiler_name.lower()), mlog.red('NO')) self.tools_detected = True @@ -137,10 +137,28 @@ class QtBaseModule: moc_output = moc_gen.process_files('Qt{} moc header'.format(self.qt_version), moc_headers, state) sources.append(moc_output) if len(moc_sources) > 0: - arguments = moc_extra_arguments + ['@INPUT@', '-o', '@OUTPUT@'] + arguments = moc_extra_arguments + inc + ['@INPUT@', '-o', '@OUTPUT@'] moc_kwargs = {'output': '@BASENAME@.moc', 'arguments': arguments} moc_gen = build.Generator([self.moc], moc_kwargs) moc_output = moc_gen.process_files('Qt{} moc source'.format(self.qt_version), moc_sources, state) sources.append(moc_output) return ModuleReturnValue(sources, sources) + + @permittedKwargs({'ts_files', 'install', 'install_dir', 'build_by_default', 'method'}) + def compile_translations(self, state, args, kwargs): + ts_files, install_dir = extract_as_list(kwargs, 'ts_files', 'install_dir', pop=True) + self._detect_tools(state.environment, kwargs.get('method', 'auto')) + translations = [] + for ts in ts_files: + cmd = [self.lrelease, '@INPUT@', '-qm', '@OUTPUT@'] + lrelease_kwargs = {'output': '@BASENAME@.qm', + 'input': ts, + 'install': kwargs.get('install', False), + 'build_by_default': kwargs.get('build_by_default', False), + 'command': cmd} + if install_dir is not None: + lrelease_kwargs['install_dir'] = install_dir + lrelease_target = build.CustomTarget('qt{}-compile-{}'.format(self.qt_version, ts), state.subdir, state.subproject, lrelease_kwargs) + translations.append(lrelease_target) + return ModuleReturnValue(translations, translations) diff --git a/test cases/common/16 configure file/config6.h.in b/test cases/common/16 configure file/config6.h.in new file mode 100644 index 0000000..9719f87 --- /dev/null +++ b/test cases/common/16 configure file/config6.h.in @@ -0,0 +1,19 @@ +/* No escape */ +#define MESSAGE1 "@var1@" + +/* Single escape means no replace */ +#define MESSAGE2 "\@var1@" + +/* Replace pairs of escapes before '@' or '\@' with escape characters + * (note we have to double number of pairs due to C string escaping) + */ +#define MESSAGE3 "\\\\@var1@" + +/* Pairs of escapes and then single escape to avoid replace */ +#define MESSAGE4 "\\\\\@var1@" + +/* Check escaped variable does not overlap following variable */ +#define MESSAGE5 "\@var1@var2@" + +/* Check escape character outside variables */ +#define MESSAGE6 "\\ @ \@ \\\\@ \\\\\@" diff --git a/test cases/common/16 configure file/meson.build b/test cases/common/16 configure file/meson.build index 4264d07..eda0a8f 100644 --- a/test cases/common/16 configure file/meson.build +++ b/test cases/common/16 configure file/meson.build @@ -120,3 +120,14 @@ configure_file( configuration : conf5 ) test('test5', executable('prog5', 'prog5.c')) + +# Test escaping +conf6 = configuration_data() +conf6.set('var1', 'foo') +conf6.set('var2', 'bar') +configure_file( + input : 'config6.h.in', + output : '@BASENAME@', + configuration : conf6 +) +test('test6', executable('prog6', 'prog6.c')) diff --git a/test cases/common/16 configure file/prog6.c b/test cases/common/16 configure file/prog6.c new file mode 100644 index 0000000..7412404 --- /dev/null +++ b/test cases/common/16 configure file/prog6.c @@ -0,0 +1,11 @@ +#include <string.h> +#include <config6.h> + +int main(int argc, char **argv) { + return strcmp(MESSAGE1, "foo") + || strcmp(MESSAGE2, "@var1@") + || strcmp(MESSAGE3, "\\foo") + || strcmp(MESSAGE4, "\\@var1@") + || strcmp(MESSAGE5, "@var1bar") + || strcmp(MESSAGE6, "\\ @ @ \\@ \\@"); +} diff --git a/test cases/frameworks/4 qt/meson.build b/test cases/frameworks/4 qt/meson.build index 374707a..7c33268 100644 --- a/test cases/frameworks/4 qt/meson.build +++ b/test cases/frameworks/4 qt/meson.build @@ -59,6 +59,8 @@ foreach qt : ['qt4', 'qt5'] # We need a console test application because some test environments # do not have an X server. + translations = qtmodule.compile_translations(ts_files : qt+'core_fr.ts', build_by_default : true) + qtcore = dependency(qt, modules : 'Core', method : get_option('method')) qtcoreapp = executable(qt + 'core', 'q5core.cpp', diff --git a/test cases/frameworks/4 qt/q5core.cpp b/test cases/frameworks/4 qt/q5core.cpp index 706e4dc..25b80b8 100644 --- a/test cases/frameworks/4 qt/q5core.cpp +++ b/test cases/frameworks/4 qt/q5core.cpp @@ -1,8 +1,26 @@ #include <QCoreApplication> +#include <QtGlobal> +#include <QString> +#include <QTranslator> +#include <QLocale> +#include <QLibraryInfo> +#include <QDebug> int main(int argc, char **argv) { QCoreApplication app(argc, argv); + QTranslator qtTranslator; + qtTranslator.load("qt_" + QLocale::system().name(), + QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + app.installTranslator(&qtTranslator); + + QTranslator myappTranslator; + if(!myappTranslator.load("qt5core_fr") ) + return 1; + + app.installTranslator(&myappTranslator); + + qDebug() << QObject::tr("Translate me!"); // Don't actually start the main loop so this // can be run as a unit test. //return app.exec(); diff --git a/test cases/frameworks/4 qt/qt4core_fr.ts b/test cases/frameworks/4 qt/qt4core_fr.ts new file mode 100644 index 0000000..0638bd5 --- /dev/null +++ b/test cases/frameworks/4 qt/qt4core_fr.ts @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.0" language="fr_FR"> +<context> + <name>QObject</name> + <message> + <location filename="q5core.cpp" line="23"/> + <source>Translate me!</source> + <translation>Traduisez moi!</translation> + </message> +</context> +</TS> diff --git a/test cases/frameworks/4 qt/qt5core_fr.ts b/test cases/frameworks/4 qt/qt5core_fr.ts new file mode 100644 index 0000000..4e3116b --- /dev/null +++ b/test cases/frameworks/4 qt/qt5core_fr.ts @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="fr_FR"> +<context> + <name>QObject</name> + <message> + <location filename="q5core.cpp" line="23"/> + <source>Translate me!</source> + <translation>Traduisez moi!</translation> + </message> +</context> +</TS> |