diff options
343 files changed, 3196 insertions, 1731 deletions
diff --git a/.github/workflows/file_format.yml b/.github/workflows/file_format.yml index d61d634..a8d4ce2 100644 --- a/.github/workflows/file_format.yml +++ b/.github/workflows/file_format.yml @@ -18,3 +18,4 @@ jobs: with: python-version: '3.x' - run: python3 ./run_format_tests.py + - run: python3 ./run_shell_checks.py diff --git a/ci/ciimage/arch/install.sh b/ci/ciimage/arch/install.sh index de43cb2..4602d8f 100755 --- a/ci/ciimage/arch/install.sh +++ b/ci/ciimage/arch/install.sh @@ -15,6 +15,7 @@ pkgs=( doxygen vulkan-headers vulkan-icd-loader vulkan-validation-layers openssh mercurial gtk-sharp-2 qt5-tools libwmf cmake netcdf-fortran openmpi nasm gnustep-base gettext python-lxml hotdoc rust-bindgen qt6-base qt6-tools qt6-declarative wayland wayland-protocols + intel-oneapi-mkl # cuda ) diff --git a/ci/ciimage/fedora/install.sh b/ci/ciimage/fedora/install.sh index aa87655..2845555 100755 --- a/ci/ciimage/fedora/install.sh +++ b/ci/ciimage/fedora/install.sh @@ -17,6 +17,8 @@ pkgs=( qt6-qtdeclarative-devel qt6-qtbase-devel qt6-qttools-devel qt6-linguist qt6-qtbase-private-devel libwmf-devel valgrind cmake openmpi-devel nasm gnustep-base-devel gettext-devel ncurses-devel libxml2-devel libxslt-devel libyaml-devel glib2-devel json-glib-devel libgcrypt-devel wayland-devel wayland-protocols-devel + # HACK: remove npm once we switch back to hotdoc sdist + nodejs-npm ) # Sys update @@ -24,7 +26,12 @@ dnf -y upgrade # Install deps dnf -y install "${pkgs[@]}" -install_python_packages hotdoc +# HACK: build hotdoc from git repo since current sdist is broken on modern compilers +# change back to 'hotdoc' once it's fixed +install_python_packages git+https://github.com/hotdoc/hotdoc + +# HACK: uninstall npm after building hotdoc, remove when we remove npm +dnf -y remove nodejs-npm # Cleanup dnf -y clean all diff --git a/ci/ciimage/gentoo/install.sh b/ci/ciimage/gentoo/install.sh index 30b0299..909a595 100755 --- a/ci/ciimage/gentoo/install.sh +++ b/ci/ciimage/gentoo/install.sh @@ -108,7 +108,7 @@ cat <<-EOF > /etc/portage/package.use/ci # Some of these settings are needed just to get the binpkg but # aren't negative to have anyway - sys-devel/gcc ada d + sys-devel/gcc ada d jit >=sys-devel/gcc-13 ada objc objc++ sys-devel/gcc pgo lto diff --git a/ci/ciimage/opensuse/install.sh b/ci/ciimage/opensuse/install.sh index 7a76071..18f4ea7 100755 --- a/ci/ciimage/opensuse/install.sh +++ b/ci/ciimage/opensuse/install.sh @@ -19,6 +19,8 @@ pkgs=( boost-devel libboost_date_time-devel libboost_filesystem-devel libboost_locale-devel libboost_system-devel libboost_test-devel libboost_log-devel libboost_regex-devel libboost_python3-devel libboost_regex-devel + # HACK: remove npm once we switch back to hotdoc sdist + npm ) # Sys update @@ -27,7 +29,12 @@ zypper --non-interactive update # Install deps zypper install -y "${pkgs[@]}" -install_python_packages hotdoc +# HACK: build hotdoc from git repo since current sdist is broken on modern compilers +# change back to 'hotdoc' once it's fixed +install_python_packages git+https://github.com/hotdoc/hotdoc + +# HACK: uninstall npm after building hotdoc, remove when we remove npm +zypper remove -y -u npm echo 'export PKG_CONFIG_PATH="/usr/lib64/mpi/gcc/openmpi3/lib64/pkgconfig:$PKG_CONFIG_PATH"' >> /ci/env_vars.sh diff --git a/docs/markdown/Build-options.md b/docs/markdown/Build-options.md index 190705d..ee12f55 100644 --- a/docs/markdown/Build-options.md +++ b/docs/markdown/Build-options.md @@ -244,6 +244,10 @@ project which also has an option called `some_option`, then calling `yield` is `false`, `get_option` returns the value of the subproject's option. +*Since 1.8.0* `-Dsub:some_option=anothervalue`, when used with a +yielding option, sets the value separately from the option +it yields to. + ## Built-in build options diff --git a/docs/markdown/Builtin-options.md b/docs/markdown/Builtin-options.md index ee07df4..c23eaae 100644 --- a/docs/markdown/Builtin-options.md +++ b/docs/markdown/Builtin-options.md @@ -72,28 +72,28 @@ Options that are labeled "per machine" in the table are set per machine. See the [specifying options per machine](#specifying-options-per-machine) section for details. -| Option | Default value | Description | Is per machine | Is per subproject | -| -------------------------------------- | ------------- | ----------- | -------------- | ----------------- | +| Option | Default value | Description | Is per machine | Per subproject (since) | +| -------------------------------------- | ------------- | ----------- | -------------- | ---------------------- | | auto_features {enabled, disabled, auto} | auto | Override value of all 'auto' features | no | no | | backend {ninja, vs,<br>vs2010, vs2012, vs2013, vs2015, vs2017, vs2019, vs2022, xcode, none} | ninja | Backend to use | no | no | | genvslite {vs2022} | vs2022 | Setup multi-buildtype ninja build directories and Visual Studio solution | no | no | -| buildtype {plain, debug,<br>debugoptimized, release, minsize, custom} | debug | Build type to use | no | no | -| debug | true | Enable debug symbols and other information | no | no | -| default_both_libraries {shared, static, auto} | shared | Default library type for both_libraries | no | no | -| default_library {shared, static, both} | shared | Default library type | no | yes | +| buildtype {plain, debug,<br>debugoptimized, release, minsize, custom} | debug | Build type to use | no | 1.8.0 | +| debug | true | Enable debug symbols and other information | no | 1.8.0 | +| default_both_libraries {shared, static, auto} | shared | Default library type for both_libraries | no | 1.8.0 | +| default_library {shared, static, both} | shared | Default library type | no | 0.54.0 | | errorlogs | true | Whether to print the logs from failing tests. | no | no | | install_umask {preserve, 0000-0777} | 022 | Default umask to apply on permissions of installed files | no | no | | layout {mirror,flat} | mirror | Build directory layout | no | no | -| optimization {plain, 0, g, 1, 2, 3, s} | 0 | Optimization level | no | no | +| optimization {plain, 0, g, 1, 2, 3, s} | 0 | Optimization level | no | 1.8.0 | | pkg_config_path {OS separated path} | '' | Additional paths for pkg-config to search before builtin paths | yes | no | | prefer_static | false | Whether to try static linking before shared linking | no | no | | cmake_prefix_path | [] | Additional prefixes for cmake to search before builtin paths | yes | no | | stdsplit | true | Split stdout and stderr in test logs | no | no | -| strip | false | Strip targets on install | no | no | -| unity {on, off, subprojects} | off | Unity build | no | no | -| unity_size {>=2} | 4 | Unity file block size | no | no | -| warning_level {0, 1, 2, 3, everything} | 1 | Set the warning level. From 0 = compiler default to everything = highest | no | yes | -| werror | false | Treat warnings as errors | no | yes | +| strip | false | Strip targets on install | no | 1.8.0 | +| unity {on, off, subprojects} | off | Unity build | no | 1.8.0 | +| unity_size {>=2} | 4 | Unity file block size | no | 1.8.0 | +| warning_level {0, 1, 2, 3, everything} | 1 | Set the warning level. From 0 = compiler default to everything = highest | no | 0.56.0 | +| werror | false | Treat warnings as errors | no | 0.54.0 | | wrap_mode {default, nofallback,<br>nodownload, forcefallback, nopromote} | default | Wrap mode to use | no | no | | force_fallback_for | [] | Force fallback for those dependencies | no | no | | vsenv | false | Activate Visual Studio environment | no | no | @@ -303,6 +303,7 @@ or compiler being used: | cpp_thread_count | 4 | integer value ≥ 0 | Number of threads to use with emcc when using threads | | cpp_winlibs | see below | free-form comma-separated list | Standard Windows libs to link against | | fortran_std | none | [none, legacy, f95, f2003, f2008, f2018] | Fortran language standard to use | +| rust_dynamic_std | false | true, false | Whether to link dynamically to the Rust standard library *(Added in 1.9.0)* | | cuda_ccbindir | | filesystem path | CUDA non-default toolchain directory to use (-ccbin) *(Added in 0.57.1)* | The default values of `c_winlibs` and `cpp_winlibs` are in @@ -370,11 +371,10 @@ allowing differences in behavior to crop out. ## Specifying options per subproject -Since *0.54.0* `default_library` and `werror` built-in options can be -defined per subproject. This is useful, for example, when building -shared libraries in the main project and statically linking a subproject, -or when the main project must build with no warnings but some subprojects -cannot. +Several built-in options and all compiler options can be defined per subproject. +This is useful, for example, when building shared libraries in the main project +and statically linking a subproject, or when the main project must build +with no warnings but some subprojects cannot. Most of the time, this would be used either in the parent project by setting subproject's default_options (e.g. `subproject('foo', @@ -382,12 +382,30 @@ default_options: 'default_library=static')`), or by the user through the command line: `-Dfoo:default_library=static`. The value is overridden in this order: +- `opt=value` from parent project's `default_options` +- `opt=value` from subproject's `default_options` +- `subp:opt=value` from parent project's default options +- `opt=value` from `subproject()` `default_options` +- `opt=value` from machine file +- `opt=value` from command line +- `subp:opt=value` from machine file +- `subp:opt=value` from command line + +### Old behavior + +Between *0.54.0* and *1.7.x* only a few options could be defined per subproject: +* `default_library` and `werror` since *0.54.0*; +* `warning_level` since *0.56.0*; +* compiler options since *0.63.0* + +The value was overridden 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 +- Value from subproject's `default_options` +- Value from `subproject()` `default_options` +- Value from machine file +- Value from command line -Since *0.56.0* `warning_level` can also be defined per subproject. ## Module options diff --git a/docs/markdown/Commands.md b/docs/markdown/Commands.md index 247f2d7..09d8fd9 100644 --- a/docs/markdown/Commands.md +++ b/docs/markdown/Commands.md @@ -493,6 +493,9 @@ or `--check-only` option). input instead of reading it from a file. This cannot be used with `--recursive` or `--inline` arguments. +*Since 1.9.0* Using `-` as source file with `--editor-config` now requires +`--source-file-path` argument to ensure consistent results. + #### Differences with `muon fmt` diff --git a/docs/markdown/Fs-module.md b/docs/markdown/Fs-module.md index 7ba4832..91c706e 100644 --- a/docs/markdown/Fs-module.md +++ b/docs/markdown/Fs-module.md @@ -206,13 +206,26 @@ fs.name('foo/bar/baz.dll.a') # baz.dll.a *since 0.54.0* Returns the last component of the path, dropping the last part of the -suffix +suffix. ```meson fs.stem('foo/bar/baz.dll') # baz fs.stem('foo/bar/baz.dll.a') # baz.dll ``` +### suffix + +*since 1.9.0* + +Returns the last dot-separated portion of the final component of the path +including the dot, if any. + +```meson +fs.suffix('foo/bar/baz.dll') # .dll +fs.suffix('foo/bar/baz.dll.a') # .a +fs.suffix('foo/bar') # (empty) +``` + ### read - `read(path, encoding: 'utf-8')` *(since 0.57.0)*: return a [string](Syntax.md#strings) with the contents of the given `path`. diff --git a/docs/markdown/Release-notes-for-0.38.0.md b/docs/markdown/Release-notes-for-0.38.0.md index 5aaca0a..7be99ab 100644 --- a/docs/markdown/Release-notes-for-0.38.0.md +++ b/docs/markdown/Release-notes-for-0.38.0.md @@ -106,7 +106,7 @@ the check is now ~40% faster. ## Array indexing now supports fallback values The second argument to the array -[[list.get]] function is now returned +[[array.get]] function is now returned if the specified index could not be found ```meson diff --git a/docs/markdown/Rust.md b/docs/markdown/Rust.md index 08580cd..452dad6 100644 --- a/docs/markdown/Rust.md +++ b/docs/markdown/Rust.md @@ -27,14 +27,20 @@ feature is stabilized. ## Mixing Rust and non-Rust sources -Meson currently does not support creating a single target with Rust and non Rust -sources mixed together, therefore one must compile multiple libraries and link -them. +*(Since 1.9.0)* Rust supports mixed targets, but only supports using +`rustc` as the linker for such targets. If you need to use a non-Rust +linker, or support Meson < 1.9.0, see below. + +Until Meson 1.9.0, Meson did not support creating a single target with +Rust and non Rust sources mixed together. One had to compile a separate +Rust `static_library` or `shared_library`, and link it into the C build +target (e.g., a library or an executable). ```meson rust_lib = static_library( 'rust_lib', sources : 'lib.rs', + rust_abi: 'c', ... ) @@ -44,7 +50,6 @@ c_lib = static_library( link_with : rust_lib, ) ``` -This is an implementation detail of Meson, and is subject to change in the future. ## Mixing Generated and Static sources diff --git a/docs/markdown/Syntax.md b/docs/markdown/Syntax.md index 05f5038..f4a2e17 100644 --- a/docs/markdown/Syntax.md +++ b/docs/markdown/Syntax.md @@ -566,7 +566,7 @@ executable('exe1', 'foo.c', 'bar.c', 'foobar.c') Because of an internal implementation detail, the following syntax is currently also supported, even though the first argument of -[[executable]] is a single [[@str]] and not a [[@list]]: +[[executable]] is a single [[@str]] and not a [[@array]]: ```meson # WARNING: This example is only valid because of an internal diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index 73358e7..302546e 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -322,8 +322,8 @@ foo-bar-1.0 = foo_bar_dep **Note**: This is experimental and has no backwards or forwards compatibility guarantees. See [Meson's rules on mixing build systems](Mixing-build-systems.md). -Cargo subprojects automatically override the `<package_name>-<version>-rs` dependency -name: +Cargo subprojects automatically call `override_dependency` with the name +`<package_name>-<version>-<suffix>`, where every part is defeined as follows: - `package_name` is defined in `[package] name = ...` section of the `Cargo.toml`. - `version` is the API version deduced from `[package] version = ...` as follow: * `x.y.z` -> 'x' @@ -331,9 +331,10 @@ name: * `0.0.x` -> '0' It allows to make different dependencies for incompatible versions of the same crate. -- `-rs` suffix is added to distinguish from regular system dependencies, for - example `gstreamer-1.0` is a system pkg-config dependency and `gstreamer-0.22-rs` - is a Cargo dependency. +- the suffix is `-rs` for `rlib` and `dylib` crate types, otherwise it is the + crate type (e.g. `staticlib` or `cdylib`). The suffix is added to distinguish + Rust crates from regular system dependencies; for example `gstreamer-1.0` is a + system pkg-config dependency and `gstreamer-0.22-rs` is a Cargo dependency. That means the `.wrap` file should have `dependency_names = foo-1-rs` in their `[provide]` section when `Cargo.toml` has package name `foo` and version `1.2`. diff --git a/docs/markdown/Yaml-RefMan.md b/docs/markdown/Yaml-RefMan.md index 2872791..cce844b 100644 --- a/docs/markdown/Yaml-RefMan.md +++ b/docs/markdown/Yaml-RefMan.md @@ -150,9 +150,9 @@ optargs: ... varargs: - name: Some name # [required] - type: str | list[str | int] # [required] - description: Some helpful text # [required] + name: Some name # [required] + type: str | array[str | int] # [required] + description: Some helpful text # [required] since: 0.42.0 deprecated: 100.99.0 min_varargs: 1 diff --git a/docs/markdown/i18n-module.md b/docs/markdown/i18n-module.md index da6fce7..ac6146d 100644 --- a/docs/markdown/i18n-module.md +++ b/docs/markdown/i18n-module.md @@ -90,7 +90,7 @@ for each executable. Positional arguments are the following: * name `str`: the name of the resulting pot file. -* sources `list[str|File|build_tgt|custom_tgt]`: +* sources `array[str|File|build_tgt|custom_tgt]`: source files or targets. May be a list of `string`, `File`, [[@build_tgt]], or [[@custom_tgt]] returned from other calls to this function. diff --git a/docs/markdown/snippets/array-flatten.md b/docs/markdown/snippets/array-flatten.md new file mode 100644 index 0000000..eaab19c --- /dev/null +++ b/docs/markdown/snippets/array-flatten.md @@ -0,0 +1,5 @@ +## Array `.flatten()` method + +Arrays now have a `.flatten()` method, which turns nested arrays into a single +flat array. This provides the same effect that Meson often does to arrays +internally, such as when passed to most function arguments. diff --git a/docs/markdown/snippets/clang-tidy-improvement.md b/docs/markdown/snippets/clang-tidy-improvement.md new file mode 100644 index 0000000..f79463e --- /dev/null +++ b/docs/markdown/snippets/clang-tidy-improvement.md @@ -0,0 +1,5 @@ +## `clang-tidy`'s auto-generated targets correctly select source files + +In previous versions, the target would run `clang-tidy` on _every_ C-like source files (.c, .h, .cpp, .hpp). It did not work correctly because some files, especially headers, are not intended to be consumed as is. + +It will now run only on source files participating in targets. diff --git a/docs/markdown/snippets/fs_suffix.md b/docs/markdown/snippets/fs_suffix.md new file mode 100644 index 0000000..7059008 --- /dev/null +++ b/docs/markdown/snippets/fs_suffix.md @@ -0,0 +1,4 @@ +## Added suffix function to the FS module + +The basename and stem were already available. For completeness, expose also the +suffix. diff --git a/docs/markdown/snippets/meson-format-stdin-editorconfig.md b/docs/markdown/snippets/meson-format-stdin-editorconfig.md new file mode 100644 index 0000000..4a848b7 --- /dev/null +++ b/docs/markdown/snippets/meson-format-stdin-editorconfig.md @@ -0,0 +1,5 @@ +## meson format now has a --source-file-path argument when reading from stdin + +This argument is mandatory to mix stdin reading with the use of editor config. +It allows to know where to look for the .editorconfig, and to use the right +section of .editorconfig based on the parsed file name. diff --git a/docs/markdown/snippets/rust-dynamic-std.md b/docs/markdown/snippets/rust-dynamic-std.md new file mode 100644 index 0000000..ac4e8a7 --- /dev/null +++ b/docs/markdown/snippets/rust-dynamic-std.md @@ -0,0 +1,7 @@ +## New experimental option `rust_dynamic_std` + +A new option `rust_dynamic_std` can be used to link Rust programs so +that they use a dynamic library for the Rust `libstd`. + +Right now, `staticlib` crates cannot be produced if `rust_dynamic_std` is +true, but this may change in the future. diff --git a/docs/markdown/snippets/rust-mixed.md b/docs/markdown/snippets/rust-mixed.md new file mode 100644 index 0000000..d42ab90 --- /dev/null +++ b/docs/markdown/snippets/rust-mixed.md @@ -0,0 +1,5 @@ +## Rust and non-Rust sources in the same target + +Meson now supports creating a single target with Rust and non Rust +sources mixed together. In this case, if specified, `link_language` +must be set to `rust`. diff --git a/docs/markdown/snippets/swift-module-name.md b/docs/markdown/snippets/swift-module-name.md new file mode 100644 index 0000000..689dd84 --- /dev/null +++ b/docs/markdown/snippets/swift-module-name.md @@ -0,0 +1,9 @@ +## Explicitly setting Swift module name is now supported + +It is now possible to set the Swift module name for a target via the +*swift_module_name* target kwarg, overriding the default inferred from the +target name. + +```meson +lib = library('foo', 'foo.swift', swift_module_name: 'Foo') +``` diff --git a/docs/markdown/snippets/swift-parse-as-library.md b/docs/markdown/snippets/swift-parse-as-library.md new file mode 100644 index 0000000..5208899 --- /dev/null +++ b/docs/markdown/snippets/swift-parse-as-library.md @@ -0,0 +1,8 @@ +## Top-level statement handling in Swift libraries + +The Swift compiler normally treats modules with a single source +file (and files named main.swift) to run top-level code at program +start. This emits a main symbol which is usually undesirable in a +library target. Meson now automatically passes the *-parse-as-library* +flag to the Swift compiler in case of single-file library targets to +disable this behavior unless the source file is called main.swift. diff --git a/docs/markdown/snippets/swift-pass-c-compiler-options.md b/docs/markdown/snippets/swift-pass-c-compiler-options.md index 3610a8e..90904b9 100644 --- a/docs/markdown/snippets/swift-pass-c-compiler-options.md +++ b/docs/markdown/snippets/swift-pass-c-compiler-options.md @@ -1,8 +1,9 @@ ## Swift compiler receives select C family compiler options -Meson now passes select few C family (C/Obj-C) compiler options to the -Swift compiler, notably *-std=*, in order to improve the compatibility -of C code as interpreted by the C compiler and the Swift compiler. +Meson now passes select few C family (C/C++/Obj-C/Obj-C++) compiler +options to the Swift compiler, notably *-std=*, in order to improve +the compatibility of C code as interpreted by the C compiler and the +Swift compiler. NB: This does not include any of the options set in the target's c_flags. diff --git a/docs/markdown/snippets/swift_cxx_interoperability.md b/docs/markdown/snippets/swift_cxx_interoperability.md index f18e114..9e324f9 100644 --- a/docs/markdown/snippets/swift_cxx_interoperability.md +++ b/docs/markdown/snippets/swift_cxx_interoperability.md @@ -1,13 +1,23 @@ ## Swift/C++ interoperability is now supported It is now possible to create Swift executables that can link to C++ or -Objective-C++ libraries. Only specifying a bridging header for the Swift -target is required. +Objective-C++ libraries. To enable this feature, set the target kwarg +_swift\_interoperability\_mode_ to 'cpp'. + +To import C++ code, specify a bridging header in the Swift target's +sources, or use another way such as adding a directory containing a +Clang module map to its include path. + +Note: Enabling C++ interoperability in a library target is a breaking +change. Swift libraries that enable it need their consumers to enable +it as well, as per [the Swift documentation][1]. Swift 5.9 is required to use this feature. Xcode 15 is required if the Xcode backend is used. ```meson lib = static_library('mylib', 'mylib.cpp') -exe = executable('prog', 'main.swift', 'mylib.h', link_with: lib) +exe = executable('prog', 'main.swift', 'mylib.h', link_with: lib, swift_interoperability_mode: 'cpp') ``` + +[1]: https://www.swift.org/documentation/cxx-interop/project-build-setup/#vending-packages-that-enable-c-interoperability diff --git a/docs/markdown/snippets/wrapdbv1.md b/docs/markdown/snippets/wrapdbv1.md new file mode 100644 index 0000000..4f0c847 --- /dev/null +++ b/docs/markdown/snippets/wrapdbv1.md @@ -0,0 +1,5 @@ +## Limited support for WrapDB v1 + +WrapDB v1 has been discontinued for several years, Meson will now print a +deprecation warning if a v1 URL is still being used. Wraps can be updated to +latest version using `meson wrap update` command. diff --git a/docs/yaml/builtins/meson.yaml b/docs/yaml/builtins/meson.yaml index 516e41c..eac8368 100644 --- a/docs/yaml/builtins/meson.yaml +++ b/docs/yaml/builtins/meson.yaml @@ -139,7 +139,7 @@ methods: to install only a subset of the files. By default the script has no install tag which means it is not being run when `meson install --tags` argument is specified. - + dry_run: type: bool since: 1.1.0 @@ -447,12 +447,12 @@ methods: description: Returns the version string specified in [[project]] function call. - name: project_license - returns: list[str] + returns: array[str] since: 0.45.0 description: Returns the array of licenses specified in [[project]] function call. - name: project_license_files - returns: list[file] + returns: array[file] since: 1.1.0 description: Returns the array of license files specified in the [[project]] function call. @@ -498,10 +498,10 @@ methods: posargs: env: - type: env | str | list[str] | dict[str] | dict[list[str]] + type: env | str | array[str] | dict[str] | dict[array[str]] description: | The [[@env]] object to add. - Since *0.62.0* list of strings is allowed in dictionary values. In that + Since *0.62.0* array of strings is allowed in dictionary values. In that case values are joined using the separator. kwargs: separator: diff --git a/docs/yaml/elementary/list.yml b/docs/yaml/elementary/array.yml index 1ffb6d2..7d0480a 100644 --- a/docs/yaml/elementary/list.yml +++ b/docs/yaml/elementary/array.yml @@ -1,5 +1,5 @@ -name: list -long_name: List +name: array +long_name: Array description: An array of elements. See [arrays](Syntax.md#arrays). is_container: true @@ -30,7 +30,7 @@ methods: posargs: index: type: int - description: Index of the list position to query. Negative values start at the end of the list + description: Index of the array position to query. Negative values start at the end of the array optargs: fallback: @@ -39,4 +39,9 @@ methods: - name: length returns: int - description: Returns the current size of the array / list. + description: Returns the current size of the array. + +- name: flatten + returns: array[any] + since: 1.9.0 + description: Returns a flattened copy of the array, with all nested arrays removed. diff --git a/docs/yaml/elementary/dict.yml b/docs/yaml/elementary/dict.yml index 19263df..70844bb 100644 --- a/docs/yaml/elementary/dict.yml +++ b/docs/yaml/elementary/dict.yml @@ -44,5 +44,5 @@ methods: description: Fallback value that is returned if the key is not in the [[@dict]]. - name: keys - returns: list[str] + returns: array[str] description: Returns an array of keys in the dictionary. diff --git a/docs/yaml/elementary/str.yml b/docs/yaml/elementary/str.yml index 44aa742..c70a4f5 100644 --- a/docs/yaml/elementary/str.yml +++ b/docs/yaml/elementary/str.yml @@ -203,7 +203,7 @@ methods: # str.split - name: split - returns: list[str] + returns: array[str] description: | Splits the string at the specified character (or whitespace if not set) and returns the parts in an @@ -224,7 +224,7 @@ methods: description: Specifies the character / substring where to split the string. - name: splitlines - returns: list[str] + returns: array[str] since: 1.2.0 description: | Splits the string into an array of lines. @@ -270,7 +270,7 @@ methods: The strings to join with the current string. Before Meson *0.60.0* this function only accepts a single positional - argument of the type [[list[str]]]. + argument of the type [[array[str]]]. # str.underscorify - name: underscorify diff --git a/docs/yaml/functions/_build_target_base.yaml b/docs/yaml/functions/_build_target_base.yaml index 1721b29..b952170 100644 --- a/docs/yaml/functions/_build_target_base.yaml +++ b/docs/yaml/functions/_build_target_base.yaml @@ -43,13 +43,13 @@ kwargs: description: precompiled header file to use for the given language <lang>_args: - type: list[str] + type: array[str] description: | compiler flags to use for the given language; eg: `cpp_args` for C++ vala_args: - type: list[str | file] + type: array[str | file] description: | Compiler flags for Vala. Unlike other languages this may contain Files @@ -74,7 +74,7 @@ kwargs: but which will be removed on install dependencies: - type: list[dep] + type: array[dep] description: | one or more dependency objects created with @@ -98,7 +98,7 @@ kwargs: 0.56.0, use `win_subsystem` instead. link_args: - type: list[str] + type: array[str] description: | Flags to use during linking. You can use UNIX-style flags here for all platforms. @@ -125,7 +125,7 @@ kwargs: *(broken until 0.55.0)* link_whole: - type: list[lib | custom_tgt | custom_idx] + type: array[lib | custom_tgt | custom_idx] since: 0.40.0 description: | Links all contents of the given static libraries whether they are used or @@ -133,18 +133,18 @@ kwargs: '/WHOLEARCHIVE' MSVC linker option. This allows the linked target to re-export symbols from all objects in the static libraries. - *(since 0.41.0)* If passed a list that list will be flattened. + *(since 0.41.0)* If passed an array that array will be flattened. *(since 0.51.0)* This argument also accepts outputs produced by custom targets. The user must ensure that the output is a library in the correct format. link_with: - type: list[lib | custom_tgt | custom_idx] + type: array[lib | custom_tgt | custom_idx] description: | One or more shared or static libraries - (built by this project) that this target should be linked with. *(since 0.41.0)* If passed a - list this list will be flattened. *(since 0.51.0)* The arguments can also be custom targets. + (built by this project) that this target should be linked with. *(since 0.41.0)* If passed an + array that array will be flattened. *(since 0.51.0)* The arguments can also be custom targets. In this case Meson will assume that merely adding the output file in the linker command line is sufficient to make linking work. If this is not sufficient, then the build system writer must write all other steps manually. @@ -156,7 +156,7 @@ kwargs: description: Controls whether Meson adds the current source and build directories to the include path include_directories: - type: list[inc | str] + type: array[inc | str] description: | one or more objects created with the [[include_directories]] function, or *(since 0.50.0)* strings, which will be transparently expanded to include directory objects @@ -175,7 +175,7 @@ kwargs: something like this: `install_dir : get_option('libdir') / 'projectname-1.0'`. install_mode: - type: list[str | int] + type: array[str | int] since: 0.47.0 description: | Specify the file mode in symbolic format @@ -198,7 +198,7 @@ kwargs: (but *not* before that). On Windows, this argument has no effect. objects: - type: list[extracted_obj | file | str] + type: array[extracted_obj | file | str] description: | List of object files that should be linked in this target. @@ -208,7 +208,7 @@ kwargs: object files had to be placed in `sources`. name_prefix: - type: str | list[void] + type: str | array[void] description: | The string that will be used as the prefix for the target output filename by overriding the default (only used for @@ -219,7 +219,7 @@ kwargs: Set this to `[]`, or omit the keyword argument for the default behaviour. name_suffix: - type: str | list[void] + type: str | array[void] description: | The string that will be used as the extension for the target by overriding the default. By default on Windows this is @@ -235,7 +235,7 @@ kwargs: Set this to `[]`, or omit the keyword argument for the default behaviour. override_options: - type: list[str] | dict[str | bool | int | list[str]] + type: array[str] | dict[str | bool | int | array[str]] since: 0.40.0 description: | takes an array of strings in the same format as `project`'s `default_options` @@ -256,7 +256,7 @@ kwargs: do not support GNU visibility arguments. d_import_dirs: - type: list[inc | str] + type: array[inc | str] since: 0.62.0 description: | the directories to add to the string search path (i.e. `-J` switch for DMD). @@ -268,11 +268,11 @@ kwargs: description: When set to true, the D modules are compiled in debug mode. d_module_versions: - type: list[str | int] + type: array[str | int] description: List of module version identifiers set when compiling D sources. d_debug: - type: list[str] + type: array[str] description: | The [D version identifiers](https://dlang.org/spec/version.html#version) to add during the compilation of D source files. diff --git a/docs/yaml/functions/add_global_arguments.yaml b/docs/yaml/functions/add_global_arguments.yaml index 3b26d10..25e4eef 100644 --- a/docs/yaml/functions/add_global_arguments.yaml +++ b/docs/yaml/functions/add_global_arguments.yaml @@ -15,11 +15,11 @@ varargs: kwargs: language: - type: list[str] + type: array[str] required: true description: | Specifies the language(s) that the arguments should be - applied to. If a list of languages is given, the arguments are added + applied to. If an array of languages is given, the arguments are added to each of the corresponding compiler command lines. Note that there is no way to remove an argument set in this way. If you have an argument that is only used in a subset of targets, you have to specify diff --git a/docs/yaml/functions/add_test_setup.yaml b/docs/yaml/functions/add_test_setup.yaml index 3d666c7..a29b137 100644 --- a/docs/yaml/functions/add_test_setup.yaml +++ b/docs/yaml/functions/add_test_setup.yaml @@ -18,7 +18,7 @@ posargs: kwargs: env: - type: env | list[str] | dict[str] + type: env | array[str] | dict[str] description: | environment variables to set , such as `['NAME1=value1', 'NAME2=value2']`, @@ -26,7 +26,7 @@ kwargs: environment juggling. *(Since 0.52.0)* A dictionary is also accepted. exe_wrapper: - type: list[str | external_program] + type: array[str | external_program] description: The command or script followed by the arguments to it gdb: @@ -52,9 +52,9 @@ kwargs: without the `--setup` option. exclude_suites: - type: list[str] + type: array[str] since: 0.57.0 description: - A list of test suites that should be excluded when using this setup. + An array of test suites that should be excluded when using this setup. Suites specified in the `--suite` option - to `meson test` will always run, overriding `add_test_setup` if necessary.
\ No newline at end of file + to `meson test` will always run, overriding `add_test_setup` if necessary. diff --git a/docs/yaml/functions/benchmark.yaml b/docs/yaml/functions/benchmark.yaml index 7a555a4..fe69f28 100644 --- a/docs/yaml/functions/benchmark.yaml +++ b/docs/yaml/functions/benchmark.yaml @@ -28,11 +28,11 @@ posargs: kwargs: args: - type: list[str | file | tgt | external_program] + type: array[str | file | tgt | external_program] description: Arguments to pass to the executable env: - type: env | list[str] | dict[str] + type: env | array[str] | dict[str] description: | environment variables to set, such as `['NAME1=value1', 'NAME2=value2']`, or an [[@env]] object which allows more sophisticated @@ -46,11 +46,11 @@ kwargs: executable returns a non-zero return value (i.e. reports an error) suite: - type: str | list[str] + type: str | array[str] description: | - `'label'` (or list of labels `['label1', 'label2']`) + `'label'` (or array of labels `['label1', 'label2']`) attached to this test. The suite name is qualified by a (sub)project - name resulting in `(sub)project_name:label`. In the case of a list + name resulting in `(sub)project_name:label`. In the case of an array of strings, the suite names will be `(sub)project_name:label1`, `(sub)project_name:label2`, etc. @@ -70,7 +70,7 @@ kwargs: for the test depends: - type: list[build_tgt | custom_tgt] + type: array[build_tgt | custom_tgt] since: 0.46.0 description: | specifies that this test depends on the specified diff --git a/docs/yaml/functions/build_target.yaml b/docs/yaml/functions/build_target.yaml index a56fe75..f883e87 100644 --- a/docs/yaml/functions/build_target.yaml +++ b/docs/yaml/functions/build_target.yaml @@ -26,9 +26,9 @@ description: | build_target(<arguments and keyword arguments>, target_type : 'executable') ``` - The lists for the kwargs (such as `sources`, `objects`, and `dependencies`) are - always flattened, which means you can freely nest and add lists while - creating the final list. + The arrays for the kwargs (such as `sources`, `objects`, and `dependencies`) are + always flattened, which means you can freely nest and add arrays while + creating the final array. The returned object also has methods that are documented in [[@build_tgt]]. diff --git a/docs/yaml/functions/configure_file.yaml b/docs/yaml/functions/configure_file.yaml index 20b96aa..2deeff4 100644 --- a/docs/yaml/functions/configure_file.yaml +++ b/docs/yaml/functions/configure_file.yaml @@ -12,10 +12,12 @@ description: | A dictionary can be passed instead of a [[@cfg_data]] object. - When a list of strings is passed to the `command:` keyword argument, + When an array of strings is passed to the `command:` keyword argument, it takes any source or configured file as the `input:` and assumes that the `output:` is produced when the specified command is run. + You can install the outputted file with the `install_dir:` kwarg, see below. + *(since 0.47.0)* When the `copy:` keyword argument is set to `true`, this function will copy the file provided in `input:` to a file in the build directory with the name `output:` in the current directory. @@ -34,7 +36,7 @@ kwargs: file specified as `output`. command: - type: list[str | file] + type: array[str | file] description: | As explained above, if specified, Meson does not create the file itself but rather runs the specified command, which allows @@ -103,7 +105,7 @@ kwargs: string, the file is not installed. install_mode: - type: list[str | int] + type: array[str | int] since: 0.47.0 description: | Specify the file mode in symbolic format diff --git a/docs/yaml/functions/custom_target.yaml b/docs/yaml/functions/custom_target.yaml index 585d260..094787b 100644 --- a/docs/yaml/functions/custom_target.yaml +++ b/docs/yaml/functions/custom_target.yaml @@ -12,10 +12,12 @@ description: | custom_target('foo', output: 'file.txt', ...) ``` + You can install the outputted files with the `install_dir:` kwarg, see below. + *Since 0.60.0* the name argument is optional and defaults to the basename of the first output (`file.txt` in the example above). - The list of strings passed to the `command` keyword argument accept + The array of strings passed to the `command` keyword argument accept the following special string substitutions: - `@INPUT@`: the full path to the input passed to `input`. If more than @@ -103,7 +105,7 @@ kwargs: There are some compilers that can't be told to write their output to a file but instead write it to standard output. When this argument is set to true, Meson captures `stdout` and writes it - to the target file. Note that your command argument list may not + to the target file. Note that your command argument array may not contain `@OUTPUT@` when capture mode is active. console: @@ -118,7 +120,7 @@ kwargs: serializing all targets in this pool. command: - type: list[str | file | exe | external_program] + type: array[str | file | exe | external_program] description: | Command to run to create outputs from inputs. The command may be strings or the return value of functions that return file-like @@ -132,7 +134,7 @@ kwargs: -arg2'` as the latter will *not* work. depend_files: - type: list[str | file] + type: array[str | file] description: | files ([[@str]], [[@file]], or the return value of [[configure_file]] that @@ -140,7 +142,7 @@ kwargs: argument. Useful for adding regen dependencies. depends: - type: list[build_tgt | custom_tgt | custom_idx] + type: array[build_tgt | custom_tgt | custom_idx] description: | Specifies that this target depends on the specified target(s), even though it does not take any of them as a command @@ -162,8 +164,8 @@ kwargs: are also accepted. input: - type: list[str | file] - description: List of source files. *(since 0.41.0)* the list is flattened. + type: array[str | file] + description: Array of source files. *(since 0.41.0)* the array is flattened. install: type: bool @@ -171,7 +173,7 @@ kwargs: the install step (see `install_dir` for details). install_dir: - type: str | list[str | bool] + type: str | array[str | bool] description: | If only one install_dir is provided, all outputs are installed there. *Since 0.40.0* Allows you to specify the installation directory for each @@ -195,17 +197,17 @@ kwargs: This would install `second.file` to `otherdir` and not install `first.file`. install_mode: - type: list[str | int] + type: array[str | int] since: 0.47.0 description: | The file mode and optionally the owner/uid and group/gid. See the `install_mode` kwarg of [[install_data]] for more information. install_tag: - type: list[str] + type: array[str] since: 0.60.0 description: | - A list of strings, one per output, used by the `meson install --tags` command + An array of strings, one per output, used by the `meson install --tags` command to install only a subset of the files. By default all outputs have no install tag which means they are not being @@ -214,12 +216,12 @@ kwargs: outputs that have no tag or are not installed. output: - type: list[str] - description: List of output files. + type: array[str] + description: Array of output files. env: since: 0.57.0 - type: env | list[str] | dict[str] + type: env | array[str] | dict[str] description: | environment variables to set, such as `{'NAME1': 'value1', 'NAME2': 'value2'}` or `['NAME1=value1', 'NAME2=value2']`, @@ -234,4 +236,4 @@ kwargs: There are some compilers that can't be told to read their input from a file and instead read it from standard input. When this argument is set to `true`, Meson feeds the input file to `stdin`. Note that - your argument list may not contain `@INPUT@` when feed mode is active. + your argument array may not contain `@INPUT@` when feed mode is active. diff --git a/docs/yaml/functions/debug.yaml b/docs/yaml/functions/debug.yaml index a64bd92..4a4508c 100644 --- a/docs/yaml/functions/debug.yaml +++ b/docs/yaml/functions/debug.yaml @@ -5,10 +5,10 @@ description: Write the argument string to the meson build log. posargs: message: - type: str | int | bool | list[str | int | bool] | dict[str | int | bool] + type: str | int | bool | array[str | int | bool] | dict[str | int | bool] description: The message to print varargs: name: msg - type: str | int | bool | list[str | int | bool] | dict[str | int | bool] + type: str | int | bool | array[str | int | bool] | dict[str | int | bool] description: Additional parameters will be separated by spaces diff --git a/docs/yaml/functions/declare_dependency.yaml b/docs/yaml/functions/declare_dependency.yaml index 848082d..15d1606 100644 --- a/docs/yaml/functions/declare_dependency.yaml +++ b/docs/yaml/functions/declare_dependency.yaml @@ -12,41 +12,41 @@ description: | kwargs: compile_args: - type: list[str] + type: array[str] description: Compile arguments to use. dependencies: - type: list[dep] + type: array[dep] description: Other dependencies needed to use this dependency. include_directories: - type: list[inc | str] + type: array[inc | str] description: | the directories to add to header search path, must be [[@inc]] objects or *(since 0.50.0)* plain strings. link_args: - type: list[str] + type: array[str] description: Link arguments to use. link_with: - type: list[lib] + type: array[lib] description: Libraries to link against. link_whole: - type: list[lib] + type: array[lib] since: 0.46.0 description: Libraries to link fully, same as [[executable]]. sources: - type: list[str | file | custom_tgt | custom_idx | generated_list] + type: array[str | file | custom_tgt | custom_idx | generated_list] description: | sources to add to targets (or generated header files that should be built before sources including them are built) extra_files: - type: list[str | file] + type: array[str | file] since: 1.2.0 description: | extra files to add to targets. @@ -59,31 +59,31 @@ kwargs: such as `1.2.3`. Defaults to the project version. variables: - type: dict[str] | list[str] + type: dict[str] | array[str] since: 0.54.0 description: | a dictionary of arbitrary strings, this is meant to be used in subprojects where special variables would be provided via cmake or - pkg-config. *since 0.56.0* it can also be a list of `'key=value'` strings. + pkg-config. *since 0.56.0* it can also be an array of `'key=value'` strings. d_module_versions: - type: str | int | list[str | int] + type: str | int | array[str | int] since: 0.62.0 description: | The [D version identifiers](https://dlang.org/spec/version.html#version) to add during the compilation of D source files. d_import_dirs: - type: list[inc | str] + type: array[inc | str] since: 0.62.0 description: | the directories to add to the string search path (i.e. `-J` switch for DMD). Must be [[@inc]] objects or plain strings. objects: - type: list[extracted_obj] + type: array[extracted_obj] since: 1.1.0 description: | - a list of object files, to be linked directly into the targets that use the + an array of object files, to be linked directly into the targets that use the dependency. diff --git a/docs/yaml/functions/dependency.yaml b/docs/yaml/functions/dependency.yaml index a19deab..9d4dfc6 100644 --- a/docs/yaml/functions/dependency.yaml +++ b/docs/yaml/functions/dependency.yaml @@ -78,7 +78,7 @@ varargs: kwargs: default_options: - type: list[str] | dict[str | bool | int | list[str]] + type: array[str] | dict[str | bool | int | array[str]] since: 0.38.0 description: | An array of default option values @@ -103,7 +103,7 @@ kwargs: for more details. fallback: - type: list[str] | str + type: array[str] | str description: | Manually specifies a subproject fallback to use in case the dependency is not found in the system. @@ -120,7 +120,7 @@ kwargs: in this case the subproject must use `meson.override_dependency('dependency_name', subproj_dep)` to specify the dependency object used in the superproject. - If the value is an empty list, it has the same effect as + If the value is an empty array it has the same effect as `allow_fallback: false`. language: @@ -181,14 +181,14 @@ kwargs: value is `true` otherwise the default value is `false`. version: - type: list[str] | str + type: array[str] | str since: 0.37.0 description: | Specifies the required version, a string containing a comparison operator followed by the version string, examples include `>1.0.0`, `<=2.3.5` or `3.1.4` for exact matching. - You can also specify multiple restrictions by passing a list to this + You can also specify multiple restrictions by passing an array to this keyword argument, such as: `['>=3.14.0', '<=4.1.0']`. These requirements are never met if the version is unknown. diff --git a/docs/yaml/functions/environment.yaml b/docs/yaml/functions/environment.yaml index 4c18ffc..1f6e198 100644 --- a/docs/yaml/functions/environment.yaml +++ b/docs/yaml/functions/environment.yaml @@ -7,12 +7,12 @@ arg_flattening: false optargs: env: - type: str | list[str] | dict[str] | dict[list[str]] + type: str | array[str] | dict[str] | dict[array[str]] since: 0.52.0 description: | If provided, each key/value pair is added into the [[@env]] object as if [[env.set]] method was called for each of them. - Since *0.62.0* list of strings is allowed in dictionary values. In that + Since *0.62.0* arrays of strings are allowed in dictionary values. In that case values are joined using the separator. kwargs: diff --git a/docs/yaml/functions/executable.yaml b/docs/yaml/functions/executable.yaml index df71b79..1e463ed 100644 --- a/docs/yaml/functions/executable.yaml +++ b/docs/yaml/functions/executable.yaml @@ -4,9 +4,9 @@ description: | Creates a new executable. The first argument specifies its name and the remaining positional arguments define the input files to use. - The lists for the kwargs (such as `sources`, `objects`, and `dependencies`) are - always flattened, which means you can freely nest and add lists while - creating the final list. + The arrays for the kwargs (such as `sources`, `objects`, and `dependencies`) are + always flattened, which means you can freely nest and add arrays while + creating the final array. The returned object also has methods that are documented in [[@exe]]. diff --git a/docs/yaml/functions/files.yaml b/docs/yaml/functions/files.yaml index ca72745..4d701e1 100644 --- a/docs/yaml/functions/files.yaml +++ b/docs/yaml/functions/files.yaml @@ -1,5 +1,5 @@ name: files -returns: list[file] +returns: array[file] description: | This command takes the strings given to it in arguments and returns corresponding File objects that you can use as sources for build diff --git a/docs/yaml/functions/find_program.yaml b/docs/yaml/functions/find_program.yaml index 1899941..110f5df 100644 --- a/docs/yaml/functions/find_program.yaml +++ b/docs/yaml/functions/find_program.yaml @@ -24,7 +24,7 @@ description: | (because the command invocator will reject the command otherwise) and Unixes (if the script file does not have the executable bit set). Hence, you *must not* manually add the interpreter while using this - script as part of a list of commands. Since *0.50.0* if the "python3" + script as part of an array of commands. Since *0.50.0* if the "python3" program is requested and it is not found in the system, Meson will return its current interpreter. @@ -98,7 +98,7 @@ kwargs: instead of a not-found object. version: - type: str | list[str] + type: str | array[str] since: 0.52.0 description: | Specifies the required version, see @@ -117,12 +117,12 @@ kwargs: If this is unspecified, `program_name --version` will be used. dirs: - type: list[str] + type: array[str] since: 0.53.0 - description: extra list of absolute paths where to look for program names. + description: extra array of absolute paths where to look for program names. default_options: - type: list[str] | dict[str | bool | int | list[str]] + type: array[str] | dict[str | bool | int | array[str]] since: 1.3.0 description: | An array of default option values diff --git a/docs/yaml/functions/generator.yaml b/docs/yaml/functions/generator.yaml index 6079d30..c505394 100644 --- a/docs/yaml/functions/generator.yaml +++ b/docs/yaml/functions/generator.yaml @@ -45,12 +45,12 @@ posargs: kwargs: arguments: - type: list[str] - description: A list of template strings that will be the command line arguments passed to the executable. + type: array[str] + description: An array of template strings that will be the command line arguments passed to the executable. depends: # Not sure why this is not just `target` - type: list[build_tgt | custom_tgt | custom_idx] + type: array[build_tgt | custom_tgt | custom_idx] since: 0.51.0 description: | An array of build targets that must be built before @@ -68,9 +68,9 @@ kwargs: recompilation, output: - type: list[str] + type: array[str] description: | - Template string (or list of template strings) defining + Template string (or array of template strings) defining how an output file name is (or multiple output names are) generated from a single source file name. diff --git a/docs/yaml/functions/get_option.yaml b/docs/yaml/functions/get_option.yaml index 0934758..e23e380 100644 --- a/docs/yaml/functions/get_option.yaml +++ b/docs/yaml/functions/get_option.yaml @@ -1,5 +1,5 @@ name: get_option -returns: str | int | bool | feature | list[str | int | bool] +returns: str | int | bool | feature | array[str | int | bool] description: | Obtains the value of the [project build option](Build-options.md) specified in the positional argument. @@ -20,6 +20,11 @@ description: | See [`feature` options](Build-options.md#features) documentation for more details. + For options that are [specified + per-machine](Builtin-options.md#specifying-options-per-machine) + `get_option()` retrieves the value of the option for the + build machine if the argument starts with `build.`. + posargs: option_name: type: str diff --git a/docs/yaml/functions/install_data.yaml b/docs/yaml/functions/install_data.yaml index 9ed09a7..b9aedca 100644 --- a/docs/yaml/functions/install_data.yaml +++ b/docs/yaml/functions/install_data.yaml @@ -2,6 +2,9 @@ name: install_data returns: void description: | Installs files from the source tree that are listed as positional arguments. + Please note that this can only install static files from the source tree. + Generated files are installed via the `install_dir:` kwarg on the respective + generators, such as `custom_target()` or `configure_file(). See [Installing](Installing.md) for more examples. @@ -25,7 +28,7 @@ kwargs: If omitted, the directory defaults to `{datadir}/{projectname}` *(since 0.45.0)*. install_mode: - type: list[str | int] + type: array[str | int] since: 0.38.0 description: | specify the file mode in symbolic format and @@ -58,16 +61,16 @@ kwargs: This is equivalent to GNU Automake's `nobase` option. rename: - type: list[str] + type: array[str] since: 0.46.0 description: | - If specified renames each source file into corresponding file from `rename` list. + If specified renames each source file into corresponding file from `rename` array. Nested paths are allowed and they are - joined with `install_dir`. Length of `rename` list must be equal to + joined with `install_dir`. Length of `rename` array must be equal to the number of sources. sources: - type: list[file | str] + type: array[file | str] description: Additional files to install. follow_symlinks: diff --git a/docs/yaml/functions/install_emptydir.yaml b/docs/yaml/functions/install_emptydir.yaml index df84f60..4f77f59 100644 --- a/docs/yaml/functions/install_emptydir.yaml +++ b/docs/yaml/functions/install_emptydir.yaml @@ -16,7 +16,7 @@ varargs: kwargs: install_mode: - type: list[str | int] + type: array[str | int] description: | Specify the file mode in symbolic format and optionally the owner/uid and group/gid for the created directory. diff --git a/docs/yaml/functions/install_headers.yaml b/docs/yaml/functions/install_headers.yaml index 0ac4fc5..42f6462 100644 --- a/docs/yaml/functions/install_headers.yaml +++ b/docs/yaml/functions/install_headers.yaml @@ -9,6 +9,10 @@ description: | argument. As an example if this has the value `myproj` then the headers would be installed to `/{prefix}/include/myproj`. + Please note that this can only install static files from the source tree. + Generated files are installed via the `install_dir:` kwarg on the respective + generators, such as `custom_target()` or `configure_file(). + example: | For example, this will install `common.h` and `kola.h` into `/{prefix}/include`: @@ -57,7 +61,7 @@ kwargs: Incompatible with the `install_dir` kwarg. install_mode: - type: list[str | int] + type: array[str | int] since: 0.47.0 description: | Specify the file mode in symbolic format diff --git a/docs/yaml/functions/install_man.yaml b/docs/yaml/functions/install_man.yaml index 8d9ba60..1deef59 100644 --- a/docs/yaml/functions/install_man.yaml +++ b/docs/yaml/functions/install_man.yaml @@ -20,7 +20,7 @@ warnings: kwargs: install_mode: - type: list[str | int] + type: array[str | int] since: 0.47.0 description: | Specify the file mode in symbolic format diff --git a/docs/yaml/functions/install_subdir.yaml b/docs/yaml/functions/install_subdir.yaml index 19abee3..3420f5e 100644 --- a/docs/yaml/functions/install_subdir.yaml +++ b/docs/yaml/functions/install_subdir.yaml @@ -66,7 +66,7 @@ posargs: kwargs: install_mode: - type: list[str | int] + type: array[str | int] since: 0.47.0 description: | Specify the file mode in symbolic format @@ -83,16 +83,16 @@ kwargs: tag which means they are not being installed when `--tags` argument is specified. exclude_files: - type: list[str] + type: array[str] description: | - A list of file names that should not be installed. + An array of file names that should not be installed. Names are interpreted as paths relative to the `subdir_name` location. exclude_directories: - type: list[str] + type: array[str] since: 0.47.0 description: | - A list of directory names that should not be installed. + An array of directory names that should not be installed. Names are interpreted as paths relative to the `subdir_name` location. install_dir: diff --git a/docs/yaml/functions/library.yaml b/docs/yaml/functions/library.yaml index 1d406f1..4a4611c 100644 --- a/docs/yaml/functions/library.yaml +++ b/docs/yaml/functions/library.yaml @@ -40,13 +40,13 @@ kwargs: type being build. <lang>_static_args: - type: list[str] + type: array[str] since: 1.3.0 description: Arguments that are only passed to a static library vala_static_args: - type: list[str | file] + type: array[str | file] since: 1.3.0 description: Arguments that are only passed to a static library @@ -54,13 +54,13 @@ kwargs: Like `vala_args`, [[files]] is allowed in addition to string <lang>_shared_args: - type: list[str] + type: array[str] since: 1.3.0 description: Arguments that are only passed to a shared library vala_shared_args: - type: list[str | file] + type: array[str | file] since: 1.3.0 description: Arguments that are only passed to a shared library diff --git a/docs/yaml/functions/message.yaml b/docs/yaml/functions/message.yaml index e480457..339dfc4 100644 --- a/docs/yaml/functions/message.yaml +++ b/docs/yaml/functions/message.yaml @@ -6,11 +6,11 @@ arg_flattening: false posargs: text: - type: str | int | bool | list[str | int | bool] | dict[str | int | bool] + type: str | int | bool | array[str | int | bool] | dict[str | int | bool] description: The message to print. varargs: name: more_text since: 0.54.0 - type: str | int | bool | list[str | int | bool] | dict[str | int | bool] + type: str | int | bool | array[str | int | bool] | dict[str | int | bool] description: Additional text that will be printed separated by spaces. diff --git a/docs/yaml/functions/project.yaml b/docs/yaml/functions/project.yaml index 5be8cac..0db8c03 100644 --- a/docs/yaml/functions/project.yaml +++ b/docs/yaml/functions/project.yaml @@ -13,9 +13,9 @@ description: | would probably want to use the name _libfoobar_ instead of _The Foobar Library_. - It may be followed by the list of programming languages that the project uses. + It may be followed by the array of programming languages that the project uses. - *(since 0.40.0)* The list of languages is optional. + *(since 0.40.0)* The array of languages is optional. These languages may be used both for `native: false` (the default) (host machine) targets and for `native: true` (build machine) targets. @@ -24,7 +24,7 @@ description: | Supported values for languages are `c`, `cpp` (for `C++`), `cuda`, `cython`, `d`, `objc`, `objcpp`, `fortran`, `java`, `cs` (for `C#`), - `vala` and `rust`. + `swift`, `nasm`, `masm`, `linearasm`, `vala` and `rust`. posargs: project_name: @@ -38,22 +38,26 @@ varargs: kwargs: default_options: - type: list[str] | dict[str | bool | int | list[str]] + type: array[str] | dict[str | bool | int | array[str]] description: | Accepts strings in the form `key=value` which have the same format as options to `meson configure`. For example to set the default project type you would set this: `default_options : ['buildtype=debugoptimized']`. Note that these settings are only used when running Meson for the first - time. Global options such as `buildtype` can only be specified in - the master project, settings in subprojects are ignored. Project - specific options are used normally even in subprojects. + time. Note that some options can override the default behavior; for example, using `c_args` here means that the `CFLAGS` environment variable is not used. Consider using [[add_project_arguments()]] instead. + Also note that not all options are taken into account when + building as a subproject, and the exact set of options + that are per-subproject has increased over time; for more + information, see [core options](Builtin-options.md#core-options) + and [compiler options](Builtin-options.md#compiler-options). + *(since 1.2.0)*: A dictionary may now be passed. version: @@ -72,7 +76,7 @@ kwargs: Usually something like `>=0.28.0`. license: - type: str | list[str] + type: str | array[str] description: | Takes a string or array of strings describing the license(s) the code is under. @@ -94,7 +98,7 @@ kwargs: value in your Meson build files with `meson.project_license()`. license_files: - type: str | list[str] + type: str | array[str] since: 1.1.0 description: | Takes a string or array of strings with the paths to the license file(s) diff --git a/docs/yaml/functions/run_command.yaml b/docs/yaml/functions/run_command.yaml index 5803f82..1c9cc53 100644 --- a/docs/yaml/functions/run_command.yaml +++ b/docs/yaml/functions/run_command.yaml @@ -40,7 +40,7 @@ kwargs: empty string. env: - type: env | list[str] | dict[str] + type: env | array[str] | dict[str] since: 0.50.0 description: | environment variables to set, diff --git a/docs/yaml/functions/run_target.yaml b/docs/yaml/functions/run_target.yaml index 3ede1c9..bf670d8 100644 --- a/docs/yaml/functions/run_target.yaml +++ b/docs/yaml/functions/run_target.yaml @@ -31,25 +31,25 @@ posargs: kwargs: command: - type: list[exe| external_program | custom_tgt | file | str] + type: array[exe| external_program | custom_tgt | file | str] description: | - A list containing the command to run and the arguments - to pass to it. Each list item may be a string or a target. For + An array containing the command to run and the arguments + to pass to it. Each array element may be a string or a target. For instance, passing the return value of [[executable]] as the first item will run that executable, or passing a string as the first item will find that command in `PATH` and run it. depends: - type: list[build_tgt | custom_tgt | custom_idx] + type: array[build_tgt | custom_tgt | custom_idx] description: | - A list of targets that this target depends on but which + An array of targets that this target depends on but which are not listed in the command array (because, for example, the script does file globbing internally, custom_idx was not possible as a type between 0.60 and 1.4.0). env: since: 0.57.0 - type: env | list[str] | dict[str] + type: env | array[str] | dict[str] description: | environment variables to set, such as `{'NAME1': 'value1', 'NAME2': 'value2'}` or `['NAME1=value1', 'NAME2=value2']`, diff --git a/docs/yaml/functions/shared_library.yaml b/docs/yaml/functions/shared_library.yaml index f633aca..eeae3ce 100644 --- a/docs/yaml/functions/shared_library.yaml +++ b/docs/yaml/functions/shared_library.yaml @@ -29,13 +29,13 @@ kwargs: `soversion` is not defined, it is set to `3`. darwin_versions: - type: str | int | list[str] + type: str | int | array[str] since: 0.48.0 description: | Defines the `compatibility version` and `current version` for the dylib on macOS. - If a list is specified, it must be + If an array is specified, it must be either zero, one, or two elements. If only one element is specified - or if it's not a list, the specified value will be used for setting + or if it's not an array the specified value will be used for setting both compatibility version and current version. If unspecified, the `soversion` will be used as per the aforementioned rules. diff --git a/docs/yaml/functions/structured_sources.yaml b/docs/yaml/functions/structured_sources.yaml index a5f0a83..0dbf585 100644 --- a/docs/yaml/functions/structured_sources.yaml +++ b/docs/yaml/functions/structured_sources.yaml @@ -10,7 +10,7 @@ description: | posargs: root: - type: list[str | file | custom_tgt | custom_idx | generated_list] + type: array[str | file | custom_tgt | custom_idx | generated_list] description: Sources to put at the root of the generated structure optargs: diff --git a/docs/yaml/functions/subdir.yaml b/docs/yaml/functions/subdir.yaml index 428563b..96e17ba 100644 --- a/docs/yaml/functions/subdir.yaml +++ b/docs/yaml/functions/subdir.yaml @@ -21,6 +21,6 @@ posargs: kwargs: if_found: - type: list[dep] + type: array[dep] since: 0.44.0 description: Only enter the subdir if all [[dep.found]] methods return `true`. diff --git a/docs/yaml/functions/subproject.yaml b/docs/yaml/functions/subproject.yaml index bccac79..508837c 100644 --- a/docs/yaml/functions/subproject.yaml +++ b/docs/yaml/functions/subproject.yaml @@ -42,7 +42,7 @@ posargs: kwargs: default_options: - type: list[str] | dict[str | bool | int | list[str]] + type: array[str] | dict[str | bool | int | array[str]] since: 0.37.0 description: | An array of default option values diff --git a/docs/yaml/functions/summary.yaml b/docs/yaml/functions/summary.yaml index cf63dcd..b531282 100644 --- a/docs/yaml/functions/summary.yaml +++ b/docs/yaml/functions/summary.yaml @@ -16,7 +16,7 @@ description: | - an integer, boolean or string - *since 0.57.0* an external program or a dependency - *since 0.58.0* a feature option - - a list of those. + - an array of those. Instead of calling summary as `summary(key, value)`, it is also possible to directly pass a dictionary to the [[summary]] function, as seen in the example @@ -38,7 +38,7 @@ example: | summary({'Some boolean': false, 'Another boolean': true, 'Some string': 'Hello World', - 'A list': ['string', 1, true], + 'An array': ['string', 1, true], }, section: 'Configuration') ``` @@ -56,7 +56,7 @@ example: | Some boolean : False Another boolean: True Some string : Hello World - A list : string + An array : string 1 True ``` @@ -65,7 +65,7 @@ arg_flattening: false posargs: key_or_dict: - type: str | dict[str | bool | int | dep | external_program | list[str | bool | int | dep | external_program]] + type: str | dict[str | bool | int | dep | external_program | array[str | bool | int | dep | external_program]] description: | The name of the new entry, or a dict containing multiple entries. If a dict is passed it is equivalent to calling summary() once for each @@ -74,7 +74,7 @@ posargs: optargs: value: - type: str | bool | int | dep | external_program | list[str | bool | int | dep | external_program] + type: str | bool | int | dep | external_program | array[str | bool | int | dep | external_program] description: | The value to print for the `key`. Only valid if `key_or_dict` is a str. @@ -92,5 +92,5 @@ kwargs: type: str since: 0.54.0 description: | - The separator to use when printing list values in this summary. If no - separator is given, each list item will be printed on its own line. + The separator to use when printing array values in this summary. If no + separator is given, each array item will be printed on its own line. diff --git a/docs/yaml/functions/vcs_tag.yaml b/docs/yaml/functions/vcs_tag.yaml index 3a35684..bf223e7 100644 --- a/docs/yaml/functions/vcs_tag.yaml +++ b/docs/yaml/functions/vcs_tag.yaml @@ -22,7 +22,7 @@ description: | kwargs: command: - type: list[exe | external_program | custom_tgt | file | str] + type: array[exe | external_program | custom_tgt | file | str] description: | The command to execute, see [[custom_target]] for details on how this command must be specified. @@ -71,7 +71,7 @@ kwargs: The subdirectory to install the generated file to (e.g. `share/myproject`). install_mode: - type: list[str | int] + type: array[str | int] since: 1.7.0 description: | Specify the file mode in symbolic format diff --git a/docs/yaml/objects/cfg_data.yaml b/docs/yaml/objects/cfg_data.yaml index 03abb17..36f9755 100644 --- a/docs/yaml/objects/cfg_data.yaml +++ b/docs/yaml/objects/cfg_data.yaml @@ -114,7 +114,7 @@ methods: description: The name of the variable to query - name: keys - returns: list[str] + returns: array[str] since: 0.57.0 description: | Returns an array of keys of diff --git a/docs/yaml/objects/compiler.yaml b/docs/yaml/objects/compiler.yaml index 43831d2..763060f 100644 --- a/docs/yaml/objects/compiler.yaml +++ b/docs/yaml/objects/compiler.yaml @@ -45,9 +45,9 @@ methods: description: You have found a bug if you can see this! kwargs: args: - type: list[str] + type: array[str] description: | - Used to pass a list of compiler arguments. + Used to pass an array of compiler arguments. Defining include paths for headers not in the default include path via `-Isome/path/to/header` is generally supported, however, usually not recommended. @@ -61,7 +61,7 @@ methods: description: You have found a bug if you can see this! kwargs: include_directories: - type: inc | list[inc] + type: inc | array[inc] since: 0.38.0 description: Extra directories for header searches. @@ -70,7 +70,7 @@ methods: description: You have found a bug if you can see this! kwargs: dependencies: - type: dep | list[dep] + type: dep | array[dep] description: Additionally dependencies required for compiling and / or linking. - name: _prefix @@ -78,7 +78,7 @@ methods: description: You have found a bug if you can see this! kwargs: prefix: - type: str | list[str] + type: str | array[str] description: | Used to add `#include`s and other things that are required for the symbol to be declared. Since 1.0.0 an array is accepted @@ -184,7 +184,7 @@ methods: description: Returns the compiler's version number as a string. - name: cmd_array - returns: list[str] + returns: array[str] description: Returns an array containing the command(s) for the compiler. @@ -441,10 +441,10 @@ methods: *(since 0.47.0)* The value of a `feature` option can also be passed here. has_headers: - type: list[str] + type: array[str] since: 0.50.0 description: | - List of headers that must be found as well. + An array of headers that must be found as well. This check is equivalent to checking each header with a [[compiler.has_header]] call. @@ -468,7 +468,7 @@ methods: description: If `true`, this method will return a [[@disabler]] on a failed check. dirs: - type: list[str] + type: array[str] description: | Additional directories to search in. @@ -478,19 +478,19 @@ methods: # does not work, since all _common kwargs need to be prefixed `header_` here # kwargs_inherit: compiler._common header_args: - type: list[str] + type: array[str] since: 0.51.0 description: | When the `has_headers` kwarg is also used, this argument is passed to [[compiler.has_header]] as `args`. header_include_directories: - type: inc | list[inc] + type: inc | array[inc] since: 0.51.0 description: | When the `has_headers` kwarg is also used, this argument is passed to [[compiler.has_header]] as `include_directories`. header_dependencies: - type: dep | list[dep] + type: dep | array[dep] since: 0.51.0 description: | When the `has_headers` kwarg is also used, this argument is passed to @@ -539,7 +539,7 @@ methods: - compiler._required - name: get_supported_arguments - returns: list[str] + returns: array[str] since: 0.43.0 varargs_inherit: compiler.has_multi_arguments description: | @@ -558,11 +558,11 @@ methods: - `'require'`: Abort if at least one argument is not supported - name: first_supported_argument - returns: list[str] + returns: array[str] since: 0.43.0 varargs_inherit: compiler.has_multi_arguments description: | - Given a list of strings, returns a single-element list containing the first + Given an array of strings, returns a single-element array containing the first argument that passes the [[compiler.has_argument]] test or an empty array if none pass. @@ -601,7 +601,7 @@ methods: - compiler._required - name: get_supported_link_arguments - returns: list[str] + returns: array[str] since: 0.46.0 varargs_inherit: compiler.has_multi_link_arguments description: | @@ -621,11 +621,11 @@ methods: # - `'require'`: Abort if at least one argument is not supported - name: first_supported_link_argument - returns: list[str] + returns: array[str] since: 0.46.0 varargs_inherit: compiler.has_multi_link_arguments description: | - Given a list of strings, returns the first argument that passes the + Given an array of strings, returns the first argument that passes the [[compiler.has_link_argument]] test or an empty array if none pass. - name: has_function_attribute @@ -645,7 +645,7 @@ methods: - compiler._required - name: get_supported_function_attributes - returns: list[str] + returns: array[str] since: 0.48.0 description: | Returns an array containing any names that are supported GCC style attributes. @@ -666,10 +666,10 @@ methods: operating systems. - name: preprocess - returns: list[custom_idx] + returns: array[custom_idx] since: 0.64.0 description: | - Preprocess a list of source files but do not compile them. The preprocessor + Preprocess an array of source files but do not compile them. The preprocessor will receive the same arguments (include directories, defines, etc) as with normal compilation. That includes for example args added with `add_project_arguments()`, or on the command line with `-Dc_args=-DFOO`. @@ -694,15 +694,15 @@ methods: the source filename and `@BASENAME@` is replaced by the source filename without its extension. compile_args: - type: list[str] + type: array[str] description: | Extra flags to pass to the preprocessor dependencies: - type: dep | list[dep] + type: dep | array[dep] description: Additionally dependencies required. since: 1.1.0 depends: - type: list[build_tgt | custom_tgt] + type: array[build_tgt | custom_tgt] description: | Specifies that this target depends on the specified target(s). These targets should be built before starting diff --git a/docs/yaml/objects/custom_tgt.yaml b/docs/yaml/objects/custom_tgt.yaml index 5102ab9..60e067c 100644 --- a/docs/yaml/objects/custom_tgt.yaml +++ b/docs/yaml/objects/custom_tgt.yaml @@ -25,9 +25,9 @@ methods: custom target's output argument. - name: to_list - returns: list[custom_idx] + returns: array[custom_idx] since: 0.54.0 description: | - Returns a list of opaque objects that references this target, + Returns an array 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. diff --git a/docs/yaml/objects/dep.yaml b/docs/yaml/objects/dep.yaml index ffd19f7..fdf5fe5 100644 --- a/docs/yaml/objects/dep.yaml +++ b/docs/yaml/objects/dep.yaml @@ -34,11 +34,11 @@ methods: kwargs: define_variable: - type: list[str] + type: array[str] since: 0.44.0 description: | You can also redefine a - variable by passing a list to this kwarg + variable by passing an array to this kwarg that can affect the retrieved variable: `['prefix', '/'])`. *(Since 1.3.0)* Multiple variables can be specified in pairs. @@ -224,7 +224,7 @@ methods: description: The default value to return when the variable does not exist pkgconfig_define: - type: list[str] + type: array[str] description: See [[dep.get_pkgconfig_variable]] - name: as_static @@ -250,4 +250,3 @@ methods: recursive: type: bool description: If true, this is recursively applied to dependencies -
\ No newline at end of file diff --git a/docs/yaml/objects/generator.yaml b/docs/yaml/objects/generator.yaml index fbef95f..3dbcdd5 100644 --- a/docs/yaml/objects/generator.yaml +++ b/docs/yaml/objects/generator.yaml @@ -9,20 +9,20 @@ methods: - name: process returns: generated_list description: | - Takes a list of files, causes them to be processed and returns an object containing the result + Takes an array of files, causes them to be processed and returns an object containing the result which can then, for example, be passed into a build target definition. varargs: name: source min_varargs: 1 type: str | file | custom_tgt | custom_idx | generated_list - description: List of sources to process. + description: sources to process. kwargs: extra_args: - type: list[str] + type: array[str] description: | - If present, will be used to replace an entry `@EXTRA_ARGS@` in the argument list. + If present, will be used to replace an entry `@EXTRA_ARGS@` in the argument array. preserve_path_from: type: str @@ -36,7 +36,7 @@ methods: directory}/one.out`. env: - type: env | list[str] | dict[str] + type: env | array[str] | dict[str] since: 1.3.0 description: | environment variables to set, such as diff --git a/man/meson.1 b/man/meson.1 index b78cf96..ef3fb7b 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -1,4 +1,4 @@ -.TH MESON "1" "April 2025" "meson 1.8.0" "User Commands" +.TH MESON "1" "August 2025" "meson 1.9.0" "User Commands" .SH NAME meson - a high productivity build system .SH DESCRIPTION diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index 147436d..decce4b 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -284,7 +284,7 @@ class IntrospectionInterpreter(AstInterpreter): return new_target def build_library(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]: - default_library = self.coredata.optstore.get_value_for(OptionKey('default_library')) + default_library = self.coredata.optstore.get_value_for(OptionKey('default_library', subproject=self.subproject)) if default_library == 'shared': return self.build_target(node, args, kwargs, SharedLibrary) elif default_library == 'static': diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index ed57a4c..e3d6c60 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -24,12 +24,12 @@ from .. import dependencies from .. import programs from .. import mesonlib from .. import mlog -from ..compilers import LANGUAGES_USING_LDFLAGS, detect, lang_suffixes +from ..compilers import detect, lang_suffixes from ..mesonlib import ( File, MachineChoice, MesonException, MesonBugException, OrderedSet, ExecutableSerialisation, EnvironmentException, classify_unity_sources, get_compiler_for_source, - is_parent_path, get_rsp_threshold, + get_rsp_threshold, ) from ..options import OptionKey @@ -61,7 +61,7 @@ if T.TYPE_CHECKING: # Languages that can mix with C or C++ but don't support unity builds yet # because the syntax we use for unity builds is specific to C/++/ObjC/++. # Assembly files cannot be unitified and neither can LLVM IR files -LANGS_CANT_UNITY = ('d', 'fortran', 'vala') +LANGS_CANT_UNITY = ('d', 'fortran', 'vala', 'rust') @dataclass(eq=False) class RegenInfo: @@ -150,7 +150,7 @@ class TargetInstallData: def __post_init__(self, outdir_name: T.Optional[str]) -> None: if outdir_name is None: outdir_name = os.path.join('{prefix}', self.outdir) - self.out_name = os.path.join(outdir_name, os.path.basename(self.fname)) + self.out_name = Path(outdir_name, os.path.basename(self.fname)).as_posix() @dataclass(eq=False) class InstallEmptyDir: @@ -307,16 +307,16 @@ class Backend: else: assert isinstance(t, build.BuildTarget), t filename = t.get_filename() - return os.path.join(self.get_target_dir(t), filename) + return Path(self.get_target_dir(t), filename).as_posix() def get_target_filename_abs(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str: - return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)) + return Path(self.environment.get_build_dir(), self.get_target_filename(target)).as_posix() def get_target_debug_filename(self, target: build.BuildTarget) -> T.Optional[str]: assert isinstance(target, build.BuildTarget), target if target.get_debug_filename(): debug_filename = target.get_debug_filename() - return os.path.join(self.get_target_dir(target), debug_filename) + return Path(self.get_target_dir(target), debug_filename).as_posix() else: return None @@ -324,7 +324,7 @@ class Backend: assert isinstance(target, build.BuildTarget), target if not target.get_debug_filename(): return None - return os.path.join(self.environment.get_build_dir(), self.get_target_debug_filename(target)) + return Path(self.environment.get_build_dir(), self.get_target_debug_filename(target)).as_posix() def get_source_dir_include_args(self, target: build.BuildTarget, compiler: 'Compiler', *, absolute_path: bool = False) -> T.List[str]: curdir = target.get_subdir() @@ -733,118 +733,6 @@ class Backend: return l, stdlib_args @staticmethod - def _libdir_is_system(libdir: str, compilers: T.Mapping[str, 'Compiler'], env: 'Environment') -> bool: - libdir = os.path.normpath(libdir) - for cc in compilers.values(): - if libdir in cc.get_library_dirs(env): - return True - return False - - def get_external_rpath_dirs(self, target: build.BuildTarget) -> T.Set[str]: - args: T.List[str] = [] - for lang in LANGUAGES_USING_LDFLAGS: - try: - e = self.environment.coredata.get_external_link_args(target.for_machine, lang) - if isinstance(e, str): - args.append(e) - else: - args.extend(e) - except Exception: - pass - return self.get_rpath_dirs_from_link_args(args) - - @staticmethod - def get_rpath_dirs_from_link_args(args: T.List[str]) -> T.Set[str]: - dirs: T.Set[str] = set() - # Match rpath formats: - # -Wl,-rpath= - # -Wl,-rpath, - rpath_regex = re.compile(r'-Wl,-rpath[=,]([^,]+)') - # Match solaris style compat runpath formats: - # -Wl,-R - # -Wl,-R, - runpath_regex = re.compile(r'-Wl,-R[,]?([^,]+)') - # Match symbols formats: - # -Wl,--just-symbols= - # -Wl,--just-symbols, - symbols_regex = re.compile(r'-Wl,--just-symbols[=,]([^,]+)') - for arg in args: - rpath_match = rpath_regex.match(arg) - if rpath_match: - for dir in rpath_match.group(1).split(':'): - dirs.add(dir) - runpath_match = runpath_regex.match(arg) - if runpath_match: - for dir in runpath_match.group(1).split(':'): - # The symbols arg is an rpath if the path is a directory - if Path(dir).is_dir(): - dirs.add(dir) - symbols_match = symbols_regex.match(arg) - if symbols_match: - for dir in symbols_match.group(1).split(':'): - # Prevent usage of --just-symbols to specify rpath - if Path(dir).is_dir(): - raise MesonException(f'Invalid arg for --just-symbols, {dir} is a directory.') - return dirs - - @lru_cache(maxsize=None) - def rpaths_for_non_system_absolute_shared_libraries(self, target: build.BuildTarget, exclude_system: bool = True) -> 'ImmutableListProtocol[str]': - paths: OrderedSet[str] = OrderedSet() - srcdir = self.environment.get_source_dir() - - for dep in target.external_deps: - if dep.type_name not in {'library', 'pkgconfig', 'cmake'}: - continue - for libpath in dep.link_args: - # For all link args that are absolute paths to a library file, add RPATH args - if not os.path.isabs(libpath): - continue - libdir = os.path.dirname(libpath) - if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment): - # No point in adding system paths. - continue - # Don't remove rpaths specified in LDFLAGS. - if libdir in self.get_external_rpath_dirs(target): - continue - # Windows doesn't support rpaths, but we use this function to - # emulate rpaths by setting PATH - # .dll is there for mingw gcc - # .so's may be extended with version information, e.g. libxyz.so.1.2.3 - if not ( - os.path.splitext(libpath)[1] in {'.dll', '.lib', '.so', '.dylib'} - or re.match(r'.+\.so(\.|$)', os.path.basename(libpath)) - ): - continue - - if is_parent_path(srcdir, libdir): - rel_to_src = libdir[len(srcdir) + 1:] - assert not os.path.isabs(rel_to_src), f'rel_to_src: {rel_to_src} is absolute' - paths.add(os.path.join(self.build_to_src, rel_to_src)) - else: - paths.add(libdir) - # Don't remove rpaths specified by the dependency - paths.difference_update(self.get_rpath_dirs_from_link_args(dep.link_args)) - for i in chain(target.link_targets, target.link_whole_targets): - if isinstance(i, build.BuildTarget): - paths.update(self.rpaths_for_non_system_absolute_shared_libraries(i, exclude_system)) - return list(paths) - - # This may take other types - def determine_rpath_dirs(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex] - ) -> T.Tuple[str, ...]: - result: OrderedSet[str] - if self.environment.coredata.optstore.get_value_for(OptionKey('layout')) == 'mirror': - # Need a copy here - result = OrderedSet(target.get_link_dep_subdirs()) - else: - result = OrderedSet() - result.add('meson-out') - if isinstance(target, build.BuildTarget): - result.update(self.rpaths_for_non_system_absolute_shared_libraries(target)) - target.rpath_dirs_to_remove.update([d.encode('utf-8') for d in result]) - return tuple(result) - - @staticmethod @lru_cache(maxsize=None) def canonicalize_filename(fname: str) -> str: if os.path.altsep is not None: diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 73f2db7..595a27a 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -891,14 +891,14 @@ class NinjaBackend(backends.Backend): self.generate_shlib_aliases(target, self.get_target_dir(target)) + # Generate rules for GeneratedLists + self.generate_generator_list_rules(target) + # If target uses a language that cannot link to C objects, # just generate for that language and return. if isinstance(target, build.Jar): self.generate_jar_target(target) return - if target.uses_rust(): - self.generate_rust_target(target) - return if 'cs' in target.compilers: self.generate_cs_target(target) return @@ -935,8 +935,6 @@ class NinjaBackend(backends.Backend): generated_sources = self.get_target_generated_sources(target) transpiled_sources = [] self.scan_fortran_module_outputs(target) - # Generate rules for GeneratedLists - self.generate_generator_list_rules(target) # Generate rules for building the remaining source files in this target outname = self.get_target_filename(target) @@ -992,6 +990,8 @@ class NinjaBackend(backends.Backend): # this target. We create the Ninja build file elements for this here # because we need `header_deps` to be fully generated in the above loop. for src in generated_source_files: + if not self.environment.is_separate_compile(src): + continue if self.environment.is_llvm_ir(src): o, s = self.generate_llvm_ir_compile(target, src) else: @@ -1050,21 +1050,24 @@ class NinjaBackend(backends.Backend): # Generate compile targets for all the preexisting sources for this target for src in target_sources.values(): - if not self.environment.is_header(src) or is_compile_target: - if self.environment.is_llvm_ir(src): - o, s = self.generate_llvm_ir_compile(target, src) - obj_list.append(o) - elif is_unity and self.get_target_source_can_unity(target, src): - abs_src = os.path.join(self.environment.get_build_dir(), - src.rel_to_builddir(self.build_to_src)) - unity_src.append(abs_src) - else: - o, s = self.generate_single_compile(target, src, False, [], - header_deps + d_generated_deps + fortran_order_deps, - fortran_inc_args) - obj_list.append(o) - compiled_sources.append(s) - source2object[s] = o + if not self.environment.is_separate_compile(src): + continue + if self.environment.is_header(src) and not is_compile_target: + continue + if self.environment.is_llvm_ir(src): + o, s = self.generate_llvm_ir_compile(target, src) + obj_list.append(o) + elif is_unity and self.get_target_source_can_unity(target, src): + abs_src = os.path.join(self.environment.get_build_dir(), + src.rel_to_builddir(self.build_to_src)) + unity_src.append(abs_src) + else: + o, s = self.generate_single_compile(target, src, False, [], + header_deps + d_generated_deps + fortran_order_deps, + fortran_inc_args) + obj_list.append(o) + compiled_sources.append(s) + source2object[s] = o if is_unity: for src in self.generate_unity_files(target, unity_src): @@ -1084,8 +1087,14 @@ class NinjaBackend(backends.Backend): final_obj_list = self.generate_prelink(target, obj_list) else: final_obj_list = obj_list - elem = self.generate_link(target, outname, final_obj_list, linker, pch_objects, stdlib_args=stdlib_args) + self.generate_dependency_scan_target(target, compiled_sources, source2object, fortran_order_deps) + + if target.uses_rust(): + self.generate_rust_target(target, outname, final_obj_list, fortran_order_deps) + return + + elem = self.generate_link(target, outname, final_obj_list, linker, pch_objects, stdlib_args=stdlib_args) self.add_build(elem) #In AIX, we archive shared libraries. If the instance is a shared library, we add a command to archive the shared library #object and create the build element. @@ -1556,7 +1565,6 @@ class NinjaBackend(backends.Backend): elem.add_item('ARGS', commands) self.add_build(elem) - self.generate_generator_list_rules(target) self.create_target_source_introspection(target, compiler, commands, rel_srcs, generated_rel_srcs) def determine_java_compile_args(self, target, compiler) -> T.List[str]: @@ -1769,7 +1777,7 @@ class NinjaBackend(backends.Backend): valac_outputs.append(girname) shared_target = target.get('shared') if isinstance(shared_target, build.SharedLibrary): - args += ['--shared-library', self.get_target_filename_for_linking(shared_target)] + args += ['--shared-library', shared_target.get_filename()] # Install GIR to default location if requested by user if len(target.install_dir) > 3 and target.install_dir[3] is True: target.install_dir[3] = os.path.join(self.environment.get_datadir(), 'gir-1.0') @@ -1972,6 +1980,7 @@ class NinjaBackend(backends.Backend): for s in f.get_outputs()]) self.all_structured_sources.update(_ods) orderdeps.extend(_ods) + return orderdeps, main_rust_file for i in target.get_sources(): if main_rust_file is None: @@ -2010,7 +2019,8 @@ class NinjaBackend(backends.Backend): args += target.get_extra_args('rust') return args - def get_rust_compiler_deps_and_args(self, target: build.BuildTarget, rustc: Compiler) -> T.Tuple[T.List[str], T.List[str], T.List[RustDep], T.List[str]]: + def get_rust_compiler_deps_and_args(self, target: build.BuildTarget, rustc: Compiler, + obj_list: T.List[str]) -> T.Tuple[T.List[str], T.List[RustDep], T.List[str]]: deps: T.List[str] = [] project_deps: T.List[RustDep] = [] args: T.List[str] = [] @@ -2042,11 +2052,9 @@ class NinjaBackend(backends.Backend): type_ += ':' + ','.join(modifiers) args.append(f'-l{type_}={libname}') - objs, od = self.flatten_object_list(target) - for o in objs: + for o in obj_list: args.append(f'-Clink-arg={o}') deps.append(o) - fortran_order_deps = self.get_fortran_order_deps(od) linkdirs = mesonlib.OrderedSet() external_deps = target.external_deps.copy() @@ -2096,20 +2104,24 @@ class NinjaBackend(backends.Backend): for a in e.get_link_args(): if a in rustc.native_static_libs: # Exclude link args that rustc already add by default - pass + continue elif a.startswith('-L'): args.append(a) - elif a.endswith(('.dll', '.so', '.dylib', '.a', '.lib')) and isinstance(target, build.StaticLibrary): + continue + elif a.endswith(('.dll', '.so', '.dylib', '.a', '.lib')): dir_, lib = os.path.split(a) linkdirs.add(dir_) - if not verbatim: - lib, ext = os.path.splitext(lib) - if lib.startswith('lib'): - lib = lib[3:] - static = a.endswith(('.a', '.lib')) - _link_library(lib, static) - else: - args.append(f'-Clink-arg={a}') + + if isinstance(target, build.StaticLibrary): + if not verbatim: + lib, ext = os.path.splitext(lib) + if lib.startswith('lib'): + lib = lib[3:] + static = a.endswith(('.a', '.lib')) + _link_library(lib, static) + continue + + args.append(f'-Clink-arg={a}') for d in linkdirs: d = d or '.' @@ -2124,40 +2136,44 @@ class NinjaBackend(backends.Backend): and dep.rust_crate_type == 'dylib' for dep in target_deps) - if target.rust_crate_type in {'dylib', 'proc-macro'} or has_rust_shared_deps: - # add prefer-dynamic if any of the Rust libraries we link + if target.rust_crate_type in {'dylib', 'proc-macro'}: + # also add prefer-dynamic if any of the Rust libraries we link # against are dynamic or this is a dynamic library itself, # otherwise we'll end up with multiple implementations of libstd. + has_rust_shared_deps = True + elif self.get_target_option(target, 'rust_dynamic_std'): + if target.rust_crate_type == 'staticlib': + # staticlib crates always include a copy of the Rust libstd, + # therefore it is not possible to also link it dynamically. + # The options to avoid this (-Z staticlib-allow-rdylib-deps and + # -Z staticlib-prefer-dynamic) are not yet stable; alternatively, + # one could use "--emit obj" (implemented in the pull request at + # https://github.com/mesonbuild/meson/pull/11213) or "--emit rlib" + # (officially not recommended for linking with C programs). + raise MesonException('rust_dynamic_std does not support staticlib crates yet') + # want libstd as a shared dep + has_rust_shared_deps = True + + if has_rust_shared_deps: args += ['-C', 'prefer-dynamic'] - - if isinstance(target, build.SharedLibrary) or has_shared_deps: + if has_shared_deps or has_rust_shared_deps: args += self.get_build_rpath_args(target, rustc) - return deps, fortran_order_deps, project_deps, args - - def generate_rust_target(self, target: build.BuildTarget) -> None: - rustc = T.cast('RustCompiler', target.compilers['rust']) - self.generate_generator_list_rules(target) - - for i in target.get_sources(): - if not rustc.can_compile(i): - raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') - for g in target.get_generated_sources(): - for i in g.get_outputs(): - if not rustc.can_compile(i): - raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') + return deps, project_deps, args + def generate_rust_target(self, target: build.BuildTarget, target_name: str, obj_list: T.List[str], + fortran_order_deps: T.List[str]) -> None: orderdeps, main_rust_file = self.generate_rust_sources(target) - target_name = self.get_target_filename(target) if main_rust_file is None: raise RuntimeError('A Rust target has no Rust sources. This is weird. Also a bug. Please report') + rustc = T.cast('RustCompiler', target.compilers['rust']) args = rustc.compiler_args() depfile = os.path.join(self.get_target_private_dir(target), target.name + '.d') args += self.get_rust_compiler_args(target, rustc, target.rust_crate_type, depfile) - deps, fortran_order_deps, project_deps, deps_args = self.get_rust_compiler_deps_and_args(target, rustc) + deps, project_deps, deps_args = self.get_rust_compiler_deps_and_args(target, rustc, obj_list) args += deps_args proc_macro_dylib_path = None @@ -2192,7 +2208,10 @@ class NinjaBackend(backends.Backend): rustdoc = rustc.get_rustdoc(self.environment) args = rustdoc.get_exe_args() args += self.get_rust_compiler_args(target.doctests.target, rustdoc, target.rust_crate_type) - _, _, _, deps_args = self.get_rust_compiler_deps_and_args(target.doctests.target, rustdoc) + # There can be no non-Rust objects: the doctests are gathered from Rust + # sources and the tests are linked with the target (which is where the + # obj_list was linked into) + _, _, deps_args = self.get_rust_compiler_deps_and_args(target.doctests.target, rustdoc, []) args += deps_args target.doctests.cmd_args = args.to_native() + [main_rust_file] + target.doctests.cmd_args @@ -2214,10 +2233,7 @@ class NinjaBackend(backends.Backend): def swift_module_file_name(self, target): return os.path.join(self.get_target_private_dir(target), - self.target_swift_modulename(target) + '.swiftmodule') - - def target_swift_modulename(self, target): - return target.name + target.swift_module_name + '.swiftmodule') def determine_swift_dep_modules(self, target): result = [] @@ -2244,12 +2260,26 @@ class NinjaBackend(backends.Backend): return srcs, others def generate_swift_target(self, target) -> None: - module_name = self.target_swift_modulename(target) + module_name = target.swift_module_name swiftc = target.compilers['swift'] abssrc = [] relsrc = [] abs_headers = [] header_imports = [] + + if not target.uses_swift_cpp_interop(): + cpp_targets = [t for t in target.link_targets if t.uses_swift_cpp_interop()] + if cpp_targets != []: + target_word = 'targets' if len(cpp_targets) > 1 else 'target' + first = ', '.join(repr(t.name) for t in cpp_targets[:-1]) + and_word = ' and ' if len(cpp_targets) > 1 else '' + last = repr(cpp_targets[-1].name) + enable_word = 'enable' if len(cpp_targets) > 1 else 'enables' + raise MesonException('Swift target {0} links against {1} {2}{3}{4} which {5} C++ interoperability. ' + 'This requires {0} to also have it enabled. ' + 'Add "swift_interoperability_mode: \'cpp\'" to the definition of {0}.' + .format(repr(target.name), target_word, first, and_word, last, enable_word)) + for i in target.get_sources(): if swiftc.can_compile(i): rels = i.rel_to_builddir(self.build_to_src) @@ -2266,10 +2296,16 @@ class NinjaBackend(backends.Backend): os.makedirs(self.get_target_private_dir_abs(target), exist_ok=True) compile_args = self.generate_basic_compiler_args(target, swiftc) compile_args += swiftc.get_module_args(module_name) - if mesonlib.version_compare(swiftc.version, '>=5.9'): - compile_args += swiftc.get_cxx_interoperability_args(target.compilers) + compile_args += swiftc.get_cxx_interoperability_args(target) compile_args += self.build.get_project_args(swiftc, target.subproject, target.for_machine) compile_args += self.build.get_global_args(swiftc, target.for_machine) + if isinstance(target, (build.StaticLibrary, build.SharedLibrary)): + # swiftc treats modules with a single source file, and the main.swift file in multi-source file modules + # as top-level code. This is undesirable in library targets since it emits a main function. Add the + # -parse-as-library option as necessary to prevent emitting the main function while keeping files explicitly + # named main.swift treated as the entrypoint of the module in case this is desired. + if len(abssrc) == 1 and os.path.basename(abssrc[0]) != 'main.swift': + compile_args += swiftc.get_library_args() for i in reversed(target.get_include_dirs()): basedir = i.get_curdir() for d in i.get_incdirs(): @@ -3353,7 +3389,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) def get_target_shsym_filename(self, target): # Always name the .symbols file after the primary build output because it always exists targetdir = self.get_target_private_dir(target) - return os.path.join(targetdir, target.get_filename() + '.symbols') + return Path(targetdir, target.get_filename() + '.symbols').as_posix() def generate_shsym(self, target) -> None: target_file = self.get_target_filename(target) @@ -3372,7 +3408,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) self.add_build(elem) def get_import_filename(self, target) -> str: - return os.path.join(self.get_target_dir(target), target.import_filename) + return Path(self.get_target_dir(target), target.import_filename).as_posix() def get_target_type_link_args(self, target, linker): commands = [] @@ -3560,9 +3596,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) linker.build_rpath_args(self.environment, self.environment.get_build_dir(), target_slashname_workaround_dir, - self.determine_rpath_dirs(target), - target.build_rpath, - target.install_rpath)) + target)) return rpath_args def generate_link(self, target: build.BuildTarget, outname, obj_list, linker: T.Union['Compiler', 'StaticLinker'], extra_args=None, stdlib_args=None): diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index 6ad982d..e7bd487 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -176,6 +176,15 @@ class PbxDict: self.keys.add(key) self.items.append(item) + def get_item(self, key: str) -> PbxDictItem: + assert key in self.keys + for item in self.items: + if not isinstance(item, PbxDictItem): + continue + if item.key == key: + return item + return None + def has_item(self, key: str) -> bool: return key in self.keys @@ -396,10 +405,23 @@ class XCodeBackend(backends.Backend): def generate_filemap(self) -> None: self.filemap = {} # Key is source file relative to src root. + self.foldermap = {} self.target_filemap = {} for name, t in self.build_targets.items(): for s in t.sources: if isinstance(s, mesonlib.File): + if '/' in s.fname: + # From the top level down, add the folders containing the source file. + folder = os.path.split(os.path.dirname(s.fname)) + while folder: + fpath = os.path.join(*folder) + # Multiple targets might use the same folders, so store their targets with them. + # Otherwise, folders and their source files will appear in the wrong places in Xcode. + if (fpath, t) not in self.foldermap: + self.foldermap[(fpath, t)] = self.gen_id() + else: + break + folder = folder[:-1] s = os.path.join(s.subdir, s.fname) self.filemap[s] = self.gen_id() for o in t.objects: @@ -1052,6 +1074,24 @@ class XCodeBackend(backends.Backend): main_children.add_item(frameworks_id, 'Frameworks') main_dict.add_item('sourceTree', '<group>') + # Define each folder as a group in Xcode. That way, it can build the file tree correctly. + # This must be done before the project tree group is generated, as source files are added during that phase. + for (path, target), id in self.foldermap.items(): + folder_dict = PbxDict() + objects_dict.add_item(id, folder_dict, path) + folder_dict.add_item('isa', 'PBXGroup') + folder_children = PbxArray() + folder_dict.add_item('children', folder_children) + folder_dict.add_item('name', '"{}"'.format(path.rsplit('/', 1)[-1])) + folder_dict.add_item('path', f'"{path}"') + folder_dict.add_item('sourceTree', 'SOURCE_ROOT') + + # Add any detected subdirectories (not declared as subdir()) here, but only one level higher. + # Example: In "root", add "root/sub", but not "root/sub/subtwo". + for path_dep, target_dep in self.foldermap: + if path_dep.startswith(path) and path_dep.split('/', 1)[0] == path.split('/', 1)[0] and path_dep != path and path_dep.count('/') == path.count('/') + 1 and target == target_dep: + folder_children.add_item(self.foldermap[(path_dep, target)], path_dep) + self.add_projecttree(objects_dict, projecttree_id) resource_dict = PbxDict() @@ -1121,6 +1161,7 @@ class XCodeBackend(backends.Backend): tid = t.get_id() group_id = self.gen_id() target_dict = PbxDict() + folder_ids = set() objects_dict.add_item(group_id, target_dict, tid) target_dict.add_item('isa', 'PBXGroup') target_children = PbxArray() @@ -1130,6 +1171,18 @@ class XCodeBackend(backends.Backend): source_files_dict = PbxDict() for s in t.sources: if isinstance(s, mesonlib.File): + # If the file is in a folder, add it to the group representing that folder. + if '/' in s.fname: + folder = '/'.join(s.fname.split('/')[:-1]) + folder_dict = objects_dict.get_item(self.foldermap[(folder, t)]).value.get_item('children').value + temp = os.path.join(s.subdir, s.fname) + folder_dict.add_item(self.fileref_ids[(tid, temp)], temp) + if self.foldermap[(folder, t)] in folder_ids: + continue + if len(folder.split('/')) == 1: + target_children.add_item(self.foldermap[(folder, t)], folder) + folder_ids.add(self.foldermap[(folder, t)]) + continue s = os.path.join(s.subdir, s.fname) elif isinstance(s, str): s = os.path.join(t.subdir, s) @@ -1778,7 +1831,7 @@ class XCodeBackend(backends.Backend): settings_dict.add_item('SECTORDER_FLAGS', '') if is_swift and bridging_header: settings_dict.add_item('SWIFT_OBJC_BRIDGING_HEADER', bridging_header) - if self.objversion >= 60 and 'cpp' in langs: + if self.objversion >= 60 and target.uses_swift_cpp_interop(): settings_dict.add_item('SWIFT_OBJC_INTEROP_MODE', 'objcxx') settings_dict.add_item('BUILD_DIR', symroot) settings_dict.add_item('OBJROOT', f'{symroot}/build') diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 72d376d..2adfb98 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -24,14 +24,14 @@ from .mesonlib import ( File, MesonException, MachineChoice, PerMachine, OrderedSet, listify, extract_as_list, typeslistify, stringlistify, classify_unity_sources, get_filenames_templates_dict, substitute_values, has_path_sep, - is_parent_path, PerMachineDefaultable, + is_parent_path, relpath, PerMachineDefaultable, MesonBugException, EnvironmentVariables, pickle_load, lazy_property, ) from .options import OptionKey from .compilers import ( is_header, is_object, is_source, clink_langs, sort_clink, all_languages, - is_known_suffix, detect_static_linker + is_known_suffix, detect_static_linker, LANGUAGES_USING_LDFLAGS ) from .interpreterbase import FeatureNew, FeatureDeprecated, UnknownValue @@ -75,6 +75,7 @@ lang_arg_kwargs |= { vala_kwargs = {'vala_header', 'vala_gir', 'vala_vapi'} rust_kwargs = {'rust_crate_type', 'rust_dependency_map'} cs_kwargs = {'resources', 'cs_args'} +swift_kwargs = {'swift_interoperability_mode', 'swift_module_name'} buildtarget_kwargs = { 'build_by_default', @@ -110,7 +111,8 @@ known_build_target_kwargs = ( pch_kwargs | vala_kwargs | rust_kwargs | - cs_kwargs) + cs_kwargs | + swift_kwargs) known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie', 'vs_module_defs', 'android_exe_type'} known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions', 'rust_abi'} @@ -769,14 +771,23 @@ class BuildTarget(Target): ''' Initialisations and checks requiring the final list of compilers to be known ''' self.validate_sources() - if self.structured_sources and any([self.sources, self.generated]): - raise MesonException('cannot mix structured sources and unstructured sources') - if self.structured_sources and 'rust' not in self.compilers: - raise MesonException('structured sources are only supported in Rust targets') if self.uses_rust(): + if self.link_language and self.link_language != 'rust': + raise MesonException('cannot build Rust sources with a different link_language') + if self.structured_sources: + # TODO: the interpreter should be able to generate a better error message? + if any((s.endswith('.rs') for s in self.sources)) or \ + any(any((s.endswith('.rs') for s in g.get_outputs())) for g in self.generated): + raise MesonException('cannot mix Rust structured sources and unstructured sources') + # relocation-model=pic is rustc's default and Meson does not # currently have a way to disable PIC. self.pic = True + self.pie = True + else: + if self.structured_sources: + raise MesonException('structured sources are only supported in Rust targets') + if 'vala' in self.compilers and self.is_linkable_target(): self.outputs += [self.vala_header, self.vala_vapi] self.install_tag += ['devel', 'devel'] @@ -878,6 +889,10 @@ class BuildTarget(Target): if isinstance(t, (CustomTarget, CustomTargetIndex)): continue # We can't know anything about these. for name, compiler in t.compilers.items(): + if name == 'rust': + # Rust is always linked through a C-ABI target, so do not add + # the compiler here + continue if name in link_langs and name not in self.compilers: self.compilers[name] = compiler @@ -963,7 +978,7 @@ class BuildTarget(Target): self.compilers[lang] = compiler break else: - if is_known_suffix(s): + if is_known_suffix(s) and not is_header(s): path = pathlib.Path(str(s)).as_posix() m = f'No {self.for_machine.get_lower_case_name()} machine compiler for {path!r}' raise MesonException(m) @@ -1260,6 +1275,12 @@ class BuildTarget(Target): raise InvalidArguments(f'Invalid rust_dependency_map "{rust_dependency_map}": must be a dictionary with string values.') self.rust_dependency_map = rust_dependency_map + self.swift_interoperability_mode = kwargs.get('swift_interoperability_mode') + + self.swift_module_name = kwargs.get('swift_module_name') + if self.swift_module_name == '': + self.swift_module_name = self.name + def _extract_pic_pie(self, kwargs: T.Dict[str, T.Any], arg: str, option: str) -> bool: # Check if we have -fPIC, -fpic, -fPIE, or -fpie in cflags all_flags = self.extra_args['c'] + self.extra_args['cpp'] @@ -1589,6 +1610,9 @@ class BuildTarget(Target): if isinstance(link_target, (CustomTarget, CustomTargetIndex)): continue for language in link_target.compilers: + if language == 'rust' and not link_target.uses_rust_abi(): + # All Rust dependencies must go through a C-ABI dependency, so ignore it + continue if language not in langs: langs.append(language) @@ -1680,6 +1704,9 @@ class BuildTarget(Target): def uses_fortran(self) -> bool: return 'fortran' in self.compilers + def uses_swift_cpp_interop(self) -> bool: + return self.swift_interoperability_mode == 'cpp' and 'swift' in self.compilers + def get_using_msvc(self) -> bool: ''' Check if the dynamic linker is MSVC. Used by Executable, StaticLibrary, @@ -1774,6 +1801,121 @@ class BuildTarget(Target): """Base case used by BothLibraries""" return self + def determine_rpath_dirs(self) -> T.Tuple[str, ...]: + result: OrderedSet[str] + if self.environment.coredata.optstore.get_value_for(OptionKey('layout')) == 'mirror': + # Need a copy here + result = OrderedSet(self.get_link_dep_subdirs()) + else: + result = OrderedSet() + result.add('meson-out') + result.update(self.rpaths_for_non_system_absolute_shared_libraries()) + self.rpath_dirs_to_remove.update([d.encode('utf-8') for d in result]) + return tuple(result) + + @lru_cache(maxsize=None) + def rpaths_for_non_system_absolute_shared_libraries(self, exclude_system: bool = True) -> ImmutableListProtocol[str]: + paths: OrderedSet[str] = OrderedSet() + srcdir = self.environment.get_source_dir() + + system_dirs = set() + if exclude_system: + for cc in self.compilers.values(): + system_dirs.update(cc.get_library_dirs(self.environment)) + + external_rpaths = self.get_external_rpath_dirs() + build_to_src = relpath(self.environment.get_source_dir(), + self.environment.get_build_dir()) + + for dep in self.external_deps: + if dep.type_name not in {'library', 'pkgconfig', 'cmake'}: + continue + for libpath in dep.link_args: + if libpath.startswith('-'): + continue + # For all link args that are absolute paths to a library file, add RPATH args + if not os.path.isabs(libpath): + continue + libdir, libname = os.path.split(libpath) + # Windows doesn't support rpaths, but we use this function to + # emulate rpaths by setting PATH + # .dll is there for mingw gcc + # .so's may be extended with version information, e.g. libxyz.so.1.2.3 + if not ( + libname.endswith(('.dll', '.lib', '.so', '.dylib')) + or '.so.' in libname + ): + continue + + # Don't remove rpaths specified in LDFLAGS. + if libdir in external_rpaths: + continue + if system_dirs and os.path.normpath(libdir) in system_dirs: + # No point in adding system paths. + continue + + if is_parent_path(srcdir, libdir): + rel_to_src = libdir[len(srcdir) + 1:] + assert not os.path.isabs(rel_to_src), f'rel_to_src: {rel_to_src} is absolute' + paths.add(os.path.join(build_to_src, rel_to_src)) + else: + paths.add(libdir) + # Don't remove rpaths specified by the dependency + paths.difference_update(self.get_rpath_dirs_from_link_args(dep.link_args)) + for i in itertools.chain(self.link_targets, self.link_whole_targets): + if isinstance(i, BuildTarget): + paths.update(i.rpaths_for_non_system_absolute_shared_libraries(exclude_system)) + return list(paths) + + def get_external_rpath_dirs(self) -> T.Set[str]: + args: T.List[str] = [] + for lang in LANGUAGES_USING_LDFLAGS: + try: + args += self.environment.coredata.get_external_link_args(self.for_machine, lang) + except KeyError: + pass + return self.get_rpath_dirs_from_link_args(args) + + # Match rpath formats: + # -Wl,-rpath= + # -Wl,-rpath, + _rpath_regex = re.compile(r'-Wl,-rpath[=,]([^,]+)') + # Match solaris style compat runpath formats: + # -Wl,-R + # -Wl,-R, + _runpath_regex = re.compile(r'-Wl,-R[,]?([^,]+)') + # Match symbols formats: + # -Wl,--just-symbols= + # -Wl,--just-symbols, + _symbols_regex = re.compile(r'-Wl,--just-symbols[=,]([^,]+)') + + @classmethod + def get_rpath_dirs_from_link_args(cls, args: T.List[str]) -> T.Set[str]: + dirs: T.Set[str] = set() + + for arg in args: + if not arg.startswith('-Wl,'): + continue + + rpath_match = cls._rpath_regex.match(arg) + if rpath_match: + for dir in rpath_match.group(1).split(':'): + dirs.add(dir) + runpath_match = cls._runpath_regex.match(arg) + if runpath_match: + for dir in runpath_match.group(1).split(':'): + # The symbols arg is an rpath if the path is a directory + if os.path.isdir(dir): + dirs.add(dir) + symbols_match = cls._symbols_regex.match(arg) + if symbols_match: + for dir in symbols_match.group(1).split(':'): + # Prevent usage of --just-symbols to specify rpath + if os.path.isdir(dir): + raise MesonException(f'Invalid arg for --just-symbols, {dir} is a directory.') + return dirs + + class FileInTargetPrivateDir: """Represents a file with the path '/path/to/build/target_private_dir/fname'. target_private_dir is the return value of get_target_private_dir which is e.g. 'subdir/target.p'. diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index a5d703e..a0d4371 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -11,439 +11,30 @@ port will be required. from __future__ import annotations import dataclasses -import importlib -import json import os -import shutil import collections import urllib.parse import itertools import typing as T from . import builder, version, cfg -from ..mesonlib import MesonException, Popen_safe, MachineChoice +from .toml import load_toml, TomlImplementationMissing +from .manifest import Manifest, CargoLock, fixup_meson_varname +from ..mesonlib import MesonException, MachineChoice from .. import coredata, mlog from ..wrap.wrap import PackageDefinition if T.TYPE_CHECKING: - from types import ModuleType - - from typing_extensions import Protocol, Self - - from . import manifest + from . import raw from .. import mparser + from .manifest import Dependency, SystemDependency from ..environment import Environment from ..interpreterbase import SubProject from ..compilers.rust import RustCompiler - # Copied from typeshed. Blarg that they don't expose this - class DataclassInstance(Protocol): - __dataclass_fields__: T.ClassVar[dict[str, dataclasses.Field[T.Any]]] - - _UnknownKeysT = T.TypeVar('_UnknownKeysT', manifest.FixedPackage, - manifest.FixedDependency, manifest.FixedLibTarget, - manifest.FixedBuildTarget) - - -# tomllib is present in python 3.11, before that it is a pypi module called tomli, -# we try to import tomllib, then tomli, -# TODO: add a fallback to toml2json? -tomllib: T.Optional[ModuleType] = None -toml2json: T.Optional[str] = None -for t in ['tomllib', 'tomli']: - try: - tomllib = importlib.import_module(t) - break - except ImportError: - pass -else: - # TODO: it would be better to use an Executable here, which could be looked - # up in the cross file or provided by a wrap. However, that will have to be - # passed in externally, since we don't have (and I don't think we should), - # have access to the `Environment` for that in this module. - toml2json = shutil.which('toml2json') - - -_EXTRA_KEYS_WARNING = ( - "This may (unlikely) be an error in the cargo manifest, or may be a missing " - "implementation in Meson. If this issue can be reproduced with the latest " - "version of Meson, please help us by opening an issue at " - "https://github.com/mesonbuild/meson/issues. Please include the crate and " - "version that is generating this warning if possible." -) - -class TomlImplementationMissing(MesonException): - pass - - -def load_toml(filename: str) -> T.Dict[object, object]: - if tomllib: - with open(filename, 'rb') as f: - raw = tomllib.load(f) - else: - if toml2json is None: - raise TomlImplementationMissing('Could not find an implementation of tomllib, nor toml2json') - - p, out, err = Popen_safe([toml2json, filename]) - if p.returncode != 0: - raise MesonException('toml2json failed to decode output\n', err) - - raw = json.loads(out) - - if not isinstance(raw, dict): - raise MesonException("Cargo.toml isn't a dictionary? How did that happen?") - - return raw - - -def fixup_meson_varname(name: str) -> str: - """Fixup a meson variable name - - :param name: The name to fix - :return: the fixed name - """ - return name.replace('-', '_') - - -# Pylance can figure out that these do not, in fact, overlap, but mypy can't -@T.overload -def _fixup_raw_mappings(d: manifest.BuildTarget) -> manifest.FixedBuildTarget: ... # type: ignore - -@T.overload -def _fixup_raw_mappings(d: manifest.LibTarget) -> manifest.FixedLibTarget: ... # type: ignore - -@T.overload -def _fixup_raw_mappings(d: manifest.Dependency) -> manifest.FixedDependency: ... - -def _fixup_raw_mappings(d: T.Union[manifest.BuildTarget, manifest.LibTarget, manifest.Dependency] - ) -> T.Union[manifest.FixedBuildTarget, manifest.FixedLibTarget, - manifest.FixedDependency]: - """Fixup raw cargo mappings to ones more suitable for python to consume. - - This does the following: - * replaces any `-` with `_`, cargo likes the former, but python dicts make - keys with `-` in them awkward to work with - * Convert Dependency versions from the cargo format to something meson - understands - - :param d: The mapping to fix - :return: the fixed string - """ - raw = {fixup_meson_varname(k): v for k, v in d.items()} - if 'version' in raw: - assert isinstance(raw['version'], str), 'for mypy' - raw['version'] = version.convert(raw['version']) - return T.cast('T.Union[manifest.FixedBuildTarget, manifest.FixedLibTarget, manifest.FixedDependency]', raw) - - -def _handle_unknown_keys(data: _UnknownKeysT, cls: T.Union[DataclassInstance, T.Type[DataclassInstance]], - msg: str) -> _UnknownKeysT: - """Remove and warn on keys that are coming from cargo, but are unknown to - our representations. - - This is intended to give users the possibility of things proceeding when a - new key is added to Cargo.toml that we don't yet handle, but to still warn - them that things might not work. - - :param data: The raw data to look at - :param cls: The Dataclass derived type that will be created - :param msg: the header for the error message. Usually something like "In N structure". - :return: The original data structure, but with all unknown keys removed. - """ - unexpected = set(data) - {x.name for x in dataclasses.fields(cls)} - if unexpected: - mlog.warning(msg, 'has unexpected keys', '"{}".'.format(', '.join(sorted(unexpected))), - _EXTRA_KEYS_WARNING) - for k in unexpected: - # Mypy and Pyright can't prove that this is okay - del data[k] # type: ignore[misc] - return data - - -@dataclasses.dataclass -class Package: - - """Representation of a Cargo Package entry, with defaults filled in.""" - - name: str - version: str - description: T.Optional[str] = None - resolver: T.Optional[str] = None - authors: T.List[str] = dataclasses.field(default_factory=list) - edition: manifest.EDITION = '2015' - rust_version: T.Optional[str] = None - documentation: T.Optional[str] = None - readme: T.Optional[str] = None - homepage: T.Optional[str] = None - repository: T.Optional[str] = None - license: T.Optional[str] = None - license_file: T.Optional[str] = None - keywords: T.List[str] = dataclasses.field(default_factory=list) - categories: T.List[str] = dataclasses.field(default_factory=list) - workspace: T.Optional[str] = None - build: T.Optional[str] = None - links: T.Optional[str] = None - exclude: T.List[str] = dataclasses.field(default_factory=list) - include: T.List[str] = dataclasses.field(default_factory=list) - publish: bool = True - metadata: T.Dict[str, T.Any] = dataclasses.field(default_factory=dict) - default_run: T.Optional[str] = None - autolib: bool = True - autobins: bool = True - autoexamples: bool = True - autotests: bool = True - autobenches: bool = True - api: str = dataclasses.field(init=False) - - def __post_init__(self) -> None: - self.api = _version_to_api(self.version) - - @classmethod - def from_raw(cls, raw: manifest.Package) -> Self: - pkg = T.cast('manifest.FixedPackage', - {fixup_meson_varname(k): v for k, v in raw.items()}) - pkg = _handle_unknown_keys(pkg, cls, f'Package entry {pkg["name"]}') - return cls(**pkg) - -@dataclasses.dataclass -class SystemDependency: - - """ Representation of a Cargo system-deps entry - https://docs.rs/system-deps/latest/system_deps - """ - - name: str - version: T.List[str] - optional: bool = False - feature: T.Optional[str] = None - feature_overrides: T.Dict[str, T.Dict[str, str]] = dataclasses.field(default_factory=dict) - - @classmethod - def from_raw(cls, name: str, raw: T.Any) -> SystemDependency: - if isinstance(raw, str): - return cls(name, SystemDependency.convert_version(raw)) - name = raw.get('name', name) - version = SystemDependency.convert_version(raw.get('version')) - optional = raw.get('optional', False) - feature = raw.get('feature') - # Everything else are overrides when certain features are enabled. - feature_overrides = {k: v for k, v in raw.items() if k not in {'name', 'version', 'optional', 'feature'}} - return cls(name, version, optional, feature, feature_overrides) - - @staticmethod - def convert_version(version: T.Optional[str]) -> T.List[str]: - vers = version.split(',') if version is not None else [] - result: T.List[str] = [] - for v in vers: - v = v.strip() - if v[0] not in '><=': - v = f'>={v}' - result.append(v) - return result - - def enabled(self, features: T.Set[str]) -> bool: - return self.feature is None or self.feature in features - -@dataclasses.dataclass -class Dependency: - - """Representation of a Cargo Dependency Entry.""" - - name: dataclasses.InitVar[str] - version: T.List[str] - registry: T.Optional[str] = None - git: T.Optional[str] = None - branch: T.Optional[str] = None - rev: T.Optional[str] = None - path: T.Optional[str] = None - optional: bool = False - package: str = '' - default_features: bool = True - features: T.List[str] = dataclasses.field(default_factory=list) - api: str = dataclasses.field(init=False) - - def __post_init__(self, name: str) -> None: - self.package = self.package or name - # Extract wanted API version from version constraints. - api = set() - for v in self.version: - if v.startswith(('>=', '==')): - api.add(_version_to_api(v[2:].strip())) - elif v.startswith('='): - api.add(_version_to_api(v[1:].strip())) - if not api: - self.api = '0' - elif len(api) == 1: - self.api = api.pop() - else: - raise MesonException(f'Cannot determine minimum API version from {self.version}.') - - @classmethod - def from_raw(cls, name: str, raw: manifest.DependencyV) -> Dependency: - """Create a dependency from a raw cargo dictionary""" - if isinstance(raw, str): - return cls(name, version.convert(raw)) - fixed = _handle_unknown_keys(_fixup_raw_mappings(raw), cls, f'Dependency entry {name}') - return cls(name, **fixed) - - -@dataclasses.dataclass -class BuildTarget: - - name: str - crate_type: T.List[manifest.CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['lib']) - path: dataclasses.InitVar[T.Optional[str]] = None - - # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-test-field - # True for lib, bin, test - test: bool = True - - # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-doctest-field - # True for lib - doctest: bool = False - - # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-bench-field - # True for lib, bin, benchmark - bench: bool = True - - # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-doc-field - # True for libraries and binaries - doc: bool = False - - harness: bool = True - edition: manifest.EDITION = '2015' - required_features: T.List[str] = dataclasses.field(default_factory=list) - plugin: bool = False - - @classmethod - def from_raw(cls, raw: manifest.BuildTarget) -> Self: - name = raw.get('name', '<anonymous>') - build = _handle_unknown_keys(_fixup_raw_mappings(raw), cls, f'Binary entry {name}') - return cls(**build) - -@dataclasses.dataclass -class Library(BuildTarget): - - """Representation of a Cargo Library Entry.""" - - doctest: bool = True - doc: bool = True - path: str = os.path.join('src', 'lib.rs') - proc_macro: bool = False - crate_type: T.List[manifest.CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['lib']) - doc_scrape_examples: bool = True - - @classmethod - def from_raw(cls, raw: manifest.LibTarget, fallback_name: str) -> Self: # type: ignore[override] - fixed = _fixup_raw_mappings(raw) - - # We need to set the name field if it's not set manually, including if - # other fields are set in the lib section - if 'name' not in fixed: - fixed['name'] = fallback_name - fixed = _handle_unknown_keys(fixed, cls, f'Library entry {fixed["name"]}') - - return cls(**fixed) - - -@dataclasses.dataclass -class Binary(BuildTarget): - - """Representation of a Cargo Bin Entry.""" - - doc: bool = True - - -@dataclasses.dataclass -class Test(BuildTarget): - - """Representation of a Cargo Test Entry.""" - - bench: bool = True - - -@dataclasses.dataclass -class Benchmark(BuildTarget): - - """Representation of a Cargo Benchmark Entry.""" - - test: bool = True - - -@dataclasses.dataclass -class Example(BuildTarget): - - """Representation of a Cargo Example Entry.""" - - crate_type: T.List[manifest.CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['bin']) - - -@dataclasses.dataclass -class Manifest: - - """Cargo Manifest definition. - - Most of these values map up to the Cargo Manifest, but with default values - if not provided. - - Cargo subprojects can contain what Meson wants to treat as multiple, - interdependent, subprojects. - - :param path: the path within the cargo subproject. - """ - - package: Package - dependencies: T.Dict[str, Dependency] - dev_dependencies: T.Dict[str, Dependency] - build_dependencies: T.Dict[str, Dependency] - system_dependencies: T.Dict[str, SystemDependency] = dataclasses.field(init=False) - lib: Library - bin: T.List[Binary] - test: T.List[Test] - bench: T.List[Benchmark] - example: T.List[Example] - features: T.Dict[str, T.List[str]] - target: T.Dict[str, T.Dict[str, Dependency]] - path: str = '' - - def __post_init__(self) -> None: - self.features.setdefault('default', []) - self.system_dependencies = {k: SystemDependency.from_raw(k, v) for k, v in self.package.metadata.get('system-deps', {}).items()} - - -def _convert_manifest(raw_manifest: manifest.Manifest, subdir: str, path: str = '') -> Manifest: - return Manifest( - Package.from_raw(raw_manifest['package']), - {k: Dependency.from_raw(k, v) for k, v in raw_manifest.get('dependencies', {}).items()}, - {k: Dependency.from_raw(k, v) for k, v in raw_manifest.get('dev-dependencies', {}).items()}, - {k: Dependency.from_raw(k, v) for k, v in raw_manifest.get('build-dependencies', {}).items()}, - Library.from_raw(raw_manifest.get('lib', {}), raw_manifest['package']['name']), - [Binary.from_raw(b) for b in raw_manifest.get('bin', {})], - [Test.from_raw(b) for b in raw_manifest.get('test', {})], - [Benchmark.from_raw(b) for b in raw_manifest.get('bench', {})], - [Example.from_raw(b) for b in raw_manifest.get('example', {})], - raw_manifest.get('features', {}), - {k: {k2: Dependency.from_raw(k2, v2) for k2, v2 in v.get('dependencies', {}).items()} - for k, v in raw_manifest.get('target', {}).items()}, - path, - ) - - -def _version_to_api(version: str) -> str: - # x.y.z -> x - # 0.x.y -> 0.x - # 0.0.x -> 0 - vers = version.split('.') - if int(vers[0]) != 0: - return vers[0] - elif len(vers) >= 2 and int(vers[1]) != 0: - return f'0.{vers[1]}' - return '0' - - -def _dependency_name(package_name: str, api: str) -> str: - basename = package_name[:-3] if package_name.endswith('-rs') else package_name - return f'{basename}-{api}-rs' +def _dependency_name(package_name: str, api: str, suffix: str = '-rs') -> str: + basename = package_name[:-len(suffix)] if package_name.endswith(suffix) else package_name + return f'{basename}-{api}{suffix}' def _dependency_varname(package_name: str) -> str: @@ -458,13 +49,13 @@ def _extra_deps_varname() -> str: return 'extra_deps' +@dataclasses.dataclass class PackageState: - def __init__(self, manifest: Manifest, downloaded: bool) -> None: - self.manifest = manifest - self.downloaded = downloaded - self.features: T.Set[str] = set() - self.required_deps: T.Set[str] = set() - self.optional_deps_features: T.Dict[str, T.Set[str]] = collections.defaultdict(set) + manifest: Manifest + downloaded: bool = False + features: T.Set[str] = dataclasses.field(default_factory=set) + required_deps: T.Set[str] = dataclasses.field(default_factory=set) + optional_deps_features: T.Dict[str, T.Set[str]] = dataclasses.field(default_factory=lambda: collections.defaultdict(set)) @dataclasses.dataclass(frozen=True) @@ -484,6 +75,9 @@ class Interpreter: # Rustc's config self.cfgs = self._get_cfgs() + def get_build_def_files(self) -> T.List[str]: + return [os.path.join(subdir, 'Cargo.toml') for subdir in self.manifests] + def interpret(self, subdir: str) -> mparser.CodeBlockNode: manifest = self._load_manifest(subdir) pkg, cached = self._fetch_package(manifest.package.name, manifest.package.api) @@ -506,9 +100,7 @@ class Interpreter: ast += self._create_dependencies(pkg, build) ast += self._create_meson_subdir(build) - # Libs are always auto-discovered and there's no other way to handle them, - # which is unfortunate for reproducability - if os.path.exists(os.path.join(self.environment.source_dir, subdir, pkg.manifest.path, pkg.manifest.lib.path)): + if pkg.manifest.lib: for crate_type in pkg.manifest.lib.crate_type: ast.extend(self._create_lib(pkg, build, crate_type)) @@ -545,11 +137,12 @@ class Interpreter: def _load_manifest(self, subdir: str) -> Manifest: manifest_ = self.manifests.get(subdir) if not manifest_: - filename = os.path.join(self.environment.source_dir, subdir, 'Cargo.toml') - raw = load_toml(filename) - if 'package' in raw: - raw_manifest = T.cast('manifest.Manifest', raw) - manifest_ = _convert_manifest(raw_manifest, subdir) + path = os.path.join(self.environment.source_dir, subdir) + filename = os.path.join(path, 'Cargo.toml') + toml = load_toml(filename) + if 'package' in toml: + raw_manifest = T.cast('raw.Manifest', toml) + manifest_ = Manifest.from_raw(raw_manifest, path) self.manifests[subdir] = manifest_ else: raise MesonException(f'{subdir}/Cargo.toml does not have [package] section') @@ -668,8 +261,9 @@ class Interpreter: return ast def _create_system_dependency(self, name: str, dep: SystemDependency, build: builder.Builder) -> T.List[mparser.BaseNode]: + # TODO: handle feature_overrides kw = { - 'version': build.array([build.string(s) for s in dep.version]), + 'version': build.array([build.string(s) for s in dep.meson_version]), 'required': build.bool(not dep.optional), } varname = f'{fixup_meson_varname(name)}_system_dep' @@ -696,7 +290,7 @@ class Interpreter: def _create_dependency(self, dep: Dependency, build: builder.Builder) -> T.List[mparser.BaseNode]: pkg = self._dep_package(dep) kw = { - 'version': build.array([build.string(s) for s in dep.version]), + 'version': build.array([build.string(s) for s in dep.meson_version]), } # Lookup for this dependency with the features we want in default_options kwarg. # @@ -772,7 +366,7 @@ class Interpreter: build.block([build.function('subdir', [build.string('meson')])])) ] - def _create_lib(self, pkg: PackageState, build: builder.Builder, crate_type: manifest.CRATE_TYPE) -> T.List[mparser.BaseNode]: + def _create_lib(self, pkg: PackageState, build: builder.Builder, crate_type: raw.CRATE_TYPE) -> T.List[mparser.BaseNode]: dependencies: T.List[mparser.BaseNode] = [] dependency_map: T.Dict[mparser.BaseNode, mparser.BaseNode] = {} for name in pkg.required_deps: @@ -805,6 +399,9 @@ class Interpreter: 'rust_args': build.array(rust_args), } + depname_suffix = '-rs' if crate_type in {'lib', 'rlib', 'proc-macro'} else f'-{crate_type}' + depname = _dependency_name(pkg.manifest.package.name, pkg.manifest.package.api, depname_suffix) + lib: mparser.BaseNode if pkg.manifest.lib.proc_macro or crate_type == 'proc-macro': lib = build.method('proc_macro', build.identifier('rust'), posargs, kwargs) @@ -837,7 +434,8 @@ class Interpreter: 'link_with': build.identifier('lib'), 'variables': build.dict({ build.string('features'): build.string(','.join(pkg.features)), - }) + }), + 'version': build.string(pkg.manifest.package.version), }, ), 'dep' @@ -846,7 +444,7 @@ class Interpreter: 'override_dependency', build.identifier('meson'), [ - build.string(_dependency_name(pkg.manifest.package.name, pkg.manifest.package.api)), + build.string(depname), build.identifier('dep'), ], ), @@ -860,24 +458,23 @@ def load_wraps(source_dir: str, subproject_dir: str) -> T.List[PackageDefinition filename = os.path.join(source_dir, 'Cargo.lock') if os.path.exists(filename): try: - cargolock = T.cast('manifest.CargoLock', load_toml(filename)) + toml = load_toml(filename) except TomlImplementationMissing as e: mlog.warning('Failed to load Cargo.lock:', str(e), fatal=False) return wraps - for package in cargolock['package']: - name = package['name'] - version = package['version'] - subp_name = _dependency_name(name, _version_to_api(version)) - source = package.get('source') - if source is None: + raw_cargolock = T.cast('raw.CargoLock', toml) + cargolock = CargoLock.from_raw(raw_cargolock) + for package in cargolock.package: + subp_name = _dependency_name(package.name, version.api(package.version)) + if package.source is None: # This is project's package, or one of its workspace members. pass - elif source == 'registry+https://github.com/rust-lang/crates.io-index': - checksum = package.get('checksum') + elif package.source == 'registry+https://github.com/rust-lang/crates.io-index': + checksum = package.checksum if checksum is None: - checksum = cargolock['metadata'][f'checksum {name} {version} ({source})'] - url = f'https://crates.io/api/v1/crates/{name}/{version}/download' - directory = f'{name}-{version}' + checksum = cargolock.metadata[f'checksum {package.name} {package.version} ({package.source})'] + url = f'https://crates.io/api/v1/crates/{package.name}/{package.version}/download' + directory = f'{package.name}-{package.version}' wraps.append(PackageDefinition.from_values(subp_name, subproject_dir, 'file', { 'directory': directory, 'source_url': url, @@ -885,18 +482,18 @@ def load_wraps(source_dir: str, subproject_dir: str) -> T.List[PackageDefinition 'source_hash': checksum, 'method': 'cargo', })) - elif source.startswith('git+'): - parts = urllib.parse.urlparse(source[4:]) + elif package.source.startswith('git+'): + parts = urllib.parse.urlparse(package.source[4:]) query = urllib.parse.parse_qs(parts.query) branch = query['branch'][0] if 'branch' in query else '' revision = parts.fragment or branch url = urllib.parse.urlunparse(parts._replace(params='', query='', fragment='')) wraps.append(PackageDefinition.from_values(subp_name, subproject_dir, 'git', { - 'directory': name, + 'directory': package.name, 'url': url, 'revision': revision, 'method': 'cargo', })) else: - mlog.warning(f'Unsupported source URL in {filename}: {source}') + mlog.warning(f'Unsupported source URL in {filename}: {package.source}') return wraps diff --git a/mesonbuild/cargo/manifest.py b/mesonbuild/cargo/manifest.py index d95df7f..ab059b0 100644 --- a/mesonbuild/cargo/manifest.py +++ b/mesonbuild/cargo/manifest.py @@ -4,244 +4,505 @@ """Type definitions for cargo manifest files.""" from __future__ import annotations + +import dataclasses +import os import typing as T -from typing_extensions import Literal, TypedDict, Required - -EDITION = Literal['2015', '2018', '2021'] -CRATE_TYPE = Literal['bin', 'lib', 'dylib', 'staticlib', 'cdylib', 'rlib', 'proc-macro'] - -Package = TypedDict( - 'Package', - { - 'name': Required[str], - 'version': Required[str], - 'authors': T.List[str], - 'edition': EDITION, - 'rust-version': str, - 'description': str, - 'readme': str, - 'license': str, - 'license-file': str, - 'keywords': T.List[str], - 'categories': T.List[str], - 'workspace': str, - 'build': str, - 'links': str, - 'include': T.List[str], - 'exclude': T.List[str], - 'publish': bool, - 'metadata': T.Dict[str, T.Dict[str, str]], - 'default-run': str, - 'autolib': bool, - 'autobins': bool, - 'autoexamples': bool, - 'autotests': bool, - 'autobenches': bool, - }, - total=False, -) -"""A description of the Package Dictionary.""" - -class FixedPackage(TypedDict, total=False): - - """A description of the Package Dictionary, fixed up.""" - - name: Required[str] - version: Required[str] - authors: T.List[str] - edition: EDITION - rust_version: str - description: str - readme: str - license: str - license_file: str - keywords: T.List[str] - categories: T.List[str] - workspace: str - build: str - links: str - include: T.List[str] - exclude: T.List[str] - publish: bool - metadata: T.Dict[str, T.Dict[str, str]] - default_run: str - autolib: bool - autobins: bool - autoexamples: bool - autotests: bool - autobenches: bool - - -class Badge(TypedDict): - - """An entry in the badge section.""" - - status: Literal['actively-developed', 'passively-developed', 'as-is', 'experimental', 'deprecated', 'none'] - - -Dependency = TypedDict( - 'Dependency', - { - 'version': str, - 'registry': str, - 'git': str, - 'branch': str, - 'rev': str, - 'path': str, - 'optional': bool, - 'package': str, - 'default-features': bool, - 'features': T.List[str], - }, - total=False, -) -"""An entry in the *dependencies sections.""" +from . import version +from ..mesonlib import MesonException, lazy_property +from .. import mlog +if T.TYPE_CHECKING: + from typing_extensions import Protocol, Self -class FixedDependency(TypedDict, total=False): + from . import raw + from .raw import EDITION, CRATE_TYPE - """An entry in the *dependencies sections, fixed up.""" + # Copied from typeshed. Blarg that they don't expose this + class DataclassInstance(Protocol): + __dataclass_fields__: T.ClassVar[dict[str, dataclasses.Field[T.Any]]] - version: T.List[str] - registry: str - git: str - branch: str - rev: str - path: str - optional: bool - package: str - default_features: bool - features: T.List[str] - - -DependencyV = T.Union[Dependency, str] -"""A Dependency entry, either a string or a Dependency Dict.""" - - -_BaseBuildTarget = TypedDict( - '_BaseBuildTarget', - { - 'path': str, - 'test': bool, - 'doctest': bool, - 'bench': bool, - 'doc': bool, - 'plugin': bool, - 'proc-macro': bool, - 'harness': bool, - 'edition': EDITION, - 'crate-type': T.List[CRATE_TYPE], - 'required-features': T.List[str], - }, - total=False, +_DI = T.TypeVar('_DI', bound='DataclassInstance') +_R = T.TypeVar('_R', bound='raw._BaseBuildTarget') + +_EXTRA_KEYS_WARNING = ( + "This may (unlikely) be an error in the cargo manifest, or may be a missing " + "implementation in Meson. If this issue can be reproduced with the latest " + "version of Meson, please help us by opening an issue at " + "https://github.com/mesonbuild/meson/issues. Please include the crate and " + "version that is generating this warning if possible." ) -class BuildTarget(_BaseBuildTarget, total=False): +def fixup_meson_varname(name: str) -> str: + """Fixup a meson variable name + + :param name: The name to fix + :return: the fixed name + """ + return name.replace('-', '_') + + +@T.overload +def _depv_to_dep(depv: raw.FromWorkspace) -> raw.FromWorkspace: ... + +@T.overload +def _depv_to_dep(depv: raw.DependencyV) -> raw.Dependency: ... + +def _depv_to_dep(depv: T.Union[raw.FromWorkspace, raw.DependencyV]) -> T.Union[raw.FromWorkspace, raw.Dependency]: + return {'version': depv} if isinstance(depv, str) else depv + - name: Required[str] +def _raw_to_dataclass(raw: T.Mapping[str, object], cls: T.Type[_DI], + msg: str, **kwargs: T.Callable[[T.Any], object]) -> _DI: + """Fixup raw cargo mappings to ones more suitable for python to consume as dataclass. -class LibTarget(_BaseBuildTarget, total=False): + * Replaces any `-` with `_` in the keys. + * Optionally pass values through the functions in kwargs, in order to do + recursive conversions. + * Remove and warn on keys that are coming from cargo, but are unknown to + our representations. + + This is intended to give users the possibility of things proceeding when a + new key is added to Cargo.toml that we don't yet handle, but to still warn + them that things might not work. + + :param data: The raw data to look at + :param cls: The Dataclass derived type that will be created + :param msg: the header for the error message. Usually something like "In N structure". + :return: The original data structure, but with all unknown keys removed. + """ + new_dict = {} + unexpected = set() + fields = {x.name for x in dataclasses.fields(cls)} + for orig_k, v in raw.items(): + k = fixup_meson_varname(orig_k) + if k not in fields: + unexpected.add(orig_k) + continue + if k in kwargs: + v = kwargs[k](v) + new_dict[k] = v + + if unexpected: + mlog.warning(msg, 'has unexpected keys', '"{}".'.format(', '.join(sorted(unexpected))), + _EXTRA_KEYS_WARNING) + return cls(**new_dict) + + +@T.overload +def _inherit_from_workspace(raw: raw.Package, + raw_from_workspace: T.Optional[T.Mapping[str, object]], + msg: str, + **kwargs: T.Callable[[T.Any, T.Any], object]) -> raw.Package: ... + +@T.overload +def _inherit_from_workspace(raw: T.Union[raw.FromWorkspace, raw.Dependency], + raw_from_workspace: T.Optional[T.Mapping[str, object]], + msg: str, + **kwargs: T.Callable[[T.Any, T.Any], object]) -> raw.Dependency: ... + +def _inherit_from_workspace(raw_: T.Union[raw.FromWorkspace, raw.Package, raw.Dependency], # type: ignore[misc] + raw_from_workspace: T.Optional[T.Mapping[str, object]], + msg: str, + **kwargs: T.Callable[[T.Any, T.Any], object]) -> T.Mapping[str, object]: + # allow accesses by non-literal key below + raw = T.cast('T.Mapping[str, object]', raw_) + + if not raw_from_workspace: + if raw.get('workspace', False) or \ + any(isinstance(v, dict) and v.get('workspace', False) for v in raw): + raise MesonException(f'Cargo.toml file requests {msg} from workspace') + + return raw + + result = {k: v for k, v in raw.items() if k != 'workspace'} + for k, v in raw.items(): + if isinstance(v, dict) and v.get('workspace', False): + if k in raw_from_workspace: + result[k] = raw_from_workspace[k] + if k in kwargs: + result[k] = kwargs[k](v, result[k]) + else: + del result[k] + + if raw.get('workspace', False): + for k, v in raw_from_workspace.items(): + if k not in result or k in kwargs: + if k in kwargs: + v = kwargs[k](raw.get(k), v) + result[k] = v + return result + + +@dataclasses.dataclass +class Package: + + """Representation of a Cargo Package entry, with defaults filled in.""" + + name: str + version: str + description: T.Optional[str] = None + resolver: T.Optional[str] = None + authors: T.List[str] = dataclasses.field(default_factory=list) + edition: EDITION = '2015' + rust_version: T.Optional[str] = None + documentation: T.Optional[str] = None + readme: T.Optional[str] = None + homepage: T.Optional[str] = None + repository: T.Optional[str] = None + license: T.Optional[str] = None + license_file: T.Optional[str] = None + keywords: T.List[str] = dataclasses.field(default_factory=list) + categories: T.List[str] = dataclasses.field(default_factory=list) + workspace: T.Optional[str] = None + build: T.Optional[str] = None + links: T.Optional[str] = None + exclude: T.List[str] = dataclasses.field(default_factory=list) + include: T.List[str] = dataclasses.field(default_factory=list) + publish: bool = True + metadata: T.Dict[str, T.Any] = dataclasses.field(default_factory=dict) + default_run: T.Optional[str] = None + autolib: bool = True + autobins: bool = True + autoexamples: bool = True + autotests: bool = True + autobenches: bool = True + + @lazy_property + def api(self) -> str: + return version.api(self.version) + + @classmethod + def from_raw(cls, raw_pkg: raw.Package, workspace: T.Optional[Workspace] = None) -> Self: + raw_ws_pkg = None + if workspace is not None: + raw_ws_pkg = workspace.package + + raw_pkg = _inherit_from_workspace(raw_pkg, raw_ws_pkg, f'Package entry {raw_pkg["name"]}') + return _raw_to_dataclass(raw_pkg, cls, f'Package entry {raw_pkg["name"]}') + +@dataclasses.dataclass +class SystemDependency: + + """ Representation of a Cargo system-deps entry + https://docs.rs/system-deps/latest/system_deps + """ name: str + version: str = '' + optional: bool = False + feature: T.Optional[str] = None + # TODO: convert values to dataclass + feature_overrides: T.Dict[str, T.Dict[str, str]] = dataclasses.field(default_factory=dict) + + @classmethod + def from_raw(cls, name: str, raw: T.Union[T.Dict[str, T.Any], str]) -> SystemDependency: + if isinstance(raw, str): + raw = {'version': raw} + name = raw.get('name', name) + version = raw.get('version', '') + optional = raw.get('optional', False) + feature = raw.get('feature') + # Everything else are overrides when certain features are enabled. + feature_overrides = {k: v for k, v in raw.items() if k not in {'name', 'version', 'optional', 'feature'}} + return cls(name, version, optional, feature, feature_overrides) + + @lazy_property + def meson_version(self) -> T.List[str]: + vers = self.version.split(',') if self.version else [] + result: T.List[str] = [] + for v in vers: + v = v.strip() + if v[0] not in '><=': + v = f'>={v}' + result.append(v) + return result + + def enabled(self, features: T.Set[str]) -> bool: + return self.feature is None or self.feature in features + +@dataclasses.dataclass +class Dependency: + + """Representation of a Cargo Dependency Entry.""" + package: str + version: str = '' + registry: T.Optional[str] = None + git: T.Optional[str] = None + branch: T.Optional[str] = None + rev: T.Optional[str] = None + path: T.Optional[str] = None + optional: bool = False + default_features: bool = True + features: T.List[str] = dataclasses.field(default_factory=list) + + @lazy_property + def meson_version(self) -> T.List[str]: + return version.convert(self.version) + + @lazy_property + def api(self) -> str: + # Extract wanted API version from version constraints. + api = set() + for v in self.meson_version: + if v.startswith(('>=', '==')): + api.add(version.api(v[2:].strip())) + elif v.startswith('='): + api.add(version.api(v[1:].strip())) + if not api: + return '0' + elif len(api) == 1: + return api.pop() + else: + raise MesonException(f'Cannot determine minimum API version from {self.version}.') + + @classmethod + def from_raw_dict(cls, name: str, raw_dep: T.Union[raw.FromWorkspace, raw.Dependency], member_path: str = '', raw_ws_dep: T.Optional[raw.Dependency] = None) -> Dependency: + raw_dep = _inherit_from_workspace(raw_dep, raw_ws_dep, + f'Dependency entry {name}', + path=lambda pkg_path, ws_path: os.path.relpath(ws_path, member_path), + features=lambda pkg_path, ws_path: (pkg_path or []) + (ws_path or [])) + raw_dep.setdefault('package', name) + return _raw_to_dataclass(raw_dep, cls, f'Dependency entry {name}') + + @classmethod + def from_raw(cls, name: str, raw_depv: T.Union[raw.FromWorkspace, raw.DependencyV], member_path: str = '', workspace: T.Optional[Workspace] = None) -> Dependency: + """Create a dependency from a raw cargo dictionary or string""" + raw_ws_dep: T.Optional[raw.Dependency] = None + if workspace is not None: + raw_ws_depv = workspace.dependencies.get(name, {}) + raw_ws_dep = _depv_to_dep(raw_ws_depv) + + raw_dep = _depv_to_dep(raw_depv) + return cls.from_raw_dict(name, raw_dep, member_path, raw_ws_dep) + + +@dataclasses.dataclass +class BuildTarget(T.Generic[_R]): -class _BaseFixedBuildTarget(TypedDict, total=False): + name: str path: str - test: bool - doctest: bool - bench: bool - doc: bool - plugin: bool - harness: bool - edition: EDITION crate_type: T.List[CRATE_TYPE] - required_features: T.List[str] + # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-test-field + # True for lib, bin, test + test: bool = True -class FixedBuildTarget(_BaseFixedBuildTarget, total=False): + # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-doctest-field + # True for lib + doctest: bool = False - name: str + # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-bench-field + # True for lib, bin, benchmark + bench: bool = True -class FixedLibTarget(_BaseFixedBuildTarget, total=False): + # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-doc-field + # True for libraries and binaries + doc: bool = False - name: Required[str] - proc_macro: bool + harness: bool = True + edition: EDITION = '2015' + required_features: T.List[str] = dataclasses.field(default_factory=list) + plugin: bool = False + @classmethod + def from_raw(cls, raw: _R) -> Self: + name = raw.get('name', '<anonymous>') + return _raw_to_dataclass(raw, cls, f'Binary entry {name}') -class Target(TypedDict): +@dataclasses.dataclass +class Library(BuildTarget['raw.LibTarget']): - """Target entry in the Manifest File.""" + """Representation of a Cargo Library Entry.""" - dependencies: T.Dict[str, DependencyV] + doctest: bool = True + doc: bool = True + path: str = os.path.join('src', 'lib.rs') + proc_macro: bool = False + crate_type: T.List[CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['lib']) + doc_scrape_examples: bool = True + @classmethod + def from_raw(cls, raw: raw.LibTarget, fallback_name: str) -> Self: # type: ignore[override] + # We need to set the name field if it's not set manually, including if + # other fields are set in the lib section + raw.setdefault('name', fallback_name) + return _raw_to_dataclass(raw, cls, f'Library entry {raw["name"]}') -class Workspace(TypedDict): - """The representation of a workspace. +@dataclasses.dataclass +class Binary(BuildTarget['raw.BuildTarget']): - In a vritual manifest the :attribute:`members` is always present, but in a - project manifest, an empty workspace may be provided, in which case the - workspace is implicitly filled in by values from the path based dependencies. + """Representation of a Cargo Bin Entry.""" - the :attribute:`exclude` is always optional - """ + doc: bool = True + crate_type: T.List[CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['bin']) - members: T.List[str] - exclude: T.List[str] - - -Manifest = TypedDict( - 'Manifest', - { - 'package': Required[Package], - 'badges': T.Dict[str, Badge], - 'dependencies': T.Dict[str, DependencyV], - 'dev-dependencies': T.Dict[str, DependencyV], - 'build-dependencies': T.Dict[str, DependencyV], - 'lib': LibTarget, - 'bin': T.List[BuildTarget], - 'test': T.List[BuildTarget], - 'bench': T.List[BuildTarget], - 'example': T.List[BuildTarget], - 'features': T.Dict[str, T.List[str]], - 'target': T.Dict[str, Target], - 'workspace': Workspace, - - # TODO: patch? - # TODO: replace? - }, - total=False, -) -"""The Cargo Manifest format.""" + @classmethod + def from_raw(cls, raw: raw.BuildTarget) -> Self: + if 'path' not in raw: + raw['path'] = os.path.join('bin', raw['name'] + '.rs') + return super().from_raw(raw) -class VirtualManifest(TypedDict): +@dataclasses.dataclass +class Test(BuildTarget['raw.BuildTarget']): - """The Representation of a virtual manifest. + """Representation of a Cargo Test Entry.""" - Cargo allows a root manifest that contains only a workspace, this is called - a virtual manifest. This doesn't really map 1:1 with any meson concept, - except perhaps the proposed "meta project". + bench: bool = True + crate_type: T.List[CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['bin']) + + @classmethod + def from_raw(cls, raw: raw.BuildTarget) -> Self: + if 'path' not in raw: + raw['path'] = os.path.join('tests', raw['name'] + '.rs') + return super().from_raw(raw) + +@dataclasses.dataclass +class Benchmark(BuildTarget['raw.BuildTarget']): + + """Representation of a Cargo Benchmark Entry.""" + + test: bool = True + crate_type: T.List[CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['bin']) + + @classmethod + def from_raw(cls, raw: raw.BuildTarget) -> Self: + if 'path' not in raw: + raw['path'] = os.path.join('benches', raw['name'] + '.rs') + return super().from_raw(raw) + + +@dataclasses.dataclass +class Example(BuildTarget['raw.BuildTarget']): + + """Representation of a Cargo Example Entry.""" + + crate_type: T.List[CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['bin']) + + @classmethod + def from_raw(cls, raw: raw.BuildTarget) -> Self: + if 'path' not in raw: + raw['path'] = os.path.join('examples', raw['name'] + '.rs') + return super().from_raw(raw) + + +@dataclasses.dataclass +class Manifest: + + """Cargo Manifest definition. + + Most of these values map up to the Cargo Manifest, but with default values + if not provided. + + Cargo subprojects can contain what Meson wants to treat as multiple, + interdependent, subprojects. + + :param path: the path within the cargo subproject. """ - workspace: Workspace + package: Package + dependencies: T.Dict[str, Dependency] = dataclasses.field(default_factory=dict) + dev_dependencies: T.Dict[str, Dependency] = dataclasses.field(default_factory=dict) + build_dependencies: T.Dict[str, Dependency] = dataclasses.field(default_factory=dict) + lib: T.Optional[Library] = None + bin: T.List[Binary] = dataclasses.field(default_factory=list) + test: T.List[Test] = dataclasses.field(default_factory=list) + bench: T.List[Benchmark] = dataclasses.field(default_factory=list) + example: T.List[Example] = dataclasses.field(default_factory=list) + features: T.Dict[str, T.List[str]] = dataclasses.field(default_factory=dict) + target: T.Dict[str, T.Dict[str, Dependency]] = dataclasses.field(default_factory=dict) + + path: str = '' + + def __post_init__(self) -> None: + self.features.setdefault('default', []) + + @lazy_property + def system_dependencies(self) -> T.Dict[str, SystemDependency]: + return {k: SystemDependency.from_raw(k, v) for k, v in self.package.metadata.get('system-deps', {}).items()} + + @classmethod + def from_raw(cls, raw: raw.Manifest, path: str = '', workspace: T.Optional[Workspace] = None, member_path: str = '') -> Self: + # Libs are always auto-discovered and there's no other way to handle them, + # which is unfortunate for reproducability + pkg = Package.from_raw(raw['package'], workspace) + if pkg.autolib and 'lib' not in raw and \ + os.path.exists(os.path.join(path, 'src/lib.rs')): + raw['lib'] = {} + fixed = _raw_to_dataclass(raw, cls, f'Cargo.toml package {raw["package"]["name"]}', + package=lambda x: pkg, + dependencies=lambda x: {k: Dependency.from_raw(k, v, member_path, workspace) for k, v in x.items()}, + dev_dependencies=lambda x: {k: Dependency.from_raw(k, v, member_path, workspace) for k, v in x.items()}, + build_dependencies=lambda x: {k: Dependency.from_raw(k, v, member_path, workspace) for k, v in x.items()}, + lib=lambda x: Library.from_raw(x, raw['package']['name']), + bin=lambda x: [Binary.from_raw(b) for b in x], + test=lambda x: [Test.from_raw(b) for b in x], + bench=lambda x: [Benchmark.from_raw(b) for b in x], + example=lambda x: [Example.from_raw(b) for b in x], + target=lambda x: {k: {k2: Dependency.from_raw(k2, v2, member_path, workspace) for k2, v2 in v.get('dependencies', {}).items()} + for k, v in x.items()}) + fixed.path = path + return fixed + + +@dataclasses.dataclass +class Workspace: + + """Cargo Workspace definition. + """ + + resolver: str = dataclasses.field(default_factory=lambda: '2') + members: T.List[str] = dataclasses.field(default_factory=list) + exclude: T.List[str] = dataclasses.field(default_factory=list) + default_members: T.List[str] = dataclasses.field(default_factory=list) + + # inheritable settings are kept in raw format, for use with _inherit_from_workspace + package: T.Optional[raw.Package] = None + dependencies: T.Dict[str, raw.Dependency] = dataclasses.field(default_factory=dict) + lints: T.Dict[str, T.Any] = dataclasses.field(default_factory=dict) + metadata: T.Dict[str, T.Any] = dataclasses.field(default_factory=dict) -class CargoLockPackage(TypedDict, total=False): + # A workspace can also have a root package. + root_package: T.Optional[Manifest] = dataclasses.field(init=False) + + @classmethod + def from_raw(cls, raw: raw.VirtualManifest) -> Workspace: + ws_raw = raw['workspace'] + fixed = _raw_to_dataclass(ws_raw, cls, 'Workspace') + return fixed + + +@dataclasses.dataclass +class CargoLockPackage: """A description of a package in the Cargo.lock file format.""" name: str version: str - source: str - checksum: str + source: T.Optional[str] = None + checksum: T.Optional[str] = None + dependencies: T.List[str] = dataclasses.field(default_factory=list) + @classmethod + def from_raw(cls, raw: raw.CargoLockPackage) -> CargoLockPackage: + return _raw_to_dataclass(raw, cls, 'Cargo.lock package') -class CargoLock(TypedDict, total=False): + +@dataclasses.dataclass +class CargoLock: """A description of the Cargo.lock file format.""" - version: str - package: T.List[CargoLockPackage] - metadata: T.Dict[str, str] + version: int = 1 + package: T.List[CargoLockPackage] = dataclasses.field(default_factory=list) + metadata: T.Dict[str, str] = dataclasses.field(default_factory=dict) + + @classmethod + def from_raw(cls, raw: raw.CargoLock) -> CargoLock: + return _raw_to_dataclass(raw, cls, 'Cargo.lock', + package=lambda x: [CargoLockPackage.from_raw(p) for p in x]) diff --git a/mesonbuild/cargo/raw.py b/mesonbuild/cargo/raw.py new file mode 100644 index 0000000..67dd58a --- /dev/null +++ b/mesonbuild/cargo/raw.py @@ -0,0 +1,192 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2022-2024 Intel Corporation + +"""Type definitions for cargo manifest files.""" + +from __future__ import annotations +import typing as T + +from typing_extensions import Literal, TypedDict, Required + +EDITION = Literal['2015', '2018', '2021'] +CRATE_TYPE = Literal['bin', 'lib', 'dylib', 'staticlib', 'cdylib', 'rlib', 'proc-macro'] + + +class FromWorkspace(TypedDict): + + """An entry or section that is copied from the workspace.""" + + workspace: bool + + +Package = TypedDict( + 'Package', + { + 'name': Required[str], + 'version': Required[T.Union[FromWorkspace, str]], + 'authors': T.Union[FromWorkspace, T.List[str]], + 'edition': T.Union[FromWorkspace, EDITION], + 'rust-version': T.Union[FromWorkspace, str], + 'description': T.Union[FromWorkspace, str], + 'readme': T.Union[FromWorkspace, str], + 'license': T.Union[FromWorkspace, str], + 'license-file': T.Union[FromWorkspace, str], + 'keywords': T.Union[FromWorkspace, T.List[str]], + 'categories': T.Union[FromWorkspace, T.List[str]], + 'homepage': T.Union[FromWorkspace, str], + 'repository': T.Union[FromWorkspace, str], + 'documentation': T.Union[FromWorkspace, str], + 'workspace': str, + 'build': str, + 'links': str, + 'include': T.Union[FromWorkspace, T.List[str]], + 'exclude': T.Union[FromWorkspace, T.List[str]], + 'publish': T.Union[FromWorkspace, bool], + 'metadata': T.Dict[str, T.Dict[str, str]], + 'default-run': str, + 'autolib': bool, + 'autobins': bool, + 'autoexamples': bool, + 'autotests': bool, + 'autobenches': bool, + }, + total=False, +) +"""A description of the Package Dictionary.""" + +class Badge(TypedDict): + + """An entry in the badge section.""" + + status: Literal['actively-developed', 'passively-developed', 'as-is', 'experimental', 'deprecated', 'none'] + + +Dependency = TypedDict( + 'Dependency', + { + 'version': str, + 'registry': str, + 'git': str, + 'branch': str, + 'rev': str, + 'path': str, + 'optional': bool, + 'package': str, + 'default-features': bool, + 'features': T.List[str], + }, + total=False, +) +"""An entry in the *dependencies sections.""" + + +DependencyV = T.Union[Dependency, str] +"""A Dependency entry, either a string or a Dependency Dict.""" + + +_BaseBuildTarget = TypedDict( + '_BaseBuildTarget', + { + 'path': str, + 'test': bool, + 'doctest': bool, + 'bench': bool, + 'doc': bool, + 'plugin': bool, + 'proc-macro': bool, + 'harness': bool, + 'edition': EDITION, + 'crate-type': T.List[CRATE_TYPE], + 'required-features': T.List[str], + }, + total=False, +) + + +class BuildTarget(_BaseBuildTarget, total=False): + + name: Required[str] + + +class LibTarget(_BaseBuildTarget, total=False): + + name: str + + +class Target(TypedDict): + + """Target entry in the Manifest File.""" + + dependencies: T.Dict[str, T.Union[FromWorkspace, DependencyV]] + + +class Workspace(TypedDict): + + """The representation of a workspace. + + In a vritual manifest the :attribute:`members` is always present, but in a + project manifest, an empty workspace may be provided, in which case the + workspace is implicitly filled in by values from the path based dependencies. + + the :attribute:`exclude` is always optional + """ + + members: T.List[str] + exclude: T.List[str] + package: Package + dependencies: T.Dict[str, DependencyV] + + +Manifest = TypedDict( + 'Manifest', + { + 'package': Required[Package], + 'badges': T.Dict[str, Badge], + 'dependencies': T.Dict[str, T.Union[FromWorkspace, DependencyV]], + 'dev-dependencies': T.Dict[str, T.Union[FromWorkspace, DependencyV]], + 'build-dependencies': T.Dict[str, T.Union[FromWorkspace, DependencyV]], + 'lib': LibTarget, + 'bin': T.List[BuildTarget], + 'test': T.List[BuildTarget], + 'bench': T.List[BuildTarget], + 'example': T.List[BuildTarget], + 'features': T.Dict[str, T.List[str]], + 'target': T.Dict[str, Target], + 'workspace': Workspace, + + # TODO: patch? + # TODO: replace? + }, + total=False, +) +"""The Cargo Manifest format.""" + + +class VirtualManifest(TypedDict, total=False): + + """The Representation of a virtual manifest. + + Cargo allows a root manifest that contains only a workspace, this is called + a virtual manifest. This doesn't really map 1:1 with any meson concept, + except perhaps the proposed "meta project". + """ + + workspace: Workspace + +class CargoLockPackage(TypedDict, total=False): + + """A description of a package in the Cargo.lock file format.""" + + name: str + version: str + source: str + checksum: str + + +class CargoLock(TypedDict, total=False): + + """A description of the Cargo.lock file format.""" + + version: int + package: T.List[CargoLockPackage] + metadata: T.Dict[str, str] diff --git a/mesonbuild/cargo/toml.py b/mesonbuild/cargo/toml.py new file mode 100644 index 0000000..601510e --- /dev/null +++ b/mesonbuild/cargo/toml.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +import importlib +import shutil +import json +import typing as T + +from ..mesonlib import MesonException, Popen_safe +if T.TYPE_CHECKING: + from types import ModuleType + + +# tomllib is present in python 3.11, before that it is a pypi module called tomli, +# we try to import tomllib, then tomli, +tomllib: T.Optional[ModuleType] = None +toml2json: T.Optional[str] = None +for t in ['tomllib', 'tomli']: + try: + tomllib = importlib.import_module(t) + break + except ImportError: + pass +else: + # TODO: it would be better to use an Executable here, which could be looked + # up in the cross file or provided by a wrap. However, that will have to be + # passed in externally, since we don't have (and I don't think we should), + # have access to the `Environment` for that in this module. + toml2json = shutil.which('toml2json') + +class TomlImplementationMissing(MesonException): + pass + + +def load_toml(filename: str) -> T.Dict[str, object]: + if tomllib: + with open(filename, 'rb') as f: + raw = tomllib.load(f) + else: + if toml2json is None: + raise TomlImplementationMissing('Could not find an implementation of tomllib, nor toml2json') + + p, out, err = Popen_safe([toml2json, filename]) + if p.returncode != 0: + raise MesonException('toml2json failed to decode output\n', err) + + raw = json.loads(out) + + # tomllib.load() returns T.Dict[str, T.Any] but not other implementations. + return T.cast('T.Dict[str, object]', raw) diff --git a/mesonbuild/cargo/version.py b/mesonbuild/cargo/version.py index cde7a83..ce58945 100644 --- a/mesonbuild/cargo/version.py +++ b/mesonbuild/cargo/version.py @@ -7,6 +7,18 @@ from __future__ import annotations import typing as T +def api(version: str) -> str: + # x.y.z -> x + # 0.x.y -> 0.x + # 0.0.x -> 0 + vers = version.split('.') + if int(vers[0]) != 0: + return vers[0] + elif len(vers) >= 2 and int(vers[1]) != 0: + return f'0.{vers[1]}' + return '0' + + def convert(cargo_ver: str) -> T.List[str]: """Convert a Cargo compatible version into a Meson compatible one. @@ -15,6 +27,8 @@ def convert(cargo_ver: str) -> T.List[str]: """ # Cleanup, just for safety cargo_ver = cargo_ver.strip() + if not cargo_ver: + return [] cargo_vers = [c.strip() for c in cargo_ver.split(',')] out: T.List[str] = [] diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 609038d..c68cb60 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -125,7 +125,7 @@ TRANSFER_DEPENDENCIES_FROM: T.Collection[str] = ['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) - if name in FORBIDDEN_TARGET_NAMES or name.startswith('meson'): + if name in FORBIDDEN_TARGET_NAMES or name.startswith('meson') or name[0].isdigit(): name = 'cm_' + name return name diff --git a/mesonbuild/cmake/tracetargets.py b/mesonbuild/cmake/tracetargets.py index 9873845..2b2b93d 100644 --- a/mesonbuild/cmake/tracetargets.py +++ b/mesonbuild/cmake/tracetargets.py @@ -87,6 +87,7 @@ def resolve_cmake_trace_targets(target_name: str, curr_path = Path(*path_to_framework) framework_path = curr_path.parent framework_name = curr_path.stem + res.public_compile_opts += [f"-F{framework_path}"] res.libraries += [f'-F{framework_path}', '-framework', framework_name] else: res.libraries += [curr] diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py index aab761a..f645090 100644 --- a/mesonbuild/compilers/__init__.py +++ b/mesonbuild/compilers/__init__.py @@ -18,6 +18,7 @@ __all__ = [ 'is_library', 'is_llvm_ir', 'is_object', + 'is_separate_compile', 'is_source', 'is_java', 'is_known_suffix', @@ -62,6 +63,7 @@ from .compilers import ( is_object, is_library, is_known_suffix, + is_separate_compile, lang_suffixes, LANGUAGES_USING_LDFLAGS, sort_clink, diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 7a2fec5..424b612 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -504,7 +504,7 @@ class IntelClCCompiler(IntelVisualStudioLikeCompiler, VisualStudioLikeCCompilerM def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('winlibs', env, target, subproject) + std = self.get_compileropt_value('std', env, target, subproject) assert isinstance(std, str) if std == 'c89': mlog.log("ICL doesn't explicitly implement c89, setting the standard to 'none', which is close.", once=True) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 0376922..a823aeb 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -42,7 +42,7 @@ _T = T.TypeVar('_T') about. To support a new compiler, add its information below. Also add corresponding autodetection code in detect.py.""" -header_suffixes = {'h', 'hh', 'hpp', 'hxx', 'H', 'ipp', 'moc', 'vapi', 'di'} +header_suffixes = {'h', 'hh', 'hpp', 'hxx', 'H', 'ipp', 'moc', 'vapi', 'di', 'pxd', 'pxi'} obj_suffixes = {'o', 'obj', 'res'} # To the emscripten compiler, .js files are libraries lib_suffixes = {'a', 'lib', 'dll', 'dll.a', 'dylib', 'so', 'js'} @@ -84,7 +84,7 @@ clib_langs = ('objcpp', 'cpp', 'objc', 'c', 'nasm', 'fortran') # List of languages that can be linked with C code directly by the linker # used in build.py:process_compilers() and build.py:get_dynamic_linker() # This must be sorted, see sort_clink(). -clink_langs = ('d', 'cuda') + clib_langs +clink_langs = ('rust', 'd', 'cuda') + clib_langs SUFFIX_TO_LANG = dict(itertools.chain(*( [(suffix, lang) for suffix in v] for lang, v in lang_suffixes.items()))) @@ -154,6 +154,9 @@ def is_java(fname: mesonlib.FileOrString) -> bool: suffix = fname.split('.')[-1] return suffix in lang_suffixes['java'] +def is_separate_compile(fname: mesonlib.FileOrString) -> bool: + return not fname.endswith('.rs') + def is_llvm_ir(fname: 'mesonlib.FileOrString') -> bool: if isinstance(fname, mesonlib.File): fname = fname.fname @@ -933,11 +936,10 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): """ return None - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: return self.linker.build_rpath_args( - env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + env, build_dir, from_dir, target, extra_paths) def get_archive_name(self, filename: str) -> str: return self.linker.get_archive_name(filename) @@ -1119,9 +1121,6 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): def get_compile_only_args(self) -> T.List[str]: return [] - def get_cxx_interoperability_args(self, lang: T.Dict[str, Compiler]) -> T.List[str]: - raise EnvironmentException('This compiler does not support CXX interoperability') - def get_preprocess_only_args(self) -> T.List[str]: raise EnvironmentException('This compiler does not have a preprocessor') @@ -1417,50 +1416,3 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): if 'none' not in value: value = ['none'] + value std.choices = value - - -def get_global_options(lang: str, - comp: T.Type[Compiler], - for_machine: MachineChoice, - env: 'Environment') -> dict[OptionKey, options.AnyOptionType]: - """Retrieve options that apply to all compilers for a given language.""" - description = f'Extra arguments passed to the {lang}' - argkey = OptionKey(f'{lang}_args', machine=for_machine) - largkey = OptionKey(f'{lang}_link_args', machine=for_machine) - - comp_args_from_envvar = False - comp_options = env.coredata.optstore.get_pending_value(argkey) - if comp_options is None: - comp_args_from_envvar = True - comp_options = env.env_opts.get(argkey, []) - - link_args_from_envvar = False - link_options = env.coredata.optstore.get_pending_value(largkey) - if link_options is None: - link_args_from_envvar = True - link_options = env.env_opts.get(largkey, []) - - assert isinstance(comp_options, (str, list)), 'for mypy' - assert isinstance(link_options, (str, list)), 'for mypy' - - cargs = options.UserStringArrayOption( - argkey.name, - description + ' compiler', - comp_options, split_args=True, allow_dups=True) - - largs = options.UserStringArrayOption( - largkey.name, - description + ' linker', - link_options, split_args=True, allow_dups=True) - - if comp.INVOKES_LINKER and comp_args_from_envvar and link_args_from_envvar: - # If the compiler acts as a linker driver, and we're using the - # environment variable flags for both the compiler and linker - # arguments, then put the compiler flags in the linker flags as well. - # This is how autotools works, and the env vars feature is for - # autotools compatibility. - largs.extend_value(comp_options) - - opts: dict[OptionKey, options.AnyOptionType] = {argkey: cargs, largkey: largs} - - return opts diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index fd747d1..7e050f1 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -198,6 +198,7 @@ class CudaCompiler(Compiler): for level, flags in host_compiler.warn_args.items() } self.host_werror_args = ['-Xcompiler=' + x for x in self.host_compiler.get_werror_args()] + self.debug_macros_available = version_compare(self.version, '>=12.9') @classmethod def _shield_nvcc_list_arg(cls, arg: str, listmode: bool = True) -> str: @@ -730,11 +731,10 @@ class CudaCompiler(Compiler): def get_optimization_link_args(self, optimization_level: str) -> T.List[str]: return self._to_host_flags(self.host_compiler.get_optimization_link_args(optimization_level), Phase.LINKER) - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: (rpath_args, rpath_dirs_to_remove) = self.host_compiler.build_rpath_args( - env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + env, build_dir, from_dir, target, extra_paths) return (self._to_host_flags(rpath_args, Phase.LINKER), rpath_dirs_to_remove) def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]: @@ -808,7 +808,12 @@ class CudaCompiler(Compiler): return ['-Xcompiler=' + x for x in self.host_compiler.get_profile_use_args()] def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]: - return self.host_compiler.get_assert_args(disable, env) + cccl_macros = [] + if not disable and self.debug_macros_available: + # https://github.com/NVIDIA/cccl/pull/2382 + cccl_macros = ['-DCCCL_ENABLE_ASSERTIONS=1'] + + return self.host_compiler.get_assert_args(disable, env) + cccl_macros def has_multi_arguments(self, args: T.List[str], env: Environment) -> T.Tuple[bool, bool]: args = self._to_host_flags(args) diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py index 51f2436..9f662ad 100644 --- a/mesonbuild/compilers/d.py +++ b/mesonbuild/compilers/d.py @@ -26,7 +26,7 @@ from .mixins.gnu import gnu_common_warning_args if T.TYPE_CHECKING: from . import compilers - from ..build import DFeatures + from ..build import BuildTarget, DFeatures from ..dependencies import Dependency from ..envconfig import MachineInfo from ..environment import Environment @@ -175,9 +175,8 @@ class DmdLikeCompilerMixin(CompilerMixinBase): def gen_import_library_args(self, implibname: str) -> T.List[str]: return self.linker.import_library_args(implibname) - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: if self.info.is_windows(): return ([], set()) @@ -188,7 +187,7 @@ class DmdLikeCompilerMixin(CompilerMixinBase): # split into two separate arguments both prefaced with the -L=. args: T.List[str] = [] (rpath_args, rpath_dirs_to_remove) = super().build_rpath_args( - env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + env, build_dir, from_dir, target) for r in rpath_args: if ',' in r: a, b = r.split(',', maxsplit=1) @@ -199,7 +198,7 @@ class DmdLikeCompilerMixin(CompilerMixinBase): return (args, rpath_dirs_to_remove) return super().build_rpath_args( - env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + env, build_dir, from_dir, target) @classmethod def _translate_args_to_nongnu(cls, args: T.List[str], info: MachineInfo, link_id: str) -> T.List[str]: diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py index 040c42f..f57957f 100644 --- a/mesonbuild/compilers/detect.py +++ b/mesonbuild/compilers/detect.py @@ -366,7 +366,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin if 'Emscripten' in out: cls = c.EmscriptenCCompiler if lang == 'c' else cpp.EmscriptenCPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) # emcc requires a file input in order to pass arguments to the # linker. It'll exit with an error code, but still print the @@ -410,7 +410,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin full_version = arm_ver_str cls = c.ArmclangCCompiler if lang == 'c' else cpp.ArmclangCPPCompiler linker = linkers.ArmClangDynamicLinker(for_machine, version=version) - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) return cls( ccache, compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) @@ -445,7 +445,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin if identifier in out: cls = compiler_classes[0] if lang == 'c' else compiler_classes[1] lnk = compiler_classes[2] - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = lnk(compiler, for_machine, version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, @@ -482,7 +482,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin version = search_version(err) target = 'x86' if 'IA-32' in err else 'x86_64' cls = c.IntelClCCompiler if lang == 'c' else cpp.IntelClCPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) return cls( compiler, version, for_machine, is_cross, info, target, @@ -491,7 +491,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin version = search_version(err) target = 'x86' if 'IA-32' in err else 'x86_64' cls = c.IntelLLVMClCCompiler if lang == 'c' else cpp.IntelLLVMClCPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) return cls( compiler, version, for_machine, is_cross, info, target, @@ -524,14 +524,14 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin full_version=cl_signature, linker=linker) if 'PGI Compilers' in out: cls = c.PGICCompiler if lang == 'c' else cpp.PGICPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.PGIDynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, linker=linker) if 'NVIDIA Compilers and Tools' in out: cls = c.NvidiaHPC_CCompiler if lang == 'c' else cpp.NvidiaHPC_CPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.NvidiaHPC_DynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( ccache, compiler, version, for_machine, is_cross, @@ -550,14 +550,14 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin full_version=full_version, linker=l) if 'ARM' in out and not ('Metrowerks' in out or 'Freescale' in out): cls = c.ArmCCompiler if lang == 'c' else cpp.ArmCPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.ArmDynamicLinker(for_machine, version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, full_version=full_version, linker=linker) if 'RX Family' in out: cls = c.CcrxCCompiler if lang == 'c' else cpp.CcrxCPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.CcrxDynamicLinker(for_machine, version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, @@ -565,7 +565,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin if 'Microchip Technology' in out: cls = c.Xc16CCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.Xc16DynamicLinker(for_machine, version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, @@ -573,7 +573,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin if 'CompCert' in out: cls = c.CompCertCCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.CompCertDynamicLinker(for_machine, version=version) return cls( ccache, compiler, version, for_machine, is_cross, info, @@ -591,7 +591,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin assert mwcc_ver_match is not None, 'for mypy' # because mypy *should* be complaining that this could be None compiler_version = '.'.join(x for x in mwcc_ver_match.groups() if x is not None) - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) ld = env.lookup_binary_entry(for_machine, cls.language + '_ld') if ld is not None: @@ -616,7 +616,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin assert tasking_ver_match is not None, 'for mypy' tasking_version = '.'.join(x for x in tasking_ver_match.groups() if x is not None) - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) ld = env.lookup_binary_entry(for_machine, cls.language + '_ld') if ld is None: raise MesonException(f'{cls.language}_ld was not properly defined in your cross file') @@ -668,7 +668,7 @@ def detect_cuda_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp version = out.strip().rsplit('V', maxsplit=1)[-1] cpp_compiler = detect_cpp_compiler(env, for_machine) cls = CudaCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) key = OptionKey('cuda_link_args', machine=for_machine) if key in env.options: # To fix LDFLAGS issue @@ -759,7 +759,7 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C version = search_version(err) target = 'x86' if 'IA-32' in err else 'x86_64' cls = fortran.IntelLLVMClFortranCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) return cls( compiler, version, for_machine, is_cross, info, @@ -769,7 +769,7 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C version = search_version(err) target = 'x86' if 'IA-32' in err else 'x86_64' cls = fortran.IntelClFortranCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) return cls( compiler, version, for_machine, is_cross, info, @@ -796,7 +796,7 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C if 'PGI Compilers' in out: cls = fortran.PGIFortranCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.PGIDynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( @@ -805,7 +805,7 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C if 'NVIDIA Compilers and Tools' in out: cls = fortran.NvidiaHPC_FortranCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.PGIDynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( @@ -856,7 +856,7 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C full_version = err.split('\n', 1)[0] version = full_version.split()[-1] cls = fortran.NAGFortranCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.NAGDynamicLinker( compiler, for_machine, cls.LINKER_PREFIX, [], version=version) @@ -948,7 +948,7 @@ def detect_java_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp if len(parts) > 1: version = parts[1] comp_class = JavaCompiler - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + env.add_lang_args(comp_class.language, comp_class, for_machine) return comp_class(exelist, version, for_machine, info) raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) @@ -972,7 +972,7 @@ def detect_cs_compiler(env: 'Environment', for_machine: MachineChoice) -> Compil cls = cs.VisualStudioCsCompiler else: continue - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) return cls(comp, version, for_machine, info) _handle_exceptions(popen_exceptions, compilers) @@ -1002,7 +1002,7 @@ def detect_cython_compiler(env: 'Environment', for_machine: MachineChoice) -> Co version = search_version(err) if version is not None: comp_class = CythonCompiler - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + env.add_lang_args(comp_class.language, comp_class, for_machine) return comp_class([], comp, version, for_machine, info, is_cross=is_cross) _handle_exceptions(popen_exceptions, compilers) raise EnvironmentException('Unreachable code (exception to make mypy happy)') @@ -1023,7 +1023,7 @@ def detect_vala_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp version = search_version(out) if 'Vala' in out: comp_class = ValaCompiler - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + env.add_lang_args(comp_class.language, comp_class, for_machine) return comp_class(exelist, version, for_machine, is_cross, info) raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) @@ -1145,7 +1145,7 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust c = linker.exelist[1] if linker.exelist[0].endswith('ccache') else linker.exelist[0] compiler.extend(cls.use_linker_args(c, '')) - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) return cls( compiler, version, for_machine, is_cross, info, linker=linker, full_version=full_version) @@ -1329,20 +1329,20 @@ def detect_nasm_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp version = search_version(output) if 'NASM' in output: comp_class = NasmCompiler - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + env.add_lang_args(comp_class.language, comp_class, for_machine) return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) elif 'yasm' in output: comp_class = YasmCompiler - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + env.add_lang_args(comp_class.language, comp_class, for_machine) return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) elif 'Metrowerks' in output or 'Freescale' in output: if 'ARM' in output: comp_class_mwasmarm = MetrowerksAsmCompilerARM - env.coredata.add_lang_args(comp_class_mwasmarm.language, comp_class_mwasmarm, for_machine, env) + env.add_lang_args(comp_class_mwasmarm.language, comp_class_mwasmarm, for_machine) return comp_class_mwasmarm([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) else: comp_class_mwasmeppc = MetrowerksAsmCompilerEmbeddedPowerPC - env.coredata.add_lang_args(comp_class_mwasmeppc.language, comp_class_mwasmeppc, for_machine, env) + env.add_lang_args(comp_class_mwasmeppc.language, comp_class_mwasmeppc, for_machine) return comp_class_mwasmeppc([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) _handle_exceptions(popen_exceptions, compilers) @@ -1383,7 +1383,7 @@ def detect_masm_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp try: output = Popen_safe(comp + [arg])[2] version = search_version(output) - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + env.add_lang_args(comp_class.language, comp_class, for_machine) return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) except OSError as e: popen_exceptions[' '.join(comp + [arg])] = e @@ -1403,7 +1403,7 @@ def detect_linearasm_compiler(env: Environment, for_machine: MachineChoice) -> C try: output = Popen_safe(comp + [arg])[2] version = search_version(output) - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + env.add_lang_args(comp_class.language, comp_class, for_machine) return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) except OSError as e: popen_exceptions[' '.join(comp + [arg])] = e diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index 1c875a3..d2eb611 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py @@ -1272,12 +1272,19 @@ class CLikeCompiler(Compiler): # check the equivalent enable flag too "-Wforgotten-towel". if arg.startswith('-Wno-'): # Make an exception for -Wno-attributes=x as -Wattributes=x is invalid - # for GCC at least. Also, the opposite of -Wno-vla-larger-than is - # -Wvla-larger-than=N + # for GCC at least. Also, the positive form of some flags require a + # value to be specified, i.e. we need to pass -Wfoo=N rather than just + # -Wfoo. if arg.startswith('-Wno-attributes='): pass - elif arg == '-Wno-vla-larger-than': - new_args.append('-Wvla-larger-than=1000') + elif arg in {'-Wno-alloc-size-larger-than', + '-Wno-alloca-larger-than', + '-Wno-frame-larger-than', + '-Wno-stack-usage', + '-Wno-vla-larger-than'}: + # Pass an arbitrary value to the enabling flag; since the test program + # is trivial, it is unlikely to provoke any of these warnings. + new_args.append('-W' + arg[5:] + '=1000') else: new_args.append('-W' + arg[5:]) if arg.startswith('-Wl,'): diff --git a/mesonbuild/compilers/mixins/islinker.py b/mesonbuild/compilers/mixins/islinker.py index 3f35619..e359fb3 100644 --- a/mesonbuild/compilers/mixins/islinker.py +++ b/mesonbuild/compilers/mixins/islinker.py @@ -101,9 +101,8 @@ class BasicLinkerIsCompilerMixin(Compiler): darwin_versions: T.Tuple[str, str]) -> T.List[str]: raise MesonException("This linker doesn't support soname args") - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) def get_asneeded_args(self) -> T.List[str]: diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index 5ebb093..bc27779 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -197,18 +197,15 @@ class RustCompiler(Compiler): def get_optimization_args(self, optimization_level: str) -> T.List[str]: return rust_optimization_args[optimization_level] - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: - args, to_remove = super().build_rpath_args(env, build_dir, from_dir, rpath_paths, - build_rpath, install_rpath) - - # ... but then add rustc's sysroot to account for rustup - # installations + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: + # add rustc's sysroot to account for rustup installations + args, to_remove = super().build_rpath_args(env, build_dir, from_dir, target, [self.get_target_libdir()]) + rustc_rpath_args = [] for arg in args: rustc_rpath_args.append('-C') - rustc_rpath_args.append(f'link-arg={arg}:{self.get_target_libdir()}') + rustc_rpath_args.append(f'link-arg={arg}') return rustc_rpath_args, to_remove def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], @@ -241,6 +238,12 @@ class RustCompiler(Compiler): 'none', choices=['none', '2015', '2018', '2021', '2024']) + key = self.form_compileropt_key('dynamic_std') + opts[key] = options.UserBooleanOption( + self.make_option_name(key), + 'Whether to link Rust build targets to a dynamic libstd', + False) + return opts def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]: @@ -335,7 +338,7 @@ class RustCompiler(Compiler): return RustdocTestCompiler(exelist, self.version, self.for_machine, self.is_cross, self.info, full_version=self.full_version, - linker=self.linker) + linker=self.linker, rustc=self) class ClippyRustCompiler(RustCompiler): @@ -355,6 +358,26 @@ class RustdocTestCompiler(RustCompiler): id = 'rustdoc --test' + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + full_version: T.Optional[str], + linker: T.Optional['DynamicLinker'], rustc: RustCompiler): + super().__init__(exelist, version, for_machine, + is_cross, info, full_version, linker) + self.rustc = rustc + + @functools.lru_cache(maxsize=None) + def get_sysroot(self) -> str: + return self.rustc.get_sysroot() + + @functools.lru_cache(maxsize=None) + def get_target_libdir(self) -> str: + return self.rustc.get_target_libdir() + + @functools.lru_cache(maxsize=None) + def get_cfgs(self) -> T.List[str]: + return self.rustc.get_cfgs() + def get_debug_args(self, is_debug: bool) -> T.List[str]: return [] diff --git a/mesonbuild/compilers/swift.py b/mesonbuild/compilers/swift.py index 47d254b..4ad3aff 100644 --- a/mesonbuild/compilers/swift.py +++ b/mesonbuild/compilers/swift.py @@ -140,7 +140,11 @@ class SwiftCompiler(Compiler): args += ['-swift-version', std] # Pass C compiler -std=... arg to swiftc - c_lang = first(['objc', 'c'], lambda x: x in target.compilers) + c_langs = ['objc', 'c'] + if target.uses_swift_cpp_interop(): + c_langs = ['objcpp', 'cpp', *c_langs] + + c_lang = first(c_langs, lambda x: x in target.compilers) if c_lang is not None: cc = target.compilers[c_lang] args.extend(arg for c_arg in cc.get_option_std_args(target, env, subproject) for arg in ['-Xcc', c_arg]) @@ -153,11 +157,17 @@ class SwiftCompiler(Compiler): return ['-working-directory', path] - def get_cxx_interoperability_args(self, lang: T.Dict[str, Compiler]) -> T.List[str]: - if 'cpp' in lang or 'objcpp' in lang: - return ['-cxx-interoperability-mode=default'] - else: - return ['-cxx-interoperability-mode=off'] + def get_cxx_interoperability_args(self, target: T.Optional[build.BuildTarget] = None) -> T.List[str]: + if target is not None and not target.uses_swift_cpp_interop(): + return [] + + if version_compare(self.version, '<5.9'): + raise MesonException(f'Compiler {self} does not support C++ interoperability') + + return ['-cxx-interoperability-mode=default'] + + def get_library_args(self) -> T.List[str]: + return ['-parse-as-library'] def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 26ef1b8..27795b0 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -7,6 +7,7 @@ from __future__ import annotations import copy from . import mlog, options +import argparse import pickle, os, uuid import sys from functools import lru_cache @@ -18,7 +19,6 @@ from .mesonlib import ( MesonException, MachineChoice, PerMachine, PerMachineDefaultable, default_prefix, - stringlistify, pickle_load ) @@ -32,13 +32,11 @@ import shlex import typing as T if T.TYPE_CHECKING: - import argparse from typing_extensions import Protocol from . import dependencies from .compilers.compilers import Compiler, CompileResult, RunResult, CompileCheckMode from .dependencies.detect import TV_DepID - from .environment import Environment from .mesonlib import FileOrString from .cmake.traceparser import CMakeCacheEntry from .interpreterbase import SubProject @@ -50,13 +48,11 @@ if T.TYPE_CHECKING: """Representation of command line options from Meson setup, configure, and dist. - :param projectoptions: The raw list of command line options given :param cmd_line_options: command line options parsed into an OptionKey: str mapping """ - cmd_line_options: T.Dict[OptionKey, str] - projectoptions: T.List[str] + cmd_line_options: T.Dict[OptionKey, T.Optional[str]] cross_file: T.List[str] native_file: T.List[str] @@ -72,7 +68,7 @@ if T.TYPE_CHECKING: # # Pip requires that RCs are named like this: '0.1.0.rc1' # But the corresponding Git tag needs to be '0.1.0rc1' -version = '1.8.99' +version = '1.9.0.rc1' # The next stable version when we are in dev. This is used to allow projects to # require meson version >=1.2.0 when using 1.1.99. FeatureNew won't warn when @@ -147,13 +143,13 @@ class DependencyCache: def __init__(self, builtins: options.OptionStore, for_machine: MachineChoice): self.__cache: T.MutableMapping[TV_DepID, DependencySubCache] = OrderedDict() self.__builtins = builtins - self.__pkg_conf_key = options.OptionKey('pkg_config_path') - self.__cmake_key = options.OptionKey('cmake_prefix_path') + self.__pkg_conf_key = options.OptionKey('pkg_config_path', machine=for_machine) + self.__cmake_key = options.OptionKey('cmake_prefix_path', machine=for_machine) def __calculate_subkey(self, type_: DependencyCacheType) -> T.Tuple[str, ...]: data: T.Dict[DependencyCacheType, T.List[str]] = { - DependencyCacheType.PKG_CONFIG: stringlistify(self.__builtins.get_value_for(self.__pkg_conf_key)), - DependencyCacheType.CMAKE: stringlistify(self.__builtins.get_value_for(self.__cmake_key)), + DependencyCacheType.PKG_CONFIG: T.cast('T.List[str]', self.__builtins.get_value_for(self.__pkg_conf_key)), + DependencyCacheType.CMAKE: T.cast('T.List[str]', self.__builtins.get_value_for(self.__cmake_key)), DependencyCacheType.OTHER: [], } assert type_ in data, 'Someone forgot to update subkey calculations for a new type' @@ -414,11 +410,7 @@ class CoreData: return value def set_from_configure_command(self, options: SharedCMDOptions) -> bool: - unset_opts = getattr(options, 'unset_opts', []) - all_D = options.projectoptions[:] - for key, valstr in options.cmd_line_options.items(): - all_D.append(f'{key!s}={valstr}') - return self.optstore.set_from_configure_command(all_D, unset_opts) + return self.optstore.set_from_configure_command(options.cmd_line_options) def set_option(self, key: OptionKey, value, first_invocation: bool = False) -> bool: dirty = False @@ -584,16 +576,6 @@ class CoreData: else: self.optstore.add_compiler_option(lang, k, o) - def add_lang_args(self, lang: str, comp: T.Type['Compiler'], - for_machine: MachineChoice, env: 'Environment') -> None: - """Add global language arguments that are needed before compiler/linker detection.""" - from .compilers import compilers - # These options are all new at this point, because the compiler is - # responsible for adding its own options, thus calling - # `self.optstore.update()`` is perfectly safe. - for gopt_key, gopt_valobj in compilers.get_global_options(lang, comp, for_machine, env).items(): - self.optstore.add_compiler_option(lang, gopt_key, gopt_valobj) - def process_compiler_options(self, lang: str, comp: Compiler, subproject: str) -> None: self.add_compiler_options(comp.get_options(), lang, comp.for_machine) @@ -699,25 +681,60 @@ def save(obj: CoreData, build_dir: str) -> str: return filename +class KeyNoneAction(argparse.Action): + """ + Custom argparse Action that stores values in a dictionary as keys with value None. + """ + + def __init__(self, option_strings, dest, nargs=None, **kwargs: object) -> None: + assert nargs is None or nargs == 1 + super().__init__(option_strings, dest, nargs=1, **kwargs) + + def __call__(self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, + arg: T.List[str], option_string: str = None) -> None: + current_dict = getattr(namespace, self.dest) + if current_dict is None: + current_dict = {} + setattr(namespace, self.dest, current_dict) + + key = OptionKey.from_string(arg[0]) + current_dict[key] = None + + +class KeyValueAction(argparse.Action): + """ + Custom argparse Action that parses KEY=VAL arguments and stores them in a dictionary. + """ + + def __init__(self, option_strings, dest, nargs=None, **kwargs: object) -> None: + assert nargs is None or nargs == 1 + super().__init__(option_strings, dest, nargs=1, **kwargs) + + def __call__(self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, + arg: T.List[str], option_string: str = None) -> None: + current_dict = getattr(namespace, self.dest) + if current_dict is None: + current_dict = {} + setattr(namespace, self.dest, current_dict) + + try: + keystr, value = arg[0].split('=', 1) + key = OptionKey.from_string(keystr) + current_dict[key] = value + except ValueError: + parser.error(f'The argument for option {option_string!r} must be in OPTION=VALUE format.') + + def register_builtin_arguments(parser: argparse.ArgumentParser) -> None: for n, b in options.BUILTIN_OPTIONS.items(): options.option_to_argparse(b, n, parser, '') for n, b in options.BUILTIN_OPTIONS_PER_MACHINE.items(): options.option_to_argparse(b, n, parser, ' (just for host machine)') options.option_to_argparse(b, n.as_build(), parser, ' (just for build machine)') - parser.add_argument('-D', action='append', dest='projectoptions', default=[], metavar="option", + parser.add_argument('-D', action=KeyValueAction, dest='cmd_line_options', default={}, metavar="option=value", help='Set the value of an option, can be used several times to set multiple options.') def parse_cmd_line_options(args: SharedCMDOptions) -> None: - args.cmd_line_options = {} - for o in args.projectoptions: - try: - keystr, value = o.split('=', 1) - except ValueError: - raise MesonException(f'Option {o!r} must have a value separated by equals sign.') - key = OptionKey.from_string(keystr) - args.cmd_line_options[key] = value - # Merge builtin options set with --option into the dict. for key in chain( options.BUILTIN_OPTIONS.keys(), diff --git a/mesonbuild/dependencies/detect.py b/mesonbuild/dependencies/detect.py index aa62c66..4cdf16d 100644 --- a/mesonbuild/dependencies/detect.py +++ b/mesonbuild/dependencies/detect.py @@ -15,7 +15,7 @@ if T.TYPE_CHECKING: from ..environment import Environment from .factory import DependencyFactory, WrappedFactoryFunc, DependencyGenerator - TV_DepIDEntry = T.Union[str, bool, int, T.Tuple[str, ...]] + TV_DepIDEntry = T.Union[str, bool, int, None, T.Tuple[str, ...]] TV_DepID = T.Tuple[T.Tuple[str, TV_DepIDEntry], ...] PackageTypes = T.Union[T.Type[ExternalDependency], DependencyFactory, WrappedFactoryFunc] @@ -40,10 +40,14 @@ _packages_accept_language: T.Set[str] = set() def get_dep_identifier(name: str, kwargs: T.Dict[str, T.Any]) -> 'TV_DepID': identifier: 'TV_DepID' = (('name', name), ) + from ..interpreter.type_checking import DEPENDENCY_KWS + nkwargs = {k.name: k.default for k in DEPENDENCY_KWS} + nkwargs.update(kwargs) + from ..interpreter import permitted_dependency_kwargs assert len(permitted_dependency_kwargs) == 19, \ 'Extra kwargs have been added to dependency(), please review if it makes sense to handle it here' - for key, value in kwargs.items(): + for key, value in nkwargs.items(): # 'version' is irrelevant for caching; the caller must check version matches # 'native' is handled above with `for_machine` # 'required' is irrelevant for caching; the caller handles it separately @@ -62,7 +66,7 @@ def get_dep_identifier(name: str, kwargs: T.Dict[str, T.Any]) -> 'TV_DepID': assert isinstance(i, str), i value = tuple(frozenset(listify(value))) else: - assert isinstance(value, (str, bool, int)), value + assert value is None or isinstance(value, (str, bool, int)), value identifier = (*identifier, (key, value),) return identifier diff --git a/mesonbuild/dependencies/qt.py b/mesonbuild/dependencies/qt.py index a3a9388..8bb269e 100644 --- a/mesonbuild/dependencies/qt.py +++ b/mesonbuild/dependencies/qt.py @@ -9,6 +9,7 @@ from __future__ import annotations import abc import re import os +from pathlib import Path import typing as T from .base import DependencyException, DependencyMethods @@ -50,7 +51,7 @@ def _qt_get_private_includes(mod_inc_dir: str, module: str, mod_version: str) -> if len(dirname.split('.')) == 3: private_dir = dirname break - return [private_dir, os.path.join(private_dir, 'Qt' + module)] + return [private_dir, Path(private_dir, f'Qt{module}').as_posix()] def get_qmake_host_bins(qvars: T.Dict[str, str]) -> str: @@ -303,7 +304,7 @@ class QmakeQtDependency(_QtBase, ConfigToolDependency, metaclass=abc.ABCMeta): modules_lib_suffix = _get_modules_lib_suffix(self.version, self.env.machines[self.for_machine], is_debug) for module in self.requested_modules: - mincdir = os.path.join(incdir, 'Qt' + module) + mincdir = Path(incdir, f'Qt{module}').as_posix() self.compile_args.append('-I' + mincdir) if module == 'QuickTest': diff --git a/mesonbuild/dependencies/scalapack.py b/mesonbuild/dependencies/scalapack.py index c04d1f5..f34692c 100644 --- a/mesonbuild/dependencies/scalapack.py +++ b/mesonbuild/dependencies/scalapack.py @@ -9,7 +9,7 @@ import os import typing as T from ..options import OptionKey -from .base import DependencyMethods +from .base import DependencyException, DependencyMethods from .cmake import CMakeDependency from .detect import packages from .pkgconfig import PkgConfigDependency @@ -65,8 +65,7 @@ class MKLPkgConfigDependency(PkgConfigDependency): super().__init__(name, env, kwargs, language=language) # Doesn't work with gcc on windows, but does on Linux - if (not self.__mklroot or (env.machines[self.for_machine].is_windows() - and self.clib_compiler.id == 'gcc')): + if env.machines[self.for_machine].is_windows() and self.clib_compiler.id == 'gcc': self.is_found = False # This can happen either because we're using GCC, we couldn't find the @@ -96,6 +95,9 @@ class MKLPkgConfigDependency(PkgConfigDependency): self.version = v def _set_libs(self) -> None: + if self.__mklroot is None: + raise DependencyException('MKLROOT not set') + super()._set_libs() if self.env.machines[self.for_machine].is_windows(): @@ -133,6 +135,9 @@ class MKLPkgConfigDependency(PkgConfigDependency): self.link_args.insert(i + 1, '-lmkl_blacs_intelmpi_lp64') def _set_cargs(self) -> None: + if self.__mklroot is None: + raise DependencyException('MKLROOT not set') + allow_system = False if self.language == 'fortran': # gfortran doesn't appear to look in system paths for INCLUDE files, diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 2c3bdec..489ef50 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -12,6 +12,7 @@ import collections from . import coredata from . import mesonlib from . import machinefile +from . import options CmdLineFileParser = machinefile.CmdLineFileParser @@ -34,6 +35,7 @@ from .compilers import ( is_library, is_llvm_ir, is_object, + is_separate_compile, is_source, ) @@ -728,13 +730,14 @@ class Environment: def mfilestr2key(self, machine_file_string: str, section: T.Optional[str], section_subproject: T.Optional[str], machine: MachineChoice) -> OptionKey: key = OptionKey.from_string(machine_file_string) - assert key.machine == MachineChoice.HOST if key.subproject: suggestion = section if section == 'project options' else 'built-in options' raise MesonException(f'Do not set subproject options in [{section}] section, use [subproject:{suggestion}] instead.') if section_subproject: key = key.evolve(subproject=section_subproject) if machine == MachineChoice.BUILD: + if key.machine == MachineChoice.BUILD: + mlog.deprecation('Setting build machine options in the native file does not need the "build." prefix', once=True) return key.evolve(machine=machine) return key @@ -935,6 +938,9 @@ class Environment: def is_assembly(self, fname: 'mesonlib.FileOrString') -> bool: return is_assembly(fname) + def is_separate_compile(self, fname: 'mesonlib.FileOrString') -> bool: + return is_separate_compile(fname) + def is_llvm_ir(self, fname: 'mesonlib.FileOrString') -> bool: return is_llvm_ir(fname) @@ -1071,3 +1077,44 @@ class Environment: if extra_paths: env.prepend('PATH', list(extra_paths)) return env + + def add_lang_args(self, lang: str, comp: T.Type['Compiler'], + for_machine: MachineChoice) -> None: + """Add global language arguments that are needed before compiler/linker detection.""" + description = f'Extra arguments passed to the {lang}' + argkey = OptionKey(f'{lang}_args', machine=for_machine) + largkey = OptionKey(f'{lang}_link_args', machine=for_machine) + + comp_args_from_envvar = False + comp_options = self.coredata.optstore.get_pending_value(argkey) + if comp_options is None: + comp_args_from_envvar = True + comp_options = self.env_opts.get(argkey, []) + + link_options = self.coredata.optstore.get_pending_value(largkey) + if link_options is None: + link_options = self.env_opts.get(largkey, []) + + assert isinstance(comp_options, (str, list)), 'for mypy' + assert isinstance(link_options, (str, list)), 'for mypy' + + cargs = options.UserStringArrayOption( + argkey.name, + description + ' compiler', + comp_options, split_args=True, allow_dups=True) + + largs = options.UserStringArrayOption( + largkey.name, + description + ' linker', + link_options, split_args=True, allow_dups=True) + + self.coredata.optstore.add_compiler_option(lang, argkey, cargs) + self.coredata.optstore.add_compiler_option(lang, largkey, largs) + + if comp.INVOKES_LINKER and comp_args_from_envvar: + # If the compiler acts as a linker driver, and we're using the + # environment variable flags for both the compiler and linker + # arguments, then put the compiler flags in the linker flags as well. + # This is how autotools works, and the env vars feature is for + # autotools compatibility. + largs.extend_value(comp_options) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 29bb705..2cf5b7a 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -62,6 +62,7 @@ from .type_checking import ( OUTPUT_KW, DEFAULT_OPTIONS, DEPENDENCIES_KW, + DEPENDENCY_KWS, DEPENDS_KW, DEPEND_FILES_KW, DEPFILE_KW, @@ -523,6 +524,8 @@ class Interpreter(InterpreterBase, HoldableObject): self.handle_meson_version(val.value, val) def get_build_def_files(self) -> mesonlib.OrderedSet[str]: + if self.environment.cargo: + self.build_def_files.update(self.environment.cargo.get_build_def_files()) return self.build_def_files def add_build_def_file(self, f: mesonlib.FileOrString) -> None: @@ -1085,7 +1088,7 @@ class Interpreter(InterpreterBase, HoldableObject): value_object: T.Optional[options.AnyOptionType] try: - optkey = options.OptionKey(optname, self.subproject) + optkey = options.OptionKey.from_string(optname).evolve(subproject=self.subproject) value_object, value = self.coredata.optstore.get_value_object_and_value_for(optkey) except KeyError: if self.coredata.optstore.is_base_option(optkey): @@ -1787,8 +1790,8 @@ class Interpreter(InterpreterBase, HoldableObject): @disablerIfNotFound @permittedKwargs(permitted_dependency_kwargs) @typed_pos_args('dependency', varargs=str, min_varargs=1) - @typed_kwargs('dependency', DEFAULT_OPTIONS.evolve(since='0.38.0'), allow_unknown=True) - def func_dependency(self, node: mparser.BaseNode, args: T.Tuple[T.List[str]], kwargs) -> Dependency: + @typed_kwargs('dependency', *DEPENDENCY_KWS, allow_unknown=True) + def func_dependency(self, node: mparser.BaseNode, args: T.Tuple[T.List[str]], kwargs: kwtypes.FuncDependency) -> Dependency: # Replace '' by empty list of names names = [n for n in args[0] if n] if len(names) > 1: @@ -3255,9 +3258,9 @@ class Interpreter(InterpreterBase, HoldableObject): def build_both_libraries(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType], kwargs: kwtypes.Library) -> build.BothLibraries: shared_lib = self.build_target(node, args, kwargs, build.SharedLibrary) static_lib = self.build_target(node, args, kwargs, build.StaticLibrary) - preferred_library = self.coredata.optstore.get_value_for(OptionKey('default_both_libraries')) + preferred_library = self.coredata.optstore.get_value_for(OptionKey('default_both_libraries', subproject=self.subproject)) if preferred_library == 'auto': - preferred_library = self.coredata.optstore.get_value_for(OptionKey('default_library')) + preferred_library = self.coredata.optstore.get_value_for(OptionKey('default_library', subproject=self.subproject)) if preferred_library == 'both': preferred_library = 'shared' diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index d741aab..7dd49a1 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -363,6 +363,8 @@ class _BuildTarget(_BaseBuildTarget): d_module_versions: T.List[T.Union[str, int]] d_unittest: bool rust_dependency_map: T.Dict[str, str] + swift_interoperability_mode: Literal['c', 'cpp'] + swift_module_name: str sources: SourcesVarargsType c_args: T.List[str] cpp_args: T.List[str] @@ -486,3 +488,8 @@ class FuncDeclareDependency(TypedDict): sources: T.List[T.Union[FileOrString, build.GeneratedTypes]] variables: T.Dict[str, str] version: T.Optional[str] + + +class FuncDependency(TypedDict): + + default_options: T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]] diff --git a/mesonbuild/interpreter/primitives/array.py b/mesonbuild/interpreter/primitives/array.py index ff520a2..d0a2441 100644 --- a/mesonbuild/interpreter/primitives/array.py +++ b/mesonbuild/interpreter/primitives/array.py @@ -97,3 +97,17 @@ class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject): return self.held_object[other] except IndexError: raise InvalidArguments(f'Index {other} out of bounds of array of size {len(self.held_object)}.') + + @noPosargs + @noKwargs + @FeatureNew('array.flatten', '1.9.0') + @InterpreterObject.method('flatten') + def flatten_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> TYPE_var: + def flatten(obj: TYPE_var) -> T.Iterable[TYPE_var]: + if isinstance(obj, list): + for o in obj: + yield from flatten(o) + else: + yield obj + + return list(flatten(self.held_object)) diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index fbe3e3e..a551d0f 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -633,6 +633,8 @@ _BUILD_TARGET_KWS: T.List[KwargInfo] = [ default={}, since='1.2.0', ), + KwargInfo('swift_interoperability_mode', str, default='c', validator=in_set_validator({'c', 'cpp'}), since='1.9.0'), + KwargInfo('swift_module_name', str, default='', since='1.9.0'), KwargInfo('build_rpath', str, default='', since='0.42.0'), KwargInfo( 'gnu_symbol_visibility', @@ -865,3 +867,8 @@ PKGCONFIG_DEFINE_KW: KwargInfo = KwargInfo( default=[], convertor=_pkgconfig_define_convertor, ) + + +DEPENDENCY_KWS: T.List[KwargInfo] = [ + DEFAULT_OPTIONS.evolve(since='0.38.0'), +] diff --git a/mesonbuild/linkers/detect.py b/mesonbuild/linkers/detect.py index f6c0fbc..6fbe6e4 100644 --- a/mesonbuild/linkers/detect.py +++ b/mesonbuild/linkers/detect.py @@ -39,7 +39,7 @@ def guess_win_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty use_linker_prefix: bool = True, invoked_directly: bool = True, extra_args: T.Optional[T.List[str]] = None) -> 'DynamicLinker': from . import linkers - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + env.add_lang_args(comp_class.language, comp_class, for_machine) if invoked_directly or comp_class.get_argument_syntax() == 'msvc': rsp_syntax = RSPFileSyntax.MSVC @@ -128,7 +128,7 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty :extra_args: Any additional arguments required (such as a source file) """ from . import linkers - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + env.add_lang_args(comp_class.language, comp_class, for_machine) extra_args = extra_args or [] system = env.machines[for_machine].system @@ -166,7 +166,7 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty linker = lld_cls( compiler, for_machine, comp_class.LINKER_PREFIX, override, system=system, version=v) - elif 'Hexagon' in o and 'LLVM' in o: + elif o.startswith("eld"): linker = linkers.ELDDynamicLinker( compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) elif 'Snapdragon' in e and 'LLVM' in e: diff --git a/mesonbuild/linkers/linkers.py b/mesonbuild/linkers/linkers.py index d81892b..c528db7 100644 --- a/mesonbuild/linkers/linkers.py +++ b/mesonbuild/linkers/linkers.py @@ -65,9 +65,8 @@ class StaticLinker: def get_coverage_link_args(self) -> T.List[str]: return [] - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) def thread_link_flags(self, env: 'Environment') -> T.List[str]: @@ -297,9 +296,8 @@ class DynamicLinker(metaclass=abc.ABCMeta): def bitcode_args(self) -> T.List[str]: raise MesonException('This linker does not support bitcode bundles') - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, @@ -703,13 +701,13 @@ class GnuLikeDynamicLinkerMixin(DynamicLinkerBase): sostr = '' if soversion is None else '.' + soversion return self._apply_prefix(f'-soname,{prefix}{shlib_name}.{suffix}{sostr}') - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: m = env.machines[self.for_machine] if m.is_windows() or m.is_cygwin(): return ([], set()) - if not rpath_paths and not install_rpath and not build_rpath: + rpath_paths = target.determine_rpath_dirs() + if not rpath_paths and not target.install_rpath and not target.build_rpath and not extra_paths: return ([], set()) args: T.List[str] = [] origin_placeholder = '$ORIGIN' @@ -722,10 +720,12 @@ class GnuLikeDynamicLinkerMixin(DynamicLinkerBase): for p in all_paths: rpath_dirs_to_remove.add(p.encode('utf8')) # Build_rpath is used as-is (it is usually absolute). - if build_rpath != '': - all_paths.add(build_rpath) - for p in build_rpath.split(':'): + if target.build_rpath != '': + all_paths.add(target.build_rpath) + for p in target.build_rpath.split(':'): rpath_dirs_to_remove.add(p.encode('utf8')) + if extra_paths: + all_paths.update(extra_paths) # TODO: should this actually be "for (dragonfly|open)bsd"? if mesonlib.is_dragonflybsd() or mesonlib.is_openbsd(): @@ -740,7 +740,7 @@ class GnuLikeDynamicLinkerMixin(DynamicLinkerBase): # enough space in the ELF header to hold the final installation RPATH. paths = ':'.join(all_paths) paths_length = len(paths.encode('utf-8')) - install_rpath_length = len(install_rpath.encode('utf-8')) + install_rpath_length = len(target.install_rpath.encode('utf-8')) if paths_length < install_rpath_length: padding = 'X' * (install_rpath_length - paths_length) if not paths: @@ -873,10 +873,10 @@ class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): '-current_version', darwin_versions[1]]) return args - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: - if not rpath_paths and not install_rpath and not build_rpath: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: + rpath_paths = target.determine_rpath_dirs() + if not rpath_paths and not target.install_rpath and not target.build_rpath and not extra_paths: return ([], set()) args: T.List[str] = [] rpath_dirs_to_remove: T.Set[bytes] = set() @@ -885,8 +885,10 @@ class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): origin_placeholder = '@loader_path' processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths]) - if build_rpath != '': - all_paths.update(build_rpath.split(':')) + if target.build_rpath != '': + all_paths.update(target.build_rpath.split(':')) + if extra_paths: + all_paths.update(extra_paths) for rp in all_paths: rpath_dirs_to_remove.add(rp.encode('utf8')) args.extend(self._apply_prefix('-rpath,' + rp)) @@ -1022,9 +1024,8 @@ class WASMDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, Dyna def get_asneeded_args(self) -> T.List[str]: return [] - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) @@ -1100,9 +1101,8 @@ class Xc16DynamicLinker(DynamicLinker): suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: return [] - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) class CompCertDynamicLinker(DynamicLinker): @@ -1143,9 +1143,8 @@ class CompCertDynamicLinker(DynamicLinker): suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: raise MesonException(f'{self.id} does not support shared libraries.') - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) class TIDynamicLinker(DynamicLinker): @@ -1255,17 +1254,19 @@ class NAGDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): id = 'nag' - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: - if not rpath_paths and not install_rpath and not build_rpath: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: + rpath_paths = target.determine_rpath_dirs() + if not rpath_paths and not target.install_rpath and not target.build_rpath and not extra_paths: return ([], set()) args: T.List[str] = [] origin_placeholder = '$ORIGIN' processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths]) - if build_rpath != '': - all_paths.add(build_rpath) + if target.build_rpath != '': + all_paths.add(target.build_rpath) + if extra_paths: + all_paths.update(extra_paths) for rp in all_paths: args.extend(self._apply_prefix('-Wl,-Wl,,-rpath,,' + rp)) @@ -1300,10 +1301,10 @@ class PGIDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): return ['-shared'] return [] - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: if not env.machines[self.for_machine].is_windows(): + rpath_paths = target.determine_rpath_dirs() return (['-R' + os.path.join(build_dir, p) for p in rpath_paths], set()) return ([], set()) @@ -1511,26 +1512,28 @@ class SolarisDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): def fatal_warnings(self) -> T.List[str]: return ['-z', 'fatal-warnings'] - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: - if not rpath_paths and not install_rpath and not build_rpath: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: + rpath_paths = target.determine_rpath_dirs() + if not rpath_paths and not target.install_rpath and not target.build_rpath and not extra_paths: return ([], set()) processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) all_paths = mesonlib.OrderedSet([os.path.join('$ORIGIN', p) for p in processed_rpaths]) rpath_dirs_to_remove: T.Set[bytes] = set() for p in all_paths: rpath_dirs_to_remove.add(p.encode('utf8')) - if build_rpath != '': - all_paths.add(build_rpath) - for p in build_rpath.split(':'): + if target.build_rpath != '': + all_paths.add(target.build_rpath) + for p in target.build_rpath.split(':'): rpath_dirs_to_remove.add(p.encode('utf8')) + if extra_paths: + all_paths.update(extra_paths) # In order to avoid relinking for RPATH removal, the binary needs to contain just # enough space in the ELF header to hold the final installation RPATH. paths = ':'.join(all_paths) paths_length = len(paths.encode('utf-8')) - install_rpath_length = len(install_rpath.encode('utf-8')) + install_rpath_length = len(target.install_rpath.encode('utf-8')) if paths_length < install_rpath_length: padding = 'X' * (install_rpath_length - paths_length) if not paths: @@ -1581,16 +1584,15 @@ class AIXDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): # archives or not." return args - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, env: Environment, build_dir: str, from_dir: str, + target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]: all_paths: mesonlib.OrderedSet[str] = mesonlib.OrderedSet() # install_rpath first, followed by other paths, and the system path last - if install_rpath != '': - all_paths.add(install_rpath) - if build_rpath != '': - all_paths.add(build_rpath) - for p in rpath_paths: + if target.install_rpath != '': + all_paths.add(target.install_rpath) + if target.build_rpath != '': + all_paths.add(target.build_rpath) + for p in target.determine_rpath_dirs(): all_paths.add(os.path.join(build_dir, p)) # We should consider allowing the $LIBPATH environment variable # to override sys_path. @@ -1604,6 +1606,8 @@ class AIXDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): for p in sys_path: if os.path.isdir(p): all_paths.add(p) + if extra_paths: + all_paths.update(extra_paths) return (self._apply_prefix('-blibpath:' + ':'.join(all_paths)), set()) def thread_flags(self, env: 'Environment') -> T.List[str]: diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 217379f..7f62ba0 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -33,7 +33,6 @@ if T.TYPE_CHECKING: builddir: str clearcache: bool pager: bool - unset_opts: T.List[str] # cannot be TV_Loggable, because non-ansidecorators do direct string concat LOGLINE = T.Union[str, mlog.AnsiDecorator] @@ -47,7 +46,7 @@ def add_arguments(parser: 'argparse.ArgumentParser') -> None: help='Clear cached state (e.g. found dependencies)') parser.add_argument('--no-pager', action='store_false', dest='pager', help='Do not redirect output to a pager') - parser.add_argument('-U', action='append', dest='unset_opts', default=[], + parser.add_argument('-U', action=coredata.KeyNoneAction, dest='cmd_line_options', default={}, help='Remove a subproject option.') def stringify(val: T.Any) -> str: @@ -147,7 +146,7 @@ class Conf: Each column will have a specific width, and will be line wrapped. """ total_width = shutil.get_terminal_size(fallback=(160, 0))[0] - _col = max(total_width // 5, 20) + _col = max(total_width // 5, 24) last_column = total_width - (3 * _col) - 3 four_column = (_col, _col, _col, last_column if last_column > 1 else _col) @@ -207,11 +206,12 @@ class Conf: self.choices_col.append(choices) self.descr_col.append(descr) - def add_option(self, name: str, descr: str, value: T.Any, choices: T.Any) -> None: + def add_option(self, key: OptionKey, descr: str, value: T.Any, choices: T.Any) -> None: self._add_section() value = stringify(value) choices = stringify(choices) - self._add_line(mlog.green(name), mlog.yellow(value), mlog.blue(choices), descr) + self._add_line(mlog.green(str(key.evolve(subproject=None))), mlog.yellow(value), + mlog.blue(choices), descr) def add_title(self, title: str) -> None: self._add_section() @@ -248,7 +248,7 @@ class Conf: # printable_value = '<inherited from main project>' #if isinstance(o, options.UserFeatureOption) and o.is_auto(): # printable_value = auto.printable_value() - self.add_option(k.name, o.description, printable_value, o.printable_choices()) + self.add_option(k, o.description, printable_value, o.printable_choices()) def print_conf(self, pager: bool) -> None: if pager: @@ -354,11 +354,7 @@ class Conf: mlog.log('\nThere are no option augments.') def has_option_flags(options: CMDOptions) -> bool: - if options.cmd_line_options: - return True - if options.unset_opts: - return True - return False + return bool(options.cmd_line_options) def is_print_only(options: CMDOptions) -> bool: if has_option_flags(options): diff --git a/mesonbuild/mformat.py b/mesonbuild/mformat.py index 1e134f5..2131ff7 100644 --- a/mesonbuild/mformat.py +++ b/mesonbuild/mformat.py @@ -837,7 +837,15 @@ class Formatter: # See https://editorconfig.org/ config = EditorConfig() - for p in source_file.parents: + if source_file == Path('STDIN'): + raise MesonException('Using editorconfig with stdin requires --source-file-path argument') + + try: + source_file_path = source_file.resolve() + except FileNotFoundError: + raise MesonException(f'Unable to resolve path for "{source_file}"') + + for p in source_file_path.parents: editorconfig_file = p / '.editorconfig' if not editorconfig_file.exists(): continue @@ -956,6 +964,11 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: help='output file (implies having exactly one input)' ) parser.add_argument( + '--source-file-path', + type=Path, + help='path to use, when reading from stdin' + ) + parser.add_argument( 'sources', nargs='*', type=Path, @@ -981,6 +994,10 @@ def run(options: argparse.Namespace) -> int: raise MesonException('--recursive argument is not compatible with stdin input') if options.inplace and from_stdin: raise MesonException('--inplace argument is not compatible with stdin input') + if options.source_file_path and not from_stdin: + raise MesonException('--source-file-path argument is only compatible with stdin input') + if from_stdin and options.editor_config and not options.source_file_path: + raise MesonException('using --editor-config with stdin input requires --source-file-path argument') sources: T.List[Path] = options.sources.copy() or [Path(build_filename)] @@ -996,7 +1013,7 @@ def run(options: argparse.Namespace) -> int: try: if from_stdin: - src_file = Path('STDIN') # used for error messages and introspection + src_file = options.source_file_path or Path('STDIN') # used for error messages and introspection code = sys.stdin.read() else: code = src_file.read_text(encoding='utf-8') diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 6986186..e19e528 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -125,14 +125,15 @@ def list_installed(installdata: backends.InstallData) -> T.Dict[str, str]: res[basename] = os.path.join(installdata.prefix, s.install_path, basename) return res -def list_install_plan(installdata: backends.InstallData) -> T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]]: - plan: T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]] = { +def list_install_plan(installdata: backends.InstallData) -> T.Dict[str, T.Dict[str, T.Dict[str, T.Union[str, T.List[str], None]]]]: + plan: T.Dict[str, T.Dict[str, T.Dict[str, T.Union[str, T.List[str], None]]]] = { 'targets': { - os.path.join(installdata.build_dir, target.fname): { + Path(installdata.build_dir, target.fname).as_posix(): { 'destination': target.out_name, 'tag': target.tag or None, 'subproject': target.subproject or None, - 'install_rpath': target.install_rpath or None + 'install_rpath': target.install_rpath or None, + 'build_rpaths': sorted(x.decode('utf8') for x in target.rpath_dirs_to_remove), } for target in installdata.targets }, @@ -145,13 +146,14 @@ def list_install_plan(installdata: backends.InstallData) -> T.Dict[str, T.Dict[s }.items(): # Mypy doesn't recognize SubdirInstallData as a subclass of InstallDataBase for data in data_list: # type: ignore[attr-defined] + data_path = Path(data.path).as_posix() data_type = data.data_type or key - install_path_name = data.install_path_name + install_path_name = Path(data.install_path_name) if key == 'headers': # in the headers, install_path_name is the directory - install_path_name = os.path.join(install_path_name, os.path.basename(data.path)) + install_path_name = install_path_name / os.path.basename(data.path) entry = { - 'destination': install_path_name, + 'destination': install_path_name.as_posix(), 'tag': data.tag or None, 'subproject': data.subproject or None, } @@ -162,7 +164,7 @@ def list_install_plan(installdata: backends.InstallData) -> T.Dict[str, T.Dict[s entry['exclude_files'] = list(exclude_files) plan[data_type] = plan.get(data_type, {}) - plan[data_type][data.path] = entry + plan[data_type][data_path] = entry return plan diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index 87892e6..3938101 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -82,19 +82,21 @@ class ModuleState: wanted=wanted, silent=silent, for_machine=for_machine) def find_tool(self, name: str, depname: str, varname: str, required: bool = True, - wanted: T.Optional[str] = None) -> T.Union[build.OverrideExecutable, ExternalProgram, 'OverrideProgram']: - # Look in overrides in case it's built as subproject - progobj = self._interpreter.program_from_overrides([name], []) - if progobj is not None: - return progobj + wanted: T.Optional[str] = None, for_machine: MachineChoice = MachineChoice.HOST) -> T.Union[build.OverrideExecutable, ExternalProgram, 'OverrideProgram']: + if for_machine is MachineChoice.HOST: + # Look in overrides in case it's built as subproject + progobj = self._interpreter.program_from_overrides([name], []) + if progobj is not None: + return progobj # Look in machine file - prog_list = self.environment.lookup_binary_entry(MachineChoice.HOST, name) + prog_list = self.environment.lookup_binary_entry(for_machine, name) if prog_list is not None: return ExternalProgram.from_entry(name, prog_list) # Check if pkgconfig has a variable - dep = self.dependency(depname, native=True, required=False, wanted=wanted) + dep = self.dependency(depname, native=for_machine is MachineChoice.BUILD, + required=False, wanted=wanted) if dep.found() and dep.type_name == 'pkgconfig': value = dep.get_variable(pkgconfig=varname) if value: @@ -106,7 +108,7 @@ class ModuleState: return progobj # Normal program lookup - return self.find_program(name, required=required, wanted=wanted) + return self.find_program(name, required=required, wanted=wanted, for_machine=for_machine) def dependency(self, depname: str, native: bool = False, required: bool = True, wanted: T.Optional[str] = None) -> 'Dependency': diff --git a/mesonbuild/modules/fs.py b/mesonbuild/modules/fs.py index 1fa368e..57a6b6d 100644 --- a/mesonbuild/modules/fs.py +++ b/mesonbuild/modules/fs.py @@ -2,7 +2,9 @@ # Copyright 2019 The Meson development team from __future__ import annotations -from pathlib import Path, PurePath, PureWindowsPath +from ntpath import sep as ntsep +from pathlib import Path +from posixpath import sep as posixsep import hashlib import os import typing as T @@ -12,7 +14,7 @@ from .. import mlog from ..build import BuildTarget, CustomTarget, CustomTargetIndex, InvalidArguments from ..interpreter.type_checking import INSTALL_KW, INSTALL_MODE_KW, INSTALL_TAG_KW, NoneType from ..interpreterbase import FeatureNew, KwargInfo, typed_kwargs, typed_pos_args, noKwargs -from ..mesonlib import File, MesonException, has_path_sep, path_is_in_root, relpath +from ..mesonlib import File, MesonException, has_path_sep, is_windows, path_is_in_root, relpath if T.TYPE_CHECKING: from . import ModuleState @@ -42,7 +44,7 @@ class FSModule(ExtensionModule): INFO = ModuleInfo('fs', '0.53.0') - def __init__(self, interpreter: 'Interpreter') -> None: + def __init__(self, interpreter: Interpreter) -> None: super().__init__(interpreter) self.methods.update({ 'as_posix': self.as_posix, @@ -62,29 +64,30 @@ class FSModule(ExtensionModule): 'replace_suffix': self.replace_suffix, 'size': self.size, 'stem': self.stem, + 'suffix': self.suffix, }) - def _absolute_dir(self, state: 'ModuleState', arg: 'FileOrString') -> Path: + def _absolute_dir(self, state: ModuleState, arg: FileOrString) -> str: """ make an absolute path from a relative path, WITHOUT resolving symlinks """ if isinstance(arg, File): - return Path(arg.absolute_path(state.source_root, state.environment.get_build_dir())) - return Path(state.source_root) / Path(state.subdir) / Path(arg).expanduser() + return arg.absolute_path(state.source_root, state.environment.get_build_dir()) + return os.path.join(state.source_root, state.subdir, os.path.expanduser(arg)) @staticmethod - def _obj_to_path(feature_new_prefix: str, obj: T.Union[FileOrString, BuildTargetTypes], state: ModuleState) -> PurePath: + def _obj_to_pathstr(feature_new_prefix: str, obj: T.Union[FileOrString, BuildTargetTypes], state: ModuleState) -> str: if isinstance(obj, str): - return PurePath(obj) + return obj if isinstance(obj, File): FeatureNew(f'{feature_new_prefix} with file', '0.59.0').use(state.subproject, location=state.current_node) - return PurePath(str(obj)) + return str(obj) FeatureNew(f'{feature_new_prefix} with build_tgt, custom_tgt, and custom_idx', '1.4.0').use(state.subproject, location=state.current_node) - return PurePath(state.backend.get_target_filename(obj)) + return state.backend.get_target_filename(obj) - def _resolve_dir(self, state: 'ModuleState', arg: 'FileOrString') -> Path: + def _resolve_dir(self, state: ModuleState, arg: FileOrString) -> str: """ resolves symlinks and makes absolute a directory relative to calling meson.build, if not already absolute @@ -92,7 +95,7 @@ class FSModule(ExtensionModule): path = self._absolute_dir(state, arg) try: # accommodate unresolvable paths e.g. symlink loops - path = path.resolve() + path = os.path.realpath(path) except Exception: # return the best we could do pass @@ -101,123 +104,139 @@ class FSModule(ExtensionModule): @noKwargs @FeatureNew('fs.expanduser', '0.54.0') @typed_pos_args('fs.expanduser', str) - def expanduser(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: - return str(Path(args[0]).expanduser()) + def expanduser(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: + return os.path.expanduser(args[0]) @noKwargs @FeatureNew('fs.is_absolute', '0.54.0') @typed_pos_args('fs.is_absolute', (str, File)) - def is_absolute(self, state: 'ModuleState', args: T.Tuple['FileOrString'], kwargs: T.Dict[str, T.Any]) -> bool: - if isinstance(args[0], File): + def is_absolute(self, state: ModuleState, args: T.Tuple[FileOrString], kwargs: T.Dict[str, T.Any]) -> bool: + path = args[0] + if isinstance(path, File): FeatureNew('fs.is_absolute with file', '0.59.0').use(state.subproject, location=state.current_node) - return PurePath(str(args[0])).is_absolute() + path = str(path) + if is_windows(): + # os.path.isabs was broken for Windows before Python 3.13, so we implement it ourselves + path = path[:3].replace(posixsep, ntsep) + return path.startswith(ntsep * 2) or path.startswith(':' + ntsep, 1) + return path.startswith(posixsep) @noKwargs @FeatureNew('fs.as_posix', '0.54.0') @typed_pos_args('fs.as_posix', str) - def as_posix(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: + def as_posix(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: r""" 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 """ - return PureWindowsPath(args[0]).as_posix() + return args[0].replace(ntsep, posixsep) @noKwargs @typed_pos_args('fs.exists', str) - def exists(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: - return self._resolve_dir(state, args[0]).exists() + def exists(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: + return os.path.exists(self._resolve_dir(state, args[0])) @noKwargs @typed_pos_args('fs.is_symlink', (str, File)) - def is_symlink(self, state: 'ModuleState', args: T.Tuple['FileOrString'], kwargs: T.Dict[str, T.Any]) -> bool: + def is_symlink(self, state: ModuleState, args: T.Tuple[FileOrString], kwargs: T.Dict[str, T.Any]) -> bool: if isinstance(args[0], File): FeatureNew('fs.is_symlink with file', '0.59.0').use(state.subproject, location=state.current_node) - return self._absolute_dir(state, args[0]).is_symlink() + return os.path.islink(self._absolute_dir(state, args[0])) @noKwargs @typed_pos_args('fs.is_file', str) - def is_file(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: - return self._resolve_dir(state, args[0]).is_file() + def is_file(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: + return os.path.isfile(self._resolve_dir(state, args[0])) @noKwargs @typed_pos_args('fs.is_dir', str) - def is_dir(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: - return self._resolve_dir(state, args[0]).is_dir() + def is_dir(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: + return os.path.isdir(self._resolve_dir(state, args[0])) @noKwargs @typed_pos_args('fs.hash', (str, File), str) - def hash(self, state: 'ModuleState', args: T.Tuple['FileOrString', str], kwargs: T.Dict[str, T.Any]) -> str: + def hash(self, state: ModuleState, args: T.Tuple[FileOrString, str], kwargs: T.Dict[str, T.Any]) -> str: if isinstance(args[0], File): FeatureNew('fs.hash with file', '0.59.0').use(state.subproject, location=state.current_node) file = self._resolve_dir(state, args[0]) - if not file.is_file(): + if not os.path.isfile(file): raise MesonException(f'{file} is not a file and therefore cannot be hashed') try: h = hashlib.new(args[1]) except ValueError: raise MesonException('hash algorithm {} is not available'.format(args[1])) - mlog.debug('computing {} sum of {} size {} bytes'.format(args[1], file, file.stat().st_size)) - h.update(file.read_bytes()) + mlog.debug('computing {} sum of {} size {} bytes'.format(args[1], file, os.stat(file).st_size)) + with open(file, mode='rb', buffering=0) as f: + h.update(f.read()) return h.hexdigest() @noKwargs @typed_pos_args('fs.size', (str, File)) - def size(self, state: 'ModuleState', args: T.Tuple['FileOrString'], kwargs: T.Dict[str, T.Any]) -> int: + def size(self, state: ModuleState, args: T.Tuple[FileOrString], kwargs: T.Dict[str, T.Any]) -> int: if isinstance(args[0], File): FeatureNew('fs.size with file', '0.59.0').use(state.subproject, location=state.current_node) file = self._resolve_dir(state, args[0]) - if not file.is_file(): + if not os.path.isfile(file): raise MesonException(f'{file} is not a file and therefore cannot be sized') try: - return file.stat().st_size + return os.stat(file).st_size except ValueError: raise MesonException('{} size could not be determined'.format(args[0])) @noKwargs @typed_pos_args('fs.is_samepath', (str, File), (str, File)) - def is_samepath(self, state: 'ModuleState', args: T.Tuple['FileOrString', 'FileOrString'], kwargs: T.Dict[str, T.Any]) -> bool: + def is_samepath(self, state: ModuleState, args: T.Tuple[FileOrString, FileOrString], kwargs: T.Dict[str, T.Any]) -> bool: if isinstance(args[0], File) or isinstance(args[1], File): FeatureNew('fs.is_samepath with file', '0.59.0').use(state.subproject, location=state.current_node) file1 = self._resolve_dir(state, args[0]) file2 = self._resolve_dir(state, args[1]) - if not file1.exists(): + if not os.path.exists(file1): return False - if not file2.exists(): + if not os.path.exists(file2): return False try: - return file1.samefile(file2) + return os.path.samefile(file1, file2) except OSError: return False @noKwargs @typed_pos_args('fs.replace_suffix', (str, File, CustomTarget, CustomTargetIndex, BuildTarget), str) - def replace_suffix(self, state: 'ModuleState', args: T.Tuple[T.Union[FileOrString, BuildTargetTypes], str], kwargs: T.Dict[str, T.Any]) -> str: - path = self._obj_to_path('fs.replace_suffix', args[0], state) - return str(path.with_suffix(args[1])) + def replace_suffix(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes], str], kwargs: T.Dict[str, T.Any]) -> str: + if args[1] and not args[1].startswith('.'): + raise ValueError(f"Invalid suffix {args[1]!r}") + path = self._obj_to_pathstr('fs.replace_suffix', args[0], state) + return os.path.splitext(path)[0] + args[1] @noKwargs @typed_pos_args('fs.parent', (str, File, CustomTarget, CustomTargetIndex, BuildTarget)) - def parent(self, state: 'ModuleState', args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: - path = self._obj_to_path('fs.parent', args[0], state) - return str(path.parent) + def parent(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: + path = self._obj_to_pathstr('fs.parent', args[0], state) + return os.path.split(path)[0] or '.' @noKwargs @typed_pos_args('fs.name', (str, File, CustomTarget, CustomTargetIndex, BuildTarget)) - def name(self, state: 'ModuleState', args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: - path = self._obj_to_path('fs.name', args[0], state) - return str(path.name) + def name(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: + path = self._obj_to_pathstr('fs.name', args[0], state) + return os.path.basename(path) @noKwargs @typed_pos_args('fs.stem', (str, File, CustomTarget, CustomTargetIndex, BuildTarget)) @FeatureNew('fs.stem', '0.54.0') - def stem(self, state: 'ModuleState', args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: - path = self._obj_to_path('fs.stem', args[0], state) - return str(path.stem) + def stem(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: + path = self._obj_to_pathstr('fs.name', args[0], state) + return os.path.splitext(os.path.basename(path))[0] + + @noKwargs + @typed_pos_args('fs.suffix', (str, File, CustomTarget, CustomTargetIndex, BuildTarget)) + @FeatureNew('fs.suffix', '1.9.0') + def suffix(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: + path = self._obj_to_pathstr('fs.suffix', args[0], state) + return os.path.splitext(path)[1] @FeatureNew('fs.read', '0.57.0') @typed_pos_args('fs.read', (str, File)) @typed_kwargs('fs.read', KwargInfo('encoding', str, default='utf-8')) - def read(self, state: 'ModuleState', args: T.Tuple['FileOrString'], kwargs: 'ReadKwArgs') -> str: + def read(self, state: ModuleState, args: T.Tuple[FileOrString], kwargs: ReadKwArgs) -> str: """Read a file from the source tree and return its value as a decoded string. diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 9f955ae..53919bc 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -22,7 +22,7 @@ from .. import build from .. import interpreter from .. import mesonlib from .. import mlog -from ..build import CustomTarget, CustomTargetIndex, Executable, GeneratedList, InvalidArguments +from ..build import CustomTarget, CustomTargetIndex, Executable, GeneratedList, InvalidArguments, OverrideExecutable from ..dependencies import Dependency, InternalDependency from ..dependencies.pkgconfig import PkgConfigDependency, PkgConfigInterface from ..interpreter.type_checking import DEPENDS_KW, DEPEND_FILES_KW, ENV_KW, INSTALL_DIR_KW, INSTALL_KW, NoneType, DEPENDENCY_SOURCES_KW, in_set_validator @@ -33,11 +33,11 @@ from ..mesonlib import ( MachineChoice, MesonException, OrderedSet, Popen_safe, join_args, quote_arg ) from ..options import OptionKey -from ..programs import OverrideProgram +from ..programs import ExternalProgram, OverrideProgram from ..scripts.gettext import read_linguas if T.TYPE_CHECKING: - from typing_extensions import Literal, TypedDict + from typing_extensions import Literal, TypeAlias, TypedDict from . import ModuleState from ..build import BuildTarget @@ -45,7 +45,6 @@ if T.TYPE_CHECKING: from ..interpreter import Interpreter from ..interpreterbase import TYPE_var, TYPE_kwargs from ..mesonlib import FileOrString - from ..programs import ExternalProgram class PostInstall(TypedDict): glib_compile_schemas: bool @@ -198,7 +197,7 @@ if T.TYPE_CHECKING: vtail: T.Optional[str] depends: T.List[T.Union[BuildTarget, CustomTarget, CustomTargetIndex]] - ToolType = T.Union[Executable, ExternalProgram, OverrideProgram] + ToolType: TypeAlias = T.Union[OverrideExecutable, ExternalProgram, OverrideProgram] # Differs from the CustomTarget version in that it straight defaults to True @@ -255,9 +254,8 @@ class GnomeModule(ExtensionModule): def __init__(self, interpreter: 'Interpreter') -> None: super().__init__(interpreter) - self.gir_dep: T.Optional[Dependency] = None - self.giscanner: T.Optional[T.Union[ExternalProgram, Executable, OverrideProgram]] = None - self.gicompiler: T.Optional[T.Union[ExternalProgram, Executable, OverrideProgram]] = None + self.giscanner: T.Optional[ToolType] = None + self.gicompiler: T.Optional[ToolType] = None self.install_glib_compile_schemas = False self.install_gio_querymodules: T.List[str] = [] self.install_gtk_update_icon_cache = False @@ -309,7 +307,7 @@ class GnomeModule(ExtensionModule): once=True, fatal=False) @staticmethod - def _find_tool(state: 'ModuleState', tool: str) -> 'ToolType': + def _find_tool(state: 'ModuleState', tool: str, for_machine: MachineChoice = MachineChoice.HOST) -> 'ToolType': tool_map = { 'gio-querymodules': 'gio-2.0', 'glib-compile-schemas': 'gio-2.0', @@ -322,7 +320,7 @@ class GnomeModule(ExtensionModule): } depname = tool_map[tool] varname = tool.replace('-', '_') - return state.find_tool(tool, depname, varname) + return state.find_tool(tool, depname, varname, for_machine=for_machine) @typed_kwargs( 'gnome.post_install', @@ -636,7 +634,7 @@ class GnomeModule(ExtensionModule): # https://github.com/mesonbuild/meson/issues/1911 # However, g-ir-scanner does not understand -Wl,-rpath # so we need to use -L instead - for d in state.backend.determine_rpath_dirs(lib): + for d in lib.determine_rpath_dirs(): d = os.path.join(state.environment.get_build_dir(), d) link_command.append('-L' + d) if include_rpath: @@ -775,9 +773,7 @@ class GnomeModule(ExtensionModule): STATIC_BUILD_REQUIRED_VERSION = ">=1.58.1" if isinstance(girtarget, (build.StaticLibrary)) and \ - not mesonlib.version_compare( - self._get_gir_dep(state)[0].get_version(), - STATIC_BUILD_REQUIRED_VERSION): + not self._giscanner_version_compare(state, STATIC_BUILD_REQUIRED_VERSION): raise MesonException('Static libraries can only be introspected with GObject-Introspection ' + STATIC_BUILD_REQUIRED_VERSION) return girtarget @@ -791,18 +787,26 @@ class GnomeModule(ExtensionModule): if self.devenv is not None: b.devenv.append(self.devenv) - def _get_gir_dep(self, state: 'ModuleState') -> T.Tuple[Dependency, T.Union[Executable, 'ExternalProgram', 'OverrideProgram'], - T.Union[Executable, 'ExternalProgram', 'OverrideProgram']]: - if not self.gir_dep: - self.gir_dep = state.dependency('gobject-introspection-1.0') - self.giscanner = self._find_tool(state, 'g-ir-scanner') - self.gicompiler = self._find_tool(state, 'g-ir-compiler') - return self.gir_dep, self.giscanner, self.gicompiler + def _get_gi(self, state: 'ModuleState') -> T.Tuple[ToolType, ToolType]: + if not self.giscanner: + self.giscanner = self._find_tool(state, 'g-ir-scanner', for_machine=MachineChoice.BUILD) + self.gicompiler = self._find_tool(state, 'g-ir-compiler', for_machine=MachineChoice.HOST) + return self.giscanner, self.gicompiler + + def _giscanner_version_compare(self, state: 'ModuleState', cmp: str) -> bool: + # Support for --version was introduced in g-i 1.58, but Ubuntu + # Bionic shipped 1.56.1. As all our version checks are greater + # than 1.58, we can just return False if get_version fails. + try: + giscanner, _ = self._get_gi(state) + return mesonlib.version_compare(giscanner.get_version(), cmp) + except MesonException: + return False @functools.lru_cache(maxsize=None) def _gir_has_option(self, option: str) -> bool: exe = self.giscanner - if isinstance(exe, OverrideProgram): + if isinstance(exe, (Executable, OverrideProgram)): # Handle overridden g-ir-scanner assert option in {'--extra-library', '--sources-top-dirs'} return True @@ -867,7 +871,7 @@ class GnomeModule(ExtensionModule): # https://github.com/mesonbuild/meson/issues/1911 # However, g-ir-scanner does not understand -Wl,-rpath # so we need to use -L instead - for d in state.backend.determine_rpath_dirs(girtarget): + for d in girtarget.determine_rpath_dirs(): d = os.path.join(state.environment.get_build_dir(), d) ret.append('-L' + d) @@ -990,10 +994,10 @@ class GnomeModule(ExtensionModule): run_env.set('CFLAGS', [quote_arg(x) for x in env_flags], ' ') run_env.merge(kwargs['env']) - gir_dep, _, _ = self._get_gir_dep(state) + giscanner, _ = self._get_gi(state) # response file supported? - rspable = mesonlib.version_compare(gir_dep.get_version(), '>= 1.85.0') + rspable = self._giscanner_version_compare(state, '>= 1.85.0') return GirTarget( girfile, @@ -1145,7 +1149,7 @@ class GnomeModule(ExtensionModule): if len(girtargets) > 1 and any(isinstance(el, Executable) for el in girtargets): raise MesonException('generate_gir only accepts a single argument when one of the arguments is an executable') - gir_dep, giscanner, gicompiler = self._get_gir_dep(state) + giscanner, gicompiler = self._get_gi(state) ns = kwargs['namespace'] nsversion = kwargs['nsversion'] @@ -1156,14 +1160,13 @@ class GnomeModule(ExtensionModule): builddir = os.path.join(state.environment.get_build_dir(), state.subdir) depends: T.List[T.Union['FileOrString', 'build.GeneratedTypes', build.BuildTarget, build.StructuredSources]] = [] - depends.extend(gir_dep.sources) depends.extend(girtargets) langs_compilers = self._get_girtargets_langs_compilers(girtargets) cflags, internal_ldflags, external_ldflags = self._get_langs_compilers_flags(state, langs_compilers) deps = self._get_gir_targets_deps(girtargets) deps += kwargs['dependencies'] - deps += [gir_dep] + deps += [state.dependency('glib-2.0'), state.dependency('gobject-2.0'), state.dependency('gmodule-2.0'), state.dependency('gio-2.0')] typelib_includes, depends = self._gather_typelib_includes_and_update_depends(state, deps, depends) # ldflags will be misinterpreted by gir scanner (showing # spurious dependencies) but building GStreamer fails if they @@ -1190,6 +1193,32 @@ class GnomeModule(ExtensionModule): scan_command: T.List[T.Union[str, Executable, 'ExternalProgram', 'OverrideProgram']] = [giscanner] scan_command += ['--quiet'] + + if state.environment.is_cross_build() and state.environment.need_exe_wrapper(): + if not state.environment.has_exe_wrapper(): + mlog.error('generate_gir requires exe_wrapper') + + binary_wrapper = state.environment.get_exe_wrapper().get_command() + ldd = state.environment.lookup_binary_entry(MachineChoice.HOST, 'ldd') + if ldd is None: + ldd_wrapper = ['ldd'] + else: + ldd_wrapper = ExternalProgram.from_bin_list(state.environment, MachineChoice.HOST, 'ldd').get_command() + + WRAPPER_ARGS_REQUIRED_VERSION = ">=1.85.0" + if not self._giscanner_version_compare(state, WRAPPER_ARGS_REQUIRED_VERSION): + msg = ('Use of gnome.generate_gir during cross compilation requires' + f'g-ir-scanner {WRAPPER_ARGS_REQUIRED_VERSION}') + raise MesonException(msg) + else: + scan_command += ['--use-binary-wrapper', binary_wrapper[0]] + if len(binary_wrapper) > 1: + scan_command += ['--binary-wrapper-args-begin', *binary_wrapper[1:], '--binary-wrapper-args-end'] + + scan_command += ['--use-ldd-wrapper', ldd_wrapper[0]] + if len(ldd_wrapper) > 1: + scan_command += ['--ldd-wrapper-args-begin', *ldd_wrapper[1:], '--ldd-wrapper-args-end'] + scan_command += ['--no-libtool'] scan_command += ['--namespace=' + ns, '--nsversion=' + nsversion] scan_command += ['--warn-all'] diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index e3f7a97..bef14e9 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -156,6 +156,14 @@ class DependenciesHelper: pass elif isinstance(obj, dependencies.ExternalDependency) and obj.name == 'threads': pass + elif isinstance(obj, dependencies.InternalDependency) and all(lib.get_id() in self.metadata for lib in obj.libraries): + # Ensure BothLibraries are resolved: + if self.pub_libs and isinstance(self.pub_libs[0], build.StaticLibrary): + obj = obj.get_as_static(recursive=True) + else: + obj = obj.get_as_shared(recursive=True) + for lib in obj.libraries: + processed_reqs.append(self.metadata[lib.get_id()].filebase) else: raise mesonlib.MesonException('requires argument not a string, ' 'library with pkgconfig-generated file ' diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 8d82a33..3c07960 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -16,7 +16,7 @@ from ..dependencies.detect import get_dep_identifier, find_external_dependency from ..dependencies.python import BasicPythonExternalProgram, python_factory, _PythonDependencyBase from ..interpreter import extract_required_kwarg, permitted_dependency_kwargs, primitives as P_OBJ from ..interpreter.interpreterobjects import _ExternalProgramHolder -from ..interpreter.type_checking import NoneType, PRESERVE_PATH_KW, SHARED_MOD_KWS +from ..interpreter.type_checking import NoneType, DEPENDENCY_KWS, PRESERVE_PATH_KW, SHARED_MOD_KWS from ..interpreterbase import ( noPosargs, noKwargs, permittedKwargs, ContainerTypeInfo, InvalidArguments, typed_pos_args, typed_kwargs, KwargInfo, @@ -256,6 +256,7 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): @permittedKwargs(permitted_dependency_kwargs | {'embed'}) @FeatureNewKwargs('python_installation.dependency', '0.53.0', ['embed']) @noPosargs + @typed_kwargs('python_installation.dependency', *DEPENDENCY_KWS, allow_unknown=True) @InterpreterObject.method('dependency') def dependency_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> 'Dependency': disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index c5f18e8..d0e8091 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -242,6 +242,10 @@ class RustModule(ExtensionModule): def doctest(self, state: ModuleState, args: T.Tuple[str, T.Union[SharedLibrary, StaticLibrary]], kwargs: FuncDoctest) -> ModuleReturnValue: name, base_target = args + if not base_target.uses_rust(): + raise MesonException('doc tests are only supported for Rust targets') + if not base_target.uses_rust_abi(): + raise MesonException("doc tests are not supported for rust_abi: 'c'") if state.environment.is_cross_build() and state.environment.need_exe_wrapper(base_target.for_machine): mlog.notice('skipping Rust doctests due to cross compilation', once=True) return ModuleReturnValue(None, []) diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py index b08d5e8..8d7dd0b 100644 --- a/mesonbuild/msetup.py +++ b/mesonbuild/msetup.py @@ -27,7 +27,6 @@ if T.TYPE_CHECKING: builddir: str sourcedir: str pager: bool - unset_opts: T.List[str] git_ignore_file = '''# This file is autogenerated by Meson. If you change or delete it, it won't be recreated. * @@ -194,22 +193,25 @@ class MesonApp: return self._generate(env, capture, vslite_ctx) def check_unused_options(self, coredata: 'coredata.CoreData', cmd_line_options: T.Dict[OptionKey, str], all_subprojects: T.Mapping[str, object]) -> None: - pending = coredata.optstore.pending_options errlist: T.List[str] = [] known_subprojects = all_subprojects.keys() - for opt in pending: - # It is not an error to set wrong option for unknown subprojects - # because they might be used in future reconfigurations - if coredata.optstore.accept_as_pending_option(opt, known_subprojects): + for opt in cmd_line_options: + # Accept options that exist or could appear in subsequent reconfigurations, + # including options for subprojects that were not used + if opt in coredata.optstore or \ + opt.evolve(subproject=None) in coredata.optstore or \ + coredata.optstore.accept_as_pending_option(opt): continue - if opt in cmd_line_options: - errlist.append(f'"{opt}"') + if opt.subproject and opt.subproject not in known_subprojects: + continue + # "foo=true" may also refer to toplevel project option ":foo" + if opt.subproject is None and coredata.optstore.is_project_option(opt.as_root()): + continue + errlist.append(f'"{opt}"') if errlist: errstr = ', '.join(errlist) raise MesonException(f'Unknown options: {errstr}') - coredata.optstore.clear_pending() - def _generate(self, env: environment.Environment, capture: bool, vslite_ctx: T.Optional[dict]) -> T.Optional[dict]: # Get all user defined options, including options that have been defined # during a previous invocation or using meson configure. diff --git a/mesonbuild/msubprojects.py b/mesonbuild/msubprojects.py index c74283c..d4549c0 100755 --- a/mesonbuild/msubprojects.py +++ b/mesonbuild/msubprojects.py @@ -4,6 +4,7 @@ from dataclasses import dataclass, InitVar import os, subprocess import argparse import asyncio +import fnmatch import threading import copy import shutil @@ -640,9 +641,14 @@ def add_common_arguments(p: argparse.ArgumentParser) -> None: p.add_argument('--allow-insecure', default=False, action='store_true', help='Allow insecure server connections.') -def add_subprojects_argument(p: argparse.ArgumentParser) -> None: - p.add_argument('subprojects', nargs='*', - help='List of subprojects (default: all)') +def add_subprojects_argument(p: argparse.ArgumentParser, name: str = None) -> None: + helpstr = 'Patterns of subprojects to operate on (default: all)' + if name: + p.add_argument(name, dest='subprojects', metavar='pattern', nargs=1, action='append', + default=[], help=helpstr) + else: + p.add_argument('subprojects', metavar='pattern', nargs='*', default=[], + help=helpstr) def add_wrap_update_parser(subparsers: 'SubParsers') -> argparse.ArgumentParser: p = subparsers.add_parser('update', help='Update wrap files from WrapDB (Since 0.63.0)') @@ -692,7 +698,7 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: p.add_argument('args', nargs=argparse.REMAINDER, help=argparse.SUPPRESS) add_common_arguments(p) - p.set_defaults(subprojects=[]) + add_subprojects_argument(p, '--filter') p.set_defaults(subprojects_func=Runner.foreach) p = subparsers.add_parser('purge', help='Remove all wrap-based subproject artifacts') @@ -724,7 +730,8 @@ def run(options: 'Arguments') -> int: return 0 r = Resolver(source_dir, subproject_dir, wrap_frontend=True, allow_insecure=options.allow_insecure, silent=True) if options.subprojects: - wraps = [wrap for name, wrap in r.wraps.items() if name in options.subprojects] + wraps = [wrap for name, wrap in r.wraps.items() + if any(fnmatch.fnmatch(name, pat) for pat in options.subprojects)] else: wraps = list(r.wraps.values()) types = [t.strip() for t in options.types.split(',')] if options.types else [] diff --git a/mesonbuild/options.py b/mesonbuild/options.py index bc4d79f..988b4f3 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -321,6 +321,7 @@ class UserOption(T.Generic[_T], HoldableObject): yielding: bool = DEFAULT_YIELDING deprecated: DeprecatedType = False readonly: bool = dataclasses.field(default=False) + parent: T.Optional[UserOption] = None def __post_init__(self, value_: _T) -> None: self.value = self.validate_value(value_) @@ -805,6 +806,7 @@ class OptionStore: def __init__(self, is_cross: bool) -> None: self.options: T.Dict['OptionKey', 'AnyOptionType'] = {} + self.subprojects: T.Set[str] = set() self.project_options: T.Set[OptionKey] = set() self.module_options: T.Set[OptionKey] = set() from .compilers import all_languages @@ -812,13 +814,11 @@ class OptionStore: self.augments: OptionDict = {} self.is_cross = is_cross - # Pending options are options that need to be initialized later, either - # configuration dependent options like compiler options, or options for - # a different subproject + # Pending options are configuration dependent options that could be + # initialized later, such as compiler options self.pending_options: OptionDict = {} - - def clear_pending(self) -> None: - self.pending_options = {} + # Subproject options from toplevel project() + self.pending_subproject_options: OptionDict = {} def ensure_and_validate_key(self, key: T.Union[OptionKey, str]) -> OptionKey: if isinstance(key, str): @@ -854,40 +854,33 @@ class OptionStore: potential = self.options.get(key, None) if self.is_project_option(key): assert key.subproject is not None - if potential is not None and potential.yielding: - parent_key = key.as_root() - try: - parent_option = self.options[parent_key] - except KeyError: - # Subproject is set to yield, but top level - # project does not have an option of the same - # name. Return the subproject option. - return potential - # If parent object has different type, do not yield. - # This should probably be an error. - if type(parent_option) is type(potential): - return parent_option - return potential if potential is None: raise KeyError(f'Tried to access nonexistant project option {key}.') - return potential else: if potential is None: parent_key = OptionKey(key.name, subproject=None, machine=key.machine) if parent_key not in self.options: raise KeyError(f'Tried to access nonexistant project parent option {parent_key}.') + # This is a global option but it can still have per-project + # augment, so return the subproject key. return self.options[parent_key] - return potential + return potential def get_value_object_and_value_for(self, key: OptionKey) -> T.Tuple[AnyOptionType, ElementaryOptionValues]: assert isinstance(key, OptionKey) vobject = self.get_value_object_for(key) computed_value = vobject.value - if key.subproject is not None: - if key in self.augments: - computed_value = vobject.validate_value(self.augments[key]) + if key in self.augments: + assert key.subproject is not None + computed_value = self.augments[key] + elif vobject.yielding: + computed_value = vobject.parent.value return (vobject, computed_value) + def option_has_value(self, key: OptionKey, value: ElementaryOptionValues) -> bool: + vobject, current_value = self.get_value_object_and_value_for(key) + return vobject.validate_value(value) == current_value + def get_value_for(self, name: 'T.Union[OptionKey, str]', subproject: T.Optional[str] = None) -> ElementaryOptionValues: if isinstance(name, str): key = OptionKey(name, subproject) @@ -932,6 +925,19 @@ class OptionStore: assert key.subproject is not None if key in self.options: raise MesonException(f'Internal error: tried to add a project option {key} that already exists.') + if valobj.yielding and key.subproject: + parent_key = key.as_root() + try: + parent_option = self.options[parent_key] + # If parent object has different type, do not yield. + # This should probably be an error. + if type(parent_option) is type(valobj): + valobj.parent = parent_option + except KeyError: + # Subproject is set to yield, but top level + # project does not have an option of the same + pass + valobj.yielding = bool(valobj.parent) self.options[key] = valobj self.project_options.add(key) @@ -998,6 +1004,7 @@ class OptionStore: return value.as_posix() def set_option(self, key: OptionKey, new_value: ElementaryOptionValues, first_invocation: bool = False) -> bool: + changed = False error_key = key if error_key.subproject == '': error_key = error_key.evolve(subproject=None) @@ -1034,13 +1041,19 @@ class OptionStore: elif isinstance(opt.deprecated, str): mlog.deprecation(f'Option "{error_key}" is replaced by {opt.deprecated!r}') # Change both this aption and the new one pointed to. - dirty = self.set_option(key.evolve(name=opt.deprecated), new_value) - dirty |= opt.set_value(new_value) - return dirty + changed |= self.set_option(key.evolve(name=opt.deprecated), new_value, first_invocation) - old_value = opt.value - changed = opt.set_value(new_value) + new_value = opt.validate_value(new_value) + if key in self.options: + old_value = opt.value + opt.set_value(new_value) + opt.yielding = False + else: + assert key.subproject is not None + old_value = self.augments.get(key, opt.value) + self.augments[key] = new_value + changed |= old_value != new_value if opt.readonly and changed and not first_invocation: raise MesonException(f'Tried to modify read only option "{error_key}"') @@ -1054,12 +1067,12 @@ class OptionStore: optimization, debug = self.DEFAULT_DEPENDENTS[new_value] dkey = key.evolve(name='debug') optkey = key.evolve(name='optimization') - self.options[dkey].set_value(debug) - self.options[optkey].set_value(optimization) + self.set_option(dkey, debug, first_invocation) + self.set_option(optkey, optimization, first_invocation) return changed - def set_option_maybe_root(self, o: OptionKey, new_value: ElementaryOptionValues, first_invocation: bool = False) -> bool: + def set_user_option(self, o: OptionKey, new_value: ElementaryOptionValues, first_invocation: bool = False) -> bool: if not self.is_cross and o.is_for_build(): return False @@ -1070,42 +1083,51 @@ class OptionStore: # can be either # # A) a system option in which case the subproject is None - # B) a project option, in which case the subproject is '' (this method is only called from top level) + # B) a project option, in which case the subproject is '' # # The key parsing function can not handle the difference between the two # and defaults to A. if o in self.options: return self.set_option(o, new_value, first_invocation) + + # could also be an augment... + global_option = o.evolve(subproject=None) + if o.subproject is not None and global_option in self.options: + return self.set_option(o, new_value, first_invocation) + if self.accept_as_pending_option(o, first_invocation=first_invocation): old_value = self.pending_options.get(o, None) self.pending_options[o] = new_value - return old_value is None or str(old_value) == new_value - else: + return old_value is None or str(old_value) != new_value + elif o.subproject is None: o = o.as_root() return self.set_option(o, new_value, first_invocation) + else: + raise MesonException(f'Unknown option: "{o}".') - def set_from_configure_command(self, D_args: T.List[str], U_args: T.List[str]) -> bool: + def set_from_configure_command(self, D_args: T.Dict[OptionKey, T.Optional[str]]) -> bool: dirty = False - D_args = [] if D_args is None else D_args - (global_options, perproject_global_options, project_options) = self.classify_D_arguments(D_args) - U_args = [] if U_args is None else U_args - for key, valstr in global_options: - dirty |= self.set_option_maybe_root(key, valstr) - for key, valstr in project_options: - dirty |= self.set_option_maybe_root(key, valstr) - for key, valstr in perproject_global_options: - if key in self.augments: - if self.augments[key] != valstr: - self.augments[key] = valstr - dirty = True - else: - self.augments[key] = valstr - dirty = True - for keystr in U_args: - key = OptionKey.from_string(keystr) + for key, valstr in D_args.items(): + if valstr is not None: + dirty |= self.set_user_option(key, valstr) + continue + if key in self.augments: del self.augments[key] dirty = True + else: + # TODO: For project options, "dropping an augment" means going + # back to the superproject's value. However, it's confusing + # that -U does not simply remove the option from the stored + # cmd_line_options. This may cause "meson setup --wipe" to + # have surprising behavior. For this to work, UserOption + # should only store the default value and the option values + # should be stored with their source (project(), subproject(), + # machine file, command line). This way the effective value + # can be easily recomputed. + opt = self.get_value_object(key) + dirty |= not opt.yielding and bool(opt.parent) + opt.yielding = bool(opt.parent) return dirty def reset_prefixed_options(self, old_prefix: str, new_prefix: str) -> None: @@ -1226,24 +1248,6 @@ class OptionStore: def is_module_option(self, key: OptionKey) -> bool: return key in self.module_options - def classify_D_arguments(self, D: T.List[str]) -> T.Tuple[T.List[T.Tuple[OptionKey, str]], - T.List[T.Tuple[OptionKey, str]], - T.List[T.Tuple[OptionKey, str]]]: - global_options = [] - project_options = [] - perproject_global_options = [] - for setval in D: - keystr, valstr = setval.split('=', 1) - key = OptionKey.from_string(keystr) - valuetuple = (key, valstr) - if self.is_project_option(key): - project_options.append(valuetuple) - elif key.subproject is None: - global_options.append(valuetuple) - else: - perproject_global_options.append(valuetuple) - return (global_options, perproject_global_options, project_options) - def prefix_split_options(self, coll: OptionDict) -> T.Tuple[T.Optional[str], OptionDict]: prefix = None others_d: OptionDict = {} @@ -1305,15 +1309,15 @@ class OptionStore: if not self.is_cross and key.is_for_build(): continue if key.subproject: - # do apply project() default_options for subprojects here, because - # they have low priority - self.pending_options[key] = valstr + # Subproject options from toplevel project() have low priority + # and will be processed when the subproject is found + self.pending_subproject_options[key] = valstr else: # Setting a project option with default_options # should arguably be a hard error; the default # value of project option should be set in the option # file, not in the project call. - self.set_option_maybe_root(key, valstr, True) + self.set_user_option(key, valstr, True) # ignore subprojects for now for machine file and command line # options; they are applied later @@ -1323,25 +1327,18 @@ class OptionStore: if not self.is_cross and key.is_for_build(): continue if not key.subproject: - self.set_option_maybe_root(key, valstr, True) + self.set_user_option(key, valstr, True) for key, valstr in cmd_line_options.items(): # Due to backwards compatibility we ignore all build-machine options # when building natively. if not self.is_cross and key.is_for_build(): continue if not key.subproject: - self.set_option_maybe_root(key, valstr, True) + self.set_user_option(key, valstr, True) - def accept_as_pending_option(self, key: OptionKey, known_subprojects: T.Optional[T.Container[str]] = None, - first_invocation: bool = False) -> bool: - # Fail on unknown options that we can know must exist at this point in time. - # Subproject and compiler options are resolved later. - # + def accept_as_pending_option(self, key: OptionKey, first_invocation: bool = False) -> bool: # Some base options (sanitizers etc) might get added later. # Permitting them all is not strictly correct. - if key.subproject: - if known_subprojects is None or key.subproject not in known_subprojects: - return True if self.is_compiler_option(key): return True if first_invocation and self.is_backend_option(key): @@ -1365,23 +1362,40 @@ class OptionStore: project_default_options: OptionDict, cmd_line_options: OptionDict, machine_file_options: OptionDict) -> None: - # pick up pending per-project settings from the toplevel project() invocation - options = {k: v for k, v in self.pending_options.items() if k.subproject == subproject} - # apply project() and subproject() default_options - for key, valstr in itertools.chain(project_default_options.items(), spcall_default_options.items()): + options: OptionDict = {} + + # project() default_options + for key, valstr in project_default_options.items(): + if key.subproject == subproject: + without_subp = key.evolve(subproject=None) + raise MesonException(f'subproject name not needed in default_options; use "{without_subp}" instead of "{key}"') + if key.subproject is None: key = key.evolve(subproject=subproject) - elif key.subproject == subproject: + options[key] = valstr + + # augments from the toplevel project() default_options + for key, valstr in self.pending_subproject_options.items(): + if key.subproject == subproject: + options[key] = valstr + + # subproject() default_options + for key, valstr in spcall_default_options.items(): + if key.subproject == subproject: without_subp = key.evolve(subproject=None) raise MesonException(f'subproject name not needed in default_options; use "{without_subp}" instead of "{key}"') + + if key.subproject is None: + key = key.evolve(subproject=subproject) options[key] = valstr # then global settings from machine file and command line + # **but not if they are toplevel project options** for key, valstr in itertools.chain(machine_file_options.items(), cmd_line_options.items()): - if key.subproject is None: + if key.subproject is None and not self.is_project_option(key.as_root()): subp_key = key.evolve(subproject=subproject) - self.pending_options.pop(subp_key, None) + self.pending_subproject_options.pop(subp_key, None) options.pop(subp_key, None) # then finally per project augments from machine file and command line @@ -1391,12 +1405,21 @@ class OptionStore: # merge everything that has been computed above, while giving self.augments priority for key, valstr in options.items(): + if key.subproject != subproject: + if key.subproject in self.subprojects and not self.option_has_value(key, valstr): + mlog.warning('option {key} is set in subproject {subproject} but has already been processed') + continue + + # Subproject options from project() will be processed when the subproject is found + self.pending_subproject_options[key] = valstr + continue + + self.pending_subproject_options.pop(key, None) self.pending_options.pop(key, None) - valstr = self.augments.pop(key, valstr) - if key in self.project_options: - self.set_option(key, valstr, True) - else: - self.augments[key] = valstr + if key not in self.augments: + self.set_user_option(key, valstr, True) + + self.subprojects.add(subproject) def update_project_options(self, project_options: MutableKeyedOptionDictType, subproject: SubProject) -> None: for key, value in project_options.items(): diff --git a/mesonbuild/scripts/clangtidy.py b/mesonbuild/scripts/clangtidy.py index 550faee..e5f7024 100644 --- a/mesonbuild/scripts/clangtidy.py +++ b/mesonbuild/scripts/clangtidy.py @@ -11,7 +11,7 @@ import os import shutil import sys -from .run_tool import run_clang_tool, run_with_buffered_output +from .run_tool import run_with_buffered_output, run_clang_tool_on_sources from ..environment import detect_clangtidy, detect_clangapply import typing as T @@ -56,7 +56,7 @@ def run(args: T.List[str]) -> int: fixesdir.unlink() fixesdir.mkdir(parents=True) - tidyret = run_clang_tool('clang-tidy', srcdir, builddir, run_clang_tidy, tidyexe, builddir, fixesdir) + tidyret = run_clang_tool_on_sources('clang-tidy', srcdir, builddir, run_clang_tidy, tidyexe, builddir, fixesdir) if fixesdir is not None: print('Applying fix-its...') applyret = subprocess.run(applyexe + ['-format', '-style=file', '-ignore-insert-conflict', fixesdir]).returncode diff --git a/mesonbuild/scripts/run_tool.py b/mesonbuild/scripts/run_tool.py index e206ff7..6181c6d 100644 --- a/mesonbuild/scripts/run_tool.py +++ b/mesonbuild/scripts/run_tool.py @@ -128,6 +128,26 @@ def run_clang_tool(name: str, srcdir: Path, builddir: Path, fn: T.Callable[..., yield fn(path, *args) return asyncio.run(_run_workers(all_clike_files(name, srcdir, builddir), wrapper)) +def run_clang_tool_on_sources(name: str, srcdir: Path, builddir: Path, fn: T.Callable[..., T.Coroutine[None, None, int]], *args: T.Any) -> int: + if sys.platform == 'win32': + asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + + source_files = set() + with open('meson-info/intro-targets.json', encoding='utf-8') as fp: + targets = json.load(fp) + + for target in targets: + for target_source in target.get('target_sources') or []: + for source in target_source.get('sources') or []: + source_files.add(Path(source)) + + clike_files = set(all_clike_files(name, srcdir, builddir)) + source_files = source_files.intersection(clike_files) + + def wrapper(path: Path) -> T.Iterable[T.Coroutine[None, None, int]]: + yield fn(path, *args) + return asyncio.run(_run_workers(source_files, wrapper)) + def run_tool_on_targets(fn: T.Callable[[T.Dict[str, T.Any]], T.Iterable[T.Coroutine[None, None, int]]]) -> int: if sys.platform == 'win32': diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py index 4b656a6..0628310 100644 --- a/mesonbuild/utils/universal.py +++ b/mesonbuild/utils/universal.py @@ -433,7 +433,7 @@ class File(HoldableObject): absdir = srcdir if self.is_built: absdir = builddir - return os.path.join(absdir, self.relative_name()) + return os.path.normpath(os.path.join(absdir, self.relative_name())) @property def suffix(self) -> str: diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index c8eff69..1cc2cee 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -57,7 +57,21 @@ WHITELIST_SUBDOMAIN = 'wrapdb.mesonbuild.com' ALL_TYPES = ['file', 'git', 'hg', 'svn', 'redirect'] -PATCH = shutil.which('patch') +if mesonlib.is_windows(): + from ..programs import ExternalProgram + from ..mesonlib import version_compare + _exclude_paths: T.List[str] = [] + while True: + _patch = ExternalProgram('patch', silent=True, exclude_paths=_exclude_paths) + if not _patch.found(): + break + if version_compare(_patch.get_version(), '>=2.6.1'): + break + _exclude_paths.append(os.path.dirname(_patch.get_path())) + PATCH = _patch.get_path() if _patch.found() else None +else: + PATCH = shutil.which('patch') + def whitelist_wrapdb(urlstr: str) -> urllib.parse.ParseResult: """ raises WrapException if not whitelisted subdomain """ @@ -233,6 +247,15 @@ class PackageDefinition: wrap.original_filename = filename wrap.parse_provide_section(config) + patch_url = values.get('patch_url') + if patch_url and patch_url.startswith('https://wrapdb.mesonbuild.com/v1'): + if name == 'sqlite': + mlog.deprecation('sqlite wrap has been renamed to sqlite3, update using `meson wrap install sqlite3`') + elif name == 'libjpeg': + mlog.deprecation('libjpeg wrap has been renamed to libjpeg-turbo, update using `meson wrap install libjpeg-turbo`') + else: + mlog.deprecation(f'WrapDB v1 is deprecated, updated using `meson wrap update {name}`') + with open(filename, 'r', encoding='utf-8') as file: wrap.wrapfile_hash = hashlib.sha256(file.read().encode('utf-8')).hexdigest() @@ -331,6 +354,7 @@ class Resolver: self.wrapdb: T.Dict[str, T.Any] = {} self.wrapdb_provided_deps: T.Dict[str, str] = {} self.wrapdb_provided_programs: T.Dict[str, str] = {} + self.loaded_dirs: T.Set[str] = set() self.load_wraps() self.load_netrc() self.load_wrapdb() @@ -372,6 +396,7 @@ class Resolver: # Add provided deps and programs into our lookup tables for wrap in self.wraps.values(): self.add_wrap(wrap) + self.loaded_dirs.add(self.subdir) def add_wrap(self, wrap: PackageDefinition) -> None: for k in wrap.provided_deps.keys(): @@ -416,16 +441,25 @@ class Resolver: def _merge_wraps(self, other_resolver: 'Resolver') -> None: for k, v in other_resolver.wraps.items(): - self.wraps.setdefault(k, v) - for k, v in other_resolver.provided_deps.items(): - self.provided_deps.setdefault(k, v) - for k, v in other_resolver.provided_programs.items(): - self.provided_programs.setdefault(k, v) + prev_wrap = self.wraps.get(v.directory) + if prev_wrap and prev_wrap.type is None and v.type is not None: + # This happens when a subproject has been previously downloaded + # using a wrap from another subproject and the wrap-redirect got + # deleted. In that case, the main project created a bare wrap + # for the download directory, but now we have a proper wrap. + # It also happens for wraps coming from Cargo.lock files, which + # don't create wrap-redirect. + del self.wraps[v.directory] + del self.provided_deps[v.directory] + if k not in self.wraps: + self.wraps[k] = v + self.add_wrap(v) def load_and_merge(self, subdir: str, subproject: SubProject) -> None: - if self.wrap_mode != WrapMode.nopromote: + if self.wrap_mode != WrapMode.nopromote and subdir not in self.loaded_dirs: other_resolver = Resolver(self.source_dir, subdir, subproject, self.wrap_mode, self.wrap_frontend, self.allow_insecure, self.silent) self._merge_wraps(other_resolver) + self.loaded_dirs.add(subdir) def find_dep_provider(self, packagename: str) -> T.Tuple[T.Optional[str], T.Optional[str]]: # Python's ini parser converts all key values to lowercase. @@ -720,6 +754,23 @@ class Resolver: resp = open_wrapdburl(urlstring, allow_insecure=self.allow_insecure, have_opt=self.wrap_frontend) elif WHITELIST_SUBDOMAIN in urlstring: raise WrapException(f'{urlstring} may be a WrapDB-impersonating URL') + elif url.scheme == 'sftp': + sftp = shutil.which('sftp') + if sftp is None: + raise WrapException('Scheme sftp is not available. Install sftp to enable it.') + with tempfile.TemporaryDirectory() as workdir, \ + tempfile.NamedTemporaryFile(mode='wb', dir=self.cachedir, delete=False) as tmpfile: + args = [] + # Older versions of the sftp client cannot handle URLs, hence the splitting of url below + if url.port: + args += ['-P', f'{url.port}'] + user = f'{url.username}@' if url.username else '' + command = [sftp, '-o', 'KbdInteractiveAuthentication=no', *args, f'{user}{url.hostname}:{url.path[1:]}'] + subprocess.run(command, cwd=workdir, check=True) + downloaded = os.path.join(workdir, os.path.basename(url.path)) + tmpfile.close() + shutil.move(downloaded, tmpfile.name) + return self.hash_file(tmpfile.name), tmpfile.name else: headers = { 'User-Agent': f'mesonbuild/{coredata.version}', @@ -744,7 +795,7 @@ class Resolver: resp = urllib.request.urlopen(req, timeout=REQ_TIMEOUT) except OSError as e: mlog.log(str(e)) - raise WrapException(f'could not get {urlstring} is the internet available?') + raise WrapException(f'could not get {urlstring}; is the internet available?') with contextlib.closing(resp) as resp, tmpfile as tmpfile: try: dlsize = int(resp.info()['Content-Length']) @@ -775,14 +826,17 @@ class Resolver: hashvalue = h.hexdigest() return hashvalue, tmpfile.name + def hash_file(self, path: str) -> str: + h = hashlib.sha256() + with open(path, 'rb') as f: + h.update(f.read()) + return h.hexdigest() + def check_hash(self, what: str, path: str, hash_required: bool = True) -> None: if what + '_hash' not in self.wrap.values and not hash_required: return expected = self.wrap.get(what + '_hash').lower() - h = hashlib.sha256() - with open(path, 'rb') as f: - h.update(f.read()) - dhash = h.hexdigest() + dhash = self.hash_file(path) if dhash != expected: raise WrapException(f'Incorrect hash for {what}:\n {expected} expected\n {dhash} actual.') diff --git a/packaging/builddist.py b/packaging/builddist.py new file mode 100755 index 0000000..5cf3b02 --- /dev/null +++ b/packaging/builddist.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2025 The Meson development team + +# This script must be run from the source root. + +import pathlib, shutil, subprocess + +gendir = pathlib.Path('distgendir') +distdir = pathlib.Path('dist') +gitdir = pathlib.Path('.git') + +if distdir.is_dir(): + shutil.rmtree(distdir) +distdir.mkdir() + +if gendir.is_dir(): + shutil.rmtree(gendir) +gendir.mkdir() + +shutil.copytree(gitdir, gendir / '.git') + +subprocess.check_call(['git', 'reset', '--hard'], + cwd=gendir) +subprocess.check_call(['python3', 'setup.py', 'sdist', 'bdist_wheel'], + cwd=gendir) +for f in (gendir / 'dist').glob('*'): + shutil.copy(f, distdir) + +shutil.rmtree(gendir) + diff --git a/packaging/builddist.sh b/packaging/builddist.sh deleted file mode 100755 index edcf3ec..0000000 --- a/packaging/builddist.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/zsh - -# This script must be run from the source root. - -set -e - -GENDIR=distgendir - -rm -rf dist -rm -rf $GENDIR -mkdir dist -mkdir $GENDIR -cp -r .git $GENDIR -cd $GENDIR -git reset --hard -python3 setup.py sdist bdist_wheel -cp dist/* ../dist -cd .. -rm -rf $GENDIR diff --git a/run_format_tests.py b/run_format_tests.py index 30c9758..dee787b 100755 --- a/run_format_tests.py +++ b/run_format_tests.py @@ -51,7 +51,7 @@ def check_format() -> None: 'work area', '.eggs', '_cache', # e.g. .mypy_cache 'venv', # virtualenvs have DOS line endings - '120 rewrite', # we explicitly test for tab in meson.build file + '121 rewrite', # we explicitly test for tab in meson.build file '3 editorconfig', } for (root, _, filenames) in os.walk('.'): diff --git a/run_meson_command_tests.py b/run_meson_command_tests.py index 7265d3e..c2a621b 100755 --- a/run_meson_command_tests.py +++ b/run_meson_command_tests.py @@ -46,6 +46,11 @@ def get_pybindir(): return sysconfig.get_path('scripts', scheme=scheme, vars={'base': ''}).strip('\\/') return sysconfig.get_path('scripts', vars={'base': ''}).strip('\\/') +def has_python_module(module: str) -> bool: + result = subprocess.run(python_command + ['-c', f'import {module}']) + return result.returncode == 0 + + class CommandTests(unittest.TestCase): ''' Test that running meson in various ways works as expected by checking the @@ -79,9 +84,13 @@ class CommandTests(unittest.TestCase): # If this call hangs CI will just abort. It is very hard to distinguish # between CI issue and test bug in that case. Set timeout and fail loud # instead. - p = subprocess.run(command, stdout=subprocess.PIPE, - env=env, text=True, - cwd=workdir, timeout=60 * 5) + p = subprocess.run(command, + stdout=subprocess.PIPE, + env=env, + encoding='utf-8', + text=True, + cwd=workdir, + timeout=60 * 5) print(p.stdout) if p.returncode != 0: raise subprocess.CalledProcessError(p.returncode, command) @@ -141,11 +150,17 @@ class CommandTests(unittest.TestCase): # distutils complains that prefix isn't contained in PYTHONPATH os.environ['PYTHONPATH'] = os.path.join(str(pylibdir), '') os.environ['PATH'] = str(bindir) + os.pathsep + os.environ['PATH'] - self._run(python_command + ['setup.py', 'install', '--prefix', str(prefix)]) - # Fix importlib-metadata by appending all dirs in pylibdir - PYTHONPATHS = [pylibdir] + [x for x in pylibdir.iterdir() if x.name.endswith('.egg')] - PYTHONPATHS = [os.path.join(str(x), '') for x in PYTHONPATHS] - os.environ['PYTHONPATH'] = os.pathsep.join(PYTHONPATHS) + if has_python_module('gpep517'): + self._run(python_command + ['-m', 'gpep517', 'install-from-source', '--destdir', '/', '--prefix', str(prefix)]) + elif has_python_module('pip'): + self._run(python_command + ['-m', 'pip', 'install', '--prefix', str(prefix), '.']) + else: + # Legacy deprecated setuptools command used as fallback + self._run(python_command + ['setup.py', 'install', '--prefix', str(prefix)]) + # Fix importlib-metadata by appending all dirs in pylibdir + PYTHONPATHS = [pylibdir] + [x for x in pylibdir.iterdir() if x.name.endswith('.egg')] + PYTHONPATHS = [os.path.join(str(x), '') for x in PYTHONPATHS] + os.environ['PYTHONPATH'] = os.pathsep.join(PYTHONPATHS) # Check that all the files were installed correctly self.assertTrue(bindir.is_dir()) self.assertTrue(pylibdir.is_dir()) diff --git a/run_shell_checks.py b/run_shell_checks.py new file mode 100755 index 0000000..f929d80 --- /dev/null +++ b/run_shell_checks.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2025 The Meson development team + +import pathlib +import sys + +# DO NOT ADD FILES IN THIS LIST! +# They are here because they got added +# in the past before this was properly checked. +# Instead you should consider removing things +# from this list by rewriting them to Python. +# +# The CI scripts probably need to remain shell +# scripts due to the way the CI systems work. + +permitted_files = ( + 'ci/ciimage/common.sh', + 'ci/intel-scripts/cache_exclude_windows.sh', + 'ci/ciimage/opensuse/install.sh', + 'ci/ciimage/ubuntu-rolling/install.sh', + 'ci/ciimage/ubuntu-rolling/test.sh', + 'ci/ciimage/cuda-cross/install.sh', + 'ci/ciimage/cuda/install.sh', + 'ci/ciimage/bionic/install.sh', + 'ci/ciimage/fedora/install.sh', + 'ci/ciimage/arch/install.sh', + 'ci/ciimage/gentoo/install.sh', + 'manual tests/4 standalone binaries/myapp.sh', + 'manual tests/4 standalone binaries/osx_bundler.sh', + 'manual tests/4 standalone binaries/linux_bundler.sh', + 'manual tests/4 standalone binaries/build_osx_package.sh', + 'manual tests/4 standalone binaries/build_linux_package.sh', + 'test cases/failing test/3 ambiguous/test_runner.sh', + 'test cases/common/190 install_mode/runscript.sh', + 'test cases/common/48 file grabber/grabber.sh', + 'test cases/common/12 data/runscript.sh', + 'test cases/common/33 run program/scripts/hello.sh', + ) + + +def check_bad_files(filename_glob): + num_errors = 0 + for f in pathlib.Path('.').glob(f'**/{filename_glob}'): + if str(f) not in permitted_files: + print('Forbidden file type:', f) + num_errors += 1 + return num_errors + +def check_deletions(): + num_errors = 0 + for f in permitted_files: + p = pathlib.Path(f) + if not p.is_file(): + print('Exception list has a file that does not exist:', f) + num_errors += 1 + return num_errors + +def check_shell_usage(): + total_errors = 0 + total_errors += check_bad_files('Makefile') + total_errors += check_bad_files('*.sh') + total_errors += check_bad_files('*.awk') + total_errors += check_deletions() + return total_errors + +if __name__ == '__main__': + sys.exit(check_shell_usage()) + diff --git a/run_unittests.py b/run_unittests.py index 84edb34..6a84f5c 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -11,43 +11,12 @@ import time import subprocess import os import unittest +import importlib +import typing as T -import mesonbuild.mlog -import mesonbuild.depfile -import mesonbuild.dependencies.base -import mesonbuild.dependencies.factory -import mesonbuild.compilers -import mesonbuild.envconfig -import mesonbuild.environment import mesonbuild.coredata -import mesonbuild.modules.gnome from mesonbuild.mesonlib import python_command, setup_vsenv -import mesonbuild.modules.pkgconfig -from unittests.allplatformstests import AllPlatformTests -from unittests.cargotests import CargoVersionTest, CargoCfgTest, CargoLockTest -from unittests.darwintests import DarwinTests -from unittests.failuretests import FailureTests -from unittests.linuxcrosstests import LinuxCrossArmTests, LinuxCrossMingwTests -from unittests.machinefiletests import NativeFileTests, CrossFileTests -from unittests.rewritetests import RewriterTests -from unittests.taptests import TAPParserTests -from unittests.datatests import DataTests -from unittests.internaltests import InternalTests -from unittests.linuxliketests import LinuxlikeTests -from unittests.pythontests import PythonTests -from unittests.subprojectscommandtests import SubprojectsCommandTests -from unittests.windowstests import WindowsTests -from unittests.platformagnostictests import PlatformAgnosticTests - -def unset_envs(): - # For unit tests we must fully control all command lines - # so that there are no unexpected changes coming from the - # environment, for example when doing a package build. - varnames = ['CPPFLAGS', 'LDFLAGS'] + list(mesonbuild.compilers.compilers.CFLAGS_MAPPING.values()) - for v in varnames: - if v in os.environ: - del os.environ[v] def convert_args(argv): # If we got passed a list of tests, pass it on @@ -100,15 +69,33 @@ def setup_backend(): os.environ['MESON_UNIT_TEST_BACKEND'] = be sys.argv = filtered +def import_test_cases(suite: unittest.TestSuite) -> T.Set[str]: + ''' + Imports all test classes into the current module and returns their names + ''' + classes = set() + for test in suite: + if isinstance(test, unittest.TestSuite): + classes.update(import_test_cases(test)) + elif isinstance(test, unittest.TestCase): + mod = importlib.import_module(test.__module__) + class_name = test.__class__.__name__ + test_class = getattr(mod, class_name) + classes.add(class_name) + setattr(sys.modules[__name__], class_name, test_class) + return classes + +def discover_test_cases() -> T.Set[str]: + current_dir = os.path.dirname(os.path.realpath(__file__)) + loader = unittest.TestLoader() + suite = loader.discover(os.path.join(current_dir, 'unittests'), '*tests.py', current_dir) + if loader.errors: + raise SystemExit(loader.errors) + return import_test_cases(suite) + def main(): - unset_envs() + cases = sorted(discover_test_cases()) setup_backend() - cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests', - 'PythonTests', 'NativeFileTests', 'RewriterTests', 'CrossFileTests', - 'TAPParserTests', 'SubprojectsCommandTests', 'PlatformAgnosticTests', - - 'LinuxlikeTests', 'LinuxCrossArmTests', 'LinuxCrossMingwTests', - 'WindowsTests', 'DarwinTests'] try: import pytest # noqa: F401 diff --git a/test cases/common/104 has arg/meson.build b/test cases/common/104 has arg/meson.build index 466bed9..500b8a9 100644 --- a/test cases/common/104 has arg/meson.build +++ b/test cases/common/104 has arg/meson.build @@ -56,9 +56,22 @@ if cpp.get_id() == 'gcc' and cpp.version().version_compare('>=12.1.0') # Handle special -Wno-attributes=foo where -Wattributes=foo is invalid # i.e. our usual -Wno-foo -Wfoo hack doesn't work for -Wattributes=foo. assert(cpp.has_argument('-Wno-attributes=meson::i_do_not_exist')) - # Likewise, the positive counterpart to -Wno-vla-larger-than is - # -Wvla-larger-than=N - assert(cpp.has_argument('-Wno-vla-larger-than')) +endif + +if cpp.get_id() == 'gcc' + # Handle negative flags whose positive counterparts require a value to be + # specified. + if cpp.version().version_compare('>=4.4.0') + assert(cpp.has_argument('-Wno-frame-larger-than')) + endif + if cpp.version().version_compare('>=4.7.0') + assert(cpp.has_argument('-Wno-stack-usage')) + endif + if cpp.version().version_compare('>=7.1.0') + assert(cpp.has_argument('-Wno-alloc-size-larger-than')) + assert(cpp.has_argument('-Wno-alloca-larger-than')) + assert(cpp.has_argument('-Wno-vla-larger-than')) + endif endif if cc.get_id() == 'clang' and cc.version().version_compare('<=4.0.0') diff --git a/test cases/common/220 fs module/meson.build b/test cases/common/220 fs module/meson.build index e5397ee..383b263 100644 --- a/test cases/common/220 fs module/meson.build +++ b/test cases/common/220 fs module/meson.build @@ -52,6 +52,7 @@ 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') + assert(fs.is_absolute('//foo'), 'is_absolute failed on incomplete UNC path') else assert(fs.is_absolute(unixabs), 'is_absolute unix not detected') assert(not fs.is_absolute(winabs), 'is_absolute windows false positive') @@ -84,7 +85,7 @@ assert(new == 'foo', 'replace_suffix did not only delete last suffix') # `/` on windows is interpreted like `.drive` which in general may not be `c:/` # the files need not exist for fs.replace_suffix() -original = is_windows ? 'j:/foo/bar.txt' : '/foo/bar.txt' +original = is_windows ? 'j:\\foo\\bar.txt' : '/foo/bar.txt' new_check = is_windows ? 'j:\\foo\\bar.ini' : '/foo/bar.ini' new = fs.replace_suffix(original, '.ini') @@ -139,11 +140,7 @@ endif # parts of path assert(fs.parent('foo/bar') == 'foo', 'failed to get dirname') -if not is_windows -assert(fs.parent(f[1]) == 'subdir/..', 'failed to get dirname') -else -assert(fs.parent(f[1]) == 'subdir\..', 'failed to get dirname') -endif +assert(fs.parent(f[1]) == 'subdir/..', 'failed to get dirname for file') assert(fs.parent(btgt) == '.', 'failed to get dirname for build target') assert(fs.parent(ctgt) == '.', 'failed to get dirname for custom target') assert(fs.parent(ctgt[0]) == '.', 'failed to get dirname for custom target index') @@ -153,8 +150,10 @@ assert(fs.name(f[1]) == 'meson.build', 'failed to get basename') assert(fs.name('foo/bar/baz.dll.a') == 'baz.dll.a', 'failed to get basename with compound suffix') if host_machine.system() in ['cygwin', 'windows'] assert(fs.name(btgt) == 'btgt.exe', 'failed to get basename of build target') + assert(fs.suffix(btgt) == '.exe', 'failed to get build target suffix') else assert(fs.name(btgt) == 'btgt', 'failed to get basename of build target') + assert(fs.suffix(btgt) == '', 'failed to get build target suffix') endif assert(fs.name(ctgt) == 'ctgt.txt', 'failed to get basename of custom target') assert(fs.name(ctgt[0]) == 'ctgt.txt', 'failed to get basename of custom target index') @@ -163,6 +162,12 @@ assert(fs.stem('foo/bar/baz.dll.a') == 'baz.dll', 'failed to get stem with compo assert(fs.stem(btgt) == 'btgt', 'failed to get stem of build target') assert(fs.stem(ctgt) == 'ctgt', 'failed to get stem of custom target') assert(fs.stem(ctgt[0]) == 'ctgt', 'failed to get stem of custom target index') +assert(fs.suffix('foo/bar/baz') == '', 'failed to get missing suffix') +assert(fs.suffix('foo/bar/baz.') == '.', 'failed to get empty suffix') +assert(fs.suffix('foo/bar/baz.dll') == '.dll', 'failed to get plain suffix') +assert(fs.suffix('foo/bar/baz.dll.a') == '.a', 'failed to get final suffix') +assert(fs.suffix(ctgt) == '.txt', 'failed to get suffix of custom target') +assert(fs.suffix(ctgt[0]) == '.txt', 'failed to get suffix of custom target index') # relative_to if build_machine.system() == 'windows' diff --git a/test cases/common/282 wrap override/meson.build b/test cases/common/282 wrap override/meson.build new file mode 100644 index 0000000..76c84d6 --- /dev/null +++ b/test cases/common/282 wrap override/meson.build @@ -0,0 +1,8 @@ +project('wrap override') + + +subproject('sub') + +# sub has subsub.wrap that provides subsub-1.0 dependency. Even if sub itself +# does not use it, that dependency should now be available. +dependency('subsub-1.0') diff --git a/test cases/common/282 wrap override/subprojects/sub/meson.build b/test cases/common/282 wrap override/subprojects/sub/meson.build new file mode 100644 index 0000000..abefb30 --- /dev/null +++ b/test cases/common/282 wrap override/subprojects/sub/meson.build @@ -0,0 +1,7 @@ +project('sub') + +# Simulate an optional feature that requires subsub.wrap, but that feature is +# not enabled. +if false + dependency('subsub-1.0') +endif diff --git a/test cases/common/282 wrap override/subprojects/sub/subprojects/subsub.wrap b/test cases/common/282 wrap override/subprojects/sub/subprojects/subsub.wrap new file mode 100644 index 0000000..85a1a7c --- /dev/null +++ b/test cases/common/282 wrap override/subprojects/sub/subprojects/subsub.wrap @@ -0,0 +1,5 @@ +[wrap-file] + + +[provide] +dependency_names = subsub-1.0 diff --git a/test cases/common/282 wrap override/subprojects/subsub/meson.build b/test cases/common/282 wrap override/subprojects/subsub/meson.build new file mode 100644 index 0000000..668dcb3 --- /dev/null +++ b/test cases/common/282 wrap override/subprojects/subsub/meson.build @@ -0,0 +1,5 @@ +project('sub') + +# This simulates a subproject we previously downloaded using +# subproject/sub/subproject/subsub.wrap +meson.override_dependency('subsub-1.0', declare_dependency()) diff --git a/test cases/common/283 pkgconfig subproject/meson.build b/test cases/common/283 pkgconfig subproject/meson.build new file mode 100644 index 0000000..3b1335b --- /dev/null +++ b/test cases/common/283 pkgconfig subproject/meson.build @@ -0,0 +1,13 @@ +project('simple', 'c', meson_version: '>=1.9.0') +pkgg = import('pkgconfig') + +simple2_dep = dependency('simple2') + +simple_lib = library('simple', + 'simple.c', + dependencies: [simple2_dep] +) + +pkgg.generate(simple_lib, +requires: simple2_dep, +) diff --git a/test cases/common/283 pkgconfig subproject/simple.c b/test cases/common/283 pkgconfig subproject/simple.c new file mode 100644 index 0000000..da1d909 --- /dev/null +++ b/test cases/common/283 pkgconfig subproject/simple.c @@ -0,0 +1,6 @@ +#include"simple.h" +#include <simple2.h> + +int simple_function(void) { + return simple_simple_function(); +} diff --git a/test cases/common/283 pkgconfig subproject/simple.h b/test cases/common/283 pkgconfig subproject/simple.h new file mode 100644 index 0000000..6896bfd --- /dev/null +++ b/test cases/common/283 pkgconfig subproject/simple.h @@ -0,0 +1,6 @@ +#ifndef SIMPLE_H_ +#define SIMPLE_H_ + +int simple_function(void); + +#endif diff --git a/test cases/common/283 pkgconfig subproject/subprojects/simple2/exports.def b/test cases/common/283 pkgconfig subproject/subprojects/simple2/exports.def new file mode 100644 index 0000000..42c911b --- /dev/null +++ b/test cases/common/283 pkgconfig subproject/subprojects/simple2/exports.def @@ -0,0 +1,2 @@ +EXPORTS + simple_simple_function @1 diff --git a/test cases/common/283 pkgconfig subproject/subprojects/simple2/meson.build b/test cases/common/283 pkgconfig subproject/subprojects/simple2/meson.build new file mode 100644 index 0000000..199fea6 --- /dev/null +++ b/test cases/common/283 pkgconfig subproject/subprojects/simple2/meson.build @@ -0,0 +1,9 @@ +project('simple2', 'c', meson_version: '>=1.9.0') +pkgg = import('pkgconfig') + +lib2 = library('simple2', 'simple2.c', vs_module_defs: 'exports.def') +lib_dep = declare_dependency(link_with: lib2, include_directories: include_directories('.')) + +pkgg.generate(lib2) + +meson.override_dependency('simple2', lib_dep) diff --git a/test cases/common/283 pkgconfig subproject/subprojects/simple2/simple2.c b/test cases/common/283 pkgconfig subproject/subprojects/simple2/simple2.c new file mode 100644 index 0000000..215b2ae --- /dev/null +++ b/test cases/common/283 pkgconfig subproject/subprojects/simple2/simple2.c @@ -0,0 +1,5 @@ +#include"simple2.h" + +int simple_simple_function(void) { + return 42; +} diff --git a/test cases/common/283 pkgconfig subproject/subprojects/simple2/simple2.h b/test cases/common/283 pkgconfig subproject/subprojects/simple2/simple2.h new file mode 100644 index 0000000..472e135 --- /dev/null +++ b/test cases/common/283 pkgconfig subproject/subprojects/simple2/simple2.h @@ -0,0 +1,6 @@ +#ifndef SIMPLE2_H_ +#define SIMPLE2_H_ + +int simple_simple_function(void); + +#endif diff --git a/test cases/common/283 pkgconfig subproject/test.json b/test cases/common/283 pkgconfig subproject/test.json new file mode 100644 index 0000000..db6b52f --- /dev/null +++ b/test cases/common/283 pkgconfig subproject/test.json @@ -0,0 +1,15 @@ +{ + "installed": [ + { "type": "file", "file": "usr/lib/pkgconfig/simple.pc"}, + { "type": "file", "file": "usr/lib/pkgconfig/simple2.pc"} + ], + "matrix": { + "options": { + "default_library": [ + { "val": "shared" }, + { "val": "static" }, + { "val": "both" } + ] + } + } +} diff --git a/test cases/common/56 array methods/meson.build b/test cases/common/56 array methods/meson.build index e9e4969..3707775 100644 --- a/test cases/common/56 array methods/meson.build +++ b/test cases/common/56 array methods/meson.build @@ -1,4 +1,4 @@ -project('array methods') +project('array methods', meson_version : '>= 1.9') empty = [] one = ['abc'] @@ -68,3 +68,10 @@ endif if not combined.contains('ghi') error('Combined claims not to contain ghi.') endif + +# test array flattening +x = ['a', ['b'], [[[[[[['c'], 'd']]], 'e']]]] +assert(x.length() == 3) +assert(x.flatten().length() == 5) +assert(x.flatten() == ['a', 'b', 'c', 'd', 'e']) +assert(['a', ['b', 'c']].flatten() == ['a', 'b', 'c']) diff --git a/test cases/cython/2 generated sources/meson.build b/test cases/cython/2 generated sources/meson.build index d438d80..3d2837d 100644 --- a/test cases/cython/2 generated sources/meson.build +++ b/test cases/cython/2 generated sources/meson.build @@ -79,13 +79,20 @@ stuff_pxi = fs.copyfile( 'stuff.pxi' ) +stuff_pxi_2 = configure_file( + input: 'stuff.pxi.in', + output: 'stuff.pxi', + configuration: configuration_data(), + install: false +) + # Need to copy the cython source to the build directory # since meson can only generate the .pxi there includestuff_pyx = fs.copyfile( 'includestuff.pyx' ) -stuff_pxi_dep = declare_dependency(sources: stuff_pxi) +stuff_pxi_dep = declare_dependency(sources: [stuff_pxi, stuff_pxi_2]) includestuff_ext = py3.extension_module( 'includestuff', diff --git a/test cases/failing/135 rust link_language/f.rs b/test cases/failing/135 rust link_language/f.rs new file mode 100644 index 0000000..da0f5d9 --- /dev/null +++ b/test cases/failing/135 rust link_language/f.rs @@ -0,0 +1 @@ +pub fn main() {} diff --git a/test cases/failing/135 rust link_language/meson.build b/test cases/failing/135 rust link_language/meson.build new file mode 100644 index 0000000..695f9c4 --- /dev/null +++ b/test cases/failing/135 rust link_language/meson.build @@ -0,0 +1,10 @@ +# SPDX-license-identifer: Apache-2.0 +# Copyright © 2021 Intel Corporation + +project('rust wrong link language') + +if not add_languages('rust', required: false) + error('MESON_SKIP_TEST test requires rust compiler') +endif + +executable('f', 'f.rs', link_language: 'c') diff --git a/test cases/failing/135 rust link_language/test.json b/test cases/failing/135 rust link_language/test.json new file mode 100644 index 0000000..c72bb05 --- /dev/null +++ b/test cases/failing/135 rust link_language/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "line": "test cases/failing/135 rust link_language/meson.build:10:0: ERROR: cannot build Rust sources with a different link_language" + } + ] +} + diff --git a/test cases/frameworks/25 hdf5/meson.build b/test cases/frameworks/25 hdf5/meson.build index 38e0012..095c63f 100644 --- a/test cases/frameworks/25 hdf5/meson.build +++ b/test cases/frameworks/25 hdf5/meson.build @@ -28,6 +28,7 @@ test_fortran = add_languages('fortran', required: false) if test_fortran cpp = meson.get_compiler('cpp') fc = meson.get_compiler('fortran') + fs = import('fs') if host_machine.system() == 'darwin' and cpp.get_id() == 'clang' and fc.get_id() == 'gcc' # Search paths don't work correctly here and -lgfortran doesn't work @@ -35,6 +36,10 @@ if test_fortran elif host_machine.system() == 'windows' and cpp.get_id() != 'gcc' and fc.get_id() == 'gcc' # mixing gfortran with non-gcc doesn't work on windows test_fortran = false + elif fs.is_dir('/ci') and '-I' not in run_command('h5fc', '-show').stdout() + # h5fc does not include needed -I flags when HDF5 is built using CMake + # https://github.com/HDFGroup/hdf5/issues/5660 + test_fortran = false endif # --- Fortran tests diff --git a/test cases/rewrite/7 tricky dataflow/addSrc.json b/test cases/rewrite/8 tricky dataflow/addSrc.json index 17e4292..17e4292 100644 --- a/test cases/rewrite/7 tricky dataflow/addSrc.json +++ b/test cases/rewrite/8 tricky dataflow/addSrc.json diff --git a/test cases/rewrite/7 tricky dataflow/info.json b/test cases/rewrite/8 tricky dataflow/info.json index 8d4ac55..8d4ac55 100644 --- a/test cases/rewrite/7 tricky dataflow/info.json +++ b/test cases/rewrite/8 tricky dataflow/info.json diff --git a/test cases/rewrite/7 tricky dataflow/meson.build b/test cases/rewrite/8 tricky dataflow/meson.build index ab572ea..ab572ea 100644 --- a/test cases/rewrite/7 tricky dataflow/meson.build +++ b/test cases/rewrite/8 tricky dataflow/meson.build diff --git a/test cases/rust/1 basic/meson.build b/test cases/rust/1 basic/meson.build index f422beb..00bd212 100644 --- a/test cases/rust/1 basic/meson.build +++ b/test cases/rust/1 basic/meson.build @@ -6,6 +6,12 @@ e = executable('rust-program', 'prog.rs', ) test('rusttest', e) +e = executable('rust-dynamic', 'prog.rs', + override_options: {'rust_dynamic_std': true}, + install : true +) +test('rusttest-dynamic', e) + subdir('subdir') # this should fail due to debug_assert diff --git a/test cases/rust/1 basic/test.json b/test cases/rust/1 basic/test.json index 95e6ced..3cbdefa 100644 --- a/test cases/rust/1 basic/test.json +++ b/test cases/rust/1 basic/test.json @@ -3,6 +3,8 @@ {"type": "exe", "file": "usr/bin/rust-program"}, {"type": "pdb", "file": "usr/bin/rust-program"}, {"type": "exe", "file": "usr/bin/rust-program2"}, - {"type": "pdb", "file": "usr/bin/rust-program2"} + {"type": "pdb", "file": "usr/bin/rust-program2"}, + {"type": "exe", "file": "usr/bin/rust-dynamic"}, + {"type": "pdb", "file": "usr/bin/rust-dynamic"} ] } diff --git a/test cases/rust/22 cargo subproject/meson.build b/test cases/rust/22 cargo subproject/meson.build index 1b60014..7e2c662 100644 --- a/test cases/rust/22 cargo subproject/meson.build +++ b/test cases/rust/22 cargo subproject/meson.build @@ -1,6 +1,6 @@ project('cargo subproject', 'c') -foo_dep = dependency('foo-0-rs') +foo_dep = dependency('foo-0-cdylib') exe = executable('app', 'main.c', dependencies: foo_dep, ) diff --git a/test cases/rust/22 cargo subproject/subprojects/foo-0-rs.wrap b/test cases/rust/22 cargo subproject/subprojects/foo-0-rs.wrap index 99686e9..d12172b 100644 --- a/test cases/rust/22 cargo subproject/subprojects/foo-0-rs.wrap +++ b/test cases/rust/22 cargo subproject/subprojects/foo-0-rs.wrap @@ -1,2 +1,5 @@ [wrap-file] method = cargo + +[provide] +dependency_names = foo-0-cdylib diff --git a/test cases/rust/28 mixed/hello.rs b/test cases/rust/28 mixed/hello.rs new file mode 100644 index 0000000..549fa94 --- /dev/null +++ b/test cases/rust/28 mixed/hello.rs @@ -0,0 +1,6 @@ +#![no_main] + +#[no_mangle] +pub extern "C" fn hello_rust() { + println!("hello world"); +} diff --git a/test cases/rust/28 mixed/main.cc b/test cases/rust/28 mixed/main.cc new file mode 100644 index 0000000..10daae4 --- /dev/null +++ b/test cases/rust/28 mixed/main.cc @@ -0,0 +1,5 @@ +#include <iostream> + +extern "C" void hello_rust(void); + +int main() { std::cout << "This is C++!\n"; hello_rust(); } diff --git a/test cases/rust/28 mixed/meson.build b/test cases/rust/28 mixed/meson.build new file mode 100644 index 0000000..fac3d46 --- /dev/null +++ b/test cases/rust/28 mixed/meson.build @@ -0,0 +1,12 @@ +# use C++ to make it harder +project('mixed', ['cpp', 'rust']) + +e1 = executable('mixed', 'hello.rs', 'main.cc') +e2 = executable('mixed-structured', structured_sources('hello.rs'), 'main.cc') + +hello2 = import('fs').copyfile('hello.rs', 'hello2.rs') +e3 = executable('mixed-structured-gen', structured_sources(hello2), 'main.cc') + +test('mixed', e1) +test('mixed-structured', e2) +test('mixed-structured-gen', e3) diff --git a/test cases/rust/4 polyglot/stuff.rs b/test cases/rust/4 polyglot/stuff.rs index 30c3a36..e5d9386 100644 --- a/test cases/rust/4 polyglot/stuff.rs +++ b/test cases/rust/4 polyglot/stuff.rs @@ -1,4 +1,4 @@ #[no_mangle] -pub extern fn f() { +pub extern "C" fn f() { println!("Hello from Rust!"); } diff --git a/test cases/rust/9 unit tests/meson.build b/test cases/rust/9 unit tests/meson.build index 0fa2fa8..81045f2 100644 --- a/test cases/rust/9 unit tests/meson.build +++ b/test cases/rust/9 unit tests/meson.build @@ -40,6 +40,12 @@ if rustdoc.found() protocol : 'rust', suite : ['doctests'], ) + + doclib = shared_library('rust_doc_lib', ['doctest1.rs'], build_by_default : false) + rust.doctest('rust shared doctests', doclib, + protocol : 'rust', + suite : ['doctests'], + ) endif exe = executable('rust_exe', ['test2.rs', 'test.rs'], build_by_default : false) diff --git a/test cases/swift/10 mixed cpp/main.swift b/test cases/swift/11 mixed cpp/main.swift index c055dcd..c055dcd 100644 --- a/test cases/swift/10 mixed cpp/main.swift +++ b/test cases/swift/11 mixed cpp/main.swift diff --git a/test cases/swift/10 mixed cpp/meson.build b/test cases/swift/11 mixed cpp/meson.build index 94b70f0..6027341 100644 --- a/test cases/swift/10 mixed cpp/meson.build +++ b/test cases/swift/11 mixed cpp/meson.build @@ -8,5 +8,5 @@ if not swiftc.version().version_compare('>= 5.9') endif lib = static_library('mylib', 'mylib.cpp') -exe = executable('prog', 'main.swift', 'mylib.h', link_with: lib) +exe = executable('prog', 'main.swift', 'mylib.h', link_with: lib, swift_interoperability_mode: 'cpp') test('cpp interface', exe) diff --git a/test cases/swift/10 mixed cpp/mylib.cpp b/test cases/swift/11 mixed cpp/mylib.cpp index 0c61681..0c61681 100644 --- a/test cases/swift/10 mixed cpp/mylib.cpp +++ b/test cases/swift/11 mixed cpp/mylib.cpp diff --git a/test cases/swift/10 mixed cpp/mylib.h b/test cases/swift/11 mixed cpp/mylib.h index c465be4..c465be4 100644 --- a/test cases/swift/10 mixed cpp/mylib.h +++ b/test cases/swift/11 mixed cpp/mylib.h diff --git a/test cases/swift/11 c std passthrough/header.h b/test cases/swift/12 c std passthrough/header.h index 287cdf4..287cdf4 100644 --- a/test cases/swift/11 c std passthrough/header.h +++ b/test cases/swift/12 c std passthrough/header.h diff --git a/test cases/swift/11 c std passthrough/main.swift b/test cases/swift/12 c std passthrough/main.swift index f6358db..f6358db 100644 --- a/test cases/swift/11 c std passthrough/main.swift +++ b/test cases/swift/12 c std passthrough/main.swift diff --git a/test cases/swift/11 c std passthrough/meson.build b/test cases/swift/12 c std passthrough/meson.build index 202768f..202768f 100644 --- a/test cases/swift/11 c std passthrough/meson.build +++ b/test cases/swift/12 c std passthrough/meson.build diff --git a/test cases/swift/11 mixed objcpp/main.swift b/test cases/swift/13 mixed objcpp/main.swift index cd6dd2b..cd6dd2b 100644 --- a/test cases/swift/11 mixed objcpp/main.swift +++ b/test cases/swift/13 mixed objcpp/main.swift diff --git a/test cases/swift/11 mixed objcpp/meson.build b/test cases/swift/13 mixed objcpp/meson.build index 69098e2..a76162a 100644 --- a/test cases/swift/11 mixed objcpp/meson.build +++ b/test cases/swift/13 mixed objcpp/meson.build @@ -8,5 +8,5 @@ if not swiftc.version().version_compare('>= 5.9') endif lib = static_library('mylib', 'mylib.mm') -exe = executable('prog', 'main.swift', 'mylib.h', link_with: lib) +exe = executable('prog', 'main.swift', 'mylib.h', link_with: lib, swift_interoperability_mode: 'cpp') test('objcpp interface', exe) diff --git a/test cases/swift/11 mixed objcpp/mylib.h b/test cases/swift/13 mixed objcpp/mylib.h index 1e7b23d..1e7b23d 100644 --- a/test cases/swift/11 mixed objcpp/mylib.h +++ b/test cases/swift/13 mixed objcpp/mylib.h diff --git a/test cases/swift/11 mixed objcpp/mylib.mm b/test cases/swift/13 mixed objcpp/mylib.mm index f7e9ab3..f7e9ab3 100644 --- a/test cases/swift/11 mixed objcpp/mylib.mm +++ b/test cases/swift/13 mixed objcpp/mylib.mm diff --git a/test cases/swift/14 single-file library/main.swift b/test cases/swift/14 single-file library/main.swift new file mode 100644 index 0000000..ccc8fb9 --- /dev/null +++ b/test cases/swift/14 single-file library/main.swift @@ -0,0 +1,3 @@ +import SingleFile + +callMe() diff --git a/test cases/swift/14 single-file library/meson.build b/test cases/swift/14 single-file library/meson.build new file mode 100644 index 0000000..8eda1d5 --- /dev/null +++ b/test cases/swift/14 single-file library/meson.build @@ -0,0 +1,4 @@ +project('single-file library', 'swift') + +lib = static_library('SingleFile', 'singlefile.swift') +executable('program', 'main.swift', link_with: [lib]) diff --git a/test cases/swift/14 single-file library/singlefile.swift b/test cases/swift/14 single-file library/singlefile.swift new file mode 100644 index 0000000..617952f --- /dev/null +++ b/test cases/swift/14 single-file library/singlefile.swift @@ -0,0 +1 @@ +public func callMe() {} diff --git a/test cases/swift/15 main in single-file library/main.swift b/test cases/swift/15 main in single-file library/main.swift new file mode 100644 index 0000000..0d95abb --- /dev/null +++ b/test cases/swift/15 main in single-file library/main.swift @@ -0,0 +1,3 @@ +import CProgram + +precondition(callMe() == 4) diff --git a/test cases/swift/15 main in single-file library/meson.build b/test cases/swift/15 main in single-file library/meson.build new file mode 100644 index 0000000..2e1202e --- /dev/null +++ b/test cases/swift/15 main in single-file library/meson.build @@ -0,0 +1,4 @@ +project('main in single-file library', 'swift', 'c') + +lib = static_library('Library', 'main.swift', include_directories: ['.']) +executable('program', 'program.c', link_with: [lib]) diff --git a/test cases/swift/15 main in single-file library/module.modulemap b/test cases/swift/15 main in single-file library/module.modulemap new file mode 100644 index 0000000..3c1817a --- /dev/null +++ b/test cases/swift/15 main in single-file library/module.modulemap @@ -0,0 +1,3 @@ +module CProgram [extern_c] { + header "program.h" +} diff --git a/test cases/swift/15 main in single-file library/program.c b/test cases/swift/15 main in single-file library/program.c new file mode 100644 index 0000000..8959dae --- /dev/null +++ b/test cases/swift/15 main in single-file library/program.c @@ -0,0 +1,5 @@ +#include "program.h" + +int callMe() { + return 4; +} diff --git a/test cases/swift/15 main in single-file library/program.h b/test cases/swift/15 main in single-file library/program.h new file mode 100644 index 0000000..5058be3 --- /dev/null +++ b/test cases/swift/15 main in single-file library/program.h @@ -0,0 +1 @@ +int callMe(void); diff --git a/test cases/swift/16 main in multi-file library/main.swift b/test cases/swift/16 main in multi-file library/main.swift new file mode 100644 index 0000000..3682e8d --- /dev/null +++ b/test cases/swift/16 main in multi-file library/main.swift @@ -0,0 +1,4 @@ +import CProgram + +precondition(callMe() == 4) +precondition(callMe2() == 6) diff --git a/test cases/swift/16 main in multi-file library/meson.build b/test cases/swift/16 main in multi-file library/meson.build new file mode 100644 index 0000000..4d287f3 --- /dev/null +++ b/test cases/swift/16 main in multi-file library/meson.build @@ -0,0 +1,4 @@ +project('main in multi-file library', 'swift', 'c') + +lib = static_library('Library', 'main.swift', 'more.swift', include_directories: ['.']) +executable('program', 'program.c', link_with: [lib]) diff --git a/test cases/swift/16 main in multi-file library/module.modulemap b/test cases/swift/16 main in multi-file library/module.modulemap new file mode 100644 index 0000000..3c1817a --- /dev/null +++ b/test cases/swift/16 main in multi-file library/module.modulemap @@ -0,0 +1,3 @@ +module CProgram [extern_c] { + header "program.h" +} diff --git a/test cases/swift/16 main in multi-file library/more.swift b/test cases/swift/16 main in multi-file library/more.swift new file mode 100644 index 0000000..716500f --- /dev/null +++ b/test cases/swift/16 main in multi-file library/more.swift @@ -0,0 +1,3 @@ +func callMe2() -> Int { + 6 +} diff --git a/test cases/swift/16 main in multi-file library/program.c b/test cases/swift/16 main in multi-file library/program.c new file mode 100644 index 0000000..8959dae --- /dev/null +++ b/test cases/swift/16 main in multi-file library/program.c @@ -0,0 +1,5 @@ +#include "program.h" + +int callMe() { + return 4; +} diff --git a/test cases/swift/16 main in multi-file library/program.h b/test cases/swift/16 main in multi-file library/program.h new file mode 100644 index 0000000..5058be3 --- /dev/null +++ b/test cases/swift/16 main in multi-file library/program.h @@ -0,0 +1 @@ +int callMe(void); diff --git a/test cases/swift/8 extra args/lib.swift b/test cases/swift/8 extra args/lib.swift new file mode 100644 index 0000000..f8167ad --- /dev/null +++ b/test cases/swift/8 extra args/lib.swift @@ -0,0 +1,3 @@ +public func callMe() { + print("test") +} diff --git a/test cases/swift/8 extra args/main.swift b/test cases/swift/8 extra args/main.swift deleted file mode 100644 index 1ff8e07..0000000 --- a/test cases/swift/8 extra args/main.swift +++ /dev/null @@ -1 +0,0 @@ -print("test") diff --git a/test cases/swift/8 extra args/meson.build b/test cases/swift/8 extra args/meson.build index ead2ff5..d243e36 100644 --- a/test cases/swift/8 extra args/meson.build +++ b/test cases/swift/8 extra args/meson.build @@ -2,8 +2,8 @@ project('extra args', 'swift') trace_fname = 'trace.json' -lib = static_library('main', - 'main.swift', +lib = static_library('lib', + 'lib.swift', swift_args: [ '-emit-loaded-module-trace', '-emit-loaded-module-trace-path', '../' + trace_fname diff --git a/test cases/unit/99 custom target name/file.txt.in b/test cases/unit/100 custom target name/file.txt.in index e69de29..e69de29 100644 --- a/test cases/unit/99 custom target name/file.txt.in +++ b/test cases/unit/100 custom target name/file.txt.in diff --git a/test cases/unit/99 custom target name/meson.build b/test cases/unit/100 custom target name/meson.build index 8287614..8287614 100644 --- a/test cases/unit/99 custom target name/meson.build +++ b/test cases/unit/100 custom target name/meson.build diff --git a/test cases/unit/99 custom target name/subdir/meson.build b/test cases/unit/100 custom target name/subdir/meson.build index 785a7b3..785a7b3 100644 --- a/test cases/unit/99 custom target name/subdir/meson.build +++ b/test cases/unit/100 custom target name/subdir/meson.build diff --git a/test cases/unit/100 relative find program/foo.py b/test cases/unit/101 relative find program/foo.py index 21239b7..21239b7 100755 --- a/test cases/unit/100 relative find program/foo.py +++ b/test cases/unit/101 relative find program/foo.py diff --git a/test cases/unit/100 relative find program/meson.build b/test cases/unit/101 relative find program/meson.build index 5745d8a..5745d8a 100644 --- a/test cases/unit/100 relative find program/meson.build +++ b/test cases/unit/101 relative find program/meson.build diff --git a/test cases/unit/100 relative find program/subdir/meson.build b/test cases/unit/101 relative find program/subdir/meson.build index 475f5f5..475f5f5 100644 --- a/test cases/unit/100 relative find program/subdir/meson.build +++ b/test cases/unit/101 relative find program/subdir/meson.build diff --git a/test cases/unit/101 rlib linkage/lib2.rs b/test cases/unit/102 rlib linkage/lib2.rs index 3487bc5..3487bc5 100644 --- a/test cases/unit/101 rlib linkage/lib2.rs +++ b/test cases/unit/102 rlib linkage/lib2.rs diff --git a/test cases/unit/101 rlib linkage/main.rs b/test cases/unit/102 rlib linkage/main.rs index d0f82e4..d0f82e4 100644 --- a/test cases/unit/101 rlib linkage/main.rs +++ b/test cases/unit/102 rlib linkage/main.rs diff --git a/test cases/unit/101 rlib linkage/meson.build b/test cases/unit/102 rlib linkage/meson.build index 2d15b2a..2d15b2a 100644 --- a/test cases/unit/101 rlib linkage/meson.build +++ b/test cases/unit/102 rlib linkage/meson.build diff --git a/test cases/unit/102 python without pkgconfig/meson.build b/test cases/unit/103 python without pkgconfig/meson.build index 014a617..014a617 100644 --- a/test cases/unit/102 python without pkgconfig/meson.build +++ b/test cases/unit/103 python without pkgconfig/meson.build diff --git a/test cases/unit/103 strip/lib.c b/test cases/unit/104 strip/lib.c index 7d8163c..7d8163c 100644 --- a/test cases/unit/103 strip/lib.c +++ b/test cases/unit/104 strip/lib.c diff --git a/test cases/unit/103 strip/meson.build b/test cases/unit/104 strip/meson.build index dff61ab..dff61ab 100644 --- a/test cases/unit/103 strip/meson.build +++ b/test cases/unit/104 strip/meson.build diff --git a/test cases/unit/104 debug function/meson.build b/test cases/unit/105 debug function/meson.build index c3f4c76..c3f4c76 100644 --- a/test cases/unit/104 debug function/meson.build +++ b/test cases/unit/105 debug function/meson.build diff --git a/test cases/unit/105 pkgconfig relocatable with absolute path/meson.build b/test cases/unit/106 pkgconfig relocatable with absolute path/meson.build index ff21286..ff21286 100644 --- a/test cases/unit/105 pkgconfig relocatable with absolute path/meson.build +++ b/test cases/unit/106 pkgconfig relocatable with absolute path/meson.build diff --git a/test cases/unit/106 underspecified mtest/main.c b/test cases/unit/107 underspecified mtest/main.c index 8842fc1..8842fc1 100644 --- a/test cases/unit/106 underspecified mtest/main.c +++ b/test cases/unit/107 underspecified mtest/main.c diff --git a/test cases/unit/106 underspecified mtest/meson.build b/test cases/unit/107 underspecified mtest/meson.build index c0a88d6..c0a88d6 100644 --- a/test cases/unit/106 underspecified mtest/meson.build +++ b/test cases/unit/107 underspecified mtest/meson.build diff --git a/test cases/unit/106 underspecified mtest/runner.py b/test cases/unit/107 underspecified mtest/runner.py index 9fb9ac4..9fb9ac4 100755 --- a/test cases/unit/106 underspecified mtest/runner.py +++ b/test cases/unit/107 underspecified mtest/runner.py diff --git a/test cases/unit/107 subproject symlink/cp.py b/test cases/unit/108 subproject symlink/cp.py index adb0547..adb0547 100644 --- a/test cases/unit/107 subproject symlink/cp.py +++ b/test cases/unit/108 subproject symlink/cp.py diff --git a/test cases/unit/107 subproject symlink/main.c b/test cases/unit/108 subproject symlink/main.c index 62bd4b4..62bd4b4 100644 --- a/test cases/unit/107 subproject symlink/main.c +++ b/test cases/unit/108 subproject symlink/main.c diff --git a/test cases/unit/107 subproject symlink/meson.build b/test cases/unit/108 subproject symlink/meson.build index 6766c8e..6766c8e 100644 --- a/test cases/unit/107 subproject symlink/meson.build +++ b/test cases/unit/108 subproject symlink/meson.build diff --git a/test cases/unit/107 subproject symlink/symlinked_subproject/datadir/datafile b/test cases/unit/108 subproject symlink/symlinked_subproject/datadir/datafile index 6a68294..6a68294 100644 --- a/test cases/unit/107 subproject symlink/symlinked_subproject/datadir/datafile +++ b/test cases/unit/108 subproject symlink/symlinked_subproject/datadir/datafile diff --git a/test cases/unit/107 subproject symlink/symlinked_subproject/datadir/meson.build b/test cases/unit/108 subproject symlink/symlinked_subproject/datadir/meson.build index cbeb0a9..cbeb0a9 100644 --- a/test cases/unit/107 subproject symlink/symlinked_subproject/datadir/meson.build +++ b/test cases/unit/108 subproject symlink/symlinked_subproject/datadir/meson.build diff --git a/test cases/unit/107 subproject symlink/symlinked_subproject/meson.build b/test cases/unit/108 subproject symlink/symlinked_subproject/meson.build index 3930465..3930465 100644 --- a/test cases/unit/107 subproject symlink/symlinked_subproject/meson.build +++ b/test cases/unit/108 subproject symlink/symlinked_subproject/meson.build diff --git a/test cases/unit/107 subproject symlink/symlinked_subproject/src.c b/test cases/unit/108 subproject symlink/symlinked_subproject/src.c index 97d7ad1..97d7ad1 100644 --- a/test cases/unit/107 subproject symlink/symlinked_subproject/src.c +++ b/test cases/unit/108 subproject symlink/symlinked_subproject/src.c diff --git a/test cases/unit/108 new subproject on reconfigure/meson.build b/test cases/unit/109 new subproject on reconfigure/meson.build index 7342c9a..7342c9a 100644 --- a/test cases/unit/108 new subproject on reconfigure/meson.build +++ b/test cases/unit/109 new subproject on reconfigure/meson.build diff --git a/test cases/unit/108 new subproject on reconfigure/meson_options.txt b/test cases/unit/109 new subproject on reconfigure/meson_options.txt index b3dd683..b3dd683 100644 --- a/test cases/unit/108 new subproject on reconfigure/meson_options.txt +++ b/test cases/unit/109 new subproject on reconfigure/meson_options.txt diff --git a/test cases/unit/108 new subproject on reconfigure/subprojects/foo/foo.c b/test cases/unit/109 new subproject on reconfigure/subprojects/foo/foo.c index 1edf995..1edf995 100644 --- a/test cases/unit/108 new subproject on reconfigure/subprojects/foo/foo.c +++ b/test cases/unit/109 new subproject on reconfigure/subprojects/foo/foo.c diff --git a/test cases/unit/108 new subproject on reconfigure/subprojects/foo/meson.build b/test cases/unit/109 new subproject on reconfigure/subprojects/foo/meson.build index 2a6e30a..2a6e30a 100644 --- a/test cases/unit/108 new subproject on reconfigure/subprojects/foo/meson.build +++ b/test cases/unit/109 new subproject on reconfigure/subprojects/foo/meson.build diff --git a/test cases/unit/109 configure same noop/meson.build b/test cases/unit/110 configure same noop/meson.build index d3f1326..d3f1326 100644 --- a/test cases/unit/109 configure same noop/meson.build +++ b/test cases/unit/110 configure same noop/meson.build diff --git a/test cases/unit/109 configure same noop/meson_options.txt b/test cases/unit/110 configure same noop/meson_options.txt index 57b4084..57b4084 100644 --- a/test cases/unit/109 configure same noop/meson_options.txt +++ b/test cases/unit/110 configure same noop/meson_options.txt diff --git a/test cases/unit/110 freeze/freeze.c b/test cases/unit/111 freeze/freeze.c index 0a45c1a..0a45c1a 100644 --- a/test cases/unit/110 freeze/freeze.c +++ b/test cases/unit/111 freeze/freeze.c diff --git a/test cases/unit/110 freeze/meson.build b/test cases/unit/111 freeze/meson.build index 1a84f37..1a84f37 100644 --- a/test cases/unit/110 freeze/meson.build +++ b/test cases/unit/111 freeze/meson.build diff --git a/test cases/unit/111 replace unencodable xml chars/meson.build b/test cases/unit/112 replace unencodable xml chars/meson.build index 73485b0..73485b0 100644 --- a/test cases/unit/111 replace unencodable xml chars/meson.build +++ b/test cases/unit/112 replace unencodable xml chars/meson.build diff --git a/test cases/unit/111 replace unencodable xml chars/script.py b/test cases/unit/112 replace unencodable xml chars/script.py index 2f2d4d6..2f2d4d6 100644 --- a/test cases/unit/111 replace unencodable xml chars/script.py +++ b/test cases/unit/112 replace unencodable xml chars/script.py diff --git a/test cases/unit/112 classpath/com/mesonbuild/Simple.java b/test cases/unit/113 classpath/com/mesonbuild/Simple.java index 325a49a..325a49a 100644 --- a/test cases/unit/112 classpath/com/mesonbuild/Simple.java +++ b/test cases/unit/113 classpath/com/mesonbuild/Simple.java diff --git a/test cases/unit/112 classpath/meson.build b/test cases/unit/113 classpath/meson.build index e6b9b84..e6b9b84 100644 --- a/test cases/unit/112 classpath/meson.build +++ b/test cases/unit/113 classpath/meson.build diff --git a/test cases/unit/113 list build options/meson.build b/test cases/unit/114 list build options/meson.build index 2d634d3..2d634d3 100644 --- a/test cases/unit/113 list build options/meson.build +++ b/test cases/unit/114 list build options/meson.build diff --git a/test cases/unit/113 list build options/meson_options.txt b/test cases/unit/114 list build options/meson_options.txt index d84f22a..d84f22a 100644 --- a/test cases/unit/113 list build options/meson_options.txt +++ b/test cases/unit/114 list build options/meson_options.txt diff --git a/test cases/unit/114 complex link cases/main.c b/test cases/unit/115 complex link cases/main.c index 739b413..739b413 100644 --- a/test cases/unit/114 complex link cases/main.c +++ b/test cases/unit/115 complex link cases/main.c diff --git a/test cases/unit/114 complex link cases/meson.build b/test cases/unit/115 complex link cases/meson.build index 3b4b898..3b4b898 100644 --- a/test cases/unit/114 complex link cases/meson.build +++ b/test cases/unit/115 complex link cases/meson.build diff --git a/test cases/unit/114 complex link cases/s1.c b/test cases/unit/115 complex link cases/s1.c index 68bba49..68bba49 100644 --- a/test cases/unit/114 complex link cases/s1.c +++ b/test cases/unit/115 complex link cases/s1.c diff --git a/test cases/unit/114 complex link cases/s2.c b/test cases/unit/115 complex link cases/s2.c index 835870c..835870c 100644 --- a/test cases/unit/114 complex link cases/s2.c +++ b/test cases/unit/115 complex link cases/s2.c diff --git a/test cases/unit/114 complex link cases/s3.c b/test cases/unit/115 complex link cases/s3.c index 08e0620..08e0620 100644 --- a/test cases/unit/114 complex link cases/s3.c +++ b/test cases/unit/115 complex link cases/s3.c diff --git a/test cases/unit/115 c cpp stds/meson.build b/test cases/unit/116 c cpp stds/meson.build index fb68af6..fb68af6 100644 --- a/test cases/unit/115 c cpp stds/meson.build +++ b/test cases/unit/116 c cpp stds/meson.build diff --git a/test cases/unit/115 c cpp stds/meson.options b/test cases/unit/116 c cpp stds/meson.options index 7040758..7040758 100644 --- a/test cases/unit/115 c cpp stds/meson.options +++ b/test cases/unit/116 c cpp stds/meson.options diff --git a/test cases/unit/116 empty project/expected_mods.json b/test cases/unit/117 empty project/expected_mods.json index fa5e0ec..fa5e0ec 100644 --- a/test cases/unit/116 empty project/expected_mods.json +++ b/test cases/unit/117 empty project/expected_mods.json diff --git a/test cases/unit/116 empty project/meson.build b/test cases/unit/117 empty project/meson.build index b92b9b4..b92b9b4 100644 --- a/test cases/unit/116 empty project/meson.build +++ b/test cases/unit/117 empty project/meson.build diff --git a/test cases/unit/117 genvslite/main.cpp b/test cases/unit/118 genvslite/main.cpp index ca250bd..ca250bd 100644 --- a/test cases/unit/117 genvslite/main.cpp +++ b/test cases/unit/118 genvslite/main.cpp diff --git a/test cases/unit/117 genvslite/meson.build b/test cases/unit/118 genvslite/meson.build index 3445d7f..3445d7f 100644 --- a/test cases/unit/117 genvslite/meson.build +++ b/test cases/unit/118 genvslite/meson.build diff --git a/test cases/unit/118 meson package cache dir/cache_dir/bar/meson.build b/test cases/unit/119 meson package cache dir/cache_dir/bar/meson.build index dca36f6..dca36f6 100644 --- a/test cases/unit/118 meson package cache dir/cache_dir/bar/meson.build +++ b/test cases/unit/119 meson package cache dir/cache_dir/bar/meson.build diff --git a/test cases/unit/118 meson package cache dir/cache_dir/foo.zip b/test cases/unit/119 meson package cache dir/cache_dir/foo.zip Binary files differindex 91bc36a..91bc36a 100644 --- a/test cases/unit/118 meson package cache dir/cache_dir/foo.zip +++ b/test cases/unit/119 meson package cache dir/cache_dir/foo.zip diff --git a/test cases/unit/118 meson package cache dir/meson.build b/test cases/unit/119 meson package cache dir/meson.build index 2057bba..2057bba 100644 --- a/test cases/unit/118 meson package cache dir/meson.build +++ b/test cases/unit/119 meson package cache dir/meson.build diff --git a/test cases/unit/118 meson package cache dir/subprojects/bar.wrap b/test cases/unit/119 meson package cache dir/subprojects/bar.wrap index 3ec5834..3ec5834 100644 --- a/test cases/unit/118 meson package cache dir/subprojects/bar.wrap +++ b/test cases/unit/119 meson package cache dir/subprojects/bar.wrap diff --git a/test cases/unit/118 meson package cache dir/subprojects/foo.wrap b/test cases/unit/119 meson package cache dir/subprojects/foo.wrap index b7dd41d..b7dd41d 100644 --- a/test cases/unit/118 meson package cache dir/subprojects/foo.wrap +++ b/test cases/unit/119 meson package cache dir/subprojects/foo.wrap diff --git a/test cases/unit/119 openssl cmake bug/meson.build b/test cases/unit/120 openssl cmake bug/meson.build index d08a8ef..d08a8ef 100644 --- a/test cases/unit/119 openssl cmake bug/meson.build +++ b/test cases/unit/120 openssl cmake bug/meson.build diff --git a/test cases/unit/119 openssl cmake bug/nativefile.ini b/test cases/unit/120 openssl cmake bug/nativefile.ini index dd6b0ff..dd6b0ff 100644 --- a/test cases/unit/119 openssl cmake bug/nativefile.ini +++ b/test cases/unit/120 openssl cmake bug/nativefile.ini diff --git a/test cases/unit/120 rewrite/meson.build b/test cases/unit/121 rewrite/meson.build index 654b09d..654b09d 100644 --- a/test cases/unit/120 rewrite/meson.build +++ b/test cases/unit/121 rewrite/meson.build diff --git a/test cases/unit/121 executable suffix/main.c b/test cases/unit/122 executable suffix/main.c index 78f2de1..78f2de1 100644 --- a/test cases/unit/121 executable suffix/main.c +++ b/test cases/unit/122 executable suffix/main.c diff --git a/test cases/unit/121 executable suffix/meson.build b/test cases/unit/122 executable suffix/meson.build index 7dff9d6..7dff9d6 100644 --- a/test cases/unit/121 executable suffix/meson.build +++ b/test cases/unit/122 executable suffix/meson.build diff --git a/test cases/unit/122 persp options/meson.build b/test cases/unit/123 persp options/meson.build index 2df4205..2df4205 100644 --- a/test cases/unit/122 persp options/meson.build +++ b/test cases/unit/123 persp options/meson.build diff --git a/test cases/unit/122 persp options/meson.options b/test cases/unit/123 persp options/meson.options index 2bfd08d..2bfd08d 100644 --- a/test cases/unit/122 persp options/meson.options +++ b/test cases/unit/123 persp options/meson.options diff --git a/test cases/unit/122 persp options/subprojects/sub1/meson.build b/test cases/unit/123 persp options/subprojects/sub1/meson.build index 5b17618..5b17618 100644 --- a/test cases/unit/122 persp options/subprojects/sub1/meson.build +++ b/test cases/unit/123 persp options/subprojects/sub1/meson.build diff --git a/test cases/unit/122 persp options/subprojects/sub1/meson.options b/test cases/unit/123 persp options/subprojects/sub1/meson.options index ba5661a..ba5661a 100644 --- a/test cases/unit/122 persp options/subprojects/sub1/meson.options +++ b/test cases/unit/123 persp options/subprojects/sub1/meson.options diff --git a/test cases/unit/122 persp options/subprojects/sub1/sub1.c b/test cases/unit/123 persp options/subprojects/sub1/sub1.c index 4e4b873..4e4b873 100644 --- a/test cases/unit/122 persp options/subprojects/sub1/sub1.c +++ b/test cases/unit/123 persp options/subprojects/sub1/sub1.c diff --git a/test cases/unit/122 persp options/subprojects/sub2/meson.build b/test cases/unit/123 persp options/subprojects/sub2/meson.build index e8935bc..e8935bc 100644 --- a/test cases/unit/122 persp options/subprojects/sub2/meson.build +++ b/test cases/unit/123 persp options/subprojects/sub2/meson.build diff --git a/test cases/unit/122 persp options/subprojects/sub2/meson.options b/test cases/unit/123 persp options/subprojects/sub2/meson.options index ba5661a..ba5661a 100644 --- a/test cases/unit/122 persp options/subprojects/sub2/meson.options +++ b/test cases/unit/123 persp options/subprojects/sub2/meson.options diff --git a/test cases/unit/122 persp options/subprojects/sub2/sub2.c b/test cases/unit/123 persp options/subprojects/sub2/sub2.c index 4e4b873..4e4b873 100644 --- a/test cases/unit/122 persp options/subprojects/sub2/sub2.c +++ b/test cases/unit/123 persp options/subprojects/sub2/sub2.c diff --git a/test cases/unit/122 persp options/toplevel.c b/test cases/unit/123 persp options/toplevel.c index 5748d6b..5748d6b 100644 --- a/test cases/unit/122 persp options/toplevel.c +++ b/test cases/unit/123 persp options/toplevel.c diff --git a/test cases/unit/123 reconfigure base options/meson.build b/test cases/unit/124 reconfigure base options/meson.build index 8a13b78..8a13b78 100644 --- a/test cases/unit/123 reconfigure base options/meson.build +++ b/test cases/unit/124 reconfigure base options/meson.build diff --git a/test cases/unit/123 reconfigure base options/subprojects/sub/meson.build b/test cases/unit/124 reconfigure base options/subprojects/sub/meson.build index ba740d1..ba740d1 100644 --- a/test cases/unit/123 reconfigure base options/subprojects/sub/meson.build +++ b/test cases/unit/124 reconfigure base options/subprojects/sub/meson.build diff --git a/test cases/unit/124 interactive tap/meson.build b/test cases/unit/125 interactive tap/meson.build index 30518db..30518db 100644 --- a/test cases/unit/124 interactive tap/meson.build +++ b/test cases/unit/125 interactive tap/meson.build diff --git a/test cases/unit/124 interactive tap/script.py b/test cases/unit/125 interactive tap/script.py index 873a4ae..873a4ae 100755 --- a/test cases/unit/124 interactive tap/script.py +++ b/test cases/unit/125 interactive tap/script.py diff --git a/test cases/unit/125 declare_dep var/meson.build b/test cases/unit/126 declare_dep var/meson.build index 4909b59..4909b59 100644 --- a/test cases/unit/125 declare_dep var/meson.build +++ b/test cases/unit/126 declare_dep var/meson.build diff --git a/test cases/unit/125 declare_dep var/meson_options.txt b/test cases/unit/126 declare_dep var/meson_options.txt index eb15ffc..eb15ffc 100644 --- a/test cases/unit/125 declare_dep var/meson_options.txt +++ b/test cases/unit/126 declare_dep var/meson_options.txt diff --git a/test cases/unit/125 pkgsubproj/meson.build b/test cases/unit/127 pkgsubproj/meson.build index b4cf89f..b4cf89f 100644 --- a/test cases/unit/125 pkgsubproj/meson.build +++ b/test cases/unit/127 pkgsubproj/meson.build diff --git a/test cases/unit/125 pkgsubproj/subprojects/sub/meson.build b/test cases/unit/127 pkgsubproj/subprojects/sub/meson.build index 99622b6..99622b6 100644 --- a/test cases/unit/125 pkgsubproj/subprojects/sub/meson.build +++ b/test cases/unit/127 pkgsubproj/subprojects/sub/meson.build diff --git a/test cases/unit/126 test slice/meson.build b/test cases/unit/128 test slice/meson.build index a41c2f6..a41c2f6 100644 --- a/test cases/unit/126 test slice/meson.build +++ b/test cases/unit/128 test slice/meson.build diff --git a/test cases/unit/126 test slice/test.py b/test cases/unit/128 test slice/test.py index e69de29..e69de29 100644 --- a/test cases/unit/126 test slice/test.py +++ b/test cases/unit/128 test slice/test.py diff --git a/test cases/unit/127 sanitizers/meson.build b/test cases/unit/129 sanitizers/meson.build index b42fb35..b42fb35 100644 --- a/test cases/unit/127 sanitizers/meson.build +++ b/test cases/unit/129 sanitizers/meson.build diff --git a/test cases/unit/128 long opt vs D/meson.build b/test cases/unit/130 long opt vs D/meson.build index e05d88d..e05d88d 100644 --- a/test cases/unit/128 long opt vs D/meson.build +++ b/test cases/unit/130 long opt vs D/meson.build diff --git a/test cases/unit/128 long opt vs D/meson_options.txt b/test cases/unit/130 long opt vs D/meson_options.txt index 255bf15..255bf15 100644 --- a/test cases/unit/128 long opt vs D/meson_options.txt +++ b/test cases/unit/130 long opt vs D/meson_options.txt diff --git a/test cases/unit/129 vala internal glib/lib.vala b/test cases/unit/131 vala internal glib/lib.vala index e62e632..e62e632 100644 --- a/test cases/unit/129 vala internal glib/lib.vala +++ b/test cases/unit/131 vala internal glib/lib.vala diff --git a/test cases/unit/129 vala internal glib/meson.build b/test cases/unit/131 vala internal glib/meson.build index 9479082..9479082 100644 --- a/test cases/unit/129 vala internal glib/meson.build +++ b/test cases/unit/131 vala internal glib/meson.build diff --git a/test cases/unit/129 vala internal glib/meson.options b/test cases/unit/131 vala internal glib/meson.options index f8a1ece..f8a1ece 100644 --- a/test cases/unit/129 vala internal glib/meson.options +++ b/test cases/unit/131 vala internal glib/meson.options diff --git a/test cases/unit/69 cross/crossfile.in b/test cases/unit/69 cross/crossfile.in index 678e8d3..beab9bc 100644 --- a/test cases/unit/69 cross/crossfile.in +++ b/test cases/unit/69 cross/crossfile.in @@ -3,3 +3,6 @@ system = '@system@' cpu_family = '@cpu_family@' cpu = '@cpu@' endian = '@endian@' + +[built-in options] +c_args = ['-funroll-loops'] diff --git a/test cases/unit/69 cross/meson.build b/test cases/unit/69 cross/meson.build index acf4f0f..645d453 100644 --- a/test cases/unit/69 cross/meson.build +++ b/test cases/unit/69 cross/meson.build @@ -1,16 +1,25 @@ project('crosstest') +add_languages('c', native: true) if get_option('generate') conf_data = configuration_data() conf_data.set('system', build_machine.system()) conf_data.set('cpu', build_machine.cpu()) conf_data.set('cpu_family', build_machine.cpu_family()) conf_data.set('endian', build_machine.endian()) + conf_data.set('c_args', '-pedantic') configure_file(input: 'crossfile.in', output: 'crossfile', configuration: conf_data) - message('Written cross file') + configure_file(input: 'nativefile.in', + output: 'nativefile', + configuration: conf_data) + message('Written native and cross file') + + add_languages('c', native: false) + assert(get_option('build.c_args') == get_option('c_args')) else assert(meson.is_cross_build(), 'not setup as cross build') + assert(get_option('build.c_args') == ['-pedantic']) endif diff --git a/test cases/unit/69 cross/nativefile.in b/test cases/unit/69 cross/nativefile.in new file mode 100644 index 0000000..9a63999 --- /dev/null +++ b/test cases/unit/69 cross/nativefile.in @@ -0,0 +1,2 @@ +[built-in options] +build.c_args = ['@c_args@'] diff --git a/test cases/unit/91 install skip subprojects/foo.c b/test cases/unit/92 install skip subprojects/foo.c index 25927f5..25927f5 100644 --- a/test cases/unit/91 install skip subprojects/foo.c +++ b/test cases/unit/92 install skip subprojects/foo.c diff --git a/test cases/unit/91 install skip subprojects/foo.dat b/test cases/unit/92 install skip subprojects/foo.dat index 421376d..421376d 100644 --- a/test cases/unit/91 install skip subprojects/foo.dat +++ b/test cases/unit/92 install skip subprojects/foo.dat diff --git a/test cases/unit/91 install skip subprojects/foo.h b/test cases/unit/92 install skip subprojects/foo.h index a7e26ac..a7e26ac 100644 --- a/test cases/unit/91 install skip subprojects/foo.h +++ b/test cases/unit/92 install skip subprojects/foo.h diff --git a/test cases/unit/91 install skip subprojects/foo/foofile b/test cases/unit/92 install skip subprojects/foo/foofile index e69de29..e69de29 100644 --- a/test cases/unit/91 install skip subprojects/foo/foofile +++ b/test cases/unit/92 install skip subprojects/foo/foofile diff --git a/test cases/unit/91 install skip subprojects/meson.build b/test cases/unit/92 install skip subprojects/meson.build index cfbae94..cfbae94 100644 --- a/test cases/unit/91 install skip subprojects/meson.build +++ b/test cases/unit/92 install skip subprojects/meson.build diff --git a/test cases/unit/91 install skip subprojects/subprojects/bar/bar.c b/test cases/unit/92 install skip subprojects/subprojects/bar/bar.c index 25927f5..25927f5 100644 --- a/test cases/unit/91 install skip subprojects/subprojects/bar/bar.c +++ b/test cases/unit/92 install skip subprojects/subprojects/bar/bar.c diff --git a/test cases/unit/91 install skip subprojects/subprojects/bar/bar.dat b/test cases/unit/92 install skip subprojects/subprojects/bar/bar.dat index 421376d..421376d 100644 --- a/test cases/unit/91 install skip subprojects/subprojects/bar/bar.dat +++ b/test cases/unit/92 install skip subprojects/subprojects/bar/bar.dat diff --git a/test cases/unit/91 install skip subprojects/subprojects/bar/bar.h b/test cases/unit/92 install skip subprojects/subprojects/bar/bar.h index a7e26ac..a7e26ac 100644 --- a/test cases/unit/91 install skip subprojects/subprojects/bar/bar.h +++ b/test cases/unit/92 install skip subprojects/subprojects/bar/bar.h diff --git a/test cases/unit/91 install skip subprojects/subprojects/bar/bar/barfile b/test cases/unit/92 install skip subprojects/subprojects/bar/bar/barfile index 421376d..421376d 100644 --- a/test cases/unit/91 install skip subprojects/subprojects/bar/bar/barfile +++ b/test cases/unit/92 install skip subprojects/subprojects/bar/bar/barfile diff --git a/test cases/unit/91 install skip subprojects/subprojects/bar/meson.build b/test cases/unit/92 install skip subprojects/subprojects/bar/meson.build index b5b0734..b5b0734 100644 --- a/test cases/unit/91 install skip subprojects/subprojects/bar/meson.build +++ b/test cases/unit/92 install skip subprojects/subprojects/bar/meson.build diff --git a/test cases/unit/92 new subproject in configured project/meson.build b/test cases/unit/93 new subproject in configured project/meson.build index b82aa41..b82aa41 100644 --- a/test cases/unit/92 new subproject in configured project/meson.build +++ b/test cases/unit/93 new subproject in configured project/meson.build diff --git a/test cases/unit/92 new subproject in configured project/meson_options.txt b/test cases/unit/93 new subproject in configured project/meson_options.txt index 12d8395..12d8395 100644 --- a/test cases/unit/92 new subproject in configured project/meson_options.txt +++ b/test cases/unit/93 new subproject in configured project/meson_options.txt diff --git a/test cases/unit/92 new subproject in configured project/subprojects/sub/foo.c b/test cases/unit/93 new subproject in configured project/subprojects/sub/foo.c index 9713d9f..9713d9f 100644 --- a/test cases/unit/92 new subproject in configured project/subprojects/sub/foo.c +++ b/test cases/unit/93 new subproject in configured project/subprojects/sub/foo.c diff --git a/test cases/unit/92 new subproject in configured project/subprojects/sub/meson.build b/test cases/unit/93 new subproject in configured project/subprojects/sub/meson.build index a833b0c..a833b0c 100644 --- a/test cases/unit/92 new subproject in configured project/subprojects/sub/meson.build +++ b/test cases/unit/93 new subproject in configured project/subprojects/sub/meson.build diff --git a/test cases/unit/93 clangformat/.clang-format b/test cases/unit/94 clangformat/.clang-format index 689bc60..689bc60 100644 --- a/test cases/unit/93 clangformat/.clang-format +++ b/test cases/unit/94 clangformat/.clang-format diff --git a/test cases/unit/93 clangformat/.clang-format-ignore b/test cases/unit/94 clangformat/.clang-format-ignore index 7fc4d5a..7fc4d5a 100644 --- a/test cases/unit/93 clangformat/.clang-format-ignore +++ b/test cases/unit/94 clangformat/.clang-format-ignore diff --git a/test cases/unit/93 clangformat/.clang-format-include b/test cases/unit/94 clangformat/.clang-format-include index f057c00..f057c00 100644 --- a/test cases/unit/93 clangformat/.clang-format-include +++ b/test cases/unit/94 clangformat/.clang-format-include diff --git a/test cases/unit/93 clangformat/meson.build b/test cases/unit/94 clangformat/meson.build index 8f4af98..8f4af98 100644 --- a/test cases/unit/93 clangformat/meson.build +++ b/test cases/unit/94 clangformat/meson.build diff --git a/test cases/unit/93 clangformat/not-included/badformat.cpp b/test cases/unit/94 clangformat/not-included/badformat.cpp index 99a0ea6..99a0ea6 100644 --- a/test cases/unit/93 clangformat/not-included/badformat.cpp +++ b/test cases/unit/94 clangformat/not-included/badformat.cpp diff --git a/test cases/unit/93 clangformat/src/badformat.c b/test cases/unit/94 clangformat/src/badformat.c index f1d18b7..f1d18b7 100644 --- a/test cases/unit/93 clangformat/src/badformat.c +++ b/test cases/unit/94 clangformat/src/badformat.c diff --git a/test cases/unit/93 clangformat/src/badformat.cpp b/test cases/unit/94 clangformat/src/badformat.cpp index 99a0ea6..99a0ea6 100644 --- a/test cases/unit/93 clangformat/src/badformat.cpp +++ b/test cases/unit/94 clangformat/src/badformat.cpp diff --git a/test cases/unit/94 custominc/easytogrepfor/genh.py b/test cases/unit/95 custominc/easytogrepfor/genh.py index 48e033a..48e033a 100644 --- a/test cases/unit/94 custominc/easytogrepfor/genh.py +++ b/test cases/unit/95 custominc/easytogrepfor/genh.py diff --git a/test cases/unit/94 custominc/easytogrepfor/meson.build b/test cases/unit/95 custominc/easytogrepfor/meson.build index e749753..e749753 100644 --- a/test cases/unit/94 custominc/easytogrepfor/meson.build +++ b/test cases/unit/95 custominc/easytogrepfor/meson.build diff --git a/test cases/unit/94 custominc/helper.c b/test cases/unit/95 custominc/helper.c index 3237441..3237441 100644 --- a/test cases/unit/94 custominc/helper.c +++ b/test cases/unit/95 custominc/helper.c diff --git a/test cases/unit/94 custominc/meson.build b/test cases/unit/95 custominc/meson.build index bab1139..bab1139 100644 --- a/test cases/unit/94 custominc/meson.build +++ b/test cases/unit/95 custominc/meson.build diff --git a/test cases/unit/94 custominc/prog.c b/test cases/unit/95 custominc/prog.c index db9df9d..db9df9d 100644 --- a/test cases/unit/94 custominc/prog.c +++ b/test cases/unit/95 custominc/prog.c diff --git a/test cases/unit/94 custominc/prog2.c b/test cases/unit/95 custominc/prog2.c index e64b229..e64b229 100644 --- a/test cases/unit/94 custominc/prog2.c +++ b/test cases/unit/95 custominc/prog2.c diff --git a/test cases/unit/95 implicit force fallback/meson.build b/test cases/unit/96 implicit force fallback/meson.build index 623a338..623a338 100644 --- a/test cases/unit/95 implicit force fallback/meson.build +++ b/test cases/unit/96 implicit force fallback/meson.build diff --git a/test cases/unit/95 implicit force fallback/subprojects/something/meson.build b/test cases/unit/96 implicit force fallback/subprojects/something/meson.build index 89a4727..89a4727 100644 --- a/test cases/unit/95 implicit force fallback/subprojects/something/meson.build +++ b/test cases/unit/96 implicit force fallback/subprojects/something/meson.build diff --git a/test cases/unit/96 compiler.links file arg/meson.build b/test cases/unit/97 compiler.links file arg/meson.build index c409dcb..c409dcb 100644 --- a/test cases/unit/96 compiler.links file arg/meson.build +++ b/test cases/unit/97 compiler.links file arg/meson.build diff --git a/test cases/unit/96 compiler.links file arg/test.c b/test cases/unit/97 compiler.links file arg/test.c index 78f2de1..78f2de1 100644 --- a/test cases/unit/96 compiler.links file arg/test.c +++ b/test cases/unit/97 compiler.links file arg/test.c diff --git a/test cases/unit/97 link full name/.gitignore b/test cases/unit/98 link full name/.gitignore index 8129601..8129601 100644 --- a/test cases/unit/97 link full name/.gitignore +++ b/test cases/unit/98 link full name/.gitignore diff --git a/test cases/unit/97 link full name/libtestprovider/meson.build b/test cases/unit/98 link full name/libtestprovider/meson.build index 128c213..128c213 100644 --- a/test cases/unit/97 link full name/libtestprovider/meson.build +++ b/test cases/unit/98 link full name/libtestprovider/meson.build diff --git a/test cases/unit/97 link full name/libtestprovider/provider.c b/test cases/unit/98 link full name/libtestprovider/provider.c index 5e79966..5e79966 100644 --- a/test cases/unit/97 link full name/libtestprovider/provider.c +++ b/test cases/unit/98 link full name/libtestprovider/provider.c diff --git a/test cases/unit/97 link full name/proguser/meson.build b/test cases/unit/98 link full name/proguser/meson.build index 5be5bc9..5be5bc9 100644 --- a/test cases/unit/97 link full name/proguser/meson.build +++ b/test cases/unit/98 link full name/proguser/meson.build diff --git a/test cases/unit/97 link full name/proguser/receiver.c b/test cases/unit/98 link full name/proguser/receiver.c index d661ae4..d661ae4 100644 --- a/test cases/unit/97 link full name/proguser/receiver.c +++ b/test cases/unit/98 link full name/proguser/receiver.c diff --git a/test cases/unit/98 install all targets/bar-custom.txt b/test cases/unit/99 install all targets/bar-custom.txt index e69de29..e69de29 100644 --- a/test cases/unit/98 install all targets/bar-custom.txt +++ b/test cases/unit/99 install all targets/bar-custom.txt diff --git a/test cases/unit/98 install all targets/bar-devel.h b/test cases/unit/99 install all targets/bar-devel.h index e69de29..e69de29 100644 --- a/test cases/unit/98 install all targets/bar-devel.h +++ b/test cases/unit/99 install all targets/bar-devel.h diff --git a/test cases/unit/98 install all targets/bar-notag.txt b/test cases/unit/99 install all targets/bar-notag.txt index e69de29..e69de29 100644 --- a/test cases/unit/98 install all targets/bar-notag.txt +++ b/test cases/unit/99 install all targets/bar-notag.txt diff --git a/test cases/unit/98 install all targets/custom_files/data.txt b/test cases/unit/99 install all targets/custom_files/data.txt index 557db03..557db03 100644 --- a/test cases/unit/98 install all targets/custom_files/data.txt +++ b/test cases/unit/99 install all targets/custom_files/data.txt diff --git a/test cases/unit/98 install all targets/excludes/excluded.txt b/test cases/unit/99 install all targets/excludes/excluded.txt index 59b0644..59b0644 100644 --- a/test cases/unit/98 install all targets/excludes/excluded.txt +++ b/test cases/unit/99 install all targets/excludes/excluded.txt diff --git a/test cases/unit/98 install all targets/excludes/excluded/placeholder.txt b/test cases/unit/99 install all targets/excludes/excluded/placeholder.txt index 3b94f91..3b94f91 100644 --- a/test cases/unit/98 install all targets/excludes/excluded/placeholder.txt +++ b/test cases/unit/99 install all targets/excludes/excluded/placeholder.txt diff --git a/test cases/unit/98 install all targets/excludes/installed.txt b/test cases/unit/99 install all targets/excludes/installed.txt index 8437692..8437692 100644 --- a/test cases/unit/98 install all targets/excludes/installed.txt +++ b/test cases/unit/99 install all targets/excludes/installed.txt diff --git a/test cases/unit/98 install all targets/foo.in b/test cases/unit/99 install all targets/foo.in index e69de29..e69de29 100644 --- a/test cases/unit/98 install all targets/foo.in +++ b/test cases/unit/99 install all targets/foo.in diff --git a/test cases/unit/98 install all targets/foo1-devel.h b/test cases/unit/99 install all targets/foo1-devel.h index e69de29..e69de29 100644 --- a/test cases/unit/98 install all targets/foo1-devel.h +++ b/test cases/unit/99 install all targets/foo1-devel.h diff --git a/test cases/unit/98 install all targets/lib.c b/test cases/unit/99 install all targets/lib.c index 2ea9c7d..2ea9c7d 100644 --- a/test cases/unit/98 install all targets/lib.c +++ b/test cases/unit/99 install all targets/lib.c diff --git a/test cases/unit/98 install all targets/main.c b/test cases/unit/99 install all targets/main.c index 0fb4389..0fb4389 100644 --- a/test cases/unit/98 install all targets/main.c +++ b/test cases/unit/99 install all targets/main.c diff --git a/test cases/unit/98 install all targets/meson.build b/test cases/unit/99 install all targets/meson.build index c5f33a0..c5f33a0 100644 --- a/test cases/unit/98 install all targets/meson.build +++ b/test cases/unit/99 install all targets/meson.build diff --git a/test cases/unit/98 install all targets/script.py b/test cases/unit/99 install all targets/script.py index c5f3be9..c5f3be9 100644 --- a/test cases/unit/98 install all targets/script.py +++ b/test cases/unit/99 install all targets/script.py diff --git a/test cases/unit/98 install all targets/subdir/bar2-devel.h b/test cases/unit/99 install all targets/subdir/bar2-devel.h index e69de29..e69de29 100644 --- a/test cases/unit/98 install all targets/subdir/bar2-devel.h +++ b/test cases/unit/99 install all targets/subdir/bar2-devel.h diff --git a/test cases/unit/98 install all targets/subdir/foo2.in b/test cases/unit/99 install all targets/subdir/foo2.in index e69de29..e69de29 100644 --- a/test cases/unit/98 install all targets/subdir/foo2.in +++ b/test cases/unit/99 install all targets/subdir/foo2.in diff --git a/test cases/unit/98 install all targets/subdir/foo3-devel.h b/test cases/unit/99 install all targets/subdir/foo3-devel.h index e69de29..e69de29 100644 --- a/test cases/unit/98 install all targets/subdir/foo3-devel.h +++ b/test cases/unit/99 install all targets/subdir/foo3-devel.h diff --git a/test cases/unit/98 install all targets/subdir/lib.c b/test cases/unit/99 install all targets/subdir/lib.c index 2ea9c7d..2ea9c7d 100644 --- a/test cases/unit/98 install all targets/subdir/lib.c +++ b/test cases/unit/99 install all targets/subdir/lib.c diff --git a/test cases/unit/98 install all targets/subdir/main.c b/test cases/unit/99 install all targets/subdir/main.c index 0fb4389..0fb4389 100644 --- a/test cases/unit/98 install all targets/subdir/main.c +++ b/test cases/unit/99 install all targets/subdir/main.c diff --git a/test cases/unit/98 install all targets/subdir/meson.build b/test cases/unit/99 install all targets/subdir/meson.build index 53c796a..53c796a 100644 --- a/test cases/unit/98 install all targets/subdir/meson.build +++ b/test cases/unit/99 install all targets/subdir/meson.build diff --git a/test cases/unit/98 install all targets/subdir/script.py b/test cases/unit/99 install all targets/subdir/script.py index c5f3be9..c5f3be9 100644 --- a/test cases/unit/98 install all targets/subdir/script.py +++ b/test cases/unit/99 install all targets/subdir/script.py diff --git a/test cases/unit/98 install all targets/subprojects/subproject/aaa.txt b/test cases/unit/99 install all targets/subprojects/subproject/aaa.txt index 43d5a8e..43d5a8e 100644 --- a/test cases/unit/98 install all targets/subprojects/subproject/aaa.txt +++ b/test cases/unit/99 install all targets/subprojects/subproject/aaa.txt diff --git a/test cases/unit/98 install all targets/subprojects/subproject/bbb.txt b/test cases/unit/99 install all targets/subprojects/subproject/bbb.txt index ba62923..ba62923 100644 --- a/test cases/unit/98 install all targets/subprojects/subproject/bbb.txt +++ b/test cases/unit/99 install all targets/subprojects/subproject/bbb.txt diff --git a/test cases/unit/98 install all targets/subprojects/subproject/meson.build b/test cases/unit/99 install all targets/subprojects/subproject/meson.build index ff474ac..ff474ac 100644 --- a/test cases/unit/98 install all targets/subprojects/subproject/meson.build +++ b/test cases/unit/99 install all targets/subprojects/subproject/meson.build diff --git a/test cases/vala/32 valaless vapigen/clib.c b/test cases/vala/32 valaless vapigen/clib.c new file mode 100644 index 0000000..a55ecd4 --- /dev/null +++ b/test cases/vala/32 valaless vapigen/clib.c @@ -0,0 +1,5 @@ +#include "clib.h" + +int clib_fun(void) { + return 42; +} diff --git a/test cases/vala/32 valaless vapigen/clib.h b/test cases/vala/32 valaless vapigen/clib.h new file mode 100644 index 0000000..4d855c9 --- /dev/null +++ b/test cases/vala/32 valaless vapigen/clib.h @@ -0,0 +1,3 @@ +#pragma once + +int clib_fun(void); diff --git a/test cases/vala/32 valaless vapigen/meson.build b/test cases/vala/32 valaless vapigen/meson.build new file mode 100644 index 0000000..22a99e5 --- /dev/null +++ b/test cases/vala/32 valaless vapigen/meson.build @@ -0,0 +1,34 @@ +project('valaless-vapigen', 'c') + +if host_machine.system() == 'cygwin' + error('MESON_SKIP_TEST Does not work with the Vala currently packaged in cygwin') +endif + +gnome = import('gnome') + +clib_src = [ + 'clib.c', + 'clib.h' +] + +clib_lib = shared_library('clib', clib_src) + +clib_gir = gnome.generate_gir(clib_lib, + sources: clib_src, + namespace: 'Clib', + nsversion: '0', + header: 'clib.h', + symbol_prefix: 'clib' +) + +clib_vapi = gnome.generate_vapi('clib', sources: clib_gir[0]) + +clib_dep = declare_dependency( + include_directories: include_directories('.'), + link_with: clib_lib, + sources: clib_gir, + dependencies: clib_vapi +) + + +test('clib-test', executable('clib-test', 'test_clib.c', dependencies: clib_dep)) diff --git a/test cases/vala/32 valaless vapigen/test_clib.c b/test cases/vala/32 valaless vapigen/test_clib.c new file mode 100644 index 0000000..6fd426c --- /dev/null +++ b/test cases/vala/32 valaless vapigen/test_clib.c @@ -0,0 +1,9 @@ +#include <stdlib.h> +#include <clib.h> + +int main(void) { + if (clib_fun () == 42) + return EXIT_SUCCESS; + else + return EXIT_FAILURE; +} diff --git a/tools/dircondenser.py b/tools/dircondenser.py index b8679a4..a07ede1 100755 --- a/tools/dircondenser.py +++ b/tools/dircondenser.py @@ -71,6 +71,7 @@ def condense(dirname: str) -> None: replace_source('run_unittests.py', replacements) replace_source('run_project_tests.py', replacements) replace_source('run_format_tests.py', replacements) + replace_source('run_shell_checks.py', replacements) for f in glob('unittests/*.py'): replace_source(f, replacements) diff --git a/unittests/__init__.py b/unittests/__init__.py index e69de29..fb8fb8e 100644 --- a/unittests/__init__.py +++ b/unittests/__init__.py @@ -0,0 +1,20 @@ +import os + +import mesonbuild.compilers +from mesonbuild.mesonlib import setup_vsenv + +def unset_envs(): + # For unit tests we must fully control all command lines + # so that there are no unexpected changes coming from the + # environment, for example when doing a package build. + varnames = ['CPPFLAGS', 'LDFLAGS'] + list(mesonbuild.compilers.compilers.CFLAGS_MAPPING.values()) + for v in varnames: + if v in os.environ: + del os.environ[v] + +def set_envs(): + os.environ.setdefault('MESON_UNIT_TEST_BACKEND', 'ninja') + +setup_vsenv() +unset_envs() +set_envs() diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 8f56611..535e479 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -529,7 +529,7 @@ class AllPlatformTests(BasePlatformTests): ''' if not shutil.which('xmllint'): raise SkipTest('xmllint not installed') - testdir = os.path.join(self.unit_test_dir, '111 replace unencodable xml chars') + testdir = os.path.join(self.unit_test_dir, '112 replace unencodable xml chars') self.init(testdir) tests_command_output = self.run_tests() junit_xml_logs = Path(self.logdir, 'testlog.junit.xml') @@ -690,7 +690,7 @@ class AllPlatformTests(BasePlatformTests): self.run_tests() def test_implicit_forcefallback(self): - testdir = os.path.join(self.unit_test_dir, '95 implicit force fallback') + testdir = os.path.join(self.unit_test_dir, '96 implicit force fallback') with self.assertRaises(subprocess.CalledProcessError): self.init(testdir) self.init(testdir, extra_args=['--wrap-mode=forcefallback']) @@ -1442,7 +1442,7 @@ class AllPlatformTests(BasePlatformTests): Test that conflicts between -D for builtin options and the corresponding long option are detected without false positives or negatives. ''' - testdir = os.path.join(self.unit_test_dir, '128 long opt vs D') + testdir = os.path.join(self.unit_test_dir, '130 long opt vs D') for opt in ['-Dsysconfdir=/etc', '-Dsysconfdir2=/etc']: exception_raised = False @@ -2188,7 +2188,7 @@ class AllPlatformTests(BasePlatformTests): check_pcfile('libvartest2.pc', relocatable=False) self.wipe() - testdir_abs = os.path.join(self.unit_test_dir, '105 pkgconfig relocatable with absolute path') + testdir_abs = os.path.join(self.unit_test_dir, '106 pkgconfig relocatable with absolute path') self.init(testdir_abs) check_pcfile('libsimple.pc', relocatable=True, levels=3) @@ -2270,7 +2270,7 @@ class AllPlatformTests(BasePlatformTests): self.assertDictEqual(original, expected) def test_executable_names(self): - testdir = os.path.join(self.unit_test_dir, '121 executable suffix') + testdir = os.path.join(self.unit_test_dir, '122 executable suffix') self.init(testdir) self.build() exe1 = os.path.join(self.builddir, 'foo' + exe_suffix) @@ -2361,7 +2361,7 @@ class AllPlatformTests(BasePlatformTests): def test_options_listed_in_build_options(self) -> None: """Detect when changed options become listed in build options.""" - testdir = os.path.join(self.unit_test_dir, '113 list build options') + testdir = os.path.join(self.unit_test_dir, '114 list build options') out = self.init(testdir) for line in out.splitlines(): @@ -2598,7 +2598,7 @@ class AllPlatformTests(BasePlatformTests): self.assertIn(msg, out) def test_mixed_language_linker_check(self): - testdir = os.path.join(self.unit_test_dir, '96 compiler.links file arg') + testdir = os.path.join(self.unit_test_dir, '97 compiler.links file arg') self.init(testdir) cmds = self.get_meson_log_compiler_checks() self.assertEqual(len(cmds), 5) @@ -3316,10 +3316,15 @@ class AllPlatformTests(BasePlatformTests): def test_identity_cross(self): testdir = os.path.join(self.unit_test_dir, '69 cross') # Do a build to generate a cross file where the host is this target - self.init(testdir, extra_args=['-Dgenerate=true']) + # build.c_args is ignored here. + self.init(testdir, extra_args=['-Dgenerate=true', '-Dc_args=-funroll-loops', + '-Dbuild.c_args=-pedantic']) + self.meson_native_files = [os.path.join(self.builddir, "nativefile")] + self.assertTrue(os.path.exists(self.meson_native_files[0])) self.meson_cross_files = [os.path.join(self.builddir, "crossfile")] self.assertTrue(os.path.exists(self.meson_cross_files[0])) - # Now verify that this is detected as cross + # Now verify that this is detected as cross and build options are + # processed correctly self.new_builddir() self.init(testdir) @@ -4438,7 +4443,7 @@ class AllPlatformTests(BasePlatformTests): self.init(srcdir, extra_args=['-Dbuild.b_lto=true']) def test_install_skip_subprojects(self): - testdir = os.path.join(self.unit_test_dir, '91 install skip subprojects') + testdir = os.path.join(self.unit_test_dir, '92 install skip subprojects') self.init(testdir) self.build() @@ -4485,7 +4490,7 @@ class AllPlatformTests(BasePlatformTests): check_installed_files(['--skip-subprojects', 'another'], all_expected) def test_adding_subproject_to_configure_project(self) -> None: - srcdir = os.path.join(self.unit_test_dir, '92 new subproject in configured project') + srcdir = os.path.join(self.unit_test_dir, '93 new subproject in configured project') self.init(srcdir) self.build() self.setconf('-Duse-sub=true') @@ -4539,7 +4544,7 @@ class AllPlatformTests(BasePlatformTests): if not shutil.which('clang-format'): raise SkipTest('clang-format not found') - testdir = os.path.join(self.unit_test_dir, '93 clangformat') + testdir = os.path.join(self.unit_test_dir, '94 clangformat') newdir = os.path.join(self.builddir, 'testdir') shutil.copytree(testdir, newdir) self.new_builddir() @@ -4564,7 +4569,7 @@ class AllPlatformTests(BasePlatformTests): self.build('clang-format-check') def test_custom_target_implicit_include(self): - testdir = os.path.join(self.unit_test_dir, '94 custominc') + testdir = os.path.join(self.unit_test_dir, '95 custominc') self.init(testdir) self.build() compdb = self.get_compdb() @@ -4610,7 +4615,7 @@ class AllPlatformTests(BasePlatformTests): # self.assertEqual(sorted(link_args), sorted(['-flto'])) def test_install_tag(self) -> None: - testdir = os.path.join(self.unit_test_dir, '98 install all targets') + testdir = os.path.join(self.unit_test_dir, '99 install all targets') self.init(testdir) self.build() @@ -4781,7 +4786,7 @@ class AllPlatformTests(BasePlatformTests): def test_introspect_install_plan(self): - testdir = os.path.join(self.unit_test_dir, '98 install all targets') + testdir = os.path.join(self.unit_test_dir, '99 install all targets') introfile = os.path.join(self.builddir, 'meson-info', 'intro-install_plan.json') self.init(testdir) self.assertPathExists(introfile) @@ -4802,124 +4807,145 @@ class AllPlatformTests(BasePlatformTests): shared_lib_name = lambda name: output_name(name, SharedLibrary) static_lib_name = lambda name: output_name(name, StaticLibrary) exe_name = lambda name: output_name(name, Executable) + get_path = lambda f: Path(f).as_posix() expected = { 'targets': { - f'{self.builddir}/out1-notag.txt': { + get_path(f'{self.builddir}/out1-notag.txt'): { + 'build_rpaths': [], 'destination': '{datadir}/out1-notag.txt', 'install_rpath': None, 'tag': None, 'subproject': None, }, - f'{self.builddir}/out2-notag.txt': { + get_path(f'{self.builddir}/out2-notag.txt'): { + 'build_rpaths': [], 'destination': '{datadir}/out2-notag.txt', 'install_rpath': None, 'tag': None, 'subproject': None, }, - f'{self.builddir}/libstatic.a': { + get_path(f'{self.builddir}/libstatic.a'): { + 'build_rpaths': [], 'destination': '{libdir_static}/libstatic.a', 'install_rpath': None, 'tag': 'devel', 'subproject': None, }, - f'{self.builddir}/' + exe_name('app'): { + get_path(f'{self.builddir}/' + exe_name('app')): { + 'build_rpaths': [], 'destination': '{bindir}/' + exe_name('app'), 'install_rpath': None, 'tag': 'runtime', 'subproject': None, }, - f'{self.builddir}/' + exe_name('app-otherdir'): { + get_path(f'{self.builddir}/' + exe_name('app-otherdir')): { + 'build_rpaths': [], 'destination': '{prefix}/otherbin/' + exe_name('app-otherdir'), 'install_rpath': None, 'tag': 'runtime', 'subproject': None, }, - f'{self.builddir}/subdir/' + exe_name('app2'): { + get_path(f'{self.builddir}/subdir/' + exe_name('app2')): { + 'build_rpaths': [], 'destination': '{bindir}/' + exe_name('app2'), 'install_rpath': None, 'tag': 'runtime', 'subproject': None, }, - f'{self.builddir}/' + shared_lib_name('shared'): { + get_path(f'{self.builddir}/' + shared_lib_name('shared')): { + 'build_rpaths': [], 'destination': '{libdir_shared}/' + shared_lib_name('shared'), 'install_rpath': None, 'tag': 'runtime', 'subproject': None, }, - f'{self.builddir}/' + shared_lib_name('both'): { + get_path(f'{self.builddir}/' + shared_lib_name('both')): { + 'build_rpaths': [], 'destination': '{libdir_shared}/' + shared_lib_name('both'), 'install_rpath': None, 'tag': 'runtime', 'subproject': None, }, - f'{self.builddir}/' + static_lib_name('both'): { + get_path(f'{self.builddir}/' + static_lib_name('both')): { + 'build_rpaths': [], 'destination': '{libdir_static}/' + static_lib_name('both'), 'install_rpath': None, 'tag': 'devel', 'subproject': None, }, - f'{self.builddir}/' + shared_lib_name('bothcustom'): { + get_path(f'{self.builddir}/' + shared_lib_name('bothcustom')): { + 'build_rpaths': [], 'destination': '{libdir_shared}/' + shared_lib_name('bothcustom'), 'install_rpath': None, 'tag': 'custom', 'subproject': None, }, - f'{self.builddir}/' + static_lib_name('bothcustom'): { + get_path(f'{self.builddir}/' + static_lib_name('bothcustom')): { + 'build_rpaths': [], 'destination': '{libdir_static}/' + static_lib_name('bothcustom'), 'install_rpath': None, 'tag': 'custom', 'subproject': None, }, - f'{self.builddir}/subdir/' + shared_lib_name('both2'): { + get_path(f'{self.builddir}/subdir/' + shared_lib_name('both2')): { + 'build_rpaths': [], 'destination': '{libdir_shared}/' + shared_lib_name('both2'), 'install_rpath': None, 'tag': 'runtime', 'subproject': None, }, - f'{self.builddir}/subdir/' + static_lib_name('both2'): { + get_path(f'{self.builddir}/subdir/' + static_lib_name('both2')): { + 'build_rpaths': [], 'destination': '{libdir_static}/' + static_lib_name('both2'), 'install_rpath': None, 'tag': 'devel', 'subproject': None, }, - f'{self.builddir}/out1-custom.txt': { + get_path(f'{self.builddir}/out1-custom.txt'): { + 'build_rpaths': [], 'destination': '{datadir}/out1-custom.txt', 'install_rpath': None, 'tag': 'custom', 'subproject': None, }, - f'{self.builddir}/out2-custom.txt': { + get_path(f'{self.builddir}/out2-custom.txt'): { + 'build_rpaths': [], 'destination': '{datadir}/out2-custom.txt', 'install_rpath': None, 'tag': 'custom', 'subproject': None, }, - f'{self.builddir}/out3-custom.txt': { + get_path(f'{self.builddir}/out3-custom.txt'): { + 'build_rpaths': [], 'destination': '{datadir}/out3-custom.txt', 'install_rpath': None, 'tag': 'custom', 'subproject': None, }, - f'{self.builddir}/subdir/out1.txt': { + get_path(f'{self.builddir}/subdir/out1.txt'): { + 'build_rpaths': [], 'destination': '{datadir}/out1.txt', 'install_rpath': None, 'tag': None, 'subproject': None, }, - f'{self.builddir}/subdir/out2.txt': { + get_path(f'{self.builddir}/subdir/out2.txt'): { + 'build_rpaths': [], 'destination': '{datadir}/out2.txt', 'install_rpath': None, 'tag': None, 'subproject': None, }, - f'{self.builddir}/out-devel.h': { + get_path(f'{self.builddir}/out-devel.h'): { + 'build_rpaths': [], 'destination': '{includedir}/out-devel.h', 'install_rpath': None, 'tag': 'devel', 'subproject': None, }, - f'{self.builddir}/out3-notag.txt': { + get_path(f'{self.builddir}/out3-notag.txt'): { + 'build_rpaths': [], 'destination': '{datadir}/out3-notag.txt', 'install_rpath': None, 'tag': None, @@ -4927,80 +4953,80 @@ class AllPlatformTests(BasePlatformTests): }, }, 'configure': { - f'{self.builddir}/foo-notag.h': { + get_path(f'{self.builddir}/foo-notag.h'): { 'destination': '{datadir}/foo-notag.h', 'tag': None, 'subproject': None, }, - f'{self.builddir}/foo2-devel.h': { + get_path(f'{self.builddir}/foo2-devel.h'): { 'destination': '{includedir}/foo2-devel.h', 'tag': 'devel', 'subproject': None, }, - f'{self.builddir}/foo-custom.h': { + get_path(f'{self.builddir}/foo-custom.h'): { 'destination': '{datadir}/foo-custom.h', 'tag': 'custom', 'subproject': None, }, - f'{self.builddir}/subdir/foo2.h': { + get_path(f'{self.builddir}/subdir/foo2.h'): { 'destination': '{datadir}/foo2.h', 'tag': None, 'subproject': None, }, }, 'data': { - f'{testdir}/bar-notag.txt': { + get_path(f'{testdir}/bar-notag.txt'): { 'destination': '{datadir}/bar-notag.txt', 'tag': None, 'subproject': None, }, - f'{testdir}/bar-devel.h': { + get_path(f'{testdir}/bar-devel.h'): { 'destination': '{includedir}/bar-devel.h', 'tag': 'devel', 'subproject': None, }, - f'{testdir}/bar-custom.txt': { + get_path(f'{testdir}/bar-custom.txt'): { 'destination': '{datadir}/bar-custom.txt', 'tag': 'custom', 'subproject': None, }, - f'{testdir}/subdir/bar2-devel.h': { + get_path(f'{testdir}/subdir/bar2-devel.h'): { 'destination': '{includedir}/bar2-devel.h', 'tag': 'devel', 'subproject': None, }, - f'{testdir}/subprojects/subproject/aaa.txt': { + get_path(f'{testdir}/subprojects/subproject/aaa.txt'): { 'destination': '{datadir}/subproject/aaa.txt', 'tag': None, 'subproject': 'subproject', }, - f'{testdir}/subprojects/subproject/bbb.txt': { + get_path(f'{testdir}/subprojects/subproject/bbb.txt'): { 'destination': '{datadir}/subproject/bbb.txt', 'tag': 'data', 'subproject': 'subproject', }, }, 'headers': { - f'{testdir}/foo1-devel.h': { + get_path(f'{testdir}/foo1-devel.h'): { 'destination': '{includedir}/foo1-devel.h', 'tag': 'devel', 'subproject': None, }, - f'{testdir}/subdir/foo3-devel.h': { + get_path(f'{testdir}/subdir/foo3-devel.h'): { 'destination': '{includedir}/foo3-devel.h', 'tag': 'devel', 'subproject': None, }, }, 'install_subdirs': { - f'{testdir}/custom_files': { + get_path(f'{testdir}/custom_files'): { 'destination': '{datadir}/custom_files', 'tag': 'custom', 'subproject': None, 'exclude_dirs': [], 'exclude_files': [], }, - f'{testdir}/excludes': { + get_path(f'{testdir}/excludes'): { 'destination': '{datadir}/excludes', 'tag': 'custom', 'subproject': None, @@ -5010,11 +5036,10 @@ class AllPlatformTests(BasePlatformTests): } } - fix_path = lambda path: os.path.sep.join(path.split('/')) expected_fixed = { data_type: { - fix_path(source): { - key: fix_path(value) if key == 'destination' else value + get_path(source): { + key: get_path(value) if key == 'destination' else value for key, value in attributes.items() } for source, attributes in files.items() @@ -5025,6 +5050,7 @@ class AllPlatformTests(BasePlatformTests): for data_type, files in expected_fixed.items(): for file, details in files.items(): with self.subTest(key='{}.{}'.format(data_type, file)): + if data_type == 'data': print(res[data_type]) self.assertEqual(res[data_type][file], details) @skip_if_not_language('rust') @@ -5096,7 +5122,7 @@ class AllPlatformTests(BasePlatformTests): }} ''') - testdir = os.path.join(self.unit_test_dir, '101 rlib linkage') + testdir = os.path.join(self.unit_test_dir, '102 rlib linkage') gen_file = os.path.join(testdir, 'lib.rs') with open(gen_file, 'w', encoding='utf-8') as f: f.write(template.format(0)) @@ -5144,7 +5170,7 @@ class AllPlatformTests(BasePlatformTests): return def test_custom_target_name(self): - testdir = os.path.join(self.unit_test_dir, '99 custom target name') + testdir = os.path.join(self.unit_test_dir, '100 custom target name') self.init(testdir) out = self.build() if self.backend is Backend.ninja: @@ -5152,7 +5178,7 @@ class AllPlatformTests(BasePlatformTests): self.assertIn('Generating subdir/file.txt with a custom command', out) def test_symlinked_subproject(self): - testdir = os.path.join(self.unit_test_dir, '107 subproject symlink') + testdir = os.path.join(self.unit_test_dir, '108 subproject symlink') subproject_dir = os.path.join(testdir, 'subprojects') subproject = os.path.join(testdir, 'symlinked_subproject') symlinked_subproject = os.path.join(testdir, 'subprojects', 'symlinked_subproject') @@ -5168,7 +5194,7 @@ class AllPlatformTests(BasePlatformTests): self.build() def test_configure_same_noop(self): - testdir = os.path.join(self.unit_test_dir, '109 configure same noop') + testdir = os.path.join(self.unit_test_dir, '110 configure same noop') args = [ '-Dstring=val', '-Dboolean=true', @@ -5206,7 +5232,7 @@ class AllPlatformTests(BasePlatformTests): def __test_multi_stds(self, test_c: bool = True, test_objc: bool = False) -> None: assert test_c or test_objc, 'must test something' - testdir = os.path.join(self.unit_test_dir, '115 c cpp stds') + testdir = os.path.join(self.unit_test_dir, '116 c cpp stds') self.init(testdir, extra_args=[f'-Dwith-c={str(test_c).lower()}', f'-Dwith-objc={str(test_objc).lower()}']) # Invalid values should fail whatever compiler we have @@ -5265,7 +5291,7 @@ class AllPlatformTests(BasePlatformTests): self.__test_multi_stds(test_objc=True) def test_slice(self): - testdir = os.path.join(self.unit_test_dir, '126 test slice') + testdir = os.path.join(self.unit_test_dir, '128 test slice') self.init(testdir) self.build() @@ -5303,7 +5329,7 @@ class AllPlatformTests(BasePlatformTests): self.assertEqual(cc.linker.get_accepts_rsp(), has_rsp) def test_nonexisting_bargs(self): - testdir = os.path.join(self.unit_test_dir, '116 empty project') + testdir = os.path.join(self.unit_test_dir, '117 empty project') args = ['-Db_ndebug=if_release'] self.init(testdir, extra_args=args) @@ -5313,7 +5339,7 @@ class AllPlatformTests(BasePlatformTests): self.init(testdir, extra_args=['--wipe']) def test_interactive_tap(self): - testdir = os.path.join(self.unit_test_dir, '124 interactive tap') + testdir = os.path.join(self.unit_test_dir, '125 interactive tap') self.init(testdir, extra_args=['--wrap-mode=forcefallback']) output = self._run(self.mtest_command + ['--interactive']) self.assertRegex(output, r'Ok:\s*0') diff --git a/unittests/cargotests.py b/unittests/cargotests.py index eeb676b..7c09ab9 100644 --- a/unittests/cargotests.py +++ b/unittests/cargotests.py @@ -8,8 +8,10 @@ import tempfile import textwrap import typing as T -from mesonbuild.cargo import builder, cfg, load_wraps +from mesonbuild.cargo import cfg, load_wraps from mesonbuild.cargo.cfg import TokenType +from mesonbuild.cargo.manifest import Dependency, Manifest, Package, Workspace +from mesonbuild.cargo.toml import load_toml from mesonbuild.cargo.version import convert @@ -206,3 +208,261 @@ class CargoLockTest(unittest.TestCase): self.assertEqual(wraps[1].get('method'), 'cargo') self.assertEqual(wraps[1].get('url'), 'https://github.com/gtk-rs/gtk-rs-core') self.assertEqual(wraps[1].get('revision'), '23c5599424cc75ec66618891c915d9f490f6e4c2') + +class CargoTomlTest(unittest.TestCase): + CARGO_TOML_1 = textwrap.dedent('''\ + [package] + name = "mandelbrot" + version = "0.1.0" + authors = ["Sebastian Dröge <sebastian@centricular.com>"] + edition = "2018" + license = "GPL-3.0" + + [package.metadata.docs.rs] + all-features = true + rustc-args = [ + "--cfg", + "docsrs", + ] + rustdoc-args = [ + "--cfg", + "docsrs", + "--generate-link-to-definition", + ] + + [dependencies] + gtk = { package = "gtk4", version = "0.9" } + num-complex = "0.4" + rayon = "1.0" + once_cell = "1" + async-channel = "2.0" + zerocopy = { version = "0.7", features = ["derive"] } + + [dev-dependencies.gir-format-check] + version = "^0.1" + ''') + + CARGO_TOML_2 = textwrap.dedent('''\ + [package] + name = "pango" + edition = "2021" + rust-version = "1.70" + version = "0.20.4" + authors = ["The gtk-rs Project Developers"] + + [package.metadata.system-deps.pango] + name = "pango" + version = "1.40" + + [package.metadata.system-deps.pango.v1_42] + version = "1.42" + + [lib] + name = "pango" + + [[test]] + name = "check_gir" + path = "tests/check_gir.rs" + + [features] + v1_42 = ["pango-sys/v1_42"] + v1_44 = [ + "v1_42", + "pango-sys/v1_44", + ] + ''') + + CARGO_TOML_WS = textwrap.dedent('''\ + [workspace] + resolver = "2" + members = ["tutorial"] + + [workspace.package] + version = "0.14.0-alpha.1" + repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs" + edition = "2021" + rust-version = "1.83" + + [workspace.dependencies] + glib = { path = "glib" } + gtk = { package = "gtk4", version = "0.9" } + once_cell = "1.0" + syn = { version = "2", features = ["parse"] } + ''') + + def test_cargo_toml_ws_package(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_WS) + workspace_toml = load_toml(fname) + + workspace = Workspace.from_raw(workspace_toml) + pkg = Package.from_raw({'name': 'foo', 'version': {'workspace': True}}, workspace) + self.assertEqual(pkg.name, 'foo') + self.assertEqual(pkg.version, '0.14.0-alpha.1') + self.assertEqual(pkg.edition, '2015') + self.assertEqual(pkg.repository, None) + + def test_cargo_toml_ws_dependency(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_WS) + workspace_toml = load_toml(fname) + + workspace = Workspace.from_raw(workspace_toml) + dep = Dependency.from_raw('glib', {'workspace': True}, 'member', workspace) + self.assertEqual(dep.package, 'glib') + self.assertEqual(dep.version, '') + self.assertEqual(dep.meson_version, []) + self.assertEqual(dep.path, os.path.join('..', 'glib')) + self.assertEqual(dep.features, []) + + dep = Dependency.from_raw('gtk', {'workspace': True}, 'member', workspace) + self.assertEqual(dep.package, 'gtk4') + self.assertEqual(dep.version, '0.9') + self.assertEqual(dep.meson_version, ['>= 0.9', '< 0.10']) + self.assertEqual(dep.api, '0.9') + self.assertEqual(dep.features, []) + + dep = Dependency.from_raw('once_cell', {'workspace': True, 'optional': True}, 'member', workspace) + self.assertEqual(dep.package, 'once_cell') + self.assertEqual(dep.version, '1.0') + self.assertEqual(dep.meson_version, ['>= 1.0', '< 2']) + self.assertEqual(dep.api, '1') + self.assertEqual(dep.features, []) + self.assertTrue(dep.optional) + + dep = Dependency.from_raw('syn', {'workspace': True, 'features': ['full']}, 'member', workspace) + self.assertEqual(dep.package, 'syn') + self.assertEqual(dep.version, '2') + self.assertEqual(dep.meson_version, ['>= 2', '< 3']) + self.assertEqual(dep.api, '2') + self.assertEqual(sorted(set(dep.features)), ['full', 'parse']) + + def test_cargo_toml_package(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_1) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertEqual(manifest.package.name, 'mandelbrot') + self.assertEqual(manifest.package.version, '0.1.0') + self.assertEqual(manifest.package.authors[0], 'Sebastian Dröge <sebastian@centricular.com>') + self.assertEqual(manifest.package.edition, '2018') + self.assertEqual(manifest.package.license, 'GPL-3.0') + + print(manifest.package.metadata) + self.assertEqual(len(manifest.package.metadata), 1) + + def test_cargo_toml_dependencies(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_1) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertEqual(len(manifest.dependencies), 6) + self.assertEqual(manifest.dependencies['gtk'].package, 'gtk4') + self.assertEqual(manifest.dependencies['gtk'].version, '0.9') + self.assertEqual(manifest.dependencies['gtk'].meson_version, ['>= 0.9', '< 0.10']) + self.assertEqual(manifest.dependencies['gtk'].api, '0.9') + self.assertEqual(manifest.dependencies['num-complex'].package, 'num-complex') + self.assertEqual(manifest.dependencies['num-complex'].version, '0.4') + self.assertEqual(manifest.dependencies['num-complex'].meson_version, ['>= 0.4', '< 0.5']) + self.assertEqual(manifest.dependencies['rayon'].package, 'rayon') + self.assertEqual(manifest.dependencies['rayon'].version, '1.0') + self.assertEqual(manifest.dependencies['rayon'].meson_version, ['>= 1.0', '< 2']) + self.assertEqual(manifest.dependencies['rayon'].api, '1') + self.assertEqual(manifest.dependencies['once_cell'].package, 'once_cell') + self.assertEqual(manifest.dependencies['once_cell'].version, '1') + self.assertEqual(manifest.dependencies['once_cell'].meson_version, ['>= 1', '< 2']) + self.assertEqual(manifest.dependencies['once_cell'].api, '1') + self.assertEqual(manifest.dependencies['async-channel'].package, 'async-channel') + self.assertEqual(manifest.dependencies['async-channel'].version, '2.0') + self.assertEqual(manifest.dependencies['async-channel'].meson_version, ['>= 2.0', '< 3']) + self.assertEqual(manifest.dependencies['async-channel'].api, '2') + self.assertEqual(manifest.dependencies['zerocopy'].package, 'zerocopy') + self.assertEqual(manifest.dependencies['zerocopy'].version, '0.7') + self.assertEqual(manifest.dependencies['zerocopy'].meson_version, ['>= 0.7', '< 0.8']) + self.assertEqual(manifest.dependencies['zerocopy'].features, ['derive']) + self.assertEqual(manifest.dependencies['zerocopy'].api, '0.7') + + self.assertEqual(len(manifest.dev_dependencies), 1) + self.assertEqual(manifest.dev_dependencies['gir-format-check'].package, 'gir-format-check') + self.assertEqual(manifest.dev_dependencies['gir-format-check'].version, '^0.1') + self.assertEqual(manifest.dev_dependencies['gir-format-check'].meson_version, ['>= 0.1', '< 0.2']) + self.assertEqual(manifest.dev_dependencies['gir-format-check'].api, '0.1') + + def test_cargo_toml_targets(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_2) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertEqual(manifest.lib.name, 'pango') + self.assertEqual(manifest.lib.crate_type, ['lib']) + self.assertEqual(manifest.lib.path, os.path.join('src', 'lib.rs')) + self.assertEqual(manifest.lib.test, True) + self.assertEqual(manifest.lib.doctest, True) + self.assertEqual(manifest.lib.bench, True) + self.assertEqual(manifest.lib.doc, True) + self.assertEqual(manifest.lib.harness, True) + self.assertEqual(manifest.lib.edition, '2015') + self.assertEqual(manifest.lib.required_features, []) + self.assertEqual(manifest.lib.plugin, False) + self.assertEqual(manifest.lib.proc_macro, False) + self.assertEqual(manifest.lib.doc_scrape_examples, True) + + self.assertEqual(len(manifest.test), 1) + self.assertEqual(manifest.test[0].name, 'check_gir') + self.assertEqual(manifest.test[0].crate_type, ['bin']) + self.assertEqual(manifest.test[0].path, 'tests/check_gir.rs') + self.assertEqual(manifest.lib.path, os.path.join('src', 'lib.rs')) + self.assertEqual(manifest.test[0].test, True) + self.assertEqual(manifest.test[0].doctest, False) + self.assertEqual(manifest.test[0].bench, True) + self.assertEqual(manifest.test[0].doc, False) + self.assertEqual(manifest.test[0].harness, True) + self.assertEqual(manifest.test[0].edition, '2015') + self.assertEqual(manifest.test[0].required_features, []) + self.assertEqual(manifest.test[0].plugin, False) + + def test_cargo_toml_system_deps(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_2) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertIn('system-deps', manifest.package.metadata) + + self.assertEqual(len(manifest.system_dependencies), 1) + self.assertEqual(manifest.system_dependencies['pango'].name, 'pango') + self.assertEqual(manifest.system_dependencies['pango'].version, '1.40') + self.assertEqual(manifest.system_dependencies['pango'].meson_version, ['>=1.40']) + self.assertEqual(manifest.system_dependencies['pango'].optional, False) + self.assertEqual(manifest.system_dependencies['pango'].feature, None) + + self.assertEqual(len(manifest.system_dependencies['pango'].feature_overrides), 1) + self.assertEqual(manifest.system_dependencies['pango'].feature_overrides['v1_42'], {'version': '1.42'}) + + def test_cargo_toml_features(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_2) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertEqual(len(manifest.features), 3) + self.assertEqual(manifest.features['v1_42'], ['pango-sys/v1_42']) + self.assertEqual(manifest.features['v1_44'], ['v1_42', 'pango-sys/v1_44']) + self.assertEqual(manifest.features['default'], []) diff --git a/unittests/linuxliketests.py b/unittests/linuxliketests.py index 376c395..073f03c 100644 --- a/unittests/linuxliketests.py +++ b/unittests/linuxliketests.py @@ -446,6 +446,24 @@ class LinuxlikeTests(BasePlatformTests): libdir = self.installdir + os.path.join(self.prefix, self.libdir) self._test_soname_impl(libdir, True) + @skip_if_not_base_option('b_sanitize') + def test_c_link_args_and_env(self): + ''' + Test that the CFLAGS / CXXFLAGS environment variables are + included on the linker command line when c_link_args is + set but c_args is not. + ''' + if is_cygwin(): + raise SkipTest('asan not available on Cygwin') + if is_openbsd(): + raise SkipTest('-fsanitize=address is not supported on OpenBSD') + + testdir = os.path.join(self.common_test_dir, '1 trivial') + env = {'CFLAGS': '-fsanitize=address'} + self.init(testdir, extra_args=['-Dc_link_args="-L/usr/lib"'], + override_envvars=env) + self.build() + def test_compiler_check_flags_order(self): ''' Test that compiler check flags override all other flags. This can't be @@ -1379,7 +1397,7 @@ class LinuxlikeTests(BasePlatformTests): see: https://github.com/mesonbuild/meson/issues/9000 https://stackoverflow.com/questions/48532868/gcc-library-option-with-a-colon-llibevent-a ''' - testdir = os.path.join(self.unit_test_dir, '97 link full name','libtestprovider') + testdir = os.path.join(self.unit_test_dir, '98 link full name','libtestprovider') oldprefix = self.prefix # install into installdir without using DESTDIR installdir = self.installdir @@ -1392,7 +1410,7 @@ class LinuxlikeTests(BasePlatformTests): self.new_builddir() env = {'LIBRARY_PATH': os.path.join(installdir, self.libdir), 'PKG_CONFIG_PATH': _prepend_pkg_config_path(os.path.join(installdir, self.libdir, 'pkgconfig'))} - testdir = os.path.join(self.unit_test_dir, '97 link full name','proguser') + testdir = os.path.join(self.unit_test_dir, '98 link full name','proguser') self.init(testdir,override_envvars=env) # test for link with full path @@ -1798,7 +1816,7 @@ class LinuxlikeTests(BasePlatformTests): @skipUnless(is_linux() or is_osx(), 'Test only applicable to Linux and macOS') def test_install_strip(self): - testdir = os.path.join(self.unit_test_dir, '103 strip') + testdir = os.path.join(self.unit_test_dir, '104 strip') self.init(testdir) self.build() @@ -1845,7 +1863,7 @@ class LinuxlikeTests(BasePlatformTests): self.assertFalse(cpp.compiler_args([f'-isystem{symlink}' for symlink in default_symlinks]).to_native()) def test_freezing(self): - testdir = os.path.join(self.unit_test_dir, '110 freeze') + testdir = os.path.join(self.unit_test_dir, '111 freeze') self.init(testdir) self.build() with self.assertRaises(subprocess.CalledProcessError) as e: @@ -1854,7 +1872,7 @@ class LinuxlikeTests(BasePlatformTests): @skipUnless(is_linux(), "Ninja file differs on different platforms") def test_complex_link_cases(self): - testdir = os.path.join(self.unit_test_dir, '114 complex link cases') + testdir = os.path.join(self.unit_test_dir, '115 complex link cases') self.init(testdir) self.build() with open(os.path.join(self.builddir, 'build.ninja'), encoding='utf-8') as f: @@ -1875,11 +1893,11 @@ class LinuxlikeTests(BasePlatformTests): self.assertIn('build t13-e1: c_LINKER t13-e1.p/main.c.o | libt12-s1.a libt13-s3.a\n', content) def test_top_options_in_sp(self): - testdir = os.path.join(self.unit_test_dir, '125 pkgsubproj') + testdir = os.path.join(self.unit_test_dir, '127 pkgsubproj') self.init(testdir) def test_unreadable_dir_in_declare_dep(self): - testdir = os.path.join(self.unit_test_dir, '125 declare_dep var') + testdir = os.path.join(self.unit_test_dir, '126 declare_dep var') tmpdir = Path(tempfile.mkdtemp()) self.addCleanup(windows_proof_rmtree, tmpdir) declaredepdir = tmpdir / 'test' @@ -1901,7 +1919,7 @@ class LinuxlikeTests(BasePlatformTests): if self.backend is not Backend.ninja: raise SkipTest(f'{self.backend.name!r} backend can\'t install files') - testdir = os.path.join(self.unit_test_dir, '122 persp options') + testdir = os.path.join(self.unit_test_dir, '123 persp options') with self.subTest('init'): self.init(testdir, extra_args='-Doptimization=1') @@ -1954,7 +1972,7 @@ class LinuxlikeTests(BasePlatformTests): self.check_has_flag(compdb, sub2src, '-O2') def test_sanitizers(self): - testdir = os.path.join(self.unit_test_dir, '127 sanitizers') + testdir = os.path.join(self.unit_test_dir, '129 sanitizers') with self.subTest('no b_sanitize value'): try: diff --git a/unittests/machinefiletests.py b/unittests/machinefiletests.py index 7f88a54..e4687eb 100644 --- a/unittests/machinefiletests.py +++ b/unittests/machinefiletests.py @@ -388,7 +388,7 @@ class NativeFileTests(BasePlatformTests): def test_java_classpath(self): if self.backend is not Backend.ninja: raise SkipTest('Jar is only supported with Ninja') - testdir = os.path.join(self.unit_test_dir, '112 classpath') + testdir = os.path.join(self.unit_test_dir, '113 classpath') self.init(testdir) self.build() one_build_path = get_classpath(os.path.join(self.builddir, 'one.jar')) diff --git a/unittests/optiontests.py b/unittests/optiontests.py index 3e87b5c..30e5f03 100644 --- a/unittests/optiontests.py +++ b/unittests/optiontests.py @@ -121,6 +121,14 @@ class OptionTests(unittest.TestCase): self.assertEqual(optstore.get_value_for(name, 'sub'), sub_value) self.assertEqual(num_options(optstore), 2) + def test_toplevel_project_yielding(self): + optstore = OptionStore(False) + name = 'someoption' + top_value = 'top' + vo = UserStringOption(name, 'A top level option', top_value, True) + optstore.add_project_option(OptionKey(name, ''), vo) + self.assertEqual(optstore.get_value_for(name, ''), top_value) + def test_project_yielding(self): optstore = OptionStore(False) name = 'someoption' @@ -152,6 +160,30 @@ class OptionTests(unittest.TestCase): self.assertEqual(optstore.get_value_for(sub_name, 'sub'), sub_value) self.assertEqual(num_options(optstore), 2) + def test_project_yielding_initialize(self): + optstore = OptionStore(False) + name = 'someoption' + top_value = 'top' + sub_value = 'sub' + subp = 'subp' + cmd_line = { OptionKey(name): top_value, OptionKey(name, subp): sub_value } + + vo = UserStringOption(name, 'A top level option', 'default1') + optstore.add_project_option(OptionKey(name, ''), vo) + optstore.initialize_from_top_level_project_call({}, cmd_line, {}) + self.assertEqual(optstore.get_value_for(name, ''), top_value) + self.assertEqual(num_options(optstore), 1) + + vo2 = UserStringOption(name, 'A subproject option', 'default2', True) + optstore.add_project_option(OptionKey(name, 'subp'), vo2) + self.assertEqual(optstore.get_value_for(name, ''), top_value) + self.assertEqual(optstore.get_value_for(name, subp), top_value) + self.assertEqual(num_options(optstore), 2) + + optstore.initialize_from_subproject_call(subp, {}, {}, cmd_line, {}) + self.assertEqual(optstore.get_value_for(name, ''), top_value) + self.assertEqual(optstore.get_value_for(name, subp), sub_value) + def test_augments(self): optstore = OptionStore(False) name = 'cpp_std' @@ -171,25 +203,25 @@ class OptionTests(unittest.TestCase): # First augment a subproject with self.subTest('set subproject override'): - optstore.set_from_configure_command([f'{sub_name}:{name}={aug_value}'], []) + optstore.set_from_configure_command({OptionKey.from_string(f'{sub_name}:{name}'): aug_value}) self.assertEqual(optstore.get_value_for(name), top_value) self.assertEqual(optstore.get_value_for(name, sub_name), aug_value) self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) with self.subTest('unset subproject override'): - optstore.set_from_configure_command([], [f'{sub_name}:{name}']) + optstore.set_from_configure_command({OptionKey.from_string(f'{sub_name}:{name}'): None}) self.assertEqual(optstore.get_value_for(name), top_value) self.assertEqual(optstore.get_value_for(name, sub_name), top_value) self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) # And now augment the top level option - optstore.set_from_configure_command([f':{name}={aug_value}'], []) + optstore.set_from_configure_command({OptionKey.from_string(f':{name}'): aug_value}) self.assertEqual(optstore.get_value_for(name, None), top_value) self.assertEqual(optstore.get_value_for(name, ''), aug_value) self.assertEqual(optstore.get_value_for(name, sub_name), top_value) self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) - optstore.set_from_configure_command([], [f':{name}']) + optstore.set_from_configure_command({OptionKey.from_string(f':{name}'): None}) self.assertEqual(optstore.get_value_for(name), top_value) self.assertEqual(optstore.get_value_for(name, sub_name), top_value) self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) @@ -209,8 +241,8 @@ class OptionTests(unittest.TestCase): choices=['c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++23'], ) optstore.add_system_option(name, co) - optstore.set_from_configure_command([f'{sub_name}:{name}={aug_value}'], []) - optstore.set_from_configure_command([f'{sub_name}:{name}={set_value}'], []) + optstore.set_from_configure_command({OptionKey.from_string(f'{sub_name}:{name}'): aug_value}) + optstore.set_from_configure_command({OptionKey.from_string(f'{sub_name}:{name}'): set_value}) self.assertEqual(optstore.get_value_for(name), top_value) self.assertEqual(optstore.get_value_for(name, sub_name), set_value) @@ -227,18 +259,32 @@ class OptionTests(unittest.TestCase): def test_backend_option_pending(self): optstore = OptionStore(False) # backend options are known after the first invocation - self.assertTrue(optstore.accept_as_pending_option(OptionKey('backend_whatever'), set(), True)) - self.assertFalse(optstore.accept_as_pending_option(OptionKey('backend_whatever'), set(), False)) + self.assertTrue(optstore.accept_as_pending_option(OptionKey('backend_whatever'), True)) + self.assertFalse(optstore.accept_as_pending_option(OptionKey('backend_whatever'), False)) def test_reconfigure_b_nonexistent(self): optstore = OptionStore(False) - optstore.set_from_configure_command(['b_ndebug=true'], []) + optstore.set_from_configure_command({OptionKey('b_ndebug'): True}) + + def test_subproject_proj_opt_with_same_name(self): + name = 'tests' + subp = 'subp' - def test_subproject_nonexistent(self): optstore = OptionStore(False) - subprojects = {'found'} - self.assertFalse(optstore.accept_as_pending_option(OptionKey('foo', subproject='found'), subprojects)) - self.assertTrue(optstore.accept_as_pending_option(OptionKey('foo', subproject='whatisthis'), subprojects)) + prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr') + optstore.add_system_option('prefix', prefix) + o = UserBooleanOption(name, 'Tests', False) + optstore.add_project_option(OptionKey(name, subproject=''), o) + o = UserBooleanOption(name, 'Tests', True) + optstore.add_project_option(OptionKey(name, subproject=subp), o) + + cmd_line = {OptionKey(name): True} + spcall = {OptionKey(name): False} + + optstore.initialize_from_top_level_project_call({}, cmd_line, {}) + optstore.initialize_from_subproject_call(subp, spcall, {}, cmd_line, {}) + self.assertEqual(optstore.get_value_for(name, ''), True) + self.assertEqual(optstore.get_value_for(name, subp), False) def test_subproject_cmdline_override_global(self): name = 'optimization' @@ -260,6 +306,26 @@ class OptionTests(unittest.TestCase): self.assertEqual(optstore.get_value_for(name, subp), new_value) self.assertEqual(optstore.get_value_for(name), new_value) + def test_subproject_parent_override_subp(self): + name = 'optimization' + subp = 'subp' + default_value = 's' + subp_value = '0' + + optstore = OptionStore(False) + prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr') + optstore.add_system_option('prefix', prefix) + o = UserComboOption(name, 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's']) + optstore.add_system_option(name, o) + + toplevel_proj_default = {OptionKey(name, subproject=subp): subp_value, OptionKey(name): default_value} + subp_proj_default = {OptionKey(name): '3'} + + optstore.initialize_from_top_level_project_call(toplevel_proj_default, {}, {}) + optstore.initialize_from_subproject_call(subp, {}, subp_proj_default, {}, {}) + self.assertEqual(optstore.get_value_for(name, subp), subp_value) + self.assertEqual(optstore.get_value_for(name), default_value) + def test_subproject_cmdline_override_global_and_augment(self): name = 'optimization' subp = 'subp' @@ -300,7 +366,33 @@ class OptionTests(unittest.TestCase): optstore.initialize_from_top_level_project_call(toplevel_proj_default, cmd_line, {}) optstore.initialize_from_subproject_call(subp, {}, subp_proj_default, cmd_line, {}) self.assertEqual(optstore.get_value_for(name, subp), subp_value) - self.assertEqual(optstore.get_value_for(name), toplevel_value) + self.assertEqual(optstore.get_value_for(name, ''), toplevel_value) + + def test_subproject_buildtype(self): + subp = 'subp' + main1 = {OptionKey('buildtype'): 'release'} + main2 = {OptionKey('optimization'): '3', OptionKey('debug'): 'false'} + sub1 = {OptionKey('buildtype'): 'debug'} + sub2 = {OptionKey('optimization'): '0', OptionKey('debug'): 'true'} + + for mainopt, subopt in ((main1, sub1), + (main2, sub2), + ({**main1, **main2}, {**sub1, **sub2})): + optstore = OptionStore(False) + prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr') + optstore.add_system_option('prefix', prefix) + o = UserComboOption('buildtype', 'Build type to use', 'debug', choices=['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom']) + optstore.add_system_option(o.name, o) + o = UserComboOption('optimization', 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's']) + optstore.add_system_option(o.name, o) + o = UserBooleanOption('debug', 'Enable debug symbols and other information', True) + optstore.add_system_option(o.name, o) + + optstore.initialize_from_top_level_project_call(mainopt, {}, {}) + optstore.initialize_from_subproject_call(subp, {}, subopt, {}, {}) + self.assertEqual(optstore.get_value_for('buildtype', subp), 'debug') + self.assertEqual(optstore.get_value_for('optimization', subp), '0') + self.assertEqual(optstore.get_value_for('debug', subp), True) def test_deprecated_nonstring_value(self): # TODO: add a lot more deprecated option tests diff --git a/unittests/platformagnostictests.py b/unittests/platformagnostictests.py index 9c5e2bd..b0e4350 100644 --- a/unittests/platformagnostictests.py +++ b/unittests/platformagnostictests.py @@ -33,7 +33,7 @@ class PlatformAgnosticTests(BasePlatformTests): Tests that find_program() with a relative path does not find the program in current workdir. ''' - testdir = os.path.join(self.unit_test_dir, '100 relative find program') + testdir = os.path.join(self.unit_test_dir, '101 relative find program') self.init(testdir, workdir=testdir) def test_invalid_option_names(self): @@ -92,11 +92,11 @@ class PlatformAgnosticTests(BasePlatformTests): interp.process, fname) def test_python_dependency_without_pkgconfig(self): - testdir = os.path.join(self.unit_test_dir, '102 python without pkgconfig') + testdir = os.path.join(self.unit_test_dir, '103 python without pkgconfig') self.init(testdir, override_envvars={'PKG_CONFIG': 'notfound'}) def test_vala_target_with_internal_glib(self): - testdir = os.path.join(self.unit_test_dir, '129 vala internal glib') + testdir = os.path.join(self.unit_test_dir, '131 vala internal glib') for run in [{ 'version': '2.84.4', 'expected': '2.84'}, { 'version': '2.85.2', 'expected': '2.84' }]: self.new_builddir() self.init(testdir, extra_args=[f'-Dglib-version={run["version"]}']) @@ -113,7 +113,7 @@ class PlatformAgnosticTests(BasePlatformTests): self.skipTest('Current backend does not produce introspection data') def test_debug_function_outputs_to_meson_log(self): - testdir = os.path.join(self.unit_test_dir, '104 debug function') + testdir = os.path.join(self.unit_test_dir, '105 debug function') log_msg = 'This is an example debug output, should only end up in debug log' output = self.init(testdir) @@ -125,7 +125,7 @@ class PlatformAgnosticTests(BasePlatformTests): self.assertIn(log_msg, mesonlog) def test_new_subproject_reconfigure(self): - testdir = os.path.join(self.unit_test_dir, '108 new subproject on reconfigure') + testdir = os.path.join(self.unit_test_dir, '109 new subproject on reconfigure') self.init(testdir) self.build() @@ -288,7 +288,7 @@ class PlatformAgnosticTests(BasePlatformTests): thing to do as new features are added, but keeping track of them is good. ''' - testdir = os.path.join(self.unit_test_dir, '116 empty project') + testdir = os.path.join(self.unit_test_dir, '117 empty project') self.init(testdir) self._run(self.meson_command + ['--internal', 'regenerate', '--profile-self', testdir, self.builddir]) @@ -303,7 +303,7 @@ class PlatformAgnosticTests(BasePlatformTests): def test_meson_package_cache_dir(self): # Copy testdir into temporary directory to not pollute meson source tree. - testdir = os.path.join(self.unit_test_dir, '118 meson package cache dir') + testdir = os.path.join(self.unit_test_dir, '119 meson package cache dir') srcdir = os.path.join(self.builddir, 'srctree') shutil.copytree(testdir, srcdir) builddir = os.path.join(srcdir, '_build') @@ -312,7 +312,7 @@ class PlatformAgnosticTests(BasePlatformTests): def test_cmake_openssl_not_found_bug(self): """Issue #12098""" - testdir = os.path.join(self.unit_test_dir, '119 openssl cmake bug') + testdir = os.path.join(self.unit_test_dir, '120 openssl cmake bug') self.meson_native_files.append(os.path.join(testdir, 'nativefile.ini')) out = self.init(testdir, allow_fail=True) self.assertNotIn('Unhandled python exception', out) @@ -422,7 +422,7 @@ class PlatformAgnosticTests(BasePlatformTests): self.assertIn(f'Did you mean to run meson from the directory: "{testdir}"?', out) def test_reconfigure_base_options(self): - testdir = os.path.join(self.unit_test_dir, '123 reconfigure base options') + testdir = os.path.join(self.unit_test_dir, '124 reconfigure base options') out = self.init(testdir, extra_args=['-Db_ndebug=true']) self.assertIn('\nMessage: b_ndebug: true\n', out) self.assertIn('\nMessage: c_std: c89\n', out) @@ -548,7 +548,7 @@ class PlatformAgnosticTests(BasePlatformTests): self.assertEqual(self.getconf('subproject:new_option'), True) def test_mtest_rebuild_deps(self): - testdir = os.path.join(self.unit_test_dir, '106 underspecified mtest') + testdir = os.path.join(self.unit_test_dir, '107 underspecified mtest') self.init(testdir) with self.assertRaises(subprocess.CalledProcessError): diff --git a/unittests/rewritetests.py b/unittests/rewritetests.py index 767c291..11cac19 100644 --- a/unittests/rewritetests.py +++ b/unittests/rewritetests.py @@ -429,7 +429,7 @@ class RewriterTests(BasePlatformTests): self.assertEqualIgnoreOrder(out, expected) def test_tricky_dataflow(self): - self.prime('7 tricky dataflow') + self.prime('8 tricky dataflow') out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) expected = { 'target': { @@ -449,7 +449,7 @@ class RewriterTests(BasePlatformTests): self.assertEqualIgnoreOrder(out, expected) def test_raw_printer_is_idempotent(self): - test_path = Path(self.unit_test_dir, '120 rewrite') + test_path = Path(self.unit_test_dir, '121 rewrite') meson_build_file = test_path / 'meson.build' # original_contents = meson_build_file.read_bytes() original_contents = meson_build_file.read_text(encoding='utf-8') diff --git a/unittests/windowstests.py b/unittests/windowstests.py index 7fa4ab2..9506a75 100644 --- a/unittests/windowstests.py +++ b/unittests/windowstests.py @@ -185,7 +185,7 @@ class WindowsTests(BasePlatformTests): if self.backend is not Backend.ninja: raise SkipTest('Test only applies when using the Ninja backend') - testdir = os.path.join(self.unit_test_dir, '117 genvslite') + testdir = os.path.join(self.unit_test_dir, '118 genvslite') env = get_fake_env(testdir, self.builddir, self.prefix) cc = detect_c_compiler(env, MachineChoice.HOST) |