From 01efaa6764088ee0df0d1ec6e5f561707af5ebe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Sun, 30 Oct 2016 16:56:29 +0200 Subject: Greatly improve the escript support The plugin can now easily generate escripts as complex as relx or rebar/rebar3. It generates a proper structure and allows embedding extra files by extending the escript-zip target. Documentation and tests have been added. --- doc/src/guide/escripts.asciidoc | 72 ++++++++++++++++++++++++++++++++- plugins/escript.mk | 61 ++++++++++------------------ test/plugin_escript.mk | 89 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 42 deletions(-) create mode 100644 test/plugin_escript.mk diff --git a/doc/src/guide/escripts.asciidoc b/doc/src/guide/escripts.asciidoc index 3d68c77..eebd6d8 100644 --- a/doc/src/guide/escripts.asciidoc +++ b/doc/src/guide/escripts.asciidoc @@ -1,6 +1,74 @@ [[escript]] == Escripts -// @todo Write it. +Escripts are an alternative to release. They are meant to be +used for small command line executables written in Erlang. -Placeholder chapter. +They are not self-contained, unlike xref:relx[releases]. +Erlang must be installed for them to run. This however means +that they are fairly small compared to releases. + +For self-contained executables, check xref:sfx[self-extracting releases]. + +Erlang.mk uses `p7zip` by default to generate the escript +archive. Make sure it is installed. + +=== Generating an escript + +Run the following command to generate an escript: + +[source,bash] +$ make escript + +This will by default create an escript with the same name as +the project, in the project's directory. If the project is +called `relx` then the escript will be in `./relx`. + +You can run the escript as you would any executable: + +[source,bash] +$ ./relx + +=== Configuration + +You can change the name of the escript by setting `ESCRIPT_NAME`. +The name determines both the default output file name and the +entry module containing the function `main/1`. + +`ESCRIPT_FILE` can be set if you need a different file name +or location. + +The escript header can be entirely customized. The first line +is the shebang, set by `ESCRIPT_SHEBANG`. The second line is +a comment, set by `ESCRIPT_COMMENT`. The third line is the +arguments the VM will use when running the escript, set by +`ESCRIPT_EMU_ARGS`. + +Finally, `ESCRIPT_ZIP` can be set to customize the command used +to create the zip file. Read on for more information. + +=== Extra files + +Generating an escript is a two-part process. First, a zip file +is created with the contents of the escript. Then a header is +added to this file to create the escript. + +It is possible to add commands that will be executed between +the two steps. You can for example add extra files to the zip +archive: + +[source,make] +---- +escript-zip:: + $(verbose) $(ESCRIPT_ZIP) $(ESCRIPT_ZIP_FILE) priv/templates/* +---- + +The `ESCRIPT_ZIP` variable contains the command to run to add +files to the zip archive `ESCRIPT_ZIP_FILE`. + +=== Optimizing for size + +Erlang.mk will by default compile BEAM files with debug +information. You may want to disable this behavior to obtain +smaller escript files. Simply set `ERLC_OPTS` to a value that +does not include `+debug_info`. diff --git a/plugins/escript.mk b/plugins/escript.mk index 49fa989..6d70006 100644 --- a/plugins/escript.mk +++ b/plugins/escript.mk @@ -2,22 +2,19 @@ # Copyright (c) 2014, Dave Cottlehuber # This file is part of erlang.mk and subject to the terms of the ISC License. -.PHONY: distclean-escript escript +.PHONY: distclean-escript escript escript-zip # Configuration. ESCRIPT_NAME ?= $(PROJECT) ESCRIPT_FILE ?= $(ESCRIPT_NAME) +ESCRIPT_SHEBANG ?= /usr/bin/env escript ESCRIPT_COMMENT ?= This is an -*- erlang -*- file +ESCRIPT_EMU_ARGS ?= -escript main $(ESCRIPT_NAME) -ESCRIPT_BEAMS ?= "ebin/*", "deps/*/ebin/*" -ESCRIPT_SYS_CONFIG ?= "rel/sys.config" -ESCRIPT_EMU_ARGS ?= -pa . \ - -sasl errlog_type error \ - -escript main $(ESCRIPT_NAME) -ESCRIPT_SHEBANG ?= /usr/bin/env escript -ESCRIPT_STATIC ?= "deps/*/priv/**", "priv/**" +ESCRIPT_ZIP ?= 7z a -tzip -mx=9 -mtc=off $(if $(filter-out 0,$(V)),,> /dev/null) +ESCRIPT_ZIP_FILE ?= $(ERLANG_MK_TMP)/escript.zip # Core targets. @@ -30,38 +27,22 @@ help:: # Plugin-specific targets. -# Based on https://github.com/synrc/mad/blob/master/src/mad_bundle.erl -# Copyright (c) 2013 Maxim Sokhatsky, Synrc Research Center -# Modified MIT License, https://github.com/synrc/mad/blob/master/LICENSE : -# Software may only be used for the great good and the true happiness of all -# sentient beings. - -define ESCRIPT_RAW -'Read = fun(F) -> {ok, B} = file:read_file(filename:absname(F)), B end,'\ -'Files = fun(L) -> A = lists:concat([filelib:wildcard(X)||X<- L ]),'\ -' [F || F <- A, not filelib:is_dir(F) ] end,'\ -'Squash = fun(L) -> [{filename:basename(F), Read(F) } || F <- L ] end,'\ -'Zip = fun(A, L) -> {ok,{_,Z}} = zip:create(A, L, [{compress,all},memory]), Z end,'\ -'Ez = fun(Escript) ->'\ -' Static = Files([$(ESCRIPT_STATIC)]),'\ -' Beams = Squash(Files([$(ESCRIPT_BEAMS), $(ESCRIPT_SYS_CONFIG)])),'\ -' Archive = Beams ++ [{ "static.gz", Zip("static.gz", Static)}],'\ -' escript:create(Escript, [ $(ESCRIPT_OPTIONS)'\ -' {archive, Archive, [memory]},'\ -' {shebang, "$(ESCRIPT_SHEBANG)"},'\ -' {comment, "$(ESCRIPT_COMMENT)"},'\ -' {emu_args, " $(ESCRIPT_EMU_ARGS)"}'\ -' ]),'\ -' file:change_mode(Escript, 8#755)'\ -'end,'\ -'Ez("$(ESCRIPT_FILE)"),'\ -'halt().' -endef - -ESCRIPT_COMMAND = $(subst ' ',,$(ESCRIPT_RAW)) - -escript:: distclean-escript deps app - $(gen_verbose) $(ERL) -eval $(ESCRIPT_COMMAND) +escript-zip:: deps app + $(verbose) mkdir -p $(dir $(ESCRIPT_ZIP)) + $(verbose) rm -f $(ESCRIPT_ZIP_FILE) + $(gen_verbose) cd .. && $(ESCRIPT_ZIP) $(ESCRIPT_ZIP_FILE) $(PROJECT)/ebin/* +ifneq ($(DEPS),) + $(verbose) cd $(DEPS_DIR) && $(ESCRIPT_ZIP) $(ESCRIPT_ZIP_FILE) \ + `cat $(ERLANG_MK_TMP)/deps.log | sed 's/^$(subst /,\/,$(DEPS_DIR))\///' | sed 's/$$/\/ebin\/\*/'` +endif + +escript:: escript-zip + $(gen_verbose) printf "%s\n" \ + "#!$(ESCRIPT_SHEBANG)" \ + "%% $(ESCRIPT_COMMENT)" \ + "%%! $(ESCRIPT_EMU_ARGS)" > $(ESCRIPT_FILE) + $(verbose) cat $(ESCRIPT_ZIP_FILE) >> $(ESCRIPT_FILE) + $(verbose) chmod +x $(ESCRIPT_FILE) distclean-escript: $(gen_verbose) rm -f $(ESCRIPT_NAME) diff --git a/test/plugin_escript.mk b/test/plugin_escript.mk new file mode 100644 index 0000000..3916094 --- /dev/null +++ b/test/plugin_escript.mk @@ -0,0 +1,89 @@ +# Escript plugin. + +ESCRIPT_CASES = build deps extra +ESCRIPT_TARGETS = $(addprefix escript-,$(ESCRIPT_CASES)) + +.PHONY: escript $(ESCRIPT_TARGETS) + +escript: $(ESCRIPT_TARGETS) + +escript-build: build clean + + $i "Bootstrap a new OTP library named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap-lib $v + + $i "Generate a module containing a function main/1" + $t printf "%s\n" \ + "-module($(APP))." \ + "-export([main/1])." \ + 'main(_) -> io:format("good~n").' > $(APP)/src/$(APP).erl + + $i "Build the escript" + $t $(MAKE) -C $(APP) escript $v + + $i "Check that the escript exists" + $t test -f $(APP)/$(APP) + + $i "Check that the escript runs" + $t $(APP)/$(APP) | grep -q good + + $i "Distclean the application" + $t $(MAKE) -C $(APP) distclean $v + + $i "Check that the escript was removed" + $t test ! -e $(APP)/$(APP) + +escript-deps: build clean + + $i "Bootstrap a new OTP library named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap-lib $v + + $i "Add Ranch to the list of dependencies" + $t perl -ni.bak -e 'print;if ($$.==1) {print "DEPS = ranch\n"}' $(APP)/Makefile + + $i "Generate a module containing a function main/1" + $t printf "%s\n" \ + "-module($(APP))." \ + "-export([main/1])." \ + 'main(_) -> io:format("good~n").' > $(APP)/src/$(APP).erl + + $i "Build the escript" + $t $(MAKE) -C $(APP) escript $v + + $i "Check that the escript runs" + $t $(APP)/$(APP) | grep -q good + + $i "Check that the escript contains the dependency" + $t zipinfo $(APP)/$(APP) 2> /dev/null | grep -q ranch + +escript-extra: build clean + + $i "Bootstrap a new OTP library named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap-lib $v + + $i "Instruct Erlang.mk to add extra files to the escript" + $t printf "%s\n" \ + "escript-zip::" \ + ' $$(verbose) $$(ESCRIPT_ZIP) $$(ESCRIPT_ZIP_FILE) Makefile erlang.mk' >> $(APP)/Makefile + + $i "Generate a module containing a function main/1" + $t printf "%s\n" \ + "-module($(APP))." \ + "-export([main/1])." \ + 'main(_) -> io:format("good~n").' > $(APP)/src/$(APP).erl + + $i "Build the escript" + $t $(MAKE) -C $(APP) escript $v + + $i "Check that the escript runs" + $t $(APP)/$(APP) | grep -q good + + $i "Check that the escript contains the extra files" + $t zipinfo $(APP)/$(APP) 2> /dev/null | grep -q Makefile + $t zipinfo $(APP)/$(APP) 2> /dev/null | grep -q erlang.mk -- cgit v1.2.3