Use System.services_timeout/0 in Mix.

Use System.services_timeout/0 in Mix.

defmodule Mix.Server do
  @moduledoc false

  alias :ordsets, as: Ordset
  use GenServer.Behaviour

  defrecord Config, tasks: Ordset.new, projects: [], mixfile: [],
    shell: Mix.Shell.IO, scm: Ordset.new, env: nil, post_config: []

  def start_link(env) do
    :gen_server.start_link({ :local, __MODULE__ }, __MODULE__, env, [])
  end

  def call(arg) do
    :gen_server.call(__MODULE__, arg, System.services_timeout)
  end

  def cast(arg) do
    :gen_server.cast(__MODULE__, arg)
  end

  ## Callbacks

  def init(env) do
    { :ok, Config[env: env] }
  end

  def handle_call(:tasks, _from, config) do
    { :reply, config.tasks, config }
  end

  def handle_call(:projects, _from, config) do
    { :reply, config.projects, config }
  end

  def handle_call(:shell, _from, config) do
    { :reply, config.shell, config }
  end

  def handle_call(:scm, _from, config) do
    { :reply, config.scm, config }
  end

  def handle_call(:env, _from, config) do
    { :reply, config.env, config }
  end

  def handle_call(:clear_tasks, _from, config) do
    { :reply, config.tasks, config.tasks(Ordset.new) }
  end

  def handle_call({ :has_task?, task }, _from, config) do
    { :reply, Ordset.is_element(task, config.tasks), config }
  end

  def handle_call(:pop_project, _from, config) do
    case config.projects do
      [{ project, _ }|tail] ->
        { :reply, project, config.projects(tail) }
      _ ->
        { :reply, nil, config }
    end
  end

  def handle_call({ :mixfile_cache, app }, _from, config) do
    { :reply, config.mixfile[app], config }
  end

  def handle_call(request, from, config) do
    super(request, from, config)
  end

  def handle_cast({ :shell, name }, config) do
    { :noreply, config.shell(name) }
  end

  def handle_cast({ :env, env }, config) when is_atom(env) do
    { :noreply, config.env(env) }
  end

  def handle_cast({ :set_tasks, tasks }, config) do
    { :noreply, config.tasks(tasks) }
  end

  def handle_cast({ :add_task, name }, config) do
    { :noreply, config.update_tasks :ordsets.add_element(name, &1) }
  end

  def handle_cast({ :delete_task, name }, config) do
    { :noreply, config.update_tasks :ordsets.del_element(name, &1) }
  end

  def handle_cast({ :push_project, name, project }, config) do
    project = Keyword.merge(project, config.post_config)
    { :noreply, config.post_config([]).update_projects [{ name, project }|&1] }
  end

  def handle_cast({ :post_config, value }, config) do
    { :noreply, config.update_post_config(&1 |> Keyword.merge(value)) }
  end

  def handle_cast({ :add_scm, mod }, config) do
    { :noreply, config.update_scm :ordsets.add_element(mod, &1) }
  end

  def handle_cast({ :mixfile_cache, app, new }, config) do
    { :noreply, config.update_mixfile(&1 |> Keyword.merge([{ app, new }])) }
  end

  def handle_cast(:clear_mixfile_cache, config) do
    { :noreply, config.mixfile([]) }
  end

  def handle_cast(request, config) do
    super(request, config)
  end
end

defmodule Mix.Server do
  @moduledoc false

  alias :ordsets, as: Ordset
  use GenServer.Behaviour

  defrecord Config, tasks: Ordset.new, projects: [], mixfile: [],
    shell: Mix.Shell.IO, scm: Ordset.new, env: nil, post_config: []

  def start_link(env) do
    :gen_server.start_link({ :local, __MODULE__ }, __MODULE__, env, [])
  end

  def call(arg) do
    :gen_server.call(__MODULE__, arg)
  end

  def cast(arg) do
    :gen_server.cast(__MODULE__, arg)
  end

  ## Callbacks

  def init(env) do
    { :ok, Config[env: env] }
  end

  def handle_call(:tasks, _from, config) do
    { :reply, config.tasks, config }
  end

  def handle_call(:projects, _from, config) do
    { :reply, config.projects, config }
  end

  def handle_call(:shell, _from, config) do
    { :reply, config.shell, config }
  end

  def handle_call(:scm, _from, config) do
    { :reply, config.scm, config }
  end

  def handle_call(:env, _from, config) do
    { :reply, config.env, config }
  end

  def handle_call(:clear_tasks, _from, config) do
    { :reply, config.tasks, config.tasks(Ordset.new) }
  end

  def handle_call({ :has_task?, task }, _from, config) do
    { :reply, Ordset.is_element(task, config.tasks), config }
  end

  def handle_call(:pop_project, _from, config) do
    case config.projects do
      [{ project, _ }|tail] ->
        { :reply, project, config.projects(tail) }
      _ ->
        { :reply, nil, config }
    end
  end

  def handle_call({ :mixfile_cache, app }, _from, config) do
    { :reply, config.mixfile[app], config }
  end

  def handle_call(request, from, config) do
    super(request, from, config)
  end

  def handle_cast({ :shell, name }, config) do
    { :noreply, config.shell(name) }
  end

  def handle_cast({ :env, env }, config) when is_atom(env) do
    { :noreply, config.env(env) }
  end

  def handle_cast({ :set_tasks, tasks }, config) do
    { :noreply, config.tasks(tasks) }
  end

  def handle_cast({ :add_task, name }, config) do
    { :noreply, config.update_tasks :ordsets.add_element(name, &1) }
  end

  def handle_cast({ :delete_task, name }, config) do
    { :noreply, config.update_tasks :ordsets.del_element(name, &1) }
  end

  def handle_cast({ :push_project, name, project }, config) do
    project = Keyword.merge(project, config.post_config)
    { :noreply, config.post_config([]).update_projects [{ name, project }|&1] }
  end

  def handle_cast({ :post_config, value }, config) do
    { :noreply, config.update_post_config(&1 |> Keyword.merge(value)) }
  end

  def handle_cast({ :add_scm, mod }, config) do
    { :noreply, config.update_scm :ordsets.add_element(mod, &1) }
  end

  def handle_cast({ :mixfile_cache, app, new }, config) do
    { :noreply, config.update_mixfile(&1 |> Keyword.merge([{ app, new }])) }
  end

  def handle_cast(:clear_mixfile_cache, config) do
    { :noreply, config.mixfile([]) }
  end

  def handle_cast(request, config) do
    super(request, config)
  end
end


will this fix travis errors?

will this fix travis errors?

defmodule DrabTestApp.Shared3Commander do
  @moduledoc false
  use Drab.Commander

  defhandler button_clicked(_socket, _) do
    :ok
  end
end

defmodule DrabTestApp.Shared3Commander do
  @moduledoc false
  use Drab.Commander

  defhandler button_clicked(_socket, _) do
  end
end


Fix the travis check

Fix the travis check

# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

defmodule GenerateDockerfile.AppConfig do
  require Logger

  @default_workspace_dir "/workspace"
  @default_app_yaml_path "app.yaml"
  @default_service_name "default"

  def start_link(args) do
    register_module = Keyword.get(args, :register_module, true)
    opts = if register_module, do: [name: __MODULE__], else: []
    GenServer.start_link(__MODULE__, args, opts)
  end

  def get(key, server \\ __MODULE__) do
    GenServer.call(server, {:get, key})
  end

  def get!(key, server \\ __MODULE__) do
    {:ok, result} = GenServer.call(server, {:get, key})
    result
  end

  def status(server \\ __MODULE__) do
    case GenServer.call(server, {:get, :error}) do
      {:ok, error} -> {:error, error}
      :error -> :ok
    end
  end

  use GenServer

  defmodule MetadataFetcher do
    use Tesla

    plug Tesla.Middleware.Tuples
    plug Tesla.Middleware.BaseUrl, "http://169.254.169.254"
    plug Tesla.Middleware.Headers, %{"Metadata-Flavor" => "Google"}
    plug Tesla.Middleware.Opts, timeout: 100

    def get_project_id do
      "/computeMetadata/v1/project/project-id" |> get() |> handle_response()
    end

    def handle_response({:ok, %{status: 200, body: body}}), do: body
    def handle_response(_), do: nil
  end
  alias GenerateDockerfile.AppConfig.MetadataFetcher

  def init(args) do
    try do
      workspace_dir = Keyword.get(args, :workspace_dir, @default_workspace_dir)
      data = build_data(workspace_dir)
      {:ok, data}
    catch
      {:usage_error, message} -> {:ok, %{error: message}}
    end
  end

  def handle_call({:get, key}, _from, data) do
    {:reply, Map.fetch(data, key), data}
  end

  defp build_data(workspace_dir) do
    project_id = System.get_env("PROJECT_ID") ||
      if System.get_env("CI") == "true" && System.get_env("TRAVIS") == "true" do
        nil
      else
        MetadataFetcher.get_project_id()
      end
    project_id_for_display = project_id || "(unknown)"
    project_id_for_example = project_id || "my-project-id"

    deps_info = analyze_deps(workspace_dir)
    phoenix_prefix = get_phoenix_prefix(deps_info)

    app_yaml_path = System.get_env("GAE_APPLICATION_YAML_PATH") || @default_app_yaml_path
    app_config = load_config(workspace_dir, app_yaml_path)
    runtime_config = Map.get(app_config, "runtime_config") |> ensure_map
    beta_settings = Map.get(app_config, "beta_settings") |> ensure_map

    service_name = Map.get(app_config, "service", @default_service_name)
    env_variables = get_env_variables(app_config)
    install_packages = get_install_packages(runtime_config, app_config)
    cloud_sql_instances = get_cloud_sql_instances(beta_settings)
    entrypoint = get_entrypoint(runtime_config, app_config, phoenix_prefix)
    build_scripts = get_build_scripts(runtime_config, workspace_dir, phoenix_prefix)

    %{
      workspace_dir: workspace_dir,
      project_id: project_id,
      project_id_for_display: project_id_for_display,
      project_id_for_example: project_id_for_example,
      app_yaml_path: app_yaml_path,
      runtime_config: runtime_config,
      service_name: service_name,
      env_variables: env_variables,
      install_packages: install_packages,
      cloud_sql_instances: cloud_sql_instances,
      entrypoint: entrypoint,
      build_scripts: build_scripts
    }
  end

  defp load_config(workspace_dir, app_yaml_path) do
    app_config_path = Path.join(workspace_dir, app_yaml_path)
    unless File.regular?(app_config_path) do
      throw {:usage_error, "Unable to find required `#{app_yaml_path}` file."}
    end
    YamlElixir.read_from_file(app_config_path)
  end

  defp analyze_deps(workspace_dir) do
    mix_lock_path = Path.join(workspace_dir, "mix.lock")
    unless File.regular?(mix_lock_path) do
      throw {:usage_error, "Unable to find required `mix.lock` file."}
    end
    {result, _} = Code.eval_file(mix_lock_path)

    [:phoenix]
    |> Enum.reduce(%{}, fn
      (pkg, acc) ->
        pkg_info = result[pkg]
        if is_tuple(pkg_info) do
          version = elem(pkg_info, 2) |> to_string
          if Version.parse(version) == :error do
            acc
          else
            Logger.info("Detected #{pkg} #{version}")
            Map.put(acc, pkg, version)
          end
        else
          acc
        end
    end)
  end

  defp get_phoenix_prefix(%{phoenix: version}) do
    if Version.compare(version, "1.3.0") == :lt, do: "phoenix", else: "phx"
  end
  defp get_phoenix_prefix(_), do: nil

  defp get_env_variables(app_config) do
    app_config
    |> Map.get("env_variables")
    |> ensure_map
    |> Enum.reduce(%{},
      fn ({k, v}, acc) ->
        k_str = to_string(k)
        v_str = to_string(v)
        unless Regex.match?(~r{\A[a-zA-Z]\w*\z}, k_str) do
          throw {:usage_error, "Illegal environment variable name: `#{k_str}`."}
        end
        Map.put(acc, k_str, v_str)
      end)
  end

  defp get_install_packages(runtime_config, app_config) do
    install_packages =
      runtime_config
      |> Map.get_lazy("packages", fn -> Map.get(app_config, "packages") end)
      |> List.wrap
    Enum.each(install_packages,
      fn pkg ->
        unless Regex.match?(~r{\A[\w.-]+\z}, pkg) do
          throw {:usage_error, "Illegal debian package name: `#{pkg}`."}
        end
      end)
    install_packages
  end

  defp get_cloud_sql_instances(beta_settings) do
    cloud_sql_instances =
      beta_settings
      |> Map.get("cloud_sql_instances")
      |> List.wrap
      |> Enum.flat_map(fn inst -> String.split(inst, ",") end)
    Enum.each(cloud_sql_instances,
      fn name ->
        unless Regex.match?(~r{\A[\w:.-]+\z}, name) do
          throw {:usage_error, "Illegal cloud sql instance name: `#{name}`."}
        end
      end)
    cloud_sql_instances
  end

  defp get_entrypoint(runtime_config, app_config, phoenix_prefix) do
    Map.get_lazy(runtime_config, "entrypoint", fn ->
      Map.get_lazy(app_config, "entrypoint", fn ->
        e = default_entrypoint(phoenix_prefix)
        Logger.warn("No entrypoint specified. Guessing a default: `#{e}`.")
        Logger.warn("To use a different entrypoint, add an `entrypoint` field to your config.")
        e
      end)
    end)
    |> validate_entrypoint
    |> decorate_entrypoint
  end

  defp default_entrypoint(nil), do: "mix run --no-halt"
  defp default_entrypoint(phoenix_prefix), do: "mix #{phoenix_prefix}.server"

  defp validate_entrypoint(entrypoint) when is_list(entrypoint) do
    Enum.map(entrypoint, &validate_entrypoint/1)
  end
  defp validate_entrypoint("") do
    throw {:usage_error, "Entrypoint may not be empty."}
  end
  defp validate_entrypoint(entrypoint) when is_binary(entrypoint) do
    if String.contains?(entrypoint, "\n") do
      throw {:usage_error, "Entrypoint may not contain a newline."}
    end
    entrypoint
  end

  defp decorate_entrypoint(entrypoint) when is_list(entrypoint) do
    Poison.encode!(entrypoint)
  end
  defp decorate_entrypoint(entrypoint) when is_binary(entrypoint) do
    cond do
      String.starts_with?(entrypoint, "exec ") ->
        entrypoint
      Regex.match?(~r{;|&&|\|}, entrypoint) ->
        entrypoint
      true ->
        "exec #{entrypoint}"
    end
  end

  defp get_build_scripts(runtime_config, workspace_dir, phoenix_prefix) do
    runtime_config
    |> Map.get_lazy("build", fn -> default_build_scripts(workspace_dir, phoenix_prefix) end)
    |> List.wrap
    |> validate_build_scripts
  end

  defp default_build_scripts(_workspace_dir, nil), do: []
  defp default_build_scripts(workspace_dir, phoenix_prefix) do
    case find_brunch_configs_relative_dir(workspace_dir) do
      nil -> []
      "." -> ["npm install && node_modules/brunch/bin/brunch build --production && mix #{phoenix_prefix}.digest"]
      relative_dir -> ["cd #{relative_dir} && npm install && node_modules/brunch/bin/brunch build --production && cd .. && mix #{phoenix_prefix}.digest"]
    end
  end

  defp find_brunch_configs_relative_dir(workspace_dir) do
    apps_dir = Path.join(workspace_dir, "apps")
    app_dirs = if File.dir?(apps_dir) do
      apps_dir
      |> File.ls!
      |> Enum.map(fn child -> Path.join(apps_dir, child) end)
      |> Enum.filter(fn path -> File.dir?(path) end)
    else
      []
    end

    [workspace_dir | app_dirs]
    |> Enum.flat_map(fn dir -> [dir, Path.join(dir, "assets")] end)
    |> Enum.find(fn dir ->
      File.regular?(Path.join(dir, "package.json")) && File.regular?(Path.join(dir, "brunch-config.js"))
    end)
    |> case do
      nil -> nil
      ^workspace_dir -> "."
      brunch_dir -> Path.relative_to(brunch_dir, workspace_dir)
    end
  end

  defp validate_build_scripts(scripts) do
    Enum.each(scripts, fn script ->
      if String.contains?(script, "\n") do
        throw {:usage_error, "A build script may not contain a newline."}
      end
    end)
    scripts
  end

  defp ensure_map(value) when is_map(value), do: value
  defp ensure_map(_value), do: %{}
end

# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

defmodule GenerateDockerfile.AppConfig do
  require Logger

  @default_workspace_dir "/workspace"
  @default_app_yaml_path "app.yaml"
  @default_service_name "default"

  def start_link(args) do
    register_module = Keyword.get(args, :register_module, true)
    opts = if register_module, do: [name: __MODULE__], else: []
    GenServer.start_link(__MODULE__, args, opts)
  end

  def get(key, server \\ __MODULE__) do
    GenServer.call(server, {:get, key})
  end

  def get!(key, server \\ __MODULE__) do
    {:ok, result} = GenServer.call(server, {:get, key})
    result
  end

  def status(server \\ __MODULE__) do
    case GenServer.call(server, {:get, :error}) do
      {:ok, error} -> {:error, error}
      :error -> :ok
    end
  end

  use GenServer

  defmodule MetadataFetcher do
    use Tesla

    plug Tesla.Middleware.Tuples
    plug Tesla.Middleware.BaseUrl, "http://169.254.169.254"
    plug Tesla.Middleware.Headers, %{"Metadata-Flavor" => "Google"}
    plug Tesla.Middleware.Opts, timeout: 100

    def get_project_id do
      "/computeMetadata/v1/project/project-id" |> get() |> handle_response()
    end

    def handle_response({:ok, %{status: 200, body: body}}), do: body
    def handle_response(_), do: nil
  end
  alias GenerateDockerfile.AppConfig.MetadataFetcher

  def init(args) do
    try do
      workspace_dir = Keyword.get(args, :workspace_dir, @default_workspace_dir)
      data = build_data(workspace_dir)
      {:ok, data}
    catch
      {:usage_error, message} -> {:ok, %{error: message}}
    end
  end

  def handle_call({:get, key}, _from, data) do
    {:reply, Map.fetch(data, key), data}
  end

  defp build_data(workspace_dir) do
    project_id = System.get_env("PROJECT_ID") ||
      if System.get_env("CI") == "true" && System.get_env("TRAVIS") == "true" do
        MetadataFetcher.get_project_id()
      else
        nil
      end
    project_id_for_display = project_id || "(unknown)"
    project_id_for_example = project_id || "my-project-id"

    deps_info = analyze_deps(workspace_dir)
    phoenix_prefix = get_phoenix_prefix(deps_info)

    app_yaml_path = System.get_env("GAE_APPLICATION_YAML_PATH") || @default_app_yaml_path
    app_config = load_config(workspace_dir, app_yaml_path)
    runtime_config = Map.get(app_config, "runtime_config") |> ensure_map
    beta_settings = Map.get(app_config, "beta_settings") |> ensure_map

    service_name = Map.get(app_config, "service", @default_service_name)
    env_variables = get_env_variables(app_config)
    install_packages = get_install_packages(runtime_config, app_config)
    cloud_sql_instances = get_cloud_sql_instances(beta_settings)
    entrypoint = get_entrypoint(runtime_config, app_config, phoenix_prefix)
    build_scripts = get_build_scripts(runtime_config, workspace_dir, phoenix_prefix)

    %{
      workspace_dir: workspace_dir,
      project_id: project_id,
      project_id_for_display: project_id_for_display,
      project_id_for_example: project_id_for_example,
      app_yaml_path: app_yaml_path,
      runtime_config: runtime_config,
      service_name: service_name,
      env_variables: env_variables,
      install_packages: install_packages,
      cloud_sql_instances: cloud_sql_instances,
      entrypoint: entrypoint,
      build_scripts: build_scripts
    }
  end

  defp load_config(workspace_dir, app_yaml_path) do
    app_config_path = Path.join(workspace_dir, app_yaml_path)
    unless File.regular?(app_config_path) do
      throw {:usage_error, "Unable to find required `#{app_yaml_path}` file."}
    end
    YamlElixir.read_from_file(app_config_path)
  end

  defp analyze_deps(workspace_dir) do
    mix_lock_path = Path.join(workspace_dir, "mix.lock")
    unless File.regular?(mix_lock_path) do
      throw {:usage_error, "Unable to find required `mix.lock` file."}
    end
    {result, _} = Code.eval_file(mix_lock_path)

    [:phoenix]
    |> Enum.reduce(%{}, fn
      (pkg, acc) ->
        pkg_info = result[pkg]
        if is_tuple(pkg_info) do
          version = elem(pkg_info, 2) |> to_string
          if Version.parse(version) == :error do
            acc
          else
            Logger.info("Detected #{pkg} #{version}")
            Map.put(acc, pkg, version)
          end
        else
          acc
        end
    end)
  end

  defp get_phoenix_prefix(%{phoenix: version}) do
    if Version.compare(version, "1.3.0") == :lt, do: "phoenix", else: "phx"
  end
  defp get_phoenix_prefix(_), do: nil

  defp get_env_variables(app_config) do
    app_config
    |> Map.get("env_variables")
    |> ensure_map
    |> Enum.reduce(%{},
      fn ({k, v}, acc) ->
        k_str = to_string(k)
        v_str = to_string(v)
        unless Regex.match?(~r{\A[a-zA-Z]\w*\z}, k_str) do
          throw {:usage_error, "Illegal environment variable name: `#{k_str}`."}
        end
        Map.put(acc, k_str, v_str)
      end)
  end

  defp get_install_packages(runtime_config, app_config) do
    install_packages =
      runtime_config
      |> Map.get_lazy("packages", fn -> Map.get(app_config, "packages") end)
      |> List.wrap
    Enum.each(install_packages,
      fn pkg ->
        unless Regex.match?(~r{\A[\w.-]+\z}, pkg) do
          throw {:usage_error, "Illegal debian package name: `#{pkg}`."}
        end
      end)
    install_packages
  end

  defp get_cloud_sql_instances(beta_settings) do
    cloud_sql_instances =
      beta_settings
      |> Map.get("cloud_sql_instances")
      |> List.wrap
      |> Enum.flat_map(fn inst -> String.split(inst, ",") end)
    Enum.each(cloud_sql_instances,
      fn name ->
        unless Regex.match?(~r{\A[\w:.-]+\z}, name) do
          throw {:usage_error, "Illegal cloud sql instance name: `#{name}`."}
        end
      end)
    cloud_sql_instances
  end

  defp get_entrypoint(runtime_config, app_config, phoenix_prefix) do
    Map.get_lazy(runtime_config, "entrypoint", fn ->
      Map.get_lazy(app_config, "entrypoint", fn ->
        e = default_entrypoint(phoenix_prefix)
        Logger.warn("No entrypoint specified. Guessing a default: `#{e}`.")
        Logger.warn("To use a different entrypoint, add an `entrypoint` field to your config.")
        e
      end)
    end)
    |> validate_entrypoint
    |> decorate_entrypoint
  end

  defp default_entrypoint(nil), do: "mix run --no-halt"
  defp default_entrypoint(phoenix_prefix), do: "mix #{phoenix_prefix}.server"

  defp validate_entrypoint(entrypoint) when is_list(entrypoint) do
    Enum.map(entrypoint, &validate_entrypoint/1)
  end
  defp validate_entrypoint("") do
    throw {:usage_error, "Entrypoint may not be empty."}
  end
  defp validate_entrypoint(entrypoint) when is_binary(entrypoint) do
    if String.contains?(entrypoint, "\n") do
      throw {:usage_error, "Entrypoint may not contain a newline."}
    end
    entrypoint
  end

  defp decorate_entrypoint(entrypoint) when is_list(entrypoint) do
    Poison.encode!(entrypoint)
  end
  defp decorate_entrypoint(entrypoint) when is_binary(entrypoint) do
    cond do
      String.starts_with?(entrypoint, "exec ") ->
        entrypoint
      Regex.match?(~r{;|&&|\|}, entrypoint) ->
        entrypoint
      true ->
        "exec #{entrypoint}"
    end
  end

  defp get_build_scripts(runtime_config, workspace_dir, phoenix_prefix) do
    runtime_config
    |> Map.get_lazy("build", fn -> default_build_scripts(workspace_dir, phoenix_prefix) end)
    |> List.wrap
    |> validate_build_scripts
  end

  defp default_build_scripts(_workspace_dir, nil), do: []
  defp default_build_scripts(workspace_dir, phoenix_prefix) do
    case find_brunch_configs_relative_dir(workspace_dir) do
      nil -> []
      "." -> ["npm install && node_modules/brunch/bin/brunch build --production && mix #{phoenix_prefix}.digest"]
      relative_dir -> ["cd #{relative_dir} && npm install && node_modules/brunch/bin/brunch build --production && cd .. && mix #{phoenix_prefix}.digest"]
    end
  end

  defp find_brunch_configs_relative_dir(workspace_dir) do
    apps_dir = Path.join(workspace_dir, "apps")
    app_dirs = if File.dir?(apps_dir) do
      apps_dir
      |> File.ls!
      |> Enum.map(fn child -> Path.join(apps_dir, child) end)
      |> Enum.filter(fn path -> File.dir?(path) end)
    else
      []
    end

    [workspace_dir | app_dirs]
    |> Enum.flat_map(fn dir -> [dir, Path.join(dir, "assets")] end)
    |> Enum.find(fn dir ->
      File.regular?(Path.join(dir, "package.json")) && File.regular?(Path.join(dir, "brunch-config.js"))
    end)
    |> case do
      nil -> nil
      ^workspace_dir -> "."
      brunch_dir -> Path.relative_to(brunch_dir, workspace_dir)
    end
  end

  defp validate_build_scripts(scripts) do
    Enum.each(scripts, fn script ->
      if String.contains?(script, "\n") do
        throw {:usage_error, "A build script may not contain a newline."}
      end
    end)
    scripts
  end

  defp ensure_map(value) when is_map(value), do: value
  defp ensure_map(_value), do: %{}
end


Improve documentation for ExUnit callbacks (#9785)

defmodule ExUnit.Callbacks do
  @moduledoc ~S"""
  Defines ExUnit callbacks.

  This module defines the `setup/1`, `setup/2`, `setup_all/1`, and
  `setup_all/2` callbacks, as well as the `on_exit/2`, `start_supervised/2`
  and `stop_supervised/1` functions.

  The setup callbacks may be used to define [test fixtures](https://en.wikipedia.org/wiki/Test_fixture#Software)
  and run any initialization code which help bring the system into a known
  state. They are defined via macros and each one can optionally receive a map
  with test state and metadata, usually referred to as the `context`.
  Optionally, the context to be used in the tests can be extended by the
  setup callbacks by returning a properly structured value (see below).

  The `setup_all` callbacks are invoked only once per module, before any
  test is run. All `setup` callbacks are run before each test. No callback
  is run if the test case has no tests or all tests have been filtered out.

  `setup` and `setup_all` callbacks can be defined by a block, by passing
  an atom naming a one-arity function, or by passing a list of such
  atoms. Both can opt to receive the current context by specifying it
  as parameter if defined by a block. Functions used to define a test
  setup must accept the context as single argument.

  A test module can define multiple `setup` and `setup_all` callbacks,
  and they are invoked in order of appearance.

  `start_supervised/2` is used to start processes under a supervisor. The
  supervisor is linked to the current test process. The supervisor as well
  as all child processes are guaranteed to terminate before any `on_exit/2`
  callback runs.

  `on_exit/2` callbacks are registered on demand, usually to undo an action
  performed by a setup callback. `on_exit/2` may also take a reference,
  allowing the callback to be overridden in the future. A registered `on_exit/2`
  callback will always run, while failures in `setup` and `setup_all` will stop
  all remaining setup callbacks from executing.

  Finally, `setup_all` callbacks run in a separate process per module, while
  all `setup` callbacks run in the same process as the test itself. `on_exit/2`
  callbacks always run in a separate process, as implied by their name. The
  test process always exits with reason `:shutdown`, which means any process
  linked to the test process will also exit, although asynchronously. Therefore
  it is preferred to use `start_supervised/2` to guarantee synchronous termination.

  Here is a rundown of the life-cycle of the test process:

    1. the test process is spawned
    2. it runs `setup/2` callbacks
    3. it runs the test itself
    4. it stops all supervised processes
    5. the test process exits with reason `:shutdown`
    6. `on_exit/2` callbacks are executed in a separate process

  ## Context

  If `setup_all` or `setup` return a keyword list, a map, or a tuple in the shape
  of `{:ok, keyword() | map()}`, the keyword list or map will be merged into the
  current context and will be available in all subsequent `setup_all`,
  `setup`, and the `test` itself.

  Returning `:ok` leaves the context unchanged (in `setup` and `setup_all`
  callbacks).

  Returning anything else from `setup_all` will force all tests to fail,
  while a bad response from `setup` causes the current test to fail.

  ## Examples

      defmodule AssertionTest do
        use ExUnit.Case, async: true

        # "setup_all" is called once per module before any test runs
        setup_all do
          IO.puts("Starting AssertionTest")

          # Context is not updated here
          :ok
        end

        # "setup" is called before each test
        setup do
          IO.puts("This is a setup callback for #{inspect(self())}")

          on_exit(fn ->
            IO.puts("This is invoked once the test is done. Process: #{inspect(self())}")
          end)

          # Returns extra metadata to be merged into context.
          # Any of the following would also work:
          #
          #     {:ok, %{hello: "world"}}
          #     {:ok, [hello: "world"]}
          #     %{hello: "world"}
          #
          [hello: "world"]
        end

        # Same as above, but receives the context as argument
        setup context do
          IO.puts("Setting up: #{context.test}")

          # We can simply return :ok when we don't want add any extra metadata
          :ok
        end

        # Setups can also invoke a local or imported function that returns a context
        setup :invoke_local_or_imported_function

        test "always pass" do
          assert true
        end

        test "uses metadata from setup", context do
          assert context[:hello] == "world"
          assert context[:from_named_setup] == true
        end

        defp invoke_local_or_imported_function(context) do
          [from_named_setup: true]
        end
      end

  It is also common to define your setup as a series of functions,
  which are put together by calling `setup` or `setup_all` with a
  list of atoms. Each of these functions receive the context and can
  return any of the values allowed in `setup` blocks:

      defmodule ExampleContextTest do
        use ExUnit.Case

        setup [:step1, :step2, :step3]

        defp step1(_context), do: [step_one: true]
        defp step2(_context), do: {:ok, step_two: true} # return values with shape of {:ok, keyword() | map()} allowed
        defp step3(_context), do: :ok  # Context not modified

        test "context was modified", context do
          assert context[:step_one] == true
          assert context[:step_two] == true
        end
      end

  Finally, as discussed in the `ExUnit.Case` documentation, remember
  that the initial context metadata can also be set via `@tag`s, which
  can then be accessed in the `setup` block:

      defmodule ExampleTagModificationTest do
        use ExUnit.Case

        setup %{login_as: username} do
          {:ok, current_user: username}
        end

        @tag login_as: "max"
        test "tags modify context", context do
          assert context[:login_as] == "max"
          assert context[:current_user] == "max"
        end
      end
  """

  @doc false
  defmacro __using__(_) do
    quote do
      @ex_unit_describe nil
      @ex_unit_setup []
      @ex_unit_setup_all []

      @before_compile unquote(__MODULE__)
      import unquote(__MODULE__)
    end
  end

  @doc false
  defmacro __before_compile__(env) do
    [compile_callbacks(env, :setup), compile_callbacks(env, :setup_all)]
  end

  @doc """
  Defines a callback to be run before each test in a case.

  Accepts a block or the name of a one-arity function in the form of an atom,
  or a list of such atoms.

  Can return values to be merged into the context, to set up the state for
  tests. For more details, see the "Context" section shown above.

  `setup/1` callbacks are executed in the same process as the test process.

  ## Examples

      def clean_up_tmp_directory(context) do
        # perform setup
        :ok
      end

      setup :clean_up_tmp_directory

      setup do
        [conn: Plug.Conn.build_conn()]
      end

  """
  defmacro setup(block) do
    if Keyword.keyword?(block) do
      do_setup(quote(do: _), block)
    else
      quote do
        @ex_unit_setup ExUnit.Callbacks.__callback__(unquote(block), @ex_unit_describe) ++
                         @ex_unit_setup
      end
    end
  end

  @doc """
  Defines a callback to be run before each test in a case.

  Accepts a block or the name of a one-arity function in the form of an atom,
  or a list of such atoms.

  Can return values to be merged into the `context`, to set up the state for
  tests. For more details, see the "Context" section shown above.

  ## Examples

      setup context do
        [conn: Plug.Conn.build_conn()]
      end

  """
  defmacro setup(context, block) do
    do_setup(context, block)
  end

  defp do_setup(context, block) do
    quote bind_quoted: [context: escape(context), block: escape(block)] do
      name = :"__ex_unit_setup_#{length(@ex_unit_setup)}"
      defp unquote(name)(unquote(context)), unquote(block)
      @ex_unit_setup [{name, @ex_unit_describe} | @ex_unit_setup]
    end
  end

  @doc """
  Defines a callback to be run before all tests in a case.

  Accepts a block or the name of a one-arity function in the form of an atom,
  or a list of such atoms.

  Can return values to be merged into the `context`, to set up the state for
  tests. For more details, see the "Context" section shown above.

  `setup_all/1` callbacks are executed in a separate process than tests.
  All `setup_all/1` callbacks are executed in order in the same process.

  ## Examples

      # One-arity function name
      setup_all :clean_up_tmp_directory

      def clean_up_tmp_directory(_context) do
        # perform setup
        :ok
      end

      # Block
      setup_all do
        [conn: Plug.Conn.build_conn()]
      end

  The context returned by `setup_all/1` will be available in all subsequent
  `setup_all`, `setup`, and the `test` itself. For instance, the `conn` from
  the previous example can be accessed as:

      test "fetches current users", %{conn: conn} do
        # ...
      end

  """
  defmacro setup_all(block) do
    if Keyword.keyword?(block) do
      do_setup_all(quote(do: _), block)
    else
      quote do
        @ex_unit_describe &&
          raise "cannot invoke setup_all/1 inside describe as setup_all/1 " <>
                  "always applies to all tests in a module"

        @ex_unit_setup_all ExUnit.Callbacks.__callback__(unquote(block), nil) ++
                             @ex_unit_setup_all
      end
    end
  end

  @doc """
  Defines a callback to be run before all tests in a case.

  Same as `setup_all/1` but also takes a context. See
  the "Context" section in the module documentation.

  ## Examples

      setup_all _context do
        [conn: Plug.Conn.build_conn()]
      end

  """
  defmacro setup_all(context, block) do
    do_setup_all(context, block)
  end

  defp do_setup_all(context, block) do
    quote bind_quoted: [context: escape(context), block: escape(block)] do
      @ex_unit_describe && raise "cannot invoke setup_all/2 inside describe"
      name = :"__ex_unit_setup_all_#{length(@ex_unit_setup_all)}"
      defp unquote(name)(unquote(context)), unquote(block)
      @ex_unit_setup_all [{name, nil} | @ex_unit_setup_all]
    end
  end

  @doc """
  Defines a callback that runs once the test exits.

  `callback` is a function that receives no arguments and
  runs in a separate process than the caller.

  `on_exit/2` is usually called from `setup` and `setup_all`
  callbacks, often to undo the action performed during the setup.
  However, `on_exit/2` may also be called dynamically, where a
  reference can be used to guarantee the callback will be invoked
  only once.

  `on_exit/2` gets executed in a blocking fashion after a test
  exits and **before** running the next test. This means that no
  other test from the same test case will be running while the
  `on_exit/2` callback for a previous test is running.

  `on_exit/2` is executed in a different process than the test
  process.

  ## Examples

      setup do
        File.write!("fixture.json", "{}")
        on_exit(fn -> File.rm!("fixture.json") end)
      end

  """
  @spec on_exit(term, (() -> term)) :: :ok
  def on_exit(name_or_ref \\ make_ref(), callback) when is_function(callback, 0) do
    case ExUnit.OnExitHandler.add(self(), name_or_ref, callback) do
      :ok ->
        :ok

      :error ->
        raise ArgumentError, "on_exit/2 callback can only be invoked from the test process"
    end
  end

  @supervisor_opts [strategy: :one_for_one, max_restarts: 1_000_000, max_seconds: 1]

  @doc """
  Starts a child process under the test supervisor.

  It expects a child specification or a module, similar to the ones
  given to `Supervisor.start_link/2`. For example, if your application
  starts a supervision tree by running:

      Supervisor.start_link([MyServer, {OtherSupervisor, ...}], ...)

  You can start those processes under test in isolation by running:

      start_supervised(MyServer)
      start_supervised({OtherSupervisor, :initial_value})

  A keyword list can also be given if there is a need to change
  the child specification for the given child process:

      start_supervised({MyServer, :initial_value}, restart: :temporary)

  See the `Supervisor` module for a discussion on child specifications
  and the available specification keys.

  The advantage of starting a process under the test supervisor is that
  it is guaranteed to exit before the next test starts. Therefore, you
  don't need to remove the process at the end of your tests via
  `stop_supervised/1`. You only need to use `stop_supervised/1 ` if you
  want to remove a process from the supervision tree in the middle of a
  test, as simply shutting down the process would cause it to be restarted
  according to its `:restart` value.

  This function returns `{:ok, pid}` in case of success, otherwise it
  returns `{:error, reason}`.
  """
  @doc since: "1.5.0"
  @spec start_supervised(Supervisor.child_spec() | module | {module, term}, keyword) ::
          Supervisor.on_start_child()
  def start_supervised(child_spec_or_module, opts \\ []) do
    sup =
      case ExUnit.OnExitHandler.get_supervisor(self()) do
        {:ok, nil} ->
          {:ok, sup} = Supervisor.start_link([], @supervisor_opts)
          ExUnit.OnExitHandler.put_supervisor(self(), sup)
          sup

        {:ok, sup} ->
          sup

        :error ->
          raise ArgumentError, "start_supervised/2 can only be invoked from the test process"
      end

    child_spec = Supervisor.child_spec(child_spec_or_module, opts)

    case Supervisor.start_child(sup, child_spec) do
      {:error, {:already_started, _pid}} ->
        {:error, {:duplicate_child_name, child_spec.id}}

      other ->
        other
    end
  end

  @doc """
  Same as `start_supervised/2` but returns the PID on success and raises if
  not started properly.
  """
  @doc since: "1.6.0"
  @spec start_supervised!(Supervisor.child_spec() | module | {module, term}, keyword) :: pid
  def start_supervised!(child_spec_or_module, opts \\ []) do
    case start_supervised(child_spec_or_module, opts) do
      {:ok, pid} ->
        pid

      {:ok, pid, _info} ->
        pid

      {:error, reason} ->
        raise "failed to start child with the spec #{inspect(child_spec_or_module)}.\n" <>
                "Reason: #{start_supervised_error(reason)}"
    end
  end

  defp start_supervised_error({{:EXIT, reason}, info}) when is_tuple(info),
    do: Exception.format_exit(reason)

  defp start_supervised_error({reason, info}) when is_tuple(info),
    do: Exception.format_exit(reason)

  defp start_supervised_error(reason), do: Exception.format_exit({:start_spec, reason})

  @doc """
  Stops a child process started via `start_supervised/2`.

  This function expects the `id` in the child specification.
  For example:

      {:ok, _} = start_supervised(MyServer)
      :ok = stop_supervised(MyServer)

  It returns `:ok` if there is a supervised process with such
  `id`, `{:error, :not_found}` otherwise.
  """
  @doc since: "1.5.0"
  @spec stop_supervised(id :: term()) :: :ok | {:error, :not_found}
  def stop_supervised(id) do
    case ExUnit.OnExitHandler.get_supervisor(self()) do
      {:ok, nil} ->
        {:error, :not_found}

      {:ok, sup} ->
        with :ok <- Supervisor.terminate_child(sup, id),
             :ok <- Supervisor.delete_child(sup, id),
             do: :ok

      :error ->
        raise ArgumentError, "stop_supervised/1 can only be invoked from the test process"
    end
  end

  @doc """
  Same as `stop_supervised/1` but raises if it cannot be stopped.
  """
  @doc since: "1.10.0"
  @spec stop_supervised!(id :: term()) :: :ok
  def stop_supervised!(id) do
    case stop_supervised(id) do
      :ok ->
        :ok

      {:error, :not_found} ->
        raise "could not stop child ID #{inspect(id)} because it was not found"
    end
  end

  ## Helpers

  @reserved [:case, :file, :line, :test, :async, :registered, :describe]

  @doc false
  def __callback__(callback, describe) do
    for k <- List.wrap(callback) do
      if not is_atom(k) do
        raise ArgumentError,
              "setup/setup_all expect a callback name as an atom or " <>
                "a list of callback names, got: #{inspect(k)}"
      end

      {k, describe}
    end
    |> Enum.reverse()
  end

  @doc false
  def __merge__(mod, context, value) do
    merge(mod, context, value, value)
  end

  defp merge(_mod, context, :ok, _original_value) do
    context
  end

  defp merge(mod, context, {:ok, value}, original_value) do
    merge(mod, context, value, original_value)
  end

  defp merge(mod, _context, %_{}, original_value) do
    raise_merge_failed!(mod, original_value)
  end

  defp merge(mod, context, data, original_value) when is_list(data) do
    merge(mod, context, Map.new(data), original_value)
  end

  defp merge(mod, context, data, _original_value) when is_map(data) do
    context_merge(mod, context, data)
  end

  defp merge(mod, _, _return_value, original_value) do
    raise_merge_failed!(mod, original_value)
  end

  defp context_merge(mod, context, data) do
    Map.merge(context, data, fn
      k, v1, v2 when k in @reserved ->
        if v1 == v2, do: v1, else: raise_merge_reserved!(mod, k, v2)

      _, _, v ->
        v
    end)
  end

  defp raise_merge_failed!(mod, return_value) do
    raise "expected ExUnit callback in #{inspect(mod)} to return :ok | keyword | map, " <>
            "got #{inspect(return_value)} instead"
  end

  defp raise_merge_reserved!(mod, key, value) do
    raise "ExUnit callback in #{inspect(mod)} is trying to set " <>
            "reserved field #{inspect(key)} to #{inspect(value)}"
  end

  defp escape(contents) do
    Macro.escape(contents, unquote: true)
  end

  defp compile_callbacks(env, kind) do
    callbacks = Module.get_attribute(env.module, :"ex_unit_#{kind}") |> Enum.reverse()

    acc =
      case callbacks do
        [] ->
          quote(do: context)

        [h | t] ->
          Enum.reduce(t, compile_merge(h), fn callback_describe, acc ->
            quote do
              context = unquote(acc)
              unquote(compile_merge(callback_describe))
            end
          end)
      end

    quote do
      def __ex_unit__(unquote(kind), context) do
        describe = Map.get(context, :describe, nil)
        unquote(acc)
      end
    end
  end

  defp compile_merge({callback, nil}) do
    quote do
      unquote(__MODULE__).__merge__(__MODULE__, context, unquote(callback)(context))
    end
  end

  defp compile_merge({callback, {_line, describe}}) do
    quote do
      if unquote(describe) == describe do
        unquote(compile_merge({callback, nil}))
      else
        context
      end
    end
  end
end

defmodule ExUnit.Callbacks do
  @moduledoc ~S"""
  Defines ExUnit callbacks.

  This module defines the `setup/1`, `setup/2`, `setup_all/1`, and
  `setup_all/2` callbacks, as well as the `on_exit/2`, `start_supervised/2`
  and `stop_supervised/1` functions.

  The setup callbacks may be used to define [test fixtures](https://en.wikipedia.org/wiki/Test_fixture#Software)
  and run any initialization code which help bring the system into a known
  state. They are defined via macros and each one can optionally receive a map
  with test state and metadata, usually referred to as the `context`.
  Optionally, the context to be used in the tests can be extended by the
  setup callbacks by returning a properly structured value (see below).

  The `setup_all` callbacks are invoked only once per module, before any
  test is run. All `setup` callbacks are run before each test. No callback
  is run if the test case has no tests or all tests have been filtered out.

  `setup` and `setup_all` callbacks can be defined by a block, by passing
  an atom naming a one-arity function, or by passing a list of such
  atoms. Both can opt to receive the current context by specifying it
  as parameter if defined by a block. Functions used to define a test
  setup must accept the context as single argument.

  A test module can define multiple `setup` and `setup_all` callbacks,
  and they are invoked in order of appearance.

  `start_supervised/2` is used to start processes under a supervisor. The
  supervisor is linked to the current test process. The supervisor as well
  as all child processes are guaranteed to terminate before any `on_exit/2`
  callback runs.

  `on_exit/2` callbacks are registered on demand, usually to undo an action
  performed by a setup callback. `on_exit/2` may also take a reference,
  allowing the callback to be overridden in the future. A registered `on_exit/2`
  callback will always run, while failures in `setup` and `setup_all` will stop
  all remaining setup callbacks from executing.

  Finally, `setup_all` callbacks run in a separate process per module, while
  all `setup` callbacks run in the same process as the test itself. `on_exit/2`
  callbacks always run in a separate process, as implied by their name. The
  test process always exits with reason `:shutdown`, which means any process
  linked to the test process will also exit, although asynchronously. Therefore
  it is preferred to use `start_supervised/2` to guarantee synchronous termination.

  Here is a rundown of the life-cycle of the test process:

    1. the test process is spawned
    2. it runs `setup/2` callbacks
    3. it runs the test itself
    4. it stops all supervised processes
    5. the test process exits with reason `:shutdown`
    6. `on_exit/2` callbacks are executed in a separate process

  ## Context

  If `setup_all` or `setup` return a keyword list, a map, or a tuple in the shape
  of `{:ok, keyword() | map()}`, the keyword list or map will be merged into the
  current context and will be available in all subsequent `setup_all`,
  `setup`, and the `test` itself.

  Returning `:ok` leaves the context unchanged (in `setup` and `setup_all`
  callbacks).

  Returning anything else from `setup_all` will force all tests to fail,
  while a bad response from `setup` causes the current test to fail.

  ## Examples

      defmodule AssertionTest do
        use ExUnit.Case, async: true

        # "setup_all" is called once per module before any test runs
        setup_all do
          IO.puts("Starting AssertionTest")

          # Context is not updated here
          :ok
        end

        # "setup" is called before each test
        setup do
          IO.puts("This is a setup callback for #{inspect(self())}")

          on_exit(fn ->
            IO.puts("This is invoked once the test is done. Process: #{inspect(self())}")
          end)

          # Returns extra metadata to be merged into context.
          # Any of the following would also work:
          #
          #     {:ok, %{hello: "world"}}
          #     {:ok, [hello: "world"]}
          #     %{hello: "world"}
          #
          [hello: "world"]
        end

        # Same as above, but receives the context as argument
        setup context do
          IO.puts("Setting up: #{context.test}")

          # We can simply return :ok when we don't want add any extra metadata
          :ok
        end

        # Setups can also invoke a local or imported function that returns a context
        setup :invoke_local_or_imported_function

        test "always pass" do
          assert true
        end

        test "uses metadata from setup", context do
          assert context[:hello] == "world"
          assert context[:from_named_setup] == true
        end

        defp invoke_local_or_imported_function(context) do
          [from_named_setup: true]
        end
      end

  It is also common to define your setup as a series of functions,
  which are put together by calling `setup` or `setup_all` with a
  list of atoms. Each of these functions receive the context and can
  return any of the values allowed in `setup` blocks:

      defmodule ExampleContextTest do
        use ExUnit.Case

        setup [:step1, :step2, :step3]

        defp step1(_context), do: [step_one: true]
        defp step2(_context), do: {:ok, step_two: true} # return values with shape of {:ok, keyword() | map()} allowed
        defp step3(_context), do: :ok  # Context not modified

        test "context was modified", context do
          assert context[:step_one] == true
          assert context[:step_two] == true
        end
      end

  Finally, as discussed in the `ExUnit.Case` documentation, remember
  that the initial context metadata can also be set via `@tag`s, which
  can then be accessed in the `setup` block:

      defmodule ExampleTagModificationTest do
        use ExUnit.Case

        setup %{login_as: username} do
          {:ok, current_user: username}
        end

        @tag login_as: "max"
        test "tags modify context", context do
          assert context[:login_as] == "max"
          assert context[:current_user] == "max"
        end
      end
  """

  @doc false
  defmacro __using__(_) do
    quote do
      @ex_unit_describe nil
      @ex_unit_setup []
      @ex_unit_setup_all []

      @before_compile unquote(__MODULE__)
      import unquote(__MODULE__)
    end
  end

  @doc false
  defmacro __before_compile__(env) do
    [compile_callbacks(env, :setup), compile_callbacks(env, :setup_all)]
  end

  @doc """
  Defines a callback to be run before each test in a case.

  Accepts a block or the name of a one-arity function in the form of an atom,
  or a list of such atoms.

  Can return values to be merged into the context, to set up the state for
  tests. For more details, see the "Context" section shown above.

  ## Examples

      def clean_up_tmp_directory(context) do
        # perform setup
        :ok
      end

      setup :clean_up_tmp_directory

      setup do
        [conn: Plug.Conn.build_conn()]
      end

  """
  defmacro setup(block) do
    if Keyword.keyword?(block) do
      do_setup(quote(do: _), block)
    else
      quote do
        @ex_unit_setup ExUnit.Callbacks.__callback__(unquote(block), @ex_unit_describe) ++
                         @ex_unit_setup
      end
    end
  end

  @doc """
  Defines a callback to be run before each test in a case.

  Accepts a block or the name of a one-arity function in the form of an atom,
  or a list of such atoms.

  Can return values to be merged into the `context`, to set up the state for
  tests. For more details, see the "Context" section shown above.

  ## Examples

      setup context do
        [conn: Plug.Conn.build_conn()]
      end

  """
  defmacro setup(context, block) do
    do_setup(context, block)
  end

  defp do_setup(context, block) do
    quote bind_quoted: [context: escape(context), block: escape(block)] do
      name = :"__ex_unit_setup_#{length(@ex_unit_setup)}"
      defp unquote(name)(unquote(context)), unquote(block)
      @ex_unit_setup [{name, @ex_unit_describe} | @ex_unit_setup]
    end
  end

  @doc """
  Defines a callback to be run before all tests in a case.

  Accepts a block or the name of a one-arity function in the form of an atom,
  or a list of such atoms.

  Can return values to be merged into the `context`, to set up the state for
  tests. For more details, see the "Context" section shown above.

  ## Examples

      # one-arity function name
      setup_all :clean_up_tmp_directory

      def clean_up_tmp_directory(_context) do
        # perform setup
        :ok
      end

      # block
      setup_all do
        [conn: Plug.Conn.build_conn()]
      end

  `setup_all` can return a keyword list, a map, or a tuple in the shape
  of `{:ok, keyword() | map()}`, the keyword list or map will be merged
  into the current context and will be available in all subsequent `setup_all`,
  `setup`, and the `test` itself. For instance, the `conn` from the previous
  example can be accessed as:

      test "fetches current users", %{conn: conn} do
        # ...
      end

  """
  defmacro setup_all(block) do
    if Keyword.keyword?(block) do
      do_setup_all(quote(do: _), block)
    else
      quote do
        @ex_unit_describe &&
          raise "cannot invoke setup_all/1 inside describe as setup_all/1 " <>
                  "always applies to all tests in a module"

        @ex_unit_setup_all ExUnit.Callbacks.__callback__(unquote(block), nil) ++
                             @ex_unit_setup_all
      end
    end
  end

  @doc """
  Defines a callback to be run before all tests in a case.

  Accepts a block or the name of a one-arity function in the form of an atom,
  or a list of such atoms.

  Can return values to be merged into the `context`, to set up the state for
  tests. For more details, see the "Context" section shown above.

  ## Examples

      setup_all context do
        [conn: Plug.Conn.build_conn()]
      end

  """
  defmacro setup_all(context, block) do
    do_setup_all(context, block)
  end

  defp do_setup_all(context, block) do
    quote bind_quoted: [context: escape(context), block: escape(block)] do
      @ex_unit_describe && raise "cannot invoke setup_all/2 inside describe"
      name = :"__ex_unit_setup_all_#{length(@ex_unit_setup_all)}"
      defp unquote(name)(unquote(context)), unquote(block)
      @ex_unit_setup_all [{name, nil} | @ex_unit_setup_all]
    end
  end

  @doc """
  Defines a callback that runs once the test exits.

  `callback` is a function that receives no arguments and
  runs in a separate process than the caller.

  `on_exit/2` is usually called from `setup` and `setup_all`
  callbacks, often to undo the action performed during the setup.
  However, `on_exit/2` may also be called dynamically, where a
  reference can be used to guarantee the callback will be invoked
  only once.
  """
  @spec on_exit(term, (() -> term)) :: :ok
  def on_exit(name_or_ref \\ make_ref(), callback) when is_function(callback, 0) do
    case ExUnit.OnExitHandler.add(self(), name_or_ref, callback) do
      :ok ->
        :ok

      :error ->
        raise ArgumentError, "on_exit/2 callback can only be invoked from the test process"
    end
  end

  @supervisor_opts [strategy: :one_for_one, max_restarts: 1_000_000, max_seconds: 1]

  @doc """
  Starts a child process under the test supervisor.

  It expects a child specification or a module, similar to the ones
  given to `Supervisor.start_link/2`. For example, if your application
  starts a supervision tree by running:

      Supervisor.start_link([MyServer, {OtherSupervisor, ...}], ...)

  You can start those processes under test in isolation by running:

      start_supervised(MyServer)
      start_supervised({OtherSupervisor, :initial_value})

  A keyword list can also be given if there is a need to change
  the child specification for the given child process:

      start_supervised({MyServer, :initial_value}, restart: :temporary)

  See the `Supervisor` module for a discussion on child specifications
  and the available specification keys.

  The advantage of starting a process under the test supervisor is that
  it is guaranteed to exit before the next test starts. Therefore, you
  don't need to remove the process at the end of your tests via
  `stop_supervised/1`. You only need to use `stop_supervised/1 ` if you
  want to remove a process from the supervision tree in the middle of a
  test, as simply shutting down the process would cause it to be restarted
  according to its `:restart` value.

  This function returns `{:ok, pid}` in case of success, otherwise it
  returns `{:error, reason}`.
  """
  @doc since: "1.5.0"
  @spec start_supervised(Supervisor.child_spec() | module | {module, term}, keyword) ::
          Supervisor.on_start_child()
  def start_supervised(child_spec_or_module, opts \\ []) do
    sup =
      case ExUnit.OnExitHandler.get_supervisor(self()) do
        {:ok, nil} ->
          {:ok, sup} = Supervisor.start_link([], @supervisor_opts)
          ExUnit.OnExitHandler.put_supervisor(self(), sup)
          sup

        {:ok, sup} ->
          sup

        :error ->
          raise ArgumentError, "start_supervised/2 can only be invoked from the test process"
      end

    child_spec = Supervisor.child_spec(child_spec_or_module, opts)

    case Supervisor.start_child(sup, child_spec) do
      {:error, {:already_started, _pid}} ->
        {:error, {:duplicate_child_name, child_spec.id}}

      other ->
        other
    end
  end

  @doc """
  Same as `start_supervised/2` but returns the PID on success and raises if
  not started properly.
  """
  @doc since: "1.6.0"
  @spec start_supervised!(Supervisor.child_spec() | module | {module, term}, keyword) :: pid
  def start_supervised!(child_spec_or_module, opts \\ []) do
    case start_supervised(child_spec_or_module, opts) do
      {:ok, pid} ->
        pid

      {:ok, pid, _info} ->
        pid

      {:error, reason} ->
        raise "failed to start child with the spec #{inspect(child_spec_or_module)}.\n" <>
                "Reason: #{start_supervised_error(reason)}"
    end
  end

  defp start_supervised_error({{:EXIT, reason}, info}) when is_tuple(info),
    do: Exception.format_exit(reason)

  defp start_supervised_error({reason, info}) when is_tuple(info),
    do: Exception.format_exit(reason)

  defp start_supervised_error(reason), do: Exception.format_exit({:start_spec, reason})

  @doc """
  Stops a child process started via `start_supervised/2`.

  This function expects the `id` in the child specification.
  For example:

      {:ok, _} = start_supervised(MyServer)
      :ok = stop_supervised(MyServer)

  It returns `:ok` if there is a supervised process with such
  `id`, `{:error, :not_found}` otherwise.
  """
  @doc since: "1.5.0"
  @spec stop_supervised(id :: term()) :: :ok | {:error, :not_found}
  def stop_supervised(id) do
    case ExUnit.OnExitHandler.get_supervisor(self()) do
      {:ok, nil} ->
        {:error, :not_found}

      {:ok, sup} ->
        with :ok <- Supervisor.terminate_child(sup, id),
             :ok <- Supervisor.delete_child(sup, id),
             do: :ok

      :error ->
        raise ArgumentError, "stop_supervised/1 can only be invoked from the test process"
    end
  end

  @doc """
  Same as `stop_supervised/1` but raises if it cannot be stopped.
  """
  @doc since: "1.10.0"
  @spec stop_supervised!(id :: term()) :: :ok
  def stop_supervised!(id) do
    case stop_supervised(id) do
      :ok ->
        :ok

      {:error, :not_found} ->
        raise "could not stop child ID #{inspect(id)} because it was not found"
    end
  end

  ## Helpers

  @reserved [:case, :file, :line, :test, :async, :registered, :describe]

  @doc false
  def __callback__(callback, describe) do
    for k <- List.wrap(callback) do
      if not is_atom(k) do
        raise ArgumentError,
              "setup/setup_all expect a callback name as an atom or " <>
                "a list of callback names, got: #{inspect(k)}"
      end

      {k, describe}
    end
    |> Enum.reverse()
  end

  @doc false
  def __merge__(mod, context, value) do
    merge(mod, context, value, value)
  end

  defp merge(_mod, context, :ok, _original_value) do
    context
  end

  defp merge(mod, context, {:ok, value}, original_value) do
    merge(mod, context, value, original_value)
  end

  defp merge(mod, _context, %_{}, original_value) do
    raise_merge_failed!(mod, original_value)
  end

  defp merge(mod, context, data, original_value) when is_list(data) do
    merge(mod, context, Map.new(data), original_value)
  end

  defp merge(mod, context, data, _original_value) when is_map(data) do
    context_merge(mod, context, data)
  end

  defp merge(mod, _, _return_value, original_value) do
    raise_merge_failed!(mod, original_value)
  end

  defp context_merge(mod, context, data) do
    Map.merge(context, data, fn
      k, v1, v2 when k in @reserved ->
        if v1 == v2, do: v1, else: raise_merge_reserved!(mod, k, v2)

      _, _, v ->
        v
    end)
  end

  defp raise_merge_failed!(mod, return_value) do
    raise "expected ExUnit callback in #{inspect(mod)} to return :ok | keyword | map, " <>
            "got #{inspect(return_value)} instead"
  end

  defp raise_merge_reserved!(mod, key, value) do
    raise "ExUnit callback in #{inspect(mod)} is trying to set " <>
            "reserved field #{inspect(key)} to #{inspect(value)}"
  end

  defp escape(contents) do
    Macro.escape(contents, unquote: true)
  end

  defp compile_callbacks(env, kind) do
    callbacks = Module.get_attribute(env.module, :"ex_unit_#{kind}") |> Enum.reverse()

    acc =
      case callbacks do
        [] ->
          quote(do: context)

        [h | t] ->
          Enum.reduce(t, compile_merge(h), fn callback_describe, acc ->
            quote do
              context = unquote(acc)
              unquote(compile_merge(callback_describe))
            end
          end)
      end

    quote do
      def __ex_unit__(unquote(kind), context) do
        describe = Map.get(context, :describe, nil)
        unquote(acc)
      end
    end
  end

  defp compile_merge({callback, nil}) do
    quote do
      unquote(__MODULE__).__merge__(__MODULE__, context, unquote(callback)(context))
    end
  end

  defp compile_merge({callback, {_line, describe}}) do
    quote do
      if unquote(describe) == describe do
        unquote(compile_merge({callback, nil}))
      else
        context
      end
    end
  end
end


Show plugins in mix credo info

Show plugins in mix credo info

defmodule Credo.CLI.Command.Info.InfoCommand do
  @moduledoc false

  use Credo.CLI.Command,
    short_description: "Show useful debug information",
    cli_switches: Credo.CLI.Command.Suggest.SuggestCommand.cli_switches()

  alias Credo.CLI.Command.Info.InfoOutput
  alias Credo.CLI.Task
  alias Credo.Execution

  def init(exec) do
    Execution.put_pipeline(exec, "info",
      load_and_validate_source_files: [
        {Task.LoadAndValidateSourceFiles, []}
      ],
      prepare_analysis: [
        {Task.PrepareChecksToRun, []}
      ],
      print_info: [
        {__MODULE__.PrintInfo, []}
      ]
    )
  end

  @doc false
  def call(%Execution{help: true} = exec, _opts), do: InfoOutput.print_help(exec)
  def call(exec, _opts), do: Execution.run_pipeline(exec, __MODULE__)

  defmodule PrintInfo do
    def call(exec, _opts \\ []) do
      InfoOutput.print(exec, info(exec))

      exec
    end

    defp info(exec) do
      %{
        "system" => %{
          "credo" => Credo.version(),
          "elixir" => System.version(),
          "erlang" => System.otp_release()
        },
        "config" => %{
          "plugins" => plugins(exec),
          "checks" => checks(exec),
          "files" => files(exec)
        }
      }
    end

    defp plugins(exec) do
      Enum.map(exec.plugins, fn {name, params} ->
        %{"name" => name, "params" => Enum.into(params, %{})}
      end)
    end

    defp checks(exec) do
      {checks, _only_matching, _ignore_matching} = Execution.checks(exec)

      Enum.map(checks, fn {name, params} ->
        %{"name" => name, "params" => Enum.into(params, %{})}
      end)
    end

    defp files(exec) do
      exec
      |> Execution.get_source_files()
      |> Enum.map(& &1.filename)
    end
  end
end

defmodule Credo.CLI.Command.Info.InfoCommand do
  @moduledoc false

  use Credo.CLI.Command,
    short_description: "Show useful debug information",
    cli_switches: Credo.CLI.Command.Suggest.SuggestCommand.cli_switches()

  alias Credo.CLI.Command.Info.InfoOutput
  alias Credo.CLI.Task
  alias Credo.Execution

  def init(exec) do
    Execution.put_pipeline(exec, "info",
      load_and_validate_source_files: [
        {Task.LoadAndValidateSourceFiles, []}
      ],
      prepare_analysis: [
        {Task.PrepareChecksToRun, []}
      ],
      print_info: [
        {__MODULE__.PrintInfo, []}
      ]
    )
  end

  @doc false
  def call(%Execution{help: true} = exec, _opts), do: InfoOutput.print_help(exec)
  def call(exec, _opts), do: Execution.run_pipeline(exec, __MODULE__)

  defmodule PrintInfo do
    def call(exec, _opts \\ []) do
      InfoOutput.print(exec, info(exec))

      exec
    end

    defp info(exec) do
      %{
        "system" => %{
          "credo" => Credo.version(),
          "elixir" => System.version(),
          "erlang" => System.otp_release()
        },
        "config" => %{
          "checks" => checks(exec),
          "files" => files(exec)
        }
      }
    end

    defp checks(exec) do
      {checks, _only_matching, _ignore_matching} = Execution.checks(exec)

      Enum.map(checks, fn {name, _} -> name end)
    end

    defp files(exec) do
      exec
      |> Execution.get_source_files()
      |> Enum.map(& &1.filename)
    end
  end
end


Properly clean spurious entries in waiting list

defmodule Kernel.ParallelCompiler do
  @moduledoc """
  A module responsible for compiling files in parallel.
  """

  @doc """
  Compiles the given files.

  Those files are compiled in parallel and can automatically
  detect dependencies between them. Once a dependency is found,
  the current file stops being compiled until the dependency is
  resolved.

  If there is an error during compilation or if `warnings_as_errors`
  is set to `true` and there is a warning, this function will fail
  with an exception.

  This function receives a set of callbacks as options:

  * `:each_file` - for each file compiled, invokes the callback passing the file
  * `:each_module` - for each module compiled, invokes the callback
                     passing the file, module and the module bytecode

  The compiler doesn't care about the return values of the callbacks.
  Returns the modules generated by each compiled file.
  """
  def files(files, callbacks \\ [])

  def files(files, callbacks) when is_list(callbacks) do
    spawn_compilers(files, nil, callbacks)
  end

  @doc """
  Compiles the given files to the given path.
  Read `files/2` for more information.
  """
  def files_to_path(files, path, callbacks \\ [])

  def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
    spawn_compilers(files, path, callbacks)
  end

  defp spawn_compilers(files, path, callbacks) do
    Code.ensure_loaded(Kernel.ErrorHandler)
    compiler_pid = self()
    :elixir_code_server.cast({ :reset_warnings, compiler_pid })
    schedulers = max(:erlang.system_info(:schedulers_online), 2)

    result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])

    # In case --warning-as-errors is enabled and there was a warning,
    # compilation status will be set to error and we fail with CompileError
    case :elixir_code_server.call({ :compilation_status, compiler_pid }) do
      :ok    -> result
      :error -> raise CompileError, [], []
    end
  end

  # We already have 4 currently running, don't spawn new ones
  defp spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result) when
      length(queued) - length(waiting) >= schedulers do
    wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
  end

  # Release waiting processes
  defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
    { _kind, ^h, ref, _module } = List.keyfind(waiting, h, 1)
    send h, { ref, :ready }
    waiting = List.keydelete(waiting, h, 1)
    spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
  end

  # Spawn a compiler for each file in the list until we reach the limit
  defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
    parent = self()

    { pid, ref } =
      :erlang.spawn_monitor fn ->
        # Notify Code.ensure_compiled/2 that we should
        # attempt to compile the module by doing a dispatch.
        :erlang.put(:elixir_ensure_compiled, true)

        # Set the elixir_compiler_pid used by our custom Kernel.ErrorHandler.
        :erlang.put(:elixir_compiler_pid, parent)
        :erlang.process_flag(:error_handler, Kernel.ErrorHandler)

        exit(try do
          if output do
            :elixir_compiler.file_to_path(h, output)
          else
            :elixir_compiler.file(h)
          end
          { :compiled, h }
        catch
          kind, reason ->
            { :failure, kind, reason, System.stacktrace }
        end)
      end

    spawn_compilers(t, original, output, callbacks, waiting,
                    [{ pid, ref, h }|queued], schedulers, result)
  end

  # No more files, nothing waiting, queue is empty, we are done
  defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
    for { :module, mod } <- result, do: mod
  end

  # Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
  defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
    Enum.each queued, fn { child, _, _ } ->
      { _kind, ^child, ref, _module } = List.keyfind(waiting, child, 1)
      send child, { ref, :release }
    end
    wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
  end

  # No more files, but queue and waiting are not full or do not match
  defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
    wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
  end

  # Wait for messages from child processes
  defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
    receive do
      { :struct_available, module } ->
        available = for { :struct, pid, _, waiting_module } <- waiting,
                        waiting_module == module,
                        do: pid

        spawn_compilers(available ++ files, original, output, callbacks,
                        waiting, queued, schedulers, [{ :struct, module }|result])

      { :module_available, child, ref, file, module, binary } ->
        if callback = Keyword.get(callbacks, :each_module) do
          callback.(file, module, binary)
        end

        # Release the module loader which is waiting for an ack
        send child, { ref, :ack }

        available  = for { _kind, pid, _, waiting_module } <- waiting,
                         waiting_module == module,
                         do: pid

        spawn_compilers(available ++ files, original, output, callbacks,
                        waiting, queued, schedulers, [{ :module, module }|result])

      { :waiting, kind, child, ref, on } ->
        defined = fn { k, m } -> on == m and k in [kind, :module] end

        # Oops, we already got it, do not put it on waiting.
        if :lists.any(defined, result) do
          send child, { ref, :ready }
        else
          waiting = [{ kind, child, ref, on }|waiting]
        end

        spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result)

      { :DOWN, _down_ref, :process, down_pid, { :compiled, file } } ->
        if callback = Keyword.get(callbacks, :each_file) do
          callback.(file)
        end

        # Sometimes we may have spurious entries in the waiting
        # list because someone invoked try/rescue UndefinedFunctionError
        new_files   = List.delete(files, down_pid)
        new_queued  = List.keydelete(queued, down_pid, 0)
        new_waiting = List.keydelete(waiting, down_pid, 1)
        spawn_compilers(new_files, original, output, callbacks, new_waiting, new_queued, schedulers, result)

      { :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
        handle_failure(down_ref, kind, reason, stacktrace, files, waiting, queued)
        wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)

      { :DOWN, down_ref, :process, _down_pid, other } ->
        handle_failure(down_ref, :exit, other, [], files, waiting, queued)
        wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
    end
  end

  defp handle_failure(ref, kind, reason, stacktrace, files, waiting, queued) do
    case List.keyfind(queued, ref, 1) do
      { child, ^ref, file } ->
        if many_missing?(child, files, waiting, queued) do
          IO.puts "== Compilation failed =="
          IO.puts "Compilation failed on the following files:\n"

          Enum.each Enum.reverse(queued), fn { pid, _ref, file } ->
            case List.keyfind(waiting, pid, 1) do
              { _, ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
              _ -> :ok
            end
          end

          IO.puts "\nThe first failure is shown below..."
        end

        IO.puts "== Compilation error on file #{file} =="
        :erlang.raise(kind, reason, stacktrace)
      _ ->
        :ok
    end
  end

  defp many_missing?(child, files, waiting, queued) do
    waiting_length = length(waiting)

    match?({ _, ^child, _, _ }, List.keyfind(waiting, child, 1)) and
      waiting_length > 1 and files == [] and
      waiting_length == length(queued)
  end
end

defmodule Kernel.ParallelCompiler do
  @moduledoc """
  A module responsible for compiling files in parallel.
  """

  @doc """
  Compiles the given files.

  Those files are compiled in parallel and can automatically
  detect dependencies between them. Once a dependency is found,
  the current file stops being compiled until the dependency is
  resolved.

  If there is an error during compilation or if `warnings_as_errors`
  is set to `true` and there is a warning, this function will fail
  with an exception.

  This function receives a set of callbacks as options:

  * `:each_file` - for each file compiled, invokes the callback passing the file
  * `:each_module` - for each module compiled, invokes the callback
                     passing the file, module and the module bytecode

  The compiler doesn't care about the return values of the callbacks.
  Returns the modules generated by each compiled file.
  """
  def files(files, callbacks \\ [])

  def files(files, callbacks) when is_list(callbacks) do
    spawn_compilers(files, nil, callbacks)
  end

  @doc """
  Compiles the given files to the given path.
  Read `files/2` for more information.
  """
  def files_to_path(files, path, callbacks \\ [])

  def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
    spawn_compilers(files, path, callbacks)
  end

  defp spawn_compilers(files, path, callbacks) do
    Code.ensure_loaded(Kernel.ErrorHandler)
    compiler_pid = self()
    :elixir_code_server.cast({ :reset_warnings, compiler_pid })
    schedulers = max(:erlang.system_info(:schedulers_online), 2)

    result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])

    # In case --warning-as-errors is enabled and there was a warning,
    # compilation status will be set to error and we fail with CompileError
    case :elixir_code_server.call({ :compilation_status, compiler_pid }) do
      :ok    -> result
      :error -> raise CompileError, [], []
    end
  end

  # We already have 4 currently running, don't spawn new ones
  defp spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result) when
      length(queued) - length(waiting) >= schedulers do
    wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
  end

  # Release waiting processes
  defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
    { _kind, ^h, ref, _module } = List.keyfind(waiting, h, 1)
    send h, { ref, :ready }
    waiting = List.keydelete(waiting, h, 1)
    spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
  end

  # Spawn a compiler for each file in the list until we reach the limit
  defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
    parent = self()

    { pid, ref } =
      :erlang.spawn_monitor fn ->
        # Notify Code.ensure_compiled/2 that we should
        # attempt to compile the module by doing a dispatch.
        :erlang.put(:elixir_ensure_compiled, true)

        # Set the elixir_compiler_pid used by our custom Kernel.ErrorHandler.
        :erlang.put(:elixir_compiler_pid, parent)
        :erlang.process_flag(:error_handler, Kernel.ErrorHandler)

        exit(try do
          if output do
            :elixir_compiler.file_to_path(h, output)
          else
            :elixir_compiler.file(h)
          end
          { :compiled, h }
        catch
          kind, reason ->
            { :failure, kind, reason, System.stacktrace }
        end)
      end

    spawn_compilers(t, original, output, callbacks, waiting,
                    [{ pid, ref, h }|queued], schedulers, result)
  end

  # No more files, nothing waiting, queue is empty, we are done
  defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
    for { :module, mod } <- result, do: mod
  end

  # Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
  defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
    Enum.each queued, fn { child, _, _ } ->
      { _kind, ^child, ref, _module } = List.keyfind(waiting, child, 1)
      send child, { ref, :release }
    end
    wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
  end

  # No more files, but queue and waiting are not full or do not match
  defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
    wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
  end

  # Wait for messages from child processes
  defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
    receive do
      { :struct_available, module } ->
        available = for { :struct, pid, _, waiting_module } <- waiting,
                        waiting_module == module,
                        do: pid

        spawn_compilers(available ++ files, original, output, callbacks,
                        waiting, queued, schedulers, [{ :struct, module }|result])

      { :module_available, child, ref, file, module, binary } ->
        if callback = Keyword.get(callbacks, :each_module) do
          callback.(file, module, binary)
        end

        # Release the module loader which is waiting for an ack
        send child, { ref, :ack }

        available  = for { _kind, pid, _, waiting_module } <- waiting,
                         waiting_module == module,
                         do: pid

        spawn_compilers(available ++ files, original, output, callbacks,
                        waiting, queued, schedulers, [{ :module, module }|result])

      { :waiting, kind, child, ref, on } ->
        defined = fn { k, m } -> on == m and k in [kind, :module] end

        # Oops, we already got it, do not put it on waiting.
        if :lists.any(defined, result) do
          send child, { ref, :ready }
        else
          waiting = [{ kind, child, ref, on }|waiting]
        end

        spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result)

      { :DOWN, _down_ref, :process, down_pid, { :compiled, file } } ->
        if callback = Keyword.get(callbacks, :each_file) do
          callback.(file)
        end

        # Sometimes we may have spurious entries in the waiting
        # list because someone invoked try/rescue UndefinedFunctionError
        new_queued  = List.keydelete(queued, down_pid, 0)
        new_waiting = List.keydelete(waiting, down_pid, 1)
        spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)

      { :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
        handle_failure(down_ref, kind, reason, stacktrace, files, waiting, queued)
        wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)

      { :DOWN, down_ref, :process, _down_pid, other } ->
        handle_failure(down_ref, :exit, other, [], files, waiting, queued)
        wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
    end
  end

  defp handle_failure(ref, kind, reason, stacktrace, files, waiting, queued) do
    case List.keyfind(queued, ref, 1) do
      { child, ^ref, file } ->
        if many_missing?(child, files, waiting, queued) do
          IO.puts "== Compilation failed =="
          IO.puts "Compilation failed on the following files:\n"

          Enum.each Enum.reverse(queued), fn { pid, _ref, file } ->
            case List.keyfind(waiting, pid, 1) do
              { _, ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
              _ -> :ok
            end
          end

          IO.puts "\nThe first failure is shown below..."
        end

        IO.puts "== Compilation error on file #{file} =="
        :erlang.raise(kind, reason, stacktrace)
      _ ->
        :ok
    end
  end

  defp many_missing?(child, files, waiting, queued) do
    waiting_length = length(waiting)

    match?({ _, ^child, _, _ }, List.keyfind(waiting, child, 1)) and
      waiting_length > 1 and files == [] and
      waiting_length == length(queued)
  end
end


Translate role in appearance panel title

defmodule JumubaseWeb.PerformanceView do
  use JumubaseWeb, :view
  import Ecto.Changeset
  import JumubaseWeb.Internal.ContestView, only: [name_with_flag: 1]
  alias Ecto.Changeset
  alias Jumubase.JumuParams
  alias Jumubase.Foundation
  alias Jumubase.Foundation.{AgeGroups, Contest}
  alias Jumubase.Showtime.Appearance

  @doc """
  Renders JS that powers the registration form.
  """
  def render("scripts.edit.html", assigns) do
    render_registration_script(assigns)
  end

  @doc """
  Returns the path to the contest's registration form, or nil if no contest is given.
  """
  def registration_path(conn, %Contest{} = c) do
    Routes.performance_path(conn, :new, c)
  end

  def registration_path(_conn, nil), do: nil

  @doc """
  Returns text that guides the user to a Kimu registration form with the given path.
  """
  def kimu_link(path) do
    gettext("for Kimu, there’s a %{form_link}",
      form_link: safe_to_string(link(gettext("separate form"), to: path))
    )
    |> raw
  end

  @doc """
  Returns predecessor host options based on the contest, suitable for a performance form.
  """
  def predecessor_host_options(%Contest{round: 2, grouping: grouping}) do
    Foundation.list_hosts_by_grouping(grouping)
    |> Enum.map(&{&1.name, &1.id})
  end

  def predecessor_host_options(%Contest{}), do: []

  def contest_category_options(%Contest{contest_categories: contest_categories}) do
    contest_categories |> Enum.map(&{&1.category.name, &1.id})
  end

  @doc """
  Returns a title for the registration form appearance panel at the given index,
  based on the available appearance and participant data.
  """
  def appearance_panel_title(%Changeset{} = cs, index) do
    fallback_title = gettext("Participant") <> " #{index + 1}"

    case get_field(cs, :appearances, []) |> Enum.at(index) do
      %Appearance{role: role} when not is_nil(role) -> "#{fallback_title} (#{role_title(role)})"
      _ -> fallback_title
    end
  end

  def piece_panel_title(%Changeset{} = cs, index) do
    fallback_title = gettext("Piece") <> " #{index + 1}"

    case get_field(cs, :title) do
      nil ->
        fallback_title

      title ->
        case {get_field(cs, :epoch), get_field(cs, :composer), get_field(cs, :artist)} do
          {"trad", _, _} -> "#{title} (trad.)"
          {_, nil, nil} -> title
          {_, nil, artist} -> "#{title} (#{artist})"
          {_, composer, nil} -> "#{title} (#{composer})"
        end
    end
  end

  def has_composer_fields?(performance_cs, contest, piece_cs) do
    get_field(piece_cs, :epoch) != "trad" and get_genre(performance_cs, contest) != "popular"
  end

  def has_artist_field?(performance_cs, contest, piece_cs) do
    get_field(piece_cs, :epoch) != "trad" and get_genre(performance_cs, contest) == "popular"
  end

  # Private helpers

  defp get_genre(performance_cs, contest) do
    cc_id = get_field(performance_cs, :contest_category_id)

    case Enum.find(contest.contest_categories, &(&1.id == cc_id)) do
      nil -> nil
      cc -> cc.category.genre
    end
  end

  defp render_registration_script(assigns) do
    %{
      conn: conn,
      contest: contest,
      changeset: changeset
    } = assigns

    json =
      render_html_safe_json(%{
        changeset: changeset |> remove_obsolete_associations,
        params: conn.params["performance"],
        contest_category_options:
          for {name, id, type, genre} <- cc_options(contest) do
            %{id: id, name: name, type: type, genre: genre}
          end,
        birthdate_year_options: birthdate_year_options(contest.season),
        birthdate_month_options: birthdate_month_options(),
        role_options: role_options(),
        instrument_options: instrument_options(),
        epoch_options: epoch_options(),
        vocabulary: %{
          participant: gettext("Participant"),
          piece: gettext("Piece"),
          roles: %{
            soloist: gettext("Soloist"),
            ensemblist: gettext("Ensemblist"),
            accompanist: gettext("Accompanist")
          }
        }
      })

    ~E{
      <script src="/js/registration.js"></script>
      <script>registrationForm(<%= raw(json) %>)</script>
    }
  end

  defp role_title("soloist"), do: gettext("Soloist")
  defp role_title("ensemblist"), do: gettext("Ensemblist")
  defp role_title("accompanist"), do: gettext("Accompanist")

  # Excludes nested association changesets bound for deletion or replacement.
  defp remove_obsolete_associations(changeset) do
    changeset
    |> update_change(:appearances, &exclude_obsolete/1)
    |> update_change(:pieces, &exclude_obsolete/1)
  end

  defp exclude_obsolete(changesets) do
    Enum.filter(changesets, &(&1.action in [:insert, :update]))
  end

  defp cc_options(%Contest{} = contest) do
    Foundation.load_contest_categories(contest)
    |> Map.get(:contest_categories)
    |> Enum.map(&{&1.category.name, &1.id, &1.category.type, &1.category.genre})
  end

  defp birthdate_year_options(season) do
    AgeGroups.birthyear_range(season)
  end

  defp birthdate_month_options() do
    Enum.map(localized_months(), fn {ordinal, name} ->
      %{value: Integer.to_string(ordinal), label: name}
    end)
  end

  defp role_options do
    Enum.map(JumuParams.participant_roles(), fn
      role -> %{id: role, label: role_name(role)}
    end)
  end

  defp live_role_options do
    Enum.map(JumuParams.participant_roles(), fn
      role -> {role_name(role), role}
    end)
  end

  defp role_name(role) do
    case role do
      "soloist" -> gettext("Soloist")
      "accompanist" -> gettext("Accompanist")
      "ensemblist" -> gettext("Ensemblist")
    end
  end

  defp instrument_options do
    Jumubase.Showtime.Instruments.all()
    |> Enum.sort_by(fn {_value, label} -> label end)
    |> Enum.map(fn {value, label} -> %{value: value, label: label} end)
  end

  defp live_instrument_options do
    Jumubase.Showtime.Instruments.all()
    |> Enum.sort_by(fn {_value, label} -> label end)
    |> Enum.map(fn {value, label} -> {label, value} end)
  end

  defp epoch_options do
    Enum.map(JumuParams.epochs(), &%{id: &1, label: epoch_label(&1)})
  end

  defp live_epoch_options do
    Enum.map(JumuParams.epochs(), &{epoch_label(&1), &1})
  end

  defp epoch_label("trad" = epoch), do: JumuParams.epoch_description(epoch)
  defp epoch_label(epoch), do: "#{epoch}) " <> JumuParams.epoch_description(epoch)
end

defmodule JumubaseWeb.PerformanceView do
  use JumubaseWeb, :view
  import Ecto.Changeset
  import JumubaseWeb.Internal.ContestView, only: [name_with_flag: 1]
  alias Ecto.Changeset
  alias Jumubase.JumuParams
  alias Jumubase.Foundation
  alias Jumubase.Foundation.{AgeGroups, Contest}
  alias Jumubase.Showtime.Appearance

  @doc """
  Renders JS that powers the registration form.
  """
  def render("scripts.edit.html", assigns) do
    render_registration_script(assigns)
  end

  @doc """
  Returns the path to the contest's registration form, or nil if no contest is given.
  """
  def registration_path(conn, %Contest{} = c) do
    Routes.performance_path(conn, :new, c)
  end

  def registration_path(_conn, nil), do: nil

  @doc """
  Returns text that guides the user to a Kimu registration form with the given path.
  """
  def kimu_link(path) do
    gettext("for Kimu, there’s a %{form_link}",
      form_link: safe_to_string(link(gettext("separate form"), to: path))
    )
    |> raw
  end

  @doc """
  Returns predecessor host options based on the contest, suitable for a performance form.
  """
  def predecessor_host_options(%Contest{round: 2, grouping: grouping}) do
    Foundation.list_hosts_by_grouping(grouping)
    |> Enum.map(&{&1.name, &1.id})
  end

  def predecessor_host_options(%Contest{}), do: []

  def contest_category_options(%Contest{contest_categories: contest_categories}) do
    contest_categories |> Enum.map(&{&1.category.name, &1.id})
  end

  @doc """
  Returns a title for the registration form appearance panel at the given index,
  based on the available appearance and participant data.
  """
  def appearance_panel_title(%Changeset{} = cs, index) do
    fallback_title = gettext("Participant") <> " #{index + 1}"

    case get_field(cs, :appearances, []) |> Enum.at(index) do
      %Appearance{role: role} when not is_nil(role) -> "#{fallback_title} (#{role})"
      _ -> fallback_title
    end
  end

  def piece_panel_title(%Changeset{} = cs, index) do
    fallback_title = gettext("Piece") <> " #{index + 1}"

    case get_field(cs, :title) do
      nil ->
        fallback_title

      title ->
        case {get_field(cs, :epoch), get_field(cs, :composer), get_field(cs, :artist)} do
          {"trad", _, _} -> "#{title} (trad.)"
          {_, nil, nil} -> title
          {_, nil, artist} -> "#{title} (#{artist})"
          {_, composer, nil} -> "#{title} (#{composer})"
        end
    end
  end

  def has_composer_fields?(performance_cs, contest, piece_cs) do
    get_field(piece_cs, :epoch) != "trad" and get_genre(performance_cs, contest) != "popular"
  end

  def has_artist_field?(performance_cs, contest, piece_cs) do
    get_field(piece_cs, :epoch) != "trad" and get_genre(performance_cs, contest) == "popular"
  end

  # Private helpers

  defp get_genre(performance_cs, contest) do
    cc_id = get_field(performance_cs, :contest_category_id)

    case Enum.find(contest.contest_categories, &(&1.id == cc_id)) do
      nil -> nil
      cc -> cc.category.genre
    end
  end

  defp render_registration_script(assigns) do
    %{
      conn: conn,
      contest: contest,
      changeset: changeset
    } = assigns

    json =
      render_html_safe_json(%{
        changeset: changeset |> remove_obsolete_associations,
        params: conn.params["performance"],
        contest_category_options:
          for {name, id, type, genre} <- cc_options(contest) do
            %{id: id, name: name, type: type, genre: genre}
          end,
        birthdate_year_options: birthdate_year_options(contest.season),
        birthdate_month_options: birthdate_month_options(),
        role_options: role_options(),
        instrument_options: instrument_options(),
        epoch_options: epoch_options(),
        vocabulary: %{
          participant: gettext("Participant"),
          piece: gettext("Piece"),
          roles: %{
            soloist: gettext("Soloist"),
            ensemblist: gettext("Ensemblist"),
            accompanist: gettext("Accompanist")
          }
        }
      })

    ~E{
      <script src="/js/registration.js"></script>
      <script>registrationForm(<%= raw(json) %>)</script>
    }
  end

  # Excludes nested association changesets bound for deletion or replacement.
  defp remove_obsolete_associations(changeset) do
    changeset
    |> update_change(:appearances, &exclude_obsolete/1)
    |> update_change(:pieces, &exclude_obsolete/1)
  end

  defp exclude_obsolete(changesets) do
    Enum.filter(changesets, &(&1.action in [:insert, :update]))
  end

  defp cc_options(%Contest{} = contest) do
    Foundation.load_contest_categories(contest)
    |> Map.get(:contest_categories)
    |> Enum.map(&{&1.category.name, &1.id, &1.category.type, &1.category.genre})
  end

  defp birthdate_year_options(season) do
    AgeGroups.birthyear_range(season)
  end

  defp birthdate_month_options() do
    Enum.map(localized_months(), fn {ordinal, name} ->
      %{value: Integer.to_string(ordinal), label: name}
    end)
  end

  defp role_options do
    Enum.map(JumuParams.participant_roles(), fn
      role -> %{id: role, label: role_name(role)}
    end)
  end

  defp live_role_options do
    Enum.map(JumuParams.participant_roles(), fn
      role -> {role_name(role), role}
    end)
  end

  defp role_name(role) do
    case role do
      "soloist" -> gettext("Soloist")
      "accompanist" -> gettext("Accompanist")
      "ensemblist" -> gettext("Ensemblist")
    end
  end

  defp instrument_options do
    Jumubase.Showtime.Instruments.all()
    |> Enum.sort_by(fn {_value, label} -> label end)
    |> Enum.map(fn {value, label} -> %{value: value, label: label} end)
  end

  defp live_instrument_options do
    Jumubase.Showtime.Instruments.all()
    |> Enum.sort_by(fn {_value, label} -> label end)
    |> Enum.map(fn {value, label} -> {label, value} end)
  end

  defp epoch_options do
    Enum.map(JumuParams.epochs(), &%{id: &1, label: epoch_label(&1)})
  end

  defp live_epoch_options do
    Enum.map(JumuParams.epochs(), &{epoch_label(&1), &1})
  end

  defp epoch_label("trad" = epoch), do: JumuParams.epoch_description(epoch)
  defp epoch_label(epoch), do: "#{epoch}) " <> JumuParams.epoch_description(epoch)
end