Repository: phoenixframework/phoenix (elixir)

Issue:
Conditionally generate new Elixir 1.5 child spec
The project generator will check the Elixir version and produce the new format. We will officially continue to support back to Elixir 1.3.

Pull request description:
Conditionally generate child specs based on elixir version
closes #2713 

Patch:
diff --git a/installer/lib/phx_new/generator.ex b/installer/lib/phx_new/generator.ex
index 8b096c479..d838c919b 100644
--- a/installer/lib/phx_new/generator.ex
+++ b/installer/lib/phx_new/generator.ex
@@ -102,6 +102,7 @@ defmodule Phx.New.Generator do
       end
 
     binding = [
+      elixir_version: elixir_version(),
       app_name: project.app,
       app_module: inspect(project.app_mod),
       root_app_name: project.root_app,
@@ -127,11 +128,31 @@ defmodule Phx.New.Generator do
       adapter_module: adapter_module,
       adapter_config: adapter_config,
       generators: nil_if_empty(project.generators ++ adapter_generators(adapter_config)),
-      namespaced?: namespaced?(project)]
+      namespaced?: namespaced?(project),
+    ]
 
     %Project{project | binding: binding}
   end
 
+  def stub_elixir_version(vsn, func) do
+    if configured_version() do
+      raise RuntimeError, "race condition detected when trying to stub elixir version"
+    end
+
+    try do
+      Application.put_env(:phoenix, :installer_elixir_vsn, vsn)
+      func.()
+    after
+      Application.delete_env(:phoenix, :installer_elixir_vsn)
+    end
+  end
+
+  defp elixir_version do
+    configured_version() || System.version()
+  end
+
+  defp configured_version, do: Application.get_env(:phoenix, :installer_elixir_vsn)
+
   defp namespaced?(project) do
     Macro.camelize(project.app) != inspect(project.app_mod)
   end
diff --git a/installer/templates/phx_single/lib/app_name/application.ex b/installer/templates/phx_single/lib/app_name/application.ex
index ee858f9e9..b7f0087f1 100644
--- a/installer/templates/phx_single/lib/app_name/application.ex
+++ b/installer/templates/phx_single/lib/app_name/application.ex
@@ -1,8 +1,10 @@
 defmodule <%= app_module %>.Application do
-  use Application
-
   # See https://hexdocs.pm/elixir/Application.html
   # for more information on OTP Applications
+  @moduledoc false
+
+  use Application
+  <%= if Version.match?(elixir_version, "< 1.5.0") do %>
   def start(_type, _args) do
     import Supervisor.Spec
 
@@ -20,7 +22,23 @@ defmodule <%= app_module %>.Application do
     # for other strategies and supported options
     opts = [strategy: :one_for_one, name: <%= app_module %>.Supervisor]
     Supervisor.start_link(children, opts)
-  end
+  end<% else %>
+  def start(_type, _args) do
+    # List all child processes to be supervised
+    children = [<%= if ecto do %>
+      # Start the Ecto repository
+      <%= app_module %>.Repo,<% end %>
+      # Start the endpoint when the application starts
+      <%= endpoint_module %>,
+      # Starts a worker by calling: <%= app_module %>.Worker.start_link(arg)
+      # {<%= app_module %>.Worker, arg},
+    ]
+
+    # See https://hexdocs.pm/elixir/Supervisor.html
+    # for other strategies and supported options
+    opts = [strategy: :one_for_one, name: <%= app_module %>.Supervisor]
+    Supervisor.start_link(children, opts)
+  end<% end %>
 
   # Tell Phoenix to update the endpoint configuration
   # whenever the application is updated.
