diff options
author | Björn Gustavsson <[email protected]> | 2013-08-16 14:04:45 +0200 |
---|---|---|
committer | Björn Gustavsson <[email protected]> | 2013-09-19 13:30:28 +0200 |
commit | de853c60333c6e95379b69ee81bd5fff1dca95de (patch) | |
tree | cfef551d9c84661b4e379d1602d5fd9702cabbc8 | |
parent | 8c4f1595473db1f01b2dd363b49d32d4b93b90b2 (diff) | |
download | otp-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.
-rw-r--r-- | bootstrap/lib/stdlib/ebin/erl_compile.beam | bin | 4880 -> 7312 bytes | |||
-rw-r--r-- | erts/etc/common/erlc.c | 267 | ||||
-rw-r--r-- | lib/stdlib/src/erl_compile.erl | 225 |
3 files changed, 191 insertions, 301 deletions
diff --git a/bootstrap/lib/stdlib/ebin/erl_compile.beam b/bootstrap/lib/stdlib/ebin/erl_compile.beam Binary files differindex 1311018a7f..244bb5e290 100644 --- a/bootstrap/lib/stdlib/ebin/erl_compile.beam +++ b/bootstrap/lib/stdlib/ebin/erl_compile.beam diff --git a/erts/etc/common/erlc.c b/erts/etc/common/erlc.c index add65b87ca..055064abc4 100644 --- a/erts/etc/common/erlc.c +++ b/erts/etc/common/erlc.c @@ -60,7 +60,6 @@ static int eargc; /* Number of arguments in eargv. */ #define PUSH2(s, t) PUSH(s); PUSH(t) #define PUSH3(s, t, u) PUSH2(s, t); PUSH(u) -static char* output_type = NULL; /* Type of output file. */ #ifdef __WIN32__ static int pause_after_execution = 0; #endif @@ -71,7 +70,6 @@ static int pause_after_execution = 0; static char* process_opt(int* pArgc, char*** pArgv, int offset); static void error(char* format, ...); -static void usage(void); static char* emalloc(size_t size); static char* strsave(char* string); static void push_words(char* src); @@ -218,6 +216,7 @@ int main(int argc, char** argv) PUSH2("-mode", "minimal"); PUSH2("-boot", "start_clean"); PUSH3("-s", "erl_compile", "compile_cmdline"); + PUSH("-extra"); /* * Push standard arguments to Erlang. @@ -227,154 +226,31 @@ int main(int argc, char** argv) * Parse all command line switches. */ - while (argc > 1 && (argv[1][0] == '-' || argv[1][0] == '+')) { + while (argc > 1) { /* * Options starting with '+' are passed on to Erlang. */ - if (argv[1][0] == '+') { - PUSH2("@option", argv[1]+1); - } else { - /* - * Interpret options starting with '-'. - */ - + switch (argv[1][0]) { + case '+': + PUSH(argv[1]); + break; + case '-': switch (argv[1][1]) { - case 'b': - output_type = process_opt(&argc, &argv, 0); - PUSH2("@output_type", output_type); - break; - case 'c': /* Allowed for compatibility with 'erl'. */ - if (strcmp(argv[1], "-compile") != 0) - goto error; - break; case 'd': - debug = 1; - break; - case 'D': - { - char* def = process_opt(&argc, &argv, 0); - char* equals; - - def = strsave(def); /* Do not clobber original. */ - if ((equals = strchr(def, '=')) == NULL) { - PUSH2("@d", def); - } else { - *equals = '\0'; - equals++; - PUSH3("@dv", def, equals); - } - } - break; - case 'I': - PUSH2("@i", process_opt(&argc, &argv, 0)); - break; - case 'M': - { - char *buf, *key, *val; - size_t buf_len; - - if (argv[1][2] == '\0') { /* -M */ - /* Push the following options: - * o 'makedep' - * o {makedep_output, standard_io} - */ - buf = strsave("makedep"); - PUSH2("@option", buf); - - key = "makedep_output"; - val = "standard_io"; - buf_len = 1 + strlen(key) + 1 + strlen(val) + 1 + 1; - buf = emalloc(buf_len); - snprintf(buf, buf_len, "{%s,%s}", key, val); - PUSH2("@option", buf); - } else if (argv[1][3] == '\0') { - switch(argv[1][2]) { - case 'D': /* -MD */ - /* Push the following options: - * o 'makedep' - */ - buf = strsave("makedep"); - PUSH2("@option", buf); - break; - case 'F': /* -MF <file> */ - /* Push the following options: - * o 'makedep' - * o {makedep_output, <file>} - */ - buf = strsave("makedep"); - PUSH2("@option", buf); - - key = "makedep_output"; - val = process_opt(&argc, &argv, 1); - buf_len = 1 + strlen(key) + 2 + strlen(val) + 2 + 1; - buf = emalloc(buf_len); - snprintf(buf, buf_len, "{%s,\"%s\"}", key, val); - PUSH2("@option", buf); - break; - case 'T': /* -MT <target> */ - /* Push the following options: - * o {makedep_target, <target>} - */ - key = "makedep_target"; - val = process_opt(&argc, &argv, 1); - buf_len = 1 + strlen(key) + 2 + strlen(val) + 2 + 1; - buf = emalloc(buf_len); - snprintf(buf, buf_len, "{%s,\"%s\"}", key, val); - PUSH2("@option", buf); - break; - case 'Q': /* -MQ <target> */ - /* Push the following options: - * o {makedep_target, <target>} - * o makedep_quote_target - */ - key = "makedep_target"; - val = process_opt(&argc, &argv, 1); - buf_len = 1 + strlen(key) + 2 + strlen(val) + 2 + 1; - buf = emalloc(buf_len); - snprintf(buf, buf_len, "{%s,\"%s\"}", key, val); - PUSH2("@option", buf); - - buf = strsave("makedep_quote_target"); - PUSH2("@option", buf); - break; - case 'G': /* -MG */ - /* Push the following options: - * o makedep_add_missing - */ - buf = strsave("makedep_add_missing"); - PUSH2("@option", buf); - break; - case 'P': /* -MP */ - /* Push the following options: - * o makedep_phony - */ - buf = strsave("makedep_phony"); - PUSH2("@option", buf); - break; - default: - goto error; - } - } + if (argv[1][2] == '\0') { + debug = 1; + } else { + PUSH(argv[1]); } break; - case 'o': - PUSH2("@outdir", process_opt(&argc, &argv, 0)); - break; - case 'O': - PUSH("@optimize"); - if (argv[1][2] == '\0') - PUSH("1"); - else - PUSH(argv[1]+2); - break; case 'p': { int c = argv[1][2]; if (c != 'a' && c != 'z') { - goto error; + PUSH(argv[1]); #ifdef __WIN32__ } else if (strcmp(argv[1], "-pause") == 0) { pause_after_execution = 1; @@ -395,81 +271,21 @@ int main(int argc, char** argv) if (strcmp(argv[1], "-smp") == 0) { UNSHIFT(argv[1]); } else { - goto error; - } - break; - case 'v': /* Verbose. */ - PUSH2("@verbose", "true"); - break; - case 'V': - /** XXX Version perhaps, but of what? **/ - break; - case 'W': /* Enable warnings. */ - if (strcmp(argv[1]+2, "all") == 0) { - PUSH2("@warn", "999"); - } else if (strcmp(argv[1]+2, "error") == 0) { - PUSH2("@option", "warnings_as_errors"); - } else if (isdigit((int)argv[1][2])) { - PUSH2("@warn", argv[1]+2); - } else { - PUSH2("@warn", "1"); - } - break; - case 'E': - case 'S': - case 'P': - { - char* buf; - - /* - * From the given upper-case letter, construct - * a quoted atom. This is a convenience for the - * Erlang compiler, to avoid fighting with the shell's - * quoting. - */ - - buf = emalloc(4); - buf[0] = '\''; - buf[1] = argv[1][1]; - buf[2] = '\''; - buf[3] = '\0'; - - PUSH2("@option", buf); + PUSH(argv[1]); } break; - - case '-': - goto no_more_options; - default: - error: - usage(); + PUSH(argv[1]); break; } + break; + default: + PUSH(argv[1]); + break; } argc--, argv++; } - no_more_options: - - if (argc <= 1) { - /* - * To avoid starting an Erlang system unless absolutely needed - * exit if no files were specified on the command line. - */ - exit(0); - } - - /* - * The rest of the command line must be filenames. Simply push them. - */ - - PUSH("@files"); - while (argc > 1) { - PUSH(argv[1]); - argc--, argv++; - } - /* * Move up the commands for invoking the emulator and adjust eargv * accordingly. @@ -649,53 +465,6 @@ run_erlang(char* progname, char** argv) } static void -usage(void) -{ - static struct { - char* name; - char* desc; - } options[] = { - {"-b type", "type of output file (e.g. jam or 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"}, - }; - int i; - - fprintf(stderr, "Usage:\terlc [options] file.ext ...\n"); - fprintf(stderr, "Options:\n"); - for (i = 0; i < sizeof(options)/sizeof(options[0]); i++) { - fprintf(stderr, "%-14s %s\n", options[i].name, options[i].desc); - } - exit(1); -} - -static void error(char* format, ...) { char sbuf[1024]; 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. |