Skip to content

Commit f35b086

Browse files
committed
Rely on mtime to recompile app
1 parent 6cfb4e8 commit f35b086

File tree

4 files changed

+47
-64
lines changed

4 files changed

+47
-64
lines changed

lib/mix/lib/mix/release.ex

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -882,9 +882,7 @@ defmodule Mix.Release do
882882

883883
defp process_app_file(source_file, target_file) do
884884
with {:ok, [{:application, app, info}]} <- :file.consult(source_file) do
885-
# Remove :config_mtime so that .app files are deterministic between builds
886-
new_info = Keyword.delete(info, :config_mtime)
887-
File.write!(target_file, :io_lib.format("~tp.~n", [{:application, app, new_info}]))
885+
File.write!(target_file, :io_lib.format("~tp.~n", [{:application, app, info}]))
888886
else
889887
_ -> File.cp!(source_file, target_file)
890888
end

lib/mix/lib/mix/tasks/compile.app.ex

Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -130,50 +130,45 @@ defmodule Mix.Tasks.Compile.App do
130130
@impl true
131131
def run(args) do
132132
{opts, _, _} = OptionParser.parse(args, switches: [force: :boolean, compile_path: :string])
133-
134133
project = Mix.Project.get!()
135134
config = Mix.Project.config()
136135

137136
app = Keyword.get(config, :app)
138137
version = Keyword.get(config, :version)
138+
validate_app!(app)
139+
validate_version!(version)
139140

140-
validate_app(app)
141-
validate_version(version)
142-
143-
path = Keyword.get_lazy(opts, :compile_path, &Mix.Project.compile_path/0)
144-
modules = modules_from(path) |> Enum.sort()
141+
compile_path = Keyword.get_lazy(opts, :compile_path, &Mix.Project.compile_path/0)
142+
target = Path.join(compile_path, "#{app}.app")
145143

146-
target = Path.join(path, "#{app}.app")
147-
148-
# We mostly depend on the project_file through the def application function,
149-
# but it doesn't hurt to also include config_mtime.
144+
# If configurations changed, we may have changed compile_env.
145+
# If compile_path changed, we may have added or removed files.
146+
# If the project changed, we may have changed other properties.
150147
new_mtime =
151-
max(Mix.Project.config_mtime(), Mix.Utils.last_modified(Mix.Project.project_file()))
148+
Mix.Project.config_mtime()
149+
|> max(Mix.Utils.last_modified(Mix.Project.project_file()))
150+
|> max(Mix.Utils.last_modified(compile_path))
152151

153152
current_properties = current_app_properties(target)
154-
compile_env = load_compile_env(current_properties)
155-
old_mtime = Keyword.get(current_properties, :config_mtime, 0)
156153

157-
if opts[:force] || new_mtime > old_mtime ||
158-
app_changed?(current_properties, modules, compile_env) do
154+
if opts[:force] || new_mtime > Mix.Utils.last_modified(target) do
159155
properties =
160156
[
161157
description: to_charlist(config[:description] || app),
162-
modules: modules,
163158
registered: [],
164159
vsn: to_charlist(version)
165160
]
166161
|> merge_project_application(project)
167-
|> validate_properties!()
168162
|> handle_extra_applications(config)
169-
|> add_compile_env(compile_env)
163+
|> add_compile_env(current_properties)
164+
|> add_modules(compile_path)
170165

171-
properties = [config_mtime: new_mtime] ++ properties
172166
contents = :io_lib.format("~p.~n", [{:application, app, properties}])
173167
:application.load({:application, app, properties})
174168

175169
Mix.Project.ensure_structure()
176170
File.write!(target, IO.chardata_to_string(contents))
171+
File.touch!(target, new_mtime)
177172
Mix.shell().info("Generated #{app} app")
178173
{:ok, []}
179174
else
@@ -189,27 +184,15 @@ defmodule Mix.Tasks.Compile.App do
189184
end
190185
end
191186

192-
defp load_compile_env(current_properties) do
193-
case Mix.ProjectStack.compile_env(nil) do
194-
nil -> Keyword.get(current_properties, :compile_env, [])
195-
list -> list
196-
end
197-
end
198-
199-
defp app_changed?(properties, mods, compile_env) do
200-
Keyword.get(properties, :modules, []) != mods or
201-
Keyword.get(properties, :compile_env, []) != compile_env
202-
end
203-
204-
defp validate_app(app) when is_atom(app), do: :ok
187+
defp validate_app!(app) when is_atom(app), do: :ok
205188

206-
defp validate_app(app) do
207-
ensure_present(:app, app)
189+
defp validate_app!(app) do
190+
ensure_present!(:app, app)
208191
Mix.raise("Expected :app to be an atom, got: #{inspect(app)}")
209192
end
210193

211-
defp validate_version(version) do
212-
ensure_present(:version, version)
194+
defp validate_version!(version) do
195+
ensure_present!(:version, version)
213196

