Reviewed By: jvillard Differential Revision: D27767581 fbshipit-source-id: 393f787f5master
parent
8a234f1e9f
commit
4e02e58709
@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env escript
|
||||
% Copyright (c) Facebook, Inc. and its affiliates.
|
||||
%
|
||||
% This source code is licensed under the MIT license found in the
|
||||
% LICENSE file in the root directory of this source tree.
|
||||
%
|
||||
%
|
||||
% Usage:
|
||||
% erlang.escript [ast_out_dir] -- rebar3 [args ...]
|
||||
% erlang.escript [ast_out_dir] -- erlc [args ...]
|
||||
%
|
||||
% This script produces a bash command that makes rebar3 or erlc
|
||||
% to execute with [args ...], and in addition to write the JSON
|
||||
% representation of the Erlang AST for each file compiled
|
||||
% in [ast_out_dir] or - if not provided - in the build
|
||||
% directory next to the corresponding compiled beam.
|
||||
|
||||
main([]) ->
|
||||
usage();
|
||||
main(Args) ->
|
||||
{SArgs, Cmd} = split_args(Args),
|
||||
OutDir =
|
||||
case SArgs of
|
||||
[] -> false;
|
||||
[Dir] -> Dir;
|
||||
_ -> usage()
|
||||
end,
|
||||
ScriptDir = filename:dirname(escript:script_name()),
|
||||
ParseTransformDir = filename:join(ScriptDir, "infer_parse_transform"),
|
||||
case run("rebar3 compile", ParseTransformDir) of
|
||||
0 ->
|
||||
ok;
|
||||
ExitStatus ->
|
||||
io:format("error: `rebar3 compile` in `~s` returned exit code ~p~n", [
|
||||
ParseTransformDir,
|
||||
ExitStatus
|
||||
]),
|
||||
halt(1)
|
||||
end,
|
||||
LibPath = filename:join(ParseTransformDir, "_build/default/lib"),
|
||||
case Cmd of
|
||||
["rebar3" | _] ->
|
||||
rebar3(LibPath, OutDir, Cmd);
|
||||
["erlc" | _] ->
|
||||
erlc(LibPath, OutDir, Cmd);
|
||||
_ ->
|
||||
io:format("error: unrecognized command ~s~n", [string:join(Cmd, " ")]),
|
||||
halt(1)
|
||||
end.
|
||||
|
||||
usage() ->
|
||||
io:format("valid arguments:~n"),
|
||||
io:format(" [ast_out_dir] -- rebar3 [args] ...~n"),
|
||||
io:format(" [ast_out_dir] -- erlc [args] ...~n"),
|
||||
halt(1).
|
||||
|
||||
load_config_from_list([]) ->
|
||||
false;
|
||||
load_config_from_list([H | T]) ->
|
||||
case load_config(H) of
|
||||
{ok, Config} -> Config;
|
||||
_ -> load_config_from_list(T)
|
||||
end.
|
||||
|
||||
load_config(ConfigPath) when is_list(ConfigPath) ->
|
||||
case lists:suffix(".script", ConfigPath) of
|
||||
true ->
|
||||
BaseConfigPath = filename:rootname(ConfigPath, ".script"),
|
||||
BaseConfig =
|
||||
case load_config(BaseConfigPath) of
|
||||
{ok, Config} -> Config;
|
||||
_ -> []
|
||||
end,
|
||||
file:script(ConfigPath, [{'CONFIG', BaseConfig}, {'SCRIPT', ConfigPath}]);
|
||||
false ->
|
||||
file:consult(ConfigPath)
|
||||
end;
|
||||
load_config(_) ->
|
||||
false.
|
||||
|
||||
split_args(Args) ->
|
||||
try
|
||||
split_args_rec(Args, [])
|
||||
catch
|
||||
_:_ -> usage()
|
||||
end.
|
||||
|
||||
split_args_rec(["--" | RebarCmd], Args) -> {Args, RebarCmd};
|
||||
split_args_rec([H | T], Args) -> split_args_rec(T, Args ++ [H]).
|
||||
|
||||
run(Command, Dir) ->
|
||||
Port = erlang:open_port(
|
||||
{spawn, Command},
|
||||
[exit_status, {cd, Dir}]
|
||||
),
|
||||
receive
|
||||
{Port, {exit_status, Status}} -> Status
|
||||
end.
|
||||
|
||||
rebar3(LibPath, OutDir, Cmd) ->
|
||||
ConfigPaths = [os:getenv("REBAR_CONFIG"), "rebar.config.script", "rebar.config"],
|
||||
Original =
|
||||
case load_config_from_list(ConfigPaths) of
|
||||
false ->
|
||||
io:format("error: no rebar3 config found~n"),
|
||||
halt(1);
|
||||
Config ->
|
||||
Config
|
||||
end,
|
||||
Altered = inject_parse_transform(Original, OutDir),
|
||||
AltConfigPath = string:trim(os:cmd("mktemp --suffix .script")),
|
||||
file:write_file(AltConfigPath, io_lib:fwrite("~p.~n", [Altered])),
|
||||
|
||||
io:format("ERL_LIBS=\"~s:$ERL_LIBS\" REBAR_CONFIG=\"~s\" ~s~n", [
|
||||
LibPath,
|
||||
AltConfigPath,
|
||||
string:join(Cmd, " ")
|
||||
]).
|
||||
|
||||
erlc(LibPath, OutDir, Cmd) ->
|
||||
[{erl_opts, Options}] = inject_parse_transform([], OutDir),
|
||||
OptionList = ["+'" ++ io_lib:format("~p", [Item]) ++ "'" || Item <- Options],
|
||||
[ErlC | Args] = Cmd,
|
||||
io:format("ERL_LIBS=\"~s:$ERL_LIBS\" ~s ~s ~s~n", [
|
||||
LibPath,
|
||||
ErlC,
|
||||
string:join(OptionList, " "),
|
||||
string:join(Args, " ")
|
||||
]).
|
||||
|
||||
inject_parse_transform(Original, OutDir) ->
|
||||
ErlOpts =
|
||||
case lists:keyfind(erl_opts, 1, Original) of
|
||||
{erl_opts, Opts} -> Opts;
|
||||
false -> []
|
||||
end,
|
||||
ErlOpts1 =
|
||||
ErlOpts ++
|
||||
[{parse_transform, infer_parse_transform}] ++
|
||||
if
|
||||
OutDir =/= false ->
|
||||
[{ast_outdir, OutDir}];
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
lists:keystore(erl_opts, 1, Original, {erl_opts, ErlOpts1}).
|
@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
set -o pipefail
|
||||
set -u
|
||||
|
||||
basedir=$(dirname "${BASH_SOURCE[0]}")
|
||||
out=$("${basedir}"/erlang.escript "$@")
|
||||
exit_code=$?
|
||||
echo "$out"
|
||||
if [ $exit_code = 0 ]; then
|
||||
sh -c "$out"
|
||||
exit $?
|
||||
fi
|
||||
exit $exit_code
|
@ -0,0 +1,9 @@
|
||||
% Copyright (c) Facebook, Inc. and its affiliates.
|
||||
%
|
||||
% This source code is licensed under the MIT license found in the
|
||||
% LICENSE file in the root directory of this source tree.
|
||||
|
||||
{erl_opts, []}.
|
||||
{deps, [
|
||||
{jsone, {git, "https://github.com/sile/jsone.git", {tag, "1.5.6"}}}
|
||||
]}.
|
@ -0,0 +1,16 @@
|
||||
% Copyright (c) Facebook, Inc. and its affiliates.
|
||||
%
|
||||
% This source code is licensed under the MIT license found in the
|
||||
% LICENSE file in the root directory of this source tree.
|
||||
|
||||
{application, infer_parse_transform,
|
||||
[{description, "parse_transform to dump the Erlang AST as a JSON structure"},
|
||||
{vsn, "0.1.0"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
stdlib,
|
||||
jsone
|
||||
]},
|
||||
{env,[]}
|
||||
]}.
|
@ -0,0 +1,50 @@
|
||||
% Copyright (c) Facebook, Inc. and its affiliates.
|
||||
%
|
||||
% This source code is licensed under the MIT license found in the
|
||||
% LICENSE file in the root directory of this source tree.
|
||||
|
||||
-module(infer_parse_transform).
|
||||
|
||||
-export([parse_transform/2]).
|
||||
|
||||
-type forms() :: erl_parse:abstract_form() | erl_parse:form_info().
|
||||
|
||||
-spec parse_transform([forms()], [compile:option()]) -> [forms()].
|
||||
parse_transform(Forms, Options) ->
|
||||
OutDir =
|
||||
case lists:keyfind(ast_outdir, 1, Options) of
|
||||
{ast_outdir, Dir} ->
|
||||
Dir;
|
||||
_ ->
|
||||
case lists:keyfind(outdir, 1, Options) of
|
||||
{outdir, Dir} -> Dir;
|
||||
_ -> error(abort)
|
||||
end
|
||||
end,
|
||||
FileName =
|
||||
case lists:keyfind(file, 3, Forms) of
|
||||
{attribute, _, file, {FilePath, _}} -> filename:basename(FilePath);
|
||||
_ -> error(abort)
|
||||
end,
|
||||
dump_ast_as_json(OutDir, FileName, Forms),
|
||||
Forms.
|
||||
|
||||
dump_ast_as_json(Dir, SourceFileName, Forms) ->
|
||||
FileName = filename:rootname(SourceFileName) ++ ".json",
|
||||
FilePath = filename:join(Dir, FileName),
|
||||
Object = ast_to_json(Forms),
|
||||
Contents = jsone:encode(Object),
|
||||
file:write_file(FilePath, Contents).
|
||||
|
||||
ast_to_json([]) ->
|
||||
[];
|
||||
ast_to_json(Node) when is_list(Node) ->
|
||||
case lists:all(fun(Item) -> is_integer(Item) end, Node) of
|
||||
true -> unicode:characters_to_binary(Node);
|
||||
false -> [ast_to_json(Child) || Child <- Node]
|
||||
end;
|
||||
ast_to_json(Node) when is_tuple(Node) ->
|
||||
L = tuple_to_list(Node),
|
||||
ast_to_json(L);
|
||||
ast_to_json(Node) ->
|
||||
Node.
|
Loading…
Reference in new issue