Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Umbrella support #22

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 29 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
83 changes: 5 additions & 78 deletions lib/remix.ex
Original file line number Diff line number Diff line change
@@ -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
106 changes: 106 additions & 0 deletions lib/remix/worker.ex
Original file line number Diff line number Diff line change
@@ -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
24 changes: 14 additions & 10 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,31 @@ 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: []

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
Expand Down