aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/src
diff options
context:
space:
mode:
authorBjörn Gustavsson <[email protected]>2013-08-16 14:04:45 +0200
committerBjörn Gustavsson <[email protected]>2013-09-19 13:30:28 +0200
commitde853c60333c6e95379b69ee81bd5fff1dca95de (patch)
treecfef551d9c84661b4e379d1602d5fd9702cabbc8 /lib/stdlib/src
parent8c4f1595473db1f01b2dd363b49d32d4b93b90b2 (diff)
downloadotp-de853c60333c6e95379b69ee81bd5fff1dca95de.tar.gz
otp-de853c60333c6e95379b69ee81bd5fff1dca95de.tar.bz2
otp-de853c60333c6e95379b69ee81bd5fff1dca95de.zip
Teach erlc to handle UTF-8 file names
The 'erlc' program passes options to the 'erl' program using the '-s' option. The '-s' option causes all options to be converted to atoms, which implies that UTF-8 file names may not be given on the command line. We could solve just the UTF-8 problem by using '-run' and change the erl_compile module to expect strings instead of atoms, but since that is an incompatible change, we should take the opportunity to make more incompatible changes while we are at it. Specifically, when 'erlc' was first written, there was no way to pass command line arguments starting with '-' to Erlang, so 'erlc' did all parsing of arguments and translated options to atoms starting with a '@' character (for example, -I was translated to @i). Since then, the '-extra' option has been introduced which allows us to pass anything to Erlang at the end of the command line. Therefore, while at it, do the minimum of necessary command line parsing in the 'erlc' program (e.g. the '-smp' option), passing the command line essentially unchanged to 'erl' using the '-extra' option, and rewrite the option parsing in Erlang.
Diffstat (limited to 'lib/stdlib/src')
-rw-r--r--lib/stdlib/src/erl_compile.erl225
1 files changed, 173 insertions, 52 deletions
diff --git a/lib/stdlib/src/erl_compile.erl b/lib/stdlib/src/erl_compile.erl
index 8c3d59467b..ed8fea5d78 100644
--- a/lib/stdlib/src/erl_compile.erl
+++ b/lib/stdlib/src/erl_compile.erl
@@ -21,10 +21,12 @@
-include("erl_compile.hrl").
-include("file.hrl").
--export([compile_cmdline/1]).
+-export([compile_cmdline/0]).
-export_type([cmd_line_arg/0]).
+-define(STDERR, standard_error). %Macro to avoid misspellings.
+
%% Mapping from extension to {M,F} to run the correct compiler.
compiler(".erl") -> {compile, compile};
@@ -47,9 +49,10 @@ compiler(_) -> no.
-type cmd_line_arg() :: atom() | string().
--spec compile_cmdline([cmd_line_arg()]) -> no_return().
+-spec compile_cmdline() -> no_return().
-compile_cmdline(List) ->
+compile_cmdline() ->
+ List = init:get_plain_arguments(),
case compile(List) of
ok -> my_halt(0);
error -> my_halt(1);
@@ -67,8 +70,12 @@ compile(List) ->
receive
{'EXIT', Pid, {compiler_result, Result}} ->
Result;
+ {'EXIT', Pid, {compiler_error, Error}} ->
+ io:put_chars(?STDERR, Error),
+ io:nl(?STDERR),
+ error;
{'EXIT', Pid, Reason} ->
- io:format("Runtime error: ~tp~n", [Reason]),
+ io:format(?STDERR, "Runtime error: ~tp~n", [Reason]),
error
end.
@@ -83,66 +90,178 @@ compiler_runner(List) ->
%% Parses the first part of the option list.
-compile1(['@cwd', Cwd|Rest]) ->
- CwdL = atom_to_list(Cwd),
- compile1(Rest, CwdL, #options{outdir=CwdL, cwd=CwdL});
compile1(Args) ->
- %% From R13B02, the @cwd argument is optional.
{ok, Cwd} = file:get_cwd(),
- compile1(Args, Cwd, #options{outdir=Cwd, cwd=Cwd}).
+ compile1(Args, #options{outdir=Cwd,cwd=Cwd}).
%% Parses all options.
-compile1(['@i', Dir|Rest], Cwd, Opts) ->
+compile1(["--"|Files], Opts) ->
+ compile2(Files, Opts);
+compile1(["-"++Option|T], Opts) ->
+ parse_generic_option(Option, T, Opts);
+compile1(["+"++Option|Rest], Opts) ->
+ Term = make_term(Option),
+ Specific = Opts#options.specific,
+ compile1(Rest, Opts#options{specific=[Term|Specific]});
+compile1(Files, Opts) ->
+ compile2(Files, Opts).
+
+parse_generic_option("b"++Opt, T0, Opts) ->
+ {OutputType,T} = get_option("b", Opt, T0),
+ compile1(T, Opts#options{output_type=list_to_atom(OutputType)});
+parse_generic_option("D"++Opt, T0, #options{defines=Defs}=Opts) ->
+ {Val0,T} = get_option("D", Opt, T0),
+ {Key0,Val1} = split_at_equals(Val0, []),
+ Key = list_to_atom(Key0),
+ case Val1 of
+ [] ->
+ compile1(T, Opts#options{defines=[Key|Defs]});
+ Val2 ->
+ Val = make_term(Val2),
+ compile1(T, Opts#options{defines=[{Key,Val}|Defs]})
+ end;
+parse_generic_option("help", _, _Opts) ->
+ usage();
+parse_generic_option("I"++Opt, T0, #options{cwd=Cwd}=Opts) ->
+ {Dir,T} = get_option("I", Opt, T0),
AbsDir = filename:absname(Dir, Cwd),
- compile1(Rest, Cwd, Opts#options{includes=[AbsDir|Opts#options.includes]});
-compile1(['@outdir', Dir|Rest], Cwd, Opts) ->
+ compile1(T, Opts#options{includes=[AbsDir|Opts#options.includes]});
+parse_generic_option("M"++Opt, T0, #options{specific=Spec}=Opts) ->
+ case parse_dep_option(Opt, T0) of
+ error ->
+ error;
+ {SpecOpts,T} ->
+ compile1(T, Opts#options{specific=SpecOpts++Spec})
+ end;
+parse_generic_option("o"++Opt, T0, #options{cwd=Cwd}=Opts) ->
+ {Dir,T} = get_option("o", Opt, T0),
AbsName = filename:absname(Dir, Cwd),
case file_or_directory(AbsName) of
file ->
- compile1(Rest, Cwd, Opts#options{outfile=AbsName});
+ compile1(T, Opts#options{outfile=AbsName});
directory ->
- compile1(Rest, Cwd, Opts#options{outdir=AbsName})
+ compile1(T, Opts#options{outdir=AbsName})
end;
-compile1(['@d', Name|Rest], Cwd, Opts) ->
- Defines = Opts#options.defines,
- compile1(Rest, Cwd, Opts#options{defines=[Name|Defines]});
-compile1(['@dv', Name, Term|Rest], Cwd, Opts) ->
- Defines = Opts#options.defines,
- Value = make_term(atom_to_list(Term)),
- compile1(Rest, Cwd, Opts#options{defines=[{Name, Value}|Defines]});
-compile1(['@warn', Level0|Rest], Cwd, Opts) ->
- case catch list_to_integer(atom_to_list(Level0)) of
- Level when is_integer(Level) ->
- compile1(Rest, Cwd, Opts#options{warning=Level});
+parse_generic_option("O"++Opt, T, Opts) ->
+ case Opt of
+ "" ->
+ compile1(T, Opts#options{optimize=1});
_ ->
- compile1(Rest, Cwd, Opts)
+ Term = make_term(Opt),
+ compile1(T, Opts#options{optimize=Term})
end;
-compile1(['@verbose', false|Rest], Cwd, Opts) ->
- compile1(Rest, Cwd, Opts#options{verbose=false});
-compile1(['@verbose', true|Rest], Cwd, Opts) ->
- compile1(Rest, Cwd, Opts#options{verbose=true});
-compile1(['@optimize', Atom|Rest], Cwd, Opts) ->
- Term = make_term(atom_to_list(Atom)),
- compile1(Rest, Cwd, Opts#options{optimize=Term});
-compile1(['@option', Atom|Rest], Cwd, Opts) ->
- Term = make_term(atom_to_list(Atom)),
- Specific = Opts#options.specific,
- compile1(Rest, Cwd, Opts#options{specific=[Term|Specific]});
-compile1(['@output_type', OutputType|Rest], Cwd, Opts) ->
- compile1(Rest, Cwd, Opts#options{output_type=OutputType});
-compile1(['@files'|Rest], Cwd, Opts) ->
- Includes = lists:reverse(Opts#options.includes),
- compile2(Rest, Cwd, Opts#options{includes=Includes}).
-
-compile2(Files, Cwd, Opts) ->
- case {Opts#options.outfile, length(Files)} of
+parse_generic_option("v", T, Opts) ->
+ compile1(T, Opts#options{verbose=true});
+parse_generic_option("W"++Warn, T, #options{specific=Spec}=Opts) ->
+ case Warn of
+ "all" ->
+ compile1(T, Opts#options{warning=999});
+ "error" ->
+ compile1(T, Opts#options{specific=[warnings_as_errors|Spec]});
+ "" ->
+ compile1(T, Opts#options{warning=1});
+ _ ->
+ try list_to_integer(Warn) of
+ Level ->
+ compile1(T, Opts#options{warning=Level})
+ catch
+ error:badarg ->
+ usage()
+ end
+ end;
+parse_generic_option("E", T, #options{specific=Spec}=Opts) ->
+ compile1(T, Opts#options{specific=['E'|Spec]});
+parse_generic_option("P", T, #options{specific=Spec}=Opts) ->
+ compile1(T, Opts#options{specific=['P'|Spec]});
+parse_generic_option("S", T, #options{specific=Spec}=Opts) ->
+ compile1(T, Opts#options{specific=['S'|Spec]});
+parse_generic_option(Option, _T, _Opts) ->
+ io:format(?STDERR, "Unknown option: -~s\n", [Option]),
+ usage().
+
+parse_dep_option("", T) ->
+ {[makedep,{makedep_output,standard_io}],T};
+parse_dep_option("D", T) ->
+ {[makedep],T};
+parse_dep_option("F"++Opt, T0) ->
+ {File,T} = get_option("MF", Opt, T0),
+ {[makedep,{makedep_output,File}],T};
+parse_dep_option("G", T) ->
+ {[makedep_add_missing],T};
+parse_dep_option("P", T) ->
+ {[makedep_phony],T};
+parse_dep_option("Q"++Opt, T0) ->
+ {Target,T} = get_option("MT", Opt, T0),
+ {[makedep_quote_target,{makedep_target,Target}],T};
+parse_dep_option("T"++Opt, T0) ->
+ {Target,T} = get_option("MT", Opt, T0),
+ {[{makedep_target,Target}],T};
+parse_dep_option(Opt, _T) ->
+ io:format(?STDERR, "Unknown option: -M~s\n", [Opt]),
+ usage().
+
+usage() ->
+ H = [{"-b type","type of output file (e.g. beam)"},
+ {"-d","turn on debugging of erlc itself"},
+ {"-Dname","define name"},
+ {"-Dname=value","define name to have value"},
+ {"-help","shows this help text"},
+ {"-I path","where to search for include files"},
+ {"-M","generate a rule for make(1) describing the dependencies"},
+ {"-MF file","write the dependencies to 'file'"},
+ {"-MT target","change the target of the rule emitted by dependency "
+ "generation"},
+ {"-MQ target","same as -MT but quote characters special to make(1)"},
+ {"-MG","consider missing headers as generated files and add them to "
+ "the dependencies"},
+ {"-MP","add a phony target for each dependency"},
+ {"-MD","same as -M -MT file (with default 'file')"},
+ {"-o name","name output directory or file"},
+ {"-pa path","add path to the front of Erlang's code path"},
+ {"-pz path","add path to the end of Erlang's code path"},
+ {"-smp","compile using SMP emulator"},
+ {"-v","verbose compiler output"},
+ {"-Werror","make all warnings into errors"},
+ {"-W0","disable warnings"},
+ {"-Wnumber","set warning level to number"},
+ {"-Wall","enable all warnings"},
+ {"-W","enable warnings (default; same as -W1)"},
+ {"-E","generate listing of expanded code (Erlang compiler)"},
+ {"-S","generate assembly listing (Erlang compiler)"},
+ {"-P","generate listing of preprocessed code (Erlang compiler)"},
+ {"+term","pass the Erlang term unchanged to the compiler"}],
+ io:put_chars(?STDERR,
+ ["Usage: erlc [Options] file.ext ...\n",
+ "Options:\n",
+ [io_lib:format("~-14s ~s\n", [K,D]) || {K,D} <- H]]),
+ error.
+
+get_option(_Name, [], [[C|_]=Option|T]) when C =/= $- ->
+ {Option,T};
+get_option(_Name, [_|_]=Option, T) ->
+ {Option,T};
+get_option(Name, _, _) ->
+ exit({compiler_error,"No value given to -"++Name++" option"}).
+
+split_at_equals([$=|T], Acc) ->
+ {lists:reverse(Acc),T};
+split_at_equals([H|T], Acc) ->
+ split_at_equals(T, [H|Acc]);
+split_at_equals([], Acc) ->
+ {lists:reverse(Acc),[]}.
+
+compile2(Files, #options{cwd=Cwd,includes=Incl,outfile=Outfile}=Opts0) ->
+ Opts = Opts0#options{includes=lists:reverse(Incl)},
+ case {Outfile,length(Files)} of
{"", _} ->
compile3(Files, Cwd, Opts);
{[_|_], 1} ->
compile3(Files, Cwd, Opts);
{[_|_], _N} ->
- io:format("Output file name given, but more than one input file.~n"),
+ io:put_chars(?STDERR,
+ "Output file name given, "
+ "but more than one input file.\n"),
error
end.
@@ -170,23 +289,25 @@ compile3([], _Cwd, _Options) -> ok.
%% Invokes the appropriate compiler, depending on the file extension.
compile_file("", Input, _Output, _Options) ->
- io:format("File has no extension: ~ts~n", [Input]),
+ io:format(?STDERR, "File has no extension: ~ts~n", [Input]),
error;
compile_file(Ext, Input, Output, Options) ->
case compiler(Ext) of
no ->
- io:format("Unknown extension: '~ts'\n", [Ext]),
+ io:format(?STDERR, "Unknown extension: '~ts'\n", [Ext]),
error;
{M, F} ->
case catch M:F(Input, Output, Options) of
ok -> ok;
error -> error;
{'EXIT',Reason} ->
- io:format("Compiler function ~w:~w/3 failed:\n~p~n",
+ io:format(?STDERR,
+ "Compiler function ~w:~w/3 failed:\n~p~n",
[M,F,Reason]),
error;
Other ->
- io:format("Compiler function ~w:~w/3 returned:\n~p~n",
+ io:format(?STDERR,
+ "Compiler function ~w:~w/3 returned:\n~p~n",
[M,F,Other]),
error
end
@@ -215,10 +336,10 @@ make_term(Str) ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok, Term} -> Term;
{error, {_,_,Reason}} ->
- io:format("~ts: ~ts~n", [Reason, Str]),
+ io:format(?STDERR, "~ts: ~ts~n", [Reason, Str]),
throw(error)
end;
{error, {_,_,Reason}, _} ->
- io:format("~ts: ~ts~n", [Reason, Str]),
+ io:format(?STDERR, "~ts: ~ts~n", [Reason, Str]),
throw(error)
end.