Skip to content

Commit 0bb3d1c

Browse files
committed
Introduce Mix.State to store internal mix state
Moves non-config state from application env to an ETS table that is updated with an Agent.
1 parent 8647661 commit 0bb3d1c

File tree

8 files changed

+112
-28
lines changed

8 files changed

+112
-28
lines changed

lib/mix/lib/mix.ex

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,27 +161,37 @@ defmodule Mix do
161161
def start(_type, []) do
162162
import Supervisor.Spec
163163

164+
state = [shell: Mix.Shell.IO,
165+
env: String.to_atom(System.get_env("MIX_ENV") || "dev"),
166+
scm: [Mix.SCM.Git, Mix.SCM.Path]]
167+
tab = Mix.State.new(state)
164168
children = [
169+
worker(Mix.State, [tab]),
165170
worker(Mix.TasksServer, []),
166171
worker(Mix.ProjectStack, [])
167172
]
168173

169174
opts = [strategy: :one_for_one, name: Mix.Supervisor]
170-
stat = Supervisor.start_link(children, opts)
171-
172-
if env = System.get_env("MIX_ENV") do
173-
env(String.to_atom env)
175+
case Supervisor.start_link(children, opts) do
176+
{:ok, pid} ->
177+
{:ok, pid, tab}
178+
{:error, _} = error ->
179+
Mix.State.delete(tab)
180+
error
174181
end
182+
end
175183

176-
stat
184+
@doc false
185+
def stop(tab) do
186+
Mix.State.delete(tab)
177187
end
178188

179189
@doc """
180190
Returns the mix environment.
181191
"""
182192
def env do
183193
# env is not available on bootstrapping, so set a :dev default
184-
Application.get_env(:mix, :env, :dev)
194+
Mix.State.get(:env, :dev)
185195
end
186196

187197
@doc """
@@ -191,7 +201,7 @@ defmodule Mix do
191201
configuration won't be reloaded.
192202
"""
193203
def env(env) when is_atom(env) do
194-
Application.put_env(:mix, :env, env)
204+
Mix.State.put(:env, env)
195205
end
196206

197207
@doc """
@@ -218,14 +228,14 @@ defmodule Mix do
218228
messages to the current process.
219229
"""
220230
def shell do
221-
Application.get_env(:mix, :shell, Mix.Shell.IO)
231+
Mix.State.get(:shell, Mix.Shell.IO)
222232
end
223233

224234
@doc """
225235
Sets the current shell.
226236
"""
227237
def shell(shell) do
228-
Application.put_env(:mix, :shell, shell)
238+
Mix.State.put(:shell, shell)
229239
end
230240

231241
@doc """

lib/mix/lib/mix/remote_converger.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ defmodule Mix.RemoteConverger do
3030
Get registered remote converger.
3131
"""
3232
def get do
33-
Application.get_env(:mix, :remote_converger)
33+
Mix.State.get(:remote_converger)
3434
end
3535

3636
@doc """
3737
Register a remote converger.
3838
"""
3939
def register(mod) when is_atom(mod) do
40-
Application.put_env(:mix, :remote_converger, mod)
40+
Mix.State.put(:remote_converger, mod)
4141
end
4242
end

