diff options
author | Pierre Fenoll <pierrefenoll@gmail.com> | 2015-03-04 12:23:23 -0800 |
---|---|---|
committer | Pierre Fenoll <pierrefenoll@gmail.com> | 2015-03-06 17:27:47 -0800 |
commit | 2d3a0ebe213a4c6c004eb2986606f4a9e979cd62 (patch) | |
tree | 38356e068d472706b64ab796406de3ec5adcec30 | |
parent | bc5d1cb155ad99d6e605cc80952bd34d8a3de17e (diff) |
Add escriptize provider and a minimal test
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | THANKS | 1 | ||||
-rw-r--r-- | src/rebar.app.src | 23 | ||||
-rw-r--r-- | src/rebar_prv_escriptize.erl | 221 | ||||
-rw-r--r-- | test/rebar_escriptize_SUITE.erl | 37 |
5 files changed, 272 insertions, 18 deletions
@@ -38,6 +38,7 @@ limit scope. | dialyzer | Run the Dialyzer analyzer on the project | | edoc | Generate documentation using edoc | | eunit | Run eunit tests | +| escriptize | Generate escript of project | | help | Print help for rebar or task | | new | Create new rebar project from templates | | pkgs | List available packages | @@ -49,13 +50,6 @@ limit scope. | version | Print current version of Erlang/OTP and rebar | | xref | Run cross reference analysis on the project | -### Commands still to do - -| Command | Description | -|----------- |------------ | -| escriptize | Generate escript of project | - - ### Changes * Fetches and builds deps if missing when running any command that relies on them @@ -131,3 +131,4 @@ Omar Yasin Tristan Sloughter Kelly McLaughlin Martin Karlsson +Pierre Fenoll diff --git a/src/rebar.app.src b/src/rebar.app.src index 8c544aa..bd98277 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -23,30 +23,31 @@ %% Default log level {log_level, warn}, - {providers, [rebar_prv_as, + {providers, [rebar_prv_app_discovery, + rebar_prv_as, rebar_prv_clean, + rebar_prv_common_test, + rebar_prv_compile, rebar_prv_cover, rebar_prv_deps, rebar_prv_dialyzer, rebar_prv_do, - rebar_prv_eunit, rebar_prv_edoc, - rebar_prv_lock, + rebar_prv_erlydtl_compiler, + rebar_prv_escriptize, + rebar_prv_eunit, + rebar_prv_help, rebar_prv_install_deps, + rebar_prv_lock, + rebar_prv_new, rebar_prv_packages, - rebar_prv_erlydtl_compiler, - rebar_prv_compile, - rebar_prv_app_discovery, + rebar_prv_release, rebar_prv_shell, rebar_prv_tar, - rebar_prv_new, rebar_prv_update, rebar_prv_upgrade, - rebar_prv_release, rebar_prv_version, - rebar_prv_common_test, rebar_prv_wtf, - rebar_prv_xref, - rebar_prv_help]} + rebar_prv_xref]} ]} ]}. diff --git a/src/rebar_prv_escriptize.erl b/src/rebar_prv_escriptize.erl new file mode 100644 index 0000000..0254d24 --- /dev/null +++ b/src/rebar_prv_escriptize.erl @@ -0,0 +1,221 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_prv_escriptize). + +-behaviour(provider). + +-export([init/1, do/1, format_error/1]). + +-define(PROVIDER, escriptize). +-define(DEPS, [compile]). + +-include("rebar.hrl"). + +-include_lib("providers/include/providers.hrl"). +-include_lib("kernel/include/file.hrl"). + +%%%============================================================================= +%%% API +%%%============================================================================= + +-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. +init(State) -> + Provider = providers:create([ + {name, ?PROVIDER}, + {module, ?MODULE}, + {bare, false}, + {deps, ?DEPS}, + {example, "rebar escriptize"}, + {opts, []}, + {short_desc, "Generate escript archive"}, + {desc, desc()} + ]), + {ok, rebar_state:add_provider(State, Provider)}. + +desc() -> + "Generate an escript executable containing " + "the project's and its dependencies' BEAM files.". + +do(State) -> + escriptize(State). + +escriptize(State0) -> + App1 = case rebar_state:project_apps(State0) of + [App] -> + App; + Apps -> + case rebar_state:get(escript_main_app, State0, undefined) of + undefined -> + ?PRV_ERROR(no_main_app); + Name -> + rebar_app_utils:find(Name, Apps) + end + end, + + AppName = rebar_app_info:name(App1), + AppNameStr = ec_cnv:to_list(AppName), + + %% Get the output filename for the escript -- this may include dirs + Filename = filename:join([rebar_dir:base_dir(State0), "bin", + rebar_state:get(State0, escript_name, AppName)]), + ok = filelib:ensure_dir(Filename), + State = rebar_state:escript_path(State0, Filename), + + %% Look for a list of other applications (dependencies) to include + %% in the output file. We then use the .app files for each of these + %% to pull in all the .beam files. + InclApps = lists:usort(rebar_state:get(State, escript_incl_apps, []) + ++ all_deps(State)), + InclBeams = get_app_beams(InclApps), + + %% Look for a list of extra files to include in the output file. + %% For internal rebar-private use only. Do not use outside rebar. + InclExtra = get_extra(State), + + %% Construct the archive of everything in ebin/ dir -- put it on the + %% top-level of the zip file so that code loading works properly. + EbinPrefix = filename:join(AppNameStr, "ebin"), + EbinFiles = usort(load_files(EbinPrefix, "*", "ebin")), + + ExtraFiles = usort(InclBeams ++ InclExtra), + Files = get_nonempty(EbinFiles ++ ExtraFiles), + + DefaultEmuArgs = ?FMT("%%! -escript main ~s -pa ~s/~s/ebin\n", + [AppNameStr, AppNameStr, AppNameStr]), + EscriptSections = + [ {shebang, + def("#!", State, escript_shebang, "#!/usr/bin/env escript\n")} + , {comment, def("%%", State, escript_comment, "%%\n")} + , {emu_args, def("%%!", State, escript_emu_args, DefaultEmuArgs)} + , {archive, Files, []} ], + case escript:create(Filename, EscriptSections) of + ok -> ok; + {error, EscriptError} -> + throw(?PRV_ERROR({escript_creation_failed, AppName, EscriptError})) + end, + + %% Finally, update executable perms for our script + {ok, #file_info{mode = Mode}} = file:read_file_info(Filename), + ok = file:change_mode(Filename, Mode bor 8#00111), + {ok, State}. + +-spec format_error(any()) -> iolist(). +format_error({write_failed, AppName, WriteError}) -> + io_lib:format("Failed to write ~p script: ~p", [AppName, WriteError]); +format_error({zip_error, AppName, ZipError}) -> + io_lib:format("Failed to construct ~p escript: ~p", [AppName, ZipError]); +format_error({bad_name, App}) -> + io_lib:format("Failed to get ebin/ directory for " + "escript_incl_app: ~p", [App]); +format_error(no_main_app) -> + io_lib:format("Multiple project apps and {rebar_escript_plugin, [{main_app, atom()}]}." + " not set in rebar.config", []). + +%% =================================================================== +%% Internal functions +%% =================================================================== + +get_app_beams(Apps) -> + get_app_beams(Apps, []). + +get_app_beams([], Acc) -> + Acc; +get_app_beams([App | Rest], Acc) -> + case code:lib_dir(App, ebin) of + {error, bad_name} -> + throw(?PRV_ERROR({bad_name, App})); + Path -> + Prefix = filename:join(atom_to_list(App), "ebin"), + Acc2 = load_files(Prefix, "*.beam", Path), + get_app_beams(Rest, Acc2 ++ Acc) + end. + +get_extra(State) -> + Extra = rebar_state:get(State, escript_incl_extra, []), + lists:foldl(fun({Wildcard, Dir}, Files) -> + load_files(Wildcard, Dir) ++ Files + end, [], Extra). + +load_files(Wildcard, Dir) -> + load_files("", Wildcard, Dir). + +load_files(Prefix, Wildcard, Dir) -> + [read_file(Prefix, Filename, Dir) + || Filename <- filelib:wildcard(Wildcard, Dir)]. + +read_file(Prefix, Filename, Dir) -> + Filename1 = case Prefix of + "" -> + Filename; + _ -> + filename:join([Prefix, Filename]) + end, + [dir_entries(filename:dirname(Filename1)), + {Filename1, file_contents(filename:join(Dir, Filename))}]. + +file_contents(Filename) -> + {ok, Bin} = file:read_file(Filename), + Bin. + +%% Given a filename, return zip archive dir entries for each sub-dir. +%% Required to work around issues fixed in OTP-10071. +dir_entries(File) -> + Dirs = dirs(File), + [{Dir ++ "/", <<>>} || Dir <- Dirs]. + +%% Given "foo/bar/baz", return ["foo", "foo/bar", "foo/bar/baz"]. +dirs(Dir) -> + dirs1(filename:split(Dir), "", []). + +dirs1([], _, Acc) -> + lists:reverse(Acc); +dirs1([H|T], "", []) -> + dirs1(T, H, [H]); +dirs1([H|T], Last, Acc) -> + Dir = filename:join(Last, H), + dirs1(T, Dir, [Dir|Acc]). + +usort(List) -> + lists:ukeysort(1, lists:flatten(List)). + +get_nonempty(Files) -> + [{FName,FBin} || {FName,FBin} <- Files, FBin =/= <<>>]. + +all_deps(State) -> + [list_to_existing_atom(binary_to_list(rebar_app_info:name(App))) + || App <- rebar_state:all_deps(State)]. + +def(Rm, State, Key, Default) -> + Value0 = rebar_state:get(State, Key, Default), + case Rm of + "#!" -> "#!" ++ Value = Value0, rm_newline(Value); + "%%" -> "%%" ++ Value = Value0, rm_newline(Value); + "%%!" -> "%%!" ++ Value = Value0, rm_newline(Value) + end. + +rm_newline(String) -> + [C || C <- String, C =/= $\n]. diff --git a/test/rebar_escriptize_SUITE.erl b/test/rebar_escriptize_SUITE.erl new file mode 100644 index 0000000..1817d6b --- /dev/null +++ b/test/rebar_escriptize_SUITE.erl @@ -0,0 +1,37 @@ +-module(rebar_escriptize_SUITE). + +-export([suite/0, + init_per_suite/1, + end_per_suite/1, + init_per_testcase/2, + all/0, + build_and_clean_app/1]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("kernel/include/file.hrl"). + +suite() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(_, Config) -> + rebar_test_utils:init_rebar_state(Config). + +all() -> + [build_and_clean_app]. + +%% Test escriptize builds and runs the app's escript +build_and_clean_app(Config) -> + AppDir = ?config(apps, Config), + + Name = rebar_test_utils:create_random_name("app1_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]), + rebar_test_utils:run_and_check(Config, [], ["escriptize"], + {ok, [{app, Name, valid}]}). |