402 lines
12 KiB
Python
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"],
|
|
)
|