From 5258f577f78dd4b242f3afa5ac2d1456802ad721 Mon Sep 17 00:00:00 2001 From: Fred Dushin Date: Thu, 19 Oct 2023 21:47:21 -0400 Subject: [PATCH] Tighten up packbeam_api This change set tightens up the packbeam API to expose just enough information about AVM files as is needed without exposing internal implementation details. Documentation has been written for the API, and we have moved to use `ex_doc` instead of `edoc` for documentation generation. Signed-off-by: Fred Dushin --- .github/workflows/build-and-test.yaml | 2 +- ChangeLog => CHANGELOG.md | 5 + Makefile | 6 +- README.md | 120 ++++++++++++++--- rebar.config | 19 ++- src/packbeam.erl | 19 ++- src/packbeam_api.erl | 183 ++++++++++++++++---------- test/prop_packbeam.erl | 8 +- test/test_packbeam.erl | 8 +- 9 files changed, 256 insertions(+), 114 deletions(-) rename ChangeLog => CHANGELOG.md (93%) diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 8516251..4c5c30e 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -13,7 +13,7 @@ jobs: runs-on: "ubuntu-20.04" strategy: matrix: - otp: ["22", "23", "24"] + otp: ["24", "25", "26"] steps: # Setup diff --git a/ChangeLog b/CHANGELOG.md similarity index 93% rename from ChangeLog rename to CHANGELOG.md index 5c28de7..0fb7e64 100644 --- a/ChangeLog +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.7.1] (unreleased) + +- Enhanced `packbeam_api` to make it more maintainable. +- Changed documentation to use [`rebar3_ex_doc`](https://hexdocs.pm/rebar3_ex_doc/readme.html) + ## [0.7.0] - Added `version` sub-command to print version to the console diff --git a/Makefile b/Makefile index cfb9a45..bfee9c7 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ ## All rights reserved. ## -all: compile escript edoc etest rel +all: compile escript docs etest rel compile: rebar3 compile @@ -11,8 +11,8 @@ compile: escript: rebar3 escriptize -edoc: - rebar3 edoc +docs: + rebar3 ex_doc etest: rebar3 eunit --cover diff --git a/README.md b/README.md index ae1d9b7..1719235 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,20 @@ # `atomvvm_packbeam` -An Erlang Escript and OTP library used to generate an AtomVM AVM file from a set of files (beam files, previously built AVM files, or even arbitrary data files). +An Erlang Escript and library used to generate an [AtomVM](http://github.com/atomvm/AtomVM) AVM file from a set of files (beam files, previously built AVM files, or even arbitrary data files). This tool roughly approximates the functionality of the AtomVM `PackBEAM` utility, except: * Support for multiple data types, include beam files, text files, etc -* "Smart" extraction of beams from AVM files, so that only the beams that are needed are packed +* "Pruned" extraction of beams from AVM files, so that only the beams that are needed are packed +* Support for embedded OTP applications in your PackBEAM files. -The `packbeam` tool may be used on its own. More typically, it is used internally as part of the atomvm_rebar3_plugin `rebar3` plugin. +The `packbeam` tool may be used on its own as a stand-alone command-line utility. More typically, it is used internally as part of the [`atomvm_rebar3_plugin`](https://github.com/atomvm/atomvm_rebar3_plugin) [`rebar3`](https://rebar3.org) plugin. -# Prerequisites +## Prerequisites -Building Packbeam requires Erlang/OTP 22 or later, for compatibility with AtomVM, as well as a local installation of `rebar3`. Optionally, any recent version of `make` may be used to simplify builds. +Building `packbeam` requires a version of Erlang/OTP compatible with [AtomVM](https://github.com/atomvm/AtomVM), as well as a local installation of [`rebar3`](https://rebar3.org). Optionally, any recent version of `make` may be used to simplify builds. Consult the [AtomVM Documentation](https://www.atomvm.net/doc/master/) for information about supported OTP versions. -# Build +## Build To build a release, run the following commands: @@ -46,7 +47,7 @@ For example: packbeam ... -# `packbeam` command +## `packbeam` command The `packbeam` command is used to create an AVM file from a list of beam and other file types, to list the contents of an AVM file, or to delete elements from an AVM file. @@ -106,7 +107,7 @@ The `packbeam` command will return an exit status of 0 on successful completion The `packbeam` sub-commands are described in more detail below. -## `create` sub-command +### `create` sub-command To create an AVM file from a list of beam files, use the `create` sub-command to create an AVM file. The first argument is take to be the output AVM file, following by the files you would like to add, e.g., @@ -122,7 +123,7 @@ The input files specified in the create subcommand may be among the following ty Note that beam files specified are stripped of their path information, inside of the generated AVM file. Any files that have the same name will be added in the order they are listed on the command line. However, AtomVM will only resolve the first such file when loading modules at run-time. -### Start Entrypoint +#### Start Entrypoint If you are building an application that provides a start entrypoint (as opposed to a library, suitable for inclusion in another AVM file), then at least one beam module in an AVM file must contain a `start/0` entry-point, i.e., a function called `start` with arity 0. AtomVM will use this entry-point as the first function to execute, when starting. @@ -138,19 +139,19 @@ In addition, you may specify a "normal" (i.e., non-beam or non-AVM) file. Norma > Note. It is conventional in AtomVM for normal files to have the path `/priv/`. -### Pruning +#### Pruning If you specify the `--prune` (alternatively, `-p`) flag, then `packbeam` will only include beam files that are transitively dependent on the entry-point beam. Transitive dependencies are determined by imports, as well as use of an atom in a module (e.g, as the result of a dynamic function call, based on a module name). If there is no beam file with a `start/0` entry-point defined in the list of input modules and the `--prune` flag is used, the command will fail. You should _not_ use the `--prune` flag if you are trying to build libraries suitable for inclusion on other AtomVM applications. -### Line number information +#### Line number information By default, the `packbeam` tool will generate line number information for embedded BEAM files. Line number information is included in Erlang stacktraces, giving developers more clues into bugs in their programs. However, line number information does increase the size of AVM files, and in some cases can have an impact on memory in running applications. For production applications that have no need for line number information, we recommend using the `-r` (or `--remove_lines`) flags, which will strip line number information from embedded BEAM files. -## `list` sub-command +### `list` sub-command The `list` sub-command will print the contents of an AVM file to the standard output stream. @@ -170,7 +171,7 @@ You may use the `--format` (alternatively, `-f`) option to specify an output for * `bare` Output just the module name, with no annotations. * `default` Output the module name, size (in brackets), and whether the file provides a `start/0` entrypoint, indicated by an asterisk (`*`). The `default` output is used if the `--format` option is not specified. -## `extract` sub-command +### `extract` sub-command The `extract` sub-command can be used to extract elements from an AVM file. @@ -190,7 +191,7 @@ For example: x mylib/priv/sample.txt -## `delete` sub-command +### `delete` sub-command The `delete` sub-command can be used to remove elements from an AVM file. @@ -203,6 +204,93 @@ For example: mylib.beam * [284] mylib/priv/sample.txt [29] -# `packbeam_api` API +## `packbeam_api` API -> TODO Used by rebar plugin +In addition to being an `escript` command-line utility, this project provides an Erlang API and library for manipulating AVM files. Simply include `atomvm_packbeam` as a dependency in your `rebar.config`, and you will have access to this API. + +> For more detailed information about this API, see the [`packbeam_api` Reference](packbeam_api.html). + +### Creating PackBEAM files + +To create a PackBEAM file, use the `packbeam_api:create/2` function. Specify the output path of the AVM you would like to create, followed by a list of paths to the files that will go into the AVM file. Typically, these paths are a list of BEAM files, though you can also include plain data files, in addition to previously created AVM files. Previously-created AVM files will be copied into the output AVM file. + +> Note. Specify the file system paths to all files. BEAM file path information will be stripped from the AVM element path data. Any plain data files (non-BEAM files) will retain their path information. See the [AtomVM Documentation](https://www.atomvm.net/doc/master/) about how to create plain data files in AVM files that users can retrieved via the `atomvm:read_priv/2` function. + + %% erlang + ok = packbeam_api:create( + "/path/to/output.avm", [ + "/path/to/foo.beam", + "/path/to/bar.beam", + "/path/to/myapp/priv/sample.txt", + "/path/to/some_lib.avm" + ] + ). + +Alternatively, you may specify a set of options with the `packbeam_api:create/3` function, which takes a map as the third parameter. + +| Key | Type | Deafult | Description | +|-----|------|---------|-------------| +| `prune` | `boolean()` | `false` | Specify whether to prune the output AVM file. Pruned AVM files can take considerably less space and hence may lead to faster development times. | +| `start` | `module()` | n/a | Specify the start module, if it can't be determined automatically from the application. | +| `application` | `module()` | n/a | Specify the application module. The `.app` file will be encoded and included as an element in the AVM file with the path `/priv/application.bin` | +| `include_lines` | `boolean()` | `true` | Specify whether to include line number information in generated AVM files. | + +### Listing the contents of PackBEAM files + +You can list the contents of PackBEAM files using the `packbeam_api:list/1` function. Specify the file system path to the PackBEAM file you would like to list: + + %% erlang + AVMElements = packbeam_api:list("/path/to/input.avm"). + +The returned `AVMElements` is list of an opaque data structures and should not be interpreted by user applications. However, several functions are exposed to retrieve information about elements in this list. + +To get the element name, use the `packbeam_api:get_element_name/1` function, passing in an AVM element. The return type is a `string()` and represents the path in the AVM file for the AVM element. + + %% erlang + AVMElementName = packbeam_api:get_element_name(AVMElement). + +To get the element data (as a binary) use the `packbeam_api:get_element_data/1` function, passing in an AVM element. The return type is a `binary()` containing the actual data in the AVM element. + + %% erlang + AVMElementData = packbeam_api:get_element_data(AVMElement). + +To get the element module (as an atom) use the `packbeam_api:get_element_module/1` function, passing in an AVM element. The return type is a `module()` and the module name of the AVM element. + +Note that if the AVM element is not a BEAM file, this function returns `undefined`. + + %% erlang + AVMElementModule = packbeam_api:get_element_module(AVMElement). + +To determine if the element is a BEAM file, use the `packbeam_api:is_beam/1` function, passing in an AVM element. The return value is a `boolean()`. + + %% erlang + IsBEAM = packbeam_api:is_beam(AVMElement). + +To determine if the element is an entrypoint BEAM (i.e., it exports a `start/0` function), use the `packbeam_api:is_entrypoint/1` function, passing in an AVM element. The return value is a `boolean()`. + + %% erlang + IsEntrypoint = packbeam_api:is_entrypoint(AVMElement). + +### Deleting entries from PackBEAM files + +You can delete entries from an AVM file using the `packbeam_api:delete/3` function. Specify the file system path to the PackBEAM file you would like to delete from, the output path you would like to write the new AVM file to, and a list of AVM elements you would like to delete: + + %% erlang + ok = packbeam_api:delete( + "/path/to/input.avm", + "/path/to/ouput.avm", + ["foo.beam", "myapp/priv/sample.txt"] + ). + +> Note. You may specify the same values for the input and output paths. In this case, the input AVM file will be _over-written_ by the new AVM file. + +### Extracting entries from PackBEAM files + +You can extract elements from an AVM file using the `packbeam_api:extract/3` function. Specify the file system path to the PackBEAM file you would like to extract from, a list of AVM elements you would like to extract, and the output directory into which would like to extract the files: + + %% erlang + ok = packbeam_api:extract( + "/path/to/input.avm", + ["foo.beam", "myapp/priv/sample.txt"], + "/tmp" + ). diff --git a/rebar.config b/rebar.config index 6eb4277..4f61bfa 100644 --- a/rebar.config +++ b/rebar.config @@ -2,7 +2,7 @@ %% Copyright (c) dushin.net %% All rights reserved. %% -{erl_opts, [no_debug_info]}. +{erl_opts, [debug_info]}. {deps, []}. {escript_incl_apps, [atomvm_packbeam]}. @@ -12,11 +12,20 @@ {ex_doc, [ {source_url, <<"https://github.com/atomvm/atomvm_packbeam">>}, - {extras, [<<"README.md">>, <<"LICENSE">>]}, - {main, <<"readme">>} + {extras, [ + <<"README.md">>, + <<"CHANGELOG.md">>, + <<"UPDATING.md">>, + <<"LICENSE">>, + <<"CONTRIBUTING.md">>, + <<"CODE_OF_CONDUCT.md">> + ]}, + {main, <<"README.md">>}, + {api_reference, true}, + {skip_undefined_reference_warnings_on, ["README.md"]} ]}. -{hex, [{doc, edoc}]}. -{plugins, [rebar3_hex, rebar3_proper]}. +{hex, [{doc, #{provider => ex_doc}}]}. +{plugins, [rebar3_hex, rebar3_proper, rebar3_ex_doc]}. %% Profiles {profiles, [ diff --git a/src/packbeam.erl b/src/packbeam.erl index 06b231d..62102bd 100644 --- a/src/packbeam.erl +++ b/src/packbeam.erl @@ -272,13 +272,12 @@ print_modules(Modules, Format) -> print_module(ParsedFile, undefined) -> print_module(ParsedFile, "default"); print_module(ParsedFile, "default") -> - ModuleName = proplists:get_value(module_name, ParsedFile), - Flags = proplists:get_value(flags, ParsedFile), - Data = proplists:get_value(data, ParsedFile), + Name = packbeam_api:get_element_name(ParsedFile), + Data = packbeam_api:get_element_data(ParsedFile), io:format( "~s~s [~p]~n", [ - ModuleName, - case packbeam_api:is_entrypoint(Flags) of + Name, + case packbeam_api:is_entrypoint(ParsedFile) of true -> " *"; _ -> "" end, @@ -286,21 +285,21 @@ print_module(ParsedFile, "default") -> ] ); print_module(ParsedFile, "csv") -> - ModuleName = proplists:get_value(module_name, ParsedFile), - Data = proplists:get_value(data, ParsedFile), + Name = packbeam_api:get_element_name(ParsedFile), + Data = packbeam_api:get_element_data(ParsedFile), io:format( "~s,~p,~p,~p~n", [ - ModuleName, + Name, packbeam_api:is_beam(ParsedFile), packbeam_api:is_entrypoint(ParsedFile), byte_size(Data) ] ); print_module(ParsedFile, "bare") -> - ModuleName = proplists:get_value(module_name, ParsedFile), + Name = packbeam_api:get_element_name(ParsedFile), io:format( "~s~n", [ - ModuleName + Name ] ); print_module(_ParsedFile, Format) -> diff --git a/src/packbeam_api.erl b/src/packbeam_api.erl index b541b0e..3303dee 100644 --- a/src/packbeam_api.erl +++ b/src/packbeam_api.erl @@ -25,8 +25,9 @@ %% API exports -export([create/2, create/3, create/4, create/5, list/1, extract/3, delete/3]). -%% internally shared API --export([is_beam/1, is_entrypoint/1]). + +%% AVM Entry functions +-export([is_beam/1, is_entrypoint/1, get_element_name/1, get_element_data/1, get_element_module/1]). -define(AVM_HEADER, 16#23, 16#21, 16#2f, 16#75, @@ -43,15 +44,23 @@ -define(BEAM_CODE_FLAG, 2). -define(NORMAL_FILE_FLAG, 4). +-opaque avm_element() :: [atom() | {atom(), term()}]. -type path() :: string(). --type parsed_file() :: [{string(), term()}]. -- type options() :: #{ +-type avm_element_name() :: string(). +-type options() :: #{ prune => boolean(), start_module => module() | undefined, application_module => module() | undefined, include_lines => boolean() }. +-export_type([ + path/0, + avm_element/0, + avm_element_name/0, + options/0 +]). + -define(DEFAULT_OPTIONS, #{ prune => false, start_module => undefined, @@ -82,7 +91,7 @@ %% @end %%----------------------------------------------------------------------------- -spec create( - OutputPath ::path(), + OutputPath :: path(), InputPaths :: [path()] ) -> ok | {error, Reason :: term()}. create(OutputPath, InputPaths) -> @@ -101,7 +110,7 @@ create(OutputPath, InputPaths) -> %% @end %%----------------------------------------------------------------------------- -spec create( - OutputPath ::path(), + OutputPath :: path(), InputPaths :: [path()], Options :: options() ) -> ok | {error, Reason :: term()}. @@ -138,9 +147,10 @@ create(OutputPath, InputPaths, Options) -> %% %% Equivalent to create(OutputPath, InputPaths, undefined, Prune, StartModule). %% @end +%% @hidden %%----------------------------------------------------------------------------- -spec create( - OutputPath ::path(), + OutputPath :: path(), InputPaths :: [path()], Prune :: boolean(), StartModule :: module() | undefined @@ -172,9 +182,10 @@ create(OutputPath, InputPaths, Prune, StartModule) -> %% This function will create an AVM file at the location specified in %% OutputPath, using the input files specified in InputPaths. %% @end +%% @hidden %%----------------------------------------------------------------------------- -spec create( - OutputPath ::path(), + OutputPath :: path(), InputPaths :: [path()], ApplicationModule :: module() | undefined, Prune :: boolean(), @@ -188,7 +199,7 @@ create(OutputPath, InputPaths, ApplicationModule, Prune, StartModule) -> %%----------------------------------------------------------------------------- %% @param InputPath the AVM file from which to list elements -%% @returns list of parsed file data +%% @returns list of element data %% @throws Reason::string() %% @doc List the contents of an AVM file. %% @@ -196,7 +207,7 @@ create(OutputPath, InputPaths, ApplicationModule, Prune, StartModule) -> %% location specified in InputPath. %% @end %%----------------------------------------------------------------------------- --spec list(InputPath :: path()) -> [parsed_file()]. +-spec list(InputPath :: path()) -> [avm_element()]. list(InputPath) -> case file_type(InputPath) of avm -> @@ -207,7 +218,7 @@ list(InputPath) -> %%----------------------------------------------------------------------------- %% @param InputPath the AVM file from which to extract elements -%% @param Names a list of elements from the source AVM file to extract. If +%% @param AVMElementNames a list of elements from the source AVM file to extract. If %% empty, then extract all elements. %% @param OutputDir the directory to write the contents %% @returns ok if the file was created. @@ -222,14 +233,14 @@ list(InputPath) -> %%----------------------------------------------------------------------------- -spec extract( InputPath :: path(), - Names :: [path()], + AVMElementNames :: [avm_element_name()], OutputDir :: path() ) -> ok | {error, Reason :: term()}. -extract(InputPath, Keys, OutputDir) -> +extract(InputPath, AVMElementNames, OutputDir) -> case file_type(InputPath) of avm -> ParsedFiles = parse_file(InputPath, false), - write_files(filter_names(Keys, ParsedFiles), OutputDir); + write_files(filter_names(AVMElementNames, ParsedFiles), OutputDir); _ -> throw(io_lib:format("Expected AVM file: ~p", [InputPath])) end. @@ -237,7 +248,7 @@ extract(InputPath, Keys, OutputDir) -> %%----------------------------------------------------------------------------- %% @param InputPath the AVM file from which to delete elements %% @param OutputPath the path to write the AVM file -%% @param Names a list of elements from the source AVM file to delete +%% @param AVMElementNames a list of elements from the source AVM file to delete %% @returns ok if the file was created. %% @throws Reason::string() %% @doc Delete selected elements of an AVM file. @@ -250,21 +261,77 @@ extract(InputPath, Keys, OutputDir) -> -spec delete( OutputPath :: path(), InputPath :: path(), - Name :: [path()] + AVMElementNames :: [avm_element_name()] ) -> ok | {error, Reason :: term()}. -delete(OutputPath, InputPath, Names) -> +delete(OutputPath, InputPath, AVMElementNames) -> case file_type(InputPath) of avm -> ParsedFiles = parse_file(InputPath, false), - write_packbeam(OutputPath, remove_names(Names, ParsedFiles)); + write_packbeam(OutputPath, remove_names(AVMElementNames, ParsedFiles)); _ -> throw(io_lib:format("Expected AVM file: ~p", [InputPath])) end. +%%----------------------------------------------------------------------------- +%% @param AVMElement An AVM file element +%% @returns the name of the element. +%% @doc Return the name of the element. +%% @end +%%----------------------------------------------------------------------------- +-spec get_element_name(AVMElement :: avm_element()) -> avm_element_name(). +get_element_name(AVMElement) -> + proplists:get_value(element_name, AVMElement). + +%%----------------------------------------------------------------------------- +%% @param AVMElement An AVM file element +%% @returns the AVM element data. +%% @doc Return AVM element data. +%% @end +%%----------------------------------------------------------------------------- +-spec get_element_data(AVMElement :: avm_element()) -> binary(). +get_element_data(AVMElement) -> + proplists:get_value(data, AVMElement). + +%%----------------------------------------------------------------------------- +%% @param AVMElement An AVM file element +%% @returns the AVM element module, if the element is a BEAM file; `undefined', +%% otherwise. +%% @doc Return AVM element module, if the element is a BEAM file. +%% @end +%%----------------------------------------------------------------------------- +-spec get_element_module(AVMElement :: avm_element()) -> module() | undefined. +get_element_module(AVMElement) -> + proplists:get_value(module, AVMElement). + +%%----------------------------------------------------------------------------- +%% @param AVMElement An AVM file element +%% @returns `true' if the AVM element is an entrypoint (i.e., exports a `start/0' +%% function); `false' otherwise. +%% @doc Indicates whether the AVM file element is an entrypoint. +%% @end +%%----------------------------------------------------------------------------- +-spec is_entrypoint(AVMElement :: avm_element()) -> boolean(). +is_entrypoint(AVMElement) -> + (get_flags(AVMElement) band ?BEAM_START_FLAG) =:= ?BEAM_START_FLAG. + +%%----------------------------------------------------------------------------- +%% @param AVMElement An AVM file element +%% @returns `true' if the AVM element is a BEAM file; `false' otherwise. +%% @doc Indicates whether the AVM file element is a BEAM file. +%% @end +%%----------------------------------------------------------------------------- +-spec is_beam(AVMElement :: avm_element()) -> boolean(). +is_beam(AVMElement) -> + (get_flags(AVMElement) band ?BEAM_CODE_FLAG) =:= ?BEAM_CODE_FLAG. + %% %% Internal API functions %% +%% @private +get_flags(AVMElement) -> + proplists:get_value(flags, AVMElement). + %% @private parse_files(InputPaths, StartModule, IncludeLines) -> Files = lists:foldl( @@ -315,7 +382,7 @@ prune(ParsedFiles, RootApplicationModule) -> throw("No input beam files contain start/0 entrypoint"); {value, Entrypoint} -> BeamFiles = lists:filter(fun is_beam/1, ParsedFiles), - Modules = closure(Entrypoint, BeamFiles, [get_module(Entrypoint)]), + Modules = closure(Entrypoint, BeamFiles, [get_element_module(Entrypoint)]), ApplicationStartModules = find_application_modules(ParsedFiles, RootApplicationModule), ApplicationModules = find_dependencies(ApplicationStartModules, BeamFiles), filter_modules(Modules ++ ApplicationModules, ParsedFiles) @@ -382,14 +449,14 @@ find_application_spec(ApplicationSpecs, ApplicationModule) -> ). get_application_spec(ApplicationFile) -> - ApplicationData = get_data(ApplicationFile), + ApplicationData = get_element_data(ApplicationFile), <<_Size:4/binary, SerializedTerm/binary>> = ApplicationData, binary_to_term(SerializedTerm). is_application_file(ParsedFile) -> case not is_beam(ParsedFile) of true -> - ModuleName = get_module_name(ParsedFile), + ModuleName = get_element_name(ParsedFile), Components = string:split(ModuleName, "/", all), case Components of [_ModuleName, "priv", "application.bin"] -> @@ -405,23 +472,11 @@ is_application_file(ParsedFile) -> find_entrypoint(ParsedFiles) -> lists:search(fun is_entrypoint/1, ParsedFiles). -%% @private -is_entrypoint(Flags) when is_integer(Flags) -> - Flags band ?BEAM_START_FLAG =:= ?BEAM_START_FLAG; -is_entrypoint(ParsedFile) -> - is_entrypoint(proplists:get_value(flags, ParsedFile)). - -%% @private -is_beam(Flags) when is_integer(Flags) -> - Flags band ?BEAM_CODE_FLAG =:= ?BEAM_CODE_FLAG; -is_beam(ParsedFile) -> - is_beam(proplists:get_value(flags, ParsedFile)). - %% @private closure(_Current, [], Accum) -> lists:reverse(Accum); closure(Current, Candidates, Accum) -> - CandidateModules = get_modules(Candidates), + CandidateModules = get_element_modules(Candidates), CurrentsImports = get_imports(Current), CurrentsAtoms = get_atoms(Current), DepModules = intersection(CurrentsImports ++ CurrentsAtoms, CandidateModules) -- Accum, @@ -441,7 +496,7 @@ closure(Current, Candidates, Accum) -> %% @private remove(Module, ParsedFiles) -> - [P || P <- ParsedFiles, Module =/= proplists:get_value(module, P)]. + [P || P <- ParsedFiles, Module =/= get_element_module(P)]. %% @private get_imports(ParsedFile) -> @@ -487,26 +542,14 @@ extract_atoms(_Term, Accum) -> Accum. %% @private -get_modules(ParsedFiles) -> - [get_module(ParsedFile) || ParsedFile <- ParsedFiles]. - -%% @private -get_module(ParsedFile) -> - proplists:get_value(module, ParsedFile). - -%% @private -get_module_name(ParsedFile) -> - proplists:get_value(module_name, ParsedFile). - -%% @private -get_data(ParsedFile) -> - proplists:get_value(data, ParsedFile). +get_element_modules(ParsedFiles) -> + [get_element_module(ParsedFile) || ParsedFile <- ParsedFiles]. %% @private get_parsed_file(Module, ParsedFiles) -> SearchResult = lists:search( fun(ParsedFile) -> - proplists:get_value(module, ParsedFile) =:= Module + get_element_module(ParsedFile) =:= Module end, ParsedFiles ), @@ -532,7 +575,7 @@ filter_modules(Modules, ParsedFiles) -> fun(ParsedFile) -> case is_beam(ParsedFile) of true -> - lists:member(get_module(ParsedFile), Modules); + lists:member(get_element_module(ParsedFile), Modules); _ -> true end @@ -558,7 +601,7 @@ parse_file(beam, _ModuleName, Data, IncludeLines) -> [ [ {module, Module}, - {module_name, io_lib:format("~s.beam", [atom_to_list(Module)])}, + {element_name, io_lib:format("~s.beam", [atom_to_list(Module)])}, {flags, Flags}, {data, Binary}, {chunk_refs, ChunkRefs}, @@ -575,7 +618,7 @@ parse_file(avm, ModuleName, Data, _IncludeLines) -> parse_file(normal, ModuleName, Data, _IncludeLines) -> DataSize = byte_size(Data), Blob = <>, - [[{module_name, ModuleName}, {flags, ?NORMAL_FILE_FLAG}, {data, Blob}]]. + [[{element_name, ModuleName}, {flags, ?NORMAL_FILE_FLAG}, {data, Blob}]]. %% @private reorder_start_module(StartModule, Files) -> @@ -583,10 +626,9 @@ reorder_start_module(StartModule, Files) -> lists:partition( fun(Props) -> % io:format("Props: ~w~n", [Props]), - case proplists:get_value(module, Props) of + case get_element_module(Props) of StartModule -> - Flags = proplists:get_value(flags, Props), - case is_entrypoint(Flags) of + case is_entrypoint(Props) of true -> true; _ -> throw({start_module_not_start_beam, StartModule}) end; @@ -629,17 +671,17 @@ parse_avm_data(<>, Accum) -> %% @private parse_beam(<>, [], in_header, Pos, Accum) -> - parse_beam(Rest, [], in_module_name, Pos + 8, [{flags, Flags} | Accum]); -parse_beam(<<0:8, Rest/binary>>, Tmp, in_module_name, Pos, Accum) -> + parse_beam(Rest, [], in_element_name, Pos + 8, [{flags, Flags} | Accum]); +parse_beam(<<0:8, Rest/binary>>, Tmp, in_element_name, Pos, Accum) -> ModuleName = lists:reverse(Tmp), case (Pos + 1) rem 4 of 0 -> - parse_beam(Rest, Tmp, in_data, Pos, [{module_name, ModuleName} | Accum]); + parse_beam(Rest, Tmp, in_data, Pos, [{element_name, ModuleName} | Accum]); _ -> - parse_beam(Rest, [], eat_padding, Pos + 1, [{module_name, ModuleName} | Accum]) + parse_beam(Rest, [], eat_padding, Pos + 1, [{element_name, ModuleName} | Accum]) end; -parse_beam(<>, Tmp, in_module_name, Pos, Accum) -> - parse_beam(Rest, [C | Tmp], in_module_name, Pos + 1, Accum); +parse_beam(<>, Tmp, in_element_name, Pos, Accum) -> + parse_beam(Rest, [C | Tmp], in_element_name, Pos + 1, Accum); parse_beam(<<0:8, Rest/binary>>, Tmp, eat_padding, Pos, Accum) -> case (Pos + 1) rem 4 of 0 -> @@ -650,8 +692,7 @@ parse_beam(<<0:8, Rest/binary>>, Tmp, eat_padding, Pos, Accum) -> parse_beam(Bin, Tmp, eat_padding, Pos, Accum) -> parse_beam(Bin, Tmp, in_data, Pos, Accum); parse_beam(Data, _Tmp, in_data, _Pos, Accum) -> - Flags = proplists:get_value(flags, Accum), - case is_beam(Flags) orelse is_entrypoint(Flags) of + case is_beam(Accum) orelse is_entrypoint(Accum) of true -> StrippedData = strip_padding(Data), {ok, {Module, ChunkRefs}} = beam_lib:chunks(StrippedData, [imports, exports, atoms]), @@ -692,9 +733,9 @@ write_packbeam(OutputFilePath, ParsedFiles) -> %% @private pack_data(ParsedFile) -> - ModuleName = list_to_binary(proplists:get_value(module_name, ParsedFile)), - Flags = proplists:get_value(flags, ParsedFile), - Data = proplists:get_value(data, ParsedFile), + ModuleName = list_to_binary(get_element_name(ParsedFile)), + Flags = get_flags(ParsedFile), + Data = get_element_data(ParsedFile), HeaderSize = header_size(ModuleName), HeaderPadding = create_padding(HeaderSize), DataSize = byte_size(Data), @@ -739,7 +780,7 @@ allowed_chunks(true) -> remove_names(Names, ParsedFiles) -> lists:filter( fun(ParsedFile) -> - ModuleName = proplists:get_value(module_name, ParsedFile), + ModuleName = get_element_name(ParsedFile), not lists:member(ModuleName, Names) end, ParsedFiles @@ -752,12 +793,12 @@ write_files(ParsedFiles, OutputDir) -> io:format("Writing to ~s ...~n", [OutputDir]), lists:foreach( fun(ParsedFile) -> - ModuleName = proplists:get_value(module_name, ParsedFile), + ModuleName = get_element_name(ParsedFile), Path = OutputDir ++ "/" ++ ModuleName, case filelib:ensure_dir(Path) of ok -> io:format("x ~s~n", [ModuleName]), - RawData = proplists:get_value(data, ParsedFile), + RawData = get_element_data(ParsedFile), Data = case file_type(ModuleName) of normal -> @@ -784,7 +825,7 @@ filter_names([], ParsedFiles) -> filter_names(Names, ParsedFiles) -> lists:filter( fun(ParsedFile) -> - ModuleName = proplists:get_value(module_name, ParsedFile), + ModuleName = get_element_name(ParsedFile), lists:member(ModuleName, Names) end, ParsedFiles diff --git a/test/prop_packbeam.erl b/test/prop_packbeam.erl index 83af604..3615b3a 100644 --- a/test/prop_packbeam.erl +++ b/test/prop_packbeam.erl @@ -108,16 +108,16 @@ dest_dir(AVMFile) -> ?BUILD_DIR ++ AVMFile. get_module(ParsedFile) -> - proplists:get_value(module, ParsedFile). + packbeam_api:get_element_module(ParsedFile). get_module_name(ParsedFile) -> - proplists:get_value(module_name, ParsedFile). + packbeam_api:get_element_name(ParsedFile). is_start(ParsedFile) -> - proplists:get_value(flags, ParsedFile) band 16#01 == 16#01. + packbeam_api:is_entrypoint(ParsedFile). is_beam(ParsedFile) -> - proplists:get_value(flags, ParsedFile) band 16#02 == 16#02. + packbeam_api:is_beam(ParsedFile). %% %% Generators diff --git a/test/test_packbeam.erl b/test/test_packbeam.erl index 104149c..79e6796 100644 --- a/test/test_packbeam.erl +++ b/test/test_packbeam.erl @@ -408,19 +408,19 @@ test_beam_path(BeamFile) -> ?TEST_BEAM_DIR ++ BeamFile. get_module(ParsedFile) -> - proplists:get_value(module, ParsedFile). + packbeam_api:get_element_module(ParsedFile). get_module_name(ParsedFile) -> - proplists:get_value(module_name, ParsedFile). + packbeam_api:get_element_name(ParsedFile). get_exports(ParsedFile) -> proplists:get_value(exports, proplists:get_value(chunk_refs, ParsedFile)). is_start(ParsedFile) -> - proplists:get_value(flags, ParsedFile) band 16#01 == 16#01. + packbeam_api:is_entrypoint(ParsedFile). is_beam(ParsedFile) -> - proplists:get_value(flags, ParsedFile) band 16#02 == 16#02. + packbeam_api:is_beam(ParsedFile). parsed_file_contains_module(Module, ParsedFiles) -> lists:any(