From aa80307c68f2d781ae16eb04e906358f44572b29 Mon Sep 17 00:00:00 2001 From: Martin Kuntz Jacobsen Date: Thu, 5 Nov 2026 00:04:57 +0100 Subject: [PATCH] patches/rules_elixir: split hermetic_elixir.patch per upstream PR (part of #3744) The patch now matches the four upstream PRs, so each merge is a pin bump plus deleting one patch file: elixir_app_name (bazelverse/rules_elixir#3), elixir_app_deps (#2), warnings_as_errors (#4) and relocatable_tree (waits on rules_erlang#18). Applied in that order; concatenated they equal the old patch. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_011u9TXHDZrfmzFQ2WT8QAKK --- elixir/toolchain/repos.bzl | 6 +- patches/rules_elixir/BUILD.bazel | 5 +- patches/rules_elixir/elixir_app_deps.patch | 11 + patches/rules_elixir/elixir_app_name.patch | 130 ++++++++ patches/rules_elixir/hermetic_elixir.patch | 279 ------------------ patches/rules_elixir/relocatable_tree.patch | 108 +++++++ patches/rules_elixir/warnings_as_errors.patch | 40 +++ 7 files changed, 298 insertions(+), 281 deletions(-) create mode 100644 patches/rules_elixir/elixir_app_deps.patch create mode 100644 patches/rules_elixir/elixir_app_name.patch delete mode 100644 patches/rules_elixir/hermetic_elixir.patch create mode 100644 patches/rules_elixir/relocatable_tree.patch create mode 100644 patches/rules_elixir/warnings_as_errors.patch diff --git a/elixir/toolchain/repos.bzl b/elixir/toolchain/repos.bzl index da86f282fc..abe87d7e6d 100644 --- a/elixir/toolchain/repos.bzl +++ b/elixir/toolchain/repos.bzl @@ -191,7 +191,11 @@ def _elixir_impl(mctx): name = "rules_elixir", dropped = RULES_ELIXIR_DROPPED, patches = [ - Label("//patches/rules_elixir:hermetic_elixir.patch"), + # Applied in this order: the deps patch builds on the name patch. + Label("//patches/rules_elixir:elixir_app_name.patch"), + Label("//patches/rules_elixir:elixir_app_deps.patch"), + Label("//patches/rules_elixir:warnings_as_errors.patch"), + Label("//patches/rules_elixir:relocatable_tree.patch"), Label("//patches/rules_elixir:toolchain_type_alias.patch"), Label("//patches/rules_elixir:windows_ex_unit_test.patch"), ], diff --git a/patches/rules_elixir/BUILD.bazel b/patches/rules_elixir/BUILD.bazel index ceaa95e90e..d387636c7a 100644 --- a/patches/rules_elixir/BUILD.bazel +++ b/patches/rules_elixir/BUILD.bazel @@ -1,8 +1,11 @@ load("@rules_dx//quality:fixtures.bzl", "real_source_target") exports_files([ - "hermetic_elixir.patch", + "elixir_app_deps.patch", + "elixir_app_name.patch", + "relocatable_tree.patch", "toolchain_type_alias.patch", + "warnings_as_errors.patch", "windows_ex_unit_test.patch", ]) diff --git a/patches/rules_elixir/elixir_app_deps.patch b/patches/rules_elixir/elixir_app_deps.patch new file mode 100644 index 0000000000..cdfd63b7be --- /dev/null +++ b/patches/rules_elixir/elixir_app_deps.patch @@ -0,0 +1,11 @@ +diff --git a/elixir_app.bzl b/elixir_app.bzl +--- a/elixir_app.bzl ++++ b/elixir_app.bzl +@@ -80,6 +80,7 @@ def elixir_app( + app_name = app_name, + extra_apps = ["elixir"] + extra_apps, + modules = [":" + beam_files], ++ deps = deps, + **(common | kwargs) + ) + diff --git a/patches/rules_elixir/elixir_app_name.patch b/patches/rules_elixir/elixir_app_name.patch new file mode 100644 index 0000000000..2ef9ea55a5 --- /dev/null +++ b/patches/rules_elixir/elixir_app_name.patch @@ -0,0 +1,130 @@ +diff --git a/elixir_app.bzl b/elixir_app.bzl +--- a/elixir_app.bzl ++++ b/elixir_app.bzl +@@ -4,7 +4,15 @@ 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, +@@ -12,10 +20,18 @@ def elixir_app( + 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 "_", and their output dirs ++ with "/", 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 +@@ -23,6 +39,9 @@ def elixir_app( + 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 + +@@ -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 +@@ -81,6 +113,7 @@ def elixir_app( + priv = [], + visibility = ["//visibility:public"], + deps = [ +- ":elixir_without_app_overlap", ++ ":" + without_overlap, + ] + deps, ++ **common + ) diff --git a/patches/rules_elixir/hermetic_elixir.patch b/patches/rules_elixir/hermetic_elixir.patch deleted file mode 100644 index a10f666770..0000000000 --- a/patches/rules_elixir/hermetic_elixir.patch +++ /dev/null @@ -1,279 +0,0 @@ -diff --git a/elixir_app.bzl b/elixir_app.bzl ---- a/elixir_app.bzl -+++ b/elixir_app.bzl -@@ -4,7 +4,15 @@ 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, -@@ -12,10 +20,18 @@ def elixir_app( - 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 "_", and their output dirs -+ with "/", 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 -@@ -23,6 +39,9 @@ def elixir_app( - 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 - -@@ -34,45 +53,59 @@ 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], -+ deps = deps, -+ **(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 -@@ -81,6 +114,7 @@ def elixir_app( - priv = [], - visibility = ["//visibility:public"], - deps = [ -- ":elixir_without_app_overlap", -+ ":" + without_overlap, - ] + deps, -+ **common - ) -diff --git a/private/elixir_build.bzl b/private/elixir_build.bzl ---- a/private/elixir_build.bzl -+++ b/private/elixir_build.bzl -@@ -15,11 +15,19 @@ load( - - ElixirInfo = provider( - doc = "A Home directory of a built Elixir", -- fields = [ -- "release_dir", -- "elixir_home", -- "version_file", -- ], -+ fields = { -+ "release_dir": "A directory holding the Elixir installation, or None", -+ "elixir_home": "An absolute path to an external Elixir, or None", -+ "version_file": """A file containing the version of this -+elixir, used to correctly invalidate the cache when an -+external elixir is used""", -+ "tree": """Optional. A depset of every file of a relocatable -+elixir installation whose root directory holds version_file. When set, -+rules put these files in their inputs and runfiles and find the -+installation next to version_file, so release_dir and elixir_home are -+not used by the rules. Providers that do not set this field keep the -+current behaviour.""", -+ }, - ) - - def _impl(ctx): -@@ -144,6 +152,7 @@ export PATH="{erlang_home}"/bin:${{PATH}} - release_dir = release_dir, - elixir_home = None, - version_file = version_file, -+ tree = None, - ), - ] - -@@ -292,6 +301,7 @@ export PATH="{erlang_home}"/bin:${{PATH}} - release_dir = release_dir, - elixir_home = None, - version_file = version_file, -+ tree = None, - ), - ] - -@@ -344,6 +354,7 @@ export PATH="{erlang_home}"/bin:${{PATH}} - release_dir = None, - elixir_home = elixir_home, - version_file = version_file, -+ tree = None, - ), - ] - -diff --git a/private/elixir_bytecode.bzl b/private/elixir_bytecode.bzl ---- a/private/elixir_bytecode.bzl -+++ b/private/elixir_bytecode.bzl -@@ -58,11 +58,26 @@ fi - {env} - - {setup} --set -x -+diagnostics=$(mktemp) -+status=0 - ${{ABS_ELIXIR_HOME}}/bin/elixirc \\ - -o {out_dir} \\ - {elixirc_opts} \\ -- {srcs} -+ {srcs} 2>"$diagnostics" || status=$? -+cat "$diagnostics" >&2 -+# Released Elixir ignores --warnings-as-errors in elixirc and exits 0 on -+# warnings. Fail here instead. Once elixirc fails by itself, status is -+# not 0 and this check does nothing. The launcher's warning about a -+# latin1 file name encoding is not a compiler warning, so it is skipped. -+if [ "$status" = 0 ] && [ -n "{warnings_as_errors}" ] && awk ' -+ /^[[:space:]]*warning:/ && !/native name encoding of latin1/ {{ found = 1 }} -+ END {{ exit !found }} -+' "$diagnostics"; then -+ echo "Compilation failed due to warnings while using the --warnings-as-errors option" >&2 -+ status=1 -+fi -+rm -f "$diagnostics" -+exit "$status" - """.format( - maybe_install_erlang = maybe_install_erlang(ctx), - erlang_home = erlang_home, -@@ -73,6 +88,7 @@ ${{ABS_ELIXIR_HOME}}/bin/elixirc \\ - out_dir = ebin.path, - elixirc_opts = " ".join([shell.quote(opt) for opt in ctx.attr.elixirc_opts]), - srcs = " ".join([f.path for f in ctx.files.srcs]), -+ warnings_as_errors = "1" if "--warnings-as-errors" in ctx.attr.elixirc_opts else "", - ) - - inputs = depset( -diff --git a/private/elixir_toolchain.bzl b/private/elixir_toolchain.bzl ---- a/private/elixir_toolchain.bzl -+++ b/private/elixir_toolchain.bzl -@@ -2,6 +2,10 @@ load( - "@rules_erlang//private:erlang_build.bzl", - "OtpInfo", - ) -+load( -+ "@rules_erlang//tools:erlang_toolchain.bzl", -+ "TREE_ERLANG_HOME", -+) - load( - ":elixir_build.bzl", - "ElixirInfo", -@@ -28,8 +32,17 @@ elixir_toolchain = rule( - def _build_info(ctx): - return ctx.toolchains["//:toolchain_type"].otpinfo - -+def _tree(info): -+ return getattr(info, "tree", None) -+ - def erlang_dirs(ctx): - info = _build_info(ctx) -+ tree = _tree(info) -+ if tree != None: -+ runfiles = ctx.runfiles( -+ transitive_files = depset([info.version_file], transitive = [tree]), -+ ) -+ return (TREE_ERLANG_HOME, None, runfiles) - if info.release_dir_tar != None: - runfiles = ctx.runfiles([ - info.release_dir_tar, -@@ -43,6 +56,13 @@ def erlang_dirs(ctx): - - def elixir_dirs(ctx, short_path = False): - info = ctx.toolchains["//:toolchain_type"].elixirinfo -+ tree = _tree(info) -+ if tree != None: -+ version_file = info.version_file.short_path if short_path else info.version_file.path -+ runfiles = ctx.runfiles( -+ transitive_files = depset([info.version_file], transitive = [tree]), -+ ) -+ return (version_file.rpartition("/")[0], runfiles) - if info.elixir_home != None: - return (info.elixir_home, ctx.runfiles([info.version_file])) - else: -@@ -51,6 +71,9 @@ def elixir_dirs(ctx, short_path = False): - - def maybe_install_erlang(ctx, short_path = False): - info = _build_info(ctx) -+ if _tree(info) != None: -+ version_file = info.version_file.short_path if short_path else info.version_file.path -+ return 'RULES_ERLANG_HOME="$(cd "$(dirname "{}")" && pwd)"'.format(version_file) - release_dir_tar = info.release_dir_tar - if release_dir_tar == None: - return "" diff --git a/patches/rules_elixir/relocatable_tree.patch b/patches/rules_elixir/relocatable_tree.patch new file mode 100644 index 0000000000..6bdb67ee6c --- /dev/null +++ b/patches/rules_elixir/relocatable_tree.patch @@ -0,0 +1,108 @@ +diff --git a/private/elixir_build.bzl b/private/elixir_build.bzl +--- a/private/elixir_build.bzl ++++ b/private/elixir_build.bzl +@@ -15,11 +15,19 @@ load( + + ElixirInfo = provider( + doc = "A Home directory of a built Elixir", +- fields = [ +- "release_dir", +- "elixir_home", +- "version_file", +- ], ++ fields = { ++ "release_dir": "A directory holding the Elixir installation, or None", ++ "elixir_home": "An absolute path to an external Elixir, or None", ++ "version_file": """A file containing the version of this ++elixir, used to correctly invalidate the cache when an ++external elixir is used""", ++ "tree": """Optional. A depset of every file of a relocatable ++elixir installation whose root directory holds version_file. When set, ++rules put these files in their inputs and runfiles and find the ++installation next to version_file, so release_dir and elixir_home are ++not used by the rules. Providers that do not set this field keep the ++current behaviour.""", ++ }, + ) + + def _impl(ctx): +@@ -144,6 +152,7 @@ export PATH="{erlang_home}"/bin:${{PATH}} + release_dir = release_dir, + elixir_home = None, + version_file = version_file, ++ tree = None, + ), + ] + +@@ -292,6 +301,7 @@ export PATH="{erlang_home}"/bin:${{PATH}} + release_dir = release_dir, + elixir_home = None, + version_file = version_file, ++ tree = None, + ), + ] + +@@ -344,6 +354,7 @@ export PATH="{erlang_home}"/bin:${{PATH}} + release_dir = None, + elixir_home = elixir_home, + version_file = version_file, ++ tree = None, + ), + ] + +diff --git a/private/elixir_toolchain.bzl b/private/elixir_toolchain.bzl +--- a/private/elixir_toolchain.bzl ++++ b/private/elixir_toolchain.bzl +@@ -2,6 +2,10 @@ load( + "@rules_erlang//private:erlang_build.bzl", + "OtpInfo", + ) ++load( ++ "@rules_erlang//tools:erlang_toolchain.bzl", ++ "TREE_ERLANG_HOME", ++) + load( + ":elixir_build.bzl", + "ElixirInfo", +@@ -28,8 +32,17 @@ elixir_toolchain = rule( + def _build_info(ctx): + return ctx.toolchains["//:toolchain_type"].otpinfo + ++def _tree(info): ++ return getattr(info, "tree", None) ++ + def erlang_dirs(ctx): + info = _build_info(ctx) ++ tree = _tree(info) ++ if tree != None: ++ runfiles = ctx.runfiles( ++ transitive_files = depset([info.version_file], transitive = [tree]), ++ ) ++ return (TREE_ERLANG_HOME, None, runfiles) + if info.release_dir_tar != None: + runfiles = ctx.runfiles([ + info.release_dir_tar, +@@ -43,6 +56,13 @@ def erlang_dirs(ctx): + + def elixir_dirs(ctx, short_path = False): + info = ctx.toolchains["//:toolchain_type"].elixirinfo ++ tree = _tree(info) ++ if tree != None: ++ version_file = info.version_file.short_path if short_path else info.version_file.path ++ runfiles = ctx.runfiles( ++ transitive_files = depset([info.version_file], transitive = [tree]), ++ ) ++ return (version_file.rpartition("/")[0], runfiles) + if info.elixir_home != None: + return (info.elixir_home, ctx.runfiles([info.version_file])) + else: +@@ -51,6 +71,9 @@ def elixir_dirs(ctx, short_path = False): + + def maybe_install_erlang(ctx, short_path = False): + info = _build_info(ctx) ++ if _tree(info) != None: ++ version_file = info.version_file.short_path if short_path else info.version_file.path ++ return 'RULES_ERLANG_HOME="$(cd "$(dirname "{}")" && pwd)"'.format(version_file) + release_dir_tar = info.release_dir_tar + if release_dir_tar == None: + return "" diff --git a/patches/rules_elixir/warnings_as_errors.patch b/patches/rules_elixir/warnings_as_errors.patch new file mode 100644 index 0000000000..cde1ea9228 --- /dev/null +++ b/patches/rules_elixir/warnings_as_errors.patch @@ -0,0 +1,40 @@ +diff --git a/private/elixir_bytecode.bzl b/private/elixir_bytecode.bzl +--- a/private/elixir_bytecode.bzl ++++ b/private/elixir_bytecode.bzl +@@ -58,11 +58,26 @@ fi + {env} + + {setup} +-set -x ++diagnostics=$(mktemp) ++status=0 + ${{ABS_ELIXIR_HOME}}/bin/elixirc \\ + -o {out_dir} \\ + {elixirc_opts} \\ +- {srcs} ++ {srcs} 2>"$diagnostics" || status=$? ++cat "$diagnostics" >&2 ++# Released Elixir ignores --warnings-as-errors in elixirc and exits 0 on ++# warnings. Fail here instead. Once elixirc fails by itself, status is ++# not 0 and this check does nothing. The launcher's warning about a ++# latin1 file name encoding is not a compiler warning, so it is skipped. ++if [ "$status" = 0 ] && [ -n "{warnings_as_errors}" ] && awk ' ++ /^[[:space:]]*warning:/ && !/native name encoding of latin1/ {{ found = 1 }} ++ END {{ exit !found }} ++' "$diagnostics"; then ++ echo "Compilation failed due to warnings while using the --warnings-as-errors option" >&2 ++ status=1 ++fi ++rm -f "$diagnostics" ++exit "$status" + """.format( + maybe_install_erlang = maybe_install_erlang(ctx), + erlang_home = erlang_home, +@@ -73,6 +88,7 @@ ${{ABS_ELIXIR_HOME}}/bin/elixirc \\ + out_dir = ebin.path, + elixirc_opts = " ".join([shell.quote(opt) for opt in ctx.attr.elixirc_opts]), + srcs = " ".join([f.path for f in ctx.files.srcs]), ++ warnings_as_errors = "1" if "--warnings-as-errors" in ctx.attr.elixirc_opts else "", + ) + + inputs = depset(