From e73e05cd22acc603748db25399d861dec0c65450 Mon Sep 17 00:00:00 2001 From: AlikSend Date: Wed, 28 Mar 2018 02:18:47 +0300 Subject: [PATCH] Created separated file for worker - Added support for umbrella applications. - Fixed warnings --- README.md | 44 +++++++++++------- lib/remix.ex | 83 +++------------------------------- lib/remix/worker.ex | 106 ++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 24 +++++----- 4 files changed, 154 insertions(+), 103 deletions(-) create mode 100644 lib/remix/worker.ex diff --git a/README.md b/README.md index 424bc30..0011617 100644 --- a/README.md +++ b/README.md @@ -14,27 +14,36 @@ defp deps do end ``` -Add add `:remix` as a development only OTP app. - +Available configuration options with default values: ```elixir +config :remix, + poll_and_reload_interval: 3000 # files watching interval + escript: false, # escript compilation + silent: false, # silent mode (won't output to iex each time it compiles) + projects_paths: ["lib"] # paths to watch (for classic project; you can read about umbrella projects in corresponding section) + additional_paths: [] # additional paths to watch (useful for umbrella projects) +``` -def application do - [applications: applications(Mix.env)] -end - -defp applications(:dev), do: applications(:all) ++ [:remix] -defp applications(_all), do: [:logger] +## Difference for umbrella projects -``` +Default `projects_paths` for umbrella projects will contains `lib` directories of all your projects. -with escript compilation (in config.exs) and -silent mode (won't output to iex each time it compiles): +For umbrella projects you must add `remix:true` to project config of apps where you want to use remix: ```elixir -config :remix, - escript: true, - silent: true +use Mix.Project + +def project do + [ + # ... + remix: Mix.env == :dev + ] +end ``` -If these vars are not set, it will default to verbose (silent: false) and no escript compilation (escript: false). + +Also because dependencies specified in umbrella's `mix.exs` isn't started with your applications, +it is recommended to create `my_remix` app in your apps directory and add `:remix` to dependencies there. +You don't need any configuration or code in this app, it is needed only to start `:remix` for your umbrella project. +For now it is only solution, but I created [question on forum](https://elixirforum.com/t/mix-umbrella-apps-not-started/13359). ## Usage @@ -44,6 +53,11 @@ Save or create a new file in the lib directory. Thats it! Co-authored by the Agilion team during a Brown Bag Beers learning session as an exploration into Elixir, OTP, and recursion. +### Changes in fork + +- Added support for umbrella applications. +- Fixed warnings and tested on elixir 1.6.4 + ## License Remix source code is released under the Apache 2 License. Check LICENSE file for more information. diff --git a/lib/remix.ex b/lib/remix.ex index 3915d2d..cfc1259 100644 --- a/lib/remix.ex +++ b/lib/remix.ex @@ -1,87 +1,14 @@ defmodule Remix do use Application - # See http://elixir-lang.org/docs/stable/elixir/Application.html - # for more information on OTP Applications def start(_type, _args) do - import Supervisor.Spec, warn: false - children = [ - # Define workers and child supervisors to be supervised - worker(Remix.Worker, []) + %{ + id: Remix.Worker, + start: { Remix.Worker, :start_link, [] } + }, ] - # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html - # for other strategies and supported options - opts = [strategy: :one_for_one, name: Remix.Supervisor] - Supervisor.start_link(children, opts) - end - - defmodule Worker do - use GenServer - - def start_link do - Process.send_after(__MODULE__, :poll_and_reload, 10000) - GenServer.start_link(__MODULE__, %{}, name: Remix.Worker) - end - - def handle_info(:poll_and_reload, state) do - paths = Application.get_all_env(:remix)[:paths] - - new_state = Map.new paths, fn (path) -> - current_mtime = get_current_mtime path - last_mtime = case Map.fetch(state, path) do - {:ok, val} -> val - :error -> nil - end - handle_path path, current_mtime, last_mtime - end - - Process.send_after(__MODULE__, :poll_and_reload, 1000) - {:noreply, new_state} - end - - def handle_path(path, current_mtime, current_mtime), do: {path, current_mtime} - def handle_path(path, current_mtime, _) do - comp_elixir = fn -> Mix.Tasks.Compile.Elixir.run(["--ignore-module-conflict"]) end - comp_escript = fn -> Mix.Tasks.Escript.Build.run([]) end - - case Application.get_all_env(:remix)[:silent] do - true -> - ExUnit.CaptureIO.capture_io(comp_elixir) - if Application.get_all_env(:remix)[:escript] == true do - ExUnit.CaptureIO.capture_io(comp_escript) - end - - _ -> - comp_elixir.() - if Application.get_all_env(:remix)[:escript] == true do - comp_escript.() - end - end - {path, current_mtime} - end - - def get_current_mtime(dir) do - case File.ls(dir) do - {:ok, files} -> get_current_mtime(files, [], dir) - _ -> nil - end - end - - def get_current_mtime([], mtimes, _cwd) do - mtimes - |> Enum.sort - |> Enum.reverse - |> List.first - end - - def get_current_mtime([h | tail], mtimes, cwd) do - mtime = case File.dir?("#{cwd}/#{h}") do - true -> get_current_mtime("#{cwd}/#{h}") - false -> File.stat!("#{cwd}/#{h}").mtime - end - get_current_mtime tail, [mtime | mtimes], cwd - end + Supervisor.start_link(children, strategy: :one_for_one, name: Remix.Supervisor) end end diff --git a/lib/remix/worker.ex b/lib/remix/worker.ex new file mode 100644 index 0000000..8ed9cc1 --- /dev/null +++ b/lib/remix/worker.ex @@ -0,0 +1,106 @@ +defmodule Remix.Worker do + use GenServer + + def start_link do + Process.send_after(__MODULE__, :poll_and_reload, 5000) + GenServer.start_link(__MODULE__, %{}, name: Remix.Worker) + end + + ## callbacks + + def init(args) do + { :ok, args } + end + + def handle_info(:poll_and_reload, state) do + paths = Application.get_env(:remix, :projects_paths, default_paths()) ++ Application.get_env(:remix, :additional_paths, []) + + new_state = Map.new paths, fn (path) -> + current_mtime = get_current_mtime path + last_mtime = case Map.fetch(state, path) do + {:ok, val} -> val + :error -> nil + end + handle_path path, current_mtime, last_mtime + end + + poll_and_reload_interval = Application.get_env(:remix, :poll_and_reload_interval, 3000) + Process.send_after(__MODULE__, :poll_and_reload, poll_and_reload_interval) + {:noreply, new_state} + end + + def handle_info(_msg, state) do + {:noreply, state} + end + + ## private + + defp handle_path(path, current_mtime, current_mtime), do: {path, current_mtime} + defp handle_path(path, current_mtime, _) do + silence_wrapper fn -> + umbrella_wrapper fn -> + Mix.Tasks.Compile.Elixir.run(["--ignore-module-conflict"]) + if Application.get_env(:remix, :escript, false) do + Mix.Tasks.Escript.Build.run([]) + end + end + end + + {path, current_mtime} + end + + defp silence_wrapper(func) do + if Application.get_env(:remix, :silent, false) do + ExUnit.CaptureIO.capture_io(func) + else + func.() + end + end + + defp umbrella_wrapper(func) do + if Mix.Project.umbrella? do + Mix.Project.apps_paths + |> Enum.each(fn { app, path } when is_atom(app) and is_binary(path) -> + Mix.Project.in_project(app, path, fn _module_name -> + if Mix.Project.config()[:remix] do + func.() + end + end) + end) + else + func.() + end + end + + defp default_paths do + if Mix.Project.umbrella? do + Mix.Project.apps_paths() + |> Map.values() + |> Enum.map(fn s -> "#{s}/lib" end) + else + ["lib"] + end + end + + defp get_current_mtime(dir) do + case File.ls(dir) do + {:ok, files} -> get_current_mtime(files, [], dir) + _ -> nil + end + end + + defp get_current_mtime([], mtimes, _cwd) do + mtimes + |> Enum.sort + |> Enum.reverse + |> List.first + end + + defp get_current_mtime([h | tail], mtimes, cwd) do + mtime = case File.dir?("#{cwd}/#{h}") do + true -> get_current_mtime("#{cwd}/#{h}") + false -> File.stat!("#{cwd}/#{h}").mtime + end + get_current_mtime tail, [mtime | mtimes], cwd + end +end diff --git a/mix.exs b/mix.exs index 5e13298..bd2deb2 100644 --- a/mix.exs +++ b/mix.exs @@ -2,17 +2,21 @@ defmodule Remix.Mixfile do use Mix.Project def project do - [app: :remix, - version: "0.0.2", - elixir: "~> 1.0", - package: package, - description: description, - deps: deps] + [ + app: :remix, + version: "0.0.4", + elixir: "~> 1.6.1", + package: package(), + description: description(), + deps: deps() + ] end def application do - [applications: [:logger], - mod: {Remix, []}] + [ + extra_applications: [:logger], + mod: {Remix, []} + ] end defp deps, do: [] @@ -20,9 +24,9 @@ defmodule Remix.Mixfile do defp package do [ licenses: ["Apache 2.0"], - maintainers: ["Alan Peabody", "Mike Westbom", "Jordan Morano", "Brendan Fey"], + maintainers: ["Alan Peabody", "Mike Westbom", "Jordan Morano", "Brendan Fey", "Alik Send"], links: %{ - "GitHub" => "https://github.com/AgilionApps/remix" + "GitHub" => "https://github.com/aliksend/remix" } ] end