From c8b0b80eb50a78940987c946d2483c3399827bd1 Mon Sep 17 00:00:00 2001 From: Fred Dushin Date: Sat, 18 Nov 2023 09:23:14 -0500 Subject: [PATCH] Add support for OTP application framework in AtomVM Signed-off-by: Fred Dushin --- CHANGELOG.md | 8 + assets/init_shim.erl | 6 + examples/otp_application/.gitignore | 11 + examples/otp_application/LICENSE | 190 ++++++++++++++++++ examples/otp_application/README.md | 62 ++++++ examples/otp_application/rebar.config | 25 +++ .../src/otp_application.app.src | 30 +++ .../src/otp_application_app.erl | 26 +++ .../src/otp_application_sup.erl | 42 ++++ .../src/otp_application_worker.erl | 75 +++++++ src/atomvm_packbeam_provider.erl | 81 ++++++-- test/driver/apps/otp_application/rebar.config | 25 +++ .../apps/otp_application/src/my_app.app.src | 30 +++ .../apps/otp_application/src/my_app.erl | 25 +++ test/driver/src/packbeam_tests.erl | 34 ++++ 15 files changed, 655 insertions(+), 15 deletions(-) create mode 100644 assets/init_shim.erl create mode 100644 examples/otp_application/.gitignore create mode 100644 examples/otp_application/LICENSE create mode 100644 examples/otp_application/README.md create mode 100644 examples/otp_application/rebar.config create mode 100644 examples/otp_application/src/otp_application.app.src create mode 100644 examples/otp_application/src/otp_application_app.erl create mode 100644 examples/otp_application/src/otp_application_sup.erl create mode 100644 examples/otp_application/src/otp_application_worker.erl create mode 100644 test/driver/apps/otp_application/rebar.config create mode 100644 test/driver/apps/otp_application/src/my_app.app.src create mode 100644 test/driver/apps/otp_application/src/my_app.erl diff --git a/CHANGELOG.md b/CHANGELOG.md index 3553e18..4d7a598 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ 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.4] (unreleased) + +- Added support for the `--application` (or `-a`) option to support AtomVM OTP applications. + +### Changed + +- Using the `-s init` option is still supported but deprecated. Use the `--application` (or `-a`) option to generate OTP applications using AtomVM. + ## [0.7.3] (2023.11.25) - Added support for compiling "bootstrap" erlang files that `rebar3` otherwise cannot compile. diff --git a/assets/init_shim.erl b/assets/init_shim.erl new file mode 100644 index 0000000..d005d9d --- /dev/null +++ b/assets/init_shim.erl @@ -0,0 +1,6 @@ +-module(init_shim). + +-export([start/0]). + +start() -> + init:boot(). diff --git a/examples/otp_application/.gitignore b/examples/otp_application/.gitignore new file mode 100644 index 0000000..72536b5 --- /dev/null +++ b/examples/otp_application/.gitignore @@ -0,0 +1,11 @@ +## +## Copyright 2023 +## +## SPDX-License-Identifier: Apache-2.0 +## + +_build/* +_checkouts/* +rebar.lock +rebar3.crashdump +erl_crash.dump diff --git a/examples/otp_application/LICENSE b/examples/otp_application/LICENSE new file mode 100644 index 0000000..1ac85b9 --- /dev/null +++ b/examples/otp_application/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2023 + + 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. diff --git a/examples/otp_application/README.md b/examples/otp_application/README.md new file mode 100644 index 0000000..83b6ed8 --- /dev/null +++ b/examples/otp_application/README.md @@ -0,0 +1,62 @@ +# otp_application + +Welcome to the `otp_application` AtomVM application. + +This application demonstrates how to build and deploy an AtomVM application that leverages the OTP application behavior. + +This application consists of the `otp_application_app` module, which implements the OTP [Application](https://www.erlang.org/doc/design_principles/applications) behavior, the `otp_application_sup` module, which implements the OTP [Supervisor](https://www.erlang.org/doc/design_principles/sup_princ), and the `otp_application_worker` module, which implements the OTP [GenServer](https://www.erlang.org/doc/design_principles/gen_server_concepts) behavior. + +The application and its dependencies are automatically started when the AtomVM application is run. The worker module in this example is designed to send a tick message to itself once/second. After 5 ticks, the application deliberately crashes and is restarted by the application's supervisor. + +Note the definition of the application in the `otp_application.app.src` file: + + {application, otp_application, [ + {description, "An AtomVM application"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {otp_application_app, #{crash_interval => 5}}}, + {applications, [ + kernel, stdlib + ]}, + {env,[]}, + {modules, []}, + {licenses, ["Apache-2.0"]}, + {links, []} + ]}. + +Specifically, the `mod` entry tells the OTP framework which application entrypoint to load the run when AtomVM starts. + +Note also the `packbeam` properties of the `atomvm_rebar3_plguin` entry in the `rebar3.config` file: + + {atomvm_rebar3_plugin, [ + {packbeam, [application, prune]} + ]}. + +This property tell the `atomvm_rebar3_plugin` to treat this project as an OTP application. In this case, the project is expected to implement the OTP application behavior. In addition, users need not (and should not) specify a start entrypoint for the application; starting the OTP application and its dependencies is performed automatically by the OTP framework. + +## Building and running this application + +To build the application, issue the `packbeam` task under the `atomvm` namespace to the [`rebar3`](https://rebar3.org) tool: + + shell$ rebar3 atomvm packbeam + +If you have AtomVM installed on a generic UNIX host, you can run this application on the command line, supplying the generated AtomVM AVM file as an argument: + + shell$ atomvm _build/default/lib/otp_application.avm + Starting otp_application_app with start type normal and start args #{crash_interval => 5} ... + Starting otp_application_sup with args #{crash_interval => 5} ... + Application otp_application started + tick + tick + tick + tick + tick + boom! + CRASH + ====== + pid: <0.5.0> + + Stacktrace: + [{otp_application_worker,handle_info,2,[{file,"/home/joe/atomvm_rebar3_plugin/examples/otp_application/src/otp_application_worker.erl"},{line,56}]},{gen_server,loop,2,[{file,"/home/runner/AtomVM/libs/estdlib/bootstrap/gen_server.erl"},{line,468}]}] + +For more information about how to build and flash this application on other platforms, see the [`atomvm_rebar3_plugin`](https://github.com/atomvm/atomvm_rebar3_plugin) Github repository. diff --git a/examples/otp_application/rebar.config b/examples/otp_application/rebar.config new file mode 100644 index 0000000..2d86294 --- /dev/null +++ b/examples/otp_application/rebar.config @@ -0,0 +1,25 @@ +%% +%% Copyright (c) 2023 +%% All rights reserved. +%% +%% 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. +%% + +{erl_opts, [debug_info]}. +{deps, []}. +{plugins, [ + atomvm_rebar3_plugin +]}. +{atomvm_rebar3_plugin, [ + {packbeam, [application, prune]} +]}. diff --git a/examples/otp_application/src/otp_application.app.src b/examples/otp_application/src/otp_application.app.src new file mode 100644 index 0000000..b8ab517 --- /dev/null +++ b/examples/otp_application/src/otp_application.app.src @@ -0,0 +1,30 @@ +%% +%% Copyright (c) 2023 +%% All rights reserved. +%% +%% 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. +%% + +{application, otp_application, [ + {description, "An AtomVM application"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {otp_application_app, #{crash_interval => 5}}}, + {applications, [ + kernel, stdlib + ]}, + {env,[]}, + {modules, []}, + {licenses, ["Apache-2.0"]}, + {links, []} + ]}. diff --git a/examples/otp_application/src/otp_application_app.erl b/examples/otp_application/src/otp_application_app.erl new file mode 100644 index 0000000..aa787d8 --- /dev/null +++ b/examples/otp_application/src/otp_application_app.erl @@ -0,0 +1,26 @@ +%% +%% Copyright (c) 2023 +%% All rights reserved. +%% +%% 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. +%% +-module(otp_application_app). + +-export([start/2, stop/1]). + +start(StartType, StartArgs) -> + io:format("Starting otp_application_app with start type ~p and start args ~p ...~n", [StartType, StartArgs]), + otp_application_sup:start(StartArgs). + +stop(_State) -> + ok. diff --git a/examples/otp_application/src/otp_application_sup.erl b/examples/otp_application/src/otp_application_sup.erl new file mode 100644 index 0000000..a9d101c --- /dev/null +++ b/examples/otp_application/src/otp_application_sup.erl @@ -0,0 +1,42 @@ +%% +%% Copyright (c) 2023 +%% All rights reserved. +%% +%% 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. +%% +-module(otp_application_sup). + +-export([start/1, init/1]). + +start(Args) -> + io:format("Starting otp_application_sup with args ~p ...~n", [Args]), + supervisor:start_link({local, ?MODULE}, ?MODULE, Args). + +%% +%% supervisor implementation +%% + +init(Args) -> + {ok, { + {one_for_one, 1, 1}, [ + { + otp_application_worker, + {otp_application_worker, start_link, [Args]}, + permanent, + brutal_kill, + worker, + [] + } + ] + } +}. diff --git a/examples/otp_application/src/otp_application_worker.erl b/examples/otp_application/src/otp_application_worker.erl new file mode 100644 index 0000000..f3cd338 --- /dev/null +++ b/examples/otp_application/src/otp_application_worker.erl @@ -0,0 +1,75 @@ +%% +%% Copyright (c) 2023 +%% All rights reserved. +%% +%% 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. +%% +-module(otp_application_worker). + +-export([start_link/1]). + +-behavior(gen_server). +-export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2]). + +%% +%% api +%% + +start_link(Args) -> + gen_server:start_link(?MODULE, Args, []). + +%% +%% gen_server implementation +%% + +-record(state, { + crash_interval, + counter = 0 +}). + +%% @hidden +init(Args) -> + send_tick(), + {ok, #state{crash_interval = maps:get(crash_interval, Args, 5)}}. + +%% @hidden +handle_cast(_Request, State) -> + {noreply, State}. + +%% @hidden +handle_call(_Request, _From, State) -> + {reply, {error, unimplemented}, State}. + +%% @hidden +handle_info(tick, #state{counter = X, crash_interval = X} = State) -> + io:format("boom!~n"), + exit(boom), + {noreply, State}; +handle_info(tick, State) -> + io:format("tick~n"), + send_tick(), + {noreply, State#state{counter = State#state.counter + 1}}; +handle_info(_Msg, State) -> + {noreply, State}. + +%% @hidden +terminate(_Reason, _State) -> + ok. + +%% +%% internal implementation +%% + +%% @private +send_tick() -> + erlang:send_after(1000, self(), tick). diff --git a/src/atomvm_packbeam_provider.erl b/src/atomvm_packbeam_provider.erl index e39cca7..988aa57 100644 --- a/src/atomvm_packbeam_provider.erl +++ b/src/atomvm_packbeam_provider.erl @@ -29,6 +29,7 @@ {force, $f, "force", boolean, "Force rebuild"}, {prune, $p, "prune", boolean, "Prune unreferenced BEAM files"}, {start, $s, "start", atom, "Start module"}, + {application, $a, "application", boolean, "Build a OTP application"}, {remove_lines, $r, "remove_lines", boolean, "Remove line information from generated AVM files (off by default)"}, {list, $l, "list", boolean, "List the contents of AVM files after creation"} ]). @@ -38,10 +39,32 @@ force => false, prune => false, start => undefined, + application => false, remove_lines => false, list => false }). +%% abstract representation of a simple shim that +%% delegates to `init:start/0`. This form will +%% be compiled and inserted into the AVM if the +%% user has indicated that the project is an (OTP) +%% application. +%% +%% This form was generated from the init_shim.erl +%% asset, as follows: +%% +%% epp:parse_file("assets/init_shim.erl", []). +%% +-define(INIT_SHIM_FORMS, [ + {attribute, 1, file, {"assets/init_shim.erl", 1}}, + {attribute, 1, module, init_shim}, + {attribute, 3, export, [{start, 0}]}, + {function, 5, start, 0, [ + {clause, 5, [], [], [{call, 6, {remote, 6, {atom, 6, init}, {atom, 6, boot}}, []}]} + ]}, + {eof, 7} +]). + -record(file_set, { name, out_dir, beam_files, priv_files, app_file }). @@ -86,6 +109,7 @@ do(State) -> maps:get(prune, Opts), maps:get(force, Opts), get_start_module(Opts), + maps:get(application, Opts), not maps:get(remove_lines, Opts), maps:get(list, Opts) ), @@ -143,16 +167,16 @@ squash_external_avms(ParsedArgs) -> ). %% @private -do_packbeam(ProjectApps, Deps, ExternalAVMs, Prune, Force, StartModule, IncludeLines, List) -> +do_packbeam(ProjectApps, Deps, ExternalAVMs, Prune, Force, StartModule, IsApplication, IncludeLines, List) -> DepFileSets = [get_files(Dep) || Dep <- Deps], ProjectAppFileSets = [get_files(ProjectApp) || ProjectApp <- ProjectApps], DepsAvms = [ - maybe_create_packbeam(DepFileSet, [], false, Force, undefined, IncludeLines, false) + maybe_create_packbeam(DepFileSet, [], false, Force, undefined, false, IncludeLines, false) || DepFileSet <- DepFileSets ], [ maybe_create_packbeam( - ProjectAppFileSet, DepsAvms ++ ExternalAVMs, Prune, Force, StartModule, IncludeLines, List + ProjectAppFileSet, DepsAvms ++ ExternalAVMs, Prune, Force, StartModule, IsApplication, IncludeLines, List ) || ProjectAppFileSet <- ProjectAppFileSets ], @@ -224,7 +248,7 @@ get_all_files(Dir) -> RegularFiles ++ SubFiles. %% @private -maybe_create_packbeam(FileSet, AvmFiles, Prune, Force, StartModule, IncludeLines, List) -> +maybe_create_packbeam(FileSet, AvmFiles, Prune, Force, StartModule, IsApplication, IncludeLines, List) -> #file_set{ name = Name, out_dir = OutDir, @@ -237,7 +261,7 @@ maybe_create_packbeam(FileSet, AvmFiles, Prune, Force, StartModule, IncludeLines AppFiles = case AppFile of undefined -> []; _ -> [AppFile] end, case Force orelse needs_build(TargetAVM, BeamFiles ++ PrivFiles ++ AvmFiles ++ AppFiles) of true -> - create_packbeam(FileSet, AvmFiles, Prune, StartModule, IncludeLines, List); + create_packbeam(FileSet, AvmFiles, Prune, StartModule, IsApplication, IncludeLines, List); _ -> rebar_api:debug("No packbeam build needed.", []), TargetAVM @@ -258,7 +282,7 @@ latest_modified_time(PathList) -> lists:max([modified_time(Path) || Path <- PathList]). %% @private -create_packbeam(FileSet, AvmFiles, Prune, StartModule, IncludeLines, List) -> +create_packbeam(FileSet, AvmFiles, Prune, StartModule, IsApplication, IncludeLines, List) -> #file_set{ name = Name, out_dir = OutDir, @@ -269,21 +293,31 @@ create_packbeam(FileSet, AvmFiles, Prune, StartModule, IncludeLines, List) -> N = length(OutDir) + 1, PrivFilesRelative = [filename:join(Name, string:slice(PrivFile, N)) || PrivFile <- PrivFiles], {ApplicationModule, AppFileBinFiles} = create_app_file_bin_files(Name, OutDir, AppFile), - BootFile = case StartModule of - init -> - [create_boot_file(OutDir, ApplicationModule)]; - _ -> - [] - end, + BootFiles = + case IsApplication of + true -> + [create_boot_file(OutDir, ApplicationModule), create_init_shim(OutDir)]; + _ -> + case StartModule of + init -> + rebar_api:warn( + "Specifying `init` as the start module to generate an OTP application is deprecated. " + "Use the `--application` (or `-a`) option, instead.", [] + ), + [create_boot_file(OutDir, ApplicationModule)]; + _ -> + [] + end + end, Cwd = rebar_dir:get_cwd(), try DirName = filename:dirname(OutDir), ok = file:set_cwd(DirName), AvmFilename = Name ++ ".avm", - FileList = reorder_beamfiles(BeamFiles) ++ AppFileBinFiles ++ BootFile ++ PrivFilesRelative ++ AvmFiles, + FileList = reorder_beamfiles(BeamFiles) ++ AppFileBinFiles ++ BootFiles ++ PrivFilesRelative ++ AvmFiles, Opts = #{ prune => Prune, - start_module => StartModule, + start_module => effective_start_module(StartModule, IsApplication), application_module => ApplicationModule, include_lines => IncludeLines }, @@ -301,6 +335,12 @@ create_packbeam(FileSet, AvmFiles, Prune, StartModule, IncludeLines, List) -> ok = file:set_cwd(Cwd) end. +%% @private +effective_start_module(_StartModule, true) -> + init_shim; +effective_start_module(StartModule, _) -> + StartModule. + %% @private maybe_list(_, false) -> ok; @@ -355,7 +395,18 @@ create_boot_file(OutDir, ApplicationModule) -> WritePath = filename:join([OutDir, "start.boot"]), Bin = erlang:term_to_binary(BootSpec), ok = file:write_file(WritePath, Bin), - {WritePath, filename:join(["init", "priv", "start.boot"])}. + StartBootPath = filename:join(["init", "priv", "start.boot"]), + rebar_api:debug("Created boot file ~s in ~s", [StartBootPath, WritePath]), + {WritePath, StartBootPath}. + +%% @private +create_init_shim(OutDir) -> + EBinDir = filename:join([OutDir, "ebin"]), + {ok, init_shim, Data} = compile:forms(?INIT_SHIM_FORMS, []), + WritePath = filename:join([EBinDir, "init_shim.beam"]), + ok = file:write_file(WritePath, Data), + rebar_api:debug("Created init_shim.beam in ~s", [WritePath]), + {WritePath, "init_shim.beam"}. %% @private reorder_beamfiles(BeamFiles) -> diff --git a/test/driver/apps/otp_application/rebar.config b/test/driver/apps/otp_application/rebar.config new file mode 100644 index 0000000..2d86294 --- /dev/null +++ b/test/driver/apps/otp_application/rebar.config @@ -0,0 +1,25 @@ +%% +%% Copyright (c) 2023 +%% All rights reserved. +%% +%% 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. +%% + +{erl_opts, [debug_info]}. +{deps, []}. +{plugins, [ + atomvm_rebar3_plugin +]}. +{atomvm_rebar3_plugin, [ + {packbeam, [application, prune]} +]}. diff --git a/test/driver/apps/otp_application/src/my_app.app.src b/test/driver/apps/otp_application/src/my_app.app.src new file mode 100644 index 0000000..5066735 --- /dev/null +++ b/test/driver/apps/otp_application/src/my_app.app.src @@ -0,0 +1,30 @@ +%% +%% Copyright (c) 2023 +%% All rights reserved. +%% +%% 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. +%% + +{application, my_app, [ + {description, "An AtomVM application"}, + {vsn, "0.1.0"}, + {registered, []}, + {applications, [ + kernel, stdlib + ]}, + {mod, {my_app, #{foo => bar}}}, + {env,[]}, + {modules, []}, + {licenses, ["Apache-2.0"]}, + {links, []} + ]}. diff --git a/test/driver/apps/otp_application/src/my_app.erl b/test/driver/apps/otp_application/src/my_app.erl new file mode 100644 index 0000000..6b9ea7f --- /dev/null +++ b/test/driver/apps/otp_application/src/my_app.erl @@ -0,0 +1,25 @@ +%% +%% Copyright (c) 2023 +%% All rights reserved. +%% +%% 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. +%% +-module(my_app). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + {ok, dummy_pid}. + +stop(_State) -> + ok. diff --git a/test/driver/src/packbeam_tests.erl b/test/driver/src/packbeam_tests.erl index e42e183..70f4b1a 100644 --- a/test/driver/src/packbeam_tests.erl +++ b/test/driver/src/packbeam_tests.erl @@ -23,6 +23,7 @@ run(Opts) -> ok = test_start(Opts), ok = test_prune(Opts), ok = test_rebar_overrides(Opts), + ok = test_otp_application(Opts), ok. %% @private @@ -178,6 +179,39 @@ test_rebar_overrides(Opts) -> test:tick(). +%% @private +test_otp_application(Opts) -> + + AppsDir = maps:get(apps_dir, Opts), + AppDir = test:make_path([AppsDir, "otp_application"]), + + Cmd = create_packbeam_cmd(AppDir, ["-f"], []), %% -f temporary during dev + Output = test:execute_cmd(Cmd, Opts), + test:debug(Output, Opts), + + ok = test:expect_contains("AVM file written to", Output), + ok = test:expect_contains("_build/default/lib/my_app.avm", Output), + AVMPath = test:make_path([AppDir, "_build/default/lib/my_app.avm"]), + ok = test:file_exists(AVMPath), + AVMElements = test:get_avm_elements(AVMPath), + + [InitShimBeam | _Rest] = AVMElements, + true = packbeam_api:is_beam(InitShimBeam), + true = packbeam_api:is_entrypoint(InitShimBeam), + + {value, StartBoot} = test:find_avm_element_by_name("init/priv/start.boot", AVMElements), + false = packbeam_api:is_beam(StartBoot), + + {value, MyAppBeam} = test:find_avm_element_by_name("my_app.beam", AVMElements), + true = packbeam_api:is_beam(MyAppBeam), + false = packbeam_api:is_entrypoint(MyAppBeam), + + {value, MyAppApplicationBin} = test:find_avm_element_by_name("my_app/priv/application.bin", AVMElements), + false = packbeam_api:is_beam(MyAppApplicationBin), + false = packbeam_api:is_entrypoint(MyAppApplicationBin), + + test:tick(). + %% @private create_packbeam_cmd(AppDir, Opts, Env) -> test:create_rebar3_cmd(AppDir, packbeam, Opts, Env).