rabbitmq-server/deps/rabbitmq_cli/rabbitmqctl.bzl

402 lines
12 KiB
Python

load("@bazel_skylib//lib:shell.bzl", "shell")
load(
"@rules_erlang//:erlang_app_info.bzl",
"ErlangAppInfo",
"flat_deps",
)
load(
"@rules_erlang//:util.bzl",
"path_join",
)
load(
"@rules_erlang//private:util.bzl",
"additional_file_dest_relative_path",
)
load(
"//bazel/elixir:elixir_toolchain.bzl",
"elixir_dirs",
"erlang_dirs",
"maybe_install_erlang",
)
ElixirAppInfo = provider(
doc = "Compiled Elixir Application",
fields = {
"app_name": "Name of the erlang application",
"extra_apps": "Extra applications in the applications key of the .app file",
"include": "Public header files",
"beam": "ebin directory produced by mix",
"consolidated": "consolidated directory produced by mix",
"priv": "Additional files",
"license_files": "License files",
"srcs": "Source files",
"deps": "Runtime dependencies of the compiled sources",
},
)
def deps_dir_contents(ctx, deps, dir):
files = []
for dep in deps:
lib_info = dep[ErlangAppInfo]
for src in lib_info.include + lib_info.beam + lib_info.srcs:
if not src.is_directory:
rp = additional_file_dest_relative_path(dep.label, src)
f = ctx.actions.declare_file(path_join(
dir,
lib_info.app_name,
rp,
))
ctx.actions.symlink(
output = f,
target_file = src,
)
files.extend([src, f])
return files
def _impl(ctx):
(erlang_home, _, erlang_runfiles) = erlang_dirs(ctx)
(elixir_home, elixir_runfiles) = elixir_dirs(ctx)
escript = ctx.actions.declare_file(path_join("escript", "rabbitmqctl"))
ebin = ctx.actions.declare_directory("ebin")
consolidated = ctx.actions.declare_directory("consolidated")
mix_invocation_dir = ctx.actions.declare_directory("{}_mix".format(ctx.label.name))
deps = flat_deps(ctx.attr.deps)
deps_dir = ctx.label.name + "_deps"
deps_dir_files = deps_dir_contents(ctx, deps, deps_dir)
for dep, app_name in ctx.attr.source_deps.items():
for src in dep.files.to_list():
if not src.is_directory:
rp = additional_file_dest_relative_path(dep.label, src)
f = ctx.actions.declare_file(path_join(
deps_dir,
app_name,
rp,
))
ctx.actions.symlink(
output = f,
target_file = src,
)
deps_dir_files.append(f)
package_dir = path_join(
ctx.label.workspace_root,
ctx.label.package,
)
script = """set -euo pipefail
{maybe_install_erlang}
if [[ "{elixir_home}" == /* ]]; then
ABS_ELIXIR_HOME="{elixir_home}"
else
ABS_ELIXIR_HOME=$PWD/{elixir_home}
fi
ABS_EBIN_DIR=$PWD/{ebin_dir}
ABS_CONSOLIDATED_DIR=$PWD/{consolidated_dir}
ABS_ESCRIPT_PATH=$PWD/{escript_path}
export PATH="$ABS_ELIXIR_HOME"/bin:"{erlang_home}"/bin:${{PATH}}
export LANG="en_US.UTF-8"
export LC_ALL="en_US.UTF-8"
MIX_INVOCATION_DIR="{mix_invocation_dir}"
cp -r {package_dir}/config ${{MIX_INVOCATION_DIR}}/config
cp -r {package_dir}/lib ${{MIX_INVOCATION_DIR}}/lib
cp {package_dir}/mix.exs ${{MIX_INVOCATION_DIR}}/mix.exs
ORIGINAL_DIR=$PWD
cd ${{MIX_INVOCATION_DIR}}
export IS_BAZEL=true
export HOME=${{PWD}}
export DEPS_DIR=$(dirname $ABS_EBIN_DIR)/{deps_dir}
export MIX_ENV=prod
export ERL_COMPILER_OPTIONS=deterministic
for archive in {archives}; do
"${{ABS_ELIXIR_HOME}}"/bin/mix archive.install --force $ORIGINAL_DIR/$archive
done
for d in {precompiled_deps}; do
mkdir -p _build/${{MIX_ENV}}/lib/$d
ln -s ${{DEPS_DIR}}/$d/ebin _build/${{MIX_ENV}}/lib/$d
ln -s ${{DEPS_DIR}}/$d/include _build/${{MIX_ENV}}/lib/$d
done
"${{ABS_ELIXIR_HOME}}"/bin/mix deps.compile
"${{ABS_ELIXIR_HOME}}"/bin/mix compile
"${{ABS_ELIXIR_HOME}}"/bin/mix escript.build
cp escript/rabbitmqctl ${{ABS_ESCRIPT_PATH}}
cp _build/${{MIX_ENV}}/lib/rabbitmqctl/ebin/* ${{ABS_EBIN_DIR}}
cp _build/${{MIX_ENV}}/lib/rabbitmqctl/consolidated/* ${{ABS_CONSOLIDATED_DIR}}
# remove symlinks from the _build directory since it
# is not used, and bazel does not allow them
find . -type l -delete
""".format(
maybe_install_erlang = maybe_install_erlang(ctx),
erlang_home = erlang_home,
elixir_home = elixir_home,
mix_invocation_dir = mix_invocation_dir.path,
package_dir = package_dir,
deps_dir = deps_dir,
escript_path = escript.path,
ebin_dir = ebin.path,
consolidated_dir = consolidated.path,
archives = " ".join([shell.quote(a.path) for a in ctx.files.archives]),
precompiled_deps = " ".join([
dep[ErlangAppInfo].app_name
for dep in ctx.attr.deps
]),
)
inputs = depset(
direct = ctx.files.srcs,
transitive = [
erlang_runfiles.files,
elixir_runfiles.files,
depset(ctx.files.archives),
depset(deps_dir_files),
],
)
ctx.actions.run_shell(
inputs = inputs,
outputs = [
escript,
ebin,
consolidated,
mix_invocation_dir,
],
command = script,
mnemonic = "MIX",
)
runfiles = ctx.runfiles([ebin, consolidated]).merge_all([
erlang_runfiles,
elixir_runfiles,
] + [
dep[DefaultInfo].default_runfiles
for dep in deps
])
return [
DefaultInfo(
executable = escript,
files = depset([ebin, consolidated]),
runfiles = runfiles,
),
ElixirAppInfo(
app_name = "rabbitmqctl", # mix generates 'rabbitmqctl.app'
extra_apps = ["elixir", "logger"],
include = [],
beam = ebin,
consolidated = consolidated,
priv = [],
license_files = ctx.files.license_files,
srcs = ctx.files.srcs,
deps = deps,
),
]
rabbitmqctl_private = rule(
implementation = _impl,
attrs = {
"is_windows": attr.bool(
mandatory = True,
),
"srcs": attr.label_list(
mandatory = True,
allow_files = True,
),
"license_files": attr.label_list(
allow_files = True,
),
"deps": attr.label_list(
providers = [ErlangAppInfo],
),
"archives": attr.label_list(
allow_files = [".ez"],
),
"source_deps": attr.label_keyed_string_dict(),
},
toolchains = [
"//bazel/elixir:toolchain_type",
],
provides = [ElixirAppInfo],
executable = True,
)
def _elixir_app_to_erlang_app(ctx):
app_consolidated = ctx.attr.elixir_app[ElixirAppInfo].consolidated
app_ebin = ctx.attr.elixir_app[ElixirAppInfo].beam
elixir_ebin = ctx.attr.elixir_as_app[ErlangAppInfo].beam[0].path
ebin = ctx.actions.declare_directory(path_join(ctx.label.name, "ebin"))
if ctx.attr.mode == "elixir":
if len(ctx.attr.deps) > 0:
fail("deps cannot be specified in the 'elixir' mode")
ctx.actions.run_shell(
inputs = ctx.files.elixir_as_app + ctx.files.elixir_app,
outputs = [ebin],
command = """\
set -euo pipefail
cp "{elixir_ebin}"/* "{ebin}"
for beam in "{app_consolidated}"/*; do
find "{ebin}" -name "$(basename $beam)" -exec cp -f "$beam" "{ebin}" \\;
done
""".format(
elixir_ebin = elixir_ebin,
app_consolidated = app_consolidated.path,
ebin = ebin.path,
),
)
lib_info = ctx.attr.elixir_as_app[ErlangAppInfo]
return [
DefaultInfo(files = depset([ebin])),
ErlangAppInfo(
app_name = "elixir",
include = lib_info.include,
beam = [ebin],
priv = lib_info.priv,
license_files = lib_info.license_files,
srcs = lib_info.srcs,
deps = lib_info.deps,
),
]
elif ctx.attr.mode == "app":
ctx.actions.run_shell(
inputs = ctx.files.elixir_as_app + ctx.files.elixir_app,
outputs = [ebin],
command = """\
set -euo pipefail
cp "{app_ebin}"/* "{ebin}"
cp -f "{app_consolidated}"/* "{ebin}"
for beam in "{elixir_ebin}"/*; do
find "{ebin}" -name "$(basename $beam)" -delete
done
""".format(
elixir_ebin = elixir_ebin,
app_ebin = app_ebin.path,
app_consolidated = app_consolidated.path,
ebin = ebin.path,
),
)
(_, _, erlang_runfiles) = erlang_dirs(ctx)
(_, elixir_runfiles) = elixir_dirs(ctx)
lib_info = ctx.attr.elixir_app[ElixirAppInfo]
deps = lib_info.deps + ctx.attr.deps
runfiles = ctx.runfiles([ebin]).merge_all([
erlang_runfiles,
elixir_runfiles,
] + [
dep[DefaultInfo].default_runfiles
for dep in deps
])
return [
DefaultInfo(
files = depset([ebin]),
runfiles = runfiles,
),
ErlangAppInfo(
app_name = lib_info.app_name,
extra_apps = lib_info.extra_apps,
include = lib_info.include,
beam = [ebin],
priv = lib_info.priv,
license_files = lib_info.license_files,
srcs = lib_info.srcs,
deps = deps,
),
]
return []
elixir_app_to_erlang_app = rule(
implementation = _elixir_app_to_erlang_app,
attrs = {
"elixir_as_app": attr.label(
providers = [ErlangAppInfo],
),
"elixir_app": attr.label(
providers = [ElixirAppInfo],
),
"mode": attr.string(
values = [
"elixir",
"app",
],
),
"deps": attr.label_list(
providers = [ErlangAppInfo],
),
},
toolchains = [
"//bazel/elixir:toolchain_type",
],
provides = [ErlangAppInfo],
)
def rabbitmqctl(
name = None,
visibility = None,
**kwargs):
# mix produces a consolidated directory alongside the ebin
# directory, which contains .beam files for modules that
# are extended by protocols
# When used with dialyzer, this results in module conflicts
# between the original versions in elixir, and the
# consolidated ones
# So, this macro compiles the cli, then derives a copy of
# elixir that can be loaded alongside it without conflict
# (but assumes that the two are used together)
# These each have to be separate rules, as a single rule
# cannot provide multiple erlang_app (ErlangAppInfo
# provider instances)
rabbitmqctl_private(
name = name,
is_windows = select({
"@bazel_tools//src/conditions:host_windows": True,
"//conditions:default": False,
}),
visibility = visibility,
**kwargs
)
elixir_app_to_erlang_app(
name = "elixir",
elixir_as_app = Label("//bazel/elixir:erlang_app"),
elixir_app = ":" + name,
mode = "elixir",
visibility = visibility,
)
elixir_app_to_erlang_app(
name = "erlang_app",
elixir_as_app = Label("//bazel/elixir:erlang_app"),
elixir_app = ":" + name,
mode = "app",
visibility = visibility,
deps = [":elixir"],
)