Skip to content

Commit d7abb76

Browse files
authored
Merge pull request #47 from pguyot/w48/add-lib-option
2 parents 4de8162 + e6a75c4 commit d7abb76

File tree

3 files changed

+144
-22
lines changed

3 files changed

+144
-22
lines changed

src/packbeam.erl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ print_help() ->
129129
" [<input-file>]+ is a list of one or more input files,~n"
130130
" and <options> are among the following:~n"
131131
" [--prune|-p] Prune dependencies~n"
132+
" [--lib|-l] Create a library avm, with no start module~n"
132133
" [--start|-s <module>] Start module~n"
133134
" [--remove_lines|-r] Remove line number information from AVM files~n"
134135
"~n"
@@ -328,6 +329,10 @@ parse_args(["-p" | T], {Opts, Args}) ->
328329
parse_args(["--prune" | T], {Opts, Args});
329330
parse_args(["--prune" | T], {Opts, Args}) ->
330331
parse_args(T, {Opts#{prune => true}, Args});
332+
parse_args(["-l" | T], {Opts, Args}) ->
333+
parse_args(["--lib" | T], {Opts, Args});
334+
parse_args(["--lib" | T], {Opts, Args}) ->
335+
parse_args(T, {Opts#{lib => true}, Args});
331336
parse_args(["-start", Module | T], {Opts, Args}) ->
332337
io:format("WARNING. Deprecated option. Use --start instead.~n"),
333338
parse_args(["--start", Module | T], {Opts, Args});

src/packbeam_api.erl

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666

6767
-define(DEFAULT_OPTIONS, #{
6868
prune => false,
69+
lib => false,
6970
start_module => undefined,
7071
application_module => undefined,
7172
include_lines => true
@@ -86,6 +87,7 @@
8687
%%
8788
%% where `DefaultOptions' is `#{
8889
%% prune => false,
90+
%% lib => false,
8991
%% start_module => undefined,
9092
%% application_module => undefined,
9193
%% include_lines => false
@@ -120,11 +122,12 @@ create(OutputPath, InputPaths) ->
120122
create(OutputPath, InputPaths, Options) ->
121123
#{
122124
prune := Prune,
125+
lib := Lib,
123126
start_module := StartModule,
124127
application_module := ApplicationModule,
125128
include_lines := IncludeLines
126129
} = maps:merge(?DEFAULT_OPTIONS, Options),
127-
ParsedFiles = parse_files(InputPaths, StartModule, IncludeLines),
130+
ParsedFiles = parse_files(InputPaths, Lib, StartModule, IncludeLines),
128131
write_packbeam(
129132
OutputPath,
130133
case Prune of
@@ -216,7 +219,7 @@ create(OutputPath, InputPaths, ApplicationModule, Prune, StartModule) ->
216219
list(InputPath) ->
217220
case file_type(InputPath) of
218221
avm ->
219-
parse_file(InputPath, undefined, false);
222+
parse_file(InputPath, false, undefined, false);
220223
_ ->
221224
throw(io_lib:format("Expected AVM file: ~p", [InputPath]))
222225
end.
@@ -244,7 +247,7 @@ list(InputPath) ->
244247
extract(InputPath, AVMElementNames, OutputDir) ->
245248
case file_type(InputPath) of
246249
avm ->
247-
ParsedFiles = parse_file(InputPath, undefined, false),
250+
ParsedFiles = parse_file(InputPath, false, undefined, false),
248251
write_files(filter_names(AVMElementNames, ParsedFiles), OutputDir);
249252
_ ->
250253
throw(io_lib:format("Expected AVM file: ~p", [InputPath]))
@@ -271,7 +274,7 @@ extract(InputPath, AVMElementNames, OutputDir) ->
271274
delete(OutputPath, InputPath, AVMElementNames) ->
272275
case file_type(InputPath) of
273276
avm ->
274-
ParsedFiles = parse_file(InputPath, undefined, false),
277+
ParsedFiles = parse_file(InputPath, false, undefined, false),
275278
write_packbeam(OutputPath, remove_names(AVMElementNames, ParsedFiles));
276279
_ ->
277280
throw(io_lib:format("Expected AVM file: ~p", [InputPath]))
@@ -338,10 +341,10 @@ get_flags(AVMElement) ->
338341
proplists:get_value(flags, AVMElement).
339342

340343
%% @private
341-
parse_files(InputPaths, StartModule, IncludeLines) ->
344+
parse_files(InputPaths, Lib, StartModule, IncludeLines) ->
342345
Files = lists:foldl(
343346
fun(InputPath, Accum) ->
344-
Accum ++ parse_file(InputPath, StartModule, IncludeLines)
347+
Accum ++ parse_file(InputPath, Lib, StartModule, IncludeLines)
345348
end,
346349
[],
347350
InputPaths
@@ -354,10 +357,14 @@ parse_files(InputPaths, StartModule, IncludeLines) ->
354357
end.
355358

356359
%% @private
357-
parse_file({InputPath, ModuleName}, StartModule, IncludeLines) ->
358-
parse_file(file_type(InputPath), ModuleName, StartModule, load_file(InputPath), IncludeLines);
359-
parse_file(InputPath, StartModule, IncludeLines) ->
360-
parse_file(file_type(InputPath), InputPath, StartModule, load_file(InputPath), IncludeLines).
360+
parse_file({InputPath, ModuleName}, Lib, StartModule, IncludeLines) ->
361+
parse_file(
362+
file_type(InputPath), ModuleName, Lib, StartModule, load_file(InputPath), IncludeLines
363+
);
364+
parse_file(InputPath, Lib, StartModule, IncludeLines) ->
365+
parse_file(
366+
file_type(InputPath), InputPath, Lib, StartModule, load_file(InputPath), IncludeLines
367+
).
361368

362369
%% @private
363370
file_type(InputPath) ->
@@ -595,7 +602,7 @@ filter_modules(Modules, ParsedFiles) ->
595602
).
596603

597604
%% @private
598-
parse_file(beam, _ModuleName, StartModule, Data, IncludeLines) ->
605+
parse_file(beam, _ModuleName, Lib, StartModule, Data, IncludeLines) ->
599606
{ok, Module, Chunks} = beam_lib:all_chunks(Data),
600607
{UncompressedChunks, UncompressedLiterals} = maybe_uncompress_literals(Chunks),
601608
FilteredChunks = filter_chunks(UncompressedChunks, IncludeLines),
@@ -606,7 +613,7 @@ parse_file(beam, _ModuleName, StartModule, Data, IncludeLines) ->
606613
if
607614
StartModule =:= Module ->
608615
?BEAM_CODE_FLAG bor ?BEAM_START_FLAG;
609-
StartModule =:= undefined ->
616+
StartModule =:= undefined andalso Lib =:= false ->
610617
case lists:member({start, 0}, Exports) of
611618
true ->
612619
?BEAM_CODE_FLAG bor ?BEAM_START_FLAG;
@@ -626,14 +633,14 @@ parse_file(beam, _ModuleName, StartModule, Data, IncludeLines) ->
626633
{uncompressed_literals, UncompressedLiterals}
627634
]
628635
];
629-
parse_file(avm, ModuleName, _StartModule, Data, _IncludeLines) ->
636+
parse_file(avm, ModuleName, Lib, StartModule, Data, _IncludeLines) ->
630637
case Data of
631638
<<?AVM_HEADER, AVMData/binary>> ->
632-
parse_avm_data(AVMData);
639+
parse_avm_data(Lib, StartModule, AVMData);
633640
_ ->
634641
throw(io_lib:format("Invalid AVM header: ~p", [ModuleName]))
635642
end;
636-
parse_file(normal, ModuleName, _StartModule, Data, _IncludeLines) ->
643+
parse_file(normal, ModuleName, _Lib, _StartModule, Data, _IncludeLines) ->
637644
DataSize = byte_size(Data),
638645
Blob = <<DataSize:32, Data/binary>>,
639646
[[{element_name, ModuleName}, {flags, ?NORMAL_FILE_FLAG}, {data, Blob}]].
@@ -664,21 +671,41 @@ reorder_start_module(StartModule, Files) ->
664671
end.
665672

666673
%% @private
667-
parse_avm_data(AVMData) ->
668-
parse_avm_data(AVMData, []).
674+
parse_avm_data(Lib, StartModule, AVMData) ->
675+
parse_avm_data(Lib, StartModule, AVMData, []).
669676

670677
%% @private
671-
parse_avm_data(<<"">>, Accum) ->
678+
parse_avm_data(_Lib, _StartModule, <<"">>, Accum) ->
672679
lists:reverse(Accum);
673-
parse_avm_data(<<0:32/integer, _AVMRest/binary>>, Accum) ->
680+
parse_avm_data(_Lib, _StartModule, <<0:32/integer, _AVMRest/binary>>, Accum) ->
674681
lists:reverse(Accum);
675-
parse_avm_data(<<Size:32/integer, AVMRest/binary>>, Accum) ->
682+
parse_avm_data(Lib, StartModule, <<Size:32/integer, AVMRest/binary>>, Accum) ->
676683
SizeWithoutSizeField = Size - 4,
677684
case SizeWithoutSizeField =< erlang:byte_size(AVMRest) of
678685
true ->
679686
<<AVMBeamData:SizeWithoutSizeField/binary, AVMNext/binary>> = AVMRest,
680-
Beam = parse_beam(AVMBeamData, [], in_header, 0, []),
681-
parse_avm_data(AVMNext, [Beam | Accum]);
687+
Beam0 = parse_beam(AVMBeamData, [], in_header, 0, []),
688+
{flags, BeamFlags0} = lists:keyfind(flags, 1, Beam0),
689+
BeamModule =
690+
case lists:keyfind(module, 1, Beam0) of
691+
false -> undefined;
692+
{module, BeamModule0} -> BeamModule0
693+
end,
694+
BeamFlags1 =
695+
if
696+
Lib ->
697+
BeamFlags0 band (bnot ?BEAM_START_FLAG);
698+
BeamModule =:= undefined ->
699+
BeamFlags0;
700+
StartModule =:= BeamModule ->
701+
BeamFlags0 bor ?BEAM_START_FLAG;
702+
StartModule =/= undefined ->
703+
BeamFlags0 band (bnot ?BEAM_START_FLAG);
704+
true ->
705+
BeamFlags0
706+
end,
707+
Beam1 = lists:keystore(flags, 1, Beam0, {flags, BeamFlags1}),
708+
parse_avm_data(Lib, StartModule, AVMNext, [Beam1 | Accum]);
682709
_ ->
683710
throw(
684711
io_lib:format("Invalid AVM data size: ~p (AVMRest=~p)", [

test/test_packbeam.erl

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,96 @@ packbeam_create_start_main_test() ->
146146

147147
ok.
148148

149+
packbeam_create_lib_test() ->
150+
AVMFile = dest_dir("packbeam_create_lib_test.avm"),
151+
?assertMatch(
152+
ok,
153+
packbeam_api:create(
154+
AVMFile,
155+
[
156+
test_beam_path("c.beam"),
157+
test_beam_path("f.beam"),
158+
test_beam_path("g.beam")
159+
],
160+
#{
161+
lib => true
162+
}
163+
)
164+
),
165+
166+
ParsedFiles = packbeam_api:list(AVMFile),
167+
168+
?assert(is_list(ParsedFiles)),
169+
?assertEqual(length(ParsedFiles), 3),
170+
171+
[CFile, FFile, GFile] = ParsedFiles,
172+
173+
% io:format(user, "~p~n", [ParsedFiles]),
174+
175+
?assertMatch(g, get_module(GFile)),
176+
?assertMatch("g.beam", get_module_name(GFile)),
177+
?assert(is_beam(GFile)),
178+
?assertNot(is_start(CFile)),
179+
?assertNot(is_start(FFile)),
180+
?assertNot(is_start(GFile)),
181+
?assert(lists:member({main, 1}, get_exports(GFile))),
182+
?assertNot(lists:member({start, 0}, get_exports(GFile))),
183+
184+
ok.
185+
186+
packbeam_create_lib_from_avm_test() ->
187+
AVMFile0 = dest_dir("packbeam_create_start_main_test.avm"),
188+
?assertMatch(
189+
ok,
190+
packbeam_api:create(
191+
AVMFile0,
192+
[
193+
test_beam_path("c.beam"),
194+
test_beam_path("f.beam"),
195+
test_beam_path("g.beam")
196+
],
197+
false,
198+
g
199+
)
200+
),
201+
202+
AVMFile = dest_dir("packbeam_create_lib_test.avm"),
203+
?assertMatch(
204+
ok,
205+
packbeam_api:create(
206+
AVMFile,
207+
[
208+
test_beam_path("a.beam"),
209+
AVMFile0
210+
],
211+
#{
212+
lib => true
213+
}
214+
)
215+
),
216+
217+
ParsedFiles = packbeam_api:list(AVMFile),
218+
219+
?assert(is_list(ParsedFiles)),
220+
?assertEqual(length(ParsedFiles), 4),
221+
222+
% g was written first in AVMFile0
223+
[AFile, GFile, CFile, FFile] = ParsedFiles,
224+
225+
% io:format(user, "~p~n", [ParsedFiles]),
226+
227+
?assertMatch(g, get_module(GFile)),
228+
?assertMatch("g.beam", get_module_name(GFile)),
229+
?assert(is_beam(GFile)),
230+
?assertNot(is_start(AFile)),
231+
?assertNot(is_start(GFile)),
232+
?assertNot(is_start(CFile)),
233+
?assertNot(is_start(FFile)),
234+
?assert(lists:member({main, 1}, get_exports(GFile))),
235+
?assertNot(lists:member({start, 0}, get_exports(GFile))),
236+
237+
ok.
238+
149239
packbeam_create_prune_test() ->
150240
AVMFile = dest_dir("packbeam_create_prune_test.avm"),
151241
?assertMatch(

0 commit comments

Comments
 (0)