Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ jobs:
run: |
grep -E "Result: 2 passed|2 tests, 0 failures" \
bazel-testlogs/two_file_test/test.log
- name: INCOMPATIBLE APP IS SKIPPED
working-directory: test
# //... above skipped it. Asking for it directly must fail.
run: |
if bazelisk build //:incompatible_app; then
echo "//:incompatible_app built despite target_compatible_with"
exit 1
fi
- name: EXPECTED FAILURES FAIL
working-directory: test
run: |
Expand Down
63 changes: 48 additions & 15 deletions elixir_app.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,44 @@ load("//private:elixir_bytecode.bzl", "elixir_bytecode")
load("//private:elixir_ebin_dir.bzl", "elixir_ebin_dir")
load("//private:erlang_app_filter_module_conflicts.bzl", "erlang_app_filter_module_conflicts")

def _prefixed(name, suffix, sep):
# The default name keeps the internal names and output dirs that
# elixir_app has always used, so existing users see no change.
if name == "erlang_app":
return suffix
return name + sep + suffix

def elixir_app(
name = "erlang_app",
app_name = None,
extra_apps = [],
srcs = None,
data = [],
elixirc_opts = [],
ez_deps = [],
deps = [],
target_compatible_with = None,
testonly = False,
tags = None,
**kwargs):
"""compiles elixir sources in a manner compatible with @rules_erlang

Args:
name: Name of the final ErlangAppInfo target. With the default
"erlang_app", the internal targets are named beam_files,
app_file, ebin and elixir_without_app_overlap. With any other
name they are prefixed with "<name>_", and their output dirs
with "<name>/", so several apps can share a package.
app_name: Name of the application
extra_apps: additional apps (elixir is always included) injected into
the .app file
srcs: Sources. Defaults to "lib/**/*.ex"
elixirc_opts: elixirc options
ez_deps: Dependencies that are .ez files
deps: ErlangAppInfo labels
target_compatible_with: Passed to every generated target
testonly: Passed to every generated target
tags: Passed to every generated target
**kwargs: Additional args passed to the underlying app_file rule, such
as app_description

Expand All @@ -34,45 +53,58 @@ def elixir_app(
"lib/**/*.ex",
])

beam_files = _prefixed(name, "beam_files", "_")
app_file_name = _prefixed(name, "app_file", "_")
ebin = _prefixed(name, "ebin", "_")
without_overlap = _prefixed(name, "elixir_without_app_overlap", "_")
common = dict(
target_compatible_with = target_compatible_with,
testonly = testonly,
tags = tags,
)

elixir_bytecode(
name = "beam_files",
name = beam_files,
srcs = srcs,
data = data,
dest = "beam_files",
dest = _prefixed(name, "beam_files", "/"),
elixirc_opts = elixirc_opts,
ez_deps = ez_deps,
deps = deps,
**common
)

app_file(
name = "app_file",
name = app_file_name,
out = "%s.app" % app_name,
app_name = app_name,
extra_apps = ["elixir"] + extra_apps,
modules = [":beam_files"],
**kwargs
modules = [":" + beam_files],
**(common | kwargs)
)

elixir_ebin_dir(
name = "ebin",
beam_files_dir = ":beam_files",
app_file = ":app_file",
dest = "ebin",
name = ebin,
beam_files_dir = ":" + beam_files,
app_file = ":" + app_file_name,
dest = _prefixed(name, "ebin", "/"),
**common
)

erlang_app_filter_module_conflicts(
name = "elixir_without_app_overlap",
dest = "unconsolidated",
name = without_overlap,
dest = _prefixed(name, "unconsolidated", "/"),
src = Label("@rules_elixir//elixir:elixir"),
without = [":ebin"],
without = [":" + ebin],
**common
)

erlang_app_info(
name = "erlang_app",
name = name,
srcs = srcs,
hdrs = [],
app_name = app_name,
beam = [":ebin"],
beam = [":" + ebin],
extra_apps = extra_apps,
# allow_empty: plenty of Hex packages ship no LICENSE file (ash among them), and
# with --incompatible_disallow_empty_glob on -- the default since Bazel 7 -- an
Expand All @@ -81,6 +113,7 @@ def elixir_app(
priv = [],
visibility = ["//visibility:public"],
deps = [
":elixir_without_app_overlap",
":" + without_overlap,
] + deps,
**common
)
60 changes: 60 additions & 0 deletions test/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
load("@bazel_skylib//rules:build_test.bzl", "build_test")
load("@rules_elixir//:elixir_app.bzl", "elixir_app")
load("@rules_elixir//:ex_unit_test.bzl", "ex_unit_test")
load(
Expand All @@ -15,6 +16,65 @@ elixir_app(
],
)

# A second app in the same package. Its internal targets and output dirs
# are prefixed with its name, so they do not clash with the default ones.
# testonly is forwarded to every generated target; if it were not, the
# final target would fail analysis because it depends on testonly ones.
elixir_app(
name = "other",
app_name = "other",
srcs = ["other_lib/other.ex"],
testonly = True,
tags = ["other_app"],
)

build_test(
name = "app_targets_test",
targets = [
":erlang_app",
":beam_files",
":app_file",
":ebin",
":elixir_without_app_overlap",
":other",
":other_beam_files",
":other_app_file",
":other_ebin",
":other_elixir_without_app_overlap",
],
)

sh_test(
name = "app_names_test",
size = "small",
srcs = ["app_names_test.sh"],
args = [
"beam_files=$(rootpath :beam_files)",
"ebin=$(rootpath :ebin)",
"unconsolidated=$(rootpath :elixir_without_app_overlap)",
"other/beam_files=$(rootpath :other_beam_files)",
"other/ebin=$(rootpath :other_ebin)",
"other/unconsolidated=$(rootpath :other_elixir_without_app_overlap)",
],
data = [
":beam_files",
":ebin",
":elixir_without_app_overlap",
":other_beam_files",
":other_ebin",
":other_elixir_without_app_overlap",
],
)

# Incompatible with every platform, so //... skips all of its targets.
# .github/workflows/test.yaml checks that building it directly fails.
elixir_app(
name = "incompatible_app",
app_name = "incompatible_app",
srcs = ["incompatible_lib/incompatible.ex"],
target_compatible_with = ["@platforms//:incompatible"],
)

escript_archive(
name = "basic",
app = ":erlang_app",
Expand Down
10 changes: 10 additions & 0 deletions test/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ module(
version = "0.1.0",
)

bazel_dep(
name = "bazel_skylib",
version = "1.9.2",
)
bazel_dep(
name = "rules_erlang",
version = "3.18.0",
Expand All @@ -23,6 +27,12 @@ bazel_dep(
version = "0.8.0",
)

# target_compatible_with in BUILD.bazel uses @platforms//:incompatible.
bazel_dep(
name = "platforms",
version = "1.1.0",
)

# See the note in the root MODULE.bazel. This is a separate module, so it needs
# its own override; the one in the ruleset it depends on does not carry over.
git_override(
Expand Down
20 changes: 20 additions & 0 deletions test/app_names_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Each argument is "<expected path>=<rootpath>". The targets are in the
# root package, so the rootpath of an output dir is its dest. The output
# dirs of the default-named elixir_app must not gain a prefix.
set -euo pipefail

for arg in "$@"; do
expected="${arg%%=*}"
path="${arg#*=}"
path="${path#./}"
if [ "$path" != "$expected" ]; then
echo "FAIL: output dir is $path, expected $expected" >&2
exit 1
fi
if [ ! -d "$path" ]; then
echo "FAIL: $path is not a directory" >&2
exit 1
fi
echo "ok: $path"
done
3 changes: 3 additions & 0 deletions test/incompatible_lib/incompatible.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule Incompatible do
def hello, do: :world
end
3 changes: 3 additions & 0 deletions test/other_lib/other.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule Other do
def hello, do: :world
end