Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support PHX_NEW_CACHE_DIR for cached builds #6104

Merged
merged 6 commits into from
Feb 20, 2025
Merged
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
60 changes: 59 additions & 1 deletion installer/lib/mix/tasks/phx.new.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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([
Expand Down
1 change: 1 addition & 0 deletions installer/lib/phx_new/project.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 39 additions & 0 deletions installer/test/phx_new_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading