From bb6c95c0fa3b1da3261057faf6692af2450a2961 Mon Sep 17 00:00:00 2001 From: Paul Guyot Date: Sat, 1 Nov 2025 10:09:05 +0100 Subject: [PATCH] Prefer start module option to heuristics Useful for defining a start module that doesn't have start/0 (but main/1). Signed-off-by: Paul Guyot --- src/packbeam_api.erl | 35 +++++++++++++++++++++-------------- test/g.erl | 9 +++++++++ test/test_packbeam.erl | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 test/g.erl diff --git a/src/packbeam_api.erl b/src/packbeam_api.erl index 8799825..bf700e1 100644 --- a/src/packbeam_api.erl +++ b/src/packbeam_api.erl @@ -216,7 +216,7 @@ create(OutputPath, InputPaths, ApplicationModule, Prune, StartModule) -> list(InputPath) -> case file_type(InputPath) of avm -> - parse_file(InputPath, false); + parse_file(InputPath, undefined, false); _ -> throw(io_lib:format("Expected AVM file: ~p", [InputPath])) end. @@ -244,7 +244,7 @@ list(InputPath) -> extract(InputPath, AVMElementNames, OutputDir) -> case file_type(InputPath) of avm -> - ParsedFiles = parse_file(InputPath, false), + ParsedFiles = parse_file(InputPath, undefined, false), write_files(filter_names(AVMElementNames, ParsedFiles), OutputDir); _ -> throw(io_lib:format("Expected AVM file: ~p", [InputPath])) @@ -271,7 +271,7 @@ extract(InputPath, AVMElementNames, OutputDir) -> delete(OutputPath, InputPath, AVMElementNames) -> case file_type(InputPath) of avm -> - ParsedFiles = parse_file(InputPath, false), + ParsedFiles = parse_file(InputPath, undefined, false), write_packbeam(OutputPath, remove_names(AVMElementNames, ParsedFiles)); _ -> throw(io_lib:format("Expected AVM file: ~p", [InputPath])) @@ -341,7 +341,7 @@ get_flags(AVMElement) -> parse_files(InputPaths, StartModule, IncludeLines) -> Files = lists:foldl( fun(InputPath, Accum) -> - Accum ++ parse_file(InputPath, IncludeLines) + Accum ++ parse_file(InputPath, StartModule, IncludeLines) end, [], InputPaths @@ -354,10 +354,10 @@ parse_files(InputPaths, StartModule, IncludeLines) -> end. %% @private -parse_file({InputPath, ModuleName}, IncludeLines) -> - parse_file(file_type(InputPath), ModuleName, load_file(InputPath), IncludeLines); -parse_file(InputPath, IncludeLines) -> - parse_file(file_type(InputPath), InputPath, load_file(InputPath), IncludeLines). +parse_file({InputPath, ModuleName}, StartModule, IncludeLines) -> + parse_file(file_type(InputPath), ModuleName, StartModule, load_file(InputPath), IncludeLines); +parse_file(InputPath, StartModule, IncludeLines) -> + parse_file(file_type(InputPath), InputPath, StartModule, load_file(InputPath), IncludeLines). %% @private file_type(InputPath) -> @@ -595,7 +595,7 @@ filter_modules(Modules, ParsedFiles) -> ). %% @private -parse_file(beam, _ModuleName, Data, IncludeLines) -> +parse_file(beam, _ModuleName, StartModule, Data, IncludeLines) -> {ok, Module, Chunks} = beam_lib:all_chunks(Data), {UncompressedChunks, UncompressedLiterals} = maybe_uncompress_literals(Chunks), FilteredChunks = filter_chunks(UncompressedChunks, IncludeLines), @@ -603,10 +603,17 @@ parse_file(beam, _ModuleName, Data, IncludeLines) -> {ok, {Module, ChunkRefs}} = beam_lib:chunks(Data, [imports, exports, atoms]), Exports = proplists:get_value(exports, ChunkRefs), Flags = - case lists:member({start, 0}, Exports) of - true -> + if + StartModule =:= Module -> ?BEAM_CODE_FLAG bor ?BEAM_START_FLAG; - _ -> + StartModule =:= undefined -> + case lists:member({start, 0}, Exports) of + true -> + ?BEAM_CODE_FLAG bor ?BEAM_START_FLAG; + _ -> + ?BEAM_CODE_FLAG + end; + true -> ?BEAM_CODE_FLAG end, [ @@ -619,14 +626,14 @@ parse_file(beam, _ModuleName, Data, IncludeLines) -> {uncompressed_literals, UncompressedLiterals} ] ]; -parse_file(avm, ModuleName, Data, _IncludeLines) -> +parse_file(avm, ModuleName, _StartModule, Data, _IncludeLines) -> case Data of <> -> parse_avm_data(AVMData); _ -> throw(io_lib:format("Invalid AVM header: ~p", [ModuleName])) end; -parse_file(normal, ModuleName, Data, _IncludeLines) -> +parse_file(normal, ModuleName, _StartModule, Data, _IncludeLines) -> DataSize = byte_size(Data), Blob = <>, [[{element_name, ModuleName}, {flags, ?NORMAL_FILE_FLAG}, {data, Blob}]]. diff --git a/test/g.erl b/test/g.erl new file mode 100644 index 0000000..4e6e662 --- /dev/null +++ b/test/g.erl @@ -0,0 +1,9 @@ +% Copyright 2025 Paul Guyot +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + +-module(g). + +-export([main/1]). + +main(_Args) -> + c:test(). diff --git a/test/test_packbeam.erl b/test/test_packbeam.erl index d4f826b..94daf59 100644 --- a/test/test_packbeam.erl +++ b/test/test_packbeam.erl @@ -112,6 +112,40 @@ packbeam_create_start_test() -> ok. +packbeam_create_start_main_test() -> + AVMFile = dest_dir("packbeam_create_start_main_test.avm"), + ?assertMatch( + ok, + packbeam_api:create( + AVMFile, + [ + test_beam_path("c.beam"), + test_beam_path("f.beam"), + test_beam_path("g.beam") + ], + false, + g + ) + ), + + ParsedFiles = packbeam_api:list(AVMFile), + + ?assert(is_list(ParsedFiles)), + ?assertEqual(length(ParsedFiles), 3), + + [GFile | _] = ParsedFiles, + + % io:format(user, "~p~n", [ParsedFiles]), + + ?assertMatch(g, get_module(GFile)), + ?assertMatch("g.beam", get_module_name(GFile)), + ?assert(is_beam(GFile)), + ?assert(is_start(GFile)), + ?assert(lists:member({main, 1}, get_exports(GFile))), + ?assert(not lists:member({start, 0}, get_exports(GFile))), + + ok. + packbeam_create_prune_test() -> AVMFile = dest_dir("packbeam_create_prune_test.avm"), ?assertMatch(