Skip to content

Commit 356b74d

Browse files
author
José Valim
committed
Do not allow duplicated projects to be pushed onto the stack
Closes #1846
1 parent 9457984 commit 356b74d

File tree

8 files changed

+57
-40
lines changed

8 files changed

+57
-40
lines changed

lib/mix/lib/mix/cli.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ defmodule Mix.CLI do
7878
defp change_env(task) do
7979
if nil?(System.get_env("MIX_ENV")) && (env = Mix.project[:preferred_cli_env][task]) do
8080
Mix.env(env)
81-
Mix.Project.push Mix.Project.pop
81+
{ project, file } = Mix.Project.pop
82+
Mix.Project.push project, file
8283
end
8384
end
8485

lib/mix/lib/mix/deps.ex

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -175,24 +175,10 @@ defmodule Mix.Deps do
175175
changing the current working directory and loading the given
176176
project onto the project stack.
177177
178-
In case the project is a rebar dependency, a `Mix.Rebar` project
179-
will be pushed into the stack to simulate the rebar configuration.
180-
181178
It is expected a fetched dependency as argument.
182179
"""
183180
def in_dependency(dep, post_config // [], fun)
184181

185-
def in_dependency(Mix.Dep[manager: :rebar, opts: opts], post_config, fun) do
186-
# Use post_config for rebar deps
187-
Mix.Project.post_config(post_config)
188-
Mix.Project.push(Mix.Rebar)
189-
try do
190-
File.cd!(opts[:dest], fn -> fun.(Mix.Rebar) end)
191-
after
192-
Mix.Project.pop
193-
end
194-
end
195-
196182
def in_dependency(Mix.Dep[app: app, opts: opts], post_config, fun) do
197183
env = opts[:env] || :prod
198184
old_env = Mix.env

lib/mix/lib/mix/deps/retriever.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ defmodule Mix.Deps.Retriever do
144144
end)
145145
end
146146

147-
defp rebar_dep(Mix.Dep[] = dep, config) do
148-
Mix.Deps.in_dependency(dep, config, fn _ ->
147+
defp rebar_dep(Mix.Dep[opts: opts] = dep, config) do
148+
File.cd!(opts[:dest], fn ->
149149
config = Mix.Rebar.load_config(".")
150150
extra = Dict.take(config, [:sub_dirs])
151151
dep.manager(:rebar).extra(extra).deps(rebar_children(config))

lib/mix/lib/mix/project.ex

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,21 @@ defmodule Mix.Project do
4141
# Invoked after each Mix.Project is compiled.
4242
@doc false
4343
def __after_compile__(env, _binary) do
44-
push env.module
44+
push env.module, env.file
4545
end
4646

4747
# Push a project onto the project stack. Only
4848
# the top of the stack can be accessed.
4949
@doc false
50-
def push(atom) when is_atom(atom) do
50+
def push(atom, file // "nofile") when is_atom(atom) do
5151
config = Keyword.merge default_config, get_project_config(atom)
52-
53-
Mix.Server.cast({ :push_project, atom, config })
52+
case Mix.Server.call({ :push_project, atom, config, file }) do
53+
:ok ->
54+
:ok
55+
{ :error, other } when is_binary(other) ->
56+
raise Mix.Error, message: "Trying to load #{inspect atom} from #{inspect file}" <>
57+
" but another project with the same was already defined at #{inspect other}"
58+
end
5459
end
5560

5661
# Pops a project from the stack.

lib/mix/lib/mix/rebar.ex

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
defmodule Mix.Rebar do
22
@moduledoc false
33

4-
# Make Mix.Rebar work like a project so we can push it into the stack.
5-
@doc false
6-
def project, do: []
7-
84
@doc """
95
Returns the path supposed to host the local copy of rebar.
106
"""

lib/mix/lib/mix/server.ex

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule Mix.Server do
55
defrecord Config, tasks: :ordsets.new, projects: [], mixfile: [],
66
shell: Mix.Shell.IO, scm: :ordsets.new, env: nil, post_config: []
77

8-
defrecord Project, name: nil, config: nil, rec_enabled?: true, io_done: false
8+
defrecord Project, name: nil, config: nil, rec_enabled?: true, io_done: false, file: nil
99

1010
def start_link(env) do
1111
:gen_server.start_link({ :local, __MODULE__ }, __MODULE__, env, [])
@@ -55,8 +55,8 @@ defmodule Mix.Server do
5555

5656
def handle_call(:pop_project, _from, config) do
5757
case config.projects do
58-
[ Project[name: project] | tail ] ->
59-
{ :reply, project, config.projects(tail) }
58+
[ Project[name: name, file: file] | tail ] ->
59+
{ :reply, { name, file }, config.projects(tail) }
6060
_ ->
6161
{ :reply, nil, config }
6262
end
@@ -87,10 +87,29 @@ defmodule Mix.Server do
8787
end
8888
end
8989

90+
def handle_call({ :push_project, name, config, file }, _from, state) do
91+
config = Keyword.merge(config, state.post_config)
92+
project = Project[name: name, config: config, file: file]
93+
94+
cond do
95+
file = has_project_named(name, state) ->
96+
{ :reply, { :error, file }, state }
97+
true ->
98+
{ :reply, :ok, state.post_config([]).update_projects(&[project|&1]) }
99+
end
100+
end
101+
90102
def handle_call(request, from, config) do
91103
super(request, from, config)
92104
end
93105

106+
defp has_project_named(name, state) do
107+
name && Enum.find_value(state.projects, fn
108+
Project[name: ^name, file: file] -> file
109+
Project[] -> nil
110+
end)
111+
end
112+
94113
def handle_cast({ :shell, name }, config) do
95114
{ :noreply, config.shell(name) }
96115
end
@@ -111,14 +130,6 @@ defmodule Mix.Server do
111130
{ :noreply, config.update_tasks &:ordsets.filter(fn {t, _} -> t != task end, &1) }
112131
end
113132

114-
def handle_cast({ :push_project, name, conf }, config) do
115-
conf = Keyword.merge(conf, config.post_config)
116-
project = Project[name: name, config: conf]
117-
config = config.post_config([])
118-
.update_projects(&[project|&1])
119-
{ :noreply, config }
120-
end
121-
122133
def handle_cast({ :post_config, value }, config) do
123134
{ :noreply, config.update_post_config(&Keyword.merge(&1, value)) }
124135
end

lib/mix/test/mix/project_test.exs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,34 @@ defmodule Mix.ProjectTest do
1818

1919
test "push and pop projects" do
2020
refute Mix.Project.get
21-
Mix.Project.push(SampleProject)
21+
Mix.Project.push(SampleProject, "sample")
2222

2323
assert Mix.Project.get == SampleProject
2424

25-
assert Mix.Project.pop == SampleProject
25+
assert Mix.Project.pop == { SampleProject, "sample" }
2626
assert Mix.Project.pop == nil
2727
after
2828
Mix.Project.pop
2929
end
3030

31+
test "does not allow the same project to be pushed twice" do
32+
Mix.Project.push(SampleProject, "sample")
33+
34+
assert_raise Mix.Error, %r/Mix.ProjectTest.SampleProject from "another"/, fn ->
35+
Mix.Project.push(SampleProject, "another")
36+
end
37+
after
38+
Mix.Project.pop
39+
end
40+
41+
test "allows nil projects to be pushed twice" do
42+
Mix.Project.push nil
43+
Mix.Project.push nil
44+
assert Mix.Project.pop == { nil, "nofile" }
45+
assert Mix.Project.pop == { nil, "nofile" }
46+
assert Mix.Project.pop == nil
47+
end
48+
3149
test "retrieves configuration from projects" do
3250
Mix.Project.push(SampleProject)
3351
assert Mix.project[:hello] == "world"

lib/mix/test/mix/tasks/deps.git_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,9 @@ defmodule Mix.Tasks.DepsGitTest do
343343
end
344344

345345
defp refresh(post_config) do
346-
current = Mix.Project.pop
346+
{ current, file } = Mix.Project.pop
347347
Mix.Project.post_config(post_config)
348-
Mix.Project.push(current)
348+
Mix.Project.push(current, file)
349349
end
350350

351351
defp get_git_repo_revs do

0 commit comments

Comments
 (0)