diff --git a/.github/workflows/build-and-test-other.yaml b/.github/workflows/build-and-test-other.yaml index a22598fa10..63e2d31789 100644 --- a/.github/workflows/build-and-test-other.yaml +++ b/.github/workflows/build-and-test-other.yaml @@ -59,6 +59,7 @@ jobs: build_tests/**/*.avm build_tests/**/*.beam build_tests/**/*.hrl + build_tests/tools/packbeam/packbeam retention-days: 1 build-and-test-other: @@ -159,7 +160,6 @@ jobs: mkdir -p build && cd build && cmake .. ${{ matrix.cmake_opts }} && - make PackBEAM && cp ../build_tests/tests/erlang_tests/*.beam tests/erlang_tests/ && cp ../build_tests/tests/erlang_tests/code_load/*.{avm,beam,hrl} tests/erlang_tests/code_load/ && mkdir -p tests/erlang_tests/code_load/beams/ && @@ -168,6 +168,7 @@ jobs: cp ../build_tests/tests/libs/estdlib/*.avm tests/libs/estdlib/ && cp ../build_tests/tests/libs/eavmlib/*.avm tests/libs/eavmlib/ && cp ../build_tests/tests/libs/alisp/*.avm tests/libs/alisp/ && + cp ../build_tests/tools/packbeam/packbeam tools/packbeam/packbeam && touch tests/erlang_tests/code_load/code_load_pack.avm && touch tests/erlang_tests/code_load/code_load_pack_data.hrl && make AtomVM && diff --git a/CMakeModules/BuildElixir.cmake b/CMakeModules/BuildElixir.cmake index 1d97f60eae..cd496ef1bc 100644 --- a/CMakeModules/BuildElixir.cmake +++ b/CMakeModules/BuildElixir.cmake @@ -38,15 +38,15 @@ macro(pack_archive avm_name) ) if(AVM_RELEASE) - set(INCLUDE_LINES "") + set(INCLUDE_LINES "--remove_lines") else() - set(INCLUDE_LINES "-i") + set(INCLUDE_LINES "") endif() add_custom_command( OUTPUT ${avm_name}.avm DEPENDS ${avm_name}_beams PackBEAM - COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM -a ${INCLUDE_LINES} ${avm_name}.avm ${BEAMS} + COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam create ${INCLUDE_LINES} ${avm_name}.avm ${BEAMS} COMMENT "Packing archive ${avm_name}.avm" VERBATIM ) @@ -74,9 +74,9 @@ macro(pack_runnable avm_name main) ) if(AVM_RELEASE) - set(INCLUDE_LINES "") + set(INCLUDE_LINES "--remove_lines") else() - set(INCLUDE_LINES "-i") + set(INCLUDE_LINES "") endif() foreach(archive_name ${ARGN}) @@ -91,7 +91,7 @@ macro(pack_runnable avm_name main) add_custom_command( OUTPUT ${avm_name}.avm DEPENDS ${avm_name}_main ${ARCHIVE_TARGETS} PackBEAM - COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${INCLUDE_LINES} ${avm_name}.avm Elixir.${main}.beam ${ARCHIVES} + COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam create -p -s Elixir.${main} ${INCLUDE_LINES} ${avm_name}.avm Elixir.${main}.beam ${ARCHIVES} COMMENT "Packing runnable ${avm_name}.avm" VERBATIM ) @@ -146,9 +146,9 @@ macro(pack_test avm_name main) ) if(AVM_RELEASE) - set(INCLUDE_LINES "") + set(INCLUDE_LINES "--remove_lines") else() - set(INCLUDE_LINES "-i") + set(INCLUDE_LINES "") endif() # Set up standard libraries @@ -168,7 +168,7 @@ macro(pack_test avm_name main) add_custom_command( OUTPUT ${avm_name}.avm DEPENDS ${avm_name}_main ${avm_name}_tests ${ARCHIVE_TARGETS} PackBEAM - COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${INCLUDE_LINES} ${avm_name}.avm Elixir.${main}.beam ${TEST_BEAMS} ${ARCHIVES} + COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam create ${INCLUDE_LINES} ${avm_name}.avm Elixir.${main}.beam ${TEST_BEAMS} ${ARCHIVES} COMMENT "Packing test ${avm_name}.avm" VERBATIM ) diff --git a/CMakeModules/BuildErlang.cmake b/CMakeModules/BuildErlang.cmake index 41ba01018d..e1a4df7b7c 100644 --- a/CMakeModules/BuildErlang.cmake +++ b/CMakeModules/BuildErlang.cmake @@ -41,15 +41,15 @@ macro(pack_archive avm_name) endforeach() if(AVM_RELEASE) - set(INCLUDE_LINES "") + set(INCLUDE_LINES "--remove_lines") else() - set(INCLUDE_LINES "-i") + set(INCLUDE_LINES "") endif() add_custom_command( OUTPUT ${avm_name}.avm DEPENDS ${pack_archive_${avm_name}_beams} PackBEAM - COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM -a ${INCLUDE_LINES} ${avm_name}.avm ${pack_archive_${avm_name}_beams} + COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam create ${INCLUDE_LINES} ${avm_name}.avm ${pack_archive_${avm_name}_beams} COMMENT "Packing archive ${avm_name}.avm" VERBATIM ) @@ -135,16 +135,16 @@ macro(pack_lib avm_name) endforeach() if(AVM_RELEASE) - set(INCLUDE_LINES "") + set(INCLUDE_LINES "--remove_lines") else() - set(INCLUDE_LINES "-i") + set(INCLUDE_LINES "") endif() add_custom_command( OUTPUT ${avm_name}.avm DEPENDS ${pack_lib_${avm_name}_archive_targets} PackBEAM - COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM -a ${INCLUDE_LINES} ${avm_name}.avm ${pack_lib_${avm_name}_emu_archives} ${pack_lib_${avm_name}_archives} - COMMENT "Packing lib ${avm_name}.avm" + COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam create ${INCLUDE_LINES} ${avm_name}.avm ${pack_lib_${avm_name}_archives} + COMMENT "Packing runnable ${avm_name}.avm" VERBATIM ) if(NOT AVM_DISABLE_JIT) @@ -225,15 +225,15 @@ macro(pack_runnable avm_name main) endif() if(AVM_RELEASE) - set(INCLUDE_LINES "") + set(INCLUDE_LINES "--remove_lines") else() - set(INCLUDE_LINES "-i") + set(INCLUDE_LINES "") endif() add_custom_command( OUTPUT ${avm_name}.avm DEPENDS ${avm_name}_main ${pack_runnable_${avm_name}_archive_targets} PackBEAM - COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${INCLUDE_LINES} ${avm_name}.avm ${main}.beam ${pack_runnable_${avm_name}_archives} + COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam create -p -s ${main} ${INCLUDE_LINES} ${avm_name}.avm ${main}.beam ${pack_runnable_${avm_name}_archives} COMMENT "Packing runnable ${avm_name}.avm" VERBATIM ) @@ -266,9 +266,9 @@ macro(pack_test test_avm_name) endforeach() if(AVM_RELEASE) - set(INCLUDE_LINES "") + set(INCLUDE_LINES "--remove_lines") else() - set(INCLUDE_LINES "-i") + set(INCLUDE_LINES "") endif() add_custom_command( @@ -281,7 +281,7 @@ macro(pack_test test_avm_name) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${test_avm_name}.avm DEPENDS ${pack_test_${test_avm_name}_archive_targets} PackBEAM tests.beam - COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${INCLUDE_LINES} ${CMAKE_CURRENT_BINARY_DIR}/${test_avm_name}.avm ${CMAKE_CURRENT_BINARY_DIR}/tests.beam ${pack_test_${test_avm_name}_archives} + COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam create ${INCLUDE_LINES} ${CMAKE_CURRENT_BINARY_DIR}/${test_avm_name}.avm ${CMAKE_CURRENT_BINARY_DIR}/tests.beam ${pack_test_${test_avm_name}_archives} COMMENT "Packing runnable ${test_avm_name}.avm" VERBATIM ) @@ -313,15 +313,15 @@ macro(pack_eunit test_avm_name) endforeach() if(AVM_RELEASE) - set(INCLUDE_LINES "") + set(INCLUDE_LINES "--remove_lines") else() - set(INCLUDE_LINES "-i") + set(INCLUDE_LINES "") endif() add_custom_command( OUTPUT ${test_avm_name}.avm DEPENDS ${pack_eunit_${test_avm_name}_archive_targets} PackBEAM ${CMAKE_BINARY_DIR}/libs/etest/src/beams/eunit.beam - COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${INCLUDE_LINES} ${CMAKE_CURRENT_BINARY_DIR}/${test_avm_name}.avm ${CMAKE_BINARY_DIR}/libs/etest/src/beams/eunit.beam ${pack_eunit_${test_avm_name}_archives} + COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam create ${INCLUDE_LINES} ${CMAKE_CURRENT_BINARY_DIR}/${test_avm_name}.avm ${CMAKE_BINARY_DIR}/libs/etest/src/beams/eunit.beam ${pack_eunit_${test_avm_name}_archives} COMMENT "Packing runnable ${test_avm_name}.avm" VERBATIM ) @@ -359,7 +359,7 @@ macro(pack_uf2 avm_name main) add_custom_command( OUTPUT ${avm_name}.avm DEPENDS ${avm_name}_main ${pack_uf2_${avm_name}_archive_targets} PackBEAM - COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${avm_name}.avm ${main}.beam ${pack_uf2_${avm_name}_archives} + COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam create -p -s ${main} ${avm_name}.avm ${main}.beam ${pack_uf2_${avm_name}_archives} COMMENT "Packing runnable ${avm_name}.avm" VERBATIM ) diff --git a/CMakeModules/BuildGleam.cmake b/CMakeModules/BuildGleam.cmake index b688820dfe..bfc58a1e78 100644 --- a/CMakeModules/BuildGleam.cmake +++ b/CMakeModules/BuildGleam.cmake @@ -28,9 +28,9 @@ macro(pack_gleam_archive avm_name) endforeach() if(AVM_RELEASE) - set(INCLUDE_LINES "") + set(INCLUDE_LINES "--remove_lines") else() - set(INCLUDE_LINES "-i") + set(INCLUDE_LINES "") endif() add_custom_command( @@ -39,7 +39,7 @@ macro(pack_gleam_archive avm_name) COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/gleam.toml ${CMAKE_CURRENT_SOURCE_DIR}/manifest.toml ${CMAKE_CURRENT_BINARY_DIR}/ COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_BINARY_DIR}/src COMMAND gleam export erlang-shipment - COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM -a ${INCLUDE_LINES} ${avm_name}.avm ${BEAMS} + COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam create ${INCLUDE_LINES} ${avm_name}.avm ${BEAMS} COMMENT "Packing gleam archive ${avm_name}.avm" VERBATIM ) @@ -54,9 +54,9 @@ macro(pack_gleam_runnable avm_name main) list(APPEND BEAMS ${CMAKE_CURRENT_BINARY_DIR}/build/prod/erlang/${main}/ebin/${main}.beam) if(AVM_RELEASE) - set(INCLUDE_LINES "") + set(INCLUDE_LINES "--remove_lines") else() - set(INCLUDE_LINES "-i") + set(INCLUDE_LINES "") endif() foreach(archive_name ${ARGN}) @@ -74,7 +74,7 @@ macro(pack_gleam_runnable avm_name main) COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/gleam.toml ${CMAKE_CURRENT_SOURCE_DIR}/manifest.toml ${CMAKE_CURRENT_BINARY_DIR}/ COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_BINARY_DIR}/src COMMAND gleam export erlang-shipment - COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${INCLUDE_LINES} ${avm_name}.avm ${BEAMS} build/prod/erlang/gleam_stdlib/ebin/*.beam ${ARCHIVES} + COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam create ${INCLUDE_LINES} ${avm_name}.avm ${BEAMS} build/prod/erlang/gleam_stdlib/ebin/*.beam ${ARCHIVES} COMMENT "Packing gleam runnable ${avm_name}.avm" ) diff --git a/src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt b/src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt index e2d67269e8..cfdd24a445 100644 --- a/src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt +++ b/src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt @@ -57,7 +57,7 @@ compile_erlang(test_tz) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/esp32_test_modules.avm" - COMMAND HostAtomVM-prefix/src/HostAtomVM-build/tools/packbeam/PackBEAM -i esp32_test_modules.avm + COMMAND HostAtomVM-prefix/src/HostAtomVM-build/tools/packbeam/packbeam create esp32_test_modules.avm HostAtomVM-prefix/src/HostAtomVM-build/libs/atomvmlib.avm test_esp_partition.beam test_file.beam diff --git a/src/platforms/rp2/tests/test_erl_sources/CMakeLists.txt b/src/platforms/rp2/tests/test_erl_sources/CMakeLists.txt index cbdf581eef..e1b3a193d1 100644 --- a/src/platforms/rp2/tests/test_erl_sources/CMakeLists.txt +++ b/src/platforms/rp2/tests/test_erl_sources/CMakeLists.txt @@ -43,7 +43,7 @@ compile_erlang(test_crypto ../../../esp32/test/main/test_erl_sources/) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rp2_test_modules.avm" - COMMAND HostAtomVM-prefix/src/HostAtomVM-build/tools/packbeam/PackBEAM -i rp2_test_modules.avm + COMMAND HostAtomVM-prefix/src/HostAtomVM-build/tools/packbeam/packbeam create rp2_test_modules.avm HostAtomVM-prefix/src/HostAtomVM-build/libs/atomvmlib.avm test_clocks.beam test_smp.beam diff --git a/tools/packbeam/CMakeLists.txt b/tools/packbeam/CMakeLists.txt index 09cc143059..40708589e9 100644 --- a/tools/packbeam/CMakeLists.txt +++ b/tools/packbeam/CMakeLists.txt @@ -1,7 +1,7 @@ # # This file is part of AtomVM. # -# Copyright 2018-2020 Davide Bettio +# Copyright 2025 Paul Guyot # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,41 +21,30 @@ cmake_minimum_required (VERSION 3.13) project (PackBEAM) -set(PACKBEAM_SOURCES - packbeam.c -) +set(PACKBEAM_PATH "" CACHE PATH "Path to PackBEAM source tree. If unset, hex package will be used") -add_executable(PackBEAM ${PACKBEAM_SOURCES}) -target_compile_features(PackBEAM PUBLIC c_std_11) -if(CMAKE_COMPILER_IS_GNUCC) - target_compile_options(PackBEAM PUBLIC -Wall -pedantic -Wextra -ggdb) +if(NOT PACKBEAM_PATH STREQUAL "") + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/_checkouts) + file(CREATE_LINK ${PACKBEAM_PATH} ${CMAKE_CURRENT_BINARY_DIR}/_checkouts/packbeam SYMBOLIC) endif() -find_package(ZLIB) -if (ZLIB_FOUND) - target_compile_definitions(PackBEAM PUBLIC WITH_ZLIB) - target_link_libraries(PackBEAM PRIVATE ${ZLIB_LIBRARIES}) -endif (ZLIB_FOUND) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/rebar.config + DESTINATION ${CMAKE_CURRENT_BINARY_DIR} +) -if((${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") OR - (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") OR - (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") OR - (${CMAKE_SYSTEM_NAME} STREQUAL "DragonFly")) - target_include_directories(PackBEAM PRIVATE ../../src/platforms/generic_unix/lib) -else() - message(FATAL_ERROR "Unsupported platform: ${CMAKE_SYSTEM_NAME}") -endif() -set( - PLATFORM_LIB_SUFFIX - ${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR} +add_custom_command( + OUTPUT packbeam + COMMAND rebar3 escriptize && cp _build/default/bin/packbeam . + VERBATIM ) -target_link_libraries(PackBEAM PRIVATE libAtomVM${PLATFORM_LIB_SUFFIX}) -target_include_directories(PackBEAM PUBLIC ../../src/libAtomVM) -target_link_libraries(PackBEAM PRIVATE libAtomVM) +add_custom_target( + PackBEAM ALL + DEPENDS packbeam +) -if (COVERAGE) - include(CodeCoverage) - append_coverage_compiler_flags_to_target(PackBEAM) - append_coverage_linker_flags_to_target(PackBEAM) -endif() +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/packbeam + DESTINATION bin + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE +) diff --git a/tools/packbeam/README.md b/tools/packbeam/README.md deleted file mode 100644 index ebbf3290ff..0000000000 --- a/tools/packbeam/README.md +++ /dev/null @@ -1,111 +0,0 @@ - - -# `PackBEAM` - -The `PackBEAM` tool is used to pack a collection of BEAM files into a single AtomVM (`.avm`) file, or to list the contents of a previously created AVM file. - -Packing multiple BEAM files into a single AVM file allows you to load multiple BEAM modules into AtomVM, instead of just running a single module. - -Two types of AVM file may be created: - -* Runnable AVM files, suitable for flashing or supplying to the AtomVM command; -* Archive AVM files, which are used to aggregate collections of BEAM files into libraries, for subsequent use when creating runnable AVM files. - -The only difference between runnable and archive AVM files is that the first module in a runnable AVM file contains the exported function `start/0`, which is the initial entry point for the AtomVM program. Archive AVM files do not require a `start/0` entrypoint. - -Archive AVM files are typically used to create AtomVM "libraries", which can then be used as inputs when creating a runnable AVM. - -When creating an AVM file, you must specify: - -* Name name of the output AVM file, first in the list, followed by -* a sequence of compiled BEAM files (as compiled by the `erlc` compiler), or previously created AVM files. - * If you are creating a runnable AVM, the first file in this sequence must either be a BEAM file that contain an exported `start/0` function or an AVM file whose first module contain an exported `start/0` function. - -When listing modules in an AVM file, just specify the AVM file to list its included modules. - -## Usage - - Usage: PackBEAM - Options: - -h Print this help menu. - -l List the contents of an AVM file. - [-a] + Create an AVM file (archive if -a specified). - -## Examples - -Consider the three modules `mail.erl`, `foo.erl`, and `bar.erl`, defined as follows: - -`main.erl`: - - -module(main). - -export([start/0]). - - start() -> - hello:say_hello(world). - -`hello.erl`: - - -module(hello). - -export([say_hello/1]). - - say_hello(Term) -> - greet:say(hello, Term). - -`greet.erl`: - - -module(greet). - -export([say/2]). - - say(Greeting, Entity) -> - erlang:display({Greeting, Entity}). - -You can compile these modules using `erlc`: - - shell$ erlc main.erl hello.erl greet.erl - -And then pack the second two beam files into an archive using the `-a flag` as follows: - - shell$ PackBEAM -a lib.avm hello.beam greet.beam - -This will create the archive AVM `lib.avm`. - -You can list the contents of this AVM via the `-l` flag: - - shell$ PackBEAM -l lib.avm - greet.beam - hello.beam - -Note that this AVM file is not runnable: - - shell$ AtomVM lib.avm - lib.avm cannot be started. - -To create a runnable AVM file, specify the output AVM file, the beam file that contains the `start/0` function, and a sequence of 1 or more BEAM files or AVM files. For example, - - shell$ PackBEAM -a main.avm main.beam lib.avm - -This will create the output file `main.avm`, which you can run through AtomVM, e.g., - - shell$ AtomVM main.avm - {hello,world} - Return value: 3b - -You can list the modules in an AVM file via the `-l` flag: - - shell$ PackBEAM -l main.avm - main.beam * - hello.beam - greet.beam - -The module containing the `start/0` entrypoint will contain an asterisk (`*`). - -## Additional Notes - -* `PackBeam` does not require that BEAM or AVM files have any specific file suffix. You may use any suffix you like, though `.beam` and `.avm` are conventional. -* `PackBeam` makes no effort to find beam files with the `start/0` function, and order them first. In order to create a runnable AVM file, the first input file must be either a BEAM file with an exported `start/0` function, or another AVM file whose first module has an exported `start/0` function. -* `PackBeam` makes no effort the remove duplicates modules that are packed. AtomVM will only use the first module by name in an AVM files, so adding duplicate modules has no effect on the runtime behavior of the output AVM file. -* Because `PackBeam` uses positional arguments when creating AVM files, an attempt to specify a BEAM file (or other non-AVM file) as output, if it already exists, will result in a failure. This is to prevent accidental omission of an output AVM file as the first argument to `PackBeam` when creating AVM files. diff --git a/tools/packbeam/packbeam.c b/tools/packbeam/packbeam.c deleted file mode 100644 index 63f1f726d1..0000000000 --- a/tools/packbeam/packbeam.c +++ /dev/null @@ -1,498 +0,0 @@ -/* - * This file is part of AtomVM. - * - * Copyright 2018 Davide Bettio - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later - */ - -#include -#include -#include -#include -#include -#include - -#ifdef WITH_ZLIB -#include -#endif - -#include "iff.c" -#include "avmpack.h" -#include "mapped_file.h" - -#define LITT_UNCOMPRESSED_SIZE_OFFSET 8 -#define LITT_HEADER_SIZE 12 - -#define END_OF_FILE 0 -#define BEAM_START_FLAG 1 -#define BEAM_CODE_FLAG 2 -#define BUF_SIZE 1024 - -typedef struct FileData { - uint8_t *data; - size_t size; -} FileData; - -static void pad_and_align(FILE *f); -bool are_literals_compressed(const uint8_t *litT); -static void *uncompress_literals(const uint8_t *litT, int size, size_t *uncompressedSize); -static void add_module_header(FILE *f, const char *module_name, uint32_t flags); -static void pack_beam_file(FILE *pack, const uint8_t *data, size_t size, const char *filename, int is_entrypoint, bool include_lines); - -static int do_pack(int argc, char **argv, int is_archive, bool include_lines); -static int do_list(int argc, char **argv); - -static void usage3(FILE *out, const char *program, const char *msg) { - if (!IS_NULL_PTR(msg)) { - fprintf(out, "%s\n", msg); - } - fprintf(out, "Usage: %s [-h] [-l] []\n", program); - fprintf(out, " -h Print this help menu.\n"); - fprintf(out, " -i Include file and line information.\n"); - fprintf(out, " -l List the contents of an AVM file.\n"); - fprintf(out, " [-a] + Create an AVM file (archive if -a specified).\n" - ); -} - -static void usage(const char *program) -{ - usage3(stdout, program, NULL); -} - - -int main(int argc, char **argv) -{ - int opt; - - const char *action = "pack"; - int is_archive = 0; - bool include_lines = false; - while ((opt = getopt(argc, argv, "hail")) != -1) { - switch(opt) { - case 'h': - usage(argv[0]); - return EXIT_SUCCESS; - case 'a': - is_archive = 1; - break; - case 'i': - include_lines = true; - break; - case 'l': - action = "list"; - break; - case '?': { - char buf[BUF_SIZE]; - snprintf(buf, BUF_SIZE, "Unknown option: %c", optopt); - usage3(stderr, argv[0], buf); - return EXIT_FAILURE; - } - } - } - - int new_argc = argc - optind; - char **new_argv = argv + optind; - - if (new_argc < 1) { - usage3(stderr, argv[0], "Missing avm file.\n"); - return EXIT_FAILURE; - } - - if (!strcmp(action, "pack")) { - if (new_argc < 2) { - usage3(stderr, argv[0], "Missing options for pack\n"); - return EXIT_FAILURE; - } - return do_pack(new_argc, new_argv, is_archive, include_lines); - } else { - return do_list(new_argc, new_argv); - } -} - -static void assert_fread(void *buffer, size_t size, FILE* file) -{ - size_t r = fread(buffer, sizeof(uint8_t), size, file); - if (r != size) { - fprintf(stderr, "Unable to read, wanted to read %zu bytes, read %zu bytes\n", size, r); - exit(EXIT_FAILURE); - } -} - -static void assert_fwrite(const void *buffer, size_t size, FILE* file) -{ - size_t r = fwrite(buffer, 1, size, file); - if (r != size) { - fprintf(stderr, "Unable to write, wanted to write %zu bytes, wrote %zu bytes\n", size, r); - exit(EXIT_FAILURE); - } -} - -static void *pack_beam_fun(void *accum, const void *section_ptr, uint32_t section_size, const void *beam_ptr, uint32_t flags, const char *section_name) -{ - UNUSED(beam_ptr); - UNUSED(flags); - UNUSED(section_name); - if (accum == NULL) { - return NULL; - } - - FILE *pack = (FILE *)accum; - size_t r = fwrite(section_ptr, sizeof(unsigned char), section_size, pack); - if (r != section_size) { - return NULL; - } - return accum; -} - -FileData read_file_data(FILE *file) -{ - fseek(file, 0, SEEK_END); - size_t size = ftell(file); - fseek(file, 0, SEEK_SET); - uint8_t *data = malloc(size); - if (!data) { - fprintf(stderr, "Unable to allocate %zu bytes\n", size); - exit(EXIT_FAILURE); - } - assert_fread(data, size, file); - - FileData file_data = { - .data = data, - .size = size - }; - return file_data; -} - -static bool is_avm_file(FILE *file) -{ - FileData file_data = read_file_data(file); - bool ret = avmpack_is_valid(file_data.data, file_data.size); - free(file_data.data); - return ret; -} - -static bool is_beam_file(FILE *file) -{ - FileData file_data = read_file_data(file); - bool ret = iff_is_valid_beam(file_data.data); - free(file_data.data); - return ret; -} - -static void validate_pack_options(int argc, char ** argv) -{ - for (int i = 0; i < argc; ++i) { - const char *filename = argv[i]; - FILE *file = fopen(filename, "r"); - if (i == 0) { - if (file && !is_avm_file(file)) { - char buf[BUF_SIZE]; - snprintf(buf, BUF_SIZE, "Invalid AVM file: %s", filename); - usage3(stderr, "PackBeam", buf); - exit(EXIT_FAILURE); - } - } else { - if (!file) { - char buf[BUF_SIZE]; - snprintf(buf, BUF_SIZE, "%s does not exist", filename); - usage3(stderr, "PackBeam", buf); - exit(EXIT_FAILURE); - } else if (!is_avm_file(file) && !is_beam_file(file)) { - char buf[BUF_SIZE]; - snprintf(buf, BUF_SIZE, "Invalid AVM or BEAM file: %s", filename); - usage3(stderr, "PackBeam", buf); - exit(EXIT_FAILURE); - } - } - } -} - -static int do_pack(int argc, char **argv, int is_archive, bool include_lines) -{ - validate_pack_options(argc, argv); - - FILE *pack = fopen(argv[0], "w"); - if (!pack) { - char buf[BUF_SIZE]; - snprintf(buf, BUF_SIZE, "Cannot open output file for writing %s", argv[0]); - perror(buf); - return EXIT_FAILURE; - } - - const unsigned char pack_header[24] = - { - 0x23, 0x21, 0x2f, 0x75, - 0x73, 0x72, 0x2f, 0x62, - 0x69, 0x6e, 0x2f, 0x65, - 0x6e, 0x76, 0x20, 0x41, - 0x74, 0x6f, 0x6d, 0x56, - 0x4d, 0x0a, 0x00, 0x00 - }; - assert_fwrite(pack_header, 24, pack); - - for (int i = 1; i < argc; i++) { - FILE *file = fopen(argv[i], "r"); - if (!file) { - char buf[BUF_SIZE]; - snprintf(buf, BUF_SIZE, "Cannot open file %s", argv[i]); - perror(buf); - return EXIT_FAILURE; - } - - fseek(file, 0, SEEK_END); - size_t file_size = ftell(file); - fseek(file, 0, SEEK_SET); - - - uint8_t *file_data = malloc(file_size); - if (!file_data) { - fprintf(stderr, "Unable to allocate %zu bytes\n", file_size); - return EXIT_FAILURE; - } - assert_fread(file_data, file_size, file); - if (avmpack_is_valid(file_data, file_size)) { - void *result = avmpack_fold(pack, file_data, pack_beam_fun); - if (result == NULL) { - free(file_data); - return EXIT_FAILURE; - } - } else { - char *filename = basename(argv[i]); - pack_beam_file(pack, file_data, file_size, filename, !is_archive && i == 1, include_lines); - } - free(file_data); - } - - add_module_header(pack, "end", END_OF_FILE); - fclose(pack); - - return EXIT_SUCCESS; -} - -static void pack_beam_file(FILE *pack, const uint8_t *data, size_t size, const char *section_name, int is_entrypoint, bool include_lines) -{ - size_t zero_pos = ftell(pack); - - if (is_entrypoint) { - add_module_header(pack, section_name, BEAM_CODE_FLAG | BEAM_START_FLAG); - } else { - add_module_header(pack, section_name, BEAM_CODE_FLAG); - } - - int written_beam_header_pos = ftell(pack); - const unsigned char beam_header[12] = - { - 0x46, 0x4f, 0x52, 0x31, - 0x00, 0x00, 0x00, 0x00, - 0x42, 0x45, 0x41, 0x4d - }; - assert_fwrite(beam_header, 12, pack); - - unsigned long offsets[MAX_OFFS]; - unsigned long sizes[MAX_SIZES]; - scan_iff(data, size, offsets, sizes); - - if (offsets[AT8U]) { - assert_fwrite(data + offsets[AT8U], sizes[AT8U] + IFF_SECTION_HEADER_SIZE, pack); - pad_and_align(pack); - } - if (offsets[CODE]) { - assert_fwrite(data + offsets[CODE], sizes[CODE] + IFF_SECTION_HEADER_SIZE, pack); - pad_and_align(pack); - } - if (offsets[EXPT]) { - assert_fwrite(data + offsets[EXPT], sizes[EXPT] + IFF_SECTION_HEADER_SIZE, pack); - pad_and_align(pack); - } - if (offsets[LOCT]) { - assert_fwrite(data + offsets[LOCT], sizes[LOCT] + IFF_SECTION_HEADER_SIZE, pack); - pad_and_align(pack); - } - if (offsets[IMPT]) { - assert_fwrite(data + offsets[IMPT], sizes[IMPT] + IFF_SECTION_HEADER_SIZE, pack); - pad_and_align(pack); - } - if (offsets[LITU]) { - assert_fwrite(data + offsets[LITU], sizes[LITU] + IFF_SECTION_HEADER_SIZE, pack); - pad_and_align(pack); - } - if (offsets[FUNT]) { - assert_fwrite(data + offsets[FUNT], sizes[FUNT] + IFF_SECTION_HEADER_SIZE, pack); - pad_and_align(pack); - } - if (offsets[STRT]) { - assert_fwrite(data + offsets[STRT], sizes[STRT] + IFF_SECTION_HEADER_SIZE, pack); - pad_and_align(pack); - } - if (offsets[AVMN]) { - assert_fwrite(data + offsets[AVMN], sizes[AVMN] + IFF_SECTION_HEADER_SIZE, pack); - pad_and_align(pack); - } - if (offsets[TYPE]) { - assert_fwrite(data + offsets[TYPE], sizes[TYPE] + IFF_SECTION_HEADER_SIZE, pack); - pad_and_align(pack); - } - if (offsets[LINT] && include_lines) { - assert_fwrite(data + offsets[LINT], sizes[LINT] + IFF_SECTION_HEADER_SIZE, pack); - pad_and_align(pack); - } - if (offsets[LITT]) { - const uint8_t *litt = data + offsets[LITT]; - size_t litt_size = sizes[LITT]; - if (are_literals_compressed(litt)) { - size_t u_size; - void *deflated = uncompress_literals(data + offsets[LITT], litt_size, &u_size); - assert_fwrite("LitU", 4, pack); - uint32_t size_field = ENDIAN_SWAP_32(u_size); - assert_fwrite(&size_field, sizeof(size_field), pack); - assert_fwrite(deflated, u_size, pack); - free(deflated); - } else { - assert_fwrite(data + offsets[LITT], sizes[LITT] + IFF_SECTION_HEADER_SIZE, pack); - pad_and_align(pack); - } - } - - pad_and_align(pack); - - size_t end_of_module_pos = ftell(pack); - - size_t rsize = end_of_module_pos - zero_pos; - uint32_t size_field = ENDIAN_SWAP_32(rsize); - fseek(pack, zero_pos, SEEK_SET); - assert_fwrite(&size_field, sizeof(uint32_t), pack); - fseek(pack, end_of_module_pos, SEEK_SET); - - int beam_written_size = end_of_module_pos - written_beam_header_pos; - uint32_t beam_written_size_field = ENDIAN_SWAP_32(beam_written_size); - fseek(pack, written_beam_header_pos + 4, SEEK_SET); - assert_fwrite(&beam_written_size_field , sizeof(uint32_t), pack); - fseek(pack, end_of_module_pos, SEEK_SET); -} - - -static void *print_section(void *accum, const void *section_ptr, uint32_t section_size, const void *beam_ptr, uint32_t flags, const char *section_name) -{ - UNUSED(section_ptr); - UNUSED(section_size); - UNUSED(beam_ptr); - printf("%s %s\n", section_name, flags & BEAM_START_FLAG ? "*" : ""); - return accum; -} - -static void validate_list_options(const char *filename) -{ - FILE *file = fopen(filename, "r"); - if (!file) { - char buf[BUF_SIZE]; - snprintf(buf, BUF_SIZE, "%s does not exist", filename); - usage3(stderr, "PackBeam", buf); - exit(EXIT_FAILURE); - } else if (!is_avm_file(file)) { - char buf[BUF_SIZE]; - snprintf(buf, BUF_SIZE, "Invalid AVM file: %s", filename); - usage3(stderr, "PackBeam", buf); - exit(EXIT_FAILURE); - } -} - -static int do_list(int argc, char **argv) -{ - UNUSED(argc); - validate_list_options(argv[0]); - - MappedFile *mapped_file = mapped_file_open_beam(argv[0]); - if (IS_NULL_PTR(mapped_file)) { - char buf[BUF_SIZE]; - snprintf(buf, BUF_SIZE, "Cannot open AVM file %s", argv[0]); - perror(buf); - return EXIT_FAILURE; - } - - int ret = EXIT_SUCCESS; - if (avmpack_is_valid(mapped_file->mapped, mapped_file->size)) { - avmpack_fold(NULL, mapped_file->mapped, print_section); - } else { - char buf[BUF_SIZE]; - snprintf(buf, BUF_SIZE, "%s is not an AVM file.\n", argv[1]); - usage3(stderr, "PackBeam", buf); - ret = EXIT_FAILURE; - } - mapped_file_close(mapped_file); - - return ret; -} - -bool are_literals_compressed(const uint8_t *litT) -{ - unsigned int required_buf_size = READ_32_ALIGNED(litT + LITT_UNCOMPRESSED_SIZE_OFFSET); - return (required_buf_size != 0); -} - -static void *uncompress_literals(const uint8_t *litT, int size, size_t *uncompressedSize) -{ - unsigned int required_buf_size = READ_32_ALIGNED(litT + LITT_UNCOMPRESSED_SIZE_OFFSET); - - uint8_t *outBuf = malloc(required_buf_size); - if (!outBuf) { - fprintf(stderr, "Cannot allocate temporary buffer (size = %u)", required_buf_size); - AVM_ABORT(); - } - - z_stream infstream; - infstream.zalloc = Z_NULL; - infstream.zfree = Z_NULL; - infstream.opaque = Z_NULL; - infstream.avail_in = (uInt) (size - IFF_SECTION_HEADER_SIZE); - infstream.next_in = (Bytef *) (litT + LITT_HEADER_SIZE); - infstream.avail_out = (uInt) required_buf_size; - infstream.next_out = (Bytef *) outBuf; - - int ret = inflateInit(&infstream); - if (ret != Z_OK) { - fprintf(stderr, "Failed inflateInit\n"); - AVM_ABORT(); - } - ret = inflate(&infstream, Z_NO_FLUSH); - if (ret != Z_OK) { - fprintf(stderr, "Failed inflate\n"); - AVM_ABORT(); - } - inflateEnd(&infstream); - - *uncompressedSize = required_buf_size; - return outBuf; -} - -static void pad_and_align(FILE *f) -{ - while ((ftell(f) % 4) != 0) { - fputc(0, f); - } -} - -static void add_module_header(FILE *f, const char *module_name, uint32_t flags) -{ - uint32_t size_field = 0; - uint32_t flags_field = ENDIAN_SWAP_32(flags); - uint32_t reserved = 0; - - assert_fwrite(&size_field, sizeof(uint32_t), f); - assert_fwrite(&flags_field, sizeof(uint32_t), f); - assert_fwrite(&reserved, sizeof(uint32_t), f); - assert_fwrite(module_name, strlen(module_name) + 1, f); - pad_and_align(f); -} diff --git a/tools/packbeam/rebar.config b/tools/packbeam/rebar.config new file mode 100644 index 0000000000..12ddb305ab --- /dev/null +++ b/tools/packbeam/rebar.config @@ -0,0 +1,26 @@ +% +% This file is part of AtomVM. +% +% Copyright 2025 Paul Guyot +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +{deps, [ + atomvm_packbeam +]}. +{escript_main_app, atomvm_packbeam}. +{escript_name, packbeam}. +{escript_emu_args, "%%! -escript main packbeam\n"}.