-module(rebar3_elixir_plugin_utils). -export([to_binary/1, to_string/1, get_lib_dir/1, add_elixir/1, create_rebar_lock_from_mix/2, save_rebar_lock/2, compile/1, move_to_path/3, elixir_to_lock/1, add_elixir_to_path/1, delete/1 ]). %% @doc Convert binary() | list() | integer() | atom() to binary(). -spec to_binary(binary() | list() | integer() | atom()) -> binary(). to_binary(V) when is_binary(V) -> V; to_binary(V) when is_list(V) -> list_to_binary(V); to_binary(V) when is_integer(V) -> integer_to_binary(V); to_binary(V) when is_atom(V) -> atom_to_binary(V, latin1); to_binary(_) -> erlang:error(badarg). %% @doc Convert binary() | list() | integer() | atom() to string(). -spec to_string(binary() | list() | integer() | atom()) -> string(). to_string(Value) when is_binary(Value) -> binary_to_list(Value); to_string(Value) when is_list(Value) -> Value; to_string(Value) when is_integer(Value) -> lists:flatten(io_lib:format("~p", [Value])); to_string(Value) when is_atom(Value) -> atom_to_list(Value); to_string(_) -> erlang:error(badarg). %% @doc Gets the location of elixir libs dir. %% 1. Try to get it from the rebar.config file. %% 2. If it's not defined, try to find it on the system. %% 3. If it's not defined, throws an exception. -spec get_lib_dir(rebar_state:t()) -> string(). get_lib_dir(State) -> Config = rebar_state:get(State, elixir_opts, []), case lists:keyfind(lib_dir, 1, Config) of false -> case rebar_utils:sh("elixir -e \"IO.puts :code.lib_dir(:elixir)\"", [return_on_error]) of {ok, ElixirLibs_} -> filename:join(re:replace(ElixirLibs_, "\\s+", "", [global,{return,list}]), "../"); _ -> erlang:error(missing_elixir) end; {lib_dir, Dir2} -> Dir2 end. %% @doc gets the actual _build/{{Profile}}/lib directory. -spec get_build_path(rebar_state:t()) -> string(). get_build_path(State) -> [Profile | _] = rebar_state:current_profiles(State), filename:join([rebar_dir:root_dir(State), "_build/", to_string(Profile), "lib"]). %% @doc Convert the actual mix.lock file into a list of deps in rebar.lock format. -spec create_rebar_lock_from_mix(string(), [string()]) -> list(). create_rebar_lock_from_mix(AppDir, Deps) -> MixLocks = get_mix_lock(AppDir), lists:foldl( fun(AppLock, Locks) -> case AppLock of {Name, {hex, App, Version, _, _, _, _}} -> case lists:member(to_string(Name), Deps) of true -> Locks ++ [{Name, {iex_dep, App, Version}, 0}]; false -> Locks end; {Name, {git, URL, Hash, _}} -> case lists:member(to_string(Name), Deps) of true -> Locks ++ [{Name, {iex_dep, URL, Hash}, 0}]; false -> Locks end; _-> Locks end end, [], MixLocks). %% Create a rebar.lock inside the Dir with the desps defined in Locks -spec save_rebar_lock(string(), list()) -> ok | {error, term()}. save_rebar_lock(Dir, Locks) -> rebar_config:write_lock_file(filename:join(Dir, "rebar.lock"), Locks). %% @doc Add elixir to rebar_state -spec add_elixir(rebar_state:t()) -> rebar_state:t(). add_elixir(State) -> LibDir = get_lib_dir(State), code:add_patha(filename:join(LibDir, "elixir/ebin")), code:add_patha(filename:join(LibDir, "mix/ebin")), code:add_patha(filename:join(LibDir, "logger/ebin")), State. %% @doc Add elixir to the application build path -spec add_elixir_to_path(rebar_state:t()) -> rebar_state:t(). add_elixir_to_path(State)-> LibDir = get_lib_dir(State), BuildPath = get_build_path(State), %% Link Elixir ElixirPath = filename:join(LibDir, "elixir"), ElixirBuildPath = filename:join(BuildPath, "elixir"), file:make_symlink(ElixirPath, ElixirBuildPath), %% Link Logger LoggerPath = filename:join(LibDir, "logger"), LoggerBuildPath = filename:join(BuildPath, "logger"), file:make_symlink(LoggerPath, LoggerBuildPath), %% Link mix MixPath = filename:join(LibDir, "mix"), MixBuildPath = filename:join(BuildPath, "mix"), file:make_symlink(MixPath, MixBuildPath), State. %% @doc Add elixir to a list of lock dependencies. -spec elixir_to_lock(list()) -> list(). elixir_to_lock(Lock) -> Lock ++ [ {elixir, {iex_dep, <<"elixir">>, <<"">>}, 0}, {logger, {iex_dep, <<"logger">>, <<"">>}, 0}, {mix, {iex_dep, <<"mix">>, <<"">>}, 0} ]. %% @doc compiles a elixir app which is located in AppDir. %% Crash compilation when something goes wrong. -spec compile(string()) -> ok. compile(AppDir) -> {ok, _ } = rebar_utils:sh("mix deps.get", [ {cd, AppDir}, {use_stdout, true}, abort_on_error]), {ok, _ } = rebar_utils:sh("mix compile", [ {cd, AppDir}, {use_stdout, true}, abort_on_error, {env, [ {"MIX_ENV", "prod"} ] }]), ok. %% @doc Moves a list of files located in Source to a %% new directory located in Traget. -spec move_to_path([string()], string(), string()) -> list(). move_to_path(Files, Source, Traget) -> lists:map( fun(File) -> Source1 = filename:join([Source, File]), Target1 = filename:join([Traget, File]), ec_file:copy(Source1, Target1, [recursive]) end, Files). -spec delete(string()) -> string(). delete(Dir) -> os:cmd("rm -Rf " ++ Dir). %%============================= %% Private functions %%============================= %% @doc Get deps of an app in mix.lock format -spec get_mix_lock(string()) -> list(). get_mix_lock(AppDir) -> Lockfile = filename:join(AppDir, "mix.lock"), application:ensure_all_started(elixir), case 'Elixir.File':read(Lockfile) of {ok,Info} -> Opts = [{file, to_binary(Lockfile)}, {warn_on_unnecessary_quotes, false}], {ok, Quoted} = 'Elixir.Code':string_to_quoted(Info, Opts), {EvalRes, _Binding} = 'Elixir.Code':eval_quoted(Quoted, Opts), 'Elixir.Enum':to_list(EvalRes); {error, _} -> [] end.