-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Hello,
When using erlc(1) and standard Makefiles to compile Erlang source
files, there's currently no way to track header dependencies. Thus, if a
header is modified, a module that depends on it will be recompiled only
if the dependency is explicitly indicated in the Makefile. This becomes
problematic when the header is external (for instance, a header from
another application, such as xmerl.hrl).
Attached is a patch that adds support for producing dependencies
Makefiles to erlc(1) and compiler(3). It was modeled after GCC.
For example, let's take the following module:
-module(mod1).
-include("header1.hrl").
...
The command "erlc -M mod1.erl" will output:
mod1.beam: mod1.erl header1.hrl
The patch adds the following options to erlc(1) and compiler(3):
-M generate a rule describing dependencies; output on
stdout.
-MF File rule(s) is(are) written to `File'.
-MT Target change the name of the rule emitted.
-MQ Target same as -MT but quote special characters for make(1).
-MG consider missing headers as generated files and add the
to the dependencies
-MP add a phony target for each dependency.
-MD same as -M -MT file.Pbeam
They're the same as GCC. The following options are not supported:
-MM ignore system headers
-MMD same as -MD but ignore system headers
I choose to keep the same names as GCC because I'm working on Erlang
support in Automake and it wants to use these options. Regarding
compiler(3), options could have a more Erlang-fashion name.
The patch, against R12B-4, includes the documentation updates. But I
don't know how to make it, so it's untested.
Thanks,
PS: I already sent an older patch to erlang-questions@ more than a year
ago. The attached patch obsoletes it.
- --
Jean-Sébastien Pédron
http://www.dumbbell.fr/PGP Key:
http://www.dumbbell.fr/pgp/pubkey.asc-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (FreeBSD)
Comment: Using GnuPG with Mozilla -
http://enigmail.mozdev.orgiEYEARECAAYFAkkIfWAACgkQa+xGJsFYOlP9swCdGsB6Ydsa6yTz3dLijCeyFAGh
NzgAoMAsrNe2oEan+1ItXO+IuRVuSNwU
=GOml
-----END PGP SIGNATURE-----
diff -Naur otp_src_R12B-4/erts/doc/src/erlc.xml otp_src_R12B-4-makedep/erts/doc/src/erlc.xml
--- otp_src_R12B-4/erts/doc/src/erlc.xml 2008-02-05 14:37:24.000000000 +0100
+++ otp_src_R12B-4-makedep/erts/doc/src/erlc.xml 2008-10-29 15:50:58.000000000 +0100
@@ -135,6 +135,50 @@
for compiling native code, which needs to be compiled with the same
run-time system that it should be run on.</p>
</item>
+ <tag>-M</tag>
+ <item>
+ <p>Produces a Makefile rule to track headers dependencies. The
+ rule is sent to stdout. No object file is produced.
+ </p>
+ </item>
+ <tag>-MF <em>Makefile</em></tag>
+ <item>
+ <p>Like the <c><![CDATA[M]]></c> option above, except that the
+ Makefile is written to <em>Makefile</em>. No object
+ file is produced.
+ </p>
+ </item>
+ <tag>-MD</tag>
+ <item>
+ <p>Same as <c><![CDATA[-M -MF <File>.Pbeam]]></c>.
+ </p>
+ </item>
+ <tag>-MT <em>Target</em></tag>
+ <item>
+ <p>In conjunction with <c><![CDATA[-M]]></c> or
+ <c><![CDATA[-MF]]></c>, change the name of the rule emitted
+ to <em>Target</em>.
+ </p>
+ </item>
+ <tag>-MQ <em>Target</em></tag>
+ <item>
+ <p>Like the <c><![CDATA[-MT]]></c> option above, except that
+ characters special to make(1) or quoted.
+ </p>
+ </item>
+ <tag>-MP</tag>
+ <item>
+ <p>In conjunction with <c><![CDATA[-M]]></c> or
+ <c><![CDATA[-MF]]></c>, add a phony target for each dependency.
+ </p>
+ </item>
+ <tag>-MG</tag>
+ <item>
+ <p>In conjunction with <c><![CDATA[-M]]></c> or
+ <c><![CDATA[-MF]]></c>, consider missing headers as generated
+ files and add them to the dependencies.
+ </p>
+ </item>
<tag>--</tag>
<item>
<p>Signals that no more options will follow.
diff -Naur otp_src_R12B-4/erts/etc/common/erlc.c otp_src_R12B-4-makedep/erts/etc/common/erlc.c
--- otp_src_R12B-4/erts/etc/common/erlc.c 2008-02-05 14:37:24.000000000 +0100
+++ otp_src_R12B-4-makedep/erts/etc/common/erlc.c 2008-10-29 11:53:03.000000000 +0100
@@ -255,6 +255,66 @@
case 'I':
PUSH2("@i", process_opt(&argc, &argv, 0));
break;
+ case 'M':
+ {
+ char *buf, *key, *val;
+ size_t buf_len, key_len, val_len;
+
+ if (argv[1][2] == '\0') { /* -M */
+ buf = emalloc(4);
+ buf[0] = '\'';
+ buf[1] = argv[1][1];
+ buf[2] = '\'';
+ buf[3] = '\0';
+
+ PUSH2("@option", buf);
+ } else {
+ switch(argv[1][2]) {
+ case 'P': /* -MP */
+ case 'D': /* -MD */
+ case 'G': /* -MG */
+ buf = emalloc(5);
+ buf[0] = '\'';
+ buf[1] = argv[1][1];
+ buf[2] = argv[1][2];
+ buf[3] = '\'';
+ buf[4] = '\0';
+
+ PUSH2("@option", buf);
+ break;
+ case 'T': /* -MT <target> */
+ case 'Q': /* -MQ <target> */
+ case 'F': /* -MF <file> */
+ switch (argv[1][2]) {
+ case 'T':
+ key = "'MT'";
+ break;
+ case 'Q':
+ key = "'MQ'";
+ break;
+ case 'F':
+ key = "'MF'";
+ break;
+ default:
+ key = "'M?'";
+ break;
+ }
+ key_len = strlen(key);
+ val = process_opt(&argc, &argv, 1);
+ val_len = strlen(val);
+
+ buf_len = 1 + key_len + 2 + val_len + 2 + 1;
+ buf = emalloc(buf_len);
+ snprintf(buf, buf_len, "{%s,\"%s\"}", key, val);
+
+ PUSH2("@option", buf);
+ break;
+ default:
+ goto error;
+ }
+ }
+ }
+ break;
case 'o':
PUSH2("@outdir", process_opt(&argc, &argv, 0));
break;
@@ -557,6 +617,15 @@
{"-hybrid", "compile using hybrid-heap emulator"},
{"-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"},
diff -Naur otp_src_R12B-4/lib/compiler/doc/src/compile.xml otp_src_R12B-4-makedep/lib/compiler/doc/src/compile.xml
--- otp_src_R12B-4/lib/compiler/doc/src/compile.xml 2008-10-29 15:29:03.000000000 +0100
+++ otp_src_R12B-4-makedep/lib/compiler/doc/src/compile.xml 2008-10-29 15:50:26.000000000 +0100
@@ -164,6 +164,55 @@
for details.</p>
</item>
+ <tag><c>'M'</c></tag>
+ <item>
+ <p>Produces a Makefile rule to track headers dependencies. The
+ rule is sent to stdout. No object file is produced.
+ </p>
+ </item>
+
+ <tag><c>{'MF',Makefile}</c></tag>
+ <item>
+ <p>Like the <c>'M'</c> option above, except that the Makefile
+ is written to <c>Makefile</c>. No object file is produced.
+ </p>
+ </item>
+
+ <tag><c>'MD'</c></tag>
+ <item>
+ <p>Same as <c>['M', {'MF', <![CDATA[<File>.Pbeam]]>}]</c>.
+ </p>
+ </item>
+
+ <tag><c>{'MT',Target}</c></tag>
+ <item>
+ <p>In conjunction with <c>'M'</c> or <c>'MF'</c>, change the
+ name of the rule emitted to <c>Target</c>.
+ </p>
+ </item>
+
+ <tag><c>{'MQ',Target}</c></tag>
+ <item>
+ <p>Like the <c>{'MT',Target}</c> option above, except that
+ characters special to make(1) or quoted.
+ </p>
+ </item>
+
+ <tag><c>'MP'</c></tag>
+ <item>
+ <p>In conjunction with <c>'M'</c> or <c>'MF'</c>, add a phony
+ target for each dependency.
+ </p>
+ </item>
+
+ <tag><c>'MG'</c></tag>
+ <item>
+ <p>In conjunction with <c>'M'</c> or <c>'MF'</c>, consider
+ missing headers as generated files and add them to the
+ dependencies.
+ </p>
+ </item>
+
<tag><c>'P'</c></tag>
<item>
<p>Produces a listing of the parsed code after preprocessing
diff -Naur otp_src_R12B-4/lib/compiler/src/compile.erl otp_src_R12B-4-makedep/lib/compiler/src/compile.erl
--- otp_src_R12B-4/lib/compiler/src/compile.erl 2008-04-07 15:57:56.000000000 +0200
+++ otp_src_R12B-4-makedep/lib/compiler/src/compile.erl 2008-10-29 15:18:37.000000000 +0100
@@ -151,6 +151,12 @@
expand_opt(no_float_opt, Os) ->
%%Turn off the entire type optimization pass.
[no_topt|Os];
+expand_opt('MD', Os) ->
+ ['M', {'MF', default} | Os];
+expand_opt({'MQ', T}, Os) ->
+ Fun = fun($$) -> "$$"; (C) -> C end,
+ T1 = lists:flatten(lists:map(Fun, T)),
+ [{'MT', T1} | Os];
expand_opt(O, Os) -> [O|Os].
%% format_error(ErrorDescriptor) -> string()
@@ -398,6 +404,8 @@
%% file will be Ext. (Ext should not contain
%% a period.) No more passes will be run.
%%
+%% done End compilation at this point.
+%%
%% {done,Ext} End compilation at this point. Produce a listing
%% as with {listing,Ext}, unless 'binary' is
%% specified, in which case the current
@@ -431,6 +439,8 @@
[{listing,fun (St) -> src_listing(Ext, St) end}];
select_passes([{listing,Ext}|_], _Opts) ->
[{listing,fun (St) -> listing(Ext, St) end}];
+select_passes([done|_], _Opts) ->
+ [];
select_passes([{done,Ext}|_], Opts) ->
select_passes([{unless,binary,{listing,Ext}}], Opts);
select_passes([{iff,Flag,Pass}|Ps], Opts) ->
@@ -513,6 +523,10 @@
standard_passes() ->
[?pass(transform_module),
+
+ {iff,'M',?pass(makedep)},
+ {iff,'M',done},
+
{iff,'dpp',{listing,"pp"}},
?pass(lint_module),
{iff,'P',{src_listing,"P"}},
@@ -852,6 +866,120 @@
errors=St#compile.errors ++ Es}}
end.
+makedep(#compile{options = Opts} = St) ->
+ Ifile = St#compile.ifile,
+ Ofile = St#compile.ofile,
+ % Get the target of the Makefile rule.
+ Target = case proplists:get_value('MT', Opts) of
+ undefined ->
+ % The target is derived from the output filename: eventually
+ % remove the current working directory to obtain a relative
+ % path.
+ Cwd = proplists:get_value(cwd, Opts),
+ case lists:prefix(Cwd, Ofile) of
+ true -> lists:nthtail(length(Cwd) + 1, Ofile);
+ false -> Ofile
+ end;
+ T ->
+ % The caller specified one with "-MT".
+ T
+ end,
+ Target1 = Target ++ ":",
+ % List the dependencies (includes) for this target.
+ {Main_Target, Phony} = makedep_add_headers(Ifile, St#compile.code,
+ [], length(Target1), Target1, "", Opts),
+ % Prepare the content of the Makefile. For instance:
+ % hello.erl: hello.hrl common.hrl
+ %
+ % Or if phony targets are enabled:
+ % hello.erl: hello.hrl common.hrl
+ %
+ % hello.hrl:
+ %
+ % common.hrl:
+ Makefile = case lists:member('MP', Opts) of
+ true -> Main_Target ++ Phony;
+ false -> Main_Target
+ end,
+ % Write this Makefile to the selected output.
+ case proplists:get_value('MF', Opts) of
+ undefined ->
+ % Output to stdout.
+ io:format("~s~n", [Makefile]);
+ O ->
+ % Output to a regular file.
+ Output = case O of
+ default -> filename:basename(Ofile, ".beam") ++ ".Pbeam";
+ _ -> O
+ end,
+ case file:open(Output, write) of
+ {ok, Io_Dev} ->
+ io:fwrite(Io_Dev, "~s~n", [Makefile]),
+ file:close(Io_Dev);
+ {error, Reason} ->
+ io:format("Couldn't open makefile `~s': ~p~n",
+ [Output, Reason])
+ end
+ end,
+ {ok, St}.
+
+makedep_add_headers(Ifile, [{attribute, _, file, {File, _}} | Rest],
+ Included, Line_Len, Main_Target, Phony, Opts) ->
+ {Included1, Line_Len1, Main_Target1, Phony1} = makedep_add_header(
+ Ifile, Included, Line_Len, Main_Target, Phony, File),
+ makedep_add_headers(Ifile, Rest, Included1, Line_Len1,
+ Main_Target1, Phony1, Opts);
+makedep_add_headers(Ifile, [{error, {_, epp, {include, file, File}}} | Rest],
+ Included, Line_Len, Main_Target, Phony, Opts) ->
+ % The header doesn't exist, do we add it?
+ case lists:member('MG', Opts) of
+ true ->
+ {Included1, Line_Len1, Main_Target1, Phony1} = makedep_add_header(
+ Ifile, Included, Line_Len, Main_Target, Phony, File),
+ makedep_add_headers(Ifile, Rest, Included1, Line_Len1,
+ Main_Target1, Phony1, Opts);
+ false ->
+ makedep_add_headers(Ifile, Rest, Included, Line_Len,
+ Main_Target, Phony, Opts)
+ end;
+makedep_add_headers(Ifile, [_ | Rest], Included, Line_Len,
+ Main_Target, Phony, Opts) ->
+ makedep_add_headers(Ifile, Rest, Included,
+ Line_Len, Main_Target, Phony, Opts);
+makedep_add_headers(_Ifile, [], _Included, _Line_Len,
+ Main_Target, Phony, _Opts) ->
+ {Main_Target, Phony}.
+
+makedep_add_header(Ifile, Included, Line_Len, Main_Target, Phony, File) ->
+ case lists:member(File, Included) of
+ true ->
+ % This file was already listed in the dependencies, skip it.
+ {Included, Line_Len, Main_Target, Phony};
+ false ->
+ Included1 = [File | Included],
+ % Remove "./" in front of the dependency filename.
+ File1 = case lists:prefix("./", File) of
+ true -> lists:nthtail(2, File);
+ false -> File
+ end,
+ % Prepare the phony target name.
+ Phony1 = case File of
+ Ifile -> Phony;
+ _ -> Phony ++ "\n\n" ++ File1 ++ ":"
+ end,
+ % Add the file to the dependencies.
+ if
+ Line_Len + 1 + length(File1) > 76 ->
+ Line_Len1 = 2 + length(File1),
+ Main_Target1 = Main_Target ++ " \\\n " ++ File1,
+ {Included1, Line_Len1, Main_Target1, Phony1};
+ true ->
+ Line_Len1 = Line_Len + 1 + length(File1),
+ Main_Target1 = Main_Target ++ " " ++ File1,
+ {Included1, Line_Len1, Main_Target1, Phony1}
+ end
+ end.
+
%% expand_module(State) -> State'
%% Do the common preprocessing of the input forms.
_______________________________________________
erlang-patches mailing list
erlang-patches@...
http://www.erlang.org/mailman/listinfo/erlang-patches