214197
if not (is_binary(version) and match?({:ok, _}, Version.parse(version))) do
215198
Mix.raise(
@@ -218,11 +201,11 @@ defmodule Mix.Tasks.Compile.App do
218201
end
219202
end
220203

221-
defp ensure_present(name, nil) do
204+
defp ensure_present!(name, nil) do
222205
Mix.raise("Please ensure mix.exs file has the #{inspect(name)} in the project definition")
223206
end
224207

225-
defp ensure_present(_name, _val), do: :ok
208+
defp ensure_present!(_name, _val), do: :ok
226209

227210
defp modules_from(path) do
228211
case File.ls(path) do
@@ -246,7 +229,7 @@ defmodule Mix.Tasks.Compile.App do
246229
)
247230
end
248231

249-
Keyword.merge(best_guess, project_application)
232+
Keyword.merge(best_guess, validate_properties!(project_application))
250233
else
251234
best_guess
252235
end
@@ -357,8 +340,17 @@ defmodule Mix.Tasks.Compile.App do
357340
defp typed_app?({app, type}) when is_atom(app) and type in [:required, :optional], do: true
358341
defp typed_app?(_), do: false
359342

360-
defp add_compile_env(properties, []), do: properties
361-
defp add_compile_env(properties, compile_env), do: [compile_env: compile_env] ++ properties
343+
defp add_compile_env(properties, current_properties) do
344+
case Mix.ProjectStack.compile_env(nil) do
345+
nil -> Keyword.take(current_properties, [:compile_env]) ++ properties
346+
[] -> properties
347+
compile_env -> Keyword.put(properties, :compile_env, compile_env)
348+
end
349+
end
350+
351+
defp add_modules(properties, path) do
352+
Keyword.put_new_lazy(properties, :modules, fn -> path |> modules_from() |> Enum.sort() end)
353+
end
362354

363355
defp handle_extra_applications(properties, config) do
364356
{extra, properties} = Keyword.pop(properties, :extra_applications, [])

lib/mix/test/mix/release_test.exs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -716,13 +716,6 @@ defmodule Mix.ReleaseTest do
716716

717717
assert mode!(source_so_path) == mode!(tmp_path("mix_release/libtest_nif.so"))
718718
end
719-
720-
test "removes config_mtime from app files" do
721-
assert copy_ebin(release([]), @eex_ebin, tmp_path("eex_ebin"))
722-
723-
{:ok, [{:application, :eex, info}]} = :file.consult(tmp_path("eex_ebin/eex.app"))
724-
refute Keyword.get(info, :config_mtime)
725-
end
726719
end
727720

728721
describe "copy_app/2" do
@@ -755,14 +748,6 @@ defmodule Mix.ReleaseTest do
755748
refute File.exists?(Path.join(@release_lib, "runtime_tools-#{@runtime_tools_version}/ebin"))
756749
refute File.exists?(Path.join(@release_lib, "runtime_tools-#{@runtime_tools_version}/priv"))
757750
end
758-
759-
test "removes config_mtime from app files" do
760-
assert copy_app(release(strip_beams: false, applications: [eex: :permanent]), :eex)
761-
762-
eex_app_path = Path.join(@release_lib, "eex-#{@elixir_version}/ebin/eex.app")
763-
{:ok, [{:application, :eex, info}]} = :file.consult(eex_app_path)
764-
refute Keyword.get(info, :config_mtime)
765-
end
766751
end
767752

768753
describe "strip_beam/1" do

lib/mix/test/mix/tasks/compile.app_test.exs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,24 +86,32 @@ defmodule Mix.Tasks.Compile.AppTest do
8686
test "generates .app file with compile_env" do
8787
in_fixture("no_mixfile", fn ->
8888
Mix.Project.push(MixTest.Case.Sample)
89+
File.mkdir_p!("config")
90+
File.write!("config/config.exs", "[]")
91+
Mix.Task.run("loadconfig")
92+
93+
reset_config = fn ->
94+
Mix.ProjectStack.reset_config_mtime()
95+
ensure_touched("config/config.exs", "_build/dev/lib/sample/ebin/sample.app")
96+
end
8997

9098
Mix.ProjectStack.compile_env([{:app, :key, :error}])
9199
assert Mix.Tasks.Compile.App.run([]) == {:ok, []}
92100
assert parse_resource_file(:sample)[:compile_env] == [{:app, :key, :error}]
93101

94102
# No-op with untouched unset compile_env
95-
assert Mix.Tasks.Compile.App.run([]) == {:noop, []}
96-
97-
# No-op with same compile_env
98-
Mix.ProjectStack.compile_env([{:app, :key, :error}])
99-
assert Mix.Tasks.Compile.App.run([]) == {:noop, []}
103+
reset_config.()
104+
assert Mix.Tasks.Compile.App.run([]) == {:ok, []}
105+
assert parse_resource_file(:sample)[:compile_env] == [{:app, :key, :error}]
100106

101107
# Recompiles with new compile_env
108+
reset_config.()
102109
Mix.ProjectStack.compile_env([{:app, :another, :error}])
103110
assert Mix.Tasks.Compile.App.run([]) == {:ok, []}
104111
assert parse_resource_file(:sample)[:compile_env] == [{:app, :another, :error}]
105112

106113
# Keeps compile_env if forcing
114+
reset_config.()
107115
assert Mix.Tasks.Compile.App.run(["--force"]) == {:ok, []}
108116
assert parse_resource_file(:sample)[:compile_env] == [{:app, :another, :error}]
109117
end)

0 commit comments

Comments
 (0)