Skip to content

Commit 388ee6d

Browse files
committed
Support :config and :system_env in Mix.install/2
Closes #11250.
1 parent 1427d7a commit 388ee6d

File tree

2 files changed

+61
-10
lines changed

2 files changed

+61
-10
lines changed

lib/mix/lib/mix.ex

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -563,13 +563,38 @@ defmodule Mix do
563563
* `:elixir` - if set, ensures the current Elixir version matches the given
564564
version requirement (Default: `nil`)
565565
566+
* `:config` (since v1.13.0) - a keyword list of keyword lists with application
567+
configuration to be set before the apps loaded. The configuration is part of
568+
the `Mix.install/2` cache, so different configurations will lead to different
569+
apps
570+
571+
* `:system_env` (since v1.13.0) - a map of system environment variable names as
572+
binary keys and their respective values as binaries. The system environment is
573+
made part of the `Mix.install/2` cache, so different configurations will lead
574+
to different apps
575+
566576
## Examples
567577
578+
To install `:decimal` and `:jason`:
579+
568580
Mix.install([
569581
:decimal,
570582
{:jason, "~> 1.0"}
571583
])
572584
585+
Using `:nx`, `:exla`, and configure the underlying applications
586+
and environment variables:
587+
588+
Mix.install(
589+
[:nx, :exla],
590+
config: [
591+
nx: [default_backend: EXLA]
592+
],
593+
system_env: [
594+
{"XLA_TARGET", "cuda111"}
595+
]
596+
)
597+
573598
## Limitations
574599
575600
There is one limitation to `Mix.install/2`, which is actually an Elixir
@@ -636,29 +661,39 @@ defmodule Mix do
636661
other
637662
end)
638663

664+
config = Keyword.get(opts, :config, [])
665+
system_env = Keyword.get(opts, :system_env, [])
666+
667+
id =
668+
{deps, config, system_env}
669+
|> :erlang.term_to_binary()
670+
|> :erlang.md5()
671+
|> Base.encode16(case: :lower)
672+
639673
force? = !!opts[:force]
640674

641675
case Mix.State.get(:installed) do
642676
nil ->
643677
:ok
644678

645-
^deps when not force? ->
679+
^id when not force? ->
646680
:ok
647681

648682
_ ->
649683
Mix.raise("Mix.install/2 can only be called with the same dependencies in the given VM")
650684
end
651685

686+
Application.put_all_env(config, persistent: true)
687+
System.put_env(system_env)
688+
652689
installs_root =
653-
System.get_env("MIX_INSTALL_DIR") ||
654-
Path.join(Mix.Utils.mix_cache(), "installs")
690+
System.get_env("MIX_INSTALL_DIR") || Path.join(Mix.Utils.mix_cache(), "installs")
655691

656-
id = deps |> :erlang.term_to_binary() |> :erlang.md5() |> Base.encode16(case: :lower)
657692
version = "elixir-#{System.version()}-erts-#{:erlang.system_info(:version)}"
658693
dir = Path.join([installs_root, version, id])
659694

660695
if opts[:verbose] do
661-
Mix.shell().info("using #{dir}")
696+
Mix.shell().info("Mix.install/2 using #{dir}")
662697
end
663698

664699
if force? do
@@ -701,7 +736,7 @@ defmodule Mix do
701736
Application.ensure_all_started(app)
702737
end
703738

704-
Mix.State.put(:installed, deps)
739+
Mix.State.put(:installed, id)
705740
:ok
706741
after
707742
Mix.ProjectStack.pop()

lib/mix/test/mix_test.exs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ defmodule MixTest do
4747
assert apply(InstallTest, :hello, []) == :world
4848
end
4949

50-
test "call with same deps in the same VM", %{tmp_dir: tmp_dir} do
50+
test "works with same deps twice", %{tmp_dir: tmp_dir} do
5151
Mix.install([
5252
{:install_test, path: Path.join(tmp_dir, "install_test")}
5353
])
@@ -57,7 +57,7 @@ defmodule MixTest do
5757
])
5858
end
5959

60-
test "can't call with Elixir version mismatch", %{tmp_dir: tmp_dir} do
60+
test "errors on Elixir version mismatch", %{tmp_dir: tmp_dir} do
6161
assert_raise Mix.Error, ~r"Mix.install/2 declared it supports only Elixir ~> 2.0", fn ->
6262
Mix.install(
6363
[
@@ -68,7 +68,7 @@ defmodule MixTest do
6868
end
6969
end
7070

71-
test "can't call with same deps and force", %{tmp_dir: tmp_dir} do
71+
test "errors with same deps and force", %{tmp_dir: tmp_dir} do
7272
Mix.install([
7373
{:install_test, path: Path.join(tmp_dir, "install_test")}
7474
])
@@ -83,7 +83,7 @@ defmodule MixTest do
8383
end
8484
end
8585

86-
test "can't call with different deps in the same VM", %{tmp_dir: tmp_dir} do
86+
test "errors with different deps in the same VM", %{tmp_dir: tmp_dir} do
8787
Mix.install([
8888
{:install_test, path: Path.join(tmp_dir, "install_test")}
8989
])
@@ -121,6 +121,22 @@ defmodule MixTest do
121121
refute Protocol.consolidated?(InstallTest.Protocol)
122122
end
123123

124+
test "config and system_env", %{tmp_dir: tmp_dir} do
125+
Mix.install(
126+
[
127+
{:install_test, path: Path.join(tmp_dir, "install_test")}
128+
],
129+
config: [unknown_app: [foo: :bar]],
130+
system_env: %{"MIX_INSTALL_FOO" => "BAR"}
131+
)
132+
133+
assert Application.fetch_env!(:unknown_app, :foo) == :bar
134+
assert System.fetch_env!("MIX_INSTALL_FOO") == "BAR"
135+
after
136+
System.delete_env("MIX_INSTALL_FOO")
137+
Application.delete_env(:unknown_app, :foo, persistent: true)
138+
end
139+
124140
defp test_project(%{tmp_dir: tmp_dir}) do
125141
path = :code.get_path()
126142

0 commit comments

Comments
 (0)