From 6ea3ea0566162092629f4281c3625176014dac25 Mon Sep 17 00:00:00 2001 From: tywhisky Date: Sat, 3 Aug 2024 23:49:40 +0800 Subject: [PATCH 1/2] Add Incremental compilation for `Mix.install/2` --- lib/mix/lib/mix.ex | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/lib/mix/lib/mix.ex b/lib/mix/lib/mix.ex index 207496ab7ce..dda485978f7 100644 --- a/lib/mix/lib/mix.ex +++ b/lib/mix/lib/mix.ex @@ -860,11 +860,9 @@ defmodule Mix do consolidate_protocols? = Keyword.fetch!(opts, :consolidate_protocols) start_applications? = Keyword.fetch!(opts, :start_applications) - id = - {deps, config, system_env, consolidate_protocols?} - |> :erlang.term_to_binary() - |> :erlang.md5() - |> Base.encode16(case: :lower) + id_without_deps = generate_id({config, system_env, consolidate_protocols?}) + deps_id = generate_id({deps}) + id = id_without_deps <> deps_id force? = System.get_env("MIX_INSTALL_FORCE") in ["1", "true"] or Keyword.fetch!(opts, :force) @@ -874,6 +872,7 @@ defmodule Mix do System.put_env(system_env) install_project_dir = install_project_dir(id) + latest_cache_dir = latest_cache_dir(id_without_deps) if Keyword.fetch!(opts, :verbose) do Mix.shell().info("Mix.install/2 using #{install_project_dir}") @@ -899,7 +898,7 @@ defmodule Mix do try do first_build? = not File.dir?(build_dir) - restore_dir = System.get_env("MIX_INSTALL_RESTORE_PROJECT_DIR") + restore_dir = System.get_env("MIX_INSTALL_RESTORE_PROJECT_DIR") || latest_cache_dir if first_build? and restore_dir != nil and not force? do File.cp_r(restore_dir, install_project_dir) @@ -1005,6 +1004,13 @@ defmodule Mix do Path.join(app_dir, relative_path) end + defp generate_id(info) do + info + |> :erlang.term_to_binary() + |> :erlang.md5() + |> Base.encode16(case: :lower) + end + defp remove_leftover_deps(install_project_dir) do build_lib_dir = Path.join([install_project_dir, "_build", "dev", "lib"]) @@ -1033,13 +1039,34 @@ defmodule Mix do File.rm_rf(dep_path) end - defp install_project_dir(cache_id) do + defp install_project_dir(cache_id), do: Path.join([version_dir(), cache_id]) + + defp latest_cache_dir(id_without_deps) do + version_dir = version_dir() + + with {:ok, entries} <- File.ls(version_dir) do + entries + |> Enum.filter(&String.starts_with?(&1, id_without_deps)) + |> Enum.map(&Path.join(version_dir, &1)) + |> Enum.map(&{&1, File.stat!(&1).mtime}) + |> Enum.max_by(&elem(&1, 1), fn -> nil end) + |> case do + {latest_dir, _mtime} -> latest_dir + nil -> nil + end + else + _ -> nil + end + end + + defp version_dir() do install_root = System.get_env("MIX_INSTALL_DIR") || Path.join(Mix.Utils.mix_cache(), "installs") version = "elixir-#{System.version()}-erts-#{:erlang.system_info(:version)}" - Path.join([install_root, version, cache_id]) + + Path.join([install_root, version]) end defp install_project_config(dynamic_config) do From 0638b7bfc20462f98ad31b2ff46c1bcf2fea41b7 Mon Sep 17 00:00:00 2001 From: tywhisky Date: Sat, 3 Aug 2024 23:50:18 +0800 Subject: [PATCH 2/2] Update lib/mix/test/mix_test.exs --- lib/mix/test/mix_test.exs | 63 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/lib/mix/test/mix_test.exs b/lib/mix/test/mix_test.exs index 05384c7aad7..3fc8d331d07 100644 --- a/lib/mix/test/mix_test.exs +++ b/lib/mix/test/mix_test.exs @@ -326,7 +326,68 @@ defmodule MixTest do end end - test "using restore dir", %{tmp_dir: tmp_dir} do + test "incremental compilation by latest version", %{tmp_dir: tmp_dir} do + with_cleanup(fn -> + Mix.install([ + {:git_repo, git: fixture_path("git_repo")} + ]) + + assert_received {:mix_shell, :info, ["* Getting git_repo " <> _]} + assert_received {:mix_shell, :info, ["==> git_repo"]} + assert_received {:mix_shell, :info, ["Compiling 1 file (.ex)"]} + assert_received {:mix_shell, :info, ["Generated git_repo app"]} + refute_received _ + + install_project_dir = Mix.install_project_dir() + build_lib_path = Path.join([install_project_dir, "_build", "dev", "lib"]) + deps_path = Path.join([install_project_dir, "deps"]) + + assert File.ls!(build_lib_path) |> Enum.sort() == ["git_repo", "mix_install"] + assert File.ls!(deps_path) == ["git_repo"] + end) + + # Adding a dependency + + with_cleanup(fn -> + Mix.install([ + {:git_repo, git: fixture_path("git_repo")}, + {:install_test, path: Path.join(tmp_dir, "install_test")} + ]) + + assert_received {:mix_shell, :info, ["==> install_test"]} + assert_received {:mix_shell, :info, ["Compiling 2 files (.ex)"]} + assert_received {:mix_shell, :info, ["Generated install_test app"]} + refute_received _ + + install_project_dir = Mix.install_project_dir() + build_lib_path = Path.join([install_project_dir, "_build", "dev", "lib"]) + deps_path = Path.join([install_project_dir, "deps"]) + + assert File.ls!(build_lib_path) |> Enum.sort() == + ["git_repo", "install_test", "mix_install"] + + assert File.ls!(deps_path) == ["git_repo"] + end) + + # Removing a dependency + + with_cleanup(fn -> + Mix.install([ + {:install_test, path: Path.join(tmp_dir, "install_test")} + ]) + + refute_received _ + + install_project_dir = Mix.install_project_dir() + build_lib_path = Path.join([install_project_dir, "_build", "dev", "lib"]) + deps_path = Path.join([install_project_dir, "deps"]) + + assert File.ls!(build_lib_path) |> Enum.sort() == ["install_test", "mix_install"] + assert File.ls!(deps_path) == [] + end) + end + + test "using restore dir by export environment variable", %{tmp_dir: tmp_dir} do with_cleanup(fn -> Mix.install([ {:git_repo, git: fixture_path("git_repo")}