From a6f9a450af99a7a95848dde671de658bb53a43dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 21 Nov 2024 15:55:37 +0100 Subject: [PATCH] Move templates outside the source .mk files Templates now no longer use Make variables for substitution but instead replace strings with their equivalent: template_name: Corresponds to n=template_name project_name: Corresponds to $(PROJECT) or in=project_name This allows defining templates outside of Makefiles. For example an external plugin could define their templates in templates/my_template.erl and then have the following in the included Makefile: tpl_my_template = $(file < $(THIS)/templates/my_template.erl) By default the created file will be in src/template_name.erl. This can be overriden with the tplp_* variable: tplp_my_template = src/model/my_template.erl Substitution is applied both to the template contents and to its path. In addition, attempting to overwrite an existing file when creating a template will result in failure. --- .gitignore | 1 + CHANGELOG.asciidoc | 8 + Makefile | 11 +- build.config | 3 + doc/src/guide/external_plugins.asciidoc | 28 +- doc/src/guide/getting_started.asciidoc | 14 +- plugins/bootstrap.mk | 473 +++--------------------- templates/application.app.src | 13 + templates/application.erl | 11 + templates/apps_Makefile | 10 + templates/cowboy_http_h.erl | 19 + templates/cowboy_loop_h.erl | 18 + templates/cowboy_rest_h.erl | 14 + templates/cowboy_websocket_h.erl | 31 ++ templates/gen_fsm.erl | 50 +++ templates/gen_server.erl | 42 +++ templates/gen_statem.erl | 42 +++ templates/library.app.src | 11 + templates/module.erl | 2 + templates/ranch_protocol.erl | 25 ++ templates/relx.config | 6 + templates/supervisor.erl | 12 + templates/sys.config | 2 + templates/top_Makefile | 5 + templates/vm.args | 3 + test/core_plugins.mk | 39 +- test/core_upgrade.mk | 2 - test/plugin_bootstrap.mk | 18 +- 28 files changed, 488 insertions(+), 425 deletions(-) create mode 100644 templates/application.app.src create mode 100644 templates/application.erl create mode 100644 templates/apps_Makefile create mode 100644 templates/cowboy_http_h.erl create mode 100644 templates/cowboy_loop_h.erl create mode 100644 templates/cowboy_rest_h.erl create mode 100644 templates/cowboy_websocket_h.erl create mode 100644 templates/gen_fsm.erl create mode 100644 templates/gen_server.erl create mode 100644 templates/gen_statem.erl create mode 100644 templates/library.app.src create mode 100644 templates/module.erl create mode 100644 templates/ranch_protocol.erl create mode 100644 templates/relx.config create mode 100644 templates/supervisor.erl create mode 100644 templates/sys.config create mode 100644 templates/top_Makefile create mode 100644 templates/vm.args diff --git a/.gitignore b/.gitignore index 9f6d9605a..206aa1fe0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ doc/guide.pdf doc/html +templates.mk test/logs/ test/packages/ test/test_hex_core_git/ diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 83cdf6808..ea10ada61 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -47,3 +47,11 @@ without an explicit fetch method). Adding "git" at the beginning of the dep line is the modern equivalent. + +2024/11/22: Templates no longer use Make variables for + substitution. Instead the strings template_name + and project_name get replaced by their equivalent. + Note that using variables in your own templates + should still work, but that this way of doing + things is deprecated. This change makes it + possible to have templates outside of Makefiles. diff --git a/Makefile b/Makefile index f6d428799..b69bfb846 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ ERLANG_MK_VERSION = $(shell git describe --dirty --tags --always) .PHONY: all check -all: +all: templates.mk # Temporarily force the printing of the CHANGELOG. # The required variable hadn't been introduced yet. #ifdef UPGRADE @@ -40,6 +40,15 @@ all: | sed 's/^ERLANG_MK_VERSION =.*/ERLANG_MK_VERSION = $(ERLANG_MK_VERSION)/' \ | sed 's:^ERLANG_MK_WITHOUT =.*:ERLANG_MK_WITHOUT = $(WITHOUT):' > $(ERLANG_MK) +# Templates that end with .erl have the suffix removed. It is implied. +templates.mk: Makefile templates/* + for f in templates/*; do \ + echo define tpl_`basename $$f .erl`; \ + cat $$f; \ + echo endef; \ + echo; \ + done > $@ + lint: all $(MAKE) -f erlang.mk --warn-undefined-variables diff --git a/build.config b/build.config index 5ef60f89a..5f1c1932a 100644 --- a/build.config +++ b/build.config @@ -20,6 +20,9 @@ core/rel core/test core/compat +# Built-in templates. +templates + # Plugins. plugins/asciidoc plugins/bootstrap diff --git a/doc/src/guide/external_plugins.asciidoc b/doc/src/guide/external_plugins.asciidoc index 5f165f505..bb42f1a81 100644 --- a/doc/src/guide/external_plugins.asciidoc +++ b/doc/src/guide/external_plugins.asciidoc @@ -90,7 +90,7 @@ help-plugins:: Plugins declared in `DEP_PLUGINS` are loaded near the end of Erlang.mk. That's why you have access to all previously initialized variables. However, if you want your plugin to add common dependencies to -your applications, a regular is loaded too late in the process. +your applications, a regular plugin is loaded too late in the process. You need to use "Early-stage plugins". They are declared using the `DEP_EARLY_PLUGINS` variable instead. Plugins listed in this variable are loaded near the beginning of Erlang.mk Otherwise, they work exactly @@ -111,7 +111,7 @@ DEPS += cowboy TEST_DEPS = ct_helper dep_ct_helper = git https://github.com/ninenines/ct_helper master -=== Loading plugins local to the application +=== Loading plugins local to the application If the Erlang.mk plugin lives in the same directory or repository as your application or library, then you can load it exactly like an external @@ -138,3 +138,27 @@ your application: DEP_EARLY_PLUGINS = $(PROJECT) # Loads ./plugins.mk DEP_PLUGINS = $(PROJECT) + +=== Adding templates via plugins + +Plugins may add templates either from within their Makefile or from +an external file. The recommended method is to use an external file; +however do note it only works for Make 4 and above: + +[source,make] +THIS := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) +tpl_test_mk = $(file < $(THIS)/templates/my_template.erl) + +With 'templates/my_template.erl' containing: + +[source,erlang] +-module(template_name). + +Erlang.mk will do string substitution replacing the following +strings with their equivalent: + +* `template_name`: the name provided by the user +* `project_name`: either `$(PROJECT)` or the target application's name +* `template_sp`: internal; propagates whitespace settings to Makefiles +* `rel_deps_dir`: internal; path to deps/* from within an apps/* Makefile +* `rel_root_dir`: internal; path to top-level directory from within an apps/* Makefile diff --git a/doc/src/guide/getting_started.asciidoc b/doc/src/guide/getting_started.asciidoc index 5f782cafc..c1e75899b 100644 --- a/doc/src/guide/getting_started.asciidoc +++ b/doc/src/guide/getting_started.asciidoc @@ -271,8 +271,20 @@ You can list all available templates with the `list-templates` target: [source,bash] +---- $ make list-templates -Available templates: cowboy_http cowboy_loop cowboy_rest cowboy_ws gen_fsm gen_server gen_statem ranch_protocol supervisor +Available templates: + cowboy_http_h + cowboy_loop_h + cowboy_rest_h + cowboy_websocket_h + gen_fsm + gen_server + gen_statem + module + ranch_protocol + supervisor +---- To generate a module, let's say a `gen_server`, all you need to do is to call `make new` with the appropriate arguments: diff --git a/plugins/bootstrap.mk b/plugins/bootstrap.mk index 3ece18482..5c3610a03 100644 --- a/plugins/bootstrap.mk +++ b/plugins/bootstrap.mk @@ -17,387 +17,6 @@ help:: " new t=T n=N in=APP Generate a module NAME based on the template TPL in APP" \ " list-templates List available templates" -# Bootstrap templates. - -define bs_appsrc -{application, $p, [ - {description, ""}, - {vsn, "0.1.0"}, - {id, "git"}, - {modules, []}, - {registered, []}, - {applications, [ - kernel, - stdlib - ]}, - {mod, {$p_app, []}}, - {env, []} -]}. -endef - -define bs_appsrc_lib -{application, $p, [ - {description, ""}, - {vsn, "0.1.0"}, - {id, "git"}, - {modules, []}, - {registered, []}, - {applications, [ - kernel, - stdlib - ]} -]}. -endef - -# To prevent autocompletion issues with ZSH, we add "include erlang.mk" -# separately during the actual bootstrap. -define bs_Makefile -PROJECT = $p -PROJECT_DESCRIPTION = New project -PROJECT_VERSION = 0.1.0 -$(if $(SP), -# Whitespace to be used when creating files from templates. -SP = $(SP) -) -endef - -define bs_apps_Makefile -PROJECT = $p -PROJECT_DESCRIPTION = New project -PROJECT_VERSION = 0.1.0 -$(if $(SP), -# Whitespace to be used when creating files from templates. -SP = $(SP) -) -# Make sure we know where the applications are located. -ROOT_DIR ?= $(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(APPS_DIR)/app) -APPS_DIR ?= .. -DEPS_DIR ?= $(call core_relpath,$(DEPS_DIR),$(APPS_DIR)/app) - -include $$(ROOT_DIR)/erlang.mk -endef - -define bs_app --module($p_app). --behaviour(application). - --export([start/2]). --export([stop/1]). - -start(_Type, _Args) -> - $p_sup:start_link(). - -stop(_State) -> - ok. -endef - -define bs_relx_config -{release, {$p_release, "1"}, [$p, sasl, runtime_tools]}. -{dev_mode, false}. -{include_erts, true}. -{extended_start_script, true}. -{sys_config, "config/sys.config"}. -{vm_args, "config/vm.args"}. -endef - -define bs_sys_config -[ -]. -endef - -define bs_vm_args --name $p@127.0.0.1 --setcookie $p --heart -endef - -# Normal templates. - -define tpl_supervisor --module($(n)). --behaviour(supervisor). - --export([start_link/0]). --export([init/1]). - -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -init([]) -> - Procs = [], - {ok, {{one_for_one, 1, 5}, Procs}}. -endef - -define tpl_gen_server --module($(n)). --behaviour(gen_server). - -%% API. --export([start_link/0]). - -%% gen_server. --export([init/1]). --export([handle_call/3]). --export([handle_cast/2]). --export([handle_info/2]). --export([terminate/2]). --export([code_change/3]). - --record(state, { -}). - -%% API. - --spec start_link() -> {ok, pid()}. -start_link() -> - gen_server:start_link(?MODULE, [], []). - -%% gen_server. - -init([]) -> - {ok, #state{}}. - -handle_call(_Request, _From, State) -> - {reply, ignored, State}. - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. -endef - -define tpl_module --module($(n)). --export([]). -endef - -define tpl_cowboy_http --module($(n)). --behaviour(cowboy_http_handler). - --export([init/3]). --export([handle/2]). --export([terminate/3]). - --record(state, { -}). - -init(_, Req, _Opts) -> - {ok, Req, #state{}}. - -handle(Req, State=#state{}) -> - {ok, Req2} = cowboy_req:reply(200, Req), - {ok, Req2, State}. - -terminate(_Reason, _Req, _State) -> - ok. -endef - -define tpl_gen_fsm --module($(n)). --behaviour(gen_fsm). - -%% API. --export([start_link/0]). - -%% gen_fsm. --export([init/1]). --export([state_name/2]). --export([handle_event/3]). --export([state_name/3]). --export([handle_sync_event/4]). --export([handle_info/3]). --export([terminate/3]). --export([code_change/4]). - --record(state, { -}). - -%% API. - --spec start_link() -> {ok, pid()}. -start_link() -> - gen_fsm:start_link(?MODULE, [], []). - -%% gen_fsm. - -init([]) -> - {ok, state_name, #state{}}. - -state_name(_Event, StateData) -> - {next_state, state_name, StateData}. - -handle_event(_Event, StateName, StateData) -> - {next_state, StateName, StateData}. - -state_name(_Event, _From, StateData) -> - {reply, ignored, state_name, StateData}. - -handle_sync_event(_Event, _From, StateName, StateData) -> - {reply, ignored, StateName, StateData}. - -handle_info(_Info, StateName, StateData) -> - {next_state, StateName, StateData}. - -terminate(_Reason, _StateName, _StateData) -> - ok. - -code_change(_OldVsn, StateName, StateData, _Extra) -> - {ok, StateName, StateData}. -endef - -define tpl_gen_statem --module($(n)). --behaviour(gen_statem). - -%% API. --export([start_link/0]). - -%% gen_statem. --export([callback_mode/0]). --export([init/1]). --export([state_name/3]). --export([handle_event/4]). --export([terminate/3]). --export([code_change/4]). - --record(state, { -}). - -%% API. - --spec start_link() -> {ok, pid()}. -start_link() -> - gen_statem:start_link(?MODULE, [], []). - -%% gen_statem. - -callback_mode() -> - state_functions. - -init([]) -> - {ok, state_name, #state{}}. - -state_name(_EventType, _EventData, StateData) -> - {next_state, state_name, StateData}. - -handle_event(_EventType, _EventData, StateName, StateData) -> - {next_state, StateName, StateData}. - -terminate(_Reason, _StateName, _StateData) -> - ok. - -code_change(_OldVsn, StateName, StateData, _Extra) -> - {ok, StateName, StateData}. -endef - -define tpl_cowboy_loop --module($(n)). --behaviour(cowboy_loop_handler). - --export([init/3]). --export([info/3]). --export([terminate/3]). - --record(state, { -}). - -init(_, Req, _Opts) -> - {loop, Req, #state{}, 5000, hibernate}. - -info(_Info, Req, State) -> - {loop, Req, State, hibernate}. - -terminate(_Reason, _Req, _State) -> - ok. -endef - -define tpl_cowboy_rest --module($(n)). - --export([init/3]). --export([content_types_provided/2]). --export([get_html/2]). - -init(_, _Req, _Opts) -> - {upgrade, protocol, cowboy_rest}. - -content_types_provided(Req, State) -> - {[{{<<"text">>, <<"html">>, '*'}, get_html}], Req, State}. - -get_html(Req, State) -> - {<<"This is REST!">>, Req, State}. -endef - -define tpl_cowboy_ws --module($(n)). --behaviour(cowboy_websocket_handler). - --export([init/3]). --export([websocket_init/3]). --export([websocket_handle/3]). --export([websocket_info/3]). --export([websocket_terminate/3]). - --record(state, { -}). - -init(_, _, _) -> - {upgrade, protocol, cowboy_websocket}. - -websocket_init(_, Req, _Opts) -> - Req2 = cowboy_req:compact(Req), - {ok, Req2, #state{}}. - -websocket_handle({text, Data}, Req, State) -> - {reply, {text, Data}, Req, State}; -websocket_handle({binary, Data}, Req, State) -> - {reply, {binary, Data}, Req, State}; -websocket_handle(_Frame, Req, State) -> - {ok, Req, State}. - -websocket_info(_Info, Req, State) -> - {ok, Req, State}. - -websocket_terminate(_Reason, _Req, _State) -> - ok. -endef - -define tpl_ranch_protocol --module($(n)). --behaviour(ranch_protocol). - --export([start_link/4]). --export([init/4]). - --type opts() :: []. --export_type([opts/0]). - --record(state, { - socket :: inet:socket(), - transport :: module() -}). - -start_link(Ref, Socket, Transport, Opts) -> - Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), - {ok, Pid}. - --spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok. -init(Ref, Socket, Transport, _Opts) -> - ok = ranch:accept_ack(Ref), - loop(#state{socket=Socket, transport=Transport}). - -loop(State) -> - loop(State). -endef - # Plugin-specific targets. ifndef WS @@ -408,6 +27,26 @@ WS = $(tab) endif endif +ifdef SP +define template_sp + +# By default templates indent with a single tab per indentation +# level. Set this variable to the number of spaces you prefer: +SP = $(SP) + +endef +else +template_sp = +endif + +# @todo Additional template placeholders could be added. +subst_template = $(subst rel_root_dir,$(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(APPS_DIR)/app),$(subst rel_deps_dir,$(call core_relpath,$(DEPS_DIR),$(APPS_DIR)/app),$(subst template_sp,$(template_sp),$(subst project_name,$p,$(subst template_name,$n,$1))))) + +define core_render_template + $(eval define _tpl_$(1)$(newline)$(call subst_template,$(tpl_$(1)))$(newline)endef) + $(verbose) $(call core_render,_tpl_$(1),$2) +endef + bootstrap: ifneq ($(wildcard src/),) $(error Error: src/ directory already exists) @@ -416,14 +55,13 @@ endif $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) $(eval n := $(PROJECT)_sup) - $(verbose) $(call core_render,bs_Makefile,Makefile) - $(verbose) echo "include erlang.mk" >> Makefile + $(verbose) $(call core_render_template,top_Makefile,Makefile) $(verbose) mkdir src/ ifdef LEGACY - $(verbose) $(call core_render,bs_appsrc,src/$(PROJECT).app.src) + $(verbose) $(call core_render_template,application.app.src,src/$(PROJECT).app.src) endif - $(verbose) $(call core_render,bs_app,src/$(PROJECT)_app.erl) - $(verbose) $(call core_render,tpl_supervisor,src/$(PROJECT)_sup.erl) + $(verbose) $(call core_render_template,application,src/$(PROJECT)_app.erl) + $(verbose) $(call core_render_template,supervisor,src/$(PROJECT)_sup.erl) bootstrap-lib: ifneq ($(wildcard src/),) @@ -432,11 +70,10 @@ endif $(eval p := $(PROJECT)) $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) - $(verbose) $(call core_render,bs_Makefile,Makefile) - $(verbose) echo "include erlang.mk" >> Makefile + $(verbose) $(call core_render_template,top_Makefile,Makefile) $(verbose) mkdir src/ ifdef LEGACY - $(verbose) $(call core_render,bs_appsrc_lib,src/$(PROJECT).app.src) + $(verbose) $(call core_render_template,library.app.src,src/$(PROJECT).app.src) endif bootstrap-rel: @@ -447,10 +84,10 @@ ifneq ($(wildcard config/),) $(error Error: config/ directory already exists) endif $(eval p := $(PROJECT)) - $(verbose) $(call core_render,bs_relx_config,relx.config) + $(verbose) $(call core_render_template,relx.config,relx.config) $(verbose) mkdir config/ - $(verbose) $(call core_render,bs_sys_config,config/sys.config) - $(verbose) $(call core_render,bs_vm_args,config/vm.args) + $(verbose) $(call core_render_template,sys.config,config/sys.config) + $(verbose) $(call core_render_template,vm.args,config/vm.args) $(verbose) awk '/^include erlang.mk/ && !ins {print "REL_DEPS += relx";ins=1};{print}' Makefile > Makefile.bak $(verbose) mv Makefile.bak Makefile @@ -466,12 +103,12 @@ endif $(error Error: Invalid characters in the application name)) $(eval n := $(in)_sup) $(verbose) mkdir -p $(APPS_DIR)/$p/src/ - $(verbose) $(call core_render,bs_apps_Makefile,$(APPS_DIR)/$p/Makefile) + $(verbose) $(call core_render_template,apps_Makefile,$(APPS_DIR)/$p/Makefile) ifdef LEGACY - $(verbose) $(call core_render,bs_appsrc,$(APPS_DIR)/$p/src/$p.app.src) + $(verbose) $(call core_render_template,application.app.src,$(APPS_DIR)/$p/src/$p.app.src) endif - $(verbose) $(call core_render,bs_app,$(APPS_DIR)/$p/src/$p_app.erl) - $(verbose) $(call core_render,tpl_supervisor,$(APPS_DIR)/$p/src/$p_sup.erl) + $(verbose) $(call core_render_template,application,$(APPS_DIR)/$p/src/$p_app.erl) + $(verbose) $(call core_render_template,supervisor,$(APPS_DIR)/$p/src/$p_sup.erl) new-lib: ifndef in @@ -484,27 +121,37 @@ endif $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) $(verbose) mkdir -p $(APPS_DIR)/$p/src/ - $(verbose) $(call core_render,bs_apps_Makefile,$(APPS_DIR)/$p/Makefile) + $(verbose) $(call core_render_template,apps_Makefile,$(APPS_DIR)/$p/Makefile) ifdef LEGACY - $(verbose) $(call core_render,bs_appsrc_lib,$(APPS_DIR)/$p/src/$p.app.src) + $(verbose) $(call core_render_template,library.app.src,$(APPS_DIR)/$p/src/$p.app.src) endif +# These are not necessary because we don't expose those as "normal" templates. +BOOTSTRAP_TEMPLATES = apps_Makefile top_Makefile \ + application.app.src library.app.src application \ + relx.config sys.config vm.args + +# Templates may override the path they will be written to when using 'new'. +# Only special template paths must be listed. Default is src/template_name.erl +# Substitution is also applied to the paths. Examples: +# +#tplp_top_Makefile = Makefile +#tplp_application.app.src = src/project_name.app.src +#tplp_application = src/project_name_app.erl +#tplp_relx.config = relx.config + +# Erlang.mk bundles its own templates at build time into the erlang.mk file. + new: -ifeq ($(wildcard src/)$(in),) - $(error Error: src/ directory does not exist) -endif -ifndef t - $(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP]) -endif -ifndef n - $(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP]) -endif -ifdef in - $(verbose) $(call core_render,tpl_$(t),$(APPS_DIR)/$(in)/src/$(n).erl) -else - $(verbose) $(call core_render,tpl_$(t),src/$(n).erl) -endif + $(if $(t),,$(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP])) + $(if $(n),,$(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP])) + $(if $(tpl_$(t)),,$(error Error: $t template does not exist; try $(Make) list-templates)) + $(eval dest := $(if $(in),$(APPS_DIR)/$(in)/)$(call subst_template,$(if $(tplp_$(t)),$(tplp_$(t)),src/template_name.erl))) + $(if $(wildcard $(dir $(dest))),,$(error Error: $(dir $(dest)) directory does not exist)) + $(if $(wildcard $(dest)),$(error Error: The file $(dest) already exists)) + $(eval p := $(PROJECT)) + $(call core_render_template,$(t),$(dest)) list-templates: $(verbose) @echo Available templates: - $(verbose) printf " %s\n" $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES)))) + $(verbose) printf " %s\n" $(sort $(filter-out $(BOOTSTRAP_TEMPLATES),$(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES))))) diff --git a/templates/application.app.src b/templates/application.app.src new file mode 100644 index 000000000..3bd1d6bbe --- /dev/null +++ b/templates/application.app.src @@ -0,0 +1,13 @@ +{application, project_name, [ + {description, ""}, + {vsn, "0.1.0"}, + {id, "git"}, + {modules, []}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {mod, {project_name_app, []}}, + {env, []} +]}. diff --git a/templates/application.erl b/templates/application.erl new file mode 100644 index 000000000..2c8d27b34 --- /dev/null +++ b/templates/application.erl @@ -0,0 +1,11 @@ +-module(project_name_app). +-behaviour(application). + +-export([start/2]). +-export([stop/1]). + +start(_Type, _Args) -> + project_name_sup:start_link(). + +stop(_State) -> + ok. diff --git a/templates/apps_Makefile b/templates/apps_Makefile new file mode 100644 index 000000000..093287fc9 --- /dev/null +++ b/templates/apps_Makefile @@ -0,0 +1,10 @@ +PROJECT = project_name +PROJECT_DESCRIPTION = New project +PROJECT_VERSION = 0.1.0 +template_sp +# Make sure we know where the applications are located. +ROOT_DIR ?= rel_root_dir +APPS_DIR ?= .. +DEPS_DIR ?= rel_deps_dir + +include rel_root_dir/erlang.mk diff --git a/templates/cowboy_http_h.erl b/templates/cowboy_http_h.erl new file mode 100644 index 000000000..a2c3200f8 --- /dev/null +++ b/templates/cowboy_http_h.erl @@ -0,0 +1,19 @@ +-module(template_name). +-behaviour(cowboy_http_handler). + +-export([init/3]). +-export([handle/2]). +-export([terminate/3]). + +-record(state, { +}). + +init(_, Req, _Opts) -> + {ok, Req, #state{}}. + +handle(Req, State=#state{}) -> + {ok, Req2} = cowboy_req:reply(200, Req), + {ok, Req2, State}. + +terminate(_Reason, _Req, _State) -> + ok. diff --git a/templates/cowboy_loop_h.erl b/templates/cowboy_loop_h.erl new file mode 100644 index 000000000..ce301c98b --- /dev/null +++ b/templates/cowboy_loop_h.erl @@ -0,0 +1,18 @@ +-module(template_name). +-behaviour(cowboy_loop_handler). + +-export([init/3]). +-export([info/3]). +-export([terminate/3]). + +-record(state, { +}). + +init(_, Req, _Opts) -> + {loop, Req, #state{}, 5000, hibernate}. + +info(_Info, Req, State) -> + {loop, Req, State, hibernate}. + +terminate(_Reason, _Req, _State) -> + ok. diff --git a/templates/cowboy_rest_h.erl b/templates/cowboy_rest_h.erl new file mode 100644 index 000000000..a15f4a505 --- /dev/null +++ b/templates/cowboy_rest_h.erl @@ -0,0 +1,14 @@ +-module(template_name). + +-export([init/3]). +-export([content_types_provided/2]). +-export([get_html/2]). + +init(_, _Req, _Opts) -> + {upgrade, protocol, cowboy_rest}. + +content_types_provided(Req, State) -> + {[{{<<"text">>, <<"html">>, '*'}, get_html}], Req, State}. + +get_html(Req, State) -> + {<<"This is REST!">>, Req, State}. diff --git a/templates/cowboy_websocket_h.erl b/templates/cowboy_websocket_h.erl new file mode 100644 index 000000000..538f020c3 --- /dev/null +++ b/templates/cowboy_websocket_h.erl @@ -0,0 +1,31 @@ +-module(template_name). +-behaviour(cowboy_websocket_handler). + +-export([init/3]). +-export([websocket_init/3]). +-export([websocket_handle/3]). +-export([websocket_info/3]). +-export([websocket_terminate/3]). + +-record(state, { +}). + +init(_, _, _) -> + {upgrade, protocol, cowboy_websocket}. + +websocket_init(_, Req, _Opts) -> + Req2 = cowboy_req:compact(Req), + {ok, Req2, #state{}}. + +websocket_handle({text, Data}, Req, State) -> + {reply, {text, Data}, Req, State}; +websocket_handle({binary, Data}, Req, State) -> + {reply, {binary, Data}, Req, State}; +websocket_handle(_Frame, Req, State) -> + {ok, Req, State}. + +websocket_info(_Info, Req, State) -> + {ok, Req, State}. + +websocket_terminate(_Reason, _Req, _State) -> + ok. diff --git a/templates/gen_fsm.erl b/templates/gen_fsm.erl new file mode 100644 index 000000000..73505df62 --- /dev/null +++ b/templates/gen_fsm.erl @@ -0,0 +1,50 @@ +-module(template_name). +-behaviour(gen_fsm). + +%% API. +-export([start_link/0]). + +%% gen_fsm. +-export([init/1]). +-export([state_name/2]). +-export([handle_event/3]). +-export([state_name/3]). +-export([handle_sync_event/4]). +-export([handle_info/3]). +-export([terminate/3]). +-export([code_change/4]). + +-record(state, { +}). + +%% API. + +-spec start_link() -> {ok, pid()}. +start_link() -> + gen_fsm:start_link(?MODULE, [], []). + +%% gen_fsm. + +init([]) -> + {ok, state_name, #state{}}. + +state_name(_Event, StateData) -> + {next_state, state_name, StateData}. + +handle_event(_Event, StateName, StateData) -> + {next_state, StateName, StateData}. + +state_name(_Event, _From, StateData) -> + {reply, ignored, state_name, StateData}. + +handle_sync_event(_Event, _From, StateName, StateData) -> + {reply, ignored, StateName, StateData}. + +handle_info(_Info, StateName, StateData) -> + {next_state, StateName, StateData}. + +terminate(_Reason, _StateName, _StateData) -> + ok. + +code_change(_OldVsn, StateName, StateData, _Extra) -> + {ok, StateName, StateData}. diff --git a/templates/gen_server.erl b/templates/gen_server.erl new file mode 100644 index 000000000..d297edfcc --- /dev/null +++ b/templates/gen_server.erl @@ -0,0 +1,42 @@ +-module(template_name). +-behaviour(gen_server). + +%% API. +-export([start_link/0]). + +%% gen_server. +-export([init/1]). +-export([handle_call/3]). +-export([handle_cast/2]). +-export([handle_info/2]). +-export([terminate/2]). +-export([code_change/3]). + +-record(state, { +}). + +%% API. + +-spec start_link() -> {ok, pid()}. +start_link() -> + gen_server:start_link(?MODULE, [], []). + +%% gen_server. + +init([]) -> + {ok, #state{}}. + +handle_call(_Request, _From, State) -> + {reply, ignored, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/templates/gen_statem.erl b/templates/gen_statem.erl new file mode 100644 index 000000000..afdf20f0c --- /dev/null +++ b/templates/gen_statem.erl @@ -0,0 +1,42 @@ +-module(template_name). +-behaviour(gen_statem). + +%% API. +-export([start_link/0]). + +%% gen_statem. +-export([callback_mode/0]). +-export([init/1]). +-export([state_name/3]). +-export([handle_event/4]). +-export([terminate/3]). +-export([code_change/4]). + +-record(state, { +}). + +%% API. + +-spec start_link() -> {ok, pid()}. +start_link() -> + gen_statem:start_link(?MODULE, [], []). + +%% gen_statem. + +callback_mode() -> + state_functions. + +init([]) -> + {ok, state_name, #state{}}. + +state_name(_EventType, _EventData, StateData) -> + {next_state, state_name, StateData}. + +handle_event(_EventType, _EventData, StateName, StateData) -> + {next_state, StateName, StateData}. + +terminate(_Reason, _StateName, _StateData) -> + ok. + +code_change(_OldVsn, StateName, StateData, _Extra) -> + {ok, StateName, StateData}. diff --git a/templates/library.app.src b/templates/library.app.src new file mode 100644 index 000000000..e7f7d751e --- /dev/null +++ b/templates/library.app.src @@ -0,0 +1,11 @@ +{application, project_name, [ + {description, ""}, + {vsn, "0.1.0"}, + {id, "git"}, + {modules, []}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]} +]}. diff --git a/templates/module.erl b/templates/module.erl new file mode 100644 index 000000000..30a3b1a7e --- /dev/null +++ b/templates/module.erl @@ -0,0 +1,2 @@ +-module(template_name). +-export([]). diff --git a/templates/ranch_protocol.erl b/templates/ranch_protocol.erl new file mode 100644 index 000000000..1458815b5 --- /dev/null +++ b/templates/ranch_protocol.erl @@ -0,0 +1,25 @@ +-module(template_name). +-behaviour(ranch_protocol). + +-export([start_link/4]). +-export([init/4]). + +-type opts() :: []. +-export_type([opts/0]). + +-record(state, { + socket :: inet:socket(), + transport :: module() +}). + +start_link(Ref, Socket, Transport, Opts) -> + Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), + {ok, Pid}. + +-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok. +init(Ref, Socket, Transport, _Opts) -> + ok = ranch:accept_ack(Ref), + loop(#state{socket=Socket, transport=Transport}). + +loop(State) -> + loop(State). diff --git a/templates/relx.config b/templates/relx.config new file mode 100644 index 000000000..3a23af747 --- /dev/null +++ b/templates/relx.config @@ -0,0 +1,6 @@ +{release, {project_name_release, "1"}, [project_name, sasl, runtime_tools]}. +{dev_mode, false}. +{include_erts, true}. +{extended_start_script, true}. +{sys_config, "config/sys.config"}. +{vm_args, "config/vm.args"}. diff --git a/templates/supervisor.erl b/templates/supervisor.erl new file mode 100644 index 000000000..b4ddf9101 --- /dev/null +++ b/templates/supervisor.erl @@ -0,0 +1,12 @@ +-module(template_name). +-behaviour(supervisor). + +-export([start_link/0]). +-export([init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + Procs = [], + {ok, {{one_for_one, 1, 5}, Procs}}. diff --git a/templates/sys.config b/templates/sys.config new file mode 100644 index 000000000..0dbd0fd1f --- /dev/null +++ b/templates/sys.config @@ -0,0 +1,2 @@ +[ +]. diff --git a/templates/top_Makefile b/templates/top_Makefile new file mode 100644 index 000000000..137993f8e --- /dev/null +++ b/templates/top_Makefile @@ -0,0 +1,5 @@ +PROJECT = project_name +PROJECT_DESCRIPTION = New project +PROJECT_VERSION = 0.1.0 +template_sp +include erlang.mk diff --git a/templates/vm.args b/templates/vm.args new file mode 100644 index 000000000..99a507604 --- /dev/null +++ b/templates/vm.args @@ -0,0 +1,3 @@ +-name project_name@127.0.0.1 +-setcookie project_name +-heart diff --git a/test/core_plugins.mk b/test/core_plugins.mk index dcffb9a57..854494580 100644 --- a/test/core_plugins.mk +++ b/test/core_plugins.mk @@ -194,7 +194,7 @@ core-plugins-templates: init $t mkdir -p $(APP)/plugin_dep $t printf "%s\n" \ "define tpl_test_mk" \ - "-module(test_mk)." \ + "-module(template_name)." \ "endef" > $(APP)/plugin_dep/plugins.mk $t cd $(APP)/plugin_dep && \ git init -q && \ @@ -263,6 +263,43 @@ core-plugins-templates-apps-only: init $i "Check that the file was compiled correctly" $t test -f $(APP)/apps/my_app/ebin/test_mk.beam +core-plugins-templates-file: init + + $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 "Create a local git repository with a plugin containing a template file" + $t mkdir -p $(APP)/plugin_dep/templates + $t printf "%s\n" "-module(template_name)." > $(APP)/plugin_dep/templates/test_mk.erl + $t echo "THIS := \$$(dir \$$(realpath \$$(lastword \$$(MAKEFILE_LIST))))" > $(APP)/plugin_dep/plugins.mk + $t printf "%s\n" "tpl_test_mk = \$$(file < \$$(THIS)/templates/test_mk.erl)" >> $(APP)/plugin_dep/plugins.mk + $t cd $(APP)/plugin_dep && \ + git init -q && \ + git config user.email "testsuite@erlang.mk" && \ + git config user.name "test suite" && \ + git add . && \ + git commit -q --no-gpg-sign -m "Tests" + + $i "Add dependency and plugins to the Makefile" + $t perl -ni.bak -e 'print;if ($$.==1) {print "DEPS = plugin_dep\ndep_plugin_dep = git file://$(abspath $(APP)/plugin_dep) master\nDEP_PLUGINS = plugin_dep\n"}' $(APP)/Makefile + + $i "Run 'make list-templates' and check that it prints test_mk" + $t $(MAKE) --no-print-directory -C $(APP) list-templates | grep -qw test_mk + + $i "Create a new file using the template" + $t $(MAKE) --no-print-directory -C $(APP) new t=test_mk n=test_mk $v + + $i "Confirm the file exists" + $t test -f $(APP)/src/test_mk.erl + + $i "Build the application" + $t $(MAKE) -C $(APP) $v + + $i "Check that the file was compiled correctly" + $t test -f $(APP)/ebin/test_mk.beam + core-plugins-test: init $i "Bootstrap a new OTP library named $(APP)" diff --git a/test/core_upgrade.mk b/test/core_upgrade.mk index f165098f2..c9fea3d76 100644 --- a/test/core_upgrade.mk +++ b/test/core_upgrade.mk @@ -16,8 +16,6 @@ core-upgrade-changelog: init $i "Fork erlang.mk locally and set a test CHANGELOG.asciidoc" $t git clone -q https://github.com/ninenines/erlang.mk $(APP)/alt-erlangmk-repo $t echo "$(APP)$(APP)" > $(APP)/alt-erlangmk-repo/CHANGELOG.asciidoc -# Since part of this functionality needs the main Makefile, copy it. - $t cp ../Makefile $(APP)/alt-erlangmk-repo/ $t (cd $(APP)/alt-erlangmk-repo && \ git config user.email "testsuite@erlang.mk" && \ git config user.name "test suite" && \ diff --git a/test/plugin_bootstrap.mk b/test/plugin_bootstrap.mk index ac10ca0ef..aa873a859 100644 --- a/test/plugin_bootstrap.mk +++ b/test/plugin_bootstrap.mk @@ -303,13 +303,16 @@ bootstrap-templates: init $t $(MAKE) -C $(APP) --no-print-directory new t=gen_statem n=my_statem $t $(MAKE) -C $(APP) --no-print-directory new t=gen_server n=my_server $t $(MAKE) -C $(APP) --no-print-directory new t=supervisor n=my_sup - $t $(MAKE) -C $(APP) --no-print-directory new t=cowboy_http n=my_http - $t $(MAKE) -C $(APP) --no-print-directory new t=cowboy_loop n=my_loop - $t $(MAKE) -C $(APP) --no-print-directory new t=cowboy_rest n=my_rest - $t $(MAKE) -C $(APP) --no-print-directory new t=cowboy_ws n=my_ws + $t $(MAKE) -C $(APP) --no-print-directory new t=cowboy_http_h n=my_http_h + $t $(MAKE) -C $(APP) --no-print-directory new t=cowboy_loop_h n=my_loop_h + $t $(MAKE) -C $(APP) --no-print-directory new t=cowboy_rest_h n=my_rest_h + $t $(MAKE) -C $(APP) --no-print-directory new t=cowboy_websocket_h n=my_ws_h $t $(MAKE) -C $(APP) --no-print-directory new t=ranch_protocol n=my_protocol $t $(MAKE) -C $(APP) --no-print-directory new t=module n=my_module + $i "Confirm we can't overwrite existing files" + $t ! $(MAKE) -C $(APP) --no-print-directory new t=gen_server n=my_server $v + # Here we disable warnings because templates contain missing behaviors. $i "Build the application" $t $(MAKE) -C $(APP) ERLC_OPTS=+debug_info $v @@ -320,12 +323,17 @@ bootstrap-templates: init $t test -f $(APP)/ebin/my_statem.beam $t test -f $(APP)/ebin/my_server.beam $t test -f $(APP)/ebin/my_sup.beam + $t test -f $(APP)/ebin/my_http_h.beam + $t test -f $(APP)/ebin/my_loop_h.beam + $t test -f $(APP)/ebin/my_rest_h.beam + $t test -f $(APP)/ebin/my_ws_h.beam + $t test -f $(APP)/ebin/my_protocol.beam $t test -f $(APP)/ebin/my_module.beam $i "Check that all the modules can be loaded" $t $(ERL) -pa $(APP)/ebin/ -eval " \ ok = application:start($(APP)), \ - {ok, Mods = [my_fsm, my_http, my_loop, my_module, my_protocol, my_rest, my_server, my_statem, my_sup, my_ws]} \ + {ok, Mods = [my_fsm, my_http_h, my_loop_h, my_module, my_protocol, my_rest_h, my_server, my_statem, my_sup, my_ws_h]} \ = application:get_key($(APP), modules), \ [{module, M} = code:load_file(M) || M <- Mods], \ halt()"