erlang.mk 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934
  1. # Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
  2. #
  3. # Permission to use, copy, modify, and/or distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. #
  7. # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. .PHONY: all app apps deps search rel relup docs install-docs check tests clean distclean help erlang-mk
  15. ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST)))
  16. export ERLANG_MK_FILENAME
  17. ERLANG_MK_VERSION = 2017.08.28-5-g55699ee-dirty
  18. ERLANG_MK_WITHOUT =
  19. # Make 3.81 and 3.82 are deprecated.
  20. ifeq ($(MAKE_VERSION),3.81)
  21. $(warning Please upgrade to GNU Make 4 or later: https://erlang.mk/guide/installation.html)
  22. endif
  23. ifeq ($(MAKE_VERSION),3.82)
  24. $(warning Please upgrade to GNU Make 4 or later: https://erlang.mk/guide/installation.html)
  25. endif
  26. # Core configuration.
  27. PROJECT ?= $(notdir $(CURDIR))
  28. PROJECT := $(strip $(PROJECT))
  29. PROJECT_VERSION ?= rolling
  30. PROJECT_MOD ?= $(PROJECT)_app
  31. PROJECT_ENV ?= []
  32. # Verbosity.
  33. V ?= 0
  34. verbose_0 = @
  35. verbose_2 = set -x;
  36. verbose = $(verbose_$(V))
  37. gen_verbose_0 = @echo " GEN " $@;
  38. gen_verbose_2 = set -x;
  39. gen_verbose = $(gen_verbose_$(V))
  40. # Temporary files directory.
  41. ERLANG_MK_TMP ?= $(CURDIR)/.erlang.mk
  42. export ERLANG_MK_TMP
  43. # "erl" command.
  44. ERL = erl +A0 -noinput -boot start_clean
  45. # Platform detection.
  46. ifeq ($(PLATFORM),)
  47. UNAME_S := $(shell uname -s)
  48. ifeq ($(UNAME_S),Linux)
  49. PLATFORM = linux
  50. else ifeq ($(UNAME_S),Darwin)
  51. PLATFORM = darwin
  52. else ifeq ($(UNAME_S),SunOS)
  53. PLATFORM = solaris
  54. else ifeq ($(UNAME_S),GNU)
  55. PLATFORM = gnu
  56. else ifeq ($(UNAME_S),FreeBSD)
  57. PLATFORM = freebsd
  58. else ifeq ($(UNAME_S),NetBSD)
  59. PLATFORM = netbsd
  60. else ifeq ($(UNAME_S),OpenBSD)
  61. PLATFORM = openbsd
  62. else ifeq ($(UNAME_S),DragonFly)
  63. PLATFORM = dragonfly
  64. else ifeq ($(shell uname -o),Msys)
  65. PLATFORM = msys2
  66. else
  67. $(error Unable to detect platform. Please open a ticket with the output of uname -a.)
  68. endif
  69. export PLATFORM
  70. endif
  71. # Core targets.
  72. all:: deps app rel
  73. # Noop to avoid a Make warning when there's nothing to do.
  74. rel::
  75. $(verbose) :
  76. relup:: deps app
  77. check:: tests
  78. clean:: clean-crashdump
  79. clean-crashdump:
  80. ifneq ($(wildcard erl_crash.dump),)
  81. $(gen_verbose) rm -f erl_crash.dump
  82. endif
  83. distclean:: clean distclean-tmp
  84. distclean-tmp:
  85. $(gen_verbose) rm -rf $(ERLANG_MK_TMP)
  86. help::
  87. $(verbose) printf "%s\n" \
  88. "erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \
  89. "Copyright (c) 2013-2016 Loïc Hoguin <essen@ninenines.eu>" \
  90. "" \
  91. "Usage: [V=1] $(MAKE) [target]..." \
  92. "" \
  93. "Core targets:" \
  94. " all Run deps, app and rel targets in that order" \
  95. " app Compile the project" \
  96. " deps Fetch dependencies (if needed) and compile them" \
  97. " fetch-deps Fetch dependencies recursively (if needed) without compiling them" \
  98. " list-deps List dependencies recursively on stdout" \
  99. " search q=... Search for a package in the built-in index" \
  100. " rel Build a release for this project, if applicable" \
  101. " docs Build the documentation for this project" \
  102. " install-docs Install the man pages for this project" \
  103. " check Compile and run all tests and analysis for this project" \
  104. " tests Run the tests for this project" \
  105. " clean Delete temporary and output files from most targets" \
  106. " distclean Delete all temporary and output files" \
  107. " help Display this help and exit" \
  108. " erlang-mk Update erlang.mk to the latest version"
  109. # Core functions.
  110. empty :=
  111. space := $(empty) $(empty)
  112. tab := $(empty) $(empty)
  113. comma := ,
  114. define newline
  115. endef
  116. define comma_list
  117. $(subst $(space),$(comma),$(strip $(1)))
  118. endef
  119. # Adding erlang.mk to make Erlang scripts who call init:get_plain_arguments() happy.
  120. define erlang
  121. $(ERL) $(2) -pz $(ERLANG_MK_TMP)/rebar/ebin -eval "$(subst $(newline),,$(subst ",\",$(1)))" -- erlang.mk
  122. endef
  123. ifeq ($(PLATFORM),msys2)
  124. core_native_path = $(subst \,\\\\,$(shell cygpath -w $1))
  125. else
  126. core_native_path = $1
  127. endif
  128. core_http_get = curl -Lf$(if $(filter-out 0,$(V)),,s)o $(call core_native_path,$1) $2
  129. core_eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1)))
  130. core_find = $(if $(wildcard $1),$(shell find $(1:%/=%) -type f -name $(subst *,\*,$2)))
  131. core_lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(1)))))))))))))))))))))))))))
  132. core_ls = $(filter-out $(1),$(shell echo $(1)))
  133. # @todo Use a solution that does not require using perl.
  134. core_relpath = $(shell perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' $1 $2)
  135. # Automated update.
  136. ERLANG_MK_REPO ?= https://github.com/ninenines/erlang.mk
  137. ERLANG_MK_COMMIT ?=
  138. ERLANG_MK_BUILD_CONFIG ?= build.config
  139. ERLANG_MK_BUILD_DIR ?= .erlang.mk.build
  140. erlang-mk: WITHOUT ?= $(ERLANG_MK_WITHOUT)
  141. erlang-mk:
  142. git clone $(ERLANG_MK_REPO) $(ERLANG_MK_BUILD_DIR)
  143. ifdef ERLANG_MK_COMMIT
  144. cd $(ERLANG_MK_BUILD_DIR) && git checkout $(ERLANG_MK_COMMIT)
  145. endif
  146. if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR)/build.config; fi
  147. $(MAKE) -C $(ERLANG_MK_BUILD_DIR) WITHOUT='$(strip $(WITHOUT))'
  148. cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk
  149. rm -rf $(ERLANG_MK_BUILD_DIR)
  150. # The erlang.mk package index is bundled in the default erlang.mk build.
  151. # Search for the string "copyright" to skip to the rest of the code.
  152. # Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
  153. # This file is part of erlang.mk and subject to the terms of the ISC License.
  154. # Verbosity.
  155. proto_verbose_0 = @echo " PROTO " $(filter %.proto,$(?F));
  156. proto_verbose = $(proto_verbose_$(V))
  157. # Core targets.
  158. define compile_proto
  159. $(verbose) mkdir -p ebin/ include/
  160. $(proto_verbose) $(call erlang,$(call compile_proto.erl,$(1)))
  161. $(proto_verbose) erlc +debug_info -o ebin/ ebin/*.erl
  162. $(verbose) rm ebin/*.erl
  163. endef
  164. define compile_proto.erl
  165. [begin
  166. protobuffs_compile:generate_source(F,
  167. [{output_include_dir, "./include"},
  168. {output_src_dir, "./ebin"}])
  169. end || F <- string:tokens("$(1)", " ")],
  170. halt().
  171. endef
  172. ifneq ($(wildcard src/),)
  173. ebin/$(PROJECT).app:: $(sort $(call core_find,src/,*.proto))
  174. $(if $(strip $?),$(call compile_proto,$?))
  175. endif
  176. # Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
  177. # This file is part of erlang.mk and subject to the terms of the ISC License.
  178. .PHONY: clean-app
  179. # Configuration.
  180. ERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \
  181. +warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec
  182. COMPILE_FIRST ?=
  183. COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST)))
  184. ERLC_EXCLUDE ?=
  185. ERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE)))
  186. ERLC_ASN1_OPTS ?=
  187. ERLC_MIB_OPTS ?=
  188. COMPILE_MIB_FIRST ?=
  189. COMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST)))
  190. # Verbosity.
  191. app_verbose_0 = @echo " APP " $(PROJECT);
  192. app_verbose_2 = set -x;
  193. app_verbose = $(app_verbose_$(V))
  194. appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src;
  195. appsrc_verbose_2 = set -x;
  196. appsrc_verbose = $(appsrc_verbose_$(V))
  197. makedep_verbose_0 = @echo " DEPEND" $(PROJECT).d;
  198. makedep_verbose_2 = set -x;
  199. makedep_verbose = $(makedep_verbose_$(V))
  200. erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\
  201. $(filter %.erl %.core,$(?F)));
  202. erlc_verbose_2 = set -x;
  203. erlc_verbose = $(erlc_verbose_$(V))
  204. xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F));
  205. xyrl_verbose_2 = set -x;
  206. xyrl_verbose = $(xyrl_verbose_$(V))
  207. asn1_verbose_0 = @echo " ASN1 " $(filter %.asn1,$(?F));
  208. asn1_verbose_2 = set -x;
  209. asn1_verbose = $(asn1_verbose_$(V))
  210. mib_verbose_0 = @echo " MIB " $(filter %.bin %.mib,$(?F));
  211. mib_verbose_2 = set -x;
  212. mib_verbose = $(mib_verbose_$(V))
  213. ifneq ($(wildcard src/),)
  214. # Targets.
  215. ifeq ($(wildcard ebin/test),)
  216. app:: deps $(PROJECT).d
  217. $(verbose) $(MAKE) --no-print-directory app-build
  218. else
  219. app:: clean deps $(PROJECT).d
  220. $(verbose) $(MAKE) --no-print-directory app-build
  221. endif
  222. ifeq ($(wildcard src/$(PROJECT_MOD).erl),)
  223. define app_file
  224. {application, '$(PROJECT)', [
  225. {description, "$(PROJECT_DESCRIPTION)"},
  226. {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP),
  227. {id$(comma)$(space)"$(1)"}$(comma))
  228. {modules, [$(call comma_list,$(2))]},
  229. {registered, []},
  230. {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS))]},
  231. {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),)
  232. ]}.
  233. endef
  234. else
  235. define app_file
  236. {application, '$(PROJECT)', [
  237. {description, "$(PROJECT_DESCRIPTION)"},
  238. {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP),
  239. {id$(comma)$(space)"$(1)"}$(comma))
  240. {modules, [$(call comma_list,$(2))]},
  241. {registered, [$(call comma_list,$(PROJECT)_sup $(PROJECT_REGISTERED))]},
  242. {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS))]},
  243. {mod, {$(PROJECT_MOD), []}},
  244. {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),)
  245. ]}.
  246. endef
  247. endif
  248. app-build: ebin/$(PROJECT).app
  249. $(verbose) :
  250. # Source files.
  251. ALL_SRC_FILES := $(sort $(call core_find,src/,*))
  252. ERL_FILES := $(filter %.erl,$(ALL_SRC_FILES))
  253. CORE_FILES := $(filter %.core,$(ALL_SRC_FILES))
  254. # ASN.1 files.
  255. ifneq ($(wildcard asn1/),)
  256. ASN1_FILES = $(sort $(call core_find,asn1/,*.asn1))
  257. ERL_FILES += $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES))))
  258. define compile_asn1
  259. $(verbose) mkdir -p include/
  260. $(asn1_verbose) erlc -v -I include/ -o asn1/ +noobj $(ERLC_ASN1_OPTS) $(1)
  261. $(verbose) mv asn1/*.erl src/
  262. $(verbose) mv asn1/*.hrl include/
  263. $(verbose) mv asn1/*.asn1db include/
  264. endef
  265. $(PROJECT).d:: $(ASN1_FILES)
  266. $(if $(strip $?),$(call compile_asn1,$?))
  267. endif
  268. # SNMP MIB files.
  269. ifneq ($(wildcard mibs/),)
  270. MIB_FILES = $(sort $(call core_find,mibs/,*.mib))
  271. $(PROJECT).d:: $(COMPILE_MIB_FIRST_PATHS) $(MIB_FILES)
  272. $(verbose) mkdir -p include/ priv/mibs/
  273. $(mib_verbose) erlc -v $(ERLC_MIB_OPTS) -o priv/mibs/ -I priv/mibs/ $?
  274. $(mib_verbose) erlc -o include/ -- $(addprefix priv/mibs/,$(patsubst %.mib,%.bin,$(notdir $?)))
  275. endif
  276. # Leex and Yecc files.
  277. XRL_FILES := $(filter %.xrl,$(ALL_SRC_FILES))
  278. XRL_ERL_FILES = $(addprefix src/,$(patsubst %.xrl,%.erl,$(notdir $(XRL_FILES))))
  279. ERL_FILES += $(XRL_ERL_FILES)
  280. YRL_FILES := $(filter %.yrl,$(ALL_SRC_FILES))
  281. YRL_ERL_FILES = $(addprefix src/,$(patsubst %.yrl,%.erl,$(notdir $(YRL_FILES))))
  282. ERL_FILES += $(YRL_ERL_FILES)
  283. $(PROJECT).d:: $(XRL_FILES) $(YRL_FILES)
  284. $(if $(strip $?),$(xyrl_verbose) erlc -v -o src/ $(YRL_ERLC_OPTS) $?)
  285. # Erlang and Core Erlang files.
  286. define makedep.erl
  287. E = ets:new(makedep, [bag]),
  288. G = digraph:new([acyclic]),
  289. ErlFiles = lists:usort(string:tokens("$(ERL_FILES)", " ")),
  290. Modules = [{list_to_atom(filename:basename(F, ".erl")), F} || F <- ErlFiles],
  291. Add = fun (Mod, Dep) ->
  292. case lists:keyfind(Dep, 1, Modules) of
  293. false -> ok;
  294. {_, DepFile} ->
  295. {_, ModFile} = lists:keyfind(Mod, 1, Modules),
  296. ets:insert(E, {ModFile, DepFile}),
  297. digraph:add_vertex(G, Mod),
  298. digraph:add_vertex(G, Dep),
  299. digraph:add_edge(G, Mod, Dep)
  300. end
  301. end,
  302. AddHd = fun (F, Mod, DepFile) ->
  303. case file:open(DepFile, [read]) of
  304. {error, enoent} -> ok;
  305. {ok, Fd} ->
  306. F(F, Fd, Mod),
  307. {_, ModFile} = lists:keyfind(Mod, 1, Modules),
  308. ets:insert(E, {ModFile, DepFile})
  309. end
  310. end,
  311. Attr = fun
  312. (F, Mod, behavior, Dep) -> Add(Mod, Dep);
  313. (F, Mod, behaviour, Dep) -> Add(Mod, Dep);
  314. (F, Mod, compile, {parse_transform, Dep}) -> Add(Mod, Dep);
  315. (F, Mod, compile, Opts) when is_list(Opts) ->
  316. case proplists:get_value(parse_transform, Opts) of
  317. undefined -> ok;
  318. Dep -> Add(Mod, Dep)
  319. end;
  320. (F, Mod, include, Hrl) ->
  321. case filelib:is_file("include/" ++ Hrl) of
  322. true -> AddHd(F, Mod, "include/" ++ Hrl);
  323. false ->
  324. case filelib:is_file("src/" ++ Hrl) of
  325. true -> AddHd(F, Mod, "src/" ++ Hrl);
  326. false -> false
  327. end
  328. end;
  329. (F, Mod, include_lib, "$1/include/" ++ Hrl) -> AddHd(F, Mod, "include/" ++ Hrl);
  330. (F, Mod, include_lib, Hrl) -> AddHd(F, Mod, "include/" ++ Hrl);
  331. (F, Mod, import, {Imp, _}) ->
  332. IsFile =
  333. case lists:keyfind(Imp, 1, Modules) of
  334. false -> false;
  335. {_, FilePath} -> filelib:is_file(FilePath)
  336. end,
  337. case IsFile of
  338. false -> ok;
  339. true -> Add(Mod, Imp)
  340. end;
  341. (_, _, _, _) -> ok
  342. end,
  343. MakeDepend = fun(F, Fd, Mod) ->
  344. case io:parse_erl_form(Fd, undefined) of
  345. {ok, {attribute, _, Key, Value}, _} ->
  346. Attr(F, Mod, Key, Value),
  347. F(F, Fd, Mod);
  348. {eof, _} ->
  349. file:close(Fd);
  350. _ ->
  351. F(F, Fd, Mod)
  352. end
  353. end,
  354. [begin
  355. Mod = list_to_atom(filename:basename(F, ".erl")),
  356. {ok, Fd} = file:open(F, [read]),
  357. MakeDepend(MakeDepend, Fd, Mod)
  358. end || F <- ErlFiles],
  359. Depend = sofs:to_external(sofs:relation_to_family(sofs:relation(ets:tab2list(E)))),
  360. CompileFirst = [X || X <- lists:reverse(digraph_utils:topsort(G)), [] =/= digraph:in_neighbours(G, X)],
  361. TargetPath = fun(Target) ->
  362. case lists:keyfind(Target, 1, Modules) of
  363. false -> "";
  364. {_, DepFile} ->
  365. DirSubname = tl(string:tokens(filename:dirname(DepFile), "/")),
  366. string:join(DirSubname ++ [atom_to_list(Target)], "/")
  367. end
  368. end,
  369. ok = file:write_file("$(1)", [
  370. [[F, "::", [[" ", D] || D <- Deps], "; @touch \$$@\n"] || {F, Deps} <- Depend],
  371. "\nCOMPILE_FIRST +=", [[" ", TargetPath(CF)] || CF <- CompileFirst], "\n"
  372. ]),
  373. halt()
  374. endef
  375. ifeq ($(if $(NO_MAKEDEP),$(wildcard $(PROJECT).d),),)
  376. $(PROJECT).d:: $(ERL_FILES) $(call core_find,include/,*.hrl) $(MAKEFILE_LIST)
  377. $(makedep_verbose) $(call erlang,$(call makedep.erl,$@))
  378. endif
  379. ifneq ($(words $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES)),0)
  380. # Rebuild everything when the Makefile changes.
  381. $(ERLANG_MK_TMP)/last-makefile-change: $(MAKEFILE_LIST)
  382. $(verbose) mkdir -p $(ERLANG_MK_TMP)
  383. $(verbose) if test -f $@; then \
  384. touch $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES); \
  385. touch -c $(PROJECT).d; \
  386. fi
  387. $(verbose) touch $@
  388. $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES):: $(ERLANG_MK_TMP)/last-makefile-change
  389. ebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change
  390. endif
  391. include $(wildcard $(PROJECT).d)
  392. ebin/$(PROJECT).app:: ebin/
  393. ebin/:
  394. $(verbose) mkdir -p ebin/
  395. define compile_erl
  396. $(erlc_verbose) erlc -v $(if $(IS_DEP),$(filter-out -Werror,$(ERLC_OPTS)),$(ERLC_OPTS)) -o ebin/ \
  397. -pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),$(COMPILE_FIRST_PATHS) $(1))
  398. endef
  399. ebin/$(PROJECT).app:: $(ERL_FILES) $(CORE_FILES) $(wildcard src/$(PROJECT).app.src)
  400. $(eval FILES_TO_COMPILE := $(filter-out src/$(PROJECT).app.src,$?))
  401. $(if $(strip $(FILES_TO_COMPILE)),$(call compile_erl,$(FILES_TO_COMPILE)))
  402. $(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null || true))
  403. $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \
  404. $(filter-out $(ERLC_EXCLUDE_PATHS),$(ERL_FILES) $(CORE_FILES) $(BEAM_FILES)))))))
  405. ifeq ($(wildcard src/$(PROJECT).app.src),)
  406. $(app_verbose) printf '$(subst %,%%,$(subst $(newline),\n,$(subst ','\'',$(call app_file,$(GITDESCRIBE),$(MODULES)))))' \
  407. > ebin/$(PROJECT).app
  408. else
  409. $(verbose) if [ -z "$$(grep -e '^[^%]*{\s*modules\s*,' src/$(PROJECT).app.src)" ]; then \
  410. echo "Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk README for instructions." >&2; \
  411. exit 1; \
  412. fi
  413. $(appsrc_verbose) cat src/$(PROJECT).app.src \
  414. | sed "s/{[[:space:]]*modules[[:space:]]*,[[:space:]]*\[\]}/{modules, \[$(call comma_list,$(MODULES))\]}/" \
  415. | sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(subst /,\/,$(GITDESCRIBE))\"}/" \
  416. > ebin/$(PROJECT).app
  417. endif
  418. clean:: clean-app
  419. clean-app:
  420. $(gen_verbose) rm -rf $(PROJECT).d ebin/ priv/mibs/ $(XRL_ERL_FILES) $(YRL_ERL_FILES) \
  421. $(addprefix include/,$(patsubst %.mib,%.hrl,$(notdir $(MIB_FILES)))) \
  422. $(addprefix include/,$(patsubst %.asn1,%.hrl,$(notdir $(ASN1_FILES)))) \
  423. $(addprefix include/,$(patsubst %.asn1,%.asn1db,$(notdir $(ASN1_FILES)))) \
  424. $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES))))
  425. endif
  426. # Copyright (c) 2016, Loïc Hoguin <essen@ninenines.eu>
  427. # Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
  428. # This file is part of erlang.mk and subject to the terms of the ISC License.
  429. .PHONY: docs-deps
  430. # Configuration.
  431. ALL_DOC_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DOC_DEPS))
  432. # Targets.
  433. $(foreach dep,$(DOC_DEPS),$(eval $(call dep_target,$(dep))))
  434. ifneq ($(SKIP_DEPS),)
  435. doc-deps:
  436. else
  437. doc-deps: $(ALL_DOC_DEPS_DIRS)
  438. $(verbose) set -e; for dep in $(ALL_DOC_DEPS_DIRS) ; do $(MAKE) -C $$dep; done
  439. endif
  440. # Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
  441. # This file is part of erlang.mk and subject to the terms of the ISC License.
  442. .PHONY: test-deps test-dir test-build clean-test-dir
  443. # Configuration.
  444. TEST_DIR ?= $(CURDIR)/test
  445. ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS))
  446. TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard
  447. TEST_ERLC_OPTS += -DTEST=1
  448. # Targets.
  449. $(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep))))
  450. ifneq ($(SKIP_DEPS),)
  451. test-deps:
  452. else
  453. test-deps: $(ALL_TEST_DEPS_DIRS)
  454. $(verbose) set -e; for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep IS_DEP=1; done
  455. endif
  456. ifneq ($(wildcard $(TEST_DIR)),)
  457. test-dir:
  458. $(gen_verbose) erlc -v $(TEST_ERLC_OPTS) -I include/ -o $(TEST_DIR) \
  459. $(call core_find,$(TEST_DIR)/,*.erl) -pa ebin/
  460. endif
  461. ifeq ($(wildcard src),)
  462. test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)
  463. test-build:: clean deps test-deps
  464. $(verbose) $(MAKE) --no-print-directory test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
  465. else
  466. ifeq ($(wildcard ebin/test),)
  467. test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)
  468. test-build:: clean deps test-deps $(PROJECT).d
  469. $(verbose) $(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
  470. $(gen_verbose) touch ebin/test
  471. else
  472. test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)
  473. test-build:: deps test-deps $(PROJECT).d
  474. $(verbose) $(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
  475. endif
  476. clean:: clean-test-dir
  477. clean-test-dir:
  478. ifneq ($(wildcard $(TEST_DIR)/*.beam),)
  479. $(gen_verbose) rm -f $(TEST_DIR)/*.beam
  480. endif
  481. endif
  482. # Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
  483. # This file is part of erlang.mk and subject to the terms of the ISC License.
  484. .PHONY: plt distclean-plt dialyze
  485. # Configuration.
  486. DIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt
  487. export DIALYZER_PLT
  488. PLT_APPS ?=
  489. DIALYZER_DIRS ?= --src -r $(wildcard src) $(ALL_APPS_DIRS)
  490. DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions -Wunmatched_returns # -Wunderspecs
  491. # Core targets.
  492. check:: dialyze
  493. distclean:: distclean-plt
  494. help::
  495. $(verbose) printf "%s\n" "" \
  496. "Dialyzer targets:" \
  497. " plt Build a PLT file for this project" \
  498. " dialyze Analyze the project using Dialyzer"
  499. # Plugin-specific targets.
  500. define filter_opts.erl
  501. Opts = init:get_plain_arguments(),
  502. {Filtered, _} = lists:foldl(fun
  503. (O, {Os, true}) -> {[O|Os], false};
  504. (O = "-D", {Os, _}) -> {[O|Os], true};
  505. (O = [\\$$-, \\$$D, _ | _], {Os, _}) -> {[O|Os], false};
  506. (O = "-I", {Os, _}) -> {[O|Os], true};
  507. (O = [\\$$-, \\$$I, _ | _], {Os, _}) -> {[O|Os], false};
  508. (O = "-pa", {Os, _}) -> {[O|Os], true};
  509. (_, Acc) -> Acc
  510. end, {[], false}, Opts),
  511. io:format("~s~n", [string:join(lists:reverse(Filtered), " ")]),
  512. halt().
  513. endef
  514. $(DIALYZER_PLT): deps app
  515. $(eval DEPS_LOG := $(shell test -f $(ERLANG_MK_TMP)/deps.log && \
  516. while read p; do test -d $$p/ebin && echo $$p/ebin; done <$(ERLANG_MK_TMP)/deps.log))
  517. $(verbose) dialyzer --build_plt --apps erts kernel stdlib \
  518. $(PLT_APPS) $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS_LOG)
  519. plt: $(DIALYZER_PLT)
  520. distclean-plt:
  521. $(gen_verbose) rm -f $(DIALYZER_PLT)
  522. ifneq ($(wildcard $(DIALYZER_PLT)),)
  523. dialyze:
  524. else
  525. dialyze: $(DIALYZER_PLT)
  526. endif
  527. $(verbose) dialyzer --no_native `$(ERL) -eval "$(subst $(newline),,$(subst ",\",$(call filter_opts.erl)))" -extra $(ERLC_OPTS)` $(DIALYZER_DIRS) $(DIALYZER_OPTS)
  528. # Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
  529. # This file is part of erlang.mk and subject to the terms of the ISC License.
  530. .PHONY: distclean-edoc edoc
  531. # Configuration.
  532. EDOC_OPTS ?=
  533. EDOC_SRC_DIRS ?=
  534. define edoc.erl
  535. SrcPaths = lists:foldl(fun(P, Acc) ->
  536. filelib:wildcard(atom_to_list(P) ++ "/{src,c_src}") ++ Acc
  537. end, [], [$(call comma_list,$(patsubst %,'%',$(EDOC_SRC_DIRS)))]),
  538. DefaultOpts = [{source_path, SrcPaths}, {subpackages, false}],
  539. edoc:application($(1), ".", [$(2)] ++ DefaultOpts),
  540. halt(0).
  541. endef
  542. # Core targets.
  543. ifneq ($(strip $(EDOC_SRC_DIRS)$(wildcard doc/overview.edoc)),)
  544. docs:: edoc
  545. endif
  546. distclean:: distclean-edoc
  547. # Plugin-specific targets.
  548. edoc: distclean-edoc doc-deps
  549. $(gen_verbose) $(call erlang,$(call edoc.erl,$(PROJECT),$(EDOC_OPTS)))
  550. distclean-edoc:
  551. $(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info
  552. # Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
  553. # Copyright (c) 2014, Enrique Fernandez <enrique.fernandez@erlang-solutions.com>
  554. # This file is contributed to erlang.mk and subject to the terms of the ISC License.
  555. .PHONY: eunit apps-eunit
  556. # Configuration
  557. EUNIT_OPTS ?=
  558. EUNIT_ERL_OPTS ?=
  559. # Core targets.
  560. tests:: eunit
  561. help::
  562. $(verbose) printf "%s\n" "" \
  563. "EUnit targets:" \
  564. " eunit Run all the EUnit tests for this project"
  565. # Plugin-specific targets.
  566. define eunit.erl
  567. case "$(COVER)" of
  568. "" -> ok;
  569. _ ->
  570. case cover:compile_beam_directory("ebin") of
  571. {error, _} -> halt(1);
  572. _ -> ok
  573. end
  574. end,
  575. case eunit:test($1, [$(EUNIT_OPTS)]) of
  576. ok -> ok;
  577. error -> halt(2)
  578. end,
  579. case "$(COVER)" of
  580. "" -> ok;
  581. _ ->
  582. cover:export("$(COVER_DATA_DIR)/eunit.coverdata")
  583. end,
  584. halt()
  585. endef
  586. EUNIT_ERL_OPTS += -pa $(TEST_DIR) $(DEPS_DIR)/*/ebin $(APPS_DIR)/*/ebin $(CURDIR)/ebin
  587. ifdef t
  588. ifeq (,$(findstring :,$(t)))
  589. eunit: test-build cover-data-dir
  590. $(gen_verbose) $(call erlang,$(call eunit.erl,['$(t)']),$(EUNIT_ERL_OPTS))
  591. else
  592. eunit: test-build cover-data-dir
  593. $(gen_verbose) $(call erlang,$(call eunit.erl,fun $(t)/0),$(EUNIT_ERL_OPTS))
  594. endif
  595. else
  596. EUNIT_EBIN_MODS = $(notdir $(basename $(ERL_FILES) $(BEAM_FILES)))
  597. EUNIT_TEST_MODS = $(notdir $(basename $(call core_find,$(TEST_DIR)/,*.erl)))
  598. EUNIT_MODS = $(foreach mod,$(EUNIT_EBIN_MODS) $(filter-out \
  599. $(patsubst %,%_tests,$(EUNIT_EBIN_MODS)),$(EUNIT_TEST_MODS)),'$(mod)')
  600. eunit: test-build $(if $(IS_APP),,apps-eunit) cover-data-dir
  601. $(gen_verbose) $(call erlang,$(call eunit.erl,[$(call comma_list,$(EUNIT_MODS))]),$(EUNIT_ERL_OPTS))
  602. ifneq ($(ALL_APPS_DIRS),)
  603. apps-eunit:
  604. $(verbose) eunit_retcode=0 ; for app in $(ALL_APPS_DIRS); do $(MAKE) -C $$app eunit IS_APP=1; \
  605. [ $$? -ne 0 ] && eunit_retcode=1 ; done ; \
  606. exit $$eunit_retcode
  607. endif
  608. endif
  609. # Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
  610. # Copyright (c) 2014, M Robert Martin <rob@version2beta.com>
  611. # This file is contributed to erlang.mk and subject to the terms of the ISC License.
  612. .PHONY: shell
  613. # Configuration.
  614. SHELL_ERL ?= erl
  615. SHELL_PATHS ?= $(CURDIR)/ebin $(APPS_DIR)/*/ebin $(DEPS_DIR)/*/ebin
  616. SHELL_OPTS ?=
  617. ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS))
  618. # Core targets
  619. help::
  620. $(verbose) printf "%s\n" "" \
  621. "Shell targets:" \
  622. " shell Run an erlang shell with SHELL_OPTS or reasonable default"
  623. # Plugin-specific targets.
  624. $(foreach dep,$(SHELL_DEPS),$(eval $(call dep_target,$(dep))))
  625. build-shell-deps: $(ALL_SHELL_DEPS_DIRS)
  626. $(verbose) set -e; for dep in $(ALL_SHELL_DEPS_DIRS) ; do $(MAKE) -C $$dep ; done
  627. shell: build-shell-deps
  628. $(gen_verbose) $(SHELL_ERL) -pa $(SHELL_PATHS) $(SHELL_OPTS)
  629. # Copyright (c) 2016, Loïc Hoguin <essen@ninenines.eu>
  630. # Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
  631. # This file is part of erlang.mk and subject to the terms of the ISC License.
  632. COVER_REPORT_DIR ?= cover
  633. COVER_DATA_DIR ?= $(CURDIR)
  634. # Hook in coverage to ct
  635. ifdef COVER
  636. ifdef CT_RUN
  637. ifneq ($(wildcard $(TEST_DIR)),)
  638. test-build:: $(TEST_DIR)/ct.cover.spec
  639. $(TEST_DIR)/ct.cover.spec: cover-data-dir
  640. $(gen_verbose) printf "%s\n" \
  641. "{incl_app, '$(PROJECT)', details}." \
  642. '{export,"$(abspath $(COVER_DATA_DIR))/ct.coverdata"}.' > $@
  643. CT_RUN += -cover $(TEST_DIR)/ct.cover.spec
  644. endif
  645. endif
  646. endif
  647. # Core targets
  648. ifdef COVER
  649. ifneq ($(COVER_REPORT_DIR),)
  650. tests::
  651. $(verbose) $(MAKE) --no-print-directory cover-report
  652. endif
  653. cover-data-dir: | $(COVER_DATA_DIR)
  654. $(COVER_DATA_DIR):
  655. $(verbose) mkdir -p $(COVER_DATA_DIR)
  656. else
  657. cover-data-dir:
  658. endif
  659. clean:: coverdata-clean
  660. ifneq ($(COVER_REPORT_DIR),)
  661. distclean:: cover-report-clean
  662. endif
  663. help::
  664. $(verbose) printf "%s\n" "" \
  665. "Cover targets:" \
  666. " cover-report Generate a HTML coverage report from previously collected" \
  667. " cover data." \
  668. " all.coverdata Merge all coverdata files into all.coverdata." \
  669. "" \
  670. "If COVER=1 is set, coverage data is generated by the targets eunit and ct. The" \
  671. "target tests additionally generates a HTML coverage report from the combined" \
  672. "coverdata files from each of these testing tools. HTML reports can be disabled" \
  673. "by setting COVER_REPORT_DIR to empty."
  674. # Plugin specific targets
  675. COVERDATA = $(filter-out $(COVER_DATA_DIR)/all.coverdata,$(wildcard $(COVER_DATA_DIR)/*.coverdata))
  676. .PHONY: coverdata-clean
  677. coverdata-clean:
  678. $(gen_verbose) rm -f $(COVER_DATA_DIR)/*.coverdata $(TEST_DIR)/ct.cover.spec
  679. # Merge all coverdata files into one.
  680. define cover_export.erl
  681. $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),)
  682. cover:export("$(COVER_DATA_DIR)/$@"), halt(0).
  683. endef
  684. all.coverdata: $(COVERDATA) cover-data-dir
  685. $(gen_verbose) $(call erlang,$(cover_export.erl))
  686. # These are only defined if COVER_REPORT_DIR is non-empty. Set COVER_REPORT_DIR to
  687. # empty if you want the coverdata files but not the HTML report.
  688. ifneq ($(COVER_REPORT_DIR),)
  689. .PHONY: cover-report-clean cover-report
  690. cover-report-clean:
  691. $(gen_verbose) rm -rf $(COVER_REPORT_DIR)
  692. $(if $(shell ls -A $(COVER_DATA_DIR)/),,$(verbose) rmdir $(COVER_DATA_DIR))
  693. ifeq ($(COVERDATA),)
  694. cover-report:
  695. else
  696. # Modules which include eunit.hrl always contain one line without coverage
  697. # because eunit defines test/0 which is never called. We compensate for this.
  698. EUNIT_HRL_MODS = $(subst $(space),$(comma),$(shell \
  699. grep -H -e '^\s*-include.*include/eunit\.hrl"' src/*.erl \
  700. | sed "s/^src\/\(.*\)\.erl:.*/'\1'/" | uniq))
  701. define cover_report.erl
  702. $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),)
  703. Ms = cover:imported_modules(),
  704. [cover:analyse_to_file(M, "$(COVER_REPORT_DIR)/" ++ atom_to_list(M)
  705. ++ ".COVER.html", [html]) || M <- Ms],
  706. Report = [begin {ok, R} = cover:analyse(M, module), R end || M <- Ms],
  707. EunitHrlMods = [$(EUNIT_HRL_MODS)],
  708. Report1 = [{M, {Y, case lists:member(M, EunitHrlMods) of
  709. true -> N - 1; false -> N end}} || {M, {Y, N}} <- Report],
  710. TotalY = lists:sum([Y || {_, {Y, _}} <- Report1]),
  711. TotalN = lists:sum([N || {_, {_, N}} <- Report1]),
  712. Perc = fun(Y, N) -> case Y + N of 0 -> 100; S -> round(100 * Y / S) end end,
  713. TotalPerc = Perc(TotalY, TotalN),
  714. {ok, F} = file:open("$(COVER_REPORT_DIR)/index.html", [write]),
  715. io:format(F, "<!DOCTYPE html><html>~n"
  716. "<head><meta charset=\"UTF-8\">~n"
  717. "<title>Coverage report</title></head>~n"
  718. "<body>~n", []),
  719. io:format(F, "<h1>Coverage</h1>~n<p>Total: ~p%</p>~n", [TotalPerc]),
  720. io:format(F, "<table><tr><th>Module</th><th>Coverage</th></tr>~n", []),
  721. [io:format(F, "<tr><td><a href=\"~p.COVER.html\">~p</a></td>"
  722. "<td>~p%</td></tr>~n",
  723. [M, M, Perc(Y, N)]) || {M, {Y, N}} <- Report1],
  724. How = "$(subst $(space),$(comma)$(space),$(basename $(COVERDATA)))",
  725. Date = "$(shell date -u "+%Y-%m-%dT%H:%M:%SZ")",
  726. io:format(F, "</table>~n"
  727. "<p>Generated using ~s and erlang.mk on ~s.</p>~n"
  728. "</body></html>", [How, Date]),
  729. halt().
  730. endef
  731. cover-report:
  732. $(verbose) mkdir -p $(COVER_REPORT_DIR)
  733. $(gen_verbose) $(call erlang,$(cover_report.erl))
  734. endif
  735. endif # ifneq ($(COVER_REPORT_DIR),)