Current section

Files

Jump to
rebar3_lfe src r3lfe_prv_run_escript.erl
Raw

src/r3lfe_prv_run_escript.erl

-module(r3lfe_prv_run_escript).
-behaviour(provider).
-export([
init/1,
do/1,
format_error/1
]).
%% Exported for testing
-ifdef(TEST).
-export([
find_escript/1,
determine_escript_path/1,
parse_args/1,
build_command/2
]).
-endif.
-include_lib("rebar3_lfe/include/r3lfe.hrl").
-define(PROVIDER, 'run-escript').
-define(DEPS, [{?NAMESPACE, escriptize}]).
%%====================================================================
%% Provider API
%%====================================================================
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
Description = "Run an LFE escript",
Provider = providers:create([
{namespace, ?NAMESPACE},
{name, ?PROVIDER},
{module, ?MODULE},
{bare, true},
{deps, ?DEPS},
{example, "rebar3 lfe run-escript -- arg1 arg2"},
{opts, []},
{short_desc, Description},
{desc, info(Description)}
]),
{ok, rebar_state:add_provider(State, Provider)}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
?DEBUG("LFE run-escript provider starting", []),
rebar_paths:set_paths([deps, plugins], State),
try
%% Find the escript
EscriptPath = find_escript(State),
case filelib:is_file(EscriptPath) of
false ->
{error, format_error({escript_not_found, EscriptPath})};
true ->
%% Get arguments
Args = parse_args(State),
%% Execute escript
?INFO("Running ~s with args: ~p", [EscriptPath, Args]),
Result = run_escript(EscriptPath, Args),
?DEBUG("Escript result: ~p", [Result]),
{ok, State}
end
catch
throw:{error, Reason} ->
{error, format_error(Reason)};
error:Reason:Stack ->
?ERROR("Run-escript failed: ~p", [Reason]),
?DEBUG("Stack trace: ~p", [Stack]),
{error, format_error({run_error, Reason})}
end.
-spec format_error(term()) -> iolist().
format_error({escript_not_found, Path}) ->
io_lib:format(
"Escript not found: ~s~n"
"Run 'rebar3 lfe escriptize' first to build the escript.",
[Path]
);
format_error({run_error, Reason}) ->
io_lib:format("Escript execution failed: ~p", [Reason]);
format_error(Reason) ->
io_lib:format("~p", [Reason]).
%%====================================================================
%% Internal functions
%%====================================================================
%% @doc Find the escript path from rebar3 state
-spec find_escript(rebar_state:t()) -> file:filename().
find_escript(State) ->
case rebar_state:escript_path(State) of
undefined ->
%% Fallback: try to determine from config
determine_escript_path(State);
Path ->
Path
end.
%% @doc Determine escript path from configuration
-spec determine_escript_path(rebar_state:t()) -> file:filename().
determine_escript_path(State) ->
%% Get escript name from config
EscriptName = case rebar_state:get(State, escript_name, undefined) of
undefined ->
%% Use project app name as fallback
case rebar_state:project_apps(State) of
[AppInfo | _] ->
Name = rebar_app_info:name(AppInfo),
%% Name might be binary or atom
case is_binary(Name) of
true -> binary_to_list(Name);
false -> atom_to_list(Name)
end;
[] ->
"escript"
end;
Name when is_atom(Name) ->
atom_to_list(Name);
Name ->
Name
end,
%% Build path
BaseDir = rebar_dir:base_dir(State),
filename:join(BaseDir, EscriptName).
%% @doc Parse arguments after -- separator
-spec parse_args(rebar_state:t()) -> [string()].
parse_args(State) ->
RawArgs = rebar_state:command_args(State),
case lists:dropwhile(fun(X) -> X =/= "--" end, RawArgs) of
[] -> [];
["--" | Args] -> Args
end.
%% @doc Execute the escript
-spec run_escript(file:filename(), [string()]) -> term().
run_escript(EscriptPath, Args) ->
%% Build command
Cmd = build_command(EscriptPath, Args),
?DEBUG("Executing: ~s", [Cmd]),
%% Execute and capture output
Port = open_port(
{spawn, Cmd},
[stream, exit_status, use_stdio, stderr_to_stdout, in, eof]
),
collect_output(Port).
%% @doc Build command string
-spec build_command(file:filename(), [string()]) -> string().
build_command(EscriptPath, Args) ->
%% Ensure escript is executable
ok = file:change_mode(EscriptPath, 8#755),
%% Build command
string:join([EscriptPath | Args], " ").
%% @doc Collect output from port
-spec collect_output(port()) -> ok.
collect_output(Port) ->
receive
{Port, {data, Data}} ->
io:format("~s", [Data]),
collect_output(Port);
{Port, eof} ->
port_close(Port),
receive
{Port, {exit_status, 0}} ->
ok;
{Port, {exit_status, Status}} ->
?WARN("Escript exited with status: ~p", [Status]),
ok
end
end.
-spec info(string()) -> iolist().
info(Description) ->
io_lib:format(
"~n~s~n"
"~n"
"Executes a previously built LFE escript. The escript must be built~n"
"first using 'rebar3 lfe escriptize'.~n"
"~n"
"The escript path is determined from:~n"
" 1. rebar3's internal escript_path~n"
" 2. {escript_name, Name} in rebar.config~n"
" 3. Project app name as fallback~n"
"~n"
"Arguments are passed directly to the escript's main/1 function.~n"
"~n"
"Examples:~n"
" rebar3 lfe run-escript~n"
" rebar3 lfe run-escript -- arg1 arg2~n"
" rebar3 lfe run-escript -- --verbose --output file.txt~n"
"~n"
"Build and run workflow:~n"
" rebar3 lfe escriptize # Build escript~n"
" rebar3 lfe run-escript # Run it~n"
"~n"
"Or directly:~n"
" ./_build/default/bin/myapp arg1 arg2~n",
[Description]
).