Packages

Erlang wrapper for Rust's cargo build tool

Current section

Files

Jump to
cargo src cargo_cmd.erl
Raw

src/cargo_cmd.erl

-module(cargo_cmd).
-export([
run/2,
run_with_flags/3
]).
-export_type([
output/0
]).
-type output() :: list(#{
binary() => jsx:json_term()
}).
-ifdef(TEST).
-define(MAX_LINE_LENGTH, 32).
-else.
-define(MAX_LINE_LENGTH, 16384).
-endif.
-spec run_with_flags(cargo_opts:t(), iolist(), [iolist()]) -> output().
run_with_flags(Opts, Cmd, Flags) ->
Insert0 =
case cargo_opts:release(Opts) of
true ->
["--release"];
_ ->
[]
end,
Insert1 =
case cargo_opts:target(Opts) of
undefined ->
Insert0;
Target ->
[io_lib:format("--target=~s", [Target]) | Insert0]
end,
Cmd1 = [Cmd] ++ Insert1 ++ Flags,
run(Opts, Cmd1).
-spec run(cargo_opts:t(), [iolist()]) -> output().
run(Opts, Args) ->
C0 = ["cargo"],
C1 =
case cargo_opts:toolchain(Opts) of
undefined ->
C0;
Toolchain ->
C0 ++ [io_lib:format("+~s", [Toolchain])]
end,
Cmd = lists:flatten(lists:join(
" ",
[binary_to_list(cargo_util:ensure_binary(I)) || I <- C1 ++ Args]
)),
exec(Cmd, cargo_opts:path(Opts)).
% Code derived from rebar3
-spec exec(string(), file:filename_all()) -> output().
exec(Command, Path) ->
Options1 = [
in,
binary,
{cd, Path},
exit_status,
{line, ?MAX_LINE_LENGTH},
use_stdio,
hide,
eof,
{env, env()}
],
Command1 = lists:flatten(patch_on_windows(Command)),
Port = open_port({spawn, Command1}, Options1),
try
case loop(Port, []) of
{ok, Output} ->
Output;
{error, {_Rc, _Output}=Err} ->
error({cargo_error, Err})
end
after
port_close(Port)
end.
-spec loop(port(), [T | {incomplete, _}]) -> {ok, [T]} | {error, _}.
loop(Port, Acc) ->
receive
{Port, {data, {_, Line}}} ->
loop(Port, handle_output(Line, Acc));
{Port, eof} ->
Data = finalize(Acc),
receive
{Port, {exit_status, 0}} ->
{ok, Data};
{Port, {exit_status, Rc}} ->
{error, {Rc, Data}}
end
end.
-spec patch_on_windows(string()) -> string().
patch_on_windows(Cmd) ->
case os:type() of
{win32, nt} ->
"cmd /q /c " ++ Cmd;
_ ->
Cmd
end.
-spec env() -> [{string(), string()}].
env() ->
case os:type() of
{unix, darwin} ->
[{"RUSTFLAGS", "--codegen 'link-args=-flat_namespace -undefined suppress'"}];
_ ->
[]
end.
% Ignore dialyzer warnings for finalize and handle_output as the spec
% for jsx:decode/2 is broken (does not include incomplete and with_tail)
-dialyzer([
{nowarn_function, finalize/1},
{nowarn_function, handle_output/2}
]).
finalize([{incomplete, _Decode} | _Acc]) ->
error(incomplete_json);
finalize(Acc) ->
lists:reverse(Acc).
handle_output("", Acc) ->
Acc;
handle_output(<<"">>, Acc) ->
Acc;
handle_output(Line, Acc0) ->
{DecodeRes, Acc} =
case Acc0 of
[{incomplete, Decode} | Acc1] ->
{Decode(Line), Acc1};
Acc2 ->
{jsx:decode(Line, [return_maps, stream, return_tail]), Acc2}
end,
case DecodeRes of
{with_tail, Res, Tail} ->
handle_output(Tail, [Res | Acc]);
Else ->
[Else | Acc]
end.