diff --git a/installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex b/installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex
index 350e892ff..290d338de 100644
--- a/installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex
+++ b/installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex
@@ -8,16 +8,21 @@ defmodule <%= app_module %>.Application do
   for use in channels, controllers, and elsewhere.
   """
   use Application
+  <%= if Version.match?(elixir_version, "< 1.5.0") do %>
+  def start(_type, _args) do<%= if ecto do %>
+    import Supervisor.Spec
 
-  def start(_type, _args) do
+    Supervisor.start_link([
+      supervisor(<%= app_module %>.Repo, []),
+    ], strategy: :one_for_one, name: <%= app_module %>.Supervisor)<% else %>
     import Supervisor.Spec, warn: false
 
-    <%= if ecto do %>
+    Supervisor.start_link([], strategy: :one_for_one, name: <%= app_module %>.Supervisor)<% end %>
+  end<% else %>
+  def start(_type, _args) do<%= if ecto do %>
     Supervisor.start_link([
-      supervisor(<%= app_module %>.Repo, []),
-    ], strategy: :one_for_one, name: <%= app_module %>.Supervisor)
-    <% else %>
-    Supervisor.start_link([], strategy: :one_for_one, name: <%= app_module %>.Supervisor)
-    <% end %>
-  end
+      <%= app_module %>.Repo,
+    ], strategy: :one_for_one, name: <%= app_module %>.Supervisor)<% else %>
+    Supervisor.start_link([], strategy: :one_for_one, name: <%= app_module %>.Supervisor)<% end %>
+  end<% end %>
 end
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex b/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex
index 986d9dcb7..cc96c84e7 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex
+++ b/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex
@@ -1,6 +1,7 @@
 defmodule <%= web_namespace %>.Application do
+  @moduledoc false
   use Application
-
+  <%= if Version.match?(elixir_version, "< 1.5.0") do %>
   def start(_type, _args) do
     import Supervisor.Spec
 
@@ -16,7 +17,21 @@ defmodule <%= web_namespace %>.Application do
     # for other strategies and supported options
     opts = [strategy: :one_for_one, name: <%= web_namespace %>.Supervisor]
     Supervisor.start_link(children, opts)
-  end
+  end<% else %>
+  def start(_type, _args) do
+    # List all child processes to be supervised
+    children = [
+      # Start the endpoint when the application starts
+      <%= endpoint_module %>,
+      # Starts a worker by calling: <%= web_namespace %>.Worker.start_link(arg)
+      # {<%= web_namespace %>.Worker, arg},
+    ]
+
+    # See https://hexdocs.pm/elixir/Supervisor.html
+    # for other strategies and supported options
+    opts = [strategy: :one_for_one, name: <%= web_namespace %>.Supervisor]
+    Supervisor.start_link(children, opts)
+  end<% end %>
 
   # Tell Phoenix to update the endpoint configuration
   # whenever the application is updated.


Test patch:
diff --git a/installer/test/phx_new_test.exs b/installer/test/phx_new_test.exs
index 28069f34e..58199c943 100644
--- a/installer/test/phx_new_test.exs
+++ b/installer/test/phx_new_test.exs
@@ -1,7 +1,7 @@
 Code.require_file "mix_helper.exs", __DIR__
 
 defmodule Mix.Tasks.Phx.NewTest do
-  use ExUnit.Case
+  use ExUnit.Case, async: false
   import MixHelper
   import ExUnit.CaptureIO
 
@@ -19,6 +19,78 @@ defmodule Mix.Tasks.Phx.NewTest do
     assert_received {:mix_shell, :info, ["Phoenix v" <> _]}
   end
 
+  describe "new umbrella with elixir >= 1.5 child_spec" do
+    test "with defaults", config do
+      in_tmp to_string(config.test), fn ->
+        Phx.New.Generator.stub_elixir_version("1.5.0", fn ->
+          Mix.Tasks.Phx.New.run([@app_name])
+
+          assert_file "phx_blog/lib/phx_blog/application.ex", fn file ->
+            assert file =~ """
+                children = [
+                  # Start the Ecto repository
+                  PhxBlog.Repo,
+                  # Start the endpoint when the application starts
+                  PhxBlogWeb.Endpoint,
+            """
+          end
+        end)
+      end
+    end
+
+    test "without ecto", config do
+      in_tmp to_string(config.test), fn ->
+        Phx.New.Generator.stub_elixir_version("1.5.0", fn ->
+          Mix.Tasks.Phx.New.run([@app_name, "--no-ecto"])
+
+          assert_file "phx_blog/lib/phx_blog/application.ex", fn file ->
+            assert file =~ """
+                children = [
+                  # Start the endpoint when the application starts
+                  PhxBlogWeb.Endpoint,
+            """
+          end
+        end)
+      end
+    end
+  end
+
+  describe "new umbrella with elixir < 1.5 child_spec" do
+    test "with defaults", config do
+      in_tmp to_string(config.test), fn ->
+        Phx.New.Generator.stub_elixir_version("1.3.0", fn ->
+          Mix.Tasks.Phx.New.run([@app_name])
+
+          assert_file "phx_blog/lib/phx_blog/application.ex", fn file ->
+            assert file =~ """
+                children = [
+                  # Start the Ecto repository
+                  supervisor(PhxBlog.Repo, []),
+                  # Start the endpoint when the application starts
+                  supervisor(PhxBlogWeb.Endpoint, []),
+            """
+          end
+        end)
+      end
+    end
+
+    test "without ecto", config do
+      in_tmp to_string(config.test), fn ->
+        Phx.New.Generator.stub_elixir_version("1.3.0", fn ->
+          Mix.Tasks.Phx.New.run([@app_name, "--no-ecto"])
+
+          assert_file "phx_blog/lib/phx_blog/application.ex", fn file ->
+            assert file =~ """
+                children = [
+                  # Start the endpoint when the application starts
+                  supervisor(PhxBlogWeb.Endpoint, []),
+            """
+          end
+        end)
+      end
+    end
+  end
+
   test "new with defaults" do
     in_tmp "new with defaults", fn ->
       Mix.Tasks.Phx.New.run([@app_name])
diff --git a/installer/test/phx_new_umbrella_test.exs b/installer/test/phx_new_umbrella_test.exs
index fd7d8f940..e66508f4f 100644
--- a/installer/test/phx_new_umbrella_test.exs
+++ b/installer/test/phx_new_umbrella_test.exs
@@ -1,7 +1,7 @@
 Code.require_file "mix_helper.exs", __DIR__
 
 defmodule Mix.Tasks.Phx.New.UmbrellaTest do
-  use ExUnit.Case, async: true
+  use ExUnit.Case, async: false
   import MixHelper
 
   @app "phx_umb"
@@ -29,6 +29,106 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
     Path.join(["#{app}_umbrella/apps/#{app}_web", path])
   end
 
+  describe "new umbrella with elixir >= 1.5 child_spec" do
+    test "with defaults", config do
+      in_tmp to_string(config.test), fn ->
+        Phx.New.Generator.stub_elixir_version("1.5.0", fn ->
+          Mix.Tasks.Phx.New.run([@app, "--umbrella"])
+
+          assert_file app_path(@app, "lib/#{@app}/application.ex"), fn file ->
+            assert file =~ """
+              def start(_type, _args) do
+                Supervisor.start_link([
+                  PhxUmb.Repo,
+                ], strategy: :one_for_one, name: PhxUmb.Supervisor)
+              end
+            """
+          end
+
+          assert_file web_path(@app, "lib/#{@app}_web/application.ex"), fn file ->
+            assert file =~ """
+                children = [
+                  # Start the endpoint when the application starts
+                  PhxUmbWeb.Endpoint,
+                  # Starts a worker by calling: PhxUmbWeb.Worker.start_link(arg)
+                  # {PhxUmbWeb.Worker, arg},
+                ]
+            """
+          end
+        end)
+      end
+    end
+
+    test "without ecto", config do
+      in_tmp to_string(config.test), fn ->
+        Phx.New.Generator.stub_elixir_version("1.5.0", fn ->
+          Mix.Tasks.Phx.New.run([@app, "--umbrella", "--no-ecto"])
+
+          assert_file app_path(@app, "lib/#{@app}/application.ex"), fn file ->
+            refute file =~ "children ="
+            assert file =~ """
+              def start(_type, _args) do
+                Supervisor.start_link([], strategy: :one_for_one, name: PhxUmb.Supervisor)
+              end
+            """
+          end
+        end)
+      end
+    end
+  end
+
+  describe "new umbrella with elixir < 1.5" do
+    test "with defaults", config do
+      in_tmp to_string(config.test), fn ->
+        Phx.New.Generator.stub_elixir_version("1.3.0", fn ->
+          Mix.Tasks.Phx.New.run([@app, "--umbrella"])
+
+          assert_file app_path(@app, "lib/#{@app}/application.ex"), fn file ->
+            assert file =~ """
+              def start(_type, _args) do
+                import Supervisor.Spec
+
+                Supervisor.start_link([
+                  supervisor(PhxUmb.Repo, []),
+                ], strategy: :one_for_one, name: PhxUmb.Supervisor)
+              end
+            """
+          end
+
+          assert_file web_path(@app, "lib/#{@app}_web/application.ex"), fn file ->
+            assert file =~ """
+                children = [
+                  # Start the endpoint when the application starts
+                  supervisor(PhxUmbWeb.Endpoint, []),
+                  # Start your own worker by calling: PhxUmbWeb.Worker.start_link(arg1, arg2, arg3)
+                  # worker(PhxUmbWeb.Worker, [arg1, arg2, arg3]),
+                ]
+            """
+          end
+        end)
+      end
+    end
+
+    test "without ecto", config do
+      in_tmp to_string(config.test), fn ->
+        Phx.New.Generator.stub_elixir_version("1.3.0", fn ->
+          Mix.Tasks.Phx.New.run([@app, "--umbrella", "--no-ecto"])
+
+          assert_file app_path(@app, "lib/#{@app}/application.ex"), fn file ->
+            refute file =~ "children ="
+            assert file =~ """
+              def start(_type, _args) do
+                import Supervisor.Spec, warn: false
+
+                Supervisor.start_link([], strategy: :one_for_one, name: PhxUmb.Supervisor)
+              end
+            """
+          end
+        end)
+      end
+    end
+  end
+
   test "new with umbrella and defaults" do
     in_tmp "new with umbrella and defaults", fn ->
       Mix.Tasks.Phx.New.run([@app, "--umbrella"])
@@ -73,7 +173,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       end
 
       assert_file app_path(@app, "lib/#{@app}/application.ex"), ~r/defmodule PhxUmb.Application do/
-      assert_file app_path(@app, "lib/#{@app}/application.ex"), ~r/supervisor\(PhxUmb.Repo, \[\]\)/
+      assert_file app_path(@app, "lib/#{@app}/application.ex"), ~r/PhxUmb.Repo/
       assert_file app_path(@app, "lib/#{@app}.ex"), ~r/defmodule PhxUmb do/
       assert_file app_path(@app, "mix.exs"), ~r/mod: {PhxUmb.Application, \[\]}/
       assert_file app_path(@app, "test/test_helper.exs")