diff options
-rw-r--r-- | src/rebar_compiler_xrl.erl | 30 | ||||
-rw-r--r-- | src/rebar_compiler_yrl.erl | 14 | ||||
-rw-r--r-- | test/rebar_compile_SUITE.erl | 150 |
3 files changed, 176 insertions, 18 deletions
diff --git a/src/rebar_compiler_xrl.erl b/src/rebar_compiler_xrl.erl index 23ed1eb..35447ed 100644 --- a/src/rebar_compiler_xrl.erl +++ b/src/rebar_compiler_xrl.erl @@ -8,6 +8,8 @@ compile/4, clean/2]). +-export([update_opts/2]). + context(AppInfo) -> Dir = rebar_app_info:dir(AppInfo), Mappings = [{".erl", filename:join([Dir, "src"])}], @@ -25,28 +27,38 @@ needed_files(_, FoundFiles, Mappings, AppInfo) -> rebar_compiler:needs_compile(Source, ".erl", Mappings)], Opts = rebar_opts:get(rebar_app_info:opts(AppInfo), xrl_opts, []), + Opts1 = update_opts(Opts, AppInfo), - {{FirstFiles, Opts}, {RestFiles, Opts}}. + {{FirstFiles, Opts1}, {RestFiles, Opts1}}. dependencies(_, _, _) -> []. -compile(Source, [{_, OutDir}], _, Opts) -> - BaseName = filename:basename(Source), - Target = filename:join([OutDir, BaseName]), - AllOpts = [{parserfile, Target} | Opts], - AllOpts1 = [{includefile, filename:join(OutDir, I)} || {includefile, I} <- AllOpts, - filename:pathtype(I) =:= relative], - case leex:file(Source, AllOpts1 ++ [{return, true}]) of +compile(Source, [{_, _}], _, Opts) -> + case leex:file(Source, [{return, true} | Opts]) of {ok, _} -> ok; {ok, _Mod, Ws} -> rebar_compiler:ok_tuple(Source, Ws); {error, Es, Ws} -> - rebar_compiler:error_tuple(Source, Es, Ws, AllOpts1) + rebar_compiler:error_tuple(Source, Es, Ws, Opts) end. clean(XrlFiles, _AppInfo) -> rebar_file_utils:delete_each( [rebar_utils:to_list(re:replace(F, "\\.xrl$", ".erl", [unicode])) || F <- XrlFiles]). + +%% make includefile options absolute paths +update_opts(Opts, AppInfo) -> + OutDir = rebar_app_info:out_dir(AppInfo), + lists:map(fun({includefile, I}) -> + case filename:pathtype(I) =:= relative of + true -> + {includefile, filename:join(OutDir, I)}; + false -> + {includefile, I} + end; + (O) -> + O + end, Opts). diff --git a/src/rebar_compiler_yrl.erl b/src/rebar_compiler_yrl.erl index 9c1767e..8e52d0e 100644 --- a/src/rebar_compiler_yrl.erl +++ b/src/rebar_compiler_yrl.erl @@ -25,24 +25,24 @@ needed_files(_, FoundFiles, Mappings, AppInfo) -> rebar_compiler:needs_compile(Source, ".erl", Mappings)], Opts = rebar_opts:get(rebar_app_info:opts(AppInfo), yrl_opts, []), - {{FirstFiles, Opts}, {RestFiles, Opts}}. + Opts1 = rebar_compiler_xrl:update_opts(Opts, AppInfo), + + {{FirstFiles, Opts1}, {RestFiles, Opts1}}. dependencies(_, _, _) -> []. compile(Source, [{_, OutDir}], _, Opts) -> - BaseName = filename:basename(Source), + BaseName = filename:basename(Source, ".yrl"), Target = filename:join([OutDir, BaseName]), - AllOpts = [{parserfile, Target} | Opts], - AllOpts1 = [{includefile, filename:join(OutDir, I)} || {includefile, I} <- AllOpts, - filename:pathtype(I) =:= relative], - case yecc:file(Source, AllOpts1 ++ [{return, true}]) of + AllOpts = [{parserfile, Target}, {return, true} | Opts], + case yecc:file(Source, AllOpts) of {ok, _} -> ok; {ok, _Mod, Ws} -> rebar_compiler:ok_tuple(Source, Ws); {error, Es, Ws} -> - rebar_compiler:error_tuple(Source, Es, Ws, AllOpts1) + rebar_compiler:error_tuple(Source, Es, Ws, AllOpts) end. clean(YrlFiles, _AppInfo) -> diff --git a/test/rebar_compile_SUITE.erl b/test/rebar_compile_SUITE.erl index 4d51f2a..9cbe8a2 100644 --- a/test/rebar_compile_SUITE.erl +++ b/test/rebar_compile_SUITE.erl @@ -852,7 +852,13 @@ dont_recompile_yrl_or_xrl(Config) -> XrlBeam = filename:join([EbinDir, filename:basename(Xrl, ".xrl") ++ ".beam"]), YrlBeam = filename:join([EbinDir, filename:basename(Yrl, ".yrl") ++ ".beam"]), - rebar_test_utils:run_and_check(Config, [], ["compile"], {ok, [{app, Name}]}), + Hrl = filename:join([AppDir, "include", "some_header.hrl"]), + ok = filelib:ensure_dir(Hrl), + HrlBody = yeccpre_hrl(), + ok = ec_file:write(Hrl, HrlBody), + RebarConfig = [{yrl_opts, [{includefile, "include/some_header.hrl"}]}], + + rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], {ok, [{app, Name}]}), XrlModTime = filelib:last_modified(XrlErl), YrlModTime = filelib:last_modified(YrlErl), @@ -862,7 +868,7 @@ dont_recompile_yrl_or_xrl(Config) -> timer:sleep(1000), - rebar_test_utils:run_and_check(Config, [], ["compile"], {ok, [{app, Name}]}), + rebar_test_utils:run_and_check(Config, RebarConfig, ["compile"], {ok, [{app, Name}]}), NewXrlModTime = filelib:last_modified(XrlErl), NewYrlModTime = filelib:last_modified(YrlErl), @@ -2208,3 +2214,143 @@ regex_filter_regression(Config) -> {ok, [{file, Expected}]}), ok. +%% + +%% a copy of lib/parsetools/include/yeccpre.hrl so we can test yrl includefile +yeccpre_hrl() -> + <<"-type yecc_ret() :: {'error', _} | {'ok', _}. + +-spec parse(Tokens :: list()) -> yecc_ret(). +parse(Tokens) -> + yeccpars0(Tokens, {no_func, no_line}, 0, [], []). + +-spec parse_and_scan({function() | {atom(), atom()}, [_]} + | {atom(), atom(), [_]}) -> yecc_ret(). +parse_and_scan({F, A}) -> + yeccpars0([], {{F, A}, no_line}, 0, [], []); +parse_and_scan({M, F, A}) -> + Arity = length(A), + yeccpars0([], {{fun M:F/Arity, A}, no_line}, 0, [], []). + +-spec format_error(any()) -> [char() | list()]. +format_error(Message) -> + case io_lib:deep_char_list(Message) of + true -> + Message; + _ -> + io_lib:write(Message) + end. + +%% To be used in grammar files to throw an error message to the parser +%% toplevel. Doesn't have to be exported! +-compile({nowarn_unused_function, return_error/2}). +-spec return_error(integer(), any()) -> no_return(). +return_error(Line, Message) -> + throw({error, {Line, ?MODULE, Message}}). + +-define(CODE_VERSION, \"1.4\"). + +yeccpars0(Tokens, Tzr, State, States, Vstack) -> + try yeccpars1(Tokens, Tzr, State, States, Vstack) + catch + error:Error -> + try yecc_error_type(Error, []) of + Desc -> + erlang:raise(error, {yecc_bug, ?CODE_VERSION, Desc}, + []) + catch _:_ -> erlang:raise(error, Error, []) + end; + %% Probably thrown from return_error/2: + throw: {error, {_Line, ?MODULE, _M}} = Error -> + Error + end. + +yecc_error_type(function_clause, _) -> + not_implemented. + +yeccpars1([Token | Tokens], Tzr, State, States, Vstack) -> + yeccpars2(State, element(1, Token), States, Vstack, Token, Tokens, Tzr); +yeccpars1([], {{F, A},_Line}, State, States, Vstack) -> + case apply(F, A) of + {ok, Tokens, Endline} -> + yeccpars1(Tokens, {{F, A}, Endline}, State, States, Vstack); + {eof, Endline} -> + yeccpars1([], {no_func, Endline}, State, States, Vstack); + {error, Descriptor, _Endline} -> + {error, Descriptor} + end; +yeccpars1([], {no_func, no_line}, State, States, Vstack) -> + Line = 999999, + yeccpars2(State, '$end', States, Vstack, yecc_end(Line), [], + {no_func, Line}); +yeccpars1([], {no_func, Endline}, State, States, Vstack) -> + yeccpars2(State, '$end', States, Vstack, yecc_end(Endline), [], + {no_func, Endline}). + +%% yeccpars1/7 is called from generated code. +%% +%% When using the {includefile, Includefile} option, make sure that +%% yeccpars1/7 can be found by parsing the file without following +%% include directives. yecc will otherwise assume that an old +%% yeccpre.hrl is included (one which defines yeccpars1/5). +yeccpars1(State1, State, States, Vstack, Token0, [Token | Tokens], Tzr) -> + yeccpars2(State, element(1, Token), [State1 | States], + [Token0 | Vstack], Token, Tokens, Tzr); +yeccpars1(State1, State, States, Vstack, Token0, [], {{_F,_A}, _Line}=Tzr) -> + yeccpars1([], Tzr, State, [State1 | States], [Token0 | Vstack]); +yeccpars1(State1, State, States, Vstack, Token0, [], {no_func, no_line}) -> + Line = yecctoken_end_location(Token0), + yeccpars2(State, '$end', [State1 | States], [Token0 | Vstack], + yecc_end(Line), [], {no_func, Line}); +yeccpars1(State1, State, States, Vstack, Token0, [], {no_func, Line}) -> + yeccpars2(State, '$end', [State1 | States], [Token0 | Vstack], + yecc_end(Line), [], {no_func, Line}). + +%% For internal use only. +yecc_end({Line,_Column}) -> + {'$end', Line}; +yecc_end(Line) -> + {'$end', Line}. + +yecctoken_end_location(Token) -> + try erl_anno:end_location(element(2, Token)) of + undefined -> yecctoken_location(Token); + Loc -> Loc + catch _:_ -> yecctoken_location(Token) + end. + +-compile({nowarn_unused_function, yeccerror/1}). +yeccerror(Token) -> + Text = yecctoken_to_string(Token), + Location = yecctoken_location(Token), + {error, {Location, ?MODULE, [\"syntax error before: \", Text]}}. + +-compile({nowarn_unused_function, yecctoken_to_string/1}). +yecctoken_to_string(Token) -> + try erl_scan:text(Token) of + undefined -> yecctoken2string(Token); + Txt -> Txt + catch _:_ -> yecctoken2string(Token) + end. + +yecctoken_location(Token) -> + try erl_scan:location(Token) + catch _:_ -> element(2, Token) + end. + +-compile({nowarn_unused_function, yecctoken2string/1}). +yecctoken2string({atom, _, A}) -> io_lib:write_atom(A); +yecctoken2string({integer,_,N}) -> io_lib:write(N); +yecctoken2string({float,_,F}) -> io_lib:write(F); +yecctoken2string({char,_,C}) -> io_lib:write_char(C); +yecctoken2string({var,_,V}) -> io_lib:format(\"~s\", [V]); +yecctoken2string({string,_,S}) -> io_lib:write_string(S); +yecctoken2string({reserved_symbol, _, A}) -> io_lib:write(A); +yecctoken2string({_Cat, _, Val}) -> io_lib:format(\"~p\", [Val]); +yecctoken2string({dot, _}) -> \"'.'\"; +yecctoken2string({'$end', _}) -> []; +yecctoken2string({Other, _}) when is_atom(Other) -> + io_lib:write_atom(Other); +yecctoken2string(Other) -> + io_lib:format(\"~p\", [Other]). +">>. |