Skip to content
Closed
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
16 changes: 16 additions & 0 deletions lib/mix/lib/mix/tasks/clean.ex
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ defmodule Mix.Tasks.Clean do
|> Path.join("*#{opts[:only]}")

if opts[:deps] do
deps_path = Mix.Project.deps_path()
build_lib = Path.join(build, "lib")
loaded_deps = Mix.Dep.Converger.converge([])

apps =
build_lib
|> Path.join("*")
|> Path.wildcard()
|> Enum.filter(&File.dir?/1)
|> Enum.map(&Path.basename/1)
|> Enum.reject(&(&1 == to_string(Mix.Project.config()[:app])))

Mix.Project.with_deps_lock(fn ->
Mix.Tasks.Deps.Clean.clean_generated_sources(apps, build_lib, deps_path, loaded_deps)
end)

build
|> Path.wildcard()
|> Enum.each(&File.rm_rf/1)
Expand Down
55 changes: 55 additions & 0 deletions lib/mix/lib/mix/tasks/deps.clean.ex
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ defmodule Mix.Tasks.Deps.Clean do
end

Mix.Project.with_build_lock(fn ->
clean_generated_sources(apps_to_clean, build_path, deps_path, loaded_deps)
clean_build(apps_to_clean, build_path)
end)

Expand Down Expand Up @@ -118,6 +119,60 @@ defmodule Mix.Tasks.Deps.Clean do
end
end

# Called from Mix.Tasks.Clean for --deps mode.
@doc false
def clean_generated_sources([], _build_path, _deps_path, _loaded_deps), do: :ok

def clean_generated_sources([app | rest], build_path, deps_path, loaded_deps) do
dest =
case Enum.find(loaded_deps, &(Atom.to_string(&1.app) == app)) do
%{opts: opts} -> opts[:dest]
nil -> Path.expand(Path.join(deps_path, app))
end

# When no Mix manifests exist (e.g. rebar3 deps compiled externally),
# infer generated files from co-located .yrl/.xrl sources.
paths =
case get_manifest_paths(build_path, app) do
[] ->
erl_paths_from_grammar_sources(dest)

manifest_paths ->
manifest_paths
end

maybe_remove_files(paths, dest)

clean_generated_sources(rest, build_path, deps_path, loaded_deps)
end

defp maybe_remove_files(paths, dest) do
paths
|> Enum.uniq()
|> Enum.map(&Path.expand(&1, dest))
|> Enum.filter(&String.starts_with?(&1, dest <> "/"))
|> Enum.each(fn expanded ->
case File.rm(expanded) do
:ok -> :ok
{:error, reason} -> maybe_warn_failed_file_deletion({:error, reason, expanded})
end
end)
end

defp erl_paths_from_grammar_sources(dest) do
~w[yrl xrl]
|> Enum.flat_map(&Path.wildcard(Path.join([dest, "**", "*.#{&1}"])))
|> Enum.map(&(Path.rootname(&1) <> ".erl"))
end

defp get_manifest_paths(build_path, app) do
build_path
|> Path.join(to_string(app))
|> Path.wildcard()
|> Enum.flat_map(&Path.wildcard(Path.join(&1, ".mix/compile.{yecc,leex}")))
|> Enum.flat_map(&Mix.Compilers.Erlang.outputs/1)
end

defp clean_build(apps, build_path) do
shell = Mix.shell()

Expand Down
4 changes: 4 additions & 0 deletions lib/mix/test/fixtures/clean_with_yecc/deps/parser_dep/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
defmodule ParserDep.MixProject do
use Mix.Project
def project, do: [app: :parser_dep, version: "0.1.0"]
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
%% Hand-authored — NOT generated, must never be removed by mix clean.
-module(handwritten).
-export([run/0]).
run() -> ok.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
%% Generated by leex from lexer.xrl — this file should be removed by mix clean.
-module(lexer).
-export([string/1]).
string(_) -> ok.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Definitions.
D = [0-9]
Rules.
{D}+ : {token, {number, TokenLine, list_to_integer(TokenChars)}}.
Erlang code.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
%% Generated by yecc from parser.yrl — this file should be removed by mix clean.
-module(parser).
-export([parse/1]).
parse(_) -> ok.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Nonterminals expr.
Terminals number.
Rootsymbol expr.
expr -> number : '$1'.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{erl_opts, []}.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
%% Generated by yecc from rb_parser.yrl — this file should be removed by mix clean.
-module(rb_parser).
-export([parse/1]).
parse(_) -> ok.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Nonterminals expr.
Terminals number.
Rootsymbol expr.
expr -> number : '$1'.
75 changes: 75 additions & 0 deletions lib/mix/test/mix/tasks/clean_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,81 @@ defmodule Mix.Tasks.CleanTest do
end)
end

defmodule ParserOnly do
def project do
[
app: :clean_with_yecc,
version: "0.1.0",
deps: [{:parser_dep, path: "deps/parser_dep"}]
]
end
end

defp yecc_manifest(build_dir, relative_paths) do
write_manifest(build_dir, "compile.yecc", relative_paths)
end

defp leex_manifest(build_dir, relative_paths) do
write_manifest(build_dir, "compile.leex", relative_paths)
end

defp write_manifest(build_dir, name, relative_paths) do
mix_dir = Path.join(build_dir, ".mix")
File.mkdir_p!(mix_dir)
entries = Enum.map(relative_paths, &{&1, []})
File.write!(Path.join(mix_dir, name), :erlang.term_to_binary({1, entries}))
end

test "--deps removes yecc-generated .erl files from dep source" do
in_fixture("clean_with_yecc", fn ->
Mix.Project.push(ParserOnly)

build_dir = "_build/dev/lib/parser_dep"
File.mkdir_p!(Path.join(build_dir, "ebin"))
yecc_manifest(build_dir, ["src/parser.erl"])

Mix.Tasks.Clean.run(["--deps"])

refute File.exists?("deps/parser_dep/src/parser.erl")
assert File.exists?("deps/parser_dep/src/parser.yrl")
refute File.exists?("_build/dev")
end)
end

test "without --deps does not touch dep generated files" do
in_fixture("clean_with_yecc", fn ->
Mix.Project.push(ParserOnly)

Mix.Tasks.Clean.run([])

assert File.exists?("deps/parser_dep/src/parser.erl")
end)
end

test "--deps --only dev scopes generated-file cleanup to dev environment" do
in_fixture("clean_with_yecc", fn ->
Mix.Project.push(ParserOnly)

dev_dir = "_build/dev/lib/parser_dep"
test_dir = "_build/test/lib/parser_dep"

File.mkdir_p!(Path.join(dev_dir, "ebin"))
File.mkdir_p!(Path.join(test_dir, "ebin"))

# dev tracks parser.erl; test tracks lexer.erl
yecc_manifest(dev_dir, ["src/parser.erl"])
leex_manifest(test_dir, ["src/lexer.erl"])

Mix.Tasks.Clean.run(["--deps", "--only", "dev"])

refute File.exists?("deps/parser_dep/src/parser.erl")
# test manifest was not processed
assert File.exists?("deps/parser_dep/src/lexer.erl")
refute File.exists?("_build/dev")
assert File.exists?("_build/test")
end)
end

test "invokes compiler hook defined in project" do
Mix.ProjectStack.post_config(compilers: Mix.compilers() ++ [:testc])

Expand Down
Loading