diff --git a/examples/meson_simple/BUILD.bazel b/examples/meson_simple/BUILD.bazel new file mode 100644 index 000000000..f274c4bef --- /dev/null +++ b/examples/meson_simple/BUILD.bazel @@ -0,0 +1,46 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") +load("@rules_foreign_cc//foreign_cc:defs.bzl", "meson") +load("@rules_shell//shell:sh_test.bzl", "sh_test") + +meson( + name = "meson_lib", + build_data = ["//meson_simple/code:clang_wrapper.sh"], + env = select({ + "//:windows": { + "CLANG_WRAPPER": "$(execpath //meson_simple/code:clang_wrapper.sh)", + "CXX_FLAGS": "/MD", + }, + "//conditions:default": { + "CLANG_WRAPPER": "$(execpath //meson_simple/code:clang_wrapper.sh)", + "CXX_FLAGS": "-fPIC", + }, + }), + lib_source = "//meson_simple/code:srcs", + out_static_libs = ["liba.a"], +) + +cc_test( + name = "test_lib", + srcs = [ + "test_libb.cpp", + ], + deps = [ + ":meson_lib", + ], +) + +meson( + name = "meson_lib-introspect", + lib_source = "//meson_simple/code:srcs", + out_data_files = ["introspect.json"], + out_include_dir = "", + targets = ["introspect"], +) + +sh_test( + name = "test_introspect", + srcs = ["test_introspect.sh"], + args = ["$(location meson_lib-introspect)"], + data = [":meson_lib-introspect"], + deps = ["@bazel_tools//tools/bash/runfiles"], +) diff --git a/examples/meson_simple/code/BUILD.bazel b/examples/meson_simple/code/BUILD.bazel new file mode 100644 index 000000000..64d518298 --- /dev/null +++ b/examples/meson_simple/code/BUILD.bazel @@ -0,0 +1,11 @@ +exports_files(["clang_wrapper.sh"]) + +filegroup( + name = "srcs", + srcs = [ + "liba.cpp", + "liba.h", + "meson.build", + ], + visibility = ["//meson_simple:__subpackages__"], +) diff --git a/examples/meson_simple/code/clang_wrapper.sh b/examples/meson_simple/code/clang_wrapper.sh new file mode 100755 index 000000000..9dfc4b214 --- /dev/null +++ b/examples/meson_simple/code/clang_wrapper.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset + +if [[ $(uname) == *"NT"* ]]; then + # If Windows + exec clang-cl "$@" +else + exec clang "$@" +fi diff --git a/examples/meson_simple/code/liba.cpp b/examples/meson_simple/code/liba.cpp new file mode 100644 index 000000000..061f48810 --- /dev/null +++ b/examples/meson_simple/code/liba.cpp @@ -0,0 +1,3 @@ +#include "liba.h" + +std::string hello_liba(void) { return "Hello from LIBA!"; } diff --git a/examples/meson_simple/code/liba.h b/examples/meson_simple/code/liba.h new file mode 100644 index 000000000..58c231398 --- /dev/null +++ b/examples/meson_simple/code/liba.h @@ -0,0 +1,10 @@ +#ifndef LIBA_H_ +#define LIBA_H_ (1) + +#include + +#include + +std::string hello_liba(void); + +#endif diff --git a/examples/meson_simple/code/meson.build b/examples/meson_simple/code/meson.build new file mode 100644 index 000000000..92f5be28a --- /dev/null +++ b/examples/meson_simple/code/meson.build @@ -0,0 +1,16 @@ +project('liba', 'cpp') + +builddir = 'rules_foreign_cc_build' + +cxx = meson.get_compiler('cpp') + +liba = static_library( + 'a', + 'liba.cpp', + cpp_args: cxx.get_supported_arguments(), + include_directories: include_directories('.'), + install: true, + install_dir: join_paths(get_option('prefix'), 'lib'), +) + +install_headers('liba.h', subdir: 'include') diff --git a/examples/meson_simple/test_introspect.sh b/examples/meson_simple/test_introspect.sh new file mode 100755 index 000000000..20841a63b --- /dev/null +++ b/examples/meson_simple/test_introspect.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization --- +# The runfiles library itself defines rlocation which you would need to look +# up the library's runtime location, thus we have a chicken-and-egg problem. +# +# Copy-pasted from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash). +set -euo pipefail +if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + if [[ -f "$0.runfiles_manifest" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest" + elif [[ -f "$0.runfiles/MANIFEST" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST" + elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + export RUNFILES_DIR="$0.runfiles" + fi +fi +if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash" +elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \ + "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)" +else + echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash" + exit 1 +fi +# --- end runfiles.bash initialization --- + +[[ -f $(rlocation $TEST_WORKSPACE/$1) ]] && exit 0 +exit 1 diff --git a/examples/meson_simple/test_libb.cpp b/examples/meson_simple/test_libb.cpp new file mode 100644 index 000000000..5998fdc38 --- /dev/null +++ b/examples/meson_simple/test_libb.cpp @@ -0,0 +1,13 @@ +#include +#include +#include + +#include "include/liba.h" + +int main(int argc, char* argv[]) { + std::string result = hello_liba(); + if (result != "Hello from LIBA!") { + throw std::runtime_error("Wrong result: " + result); + } + std::cout << "Everything's fine!"; +} diff --git a/foreign_cc/meson.bzl b/foreign_cc/meson.bzl index cf2b5e982..3c25158c6 100644 --- a/foreign_cc/meson.bzl +++ b/foreign_cc/meson.bzl @@ -99,46 +99,98 @@ def _create_meson_script(configureParameters): root = detect_root(ctx.attr.lib_source) data = ctx.attr.data + ctx.attr.build_data - # Generate a list of arguments for meson - options_str = " ".join([ - "-D{}=\"{}\"".format(key, ctx.attr.options[key]) - for key in ctx.attr.options - ]) + if attrs.tool_prefix: + tool_prefix = "{} ".format( + expand_locations_and_make_variables(ctx, attrs.tool_prefix, "tool_prefix", data), + ) + else: + tool_prefix = "" + + meson_path = "{tool_prefix}{meson_path}".format( + tool_prefix = tool_prefix, + meson_path = attrs.meson_path, + ) - prefix = "{} ".format(expand_locations_and_make_variables(ctx, attrs.tool_prefix, "tool_prefix", data)) if attrs.tool_prefix else "" + target_args = dict(ctx.attr.target_args) - setup_args_str = " ".join(expand_locations_and_make_variables(ctx, ctx.attr.setup_args, "setup_args", data)) + # --- TODO: DEPRECATED, delete on a future release ------------------------ + # Convert the deprecated arguments into the new target_args argument. Fail + # if there's a deprecated argument being used together with its new + # target_args (e.g. setup_args and a "setup" target_args). - script.append("{prefix}{meson} setup --prefix={install_dir} {setup_args} {options} {source_dir}".format( - prefix = prefix, - meson = attrs.meson_path, - install_dir = "$$INSTALLDIR$$", - setup_args = setup_args_str, - options = options_str, - source_dir = "$$EXT_BUILD_ROOT$$/" + root, - )) + deprecated = [ + ("setup", ctx.attr.setup_args), + ("compile", ctx.attr.build_args), + ("install", ctx.attr.install_args), + ] + + for target_name, args_ in deprecated: + if args_ and target_name in target_args: + fail("Please migrate '{t}_args' to 'target_args[\"{t}\"]'".format(t = target_name)) + + target_args[target_name] = args_ + + # --- TODO: DEPRECATED, delete on a future release ------------------------ - build_args = [] + ctx.attr.build_args - build_args_str = " ".join([ - ctx.expand_location(arg, data) - for arg in build_args - ]) + # Expand target args + for target_name, args_ in target_args.items(): + if target_name == "setup": + args = expand_locations_and_make_variables(ctx, args_, "setup_args", data) + else: + args = [ctx.expand_location(arg, data) for arg in args_] - script.append("{prefix}{meson} compile {args}".format( - prefix = prefix, - meson = attrs.meson_path, - args = build_args_str, + target_args[target_name] = args + + script.append("{meson} setup --prefix={install_dir} {setup_args} {options} {source_dir}".format( + meson = meson_path, + install_dir = "$$INSTALLDIR$$", + setup_args = " ".join(target_args.get("setup", [])), + options = " ".join([ + "-D{}=\"{}\"".format(key, ctx.attr.options[key]) + for key in ctx.attr.options + ]), + source_dir = "$$EXT_BUILD_ROOT$$/" + root, )) - if ctx.attr.install: - install_args = " ".join([ - ctx.expand_location(arg, data) - for arg in ctx.attr.install_args - ]) - script.append("{prefix}{meson} install {args}".format( - prefix = prefix, - meson = attrs.meson_path, - args = install_args, + targets = ctx.attr.targets + + # --- TODO: DEPRECATED, delete on a future release ------------------------ + targets = [ + t + for t in targets + if t != "install" or ctx.attr.install + ] + # --- TODO: DEPRECATED, delete on a future release ------------------------ + + # NOTE: + # introspect has an "old API" and doesn't work like other commands. + # It requires a builddir argument and it doesn't have a flag to output to a + # file, so it requires a redirect. And, most probably, it will remain like + # this for the foreseable future (see + # https://github.com/mesonbuild/meson/issues/8182#issuecomment-758183324). + # + # To keep things simple, we provide a basic API: users must supply the + # introspection JSON file in `out_data_files`, and we offer a sensible + # default for the introspect command that users can override if needed. + if "introspect" in targets: + if len(ctx.attr.out_data_files) != 1: + msg = "Meson introspect expects a single JSON filename via " + msg += "out_data_files; only one filename should be provided." + fail(msg) + + introspect_file = ctx.attr.out_data_files[0] + + introspect_args = ["$$BUILD_TMPDIR$$"] + introspect_args += target_args.get("introspect", ["--all", "--indent"]) + introspect_args += [">", "$$INSTALLDIR$$/{}".format(introspect_file)] + + target_args["introspect"] = introspect_args + + for target_name in targets: + script.append("{meson} {target} {args}".format( + meson = meson_path, + target = target_name, + args = " ".join(target_args.get(target_name, [])), )) return script @@ -153,26 +205,34 @@ def _attrs(): attrs.update({ "build_args": attr.string_list( - doc = "Arguments for the Meson build command", + doc = "__deprecated__: please use `target_args` with `'build'` target key.", mandatory = False, ), "install": attr.bool( - doc = "If True, the `meson install` comand will be performed after a build", + doc = "__deprecated__: please use `targets` if you want to skip install.", default = True, ), "install_args": attr.string_list( - doc = "Arguments for the meson install command", + doc = "__deprecated__: please use `target_args` with `'install'` target key.", mandatory = False, ), "options": attr.string_dict( - doc = ( - "Meson option entries to initialize (they will be passed with `-Dkey=value`)" - ), + doc = "Meson `setup` options (converted to `-Dkey=value`)", mandatory = False, default = {}, ), "setup_args": attr.string_list( - doc = "Arguments for the Meson setup command", + doc = "__deprecated__: please use `target_args` with `'setup'` target key.", + mandatory = False, + ), + "target_args": attr.string_list_dict( + doc = "Dict of arguments for each of the Meson targets. The " + + "target name is the key and the list of args is the value.", + mandatory = False, + ), + "targets": attr.string_list( + doc = "A list of targets to run. Defaults to ['compile', 'install']", + default = ["compile", "install"], mandatory = False, ), }) diff --git a/foreign_cc/private/framework.bzl b/foreign_cc/private/framework.bzl index 556de5d38..441093d21 100644 --- a/foreign_cc/private/framework.bzl +++ b/foreign_cc/private/framework.bzl @@ -162,6 +162,10 @@ CC_EXTERNAL_RULE_ATTRIBUTES = { doc = "Optional names of additional directories created by the build that should be declared as bazel action outputs", mandatory = False, ), + "out_data_files": attr.string_list( + doc = "Optional names of additional files created by the build that should be declared as bazel action outputs", + mandatory = False, + ), "out_dll_dir": attr.string( doc = "Optional name of the output subdirectory with the dll files, defaults to 'bin'.", mandatory = False, @@ -579,7 +583,13 @@ def cc_external_rule_impl(ctx, attrs): lib_dir_name = attrs.out_lib_dir, include_dir_name = attrs.out_include_dir, ) - output_groups = _declare_output_groups(installdir_copy.file, outputs.out_binary_files + outputs.libraries.static_libraries + outputs.libraries.shared_libraries + [outputs.out_include_dir]) + output_groups = ( + outputs.out_binary_files + + outputs.libraries.static_libraries + + outputs.libraries.shared_libraries + + [outputs.out_include_dir] if outputs.out_include_dir else [] + ) + output_groups = _declare_output_groups(installdir_copy.file, output_groups) wrapped_files = [ wrapped_outputs.script_file, wrapped_outputs.log_file, @@ -828,24 +838,36 @@ def _define_outputs(ctx, attrs, lib_name): attr_headers_only = attrs.out_headers_only attr_interface_libs = attrs.out_interface_libs attr_out_data_dirs = attrs.out_data_dirs + attr_out_data_files = attrs.out_data_files attr_shared_libs = attrs.out_shared_libs attr_static_libs = attrs.out_static_libs static_libraries = [] if not attr_headers_only: - if not attr_static_libs and not attr_shared_libs and not attr_binaries_libs and not attr_interface_libs: + if not ( + attr_static_libs or + attr_shared_libs or + attr_binaries_libs or + attr_interface_libs or + attr_out_data_files + ): static_libraries = [lib_name + (".lib" if targets_windows(ctx, None) else ".a")] else: static_libraries = attr_static_libs _check_file_name(lib_name) - out_include_dir = ctx.actions.declare_directory(lib_name + "/" + attrs.out_include_dir) + if attrs.out_include_dir: + out_include_dir = ctx.actions.declare_directory(lib_name + "/" + attrs.out_include_dir) + else: + out_include_dir = "" out_data_dirs = [] for dir in attr_out_data_dirs: out_data_dirs.append(ctx.actions.declare_directory(lib_name + "/" + dir.lstrip("/"))) + out_data_files = _declare_out(ctx, lib_name, "/", attr_out_data_files) + out_binary_files = _declare_out(ctx, lib_name, attrs.out_bin_dir, attr_binaries_libs) libraries = LibrariesToLinkInfo( @@ -854,7 +876,8 @@ def _define_outputs(ctx, attrs, lib_name): interface_libraries = _declare_out(ctx, lib_name, attrs.out_lib_dir, attr_interface_libs), ) - declared_outputs = [out_include_dir] + out_data_dirs + out_binary_files + declared_outputs = [out_include_dir] if out_include_dir else [] + declared_outputs += out_data_dirs + out_binary_files + out_data_files declared_outputs += libraries.static_libraries declared_outputs += libraries.shared_libraries + libraries.interface_libraries @@ -1017,11 +1040,11 @@ def _get_headers(compilation_info): def _define_out_cc_info(ctx, attrs, inputs, outputs): compilation_info = cc_common.create_compilation_context( - headers = depset([outputs.out_include_dir]), + headers = depset([outputs.out_include_dir]) if outputs.out_include_dir else depset([]), system_includes = depset([outputs.out_include_dir.path] + [ outputs.out_include_dir.path + "/" + include for include in attrs.includes - ]), + ]) if outputs.out_include_dir else depset([]), includes = depset([]), quote_includes = depset([]), defines = depset(attrs.defines),