diff options
58 files changed, 1591 insertions, 269 deletions
@@ -1,4 +1,5 @@ * @jpakkane /mesonbuild/modules/pkgconfig.py @xclaesse /mesonbuild/modules/cmake.py @mensinda +/mesonbuild/ast/* @mensinda /mesonbuild/cmake/* @mensinda @@ -20,27 +20,28 @@ build system. #### Installing from source -You can run Meson directly from a revision control checkout or an -extracted tarball. If you wish you can install it locally with the -standard Python command +Meson is available on [PyPi](https://pypi.python.org/pypi/meson), so +it can be installed with `pip3 install meson`. The exact command to +type to install with `pip` can vary between systems, be sure to use +the Python 3 version of `pip`. -```sh -python3 -m pip install meson <your options here> -``` +If you wish you can install it locally with the standard Python command: -Meson is also available from -[PyPi](https://pypi.python.org/pypi/meson), so it can be installed -with `pip3 install meson` (this does not require a source checkout, -pip will download the package automatically). The exact command to -type to install with Pip can vary between systems, be sure to use the -Python 3 version of Pip. +```console +python3 -m pip install meson +``` -For builds using Ninja, Ninja can be [downloaded directly](https://github.com/ninja-build/ninja/releases) or via +For builds using Ninja, Ninja can be downloaded directly from Ninja +[GitHub release page](https://github.com/ninja-build/ninja/releases) +or via [PyPi](https://pypi.python.org/pypi/ninja) -```sh +```console python3 -m pip install ninja ``` +More on Installing Meson build can be found at the +[getting meson page](https://mesonbuild.com/Getting-meson.html). + #### Running Meson requires that you have a source directory and a build directory @@ -58,27 +59,22 @@ You can omit either of the two directories, and Meson will substitute the current directory and autodetect what you mean. This allows you to do things like this: -`cd source_root; mkdir builddir; cd builddir; meson ..` - -or - -`cd source_root; mkdir builddir; meson builddir` +```console +cd <source root> +meson builddir +``` To compile, cd into your build directory and type `ninja`. To run unit tests, type `ninja test`. -Install is the same but it can take an extra argument: - -`DESTDIR=/destdir/path ninja install` - -`DESTDIR` can be omitted. If you are installing to system directories, -you may need to run this command with sudo. - +More on running Meson build system commands can be found at the +[running meson page](https://mesonbuild.com/Running-Meson.html) +or by typing `meson --help`. #### Contributing We love code contributions. See the [contribution -page](https://mesonbuild.com/Contributing.html) on the web site for +page](https://mesonbuild.com/Contributing.html) on the website for details. @@ -96,4 +92,4 @@ to connect to this channel. More information about the Meson build system can be found at the [project's home page](https://mesonbuild.com). -Meson is a registered trademark of Jussi Pakkanen. +Meson is a registered trademark of ***Jussi Pakkanen***. diff --git a/ci/azure-steps.yml b/ci/azure-steps.yml index ef31208..66a7eed 100644 --- a/ci/azure-steps.yml +++ b/ci/azure-steps.yml @@ -102,6 +102,10 @@ steps: $vcvars = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" } + ## A multiline commit message containing "=" can interact badly with this + ## hack to extract the environment from vcvarsall.bat + Remove-Item env:BUILD_SOURCEVERSIONMESSAGE + ## ask cmd.exe to output the environment table after the batch file completes $tempFile = [IO.Path]::GetTempFileName() cmd /c " `"$vcvars`" $env:arch && set > `"$tempFile`" " diff --git a/docs/markdown/Fs-module.md b/docs/markdown/Fs-module.md index 53bf960..e5941a9 100644 --- a/docs/markdown/Fs-module.md +++ b/docs/markdown/Fs-module.md @@ -9,6 +9,13 @@ Non-absolute paths are looked up relative to the directory where the current `meson.build` file is. If specified, a leading `~` is expanded to the user home directory. +Environment variables are not available as is the rule throughout Meson. +That is, $HOME, %USERPROFILE%, $MKLROOT, etc. have no meaning to the Meson +filesystem module. If needed, pass such variables into Meson via command +line options in `meson_options.txt`, native-file or cross-file. + +Where possible, symlinks and parent directory notation are resolved to an +absolute path. ### exists @@ -19,13 +26,12 @@ special entry such as a device node. ### is_dir Takes a single string argument and returns true if a directory with -that name exists on the file system. This method follows symbolic -links. +that name exists on the file system. ### is_file Takes a single string argument and returns true if an file with that -name exists on the file system. This method follows symbolic links. +name exists on the file system. ### is_symlink @@ -34,6 +40,25 @@ by the string is a symbolic link. ## File Parameters +### is_absolute + +*since 0.54.0* + +Return a boolean indicating if the path string specified is absolute, WITHOUT expanding `~`. + +Examples: + +```meson +fs.is_absolute('~') # false + +home = fs.expanduser('~') +fs.is_absolute(home) # true + +fs.is_absolute(home / 'foo') # true, even if ~/foo doesn't exist + +fs.is_absolute('foo/bar') # false, even if ./foo/bar exists +``` + ### hash The `fs.hash(filename, hash_algorithm)` method returns a string containing @@ -44,7 +69,6 @@ md5, sha1, sha224, sha256, sha384, sha512. ### size The `fs.size(filename)` method returns the size of the file in integer bytes. -Symlinks will be resolved if possible. ### is_samepath @@ -67,7 +91,7 @@ fs.is_samepath(x, z) # true fs.is_samepath(x, j) # false p = 'foo/bar' -q = 'foo/bar/../baz' +q = 'foo/bar/baz/..' r = 'buz' # a symlink pointing to foo/bar s = 'notapath' # non-existant directory @@ -76,10 +100,39 @@ fs.is_samepath(p, r) # true fs.is_samepath(p, s) # false ``` - ## Filename modification -The files need not actually exist yet for this method, as it's just string manipulation. +The files need not actually exist yet for these path string manipulation methods. + +### expanduser + +*since 0.54.0* + +A path string with a leading `~` is expanded to the user home directory + +Examples: + +```meson +fs.expanduser('~') # user home directory + +fs.expanduser('~/foo') # <homedir>/foo +``` + +### as_posix + +*since 0.54.0* + +`fs.as_posix(path)` assumes a Windows path, even if on a Unix-like system. +Thus, all `'\'` or `'\\'` are turned to '/', even if you meant to escape a character. + +Examples + +```meson +fs.as_posix('\\') == '/' # true +fs.as_posix('\\\\') == '/' # true + +fs.as_posix('foo\\bar/baz') == 'foo/bar/baz' # true +``` ### replace_suffix @@ -118,6 +171,26 @@ new = fs.replace_suffix(original, '') # /opt/foo.dll Returns the parent directory (i.e. dirname). +```meson +new = fs.parent('foo/bar') # foo +new = fs.parent('foo/bar/baz.dll') # foo/bar +``` + ### name Returns the last component of the path (i.e. basename). + +```meson +fs.name('foo/bar/baz.dll.a') # baz.dll.a +``` + +### stem + +*since 0.54.0* + +Returns the last component of the path, dropping the last part of the suffix + +```meson +fs.stem('foo/bar/baz.dll') # baz +fs.stem('foo/bar/baz.dll.a') # baz.dll +``` diff --git a/docs/markdown/Pkgconfig-module.md b/docs/markdown/Pkgconfig-module.md index 4aa82f6..678090b 100644 --- a/docs/markdown/Pkgconfig-module.md +++ b/docs/markdown/Pkgconfig-module.md @@ -54,6 +54,8 @@ keyword arguments. `Version:` field. (*since 0.46.0*) Defaults to the project version if unspecified. - `d_module_versions` a list of module version flags used when compiling D sources referred to by this pkg-config file +- `uninstalled_variables` used instead of the `variables` keyword argument, when + generating the uninstalled pkg-config file. Since *0.54.0* Since 0.46 a `StaticLibrary` or `SharedLibrary` object can optionally be passed as first positional argument. If one is provided a default value will be @@ -62,6 +64,15 @@ provided for all required fields of the pc file: - `description` is set to the project's name followed by the library's name. - `name` is set to the library's name. +Since 0.54.0 uninstalled pkg-config files are generated as well. They are +located in `<build dir>/meson-uninstalled/`. It is sometimes +useful to build projects against libraries built by meson without having to +install them into a prefix. In order to do so, just set +`PKG_CONFIG_PATH=<builddir>/meson-uninstalled` before building your +application. That will cause pkg-config to prefer those `-uninstalled.pc` files +and find libraries and headers from the meson builddir. This is an experimental +feature provided on a best-effort basis, it might not work in all use-cases. + ### Implicit dependencies The exact rules followed to find dependencies that are implicitly added into the diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 57fc1f7..510d443 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -547,11 +547,12 @@ libraries (`.so`, `.dll`, etc) will be linked. With the Ninja backend, Meson will create a build-time [order-only dependency](https://ninja-build.org/manual.html#ref_dependencies) on -all generated input files, including unknown files. For all input -files (generated and non-generated), Meson uses the [dependency -file](https://ninja-build.org/manual.html#ref_headers) generated by -your compiler to determine when to rebuild sources. The behavior is -similar for other backends. +all generated input files, including unknown files. This is needed +to bootstrap the generation of the real dependencies in the +[depfile](https://ninja-build.org/manual.html#ref_headers) +generated by your compiler to determine when to rebuild sources. +Ninja relies on this dependency file for all input files, generated +and non-generated. The behavior is similar for other backends. Executable supports the following keyword arguments. Note that just like the positional arguments above, these keyword arguments can also @@ -1552,7 +1553,8 @@ arguments: that override those set in the subproject's `meson_options.txt` (like `default_options` in `project`, they only have effect when Meson is run for the first time, and command line arguments override - any default options in build files) + any default options in build files). *Since 0.54.0* `default_library` + built-in option can also be overridden. - `version` keyword argument that works just like the one in `dependency`. It specifies what version the subproject should be, as an example `>=1.0.1` @@ -1773,10 +1775,6 @@ the following methods. refer to files in the current or any other source directory instead of constructing paths manually with `meson.current_source_dir()`. -- `get_cross_property(propname, fallback_value)` returns the given - property from a cross file, the optional second argument is returned - if not cross compiling or the given property is not found. - - `get_compiler(language)` returns [an object describing a compiler](#compiler-object), takes one positional argument which is the language to use. It also accepts one keyword argument, `native` @@ -1786,6 +1784,19 @@ the following methods. returns the "cross" compiler if we're currently cross-compiling and the "native" compiler if we're not. +- `get_cross_property(propname, fallback_value)` + *Consider get_external_property() instead*. Returns the given + property from a cross file, the optional fallback_value is returned + if not cross compiling or the given property is not found. + +- `get_external_property(propname, fallback_value, native: true/false)` + *(added 0.54.0)* returns the given property from a native or cross file. + The optional fallback_value is returned if the given property is not found. + The optional `native: true` forces retrieving a variable from the + native file, even when cross-compiling. + If `native: false` or not specified, variable is retrieved from the + cross-file if cross-compiling, and from the native-file when not cross-compiling. + - `has_exe_wrapper()` returns true when doing a cross build if there is a wrapper command that can be used to execute cross built binaries (for example when cross compiling from Linux to Windows, @@ -2299,6 +2310,8 @@ contains a target with the following methods: NOTE: In most cases using the object itself will do the same job as this and will also allow Meson to setup inter-target dependencies correctly. Please file a bug if that doesn't work for you. + *Since 0.54.0* it can be also called on indexes objects: + `custom_targets[i].full_path()`. - `[index]` returns an opaque object that references this target, and can be used as a source in other targets. When it is used as such it @@ -2306,6 +2319,10 @@ contains a target with the following methods: source added will be the one that corresponds to the index of the custom target's output argument. +- `to_list()` *Since 0.54.0*, returns a list of opaque objects that references + this target, and can be used as a source in other targets. This can be used to + iterate outputs with `foreach` loop. + ### `dependency` object This object is returned by [`dependency()`](#dependency) and contains diff --git a/docs/markdown/snippets/more_meson_sample_templates.md b/docs/markdown/snippets/more_meson_sample_templates.md new file mode 100644 index 0000000..e10da9c --- /dev/null +++ b/docs/markdown/snippets/more_meson_sample_templates.md @@ -0,0 +1,6 @@ +## More new sample Meson templates for (`Java`, `Cuda`, and more) + +Meson now ships with predefined project templates for `Java`, +`Cuda`, `Objective-C++`, and `C#`, we provided with associated +values for corresponding languages, avalable for both library, +and executable. diff --git a/docs/markdown/snippets/native_property.md b/docs/markdown/snippets/native_property.md new file mode 100644 index 0000000..d3808d8 --- /dev/null +++ b/docs/markdown/snippets/native_property.md @@ -0,0 +1,18 @@ +## Native file properties + +As of Meson 0.54.0, the `--native-file nativefile.ini` can contain: + +* binaries +* paths +* properties + +which are defined and used the same way as in cross files. +The `properties` are new for Meson 0.54.0, and are read like: + +```meson +x = meson.get_external_property('foobar', 'foo') +``` + +where `foobar` is the property name, and the optional `foo` is the fallback string value. + +For cross-compiled projects, `get_external_property()` reads the cross-file unless `native: true` is specified.
\ No newline at end of file diff --git a/docs/markdown/snippets/per_subproject_builtin.md b/docs/markdown/snippets/per_subproject_builtin.md new file mode 100644 index 0000000..44ed1c8 --- /dev/null +++ b/docs/markdown/snippets/per_subproject_builtin.md @@ -0,0 +1,15 @@ +## Per subproject `default_library` option + +The `default_library` built-in option can now be defined per subproject. This is +useful for example when building shared libraries in the main project, but static +link a subproject. + +Most of the time this would be used either by the parent project by setting +subproject's default_options (e.g. `subproject('foo', default_options: 'default_library=static')`), +or by the user using the command line `-Dfoo:default_library=static`. + +The value is overriden in this order: +- Value from parent project +- Value from subproject's default_options if set +- Value from subproject() default_options if set +- Value from command line if set diff --git a/docs/markdown/snippets/uninstalled-pkgconfig.md b/docs/markdown/snippets/uninstalled-pkgconfig.md new file mode 100644 index 0000000..2e265ab --- /dev/null +++ b/docs/markdown/snippets/uninstalled-pkgconfig.md @@ -0,0 +1,8 @@ +## Uninstalled pkg-config files + +The `pkgconfig` module now generates uninstalled pc files as well. For any generated +`foo.pc` file, an extra `foo-uninstalled.pc` file is placed into +`<builddir>/meson-uninstalled`. They can be used to build applications against +libraries built by meson without installing them, by pointing `PKG_CONFIG_PATH` +to that directory. This is an experimental feature provided on a best-effort +basis, it might not work in all use-cases. diff --git a/docs/theme/extra/templates/navbar_links.html b/docs/theme/extra/templates/navbar_links.html index 036c20f..6980f81 100644 --- a/docs/theme/extra/templates/navbar_links.html +++ b/docs/theme/extra/templates/navbar_links.html @@ -1,21 +1,28 @@ @require(page) <li class="dropdown"> - <a class="dropdown-toggle" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - Modules <span class="caret"></span> - </a> - <ul class="dropdown-menu" id="modules-menu"> - @for tup in (("Gnome-module.html","GNOME"), \ - ("i18n-module.html","i18n"), \ - ("Pkgconfig-module.html","Pkgconfig"), \ - ("Python-module.html","Python"), \ - ("Python-3-module.html","Python 3"), \ - ("Qt4-module.html","Qt4"), \ - ("Qt5-module.html","Qt5"), \ - ("RPM-module.html","RPM"), \ - ("SourceSet-module.html","SourceSet"), \ - ("Windows-module.html","Windows"), \ - ("Hotdoc-module.html","Hotdoc")): + <a class="dropdown-toggle" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + Modules <span class="caret"></span> + </a> + <ul class="dropdown-menu" id="modules-menu"> + @for tup in ( \ + ("CMake-module.html","CMake"), \ + ("Cuda-module.html","CUDA"), \ + ("Dlang-module.html","Dlang"), \ + ("Fs-module.html","Filesystem"), \ + ("Gnome-module.html","GNOME"), \ + ("Hotdoc-module.html","Hotdoc"), \ + ("i18n-module.html","i18n"), \ + ("Icestorm-module.html","Icestorm"), \ + ("Kconfig-module.html","kconfig"), \ + ("Pkgconfig-module.html","Pkgconfig"), \ + ("Python-module.html","Python"), \ + ("Python-3-module.html","Python 3"), \ + ("Qt4-module.html","Qt4"), \ + ("Qt5-module.html","Qt5"), \ + ("RPM-module.html","RPM"), \ + ("SourceSet-module.html","SourceSet"), \ + ("Windows-module.html","Windows")): <li> <a href="@tup[0]">@tup[1]</a> </li> @@ -29,11 +36,11 @@ </a> <ul class="dropdown-menu" id="quick-refs-menu"> @for tup in (("Reference-manual.html", "Functions"), \ - ("Build-options.html", "Options"), \ - ("Configuration.html", "Configuration"), \ - ("Dependencies.html", "Dependencies"), \ - ("Unit-tests.html", "Tests"), \ - ("Syntax.html", "Syntax")): + ("Build-options.html", "Options"), \ + ("Configuration.html", "Configuration"), \ + ("Dependencies.html", "Dependencies"), \ + ("Unit-tests.html", "Tests"), \ + ("Syntax.html", "Syntax")): <li> <a href="@tup[0]">@tup[1]</a> </li> diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index a614be0..5203323 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -165,10 +165,6 @@ class Backend: def get_target_filename_abs(self, target): return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)) - def get_builtin_options_for_target(self, target): - return OptionOverrideProxy(target.option_overrides, - self.environment.coredata.builtins) - def get_base_options_for_target(self, target): return OptionOverrideProxy(target.option_overrides, self.environment.coredata.builtins, diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 1db930e..0a8ca45 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -2250,6 +2250,10 @@ class CustomTarget(Target): def __delitem__(self, index): raise NotImplementedError + def __iter__(self): + for i in self.outputs: + yield CustomTargetIndex(self, i) + class RunTarget(Target): def __init__(self, name, command, args, dependencies, subdir, subproject): self.typename = 'run' diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 2aa0c01..81844a0 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -125,10 +125,13 @@ blacklist_link_libs = [ 'advapi32.lib' ] -generated_target_name_prefix = 'cm_' - transfer_dependencies_from = ['header_only'] +_cmake_name_regex = re.compile(r'[^_a-zA-Z0-9]') +def _sanitize_cmake_name(name: str) -> str: + name = _cmake_name_regex.sub('_', name) + return 'cm_' + name + class OutputTargetMap: rm_so_version = re.compile(r'(\.[0-9]+)+$') @@ -234,8 +237,7 @@ class ConverterTarget: self.override_options = [] # Convert the target name to a valid meson target name - self.name = self.name.replace('-', '_') - self.name = generated_target_name_prefix + self.name + self.name = _sanitize_cmake_name(self.name) for i in target.files: # Determine the meson language @@ -593,8 +595,7 @@ class ConverterCustomTarget: self.depends = [] # Convert the target name to a valid meson target name - self.name = self.name.replace('-', '_') - self.name = generated_target_name_prefix + self.name + self.name = _sanitize_cmake_name(self.name) def __repr__(self) -> str: return '<{}: {} {}>'.format(self.__class__.__name__, self.name, self.outputs) @@ -1192,6 +1193,7 @@ class CMakeInterpreter: root_cb.lines += [assign(tgt_var, function('custom_target', [tgt.name], tgt_kwargs))] processed[tgt.name] = {'inc': None, 'src': None, 'dep': None, 'tgt': tgt_var, 'func': 'custom_target'} + name_map[tgt.cmake_name] = tgt.name # Now generate the target function calls for i in self.custom_targets: @@ -1208,7 +1210,7 @@ class CMakeInterpreter: def target_info(self, target: str) -> T.Optional[T.Dict[str, str]]: # Try resolving the target name # start by checking if there is a 100% match (excluding the name prefix) - prx_tgt = generated_target_name_prefix + target + prx_tgt = _sanitize_cmake_name(target) if prx_tgt in self.generated_targets: return self.generated_targets[prx_tgt] # check if there exists a name mapping @@ -1219,11 +1221,7 @@ class CMakeInterpreter: return None def target_list(self) -> T.List[str]: - prx_str = generated_target_name_prefix - prx_len = len(prx_str) - res = [x for x in self.generated_targets.keys()] - res = [x[prx_len:] if x.startswith(prx_str) else x for x in res] - return res + return list(self.internal_name_map.keys()) def _object_lib_workaround(self) -> bool: return 'link' in self.linkers and self.backend_name.startswith('vs') diff --git a/mesonbuild/cmake/traceparser.py b/mesonbuild/cmake/traceparser.py index 5bf9547..4aa34f1 100644 --- a/mesonbuild/cmake/traceparser.py +++ b/mesonbuild/cmake/traceparser.py @@ -54,6 +54,13 @@ class CMakeTarget: propSTR += " '{}': {}\n".format(i, self.properties[i]) return s.format(self.name, self.type, self.imported, propSTR, self.tline) + def strip_properties(self) -> None: + # Strip the strings in the properties + if not self.properties: + return + for key, val in self.properties.items(): + self.properties[key] = [x.strip() for x in val] + class CMakeGeneratorTarget(CMakeTarget): def __init__(self, name): super().__init__(name, 'CUSTOM', {}) @@ -63,11 +70,8 @@ class CMakeGeneratorTarget(CMakeTarget): class CMakeTraceParser: def __init__(self, cmake_version: str, build_dir: str, permissive: bool = False): - # Dict of CMake variables: '<var_name>': ['list', 'of', 'values'] - self.vars = {} - - # Dict of CMakeTarget - self.targets = {} + self.vars = {} # type: T.Dict[str, T.List[str]] + self.targets = {} # type: T.Dict[str, CMakeTarget] # T.List of targes that were added with add_custom_command to generate files self.custom_targets = [] # type: T.List[CMakeGeneratorTarget] @@ -133,6 +137,10 @@ class CMakeTraceParser: if(fn): fn(l) + # Postprocess + for tgt in self.targets.values(): + tgt.strip_properties() + def get_first_cmake_var_of(self, var_list: T.List[str]) -> T.List[str]: # Return the first found CMake variable in list var_list for i in var_list: diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 44eaa02..aea2d90 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -364,11 +364,12 @@ class CoreData: self.install_guid = str(uuid.uuid4()).upper() self.target_guids = {} self.version = version - self.init_builtins() - self.backend_options = {} # : T.Dict[str, UserOption] - self.user_options = {} # : T.Dict[str, UserOption] + self.builtins = {} # : OptionDictType + self.builtins_per_machine = PerMachine({}, {}) + self.backend_options = {} # : OptionDictType + self.user_options = {} # : OptionDictType self.compiler_options = PerMachine({}, {}) - self.base_options = {} # : T.Dict[str, UserOption] + self.base_options = {} # : OptionDictType self.cross_files = self.__load_config_files(options, scratch_dir, 'cross') self.compilers = PerMachine(OrderedDict(), OrderedDict()) @@ -378,6 +379,7 @@ class CoreData: self.compiler_check_cache = OrderedDict() # Only to print a warning if it changes between Meson invocations. self.config_files = self.__load_config_files(options, scratch_dir, 'native') + self.init_builtins('') self.libdir_cross_fixup() @staticmethod @@ -497,15 +499,25 @@ class CoreData: raise MesonException(msg.format(option, value, prefix)) return value.as_posix() - def init_builtins(self): + def init_builtins(self, subproject: str): # Create builtin options with default values - self.builtins = {} for key, opt in builtin_options.items(): - self.builtins[key] = opt.init_option() - self.builtins_per_machine = PerMachine({}, {}) + self.add_builtin_option(self.builtins, key, opt, subproject) for for_machine in iter(MachineChoice): for key, opt in builtin_options_per_machine.items(): - self.builtins_per_machine[for_machine][key] = opt.init_option() + self.add_builtin_option(self.builtins_per_machine[for_machine], key, opt, subproject) + + def add_builtin_option(self, opts_map, key, opt, subproject): + if subproject: + if opt.yielding: + # This option is global and not per-subproject + return + optname = subproject + ':' + key + value = opts_map[key].value + else: + optname = key + value = None + opts_map[optname] = opt.init_option(key, value, default_prefix()) def init_backend_options(self, backend_name): if backend_name == 'ninja': @@ -520,15 +532,20 @@ class CoreData: 'Default project to execute in Visual Studio', '') - def get_builtin_option(self, optname): + def get_builtin_option(self, optname, subproject=''): + raw_optname = optname + if subproject: + optname = subproject + ':' + optname for opts in self._get_all_builtin_options(): v = opts.get(optname) + if v is None or v.yielding: + v = opts.get(raw_optname) if v is None: continue - if optname == 'wrap_mode': + if raw_optname == 'wrap_mode': return WrapMode.from_string(v.value) return v.value - raise RuntimeError('Tried to get unknown builtin option %s.' % optname) + raise RuntimeError('Tried to get unknown builtin option %s.' % raw_optname) def _try_set_builtin_option(self, optname, value): for opts in self._get_all_builtin_options(): @@ -707,11 +724,13 @@ class CoreData: env.cmd_line_options.setdefault(k, v) # Set default options as if they were passed to the command line. - # Subprojects can only define default for user options. + # Subprojects can only define default for user options and not yielding + # builtin option. from . import optinterpreter for k, v in default_options.items(): if subproject: - if optinterpreter.is_invalid_name(k, log=False): + if (k not in builtin_options or builtin_options[k].yielding) \ + and optinterpreter.is_invalid_name(k, log=False): continue k = subproject + ':' + k env.cmd_line_options.setdefault(k, v) @@ -951,7 +970,7 @@ class BuiltinOption(T.Generic[_T, _U]): Currently doesn't support UserIntegerOption, or a few other cases. """ - def __init__(self, opt_type: T.Type[_U], description: str, default: T.Any, yielding: T.Optional[bool] = None, *, + def __init__(self, opt_type: T.Type[_U], description: str, default: T.Any, yielding: bool = True, *, choices: T.Any = None): self.opt_type = opt_type self.description = description @@ -959,9 +978,11 @@ class BuiltinOption(T.Generic[_T, _U]): self.choices = choices self.yielding = yielding - def init_option(self) -> _U: + def init_option(self, name: str, value: T.Optional[T.Any], prefix: str) -> _U: """Create an instance of opt_type and return it.""" - keywords = {'yielding': self.yielding, 'value': self.default} + if value is None: + value = self.prefixed_default(name, prefix) + keywords = {'yielding': self.yielding, 'value': value} if self.choices: keywords['choices'] = self.choices return self.opt_type(self.description, **keywords) @@ -1036,7 +1057,8 @@ builtin_options = OrderedDict([ ('buildtype', BuiltinOption(UserComboOption, 'Build type to use', 'debug', choices=['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'])), ('debug', BuiltinOption(UserBooleanOption, 'Debug', True)), - ('default_library', BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'])), + ('default_library', BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'], + yielding=False)), ('errorlogs', BuiltinOption(UserBooleanOption, "Whether to print the logs from failing tests", True)), ('install_umask', BuiltinOption(UserUmaskOption, 'Default umask to apply on permissions of installed files', '022')), ('layout', BuiltinOption(UserComboOption, 'Build directory layout', 'mirror', choices=['mirror', 'flat'])), diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index d014415..a83e3d6 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -1661,6 +1661,14 @@ class DubDependency(ExternalDependency): lib_file_name = os.path.basename(default_path) module_build_path = os.path.join(module_path, '.dub', 'build') + # If default_path is a path to lib file and + # directory of lib don't have subdir '.dub/build' + if not os.path.isdir(module_build_path) and os.path.isfile(default_path): + if folder_only: + return module_path + else: + return default_path + # Get D version implemented in the compiler # gdc doesn't support this ret, res = self._call_dubbin(['--version']) diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index 5433150..31f02e5 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -32,6 +32,9 @@ from .base import ExternalDependency, NonExistingExternalProgram from .base import ExtraFrameworkDependency, PkgConfigDependency from .base import ConfigToolDependency, DependencyFactory +if T.TYPE_CHECKING: + from ..environment import Environment + class GLDependencySystem(ExternalDependency): def __init__(self, name: str, environment, kwargs): @@ -360,7 +363,12 @@ class QtBaseDependency(ExternalDependency): if self.env.machines.host.is_darwin() and not any(s in xspec for s in ['ios', 'tvos']): mlog.debug("Building for macOS, looking for framework") self._framework_detect(qvars, mods, kwargs) - return self.qmake.name + # Sometimes Qt is built not as a framework (for instance, when using conan pkg manager) + # skip and fall back to normal procedure then + if self.is_found: + return self.qmake.name + else: + mlog.debug("Building for macOS, couldn't find framework, falling back to library search") incdir = qvars['QT_INSTALL_HEADERS'] self.compile_args.append('-I' + incdir) libdir = qvars['QT_INSTALL_LIBS'] @@ -410,6 +418,9 @@ class QtBaseDependency(ExternalDependency): suffix += 'd' if self.qtver == '4': suffix += '4' + if self.env.machines[self.for_machine].is_darwin(): + if is_debug: + suffix += '_debug' return suffix def _link_with_qtmain(self, is_debug, libdir): @@ -432,8 +443,8 @@ class QtBaseDependency(ExternalDependency): fname = 'Qt' + m mlog.debug('Looking for qt framework ' + fname) fwdep = QtExtraFrameworkDependency(fname, self.env, fw_kwargs, language=self.language) - self.compile_args.append('-F' + libdir) if fwdep.found(): + self.compile_args.append('-F' + libdir) self.compile_args += fwdep.get_compile_args(with_private_headers=self.private_headers, qt_version=self.version) self.link_args += fwdep.get_link_args() @@ -441,8 +452,8 @@ class QtBaseDependency(ExternalDependency): break else: self.is_found = True - # Used by self.compilers_detect() - self.bindir = self.get_qmake_host_bins(qvars) + # Used by self.compilers_detect() + self.bindir = self.get_qmake_host_bins(qvars) def get_qmake_host_bins(self, qvars): # Prefer QT_HOST_BINS (qt5, correct for cross and native compiling) @@ -530,17 +541,30 @@ class WxDependency(ConfigToolDependency): tools = ['wx-config-3.0', 'wx-config', 'wx-config-gtk3'] tool_name = 'wx-config' - def __init__(self, environment, kwargs): - super().__init__('WxWidgets', environment, kwargs) + def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + super().__init__('WxWidgets', environment, kwargs, language='cpp') if not self.is_found: return self.requested_modules = self.get_requested(kwargs) + + extra_args = [] + if self.static: + extra_args.append('--static=yes') + + # Check to make sure static is going to work + err = Popen_safe(self.config + extra_args)[2] + if 'No config found to match' in err: + mlog.debug('WxWidgets is missing static libraries.') + self.is_found = False + return + # wx-config seems to have a cflags as well but since it requires C++, # this should be good, at least for now. - self.compile_args = self.get_config_value(['--cxxflags'] + self.requested_modules, 'compile_args') - self.link_args = self.get_config_value(['--libs'] + self.requested_modules, 'link_args') + self.compile_args = self.get_config_value(['--cxxflags'] + extra_args + self.requested_modules, 'compile_args') + self.link_args = self.get_config_value(['--libs'] + extra_args + self.requested_modules, 'link_args') - def get_requested(self, kwargs): + @staticmethod + def get_requested(kwargs: T.Dict[str, T.Any]) -> T.List[str]: if 'modules' not in kwargs: return [] candidates = extract_as_list(kwargs, 'modules') diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index ef9480b..554d79b 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -538,6 +538,7 @@ class Environment: coredata.load_configs(self.coredata.config_files)) binaries.build = BinaryTable(config.get('binaries', {})) paths.build = Directories(**config.get('paths', {})) + properties.build = Properties(config.get('properties', {})) ## Read in cross file(s) to override host machine configuration @@ -909,7 +910,7 @@ class Environment: arg = '--vsn' elif 'ccrx' in compiler_name: arg = '-v' - elif 'icl' in compiler_name: + elif compiler_name in {'icl', 'icl.exe'}: # if you pass anything to icl you get stuck in a pager arg = '' else: @@ -1336,6 +1337,8 @@ class Environment: cls = MonoCompiler elif "Visual C#" in out: cls = VisualStudioCsCompiler + else: + continue self.coredata.add_lang_args(cls.language, cls, for_machine, self) return cls(comp, version, for_machine, info) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 8a04842..d824e3c 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -35,8 +35,11 @@ from .modules import ModuleReturnValue from .cmake import CMakeInterpreter from pathlib import Path, PurePath -import os, shutil, uuid -import re, shlex +import os +import shutil +import uuid +import re +import shlex import subprocess import collections from itertools import chain @@ -871,15 +874,23 @@ class JarHolder(BuildTargetHolder): def __init__(self, target, interp): super().__init__(target, interp) -class CustomTargetIndexHolder(InterpreterObject, ObjectHolder): - def __init__(self, object_to_hold): - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, object_to_hold) +class CustomTargetIndexHolder(TargetHolder): + def __init__(self, target, interp): + super().__init__(target, interp) + self.methods.update({'full_path': self.full_path_method, + }) + + @FeatureNew('custom_target[i].full_path', '0.54.0') + @noPosargs + @permittedKwargs({}) + def full_path_method(self, args, kwargs): + return self.interpreter.backend.get_target_filename_abs(self.held_object) class CustomTargetHolder(TargetHolder): def __init__(self, target, interp): super().__init__(target, interp) self.methods.update({'full_path': self.full_path_method, + 'to_list': self.to_list_method, }) def __repr__(self): @@ -892,8 +903,17 @@ class CustomTargetHolder(TargetHolder): def full_path_method(self, args, kwargs): return self.interpreter.backend.get_target_filename_abs(self.held_object) + @FeatureNew('custom_target.to_list', '0.54.0') + @noPosargs + @permittedKwargs({}) + def to_list_method(self, args, kwargs): + result = [] + for i in self.held_object: + result.append(CustomTargetIndexHolder(i, self.interpreter)) + return result + def __getitem__(self, index): - return CustomTargetIndexHolder(self.held_object[index]) + return CustomTargetIndexHolder(self.held_object[index], self.interpreter) def __setitem__(self, index, value): # lgtm[py/unexpected-raise-in-special-method] raise InterpreterException('Cannot set a member of a CustomTarget') @@ -1783,6 +1803,8 @@ class Summary: formatted_values.append(mlog.green('YES') if i else mlog.red('NO')) else: formatted_values.append(i) + if not formatted_values: + formatted_values = [''] self.sections[section][k] = formatted_values self.max_key_len = max(self.max_key_len, len(k)) @@ -1826,6 +1848,7 @@ class MesonMain(InterpreterObject): 'version': self.version_method, 'project_name': self.project_name_method, 'get_cross_property': self.get_cross_property_method, + 'get_external_property': self.get_external_property_method, 'backend': self.backend_method, }) @@ -1998,7 +2021,7 @@ class MesonMain(InterpreterObject): @noArgsFlattening @permittedKwargs({}) - def get_cross_property_method(self, args, kwargs): + def get_cross_property_method(self, args, kwargs) -> str: if len(args) < 1 or len(args) > 2: raise InterpreterException('Must have one or two arguments.') propname = args[0] @@ -2012,6 +2035,34 @@ class MesonMain(InterpreterObject): return args[1] raise InterpreterException('Unknown cross property: %s.' % propname) + @noArgsFlattening + @permittedKwargs({'native'}) + @FeatureNew('meson.get_external_property', '0.54.0') + def get_external_property_method(self, args: T.Sequence[str], kwargs: dict) -> str: + if len(args) < 1 or len(args) > 2: + raise InterpreterException('Must have one or two positional arguments.') + propname = args[0] + if not isinstance(propname, str): + raise InterpreterException('Property name must be string.') + + def _get_native() -> str: + try: + props = self.interpreter.environment.properties.build + return props[propname] + except Exception: + if len(args) == 2: + return args[1] + raise InterpreterException('Unknown native property: %s.' % propname) + if 'native' in kwargs: + if kwargs['native']: + return _get_native() + else: + return self.get_cross_property_method(args, {}) + else: # native: not specified + if self.build.environment.is_cross_build(): + return self.get_cross_property_method(args, kwargs) + else: + return _get_native() known_library_kwargs = ( build.known_shlib_kwargs | @@ -2699,19 +2750,21 @@ external dependencies (including libraries) must go to "dependencies".''') return result def get_option_internal(self, optname): + raw_optname = optname + if self.is_subproject(): + optname = self.subproject + ':' + optname + for opts in chain( [self.coredata.base_options, compilers.base_options, self.coredata.builtins], self.coredata.get_prefixed_options_per_machine(self.coredata.builtins_per_machine), self.coredata.get_prefixed_options_per_machine(self.coredata.compiler_options), ): v = opts.get(optname) + if v is None or v.yielding: + v = opts.get(raw_optname) if v is not None: return v - raw_optname = optname - if self.is_subproject(): - optname = self.subproject + ':' + optname - try: opt = self.coredata.user_options[optname] if opt.yielding and ':' in optname and raw_optname in self.coredata.user_options: @@ -2818,6 +2871,7 @@ external dependencies (including libraries) must go to "dependencies".''') if self.environment.first_invocation: default_options = self.project_default_options default_options.update(self.default_project_options) + self.coredata.init_builtins(self.subproject) else: default_options = {} self.coredata.set_default_options(default_options, self.subproject, self.environment) @@ -4317,7 +4371,7 @@ Try setting b_lundef to false instead.'''.format(self.coredata.base_options['b_s return BothLibrariesHolder(shared_holder, static_holder, self) def build_library(self, node, args, kwargs): - default_library = self.coredata.get_builtin_option('default_library') + default_library = self.coredata.get_builtin_option('default_library', self.subproject) if default_library == 'shared': return self.build_target(node, args, kwargs, SharedLibraryHolder) elif default_library == 'static': diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 85d883b..891e7a1 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -1,4 +1,4 @@ -# Copyright 2012-2015 The Meson development team +# Copyright 2012-2019 The Meson development team # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/mesonbuild/minit.py b/mesonbuild/minit.py index 79741e5..5f28f58 100644 --- a/mesonbuild/minit.py +++ b/mesonbuild/minit.py @@ -22,13 +22,24 @@ from mesonbuild.environment import detect_ninja from mesonbuild.templates.ctemplates import (create_exe_c_sample, create_lib_c_sample) from mesonbuild.templates.cpptemplates import (create_exe_cpp_sample, create_lib_cpp_sample) +from mesonbuild.templates.cstemplates import (create_exe_cs_sample, create_lib_cs_sample) +from mesonbuild.templates.cudatemplates import (create_exe_cuda_sample, create_lib_cuda_sample) from mesonbuild.templates.objctemplates import (create_exe_objc_sample, create_lib_objc_sample) +from mesonbuild.templates.objcpptemplates import (create_exe_objcpp_sample, create_lib_objcpp_sample) from mesonbuild.templates.dlangtemplates import (create_exe_d_sample, create_lib_d_sample) +from mesonbuild.templates.javatemplates import (create_exe_java_sample, create_lib_java_sample) from mesonbuild.templates.fortrantemplates import (create_exe_fortran_sample, create_lib_fortran_sample) from mesonbuild.templates.rusttemplates import (create_exe_rust_sample, create_lib_rust_sample) +''' +there is currently only one meson template at this time. +''' +from mesonbuild.templates.mesontemplates import create_meson_build + FORTRAN_SUFFIXES = ['.f', '.for', '.F', '.f90', '.F90'] +UNREACHABLE_CODE = 'Unreachable code' + info_message = '''Sample project created. To build it run the following commands: @@ -37,53 +48,89 @@ ninja -C builddir ''' def create_sample(options): + ''' + Based on what arguments are passed we check for a match in language + then check for project type and create new Meson samples project. + ''' if options.language == 'c': if options.type == 'executable': create_exe_c_sample(options.name, options.version) elif options.type == 'library': create_lib_c_sample(options.name, options.version) else: - raise RuntimeError('Unreachable code') + raise RuntimeError(UNREACHABLE_CODE) elif options.language == 'cpp': if options.type == 'executable': create_exe_cpp_sample(options.name, options.version) elif options.type == 'library': create_lib_cpp_sample(options.name, options.version) else: - raise RuntimeError('Unreachable code') + raise RuntimeError(UNREACHABLE_CODE) + elif options.language == 'cs': + if options.type == 'executable': + create_exe_cs_sample(options.name, options.version) + elif options.type == 'library': + create_lib_cs_sample(options.name, options.version) + else: + raise RuntimeError(UNREACHABLE_CODE) + elif options.language == 'cuda': + if options.type == 'executable': + create_exe_cuda_sample(options.name, options.version) + elif options.type == 'library': + create_lib_cuda_sample(options.name, options.version) + else: + raise RuntimeError(UNREACHABLE_CODE) elif options.language == 'd': if options.type == 'executable': create_exe_d_sample(options.name, options.version) elif options.type == 'library': create_lib_d_sample(options.name, options.version) else: - raise RuntimeError('Unreachable code') + raise RuntimeError(UNREACHABLE_CODE) elif options.language == 'fortran': if options.type == 'executable': create_exe_fortran_sample(options.name, options.version) elif options.type == 'library': create_lib_fortran_sample(options.name, options.version) else: - raise RuntimeError('Unreachable code') + raise RuntimeError(UNREACHABLE_CODE) elif options.language == 'rust': if options.type == 'executable': create_exe_rust_sample(options.name, options.version) elif options.type == 'library': create_lib_rust_sample(options.name, options.version) else: - raise RuntimeError('Unreachable code') + raise RuntimeError(UNREACHABLE_CODE) elif options.language == 'objc': if options.type == 'executable': create_exe_objc_sample(options.name, options.version) elif options.type == 'library': create_lib_objc_sample(options.name, options.version) else: - raise RuntimeError('Unreachable code') + raise RuntimeError(UNREACHABLE_CODE) + elif options.language == 'objcpp': + if options.type == 'executable': + create_exe_objcpp_sample(options.name, options.version) + elif options.type == 'library': + create_lib_objcpp_sample(options.name, options.version) + else: + raise RuntimeError(UNREACHABLE_CODE) + elif options.language == 'java': + if options.type == 'executable': + create_exe_java_sample(options.name, options.version) + elif options.type == 'library': + create_lib_java_sample(options.name, options.version) + else: + raise RuntimeError(UNREACHABLE_CODE) else: - raise RuntimeError('Unreachable code') + raise RuntimeError(UNREACHABLE_CODE) print(info_message) def autodetect_options(options, sample: bool = False): + ''' + Here we autodetect options for args not passed in so don't have to + think about it. + ''' if not options.name: options.name = Path().resolve().stem if not re.match('[a-zA-Z_][a-zA-Z0-9]*', options.name) and sample: @@ -101,7 +148,7 @@ def autodetect_options(options, sample: bool = False): if not options.srcfiles: srcfiles = [] for f in (f for f in Path().iterdir() if f.is_file()): - if f.suffix in (['.cc', '.cpp', '.c', '.d', '.m', '.rs'] + FORTRAN_SUFFIXES): + if f.suffix in (['.c', '.cc', '.cpp', '.cs', '.cu', '.d', '.m', '.mm', '.rs', '.java'] + FORTRAN_SUFFIXES): srcfiles.append(f) if not srcfiles: raise SystemExit('No recognizable source files found.\n' @@ -111,11 +158,17 @@ def autodetect_options(options, sample: bool = False): options.srcfiles = [Path(f) for f in options.srcfiles] if not options.language: for f in options.srcfiles: + if f.suffix == '.c': + options.language = 'c' + break if f.suffix in ('.cc', '.cpp'): options.language = 'cpp' break - if f.suffix == '.c': - options.language = 'c' + if f.suffix in '.cs': + options.language = 'cs' + break + if f.suffix == '.cu': + options.language = 'cuda' break if f.suffix == '.d': options.language = 'd' @@ -129,66 +182,37 @@ def autodetect_options(options, sample: bool = False): if f.suffix == '.m': options.language = 'objc' break + if f.suffix == '.mm': + options.language = 'objcpp' + break + if f.suffix == '.java': + options.language = 'java' + break if not options.language: raise SystemExit("Can't autodetect language, please specify it with -l.") print("Detected language: " + options.language) - -meson_executable_template = '''project('{project_name}', '{language}', - version : '{version}', - default_options : [{default_options}]) - -executable('{executable}', - {sourcespec},{depspec} - install : true) -''' - -def create_meson_build(options): - if options.type != 'executable': - raise SystemExit('\nGenerating a meson.build file from existing sources is\n' - 'supported only for project type "executable".\n' - 'Run meson init in an empty directory to create a sample project.') - default_options = ['warning_level=3'] - if options.language == 'cpp': - # This shows how to set this very common option. - default_options += ['cpp_std=c++14'] - # If we get a meson.build autoformatter one day, this code could - # be simplified quite a bit. - formatted_default_options = ', '.join("'{}'".format(x) for x in default_options) - sourcespec = ',\n '.join("'{}'".format(x) for x in options.srcfiles) - depspec = '' - if options.deps: - depspec = '\n dependencies : [\n ' - depspec += ',\n '.join("dependency('{}')".format(x) - for x in options.deps.split(',')) - depspec += '],' - content = meson_executable_template.format(project_name=options.name, - language=options.language, - version=options.version, - executable=options.executable, - sourcespec=sourcespec, - depspec=depspec, - default_options=formatted_default_options) - open('meson.build', 'w').write(content) - print('Generated meson.build file:\n\n' + content) - def add_arguments(parser): - parser.add_argument("srcfiles", metavar="sourcefile", nargs="*", - help="source files. default: all recognized files in current directory") + ''' + Here we add args for that the user can passed when making a new + Meson project. + ''' + parser.add_argument("srcfiles", metavar="sourcefile", nargs="*", help="source files. default: all recognized files in current directory") parser.add_argument("-n", "--name", help="project name. default: name of current directory") parser.add_argument("-e", "--executable", help="executable name. default: project name") parser.add_argument("-d", "--deps", help="dependencies, comma-separated") - parser.add_argument("-l", "--language", choices=['c', 'cpp', 'd', 'fortran', 'rust', 'objc'], + parser.add_argument("-l", "--language", choices=['c', 'cpp', 'cs', 'cuda', 'd', 'fortran', 'java', 'rust', 'objc', 'objcpp'], help="project language. default: autodetected based on source files") parser.add_argument("-b", "--build", help="build after generation", action='store_true') parser.add_argument("--builddir", help="directory for build", default='build') - parser.add_argument("-f", "--force", action="store_true", - help="force overwrite of existing files and directories.") - parser.add_argument('--type', default='executable', - choices=['executable', 'library']) + parser.add_argument("-f", "--force", action="store_true", help="force overwrite of existing files and directories.") + parser.add_argument('--type', default='executable', choices=['executable', 'library']) parser.add_argument('--version', default='0.1') def run(options) -> int: + ''' + Here we generate the new Meson sample project. + ''' if not glob('*'): autodetect_options(options, sample=True) if not options.language: diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index dff5ecc..cfa4574 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -180,9 +180,10 @@ def list_targets(builddata: build.Build, installdata, backend: backends.Backend) return tlist def list_buildoptions_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]: - return list_buildoptions(intr.coredata) + subprojects = [i['name'] for i in intr.project_data['subprojects']] + return list_buildoptions(intr.coredata, subprojects) -def list_buildoptions(coredata: cdata.CoreData) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]: +def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[str]] = None) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]: optlist = [] # type: T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]] dir_option_names = ['bindir', @@ -206,6 +207,16 @@ def list_buildoptions(coredata: cdata.CoreData) -> T.List[T.Dict[str, T.Union[st test_options = {k: o for k, o in coredata.builtins.items() if k in test_option_names} core_options = {k: o for k, o in coredata.builtins.items() if k in core_option_names} + if subprojects: + # Add per subproject built-in options + sub_core_options = {} + for sub in subprojects: + for k, o in core_options.items(): + if o.yielding: + continue + sub_core_options[sub + ':' + k] = o + core_options.update(sub_core_options) + def add_keys(options: T.Dict[str, cdata.UserOption], section: str, machine: str = 'any') -> None: for key in sorted(options.keys()): opt = options[key] diff --git a/mesonbuild/modules/fs.py b/mesonbuild/modules/fs.py index 523ad2b..d483111 100644 --- a/mesonbuild/modules/fs.py +++ b/mesonbuild/modules/fs.py @@ -14,12 +14,13 @@ import typing as T import hashlib -from pathlib import Path, PurePath +from pathlib import Path, PurePath, PureWindowsPath from .. import mlog from . import ExtensionModule from . import ModuleReturnValue from ..mesonlib import MesonException +from ..interpreterbase import FeatureNew from ..interpreterbase import stringArgs, noKwargs if T.TYPE_CHECKING: @@ -31,18 +32,62 @@ class FSModule(ExtensionModule): super().__init__(interpreter) self.snippets.add('generate_dub_file') + def _absolute_dir(self, state: 'ModuleState', arg: str) -> Path: + """ + make an absolute path from a relative path, WITHOUT resolving symlinks + """ + return Path(state.source_root) / state.subdir / Path(arg).expanduser() + def _resolve_dir(self, state: 'ModuleState', arg: str) -> Path: """ - resolves (makes absolute) a directory relative to calling meson.build, + resolves symlinks and makes absolute a directory relative to calling meson.build, if not already absolute """ - return Path(state.source_root) / state.subdir / Path(arg).expanduser() + path = self._absolute_dir(state, arg) + try: + # accomodate unresolvable paths e.g. symlink loops + path = path.resolve() + except Exception: + # return the best we could do + pass + return path def _check(self, check: str, state: 'ModuleState', args: T.Sequence[str]) -> ModuleReturnValue: if len(args) != 1: raise MesonException('fs.{} takes exactly one argument.'.format(check)) test_file = self._resolve_dir(state, args[0]) - return ModuleReturnValue(getattr(test_file, check)(), []) + val = getattr(test_file, check)() + if isinstance(val, Path): + val = str(val) + return ModuleReturnValue(val, []) + + @stringArgs + @noKwargs + @FeatureNew('fs.expanduser', '0.54.0') + def expanduser(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue: + if len(args) != 1: + raise MesonException('fs.expanduser takes exactly one argument.') + return ModuleReturnValue(str(Path(args[0]).expanduser()), []) + + @stringArgs + @noKwargs + @FeatureNew('fs.is_absolute', '0.54.0') + def is_absolute(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue: + if len(args) != 1: + raise MesonException('fs.is_absolute takes exactly one argument.') + return ModuleReturnValue(PurePath(args[0]).is_absolute(), []) + + @stringArgs + @noKwargs + @FeatureNew('fs.as_posix', '0.54.0') + def as_posix(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue: + """ + this function assumes you are passing a Windows path, even if on a Unix-like system + and so ALL '\' are turned to '/', even if you meant to escape a character + """ + if len(args) != 1: + raise MesonException('fs.as_posix takes exactly one argument.') + return ModuleReturnValue(PureWindowsPath(args[0]).as_posix(), []) @stringArgs @noKwargs @@ -52,7 +97,9 @@ class FSModule(ExtensionModule): @stringArgs @noKwargs def is_symlink(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue: - return self._check('is_symlink', state, args) + if len(args) != 1: + raise MesonException('fs.is_symlink takes exactly one argument.') + return ModuleReturnValue(self._absolute_dir(state, args[0]).is_symlink(), []) @stringArgs @noKwargs @@ -68,7 +115,7 @@ class FSModule(ExtensionModule): @noKwargs def hash(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue: if len(args) != 2: - raise MesonException('method takes exactly two arguments.') + raise MesonException('fs.hash takes exactly two arguments.') file = self._resolve_dir(state, args[0]) if not file.is_file(): raise MesonException('{} is not a file and therefore cannot be hashed'.format(file)) @@ -84,7 +131,7 @@ class FSModule(ExtensionModule): @noKwargs def size(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue: if len(args) != 1: - raise MesonException('method takes exactly one argument.') + raise MesonException('fs.size takes exactly one argument.') file = self._resolve_dir(state, args[0]) if not file.is_file(): raise MesonException('{} is not a file and therefore cannot be sized'.format(file)) @@ -113,7 +160,7 @@ class FSModule(ExtensionModule): @noKwargs def replace_suffix(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue: if len(args) != 2: - raise MesonException('method takes exactly two arguments.') + raise MesonException('fs.replace_suffix takes exactly two arguments.') original = PurePath(args[0]) new = original.with_suffix(args[1]) return ModuleReturnValue(str(new), []) @@ -122,7 +169,7 @@ class FSModule(ExtensionModule): @noKwargs def parent(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue: if len(args) != 1: - raise MesonException('method takes exactly one argument.') + raise MesonException('fs.parent takes exactly one argument.') original = PurePath(args[0]) new = original.parent return ModuleReturnValue(str(new), []) @@ -131,10 +178,20 @@ class FSModule(ExtensionModule): @noKwargs def name(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue: if len(args) != 1: - raise MesonException('method takes exactly one argument.') + raise MesonException('fs.name takes exactly one argument.') original = PurePath(args[0]) new = original.name return ModuleReturnValue(str(new), []) + @stringArgs + @noKwargs + @FeatureNew('fs.stem', '0.54.0') + def stem(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue: + if len(args) != 1: + raise MesonException('fs.stem takes exactly one argument.') + original = PurePath(args[0]) + new = original.stem + return ModuleReturnValue(str(new), []) + def initialize(*args, **kwargs) -> FSModule: return FSModule(*args, **kwargs) diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 2341bd2..da0a60e 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -256,24 +256,39 @@ class PkgConfigModule(ExtensionModule): prefix = prefix.as_posix() if isinstance(subdir, PurePath): subdir = subdir.as_posix() - if subdir.startswith(prefix): - subdir = subdir.replace(prefix, '') + try: + if os.path.commonpath([prefix, subdir]) == prefix: + skip = len(prefix) + 1 + subdir = subdir[skip:] + except ValueError: + pass return subdir def generate_pkgconfig_file(self, state, deps, subdirs, name, description, - url, version, pcfile, conflicts, variables): + url, version, pcfile, conflicts, variables, + uninstalled=False): deps.remove_dups() coredata = state.environment.get_coredata() - outdir = state.environment.scratch_dir + if uninstalled: + outdir = os.path.join(state.environment.build_dir, 'meson-uninstalled') + if not os.path.exists(outdir): + os.mkdir(outdir) + prefix = PurePath(state.environment.get_build_dir()) + srcdir = PurePath(state.environment.get_source_dir()) + else: + outdir = state.environment.scratch_dir + prefix = PurePath(coredata.get_builtin_option('prefix')) + # These always return paths relative to prefix + libdir = PurePath(coredata.get_builtin_option('libdir')) + incdir = PurePath(coredata.get_builtin_option('includedir')) fname = os.path.join(outdir, pcfile) - prefix = PurePath(coredata.get_builtin_option('prefix')) - # These always return paths relative to prefix - libdir = PurePath(coredata.get_builtin_option('libdir')) - incdir = PurePath(coredata.get_builtin_option('includedir')) with open(fname, 'w', encoding='utf-8') as ofile: ofile.write('prefix={}\n'.format(self._escape(prefix))) - ofile.write('libdir={}\n'.format(self._escape('${prefix}' / libdir))) - ofile.write('includedir={}\n'.format(self._escape('${prefix}' / incdir))) + if uninstalled: + ofile.write('srcdir={}\n'.format(self._escape(srcdir))) + else: + ofile.write('libdir={}\n'.format(self._escape('${prefix}' / libdir))) + ofile.write('includedir={}\n'.format(self._escape('${prefix}' / incdir))) if variables: ofile.write('\n') for k, v in variables: @@ -303,17 +318,20 @@ class PkgConfigModule(ExtensionModule): if isinstance(l, str): yield l else: - install_dir = l.get_custom_install_dir()[0] + if uninstalled: + install_dir = os.path.dirname(state.backend.get_target_filename_abs(l)) + else: + install_dir = l.get_custom_install_dir()[0] if install_dir is False: continue if 'cs' in l.compilers: if isinstance(install_dir, str): - Lflag = '-r${prefix}/%s/%s ' % (self._escape(self._make_relative(prefix, install_dir)), l.filename) + Lflag = '-r${prefix}/%s/%s' % (self._escape(self._make_relative(prefix, install_dir)), l.filename) else: # install_dir is True Lflag = '-r${libdir}/%s' % l.filename else: if isinstance(install_dir, str): - Lflag = '-L${prefix}/%s ' % self._escape(self._make_relative(prefix, install_dir)) + Lflag = '-L${prefix}/%s' % self._escape(self._make_relative(prefix, install_dir)) else: # install_dir is True Lflag = '-L${libdir}' if Lflag not in Lflags: @@ -327,22 +345,47 @@ class PkgConfigModule(ExtensionModule): if 'cs' not in l.compilers: yield '-l%s' % lname + def get_uninstalled_include_dirs(libs): + result = [] + for l in libs: + if isinstance(l, str): + continue + if l.get_subdir() not in result: + result.append(l.get_subdir()) + for i in l.get_include_dirs(): + curdir = i.get_curdir() + for d in i.get_incdirs(): + path = os.path.join(curdir, d) + if path not in result: + result.append(path) + return result + + def generate_uninstalled_cflags(libs): + for d in get_uninstalled_include_dirs(libs): + for basedir in ['${prefix}', '${srcdir}']: + path = os.path.join(basedir, d) + yield '-I%s' % self._escape(path) + if len(deps.pub_libs) > 0: ofile.write('Libs: {}\n'.format(' '.join(generate_libs_flags(deps.pub_libs)))) if len(deps.priv_libs) > 0: ofile.write('Libs.private: {}\n'.format(' '.join(generate_libs_flags(deps.priv_libs)))) ofile.write('Cflags:') - for h in subdirs: - ofile.write(' ') - if h == '.': - ofile.write('-I${includedir}') - else: - ofile.write(self._escape(PurePath('-I${includedir}') / h)) + if uninstalled: + ofile.write(' '.join(generate_uninstalled_cflags(deps.pub_libs + deps.priv_libs))) + else: + for h in subdirs: + ofile.write(' ') + if h == '.': + ofile.write('-I${includedir}') + else: + ofile.write(self._escape(PurePath('-I${includedir}') / h)) for f in deps.cflags: ofile.write(' ') ofile.write(self._escape(f)) ofile.write('\n') + @FeatureNewKwargs('pkgconfig.generate', '0.54.0', ['uninstalled_variables']) @FeatureNewKwargs('pkgconfig.generate', '0.42.0', ['extra_cflags']) @FeatureNewKwargs('pkgconfig.generate', '0.41.0', ['variables']) @permittedKwargs({'libraries', 'version', 'name', 'description', 'filebase', @@ -447,6 +490,11 @@ class PkgConfigModule(ExtensionModule): self.generate_pkgconfig_file(state, deps, subdirs, name, description, url, version, pcfile, conflicts, variables) res = build.Data(mesonlib.File(True, state.environment.get_scratch_dir(), pcfile), pkgroot) + variables = parse_variable_list(mesonlib.stringlistify(kwargs.get('uninstalled_variables', []))) + pcfile = filebase + '-uninstalled.pc' + self.generate_pkgconfig_file(state, deps, subdirs, name, description, url, + version, pcfile, conflicts, variables, + uninstalled=True) # Associate the main library with this generated pc file. If the library # is used in any subsequent call to the generated, it will generate a # 'Requires:' or 'Requires.private:'. diff --git a/mesonbuild/templates/cstemplates.py b/mesonbuild/templates/cstemplates.py new file mode 100644 index 0000000..baf2e8e --- /dev/null +++ b/mesonbuild/templates/cstemplates.py @@ -0,0 +1,126 @@ +# Copyright 2019 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import re + + +hello_cs_template = '''using System; + +public class {class_name} {{ + const String PROJECT_NAME = "{project_name}"; + + static int Main(String[] args) {{ + if (args.Length > 0) {{ + System.Console.WriteLine(String.Format("{project_name} takes no arguments..")); + return 1; + }} + Console.WriteLine(String.Format("This is project {{0}}.", PROJECT_NAME)); + return 0; + }} +}} + +''' + +hello_cs_meson_template = '''project('{project_name}', 'cs', + version : '{version}', + default_options : ['warning_level=3']) + +exe = executable('{exe_name}', '{source_name}', + install : true) + +test('basic', exe) +''' + +lib_cs_template = ''' +public class {class_name} {{ + private const int number = 6; + + public int get_number() {{ + return number; + }} +}} + +''' + +lib_cs_test_template = '''using System; + +public class {class_test} {{ + static int Main(String[] args) {{ + if (args.Length > 0) {{ + System.Console.WriteLine("{project_name} takes no arguments.."); + return 1; + }} + {class_name} c = new {class_name}(); + Boolean result = true; + return result.CompareTo(c.get_number() != 6); + }} +}} + +''' + +lib_cs_meson_template = '''project('{project_name}', 'cs', + version : '{version}', + default_options : ['warning_level=3']) + +stlib = shared_library('{lib_name}', '{source_file}', + install : true, +) + +test_exe = executable('{test_exe_name}', '{test_source_file}', + link_with : stlib) +test('{test_name}', test_exe) + +# Make this library usable as a Meson subproject. +{ltoken}_dep = declare_dependency( + include_directories: include_directories('.'), + link_with : stlib) + +''' + + +def create_exe_cs_sample(project_name, project_version): + lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower()) + uppercase_token = lowercase_token.upper() + class_name = uppercase_token[0] + lowercase_token[1:] + source_name = uppercase_token[0] + lowercase_token[1:] + '.cs' + open(source_name, 'w').write(hello_cs_template.format(project_name=project_name, + class_name=class_name)) + open('meson.build', 'w').write(hello_cs_meson_template.format(project_name=project_name, + exe_name=project_name, + source_name=source_name, + version=project_version)) + + +def create_lib_cs_sample(project_name, version): + lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower()) + uppercase_token = lowercase_token.upper() + class_name = uppercase_token[0] + lowercase_token[1:] + class_test = uppercase_token[0] + lowercase_token[1:] + '_test' + project_test = lowercase_token + '_test' + lib_cs_name = uppercase_token[0] + lowercase_token[1:] + '.cs' + test_cs_name = uppercase_token[0] + lowercase_token[1:] + '_test.cs' + kwargs = {'utoken': uppercase_token, + 'ltoken': lowercase_token, + 'class_test': class_test, + 'class_name': class_name, + 'source_file': lib_cs_name, + 'test_source_file': test_cs_name, + 'test_exe_name': project_test, + 'project_name': project_name, + 'lib_name': lowercase_token, + 'test_name': lowercase_token, + 'version': version, + } + open(lib_cs_name, 'w').write(lib_cs_template.format(**kwargs)) + open(test_cs_name, 'w').write(lib_cs_test_template.format(**kwargs)) + open('meson.build', 'w').write(lib_cs_meson_template.format(**kwargs)) diff --git a/mesonbuild/templates/cudatemplates.py b/mesonbuild/templates/cudatemplates.py new file mode 100644 index 0000000..d083fe8 --- /dev/null +++ b/mesonbuild/templates/cudatemplates.py @@ -0,0 +1,177 @@ +# Copyright 2019 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import re + + +hello_cuda_template = '''#include <iostream> + +#define PROJECT_NAME "{project_name}" + +int main(int argc, char **argv) {{ + if(argc != 1) {{ + std::cout << argv[0] << "takes no arguments.\\n"; + return 1; + }} + std::cout << "This is project " << PROJECT_NAME << ".\\n"; + return 0; +}} +''' + +hello_cuda_meson_template = '''project('{project_name}', 'cpp', + version : '{version}', + default_options : ['warning_level=3', + 'cpp_std=c++14']) + +exe = executable('{exe_name}', '{source_name}', + install : true) + +test('basic', exe) +''' + +lib_h_template = '''#pragma once +#if defined _WIN32 || defined __CYGWIN__ + #ifdef BUILDING_{utoken} + #define {utoken}_PUBLIC __declspec(dllexport) + #else + #define {utoken}_PUBLIC __declspec(dllimport) + #endif +#else + #ifdef BUILDING_{utoken} + #define {utoken}_PUBLIC __attribute__ ((visibility ("default"))) + #else + #define {utoken}_PUBLIC + #endif +#endif + +namespace {namespace} {{ + +class {utoken}_PUBLIC {class_name} {{ + +public: + {class_name}(); + int get_number() const; + +private: + + int number; + +}}; + +}} + +''' + +lib_cuda_template = '''#include <{header_file}> + +namespace {namespace} {{ + +{class_name}::{class_name}() {{ + number = 6; +}} + +int {class_name}::get_number() const {{ + return number; +}} + +}} +''' + +lib_cuda_test_template = '''#include <{header_file}> +#include <iostream> + +int main(int argc, char **argv) {{ + if(argc != 1) {{ + std::cout << argv[0] << " takes no arguments.\\n"; + return 1; + }} + {namespace}::{class_name} c; + return c.get_number() != 6; +}} +''' + +lib_cuda_meson_template = '''project('{project_name}', 'cuda', + version : '{version}', + default_options : ['warning_level=3']) + +# These arguments are only used to build the shared library +# not the executables that use the library. +lib_args = ['-DBUILDING_{utoken}'] + +shlib = shared_library('{lib_name}', '{source_file}', + install : true, + cpp_args : lib_args, + gnu_symbol_visibility : 'hidden', +) + +test_exe = executable('{test_exe_name}', '{test_source_file}', + link_with : shlib) +test('{test_name}', test_exe) + +# Make this library usable as a Meson subproject. +{ltoken}_dep = declare_dependency( + include_directories: include_directories('.'), + link_with : shlib) + +# Make this library usable from the system's +# package manager. +install_headers('{header_file}', subdir : '{header_dir}') + +pkg_mod = import('pkgconfig') +pkg_mod.generate( + name : '{project_name}', + filebase : '{ltoken}', + description : 'Meson sample project.', + subdirs : '{header_dir}', + libraries : shlib, + version : '{version}', +) +''' + + +def create_exe_cuda_sample(project_name, project_version): + lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower()) + source_name = lowercase_token + '.cu' + open(source_name, 'w').write(hello_cuda_template.format(project_name=project_name)) + open('meson.build', 'w').write(hello_cuda_meson_template.format(project_name=project_name, + exe_name=lowercase_token, + source_name=source_name, + version=project_version)) + + +def create_lib_cuda_sample(project_name, version): + lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower()) + uppercase_token = lowercase_token.upper() + class_name = uppercase_token[0] + lowercase_token[1:] + namespace = lowercase_token + lib_h_name = lowercase_token + '.h' + lib_cuda_name = lowercase_token + '.cu' + test_cuda_name = lowercase_token + '_test.cu' + kwargs = {'utoken': uppercase_token, + 'ltoken': lowercase_token, + 'header_dir': lowercase_token, + 'class_name': class_name, + 'namespace': namespace, + 'header_file': lib_h_name, + 'source_file': lib_cuda_name, + 'test_source_file': test_cuda_name, + 'test_exe_name': lowercase_token, + 'project_name': project_name, + 'lib_name': lowercase_token, + 'test_name': lowercase_token, + 'version': version, + } + open(lib_h_name, 'w').write(lib_h_template.format(**kwargs)) + open(lib_cuda_name, 'w').write(lib_cuda_template.format(**kwargs)) + open(test_cuda_name, 'w').write(lib_cuda_test_template.format(**kwargs)) + open('meson.build', 'w').write(lib_cuda_meson_template.format(**kwargs)) diff --git a/mesonbuild/templates/javatemplates.py b/mesonbuild/templates/javatemplates.py new file mode 100644 index 0000000..e8a8c15 --- /dev/null +++ b/mesonbuild/templates/javatemplates.py @@ -0,0 +1,129 @@ +# Copyright 2019 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import re + + +hello_java_template = ''' + +public class {class_name} {{ + final static String PROJECT_NAME = "{project_name}"; + + public static void main (String args[]) {{ + if(args.length != 0) {{ + System.out.println(args + " takes no arguments."); + System.exit(0); + }} + System.out.println("This is project " + PROJECT_NAME + "."); + System.exit(0); + }} +}} + +''' + +hello_java_meson_template = '''project('{project_name}', 'java', + version : '{version}', + default_options : ['warning_level=3']) + +exe = jar('{exe_name}', '{source_name}', + main_class : '{exe_name}', + install : true) + +test('basic', exe) +''' + +lib_java_template = ''' + +public class {class_name} {{ + final static int number = 6; + + public final int get_number() {{ + return number; + }} +}} + +''' + +lib_java_test_template = ''' + +public class {class_test} {{ + public static void main (String args[]) {{ + if(args.length != 0) {{ + System.out.println(args + " takes no arguments."); + System.exit(1); + }} + + {class_name} c = new {class_name}(); + Boolean result = true; + System.exit(result.compareTo(c.get_number() != 6)); + }} +}} + +''' + +lib_java_meson_template = '''project('{project_name}', 'java', + version : '{version}', + default_options : ['warning_level=3']) + +jarlib = jar('{class_name}', '{source_file}', + main_class : '{class_name}', + install : true, +) + +test_jar = jar('{class_test}', '{test_source_file}', + main_class : '{class_test}', + link_with : jarlib) +test('{test_name}', test_jar) + +# Make this library usable as a Meson subproject. +{ltoken}_dep = declare_dependency( + include_directories: include_directories('.'), + link_with : jarlib) +''' + + +def create_exe_java_sample(project_name, project_version): + lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower()) + uppercase_token = lowercase_token.upper() + class_name = uppercase_token[0] + lowercase_token[1:] + source_name = uppercase_token[0] + lowercase_token[1:] + '.java' + open(source_name, 'w').write(hello_java_template.format(project_name=project_name, + class_name=class_name)) + open('meson.build', 'w').write(hello_java_meson_template.format(project_name=project_name, + exe_name=class_name, + source_name=source_name, + version=project_version)) + + +def create_lib_java_sample(project_name, version): + lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower()) + uppercase_token = lowercase_token.upper() + class_name = uppercase_token[0] + lowercase_token[1:] + class_test = uppercase_token[0] + lowercase_token[1:] + '_test' + lib_java_name = uppercase_token[0] + lowercase_token[1:] + '.java' + test_java_name = uppercase_token[0] + lowercase_token[1:] + '_test.java' + kwargs = {'utoken': uppercase_token, + 'ltoken': lowercase_token, + 'class_test': class_test, + 'class_name': class_name, + 'source_file': lib_java_name, + 'test_source_file': test_java_name, + 'test_exe_name': lowercase_token, + 'project_name': project_name, + 'lib_name': lowercase_token, + 'test_name': lowercase_token, + 'version': version, + } + open(lib_java_name, 'w').write(lib_java_template.format(**kwargs)) + open(test_java_name, 'w').write(lib_java_test_template.format(**kwargs)) + open('meson.build', 'w').write(lib_java_meson_template.format(**kwargs)) diff --git a/mesonbuild/templates/mesontemplates.py b/mesonbuild/templates/mesontemplates.py new file mode 100644 index 0000000..f9dd442 --- /dev/null +++ b/mesonbuild/templates/mesontemplates.py @@ -0,0 +1,51 @@ +# Copyright 2019 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +meson_executable_template = '''project('{project_name}', '{language}', + version : '{version}', + default_options : [{default_options}]) + +executable('{executable}', + {sourcespec},{depspec} + install : true) +''' + +def create_meson_build(options): + if options.type != 'executable': + raise SystemExit('\nGenerating a meson.build file from existing sources is\n' + 'supported only for project type "executable".\n' + 'Run meson init in an empty directory to create a sample project.') + default_options = ['warning_level=3'] + if options.language == 'cpp': + # This shows how to set this very common option. + default_options += ['cpp_std=c++14'] + # If we get a meson.build autoformatter one day, this code could + # be simplified quite a bit. + formatted_default_options = ', '.join("'{}'".format(x) for x in default_options) + sourcespec = ',\n '.join("'{}'".format(x) for x in options.srcfiles) + depspec = '' + if options.deps: + depspec = '\n dependencies : [\n ' + depspec += ',\n '.join("dependency('{}')".format(x) + for x in options.deps.split(',')) + depspec += '],' + content = meson_executable_template.format(project_name=options.name, + language=options.language, + version=options.version, + executable=options.executable, + sourcespec=sourcespec, + depspec=depspec, + default_options=formatted_default_options) + open('meson.build', 'w').write(content) + print('Generated meson.build file:\n\n' + content) diff --git a/mesonbuild/templates/objcpptemplates.py b/mesonbuild/templates/objcpptemplates.py new file mode 100644 index 0000000..329a568 --- /dev/null +++ b/mesonbuild/templates/objcpptemplates.py @@ -0,0 +1,156 @@ +# Copyright 2019 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import re + + +lib_h_template = '''#pragma once +#if defined _WIN32 || defined __CYGWIN__ + #ifdef BUILDING_{utoken} + #define {utoken}_PUBLIC __declspec(dllexport) + #else + #define {utoken}_PUBLIC __declspec(dllimport) + #endif +#else + #ifdef BUILDING_{utoken} + #define {utoken}_PUBLIC __attribute__ ((visibility ("default"))) + #else + #define {utoken}_PUBLIC + #endif +#endif + +int {utoken}_PUBLIC {function_name}(); + +''' + +lib_objcpp_template = '''#import <{header_file}> + +/* This function will not be exported and is not + * directly callable by users of this library. + */ +int internal_function() {{ + return 0; +}} + +int {function_name}() {{ + return internal_function(); +}} +''' + +lib_objcpp_test_template = '''#import <{header_file}> +#import <iostream> + +int main(int argc, char **argv) {{ + if(argc != 1) {{ + std::cout << argv[0] << " takes no arguments." << std::endl; + return 1; + }} + return {function_name}(); +}} +''' + +lib_objcpp_meson_template = '''project('{project_name}', 'objcpp', + version : '{version}', + default_options : ['warning_level=3']) + +# These arguments are only used to build the shared library +# not the executables that use the library. +lib_args = ['-DBUILDING_{utoken}'] + +shlib = shared_library('{lib_name}', '{source_file}', + install : true, + objcpp_args : lib_args, + gnu_symbol_visibility : 'hidden', +) + +test_exe = executable('{test_exe_name}', '{test_source_file}', + link_with : shlib) +test('{test_name}', test_exe) + +# Make this library usable as a Meson subproject. +{ltoken}_dep = declare_dependency( + include_directories: include_directories('.'), + link_with : shlib) + +# Make this library usable from the system's +# package manager. +install_headers('{header_file}', subdir : '{header_dir}') + +pkg_mod = import('pkgconfig') +pkg_mod.generate( + name : '{project_name}', + filebase : '{ltoken}', + description : 'Meson sample project.', + subdirs : '{header_dir}', + libraries : shlib, + version : '{version}', +) +''' + +hello_objcpp_template = '''#import <iostream> + +#define PROJECT_NAME "{project_name}" + +int main(int argc, char **argv) {{ + if(argc != 1) {{ + std::cout << argv[0] << " takes no arguments." << std::endl; + return 1; + }} + std::cout << "This is project " << PROJECT_NAME << "." << std::endl; + return 0; +}} +''' + +hello_objcpp_meson_template = '''project('{project_name}', 'objcpp', + version : '{version}', + default_options : ['warning_level=3']) + +exe = executable('{exe_name}', '{source_name}', + install : true) + +test('basic', exe) +''' + +def create_exe_objcpp_sample(project_name, project_version): + lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower()) + source_name = lowercase_token + '.mm' + open(source_name, 'w').write(hello_objcpp_template.format(project_name=project_name)) + open('meson.build', 'w').write(hello_objcpp_meson_template.format(project_name=project_name, + exe_name=lowercase_token, + source_name=source_name, + version=project_version)) + +def create_lib_objcpp_sample(project_name, version): + lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower()) + uppercase_token = lowercase_token.upper() + function_name = lowercase_token[0:3] + '_func' + lib_h_name = lowercase_token + '.h' + lib_objcpp_name = lowercase_token + '.mm' + test_objcpp_name = lowercase_token + '_test.mm' + kwargs = {'utoken': uppercase_token, + 'ltoken': lowercase_token, + 'header_dir': lowercase_token, + 'function_name': function_name, + 'header_file': lib_h_name, + 'source_file': lib_objcpp_name, + 'test_source_file': test_objcpp_name, + 'test_exe_name': lowercase_token, + 'project_name': project_name, + 'lib_name': lowercase_token, + 'test_name': lowercase_token, + 'version': version, + } + open(lib_h_name, 'w').write(lib_h_template.format(**kwargs)) + open(lib_objcpp_name, 'w').write(lib_objcpp_template.format(**kwargs)) + open(test_objcpp_name, 'w').write(lib_objcpp_test_template.format(**kwargs)) + open('meson.build', 'w').write(lib_objcpp_meson_template.format(**kwargs)) diff --git a/run_project_tests.py b/run_project_tests.py index 2288abf..aac9f9d 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -15,6 +15,7 @@ # limitations under the License. import typing as T +import functools import itertools import os import subprocess @@ -76,6 +77,28 @@ class TestResult: self.testtime = testtime +@functools.total_ordering +class TestDef: + def __init__(self, path: Path, name: T.Optional[str], args: T.List[str], skip: bool = False): + self.path = path + self.name = name + self.args = args + self.skip = skip + + def __repr__(self) -> str: + return '<{}: {:<48} [{}: {}] -- {}>'.format(type(self).__name__, str(self.path), self.name, self.args, self.skip) + + def display_name(self) -> str: + if self.name: + return '{} ({})'.format(self.path.as_posix(), self.name) + return self.path.as_posix() + + def __lt__(self, other: T.Any) -> T.Union[bool, type(NotImplemented)]: + if isinstance(other, TestDef): + # None is not sortable, so replace it with an empty string + return (self.path, self.name or '') < (other.path, other.name or '') + return NotImplemented + class AutoDeletedDir: def __init__(self, d): self.dir = d @@ -343,17 +366,19 @@ def parse_test_args(testdir): # Build directory name must be the same so Ccache works over # consecutive invocations. -def create_deterministic_builddir(src_dir): +def create_deterministic_builddir(src_dir, name): import hashlib + if name: + src_dir += name rel_dirname = 'b ' + hashlib.sha256(src_dir.encode(errors='ignore')).hexdigest()[0:10] os.mkdir(rel_dirname) abs_pathname = os.path.join(os.getcwd(), rel_dirname) return abs_pathname -def run_test(skipped, testdir, extra_args, compiler, backend, flags, commands, should_fail): +def run_test(skipped, testdir, name, extra_args, compiler, backend, flags, commands, should_fail): if skipped: return None - with AutoDeletedDir(create_deterministic_builddir(testdir)) as build_dir: + with AutoDeletedDir(create_deterministic_builddir(testdir, name)) as build_dir: with AutoDeletedDir(tempfile.mkdtemp(prefix='i ', dir=os.getcwd())) as install_dir: try: return _run_test(testdir, build_dir, install_dir, extra_args, compiler, backend, flags, commands, should_fail) @@ -475,13 +500,53 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, compiler, backen return TestResult(validate_install(testdir, install_dir, compiler, builddata.environment), BuildStep.validate, stdo, stde, mesonlog, cicmds, gen_time, build_time, test_time) -def gather_tests(testdir: Path) -> T.List[Path]: - test_names = [t.name for t in testdir.glob('*') if t.is_dir()] - test_names = [t for t in test_names if not t.startswith('.')] # Filter non-tests files (dot files, etc) - test_nums = [(int(t.split()[0]), t) for t in test_names] - test_nums.sort() - tests = [testdir / t[1] for t in test_nums] - return tests +def gather_tests(testdir: Path) -> T.Iterator[TestDef]: + tests = [t.name for t in testdir.glob('*') if t.is_dir()] + tests = [t for t in tests if not t.startswith('.')] # Filter non-tests files (dot files, etc) + tests = [TestDef(testdir / t, None, []) for t in tests] + all_tests = [] + for t in tests: + matrix_file = t.path / 'test_matrix.json' + if not matrix_file.is_file(): + all_tests += [t] + continue + + # Build multiple tests from matrix definition + opt_list = [] # type: T.List[T.List[T.Tuple[str, bool]]] + matrix = json.loads(matrix_file.read_text()) + assert "options" in matrix + for key, val in matrix["options"].items(): + assert isinstance(val, list) + tmp_opts = [] # type: T.List[T.Tuple[str, bool]] + for i in val: + assert isinstance(i, dict) + assert "val" in i + skip = False + + # Skip the matrix entry if environment variable is present + if 'skip_on_env' in i: + for env in i['skip_on_env']: + if env in os.environ: + skip = True + + tmp_opts += [('{}={}'.format(key, i['val']), skip)] + + if opt_list: + new_opt_list = [] # type: T.List[T.List[T.Tuple[str, bool]]] + for i in opt_list: + for j in tmp_opts: + new_opt_list += [[*i, j]] + opt_list = new_opt_list + else: + opt_list = [[x] for x in tmp_opts] + + for i in opt_list: + name = ' '.join([x[0] for x in i]) + opts = ['-D' + x[0] for x in i] + skip = any([x[1] for x in i]) + all_tests += [TestDef(t.path, name, opts, skip)] + + return sorted(all_tests) def have_d_compiler(): if shutil.which("ldc2"): @@ -636,7 +701,7 @@ def should_skip_rust(backend: Backend) -> bool: return True return False -def detect_tests_to_run(only: T.List[str]) -> T.List[T.Tuple[str, T.List[Path], bool]]: +def detect_tests_to_run(only: T.List[str]) -> T.List[T.Tuple[str, T.List[TestDef], bool]]: """ Parameters ---------- @@ -645,7 +710,7 @@ def detect_tests_to_run(only: T.List[str]) -> T.List[T.Tuple[str, T.List[Path], Returns ------- - gathered_tests: list of tuple of str, list of pathlib.Path, bool + gathered_tests: list of tuple of str, list of TestDef, bool tests to run """ @@ -695,7 +760,7 @@ def detect_tests_to_run(only: T.List[str]) -> T.List[T.Tuple[str, T.List[Path], gathered_tests = [(name, gather_tests(Path('test cases', subdir)), skip) for name, subdir, skip in all_tests] return gathered_tests -def run_tests(all_tests: T.List[T.Tuple[str, T.List[Path], bool]], +def run_tests(all_tests: T.List[T.Tuple[str, T.List[TestDef], bool]], log_name_base: str, failfast: bool, extra_args: T.List[str]) -> T.Tuple[int, int, int]: global logfile @@ -704,7 +769,7 @@ def run_tests(all_tests: T.List[T.Tuple[str, T.List[Path], bool]], logfile = lf return _run_tests(all_tests, log_name_base, failfast, extra_args) -def _run_tests(all_tests: T.List[T.Tuple[str, T.List[Path], bool]], +def _run_tests(all_tests: T.List[T.Tuple[str, T.List[TestDef], bool]], log_name_base: str, failfast: bool, extra_args: T.List[str]) -> T.Tuple[int, int, int]: global stop, executor, futures, system_compiler @@ -746,8 +811,10 @@ def _run_tests(all_tests: T.List[T.Tuple[str, T.List[Path], bool]], for t in test_cases: # Jenkins screws us over by automatically sorting test cases by name # and getting it wrong by not doing logical number sorting. - (testnum, testbase) = t.name.split(' ', 1) + (testnum, testbase) = t.path.name.split(' ', 1) testname = '%.3d %s' % (int(testnum), testbase) + if t.name: + testname += ' ({})'.format(t.name) should_fail = False suite_args = [] if name.startswith('failing'): @@ -756,7 +823,7 @@ def _run_tests(all_tests: T.List[T.Tuple[str, T.List[Path], bool]], suite_args = ['--fatal-meson-warnings'] should_fail = name.split('warning-')[1] - result = executor.submit(run_test, skipped, t.as_posix(), extra_args + suite_args, + result = executor.submit(run_test, skipped or t.skip, t.path.as_posix(), t.name, extra_args + suite_args + t.args, system_compiler, backend, backend_flags, commands, should_fail) futures.append((testname, t, result)) for (testname, t, result) in futures: @@ -765,8 +832,8 @@ def _run_tests(all_tests: T.List[T.Tuple[str, T.List[Path], bool]], result = result.result() except CancelledError: continue - if (result is None) or (('MESON_SKIP_TEST' in result.stdo) and (skippable(name, t.as_posix()))): - print(yellow('Skipping:'), t.as_posix()) + if (result is None) or (('MESON_SKIP_TEST' in result.stdo) and (skippable(name, t.path.as_posix()))): + print(yellow('Skipping:'), t.display_name()) current_test = ET.SubElement(current_suite, 'testcase', {'name': testname, 'classname': name}) ET.SubElement(current_test, 'skipped', {}) @@ -774,7 +841,7 @@ def _run_tests(all_tests: T.List[T.Tuple[str, T.List[Path], bool]], else: without_install = "" if len(install_commands) > 0 else " (without install)" if result.msg != '': - print(red('Failed test{} during {}: {!r}'.format(without_install, result.step.name, t.as_posix()))) + print(red('Failed test{} during {}: {!r}'.format(without_install, result.step.name, t.display_name()))) print('Reason:', result.msg) failing_tests += 1 if result.step == BuildStep.configure and result.mlog != no_meson_log_msg: @@ -798,13 +865,13 @@ def _run_tests(all_tests: T.List[T.Tuple[str, T.List[Path], bool]], for (_, _, res) in futures: res.cancel() else: - print('Succeeded test%s: %s' % (without_install, t.as_posix())) + print('Succeeded test%s: %s' % (without_install, t.display_name())) passing_tests += 1 conf_time += result.conftime build_time += result.buildtime test_time += result.testtime total_time = conf_time + build_time + test_time - log_text_file(logfile, t, result.stdo, result.stde) + log_text_file(logfile, t.path, result.stdo, result.stde) current_test = ET.SubElement(current_suite, 'testcase', {'name': testname, 'classname': name, 'time': '%.3f' % total_time}) @@ -994,7 +1061,7 @@ if __name__ == '__main__': except UnicodeError: print(l.encode('ascii', errors='replace').decode(), '\n') for name, dirs, _ in all_tests: - dir_names = (x.name for x in dirs) + dir_names = list(set(x.path.name for x in dirs)) for k, g in itertools.groupby(dir_names, key=lambda x: x.split()[0]): tests = list(g) if len(tests) != 1: diff --git a/run_unittests.py b/run_unittests.py index 5388679..642a05f 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1857,8 +1857,14 @@ class AllPlatformTests(BasePlatformTests): # N.B. We don't check 'libdir' as it's platform dependent, see # default_libdir(): } + + if mesonbuild.mesonlib.default_prefix() == '/usr/local': + expected[None] = expected['/usr/local'] + for prefix in expected: - args = ['--prefix', prefix] + args = [] + if prefix: + args += ['--prefix', prefix] self.init(testdir, extra_args=args, default_args=False) opts = self.introspect('--buildoptions') for opt in opts: @@ -3119,15 +3125,28 @@ int main(int argc, char **argv) { except EnvironmentException: pass try: + env.detect_cs_compiler(MachineChoice.HOST) + langs.append('cs') + except EnvironmentException: + pass + try: env.detect_d_compiler(MachineChoice.HOST) langs.append('d') except EnvironmentException: pass try: + env.detect_java_compiler(MachineChoice.HOST) + langs.append('java') + except EnvironmentException: + pass + try: + env.detect_cuda_compiler(MachineChoice.HOST) + langs.append('cuda') + except EnvironmentException: + pass + try: env.detect_fortran_compiler(MachineChoice.HOST) - if is_windows() or platform.machine().lower() != 'e2k': - # Elbrus Fortran compiler can't generate debug information - langs.append('fortran') + langs.append('fortran') except EnvironmentException: pass try: @@ -3135,6 +3154,11 @@ int main(int argc, char **argv) { langs.append('objc') except EnvironmentException: pass + try: + env.detect_objcpp_compiler(MachineChoice.HOST) + langs.append('objcpp') + except EnvironmentException: + pass # FIXME: omitting rust as Windows AppVeyor CI finds Rust but doesn't link correctly for lang in langs: @@ -4254,6 +4278,7 @@ recommended as it is not supported on some platforms''') A list: string 1 True + empty list: A number: 1 yes: YES no: NO @@ -5004,6 +5029,21 @@ class LinuxlikeTests(BasePlatformTests): out = self._run(cmd + ['--libs'], override_envvars=env).strip().split() self.assertEqual(out, ['-llibmain2', '-llibinternal']) + def test_pkgconfig_uninstalled(self): + testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen') + self.init(testdir) + self.build() + + os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(self.builddir, 'meson-uninstalled') + if is_cygwin(): + os.environ['PATH'] += os.pathsep + self.builddir + + self.new_builddir() + testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen', 'dependencies') + self.init(testdir) + self.build() + self.run_tests() + def test_pkg_unfound(self): testdir = os.path.join(self.unit_test_dir, '23 unfound pkgconfig') self.init(testdir) diff --git a/test cases/cmake/1 basic/meson.build b/test cases/cmake/1 basic/meson.build index a23063d..8e1671a 100644 --- a/test cases/cmake/1 basic/meson.build +++ b/test cases/cmake/1 basic/meson.build @@ -3,10 +3,10 @@ project('cmakeSubTest', ['c', 'cpp']) cm = import('cmake') sub_pro = cm.subproject('cmMod') -sub_dep = sub_pro.dependency('cmModLib') +sub_dep = sub_pro.dependency('cmModLib++') -assert(sub_pro.target_list() == ['cmModLib'], 'There should be exactly one target') -assert(sub_pro.target_type('cmModLib') == 'shared_library', 'Target type should be shared_library') +assert(sub_pro.target_list() == ['cmModLib++'], 'There should be exactly one target') +assert(sub_pro.target_type('cmModLib++') == 'shared_library', 'Target type should be shared_library') exe1 = executable('main', ['main.cpp'], dependencies: [sub_dep]) test('test1', exe1) diff --git a/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt index 8b2f7e9..9798209 100644 --- a/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt +++ b/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt @@ -7,6 +7,6 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_definitions("-DDO_NOTHING_JUST_A_FLAG=1") -add_library(cmModLib SHARED cmMod.cpp) +add_library(cmModLib++ SHARED cmMod.cpp) include(GenerateExportHeader) -generate_export_header(cmModLib) +generate_export_header(cmModLib++) diff --git a/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp b/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp index 52f576b..0e6dc04 100644 --- a/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp +++ b/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp @@ -1,13 +1,14 @@ #pragma once +#include "cmmodlib++_export.h" #include <string> -#include "cmmodlib_export.h" -class CMMODLIB_EXPORT cmModClass { - private: - std::string str; - public: - cmModClass(std::string foo); +class CMMODLIB___EXPORT cmModClass { +private: + std::string str; - std::string getStr() const; +public: + cmModClass(std::string foo); + + std::string getStr() const; }; diff --git a/test cases/cmake/16 threads/meson.build b/test cases/cmake/16 threads/meson.build index d7ade1c..5efd73e 100644 --- a/test cases/cmake/16 threads/meson.build +++ b/test cases/cmake/16 threads/meson.build @@ -1,8 +1,12 @@ project('cmMod', ['c', 'cpp']) -cm = import('cmake') -cmMod = cm.subproject('cmMod') -cmModDep = cmMod.dependency('cmModLib') +cm = import('cmake') +cmOpts = ['-DUSE_PTHREAD=@0@'.format(get_option('use_pthread'))] +cmMod = cm.subproject('cmMod', cmake_options: cmOpts) +cmModDep1 = cmMod.dependency('cmModLib') +cmModDep2 = cmMod.dependency('cmModLib_shared') -exe1 = executable('exe1', ['main.cpp'], dependencies: [cmModDep]) +exe1 = executable('exe1', ['main.cpp'], dependencies: [cmModDep1]) +exe2 = executable('exe2', ['main.cpp'], dependencies: [cmModDep2]) test('exe1_OK', exe1) +test('exe2_OK', exe2) diff --git a/test cases/cmake/16 threads/meson_options.txt b/test cases/cmake/16 threads/meson_options.txt new file mode 100644 index 0000000..1fd9068 --- /dev/null +++ b/test cases/cmake/16 threads/meson_options.txt @@ -0,0 +1 @@ +option('use_pthread', type: 'combo', choices: ['ON', 'OFF', 'NOT_SET'], value: 'ON') diff --git a/test cases/cmake/16 threads/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/16 threads/subprojects/cmMod/CMakeLists.txt index 37d32c1..442a60e 100644 --- a/test cases/cmake/16 threads/subprojects/cmMod/CMakeLists.txt +++ b/test cases/cmake/16 threads/subprojects/cmMod/CMakeLists.txt @@ -3,7 +3,13 @@ cmake_minimum_required(VERSION 3.5) project(cmMod CXX) set (CMAKE_CXX_STANDARD 14) +if(NOT USE_PTHREAD STREQUAL NOT_SET) + set(THREADS_PREFER_PTHREAD_FLAG ${USE_PTHREAD}) +endif() find_package(Threads) add_library(cmModLib STATIC cmMod.cpp) target_link_libraries(cmModLib PRIVATE Threads::Threads) + +add_library(cmModLib_shared SHARED cmMod.cpp) +target_link_libraries(cmModLib_shared PUBLIC Threads::Threads) diff --git a/test cases/cmake/16 threads/subprojects/cmMod/cmMod.hpp b/test cases/cmake/16 threads/subprojects/cmMod/cmMod.hpp index 1c85a8b..81c5ec8 100644 --- a/test cases/cmake/16 threads/subprojects/cmMod/cmMod.hpp +++ b/test cases/cmake/16 threads/subprojects/cmMod/cmMod.hpp @@ -1,6 +1,17 @@ #pragma once -class CmMod { +#if defined _WIN32 || defined __CYGWIN__ +#define DLL_PUBLIC __declspec(dllexport) +#else +#if defined __GNUC__ +#define DLL_PUBLIC __attribute__((visibility("default"))) +#else +#pragma message("Compiler does not support symbol visibility.") +#define DLL_PUBLIC +#endif +#endif + +class DLL_PUBLIC CmMod { private: int num = 0; diff --git a/test cases/cmake/16 threads/test_matrix.json b/test cases/cmake/16 threads/test_matrix.json new file mode 100644 index 0000000..1c2c545 --- /dev/null +++ b/test cases/cmake/16 threads/test_matrix.json @@ -0,0 +1,9 @@ +{ + "options": { + "use_pthread": [ + { "val": "ON" }, + { "val": "OFF" }, + { "val": "NOT_SET" } + ] + } +} diff --git a/test cases/common/14 configure file/meson.build b/test cases/common/14 configure file/meson.build index 4a2f15a..f40dc52 100644 --- a/test cases/common/14 configure file/meson.build +++ b/test cases/common/14 configure file/meson.build @@ -185,6 +185,12 @@ ret = run_command(check_file, inf, outf) if ret.returncode() != 0 error('Error running command: @0@\n@1@'.format(ret.stdout(), ret.stderr())) endif +# Now the same, but using a File object as an argument. +inf2 = files('invalid-utf8.bin.in')[0] +ret = run_command(check_file, inf2, outf) +if ret.returncode() != 0 + error('Error running command: @0@\n@1@'.format(ret.stdout(), ret.stderr())) +endif # Test copy of a binary file outf = configure_file(input : inf, diff --git a/test cases/common/144 custom target multiple outputs/meson.build b/test cases/common/144 custom target multiple outputs/meson.build index 6412864..382c9b2 100644 --- a/test cases/common/144 custom target multiple outputs/meson.build +++ b/test cases/common/144 custom target multiple outputs/meson.build @@ -21,8 +21,19 @@ custom_target('only-install-first', install : true, install_dir : [join_paths(get_option('prefix'), get_option('includedir')), false]) -custom_target('only-install-second', +targets = custom_target('only-install-second', output : ['second.h', 'second.sh'], command : [gen, 'second', '@OUTDIR@'], install : true, install_dir : [false, join_paths(get_option('prefix'), get_option('bindir'))]) + +paths = [] +foreach i : targets.to_list() + paths += i.full_path() +endforeach + +# Skip on Windows because paths are not identical, '/' VS '\'. +if host_machine.system() != 'windows' + assert(paths == [meson.current_build_dir() / 'second.h', + meson.current_build_dir() / 'second.sh']) +endif diff --git a/test cases/common/192 args flattening/meson.build b/test cases/common/192 args flattening/meson.build index 6da2e8f..1dac2f9 100644 --- a/test cases/common/192 args flattening/meson.build +++ b/test cases/common/192 args flattening/meson.build @@ -1,29 +1,31 @@ project('args flattening') arr = get_variable('does-not-exist', ['bar', 'baz']) - assert(arr == ['bar', 'baz'], 'get_variable with array fallback is broken') set_variable('arr', ['bar', 'baz']) - assert(arr == ['bar', 'baz'], 'set_variable(array) is broken') conf = configuration_data() - conf.set('foo', ['bar', 'baz']) - assert(conf.get('foo') == ['bar', 'baz'], 'configuration_data.set(array) is broken') arr = conf.get('does-not-exist', ['bar', 'baz']) - assert(arr == ['bar', 'baz'], 'configuration_data.get with array fallback is broken') arr = meson.get_cross_property('does-not-exist', ['bar', 'baz']) - assert(arr == ['bar', 'baz'], 'meson.get_cross_property with array fallback is broken') +arr = meson.get_external_property('does-not-exist', ['bar', 'baz']) +assert(arr == ['bar', 'baz'], 'meson.get_external_property with array fallback is broken') + +arr = meson.get_external_property('does-not-exist', ['bar', 'baz'], native: true) +assert(arr == ['bar', 'baz'], 'meson.get_external_property native:true with array fallback is broken') + +arr = meson.get_external_property('does-not-exist', ['bar', 'baz'], native: false) +assert(arr == ['bar', 'baz'], 'meson.get_external_property native:false with array fallback is broken') + # Test deprecated behaviour conf.set(['foo', 'bar']) - message(conf.get('foo')) diff --git a/test cases/common/227 fs module/meson.build b/test cases/common/227 fs module/meson.build index 25778af..a732768 100644 --- a/test cases/common/227 fs module/meson.build +++ b/test cases/common/227 fs module/meson.build @@ -30,8 +30,34 @@ assert(fs.is_dir('subprojects'), 'Dir not detected correctly.') assert(not fs.is_dir('meson.build'), 'File detected as a dir.') assert(not fs.is_dir('nonexisting'), 'Bad path detected as a dir.') -assert(fs.is_dir('~'), 'expanduser not working') -assert(not fs.is_file('~'), 'expanduser not working') +assert(fs.is_dir('~'), 'home directory not detected') +assert(not fs.is_file('~'), 'home directory detected as file') + +# -- expanduser +assert(fs.expanduser('~') != '~','expanduser failed') +assert(fs.expanduser('~/foo').endswith('foo'), 'expanduser with tail failed') + +# -- as_posix +assert(fs.as_posix('/') == '/', 'as_posix idempotent') +assert(fs.as_posix('\\') == '/', 'as_posix simple') +assert(fs.as_posix('\\\\') == '/', 'as_posix simple') +assert(fs.as_posix('foo\\bar/baz') == 'foo/bar/baz', 'as_posix mixed slash') + +# -- is_absolute +winabs = 'q:/foo' +unixabs = '/foo' +if is_windows + assert(fs.is_absolute(winabs), 'is_absolute windows not detected') + assert(not fs.is_absolute(unixabs), 'is_absolute unix false positive') +else + assert(fs.is_absolute(unixabs), 'is_absolute unix not detected') + assert(not fs.is_absolute(winabs), 'is_absolute windows false positive') +endif + +# -- replace_suffix + +original = 'foo' +assert(fs.replace_suffix(original, '') == original, 'replace_suffix idempotent') original = 'foo.txt' new = fs.replace_suffix(original, '.ini') @@ -81,7 +107,11 @@ if not is_windows and build_machine.system() != 'cygwin' and is_git_checkout assert(fs.is_samepath('a_symlink', 'meson.build'), 'symlink is_samepath fail') endif +# parts of path assert(fs.parent('foo/bar') == 'foo', 'failed to get dirname') assert(fs.name('foo/bar') == 'bar', 'failed to get basename') +assert(fs.name('foo/bar/baz.dll.a') == 'baz.dll.a', 'failed to get basename with compound suffix') +assert(fs.stem('foo/bar/baz.dll') == 'baz', 'failed to get stem with suffix') +assert(fs.stem('foo/bar/baz.dll.a') == 'baz.dll', 'failed to get stem with compound suffix') subdir('subdir') diff --git a/test cases/common/229 native prop/crossfile.ini b/test cases/common/229 native prop/crossfile.ini new file mode 100644 index 0000000..62d63ed --- /dev/null +++ b/test cases/common/229 native prop/crossfile.ini @@ -0,0 +1,3 @@ +[properties] +astring = 'cross' +anarray = ['one', 'two']
\ No newline at end of file diff --git a/test cases/common/229 native prop/meson.build b/test cases/common/229 native prop/meson.build new file mode 100644 index 0000000..64da410 --- /dev/null +++ b/test cases/common/229 native prop/meson.build @@ -0,0 +1,25 @@ +project('get prop') + +x = meson.get_external_property('astring') +ref = meson.is_cross_build() ? 'cross' : 'mystring' +assert(x==ref, 'did not get native property string. did you use "meson setup --native-file native.txt"') + +x = meson.get_external_property('astring', native: true) +assert(x=='mystring', 'did not get native property with native:true and non-cross build.') + +x = meson.get_external_property('astring', 'fallback', native: false) +assert(x==ref, 'did not get get native property with native:false and non-cross build.') + + +x = meson.get_external_property('notexist', 'fallback') +assert(x=='fallback', 'fallback did not work') + +x = meson.get_external_property('notexist', 'fallback', native: true) +assert(x=='fallback', 'fallback native:true did not work') + +x = meson.get_external_property('notexist', 'fallback', native: false) +assert(x=='fallback', 'fallback native:false did not work') + + +x = meson.get_external_property('anarray') +assert(x==['one', 'two'], 'array did not work')
\ No newline at end of file diff --git a/test cases/common/229 native prop/nativefile.ini b/test cases/common/229 native prop/nativefile.ini new file mode 100644 index 0000000..03c1e03 --- /dev/null +++ b/test cases/common/229 native prop/nativefile.ini @@ -0,0 +1,3 @@ +[properties] +astring = 'mystring' +anarray = ['one', 'two']
\ No newline at end of file diff --git a/test cases/common/229 persubproject options/foo.c b/test cases/common/229 persubproject options/foo.c new file mode 100644 index 0000000..63e4de6 --- /dev/null +++ b/test cases/common/229 persubproject options/foo.c @@ -0,0 +1,5 @@ +int foo(void); + +int foo(void) { + return 0; +} diff --git a/test cases/common/229 persubproject options/meson.build b/test cases/common/229 persubproject options/meson.build new file mode 100644 index 0000000..6a76697 --- /dev/null +++ b/test cases/common/229 persubproject options/meson.build @@ -0,0 +1,10 @@ +project('persubproject options', 'c', default_options : ['default_library=both']) + +assert(get_option('default_library') == 'both', 'Parent default_library should be "both"') + +# Check it build both by calling a method only both_libraries target implement +lib = library('lib1', 'foo.c') +lib.get_static_lib() + +subproject('sub1') +subproject('sub2', default_options : ['default_library=static']) diff --git a/test cases/common/229 persubproject options/subprojects/sub1/foo.c b/test cases/common/229 persubproject options/subprojects/sub1/foo.c new file mode 100644 index 0000000..63e4de6 --- /dev/null +++ b/test cases/common/229 persubproject options/subprojects/sub1/foo.c @@ -0,0 +1,5 @@ +int foo(void); + +int foo(void) { + return 0; +} diff --git a/test cases/common/229 persubproject options/subprojects/sub1/meson.build b/test cases/common/229 persubproject options/subprojects/sub1/meson.build new file mode 100644 index 0000000..7afc934 --- /dev/null +++ b/test cases/common/229 persubproject options/subprojects/sub1/meson.build @@ -0,0 +1,7 @@ +project('sub1', 'c') + +assert(get_option('default_library') == 'both', 'Should inherit parent project default_library') + +# Check it build both by calling a method only both_libraries target implement +lib = library('lib1', 'foo.c') +lib.get_static_lib() diff --git a/test cases/common/229 persubproject options/subprojects/sub2/foo.c b/test cases/common/229 persubproject options/subprojects/sub2/foo.c new file mode 100644 index 0000000..63e4de6 --- /dev/null +++ b/test cases/common/229 persubproject options/subprojects/sub2/foo.c @@ -0,0 +1,5 @@ +int foo(void); + +int foo(void) { + return 0; +} diff --git a/test cases/common/229 persubproject options/subprojects/sub2/meson.build b/test cases/common/229 persubproject options/subprojects/sub2/meson.build new file mode 100644 index 0000000..546884d --- /dev/null +++ b/test cases/common/229 persubproject options/subprojects/sub2/meson.build @@ -0,0 +1,7 @@ +project('sub2', 'c', default_options : ['default_library=shared']) + +assert(get_option('default_library') == 'static', 'Parent should override default_library') + +# If it doesn't build only a static library, it would make target name clash. +library('lib1', 'foo.c') +shared_library('lib1', 'foo.c') diff --git a/test cases/common/47 pkgconfig-gen/dependencies/main.c b/test cases/common/47 pkgconfig-gen/dependencies/main.c new file mode 100644 index 0000000..61708d3 --- /dev/null +++ b/test cases/common/47 pkgconfig-gen/dependencies/main.c @@ -0,0 +1,6 @@ +#include <simple.h> + +int main(int argc, char *argv[]) +{ + return simple_function() == 42 ? 0 : 1; +} diff --git a/test cases/common/47 pkgconfig-gen/dependencies/meson.build b/test cases/common/47 pkgconfig-gen/dependencies/meson.build index 22bcc47..fb4e6b4 100644 --- a/test cases/common/47 pkgconfig-gen/dependencies/meson.build +++ b/test cases/common/47 pkgconfig-gen/dependencies/meson.build @@ -18,6 +18,9 @@ threads_dep = dependency('threads') custom_dep = declare_dependency(link_with : custom_lib, compile_args : ['-DCUSTOM']) custom2_dep = declare_dependency(link_args : ['-lcustom2'], compile_args : ['-DCUSTOM2']) +exe = executable('test1', 'main.c', dependencies : [pc_dep]) +test('Test1', exe) + # Generate a PC file: # - Having libmain in libraries should pull implicitly libexposed and libinternal in Libs.private # - Having libexposed in libraries should remove it from Libs.private diff --git a/test cases/common/47 pkgconfig-gen/meson.build b/test cases/common/47 pkgconfig-gen/meson.build index 7e6c670..09c46c5 100644 --- a/test cases/common/47 pkgconfig-gen/meson.build +++ b/test cases/common/47 pkgconfig-gen/meson.build @@ -1,7 +1,6 @@ project('pkgconfig-gen', 'c') # First check we have pkg-config >= 0.29 - pkgconfig = find_program('pkg-config', required: false) if not pkgconfig.found() error('MESON_SKIP_TEST: pkg-config not found') diff --git a/test cases/failing/99 no native prop/meson.build b/test cases/failing/99 no native prop/meson.build new file mode 100644 index 0000000..c956754 --- /dev/null +++ b/test cases/failing/99 no native prop/meson.build @@ -0,0 +1,3 @@ +project('missing property') + +message(meson.get_external_property('nonexisting')) diff --git a/test cases/unit/72 summary/meson.build b/test cases/unit/72 summary/meson.build index c6d94d9..c155889 100644 --- a/test cases/unit/72 summary/meson.build +++ b/test cases/unit/72 summary/meson.build @@ -7,6 +7,7 @@ summary({'Some boolean': false, 'Another boolean': true, 'Some string': 'Hello World', 'A list': ['string', 1, true], + 'empty list': [], }, section: 'Configuration') summary('A number', 1, section: 'Configuration') summary('yes', true, bool_yn : true, section: 'Configuration') |