diff --git a/installer/lib/mix/tasks/phx.new.ex b/installer/lib/mix/tasks/phx.new.ex index c50ddb64d2..8047a9564f 100644 --- a/installer/lib/mix/tasks/phx.new.ex +++ b/installer/lib/mix/tasks/phx.new.ex @@ -115,6 +115,34 @@ defmodule Mix.Tasks.Phx.New do You can read more about umbrella projects using the official [Elixir guide](https://hexdocs.pm/elixir/dependencies-and-umbrella-projects.html#umbrella-projects) + + ## `PHX_NEW_CACHE_DIR` + + In rare cases, it may be useful to copy the build from a previously + cached build. To do this, set the `PHX_NEW_CACHE_DIR` environment + variable before running `mix phx.new`. For example, you could generate a + cache by running: + + ```shell + mix phx.new mycache --no-install && cd mycache \ + && mix deps.get && mix deps.compile && mix assets.setup \ + && rm -rf assets config lib priv test mix.exs README.md + ``` + + Your cached build directory should contain: + + _build + deps + mix.lock + + Then you could run: + + ```shell + PHX_NEW_CACHE_DIR=/path/to/mycache mix phx.new myapp + ``` + + The entire cache directory will be copied to the new project, replacing + any existing files where conflicts exist. """ use Mix.Task alias Phx.New.{Generator, Project, Single, Umbrella, Web, Ecto} @@ -211,7 +239,8 @@ defmodule Mix.Tasks.Phx.New do |> Generator.put_binding() |> validate_project(path) |> generator.generate() - |> prompt_to_install_deps(generator, path) + |> maybe_copy_cached_build(path) + |> maybe_prompt_to_install_deps(generator, path) end defp validate_project(%Project{opts: opts} = project, path) do @@ -223,6 +252,15 @@ defmodule Mix.Tasks.Phx.New do project end + defp maybe_prompt_to_install_deps(%Project{} = project, generator, path_key) do + # we can skip the install deps setup, even with --install, because we already copied deps + if project.cached_build_path do + project + else + prompt_to_install_deps(project, generator, path_key) + end + end + defp prompt_to_install_deps(%Project{} = project, generator, path_key) do path = Map.fetch!(project, path_key) @@ -431,6 +469,26 @@ defmodule Mix.Tasks.Phx.New do end end + defp maybe_copy_cached_build(%Project{} = project, path_key) do + project_path = Map.fetch!(project, path_key) + + case System.fetch_env("PHX_NEW_CACHE_DIR") do + {:ok, cache_dir} -> + copy_cached_build(%{project_path: project_path, cache_dir: cache_dir}) + %Project{project | cached_build_path: cache_dir} + + :error -> + project + end + end + + defp copy_cached_build(%{project_path: project_path, cache_dir: cache_dir}) do + if File.exists?(cache_dir) do + Mix.shell().info("Copying cached build from #{cache_dir}") + System.cmd("cp", ["-Rp", Path.join(cache_dir, "."), project_path]) + end + end + defp maybe_warn_outdated(latest_version) do if Version.compare(@version, latest_version) == :lt do Mix.shell().info([ diff --git a/installer/lib/phx_new/project.ex b/installer/lib/phx_new/project.ex index da093e465a..5bc2ebcde6 100644 --- a/installer/lib/phx_new/project.ex +++ b/installer/lib/phx_new/project.ex @@ -16,6 +16,7 @@ defmodule Phx.New.Project do opts: :unset, in_umbrella?: false, binding: [], + cached_build_path: nil, generators: [timestamp_type: :utc_datetime] def new(project_path, opts) do diff --git a/installer/test/phx_new_test.exs b/installer/test/phx_new_test.exs index 73487e14e7..d5c01e076c 100644 --- a/installer/test/phx_new_test.exs +++ b/installer/test/phx_new_test.exs @@ -829,4 +829,43 @@ defmodule Mix.Tasks.Phx.NewTest do end) end) end + + describe "PHX_NEW_CACHE_DIR" do + @phx_new_cache_dir System.get_env("PHX_NEW_CACHE_DIR") + test "new with PHX_NEW_CACHE_DIR" do + System.put_env("PHX_NEW_CACHE_DIR", __DIR__) + cache_files = File.ls!(__DIR__) + in_tmp("new with cache dir", fn -> + Mix.Tasks.Phx.New.run([@app_name]) + project_files = File.ls!(Path.join(File.cwd!(), @app_name)) + assert "mix.exs" in project_files + for file <- cache_files do + assert file in project_files, "#{file} not copied to new project" + end + end) + after + if @phx_new_cache_dir do + System.put_env("PHX_NEW_CACHE_DIR", @phx_new_cache_dir) + else + System.delete_env("PHX_NEW_CACHE_DIR") + end + end + + test "new with PHX_NEW_CACHE_DIR that doesn't exist" do + cache_dir = Path.join(__DIR__, "does-not-exist") + System.put_env("PHX_NEW_CACHE_DIR", cache_dir) + refute File.exists?(cache_dir) + in_tmp("new with cache dir", fn -> + Mix.Tasks.Phx.New.run([@app_name]) + project_files = File.ls!(Path.join(File.cwd!(), @app_name)) + assert "mix.exs" in project_files + end) + after + if @phx_new_cache_dir do + System.put_env("PHX_NEW_CACHE_DIR", @phx_new_cache_dir) + else + System.delete_env("PHX_NEW_CACHE_DIR") + end + end + end end