1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1997-2020. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20-module(erl_compile). 21 22-include("erl_compile.hrl"). 23-include("file.hrl"). 24 25-export([compile_cmdline/0, compile/2]). 26 27-export_type([cmd_line_arg/0]). 28 29%% Mapping from extension to {M,F} to run the correct compiler. 30 31compiler(".erl") -> {compile, compile}; 32compiler(".S") -> {compile, compile_asm}; 33compiler(".beam") -> {compile, compile_beam}; 34compiler(".core") -> {compile, compile_core}; 35compiler(".mib") -> {snmpc, compile}; 36compiler(".bin") -> {snmpc, mib_to_hrl}; 37compiler(".xrl") -> {leex, compile}; 38compiler(".yrl") -> {yecc, compile}; 39compiler(".script") -> {systools, script2boot}; 40compiler(".rel") -> {systools, compile_rel}; 41compiler(".idl") -> {ic, compile}; 42compiler(".asn1") -> {asn1ct, compile_asn1}; 43compiler(".asn") -> {asn1ct, compile_asn}; 44compiler(".py") -> {asn1ct, compile_py}; 45compiler(_) -> no. 46 47-type cmd_line_arg() :: atom() | string(). 48 49%% Run a compilation based on the command line arguments and then halt. 50%% Intended for one-off compilation by erlc. 51-spec compile_cmdline() -> no_return(). 52compile_cmdline() -> 53 cmdline_init(), 54 List = init:get_plain_arguments(), 55 compile_cmdline1(List). 56 57%% Run a compilation. Meant to be used by the compilation server. 58-spec compile(list(), file:filename()) -> 'ok' | {'error', binary()}. 59compile(Args, Cwd) -> 60 try compile1(Args, #options{outdir=Cwd,cwd=Cwd}) of 61 ok -> 62 ok 63 catch 64 throw:{error, Output} -> 65 {error, unicode:characters_to_binary(Output)}; 66 C:E:Stk -> 67 {crash, {C,E,Stk}} 68 end. 69 70%% Run the the compiler in a separate process. 71compile_cmdline1(Args) -> 72 {ok, Cwd} = file:get_cwd(), 73 {Pid,Ref} = spawn_monitor(fun() -> exit(compile(Args, Cwd)) end), 74 receive 75 {'DOWN', Ref, process, Pid, Result} -> 76 case Result of 77 ok -> 78 halt(0); 79 {error, Output} -> 80 io:put_chars(standard_error, Output), 81 halt(1); 82 {crash, {C,E,Stk}} -> 83 io:format(standard_error, "Crash: ~p:~tp\n~tp\n", 84 [C,E,Stk]), 85 halt(2) 86 end 87 end. 88 89cmdline_init() -> 90 %% We don't want the current directory in the code path. 91 %% Remove it. 92 Path = [D || D <- code:get_path(), D =/= "."], 93 true = code:set_path(Path), 94 ok. 95 96%% Parse all options. 97compile1(["--"|Files], Opts) -> 98 compile2(Files, Opts); 99compile1(["-"++Option|T], Opts) -> 100 parse_generic_option(Option, T, Opts); 101compile1(["+"++Option|Rest], Opts) -> 102 Term = make_term(Option), 103 Specific = Opts#options.specific, 104 compile1(Rest, Opts#options{specific=[Term|Specific]}); 105compile1(Files, Opts) -> 106 compile2(Files, Opts). 107 108parse_generic_option("b"++Opt, T0, Opts) -> 109 {OutputType,T} = get_option("b", Opt, T0), 110 compile1(T, Opts#options{output_type=list_to_atom(OutputType)}); 111parse_generic_option("D"++Opt, T0, #options{defines=Defs}=Opts) -> 112 {Val0,T} = get_option("D", Opt, T0), 113 {Key0,Val1} = split_at_equals(Val0, []), 114 Key = list_to_atom(Key0), 115 case Val1 of 116 [] -> 117 compile1(T, Opts#options{defines=[Key|Defs]}); 118 Val2 -> 119 Val = make_term(Val2), 120 compile1(T, Opts#options{defines=[{Key,Val}|Defs]}) 121 end; 122parse_generic_option("help", _, _Opts) -> 123 usage(); 124parse_generic_option("I"++Opt, T0, #options{cwd=Cwd}=Opts) -> 125 {Dir,T} = get_option("I", Opt, T0), 126 AbsDir = filename:absname(Dir, Cwd), 127 compile1(T, Opts#options{includes=[AbsDir|Opts#options.includes]}); 128parse_generic_option("M"++Opt, T0, #options{specific=Spec}=Opts) -> 129 {SpecOpts,T} = parse_dep_option(Opt, T0), 130 compile1(T, Opts#options{specific=SpecOpts++Spec}); 131parse_generic_option("o"++Opt, T0, #options{cwd=Cwd}=Opts) -> 132 {Dir,T} = get_option("o", Opt, T0), 133 AbsName = filename:absname(Dir, Cwd), 134 case file_or_directory(AbsName) of 135 file -> 136 compile1(T, Opts#options{outfile=AbsName}); 137 directory -> 138 compile1(T, Opts#options{outdir=AbsName}) 139 end; 140parse_generic_option("O"++Opt, T, Opts) -> 141 case Opt of 142 "" -> 143 compile1(T, Opts#options{optimize=1}); 144 _ -> 145 Term = make_term(Opt), 146 compile1(T, Opts#options{optimize=Term}) 147 end; 148parse_generic_option("v", T, Opts) -> 149 compile1(T, Opts#options{verbose=true}); 150parse_generic_option("W"++Warn, T, #options{specific=Spec}=Opts) -> 151 case Warn of 152 "all" -> 153 compile1(T, Opts#options{warning=999}); 154 "error" -> 155 compile1(T, Opts#options{specific=[warnings_as_errors|Spec]}); 156 "" -> 157 compile1(T, Opts#options{warning=1}); 158 _ -> 159 try list_to_integer(Warn) of 160 Level -> 161 compile1(T, Opts#options{warning=Level}) 162 catch 163 error:badarg -> 164 usage() 165 end 166 end; 167parse_generic_option("E", T, #options{specific=Spec}=Opts) -> 168 compile1(T, Opts#options{specific=['E'|Spec]}); 169parse_generic_option("P", T, #options{specific=Spec}=Opts) -> 170 compile1(T, Opts#options{specific=['P'|Spec]}); 171parse_generic_option("S", T, #options{specific=Spec}=Opts) -> 172 compile1(T, Opts#options{specific=['S'|Spec]}); 173parse_generic_option(Option, _T, _Opts) -> 174 usage(io_lib:format("Unknown option: -~ts\n", [Option])). 175 176parse_dep_option("", T) -> 177 {[makedep,{makedep_output,standard_io}],T}; 178parse_dep_option("D", T) -> 179 {[makedep],T}; 180parse_dep_option("MD", T) -> 181 {[makedep_side_effect],T}; 182parse_dep_option("F"++Opt, T0) -> 183 {File,T} = get_option("MF", Opt, T0), 184 {[makedep,{makedep_output,File}],T}; 185parse_dep_option("G", T) -> 186 {[makedep_add_missing],T}; 187parse_dep_option("P", T) -> 188 {[makedep_phony],T}; 189parse_dep_option("Q"++Opt, T0) -> 190 {Target,T} = get_option("MT", Opt, T0), 191 {[makedep_quote_target,{makedep_target,Target}],T}; 192parse_dep_option("T"++Opt, T0) -> 193 {Target,T} = get_option("MT", Opt, T0), 194 {[{makedep_target,Target}],T}; 195parse_dep_option(Opt, _T) -> 196 usage(io_lib:format("Unknown option: -M~ts\n", [Opt])). 197 198-spec usage() -> no_return(). 199 200usage() -> 201 usage(""). 202 203usage(Error) -> 204 H = [{"-b type","type of output file (e.g. beam)"}, 205 {"-d","turn on debugging of erlc itself"}, 206 {"-Dname","define name"}, 207 {"-Dname=value","define name to have value"}, 208 {"-help","shows this help text"}, 209 {"-I path","where to search for include files"}, 210 {"-M","generate a rule for make(1) describing the dependencies"}, 211 {"-MF file","write the dependencies to 'file'"}, 212 {"-MT target","change the target of the rule emitted by dependency " 213 "generation"}, 214 {"-MQ target","same as -MT but quote characters special to make(1)"}, 215 {"-MG","consider missing headers as generated files and add them to " 216 "the dependencies"}, 217 {"-MP","add a phony target for each dependency"}, 218 {"-MD","same as -M -MT file (with default 'file')"}, 219 {"-MMD","generate dependencies as a side-effect"}, 220 {"-o name","name output directory or file"}, 221 {"-pa path","add path to the front of Erlang's code path"}, 222 {"-pz path","add path to the end of Erlang's code path"}, 223 {"-smp","compile using SMP emulator"}, 224 {"-v","verbose compiler output"}, 225 {"-Werror","make all warnings into errors"}, 226 {"-W0","disable warnings"}, 227 {"-Wnumber","set warning level to number"}, 228 {"-Wall","enable all warnings"}, 229 {"-W","enable warnings (default; same as -W1)"}, 230 {"-E","generate listing of expanded code (Erlang compiler)"}, 231 {"-S","generate assembly listing (Erlang compiler)"}, 232 {"-P","generate listing of preprocessed code (Erlang compiler)"}, 233 {"+term","pass the Erlang term unchanged to the compiler"}], 234 Msg = [Error, 235 "Usage: erlc [Options] file.ext ...\n", 236 "Options:\n", 237 [io_lib:format("~-14s ~s\n", [K,D]) || {K,D} <- H]], 238 throw({error, Msg}). 239 240get_option(_Name, [], [[C|_]=Option|T]) when C =/= $- -> 241 {Option,T}; 242get_option(_Name, [_|_]=Option, T) -> 243 {Option,T}; 244get_option(Name, _, _) -> 245 throw({error, "No value given to -"++Name++" option\n"}). 246 247split_at_equals([$=|T], Acc) -> 248 {lists:reverse(Acc),T}; 249split_at_equals([H|T], Acc) -> 250 split_at_equals(T, [H|Acc]); 251split_at_equals([], Acc) -> 252 {lists:reverse(Acc),[]}. 253 254compile2(Files, #options{cwd=Cwd,includes=Incl,outfile=Outfile}=Opts0) -> 255 Opts = Opts0#options{includes=lists:reverse(Incl)}, 256 case {Outfile,length(Files)} of 257 {"", _} -> 258 compile3(Files, Cwd, Opts); 259 {[_|_], 1} -> 260 compile3(Files, Cwd, Opts); 261 {[_|_], _N} -> 262 throw({error, "Output file name given, but more than one input file.\n"}) 263 end. 264 265%% Compile the list of files, until done or compilation fails. 266compile3([File|Rest], Cwd, Options) -> 267 Ext = filename:extension(File), 268 Root = filename:rootname(File), 269 InFile = filename:absname(Root, Cwd), 270 OutFile = 271 case Options#options.outfile of 272 "" -> 273 filename:join(Options#options.outdir, filename:basename(Root)); 274 Outfile -> 275 filename:rootname(Outfile) 276 end, 277 compile_file(Ext, InFile, OutFile, Options), 278 compile3(Rest, Cwd, Options); 279compile3([], _Cwd, _Options) -> ok. 280 281%% Invoke the appropriate compiler, depending on the file extension. 282compile_file("", Input, _Output, _Options) -> 283 throw({error, io_lib:format("File has no extension: ~ts~n", [Input])}); 284compile_file(Ext, Input, Output, Options) -> 285 case compiler(Ext) of 286 no -> 287 Error = io_lib:format("Unknown extension: '~ts'\n", [Ext]), 288 throw({error, Error}); 289 {M, F} -> 290 try M:F(Input, Output, Options) of 291 ok -> 292 ok; 293 error -> 294 throw({error, ""}); 295 Other -> 296 Error = io_lib:format("Compiler function ~w:~w/3 returned:\n~tp~n", 297 [M,F,Other]), 298 throw({error, Error}) 299 catch 300 throw:Reason:Stk -> 301 Error = io_lib:format("Compiler function ~w:~w/3 failed:\n~tp\n~tp\n", 302 [M,F,Reason,Stk]), 303 throw({error, Error}) 304 end 305 end. 306 307%% Guess whether a given name refers to a file or a directory. 308file_or_directory(Name) -> 309 case file:read_file_info(Name) of 310 {ok, #file_info{type=regular}} -> 311 file; 312 {ok, _} -> 313 directory; 314 {error, _} -> 315 case filename:extension(Name) of 316 [] -> directory; 317 _Other -> file 318 end 319 end. 320 321%% Make an Erlang term given a string. 322make_term(Str) -> 323 case erl_scan:string(Str) of 324 {ok, Tokens, _} -> 325 case erl_parse:parse_term(Tokens ++ [{dot, erl_anno:new(1)}]) of 326 {ok, Term} -> 327 Term; 328 {error, {_,_,Reason}} -> 329 throw({error, io_lib:format("~ts: ~ts~n", [Reason, Str])}) 330 end; 331 {error, {_,_,Reason}, _} -> 332 throw({error, io_lib:format("~ts: ~ts~n", [Reason, Str])}) 333 end. 334