lib/mix/lib/mix/scm.ex

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,23 +116,21 @@ defmodule Mix.SCM do
116116
until a matching one is found.
117117
"""
118118
def available do
119-
{:ok, scm} = Application.fetch_env(:mix, :scm)
119+
{:ok, scm} = Mix.State.fetch(:scm)
120120
scm
121121
end
122122

123123
@doc """
124124
Prepend the given SCM module to the list of available SCMs.
125125
"""
126126
def prepend(mod) when is_atom(mod) do
127-
available = Enum.reject(available(), &(&1 == mod))
128-
Application.put_env(:mix, :scm, [mod|available])
127+
Mix.State.prepend(:scm, mod)
129128
end
130129

131130
@doc """
132131
Append the given SCM module to the list of available SCMs.
133132
"""
134133
def append(mod) when is_atom(mod) do
135-
available = Enum.reject(available(), &(&1 == mod))
136-
Application.put_env(:mix, :scm, available ++ [mod])
134+
Mix.State.append(:scm, mod)
137135
end
138136
end

lib/mix/lib/mix/scm/git.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,12 @@ defmodule Mix.SCM.Git do
163163
end
164164

165165
defp assert_git do
166-
case Application.fetch_env(:mix, :git_available) do
166+
case Mix.State.fetch(:git_available) do
167167
{:ok, true} ->
168168
:ok
169169
:error ->
170170
if :os.find_executable('git') do
171-
Application.put_env(:mix, :git_available, true)
171+
Mix.State.put(:git_available, true)
172172
else
173173
Mix.raise "Error fetching/updating Git repository: the `git` " <>
174174
"executable is not available in your PATH. Please install " <>
@@ -179,7 +179,7 @@ defmodule Mix.SCM.Git do
179179
end
180180

181181
defp git_version do
182-
case Application.fetch_env(:mix, :git_version) do
182+
case Mix.State.fetch(:git_version) do
183183
{:ok, version} ->
184184
version
185185
:error ->
@@ -189,7 +189,7 @@ defmodule Mix.SCM.Git do
189189
|> String.strip
190190
|> parse_version
191191

192-
Application.put_env(:mix, :git_version, version)
192+
Mix.State.put(:git_version, version)
193193
version
194194
end
195195
end

lib/mix/lib/mix/state.ex

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
defmodule Mix.State do
2+
@moduledoc false
3+
4+
@table __MODULE__
5+
@agent __MODULE__
6+
7+
def new(state) do
8+
tab = :ets.new(@table, [:named_table, :public])
9+
true = :ets.insert_new(@table, state)
10+
tab
11+
end
12+
13+
def delete(@table = tab) do
14+
:ets.delete(tab)
15+
end
16+
17+
def start_link(tab) do
18+
Agent.start_link(__MODULE__, :init, [tab], [name: @agent])
19+
end
20+
21+
def init(@table = tab) do
22+
case :ets.info(tab, :protection) do
23+
:public ->
24+
tab
25+
:undefined ->
26+
raise "#{tab} does not exist"
27+
_ ->
28+
raise "#{tab} is not public"
29+
end
30+
end
31+
32+
def fetch(key) do
33+
case :ets.lookup(@table, key) do
34+
[{_, value}] ->
35+
{:ok, value}
36+
[] ->
37+
:error
38+
end
39+
end
40+
41+
def get(key, default \\ nil) do
42+
case fetch(key) do
43+
{:ok, value} -> value
44+
:error -> default
45+
end
46+
end
47+
48+
def put(key, value) do
49+
Agent.update(@agent, __MODULE__, :handle_put, [key, value])
50+
end
51+
52+
def handle_put(tab, key, value) do
53+
true = :ets.insert(tab, {key, value})
54+
tab
55+
end
56+
57+
def prepend(key, value) do
58+
Agent.update(@agent, __MODULE__, :handle_prepend, [key, value])
59+
end
60+
61+
def handle_prepend(tab, key, value) do
62+
true = :ets.insert(tab, {key, [value | reject(key, value)]})
63+
tab
64+
end
65+
66+
def append(key, value) do
67+
Agent.update(@agent, __MODULE__, :handle_append, [key, value])
68+
end
69+
70+
def handle_append(tab, key, value) do
71+
true = :ets.insert(tab, {key, reject(key, value) ++ [value]})
72+
tab
73+
end
74+
75+
defp reject(key, value) do
76+
{:ok, list} = fetch(key)
77+
Enum.reject(list, &(&1 === value))
78+
end
79+
end

lib/mix/mix.exs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@ defmodule Mix.Mixfile do
99
end
1010

1111
def application do
12-
[registered: [Mix.TasksServer, Mix.ProjectStack],
12+
[registered: [Mix.State, Mix.TasksServer, Mix.ProjectStack],
1313
mod: {Mix, []},
14-
env: [shell: Mix.Shell.IO,
15-
env: :dev,
16-
scm: [Mix.SCM.Git, Mix.SCM.Path],
17-
colors: []]]
14+
env: [colors: []]]
1815
end
1916
end

lib/mix/test/mix/rebar_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ defmodule Mix.RebarTest do
1818

1919
setup do
2020
available = Mix.SCM.available
21-
Application.put_env(:mix, :scm, [Mix.SCM.Git, MyPath])
22-
on_exit fn -> Application.put_env(:mix, :scm, available) end
21+
Mix.State.put(:scm, [Mix.SCM.Git, MyPath])
22+
on_exit fn -> Mix.State.put(:scm, available) end
2323
:ok
2424
end
2525

lib/mix/test/mix/scm_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule Mix.SCMTest do
55

66
setup do
77
available = Mix.SCM.available
8-
on_exit fn -> Application.put_env(:mix, :scm, available) end
8+
on_exit fn -> Mix.State.put(:scm, available) end
99
:ok
1010
end
1111

0 commit comments

Comments
 (0)