erlang.mk 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181
  1. # Copyright (c) 2013-2015, 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 deps app rel docs install-docs tests check clean distclean help erlang-mk
  15. ERLANG_MK_VERSION = 1.2.0-564-g178e214-dirty
  16. # Core configuration.
  17. PROJECT ?= $(notdir $(CURDIR))
  18. PROJECT := $(strip $(PROJECT))
  19. # Verbosity.
  20. V ?= 0
  21. gen_verbose_0 = @echo " GEN " $@;
  22. gen_verbose = $(gen_verbose_$(V))
  23. # Temporary files directory.
  24. ERLANG_MK_TMP ?= $(CURDIR)/.erlang.mk
  25. export ERLANG_MK_TMP
  26. # "erl" command.
  27. ERL = erl +A0 -noinput -boot start_clean
  28. # Platform detection.
  29. # @todo Add Windows/Cygwin detection eventually.
  30. ifeq ($(PLATFORM),)
  31. UNAME_S := $(shell uname -s)
  32. ifeq ($(UNAME_S),Linux)
  33. PLATFORM = linux
  34. else ifeq ($(UNAME_S),Darwin)
  35. PLATFORM = darwin
  36. else ifeq ($(UNAME_S),SunOS)
  37. PLATFORM = solaris
  38. else ifeq ($(UNAME_S),GNU)
  39. PLATFORM = gnu
  40. else ifeq ($(UNAME_S),FreeBSD)
  41. PLATFORM = freebsd
  42. else ifeq ($(UNAME_S),NetBSD)
  43. PLATFORM = netbsd
  44. else ifeq ($(UNAME_S),OpenBSD)
  45. PLATFORM = openbsd
  46. else
  47. $(error Unable to detect platform. Please open a ticket with the output of uname -a.)
  48. endif
  49. export PLATFORM
  50. endif
  51. # Core targets.
  52. ifneq ($(words $(MAKECMDGOALS)),1)
  53. .NOTPARALLEL:
  54. endif
  55. all:: deps
  56. @$(MAKE) --no-print-directory app
  57. @$(MAKE) --no-print-directory rel
  58. # Noop to avoid a Make warning when there's nothing to do.
  59. rel::
  60. @echo -n
  61. check:: clean app tests
  62. clean:: clean-crashdump
  63. clean-crashdump:
  64. ifneq ($(wildcard erl_crash.dump),)
  65. $(gen_verbose) rm -f erl_crash.dump
  66. endif
  67. distclean:: clean
  68. help::
  69. @printf "%s\n" \
  70. "erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \
  71. "Copyright (c) 2013-2014 Loïc Hoguin <essen@ninenines.eu>" \
  72. "" \
  73. "Usage: [V=1] $(MAKE) [-jNUM] [target]" \
  74. "" \
  75. "Core targets:" \
  76. " all Run deps, app and rel targets in that order" \
  77. " deps Fetch dependencies (if needed) and compile them" \
  78. " app Compile the project" \
  79. " rel Build a release for this project, if applicable" \
  80. " docs Build the documentation for this project" \
  81. " install-docs Install the man pages for this project" \
  82. " tests Run the tests for this project" \
  83. " check Compile and run all tests and analysis for this project" \
  84. " clean Delete temporary and output files from most targets" \
  85. " distclean Delete all temporary and output files" \
  86. " help Display this help and exit" \
  87. "" \
  88. "The target clean only removes files that are commonly removed." \
  89. "Dependencies and releases are left untouched." \
  90. "" \
  91. "Setting V=1 when calling $(MAKE) enables verbose mode." \
  92. "Parallel execution is supported through the -j $(MAKE) flag."
  93. # Core functions.
  94. define newline
  95. endef
  96. # Adding erlang.mk to make Erlang scripts who call init:get_plain_arguments() happy.
  97. define erlang
  98. $(ERL) -pa $(ERLANG_MK_TMP)/rebar/ebin -eval "$(subst $(newline),,$(subst ",\",$(1)))" -- erlang.mk
  99. endef
  100. ifeq ($(shell which wget 2>/dev/null | wc -l), 1)
  101. define core_http_get
  102. wget --no-check-certificate -O $(1) $(2)|| rm $(1)
  103. endef
  104. else
  105. define core_http_get.erl
  106. ssl:start(),
  107. inets:start(),
  108. case httpc:request(get, {"$(2)", []}, [{autoredirect, true}], []) of
  109. {ok, {{_, 200, _}, _, Body}} ->
  110. case file:write_file("$(1)", Body) of
  111. ok -> ok;
  112. {error, R1} -> halt(R1)
  113. end;
  114. {error, R2} ->
  115. halt(R2)
  116. end,
  117. halt(0).
  118. endef
  119. define core_http_get
  120. $(call erlang,$(call core_http_get.erl,$(1),$(2)))
  121. endef
  122. endif
  123. # Automated update.
  124. ERLANG_MK_BUILD_CONFIG ?= build.config
  125. ERLANG_MK_BUILD_DIR ?= .erlang.mk.build
  126. erlang-mk:
  127. git clone https://github.com/ninenines/erlang.mk $(ERLANG_MK_BUILD_DIR)
  128. if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR); fi
  129. cd $(ERLANG_MK_BUILD_DIR) && $(MAKE)
  130. cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk
  131. rm -rf $(ERLANG_MK_BUILD_DIR)
  132. # Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
  133. # This file is part of erlang.mk and subject to the terms of the ISC License.
  134. .PHONY: distclean-deps distclean-pkg pkg-list pkg-search
  135. # Configuration.
  136. IGNORE_DEPS ?=
  137. DEPS_DIR ?= $(CURDIR)/deps
  138. export DEPS_DIR
  139. REBAR_DEPS_DIR = $(DEPS_DIR)
  140. export REBAR_DEPS_DIR
  141. ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(filter-out $(IGNORE_DEPS),$(DEPS)))
  142. ifeq ($(filter $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),)
  143. ifeq ($(ERL_LIBS),)
  144. ERL_LIBS = $(DEPS_DIR)
  145. else
  146. ERL_LIBS := $(ERL_LIBS):$(DEPS_DIR)
  147. endif
  148. endif
  149. export ERL_LIBS
  150. PKG_FILE2 ?= $(CURDIR)/.erlang.mk.packages.v2
  151. export PKG_FILE2
  152. PKG_FILE_URL ?= https://raw.githubusercontent.com/ninenines/erlang.mk/master/packages.v2.tsv
  153. # Verbosity.
  154. dep_verbose_0 = @echo " DEP " $(1);
  155. dep_verbose = $(dep_verbose_$(V))
  156. # Core targets.
  157. ifneq ($(SKIP_DEPS),)
  158. deps::
  159. else
  160. deps:: $(ALL_DEPS_DIRS)
  161. @for dep in $(ALL_DEPS_DIRS) ; do \
  162. if [ -f $$dep/GNUmakefile ] || [ -f $$dep/makefile ] || [ -f $$dep/Makefile ] ; then \
  163. $(MAKE) -C $$dep IS_DEP=1 || exit $$? ; \
  164. else \
  165. echo "ERROR: No Makefile to build dependency $$dep." ; \
  166. exit 1 ; \
  167. fi ; \
  168. done
  169. endif
  170. distclean:: distclean-deps distclean-pkg
  171. # Deps related targets.
  172. # @todo rename GNUmakefile and makefile into Makefile first, if they exist
  173. # While Makefile file could be GNUmakefile or makefile,
  174. # in practice only Makefile is needed so far.
  175. define dep_autopatch
  176. if [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \
  177. if [ 0 != `grep -c "include ../\w*\.mk" $(DEPS_DIR)/$(1)/Makefile` ]; then \
  178. $(call dep_autopatch2,$(1)); \
  179. elif [ 0 != `grep -ci rebar $(DEPS_DIR)/$(1)/Makefile` ]; then \
  180. $(call dep_autopatch2,$(1)); \
  181. elif [ 0 != `find $(DEPS_DIR)/$(1)/ -type f -name \*.mk -not -name erlang.mk | xargs grep -ci rebar` ]; then \
  182. $(call dep_autopatch2,$(1)); \
  183. else \
  184. if [ -f $(DEPS_DIR)/$(1)/erlang.mk ]; then \
  185. $(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \
  186. $(call dep_autopatch_erlang_mk,$(1)); \
  187. else \
  188. $(call erlang,$(call dep_autopatch_app.erl,$(1))); \
  189. fi \
  190. fi \
  191. else \
  192. if [ ! -d $(DEPS_DIR)/$(1)/src/ ]; then \
  193. $(call dep_autopatch_noop,$(1)); \
  194. else \
  195. $(call dep_autopatch2,$(1)); \
  196. fi \
  197. fi
  198. endef
  199. define dep_autopatch2
  200. $(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \
  201. if [ -f $(DEPS_DIR)/$(1)/rebar.config -o -f $(DEPS_DIR)/$(1)/rebar.config.script ]; then \
  202. $(call dep_autopatch_fetch_rebar); \
  203. $(call dep_autopatch_rebar,$(1)); \
  204. else \
  205. $(call dep_autopatch_gen,$(1)); \
  206. fi
  207. endef
  208. define dep_autopatch_noop
  209. printf "noop:\n" > $(DEPS_DIR)/$(1)/Makefile
  210. endef
  211. # Overwrite erlang.mk with the current file by default.
  212. ifeq ($(NO_AUTOPATCH_ERLANG_MK),)
  213. define dep_autopatch_erlang_mk
  214. rm -f $(DEPS_DIR)/$(1)/erlang.mk; \
  215. cd $(DEPS_DIR)/$(1)/ && ln -s ../../erlang.mk
  216. endef
  217. else
  218. define dep_autopatch_erlang_mk
  219. echo -n
  220. endef
  221. endif
  222. define dep_autopatch_gen
  223. printf "%s\n" \
  224. "ERLC_OPTS = +debug_info" \
  225. "include ../../erlang.mk" > $(DEPS_DIR)/$(1)/Makefile
  226. endef
  227. define dep_autopatch_fetch_rebar
  228. mkdir -p $(ERLANG_MK_TMP); \
  229. if [ ! -d $(ERLANG_MK_TMP)/rebar ]; then \
  230. git clone -q -n -- https://github.com/rebar/rebar $(ERLANG_MK_TMP)/rebar; \
  231. cd $(ERLANG_MK_TMP)/rebar; \
  232. git checkout -q 791db716b5a3a7671e0b351f95ddf24b848ee173; \
  233. make; \
  234. cd -; \
  235. fi
  236. endef
  237. define dep_autopatch_rebar
  238. if [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \
  239. mv $(DEPS_DIR)/$(1)/Makefile $(DEPS_DIR)/$(1)/Makefile.orig.mk; \
  240. fi; \
  241. $(call erlang,$(call dep_autopatch_rebar.erl,$(1)))
  242. endef
  243. define dep_autopatch_rebar.erl
  244. application:set_env(rebar, log_level, debug),
  245. Conf1 = case file:consult("$(DEPS_DIR)/$(1)/rebar.config") of
  246. {ok, Conf0} -> Conf0;
  247. _ -> []
  248. end,
  249. {Conf, OsEnv} = fun() ->
  250. case filelib:is_file("$(DEPS_DIR)/$(1)/rebar.config.script") of
  251. false -> {Conf1, []};
  252. true ->
  253. Bindings0 = erl_eval:new_bindings(),
  254. Bindings1 = erl_eval:add_binding('CONFIG', Conf1, Bindings0),
  255. Bindings = erl_eval:add_binding('SCRIPT', "$(DEPS_DIR)/$(1)/rebar.config.script", Bindings1),
  256. Before = os:getenv(),
  257. {ok, Conf2} = file:script("$(DEPS_DIR)/$(1)/rebar.config.script", Bindings),
  258. {Conf2, lists:foldl(fun(E, Acc) -> lists:delete(E, Acc) end, os:getenv(), Before)}
  259. end
  260. end(),
  261. Write = fun (Text) ->
  262. file:write_file("$(DEPS_DIR)/$(1)/Makefile", Text, [append])
  263. end,
  264. Escape = fun (Text) ->
  265. re:replace(Text, "\\\\$$$$", "\$$$$$$$$", [global, {return, list}])
  266. end,
  267. Write("IGNORE_DEPS = edown eper eunit_formatters meck node_package "
  268. "rebar_lock_deps_plugin rebar_vsn_plugin reltool_util\n"),
  269. Write("C_SRC_DIR = /path/do/not/exist\n"),
  270. Write("DRV_CFLAGS = -fPIC\nexport DRV_CFLAGS\n"),
  271. Write(["ERLANG_ARCH = ", rebar_utils:wordsize(), "\nexport ERLANG_ARCH\n"]),
  272. fun() ->
  273. Write("ERLC_OPTS = +debug_info\nexport ERLC_OPTS\n"),
  274. case lists:keyfind(erl_opts, 1, Conf) of
  275. false -> ok;
  276. {_, ErlOpts} ->
  277. lists:foreach(fun
  278. ({d, D}) ->
  279. Write("ERLC_OPTS += -D" ++ atom_to_list(D) ++ "=1\n");
  280. ({i, I}) ->
  281. Write(["ERLC_OPTS += -I ", I, "\n"]);
  282. ({platform_define, Regex, D}) ->
  283. case rebar_utils:is_arch(Regex) of
  284. true -> Write("ERLC_OPTS += -D" ++ atom_to_list(D) ++ "=1\n");
  285. false -> ok
  286. end;
  287. ({parse_transform, PT}) ->
  288. Write("ERLC_OPTS += +'{parse_transform, " ++ atom_to_list(PT) ++ "}'\n");
  289. (_) -> ok
  290. end, ErlOpts)
  291. end,
  292. Write("\n")
  293. end(),
  294. fun() ->
  295. File = case lists:keyfind(deps, 1, Conf) of
  296. false -> [];
  297. {_, Deps} ->
  298. [begin case case Dep of
  299. {N, S} when is_tuple(S) -> {N, S};
  300. {N, _, S} -> {N, S};
  301. {N, _, S, _} -> {N, S};
  302. _ -> false
  303. end of
  304. false -> ok;
  305. {Name, Source} ->
  306. {Method, Repo, Commit} = case Source of
  307. {git, R} -> {git, R, master};
  308. {M, R, {branch, C}} -> {M, R, C};
  309. {M, R, {tag, C}} -> {M, R, C};
  310. {M, R, C} -> {M, R, C}
  311. end,
  312. Write(io_lib:format("DEPS += ~s\ndep_~s = ~s ~s ~s~n", [Name, Name, Method, Repo, Commit]))
  313. end end || Dep <- Deps]
  314. end
  315. end(),
  316. fun() ->
  317. case lists:keyfind(erl_first_files, 1, Conf) of
  318. false -> ok;
  319. {_, Files} ->
  320. Names = [[" ", case lists:reverse(F) of
  321. "lre." ++ Elif -> lists:reverse(Elif);
  322. Elif -> lists:reverse(Elif)
  323. end] || "src/" ++ F <- Files],
  324. Write(io_lib:format("COMPILE_FIRST +=~s\n", [Names]))
  325. end
  326. end(),
  327. FindFirst = fun(F, Fd) ->
  328. case io:parse_erl_form(Fd, undefined) of
  329. {ok, {attribute, _, compile, {parse_transform, PT}}, _} ->
  330. [PT, F(F, Fd)];
  331. {ok, {attribute, _, compile, CompileOpts}, _} when is_list(CompileOpts) ->
  332. case proplists:get_value(parse_transform, CompileOpts) of
  333. undefined -> [F(F, Fd)];
  334. PT -> [PT, F(F, Fd)]
  335. end;
  336. {ok, {attribute, _, include, Hrl}, _} ->
  337. case file:open("$(DEPS_DIR)/$(1)/include/" ++ Hrl, [read]) of
  338. {ok, HrlFd} -> [F(F, HrlFd), F(F, Fd)];
  339. _ ->
  340. case file:open("$(DEPS_DIR)/$(1)/src/" ++ Hrl, [read]) of
  341. {ok, HrlFd} -> [F(F, HrlFd), F(F, Fd)];
  342. _ -> [F(F, Fd)]
  343. end
  344. end;
  345. {ok, {attribute, _, include_lib, "$(1)/include/" ++ Hrl}, _} ->
  346. {ok, HrlFd} = file:open("$(DEPS_DIR)/$(1)/include/" ++ Hrl, [read]),
  347. [F(F, HrlFd), F(F, Fd)];
  348. {ok, {attribute, _, include_lib, Hrl}, _} ->
  349. case file:open("$(DEPS_DIR)/$(1)/include/" ++ Hrl, [read]) of
  350. {ok, HrlFd} -> [F(F, HrlFd), F(F, Fd)];
  351. _ -> [F(F, Fd)]
  352. end;
  353. {ok, {attribute, _, import, {Imp, _}}, _} ->
  354. case file:open("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(Imp) ++ ".erl", [read]) of
  355. {ok, ImpFd} -> [Imp, F(F, ImpFd), F(F, Fd)];
  356. _ -> [F(F, Fd)]
  357. end;
  358. {eof, _} ->
  359. file:close(Fd),
  360. [];
  361. _ ->
  362. F(F, Fd)
  363. end
  364. end,
  365. fun() ->
  366. ErlFiles = filelib:wildcard("$(DEPS_DIR)/$(1)/src/*.erl"),
  367. First0 = lists:usort(lists:flatten([begin
  368. {ok, Fd} = file:open(F, [read]),
  369. FindFirst(FindFirst, Fd)
  370. end || F <- ErlFiles])),
  371. First = lists:flatten([begin
  372. {ok, Fd} = file:open("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(M) ++ ".erl", [read]),
  373. FindFirst(FindFirst, Fd)
  374. end || M <- First0, lists:member("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(M) ++ ".erl", ErlFiles)]) ++ First0,
  375. Write(["COMPILE_FIRST +=", [[" ", atom_to_list(M)] || M <- First,
  376. lists:member("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(M) ++ ".erl", ErlFiles)], "\n"])
  377. end(),
  378. Write("\n\nrebar_dep: preprocess pre-deps deps pre-app app\n"),
  379. Write("\npreprocess::\n"),
  380. Write("\npre-deps::\n"),
  381. Write("\npre-app::\n"),
  382. PatchHook = fun(Cmd) ->
  383. case Cmd of
  384. "make -C" ++ _ -> Escape(Cmd);
  385. "gmake -C" ++ _ -> Escape(Cmd);
  386. "make " ++ Cmd1 -> "make -f Makefile.orig.mk " ++ Escape(Cmd1);
  387. "gmake " ++ Cmd1 -> "gmake -f Makefile.orig.mk " ++ Escape(Cmd1);
  388. _ -> Escape(Cmd)
  389. end
  390. end,
  391. fun() ->
  392. case lists:keyfind(pre_hooks, 1, Conf) of
  393. false -> ok;
  394. {_, Hooks} ->
  395. [case H of
  396. {'get-deps', Cmd} ->
  397. Write("\npre-deps::\n\t" ++ PatchHook(Cmd) ++ "\n");
  398. {compile, Cmd} ->
  399. Write("\npre-app::\n\tCC=$$$$\(CC) " ++ PatchHook(Cmd) ++ "\n");
  400. {Regex, compile, Cmd} ->
  401. case rebar_utils:is_arch(Regex) of
  402. true -> Write("\npre-app::\n\tCC=$$$$\(CC) " ++ PatchHook(Cmd) ++ "\n");
  403. false -> ok
  404. end;
  405. _ -> ok
  406. end || H <- Hooks]
  407. end
  408. end(),
  409. ShellToMk = fun(V) ->
  410. re:replace(re:replace(V, "(\\\\$$$$)(\\\\w*)", "\\\\1(\\\\2)", [global]),
  411. "-Werror\\\\b", "", [{return, list}, global])
  412. end,
  413. PortSpecs = fun() ->
  414. case lists:keyfind(port_specs, 1, Conf) of
  415. false ->
  416. case filelib:is_dir("$(DEPS_DIR)/$(1)/c_src") of
  417. false -> [];
  418. true ->
  419. [{"priv/" ++ proplists:get_value(so_name, Conf, "$(1)_drv.so"),
  420. proplists:get_value(port_sources, Conf, ["c_src/*.c"]), []}]
  421. end;
  422. {_, Specs} ->
  423. lists:flatten([case S of
  424. {Output, Input} -> {ShellToMk(Output), Input, []};
  425. {Regex, Output, Input} ->
  426. case rebar_utils:is_arch(Regex) of
  427. true -> {ShellToMk(Output), Input, []};
  428. false -> []
  429. end;
  430. {Regex, Output, Input, [{env, Env}]} ->
  431. case rebar_utils:is_arch(Regex) of
  432. true -> {ShellToMk(Output), Input, Env};
  433. false -> []
  434. end
  435. end || S <- Specs])
  436. end
  437. end(),
  438. PortSpecWrite = fun (Text) ->
  439. file:write_file("$(DEPS_DIR)/$(1)/c_src/Makefile.erlang.mk", Text, [append])
  440. end,
  441. case PortSpecs of
  442. [] -> ok;
  443. _ ->
  444. Write("\npre-app::\n\t$$$$\(MAKE) -f c_src/Makefile.erlang.mk\n"),
  445. PortSpecWrite(io_lib:format("ERL_CFLAGS = -finline-functions -Wall -fPIC -I ~s/erts-~s/include -I ~s\n",
  446. [code:root_dir(), erlang:system_info(version), code:lib_dir(erl_interface, include)])),
  447. PortSpecWrite(io_lib:format("ERL_LDFLAGS = -L ~s -lerl_interface -lei\n",
  448. [code:lib_dir(erl_interface, lib)])),
  449. [PortSpecWrite(["\n", E, "\n"]) || E <- OsEnv],
  450. FilterEnv = fun(Env) ->
  451. lists:flatten([case E of
  452. {_, _} -> E;
  453. {Regex, K, V} ->
  454. case rebar_utils:is_arch(Regex) of
  455. true -> {K, V};
  456. false -> []
  457. end
  458. end || E <- Env])
  459. end,
  460. MergeEnv = fun(Env) ->
  461. lists:foldl(fun ({K, V}, Acc) ->
  462. case lists:keyfind(K, 1, Acc) of
  463. false -> [{K, rebar_utils:expand_env_variable(V, K, "")}|Acc];
  464. {_, V0} -> [{K, rebar_utils:expand_env_variable(V, K, V0)}|Acc]
  465. end
  466. end, [], Env)
  467. end,
  468. PortEnv = case lists:keyfind(port_env, 1, Conf) of
  469. false -> [];
  470. {_, PortEnv0} -> FilterEnv(PortEnv0)
  471. end,
  472. PortSpec = fun ({Output, Input0, Env}) ->
  473. filelib:ensure_dir("$(DEPS_DIR)/$(1)/" ++ Output),
  474. Input = [[" ", I] || I <- Input0],
  475. PortSpecWrite([
  476. [["\n", K, " = ", ShellToMk(V)] || {K, V} <- lists:reverse(MergeEnv(PortEnv))],
  477. case $(PLATFORM) of
  478. darwin -> "\n\nLDFLAGS += -flat_namespace -undefined suppress";
  479. _ -> ""
  480. end,
  481. "\n\nall:: ", Output, "\n\n",
  482. "%.o: %.c\n\t$$$$\(CC) -c -o $$$$\@ $$$$\< $$$$\(CFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
  483. "%.o: %.C\n\t$$$$\(CXX) -c -o $$$$\@ $$$$\< $$$$\(CXXFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
  484. "%.o: %.cc\n\t$$$$\(CXX) -c -o $$$$\@ $$$$\< $$$$\(CXXFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
  485. "%.o: %.cpp\n\t$$$$\(CXX) -c -o $$$$\@ $$$$\< $$$$\(CXXFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
  486. [[Output, ": ", K, " = ", ShellToMk(V), "\n"] || {K, V} <- lists:reverse(MergeEnv(FilterEnv(Env)))],
  487. Output, ": $$$$\(foreach ext,.c .C .cc .cpp,",
  488. "$$$$\(patsubst %$$$$\(ext),%.o,$$$$\(filter %$$$$\(ext),$$$$\(wildcard", Input, "))))\n",
  489. "\t$$$$\(CC) -o $$$$\@ $$$$\? $$$$\(LDFLAGS) $$$$\(ERL_LDFLAGS) $$$$\(DRV_LDFLAGS) $$$$\(EXE_LDFLAGS)",
  490. case filename:extension(Output) of
  491. [] -> "\n";
  492. _ -> " -shared\n"
  493. end])
  494. end,
  495. [PortSpec(S) || S <- PortSpecs]
  496. end,
  497. Write("\ninclude ../../erlang.mk"),
  498. RunPlugin = fun(Plugin, Step) ->
  499. case erlang:function_exported(Plugin, Step, 2) of
  500. false -> ok;
  501. true ->
  502. c:cd("$(DEPS_DIR)/$(1)/"),
  503. Ret = Plugin:Step({config, "", Conf, dict:new(), dict:new(), dict:new(),
  504. dict:store(base_dir, "", dict:new())}, undefined),
  505. io:format("rebar plugin ~p step ~p ret ~p~n", [Plugin, Step, Ret])
  506. end
  507. end,
  508. fun() ->
  509. case lists:keyfind(plugins, 1, Conf) of
  510. false -> ok;
  511. {_, Plugins} ->
  512. [begin
  513. case lists:keyfind(deps, 1, Conf) of
  514. false -> ok;
  515. {_, Deps} ->
  516. case lists:keyfind(P, 1, Deps) of
  517. false -> ok;
  518. _ ->
  519. Path = "$(DEPS_DIR)/" ++ atom_to_list(P),
  520. io:format("~s", [os:cmd("$(MAKE) -C $(DEPS_DIR)/$(1) " ++ Path)]),
  521. io:format("~s", [os:cmd("$(MAKE) -C " ++ Path ++ " IS_DEP=1")]),
  522. code:add_patha(Path ++ "/ebin")
  523. end
  524. end
  525. end || P <- Plugins],
  526. [case code:load_file(P) of
  527. {module, P} -> ok;
  528. _ ->
  529. case lists:keyfind(plugin_dir, 1, Conf) of
  530. false -> ok;
  531. {_, PluginsDir} ->
  532. ErlFile = "$(DEPS_DIR)/$(1)/" ++ PluginsDir ++ "/" ++ atom_to_list(P) ++ ".erl",
  533. {ok, P, Bin} = compile:file(ErlFile, [binary]),
  534. {module, P} = code:load_binary(P, ErlFile, Bin)
  535. end
  536. end || P <- Plugins],
  537. [RunPlugin(P, preprocess) || P <- Plugins],
  538. [RunPlugin(P, pre_compile) || P <- Plugins]
  539. end
  540. end(),
  541. halt()
  542. endef
  543. define dep_autopatch_app.erl
  544. UpdateModules = fun(App) ->
  545. case filelib:is_regular(App) of
  546. false -> ok;
  547. true ->
  548. {ok, [{application, $(1), L0}]} = file:consult(App),
  549. Mods = filelib:fold_files("$(DEPS_DIR)/$(1)/src", "\\\\.erl$$$$", true,
  550. fun (F, Acc) -> [list_to_atom(filename:rootname(filename:basename(F)))|Acc] end, []),
  551. L = lists:keystore(modules, 1, L0, {modules, Mods}),
  552. ok = file:write_file(App, io_lib:format("~p.~n", [{application, $(1), L}]))
  553. end
  554. end,
  555. UpdateModules("$(DEPS_DIR)/$(1)/ebin/$(1).app"),
  556. halt()
  557. endef
  558. define dep_autopatch_appsrc.erl
  559. AppSrcOut = "$(DEPS_DIR)/$(1)/src/$(1).app.src",
  560. AppSrcIn = case filelib:is_regular(AppSrcOut) of false -> "$(DEPS_DIR)/$(1)/ebin/$(1).app"; true -> AppSrcOut end,
  561. case filelib:is_regular(AppSrcIn) of
  562. false -> ok;
  563. true ->
  564. {ok, [{application, $(1), L0}]} = file:consult(AppSrcIn),
  565. L1 = lists:keystore(modules, 1, L0, {modules, []}),
  566. L2 = case lists:keyfind(vsn, 1, L1) of {vsn, git} -> lists:keyreplace(vsn, 1, L1, {vsn, "git"}); _ -> L1 end,
  567. ok = file:write_file(AppSrcOut, io_lib:format("~p.~n", [{application, $(1), L2}])),
  568. case AppSrcOut of AppSrcIn -> ok; _ -> ok = file:delete(AppSrcIn) end
  569. end,
  570. halt()
  571. endef
  572. define dep_fetch
  573. if [ "$$$$VS" = "git" ]; then \
  574. git clone -q -n -- $$$$REPO $(DEPS_DIR)/$(1); \
  575. cd $(DEPS_DIR)/$(1) && git checkout -q $$$$COMMIT; \
  576. elif [ "$$$$VS" = "hg" ]; then \
  577. hg clone -q -U $$$$REPO $(DEPS_DIR)/$(1); \
  578. cd $(DEPS_DIR)/$(1) && hg update -q $$$$COMMIT; \
  579. elif [ "$$$$VS" = "svn" ]; then \
  580. svn checkout -q $$$$REPO $(DEPS_DIR)/$(1); \
  581. elif [ "$$$$VS" = "cp" ]; then \
  582. cp -R $$$$REPO $(DEPS_DIR)/$(1); \
  583. else \
  584. echo "Unknown or invalid dependency: $(1). Please consult the erlang.mk README for instructions." >&2; \
  585. exit 78; \
  586. fi
  587. endef
  588. define dep_target
  589. $(DEPS_DIR)/$(1):
  590. @mkdir -p $(DEPS_DIR)
  591. ifeq (,$(dep_$(1)))
  592. @if [ ! -f $(PKG_FILE2) ]; then $(call core_http_get,$(PKG_FILE2),$(PKG_FILE_URL)); fi
  593. $(dep_verbose) DEPPKG=$$$$(awk 'BEGIN { FS = "\t" }; $$$$1 == "$(1)" { print $$$$2 " " $$$$3 " " $$$$4 }' $(PKG_FILE2);); \
  594. VS=$$$$(echo $$$$DEPPKG | cut -d " " -f1); \
  595. REPO=$$$$(echo $$$$DEPPKG | cut -d " " -f2); \
  596. COMMIT=$$$$(echo $$$$DEPPKG | cut -d " " -f3); \
  597. $(call dep_fetch,$(1))
  598. else
  599. ifeq (1,$(words $(dep_$(1))))
  600. $(dep_verbose) VS=git; \
  601. REPO=$(dep_$(1)); \
  602. COMMIT=master; \
  603. $(call dep_fetch,$(1))
  604. else
  605. ifeq (2,$(words $(dep_$(1))))
  606. $(dep_verbose) VS=git; \
  607. REPO=$(word 1,$(dep_$(1))); \
  608. COMMIT=$(word 2,$(dep_$(1))); \
  609. $(call dep_fetch,$(1))
  610. else
  611. $(dep_verbose) VS=$(word 1,$(dep_$(1))); \
  612. REPO=$(word 2,$(dep_$(1))); \
  613. COMMIT=$(word 3,$(dep_$(1))); \
  614. $(call dep_fetch,$(1))
  615. endif
  616. endif
  617. endif
  618. @if [ -f $(DEPS_DIR)/$(1)/configure.ac -o -f $(DEPS_DIR)/$(1)/configure.in ]; then \
  619. echo " AUTO " $(1); \
  620. cd $(DEPS_DIR)/$(1) && autoreconf -Wall -vif -I m4; \
  621. fi
  622. -@if [ -f $(DEPS_DIR)/$(1)/configure ]; then \
  623. echo " CONF " $(1); \
  624. cd $(DEPS_DIR)/$(1) && ./configure; \
  625. fi
  626. ifeq ($(filter $(1),$(NO_AUTOPATCH)),)
  627. @$(call dep_autopatch,$(1))
  628. endif
  629. endef
  630. $(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep))))
  631. distclean-deps:
  632. $(gen_verbose) rm -rf $(DEPS_DIR)
  633. # Packages related targets.
  634. $(PKG_FILE2):
  635. @$(call core_http_get,$(PKG_FILE2),$(PKG_FILE_URL))
  636. pkg-list: $(PKG_FILE2)
  637. @cat $(PKG_FILE2) | awk 'BEGIN { FS = "\t" }; { print \
  638. "Name:\t\t" $$1 "\n" \
  639. "Repository:\t" $$3 "\n" \
  640. "Website:\t" $$5 "\n" \
  641. "Description:\t" $$6 "\n" }'
  642. ifdef q
  643. pkg-search: $(PKG_FILE2)
  644. @cat $(PKG_FILE2) | grep -i ${q} | awk 'BEGIN { FS = "\t" }; { print \
  645. "Name:\t\t" $$1 "\n" \
  646. "Repository:\t" $$3 "\n" \
  647. "Website:\t" $$5 "\n" \
  648. "Description:\t" $$6 "\n" }'
  649. else
  650. pkg-search:
  651. $(error Usage: $(MAKE) pkg-search q=STRING)
  652. endif
  653. ifeq ($(PKG_FILE2),$(CURDIR)/.erlang.mk.packages.v2)
  654. distclean-pkg:
  655. $(gen_verbose) rm -f $(PKG_FILE2)
  656. endif
  657. help::
  658. @printf "%s\n" "" \
  659. "Package-related targets:" \
  660. " pkg-list List all known packages" \
  661. " pkg-search q=STRING Search for STRING in the package index"
  662. # Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
  663. # This file is part of erlang.mk and subject to the terms of the ISC License.
  664. # Verbosity.
  665. proto_verbose_0 = @echo " PROTO " $(filter %.proto,$(?F));
  666. proto_verbose = $(proto_verbose_$(V))
  667. # Core targets.
  668. define compile_proto
  669. @mkdir -p ebin/ include/
  670. $(proto_verbose) $(call erlang,$(call compile_proto.erl,$(1)))
  671. $(proto_verbose) erlc +debug_info -o ebin/ ebin/*.erl
  672. @rm ebin/*.erl
  673. endef
  674. define compile_proto.erl
  675. [begin
  676. Dir = filename:dirname(filename:dirname(F)),
  677. protobuffs_compile:generate_source(F,
  678. [{output_include_dir, Dir ++ "/include"},
  679. {output_src_dir, Dir ++ "/ebin"}])
  680. end || F <- string:tokens("$(1)", " ")],
  681. halt().
  682. endef
  683. ifneq ($(wildcard src/),)
  684. ebin/$(PROJECT).app:: $(shell find src -type f -name \*.proto 2>/dev/null)
  685. $(if $(strip $?),$(call compile_proto,$?))
  686. endif
  687. # Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
  688. # This file is part of erlang.mk and subject to the terms of the ISC License.
  689. .PHONY: clean-app
  690. # Configuration.
  691. ERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \
  692. +warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec
  693. COMPILE_FIRST ?=
  694. COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST)))
  695. ERLC_EXCLUDE ?=
  696. ERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE)))
  697. ERLC_MIB_OPTS ?=
  698. COMPILE_MIB_FIRST ?=
  699. COMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST)))
  700. # Verbosity.
  701. appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src;
  702. appsrc_verbose = $(appsrc_verbose_$(V))
  703. erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\
  704. $(filter %.erl %.core,$(?F)));
  705. erlc_verbose = $(erlc_verbose_$(V))
  706. xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F));
  707. xyrl_verbose = $(xyrl_verbose_$(V))
  708. asn1_verbose_0 = @echo " ASN1 " $(filter %.asn1,$(?F));
  709. asn1_verbose = $(asn1_verbose_$(V))
  710. mib_verbose_0 = @echo " MIB " $(filter %.bin %.mib,$(?F));
  711. mib_verbose = $(mib_verbose_$(V))
  712. # Targets.
  713. ifeq ($(wildcard ebin/test),)
  714. app:: app-build
  715. else
  716. app:: clean app-build
  717. endif
  718. app-build: erlc-include ebin/$(PROJECT).app
  719. $(eval MODULES := $(shell find ebin -type f -name \*.beam \
  720. | sed "s/ebin\//'/;s/\.beam/',/" | sed '$$s/.$$//'))
  721. @if [ -z "$$(grep -E '^[^%]*{modules,' src/$(PROJECT).app.src)" ]; then \
  722. echo "Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk README for instructions." >&2; \
  723. exit 1; \
  724. fi
  725. $(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null || true))
  726. $(appsrc_verbose) cat src/$(PROJECT).app.src \
  727. | sed "s/{modules,[[:space:]]*\[\]}/{modules, \[$(MODULES)\]}/" \
  728. | sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(GITDESCRIBE)\"}/" \
  729. > ebin/$(PROJECT).app
  730. erlc-include:
  731. -@if [ -d ebin/ ]; then \
  732. find include/ src/ -type f -name \*.hrl -newer ebin -exec touch $(shell find src/ -type f -name "*.erl") \; 2>/dev/null || printf ''; \
  733. fi
  734. define compile_erl
  735. $(erlc_verbose) erlc -v $(ERLC_OPTS) -o ebin/ \
  736. -pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),\
  737. $(COMPILE_FIRST_PATHS) $(1))
  738. endef
  739. define compile_xyrl
  740. $(xyrl_verbose) erlc -v -o ebin/ $(1)
  741. $(xyrl_verbose) erlc $(ERLC_OPTS) -o ebin/ ebin/*.erl
  742. @rm ebin/*.erl
  743. endef
  744. define compile_asn1
  745. $(asn1_verbose) erlc -v -I include/ -o ebin/ $(1)
  746. @mv ebin/*.hrl include/
  747. @mv ebin/*.asn1db include/
  748. @rm ebin/*.erl
  749. endef
  750. define compile_mib
  751. $(mib_verbose) erlc -v $(ERLC_MIB_OPTS) -o priv/mibs/ \
  752. -I priv/mibs/ $(COMPILE_MIB_FIRST_PATHS) $(1)
  753. $(mib_verbose) erlc -o include/ -- priv/mibs/*.bin
  754. endef
  755. ifneq ($(wildcard src/),)
  756. ebin/$(PROJECT).app::
  757. @mkdir -p ebin/
  758. ifneq ($(wildcard asn1/),)
  759. ebin/$(PROJECT).app:: $(shell find asn1 -type f -name \*.asn1)
  760. @mkdir -p include
  761. $(if $(strip $?),$(call compile_asn1,$?))
  762. endif
  763. ifneq ($(wildcard mibs/),)
  764. ebin/$(PROJECT).app:: $(shell find mibs -type f -name \*.mib)
  765. @mkdir -p priv/mibs/ include
  766. $(if $(strip $?),$(call compile_mib,$?))
  767. endif
  768. ebin/$(PROJECT).app:: $(shell find src -type f -name \*.erl -o -name \*.core)
  769. $(if $(strip $?),$(call compile_erl,$?))
  770. ebin/$(PROJECT).app:: $(shell find src -type f -name \*.xrl -o -name \*.yrl)
  771. $(if $(strip $?),$(call compile_xyrl,$?))
  772. endif
  773. clean:: clean-app
  774. clean-app:
  775. $(gen_verbose) rm -rf ebin/ priv/mibs/ \
  776. $(addprefix include/,$(addsuffix .hrl,$(notdir $(basename $(wildcard mibs/*.mib)))))
  777. # Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
  778. # This file is part of erlang.mk and subject to the terms of the ISC License.
  779. .PHONY: docs-deps
  780. # Configuration.
  781. ALL_DOC_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DOC_DEPS))
  782. # Targets.
  783. $(foreach dep,$(DOC_DEPS),$(eval $(call dep_target,$(dep))))
  784. ifneq ($(SKIP_DEPS),)
  785. doc-deps:
  786. else
  787. doc-deps: $(ALL_DOC_DEPS_DIRS)
  788. @for dep in $(ALL_DOC_DEPS_DIRS) ; do $(MAKE) -C $$dep; done
  789. endif
  790. # Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
  791. # This file is part of erlang.mk and subject to the terms of the ISC License.
  792. .PHONY: test-deps test-dir test-build clean-test-dir
  793. # Configuration.
  794. TEST_DIR ?= $(CURDIR)/test
  795. ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS))
  796. TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard
  797. TEST_ERLC_OPTS += -DTEST=1
  798. # Targets.
  799. $(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep))))
  800. ifneq ($(SKIP_DEPS),)
  801. test-deps:
  802. else
  803. test-deps: $(ALL_TEST_DEPS_DIRS)
  804. @for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep; done
  805. endif
  806. ifneq ($(strip $(TEST_DIR)),)
  807. test-dir:
  808. $(gen_verbose) erlc -v $(TEST_ERLC_OPTS) -I include/ -o $(TEST_DIR) \
  809. $(wildcard $(TEST_DIR)/*.erl $(TEST_DIR)/*/*.erl) -pa ebin/
  810. endif
  811. ifeq ($(wildcard ebin/test),)
  812. test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)
  813. test-build:: clean deps test-deps
  814. @$(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
  815. $(gen_verbose) touch ebin/test
  816. else
  817. test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)
  818. test-build:: deps test-deps
  819. @$(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
  820. endif
  821. clean:: clean-test-dir
  822. clean-test-dir:
  823. ifneq ($(wildcard $(TEST_DIR)/*.beam),)
  824. $(gen_verbose) rm -f $(TEST_DIR)/*.beam
  825. endif
  826. # Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
  827. # This file is part of erlang.mk and subject to the terms of the ISC License.
  828. .PHONY: asciidoc asciidoc-guide asciidoc-manual install-asciidoc distclean-asciidoc
  829. MAN_INSTALL_PATH ?= /usr/local/share/man
  830. MAN_SECTIONS ?= 3 7
  831. docs:: asciidoc
  832. asciidoc: distclean-asciidoc doc-deps asciidoc-guide asciidoc-manual
  833. ifeq ($(wildcard doc/src/guide/book.asciidoc),)
  834. asciidoc-guide:
  835. else
  836. asciidoc-guide:
  837. a2x -v -f pdf doc/src/guide/book.asciidoc && mv doc/src/guide/book.pdf doc/guide.pdf
  838. a2x -v -f chunked doc/src/guide/book.asciidoc && mv doc/src/guide/book.chunked/ doc/html/
  839. endif
  840. ifeq ($(wildcard doc/src/manual/*.asciidoc),)
  841. asciidoc-manual:
  842. else
  843. asciidoc-manual:
  844. for f in doc/src/manual/*.asciidoc ; do \
  845. a2x -v -f manpage $$f ; \
  846. done
  847. for s in $(MAN_SECTIONS); do \
  848. mkdir -p doc/man$$s/ ; \
  849. mv doc/src/manual/*.$$s doc/man$$s/ ; \
  850. gzip doc/man$$s/*.$$s ; \
  851. done
  852. install-docs:: install-asciidoc
  853. install-asciidoc: asciidoc-manual
  854. for s in $(MAN_SECTIONS); do \
  855. mkdir -p $(MAN_INSTALL_PATH)/man$$s/ ; \
  856. install -g 0 -o 0 -m 0644 doc/man$$s/*.gz $(MAN_INSTALL_PATH)/man$$s/ ; \
  857. done
  858. endif
  859. distclean:: distclean-asciidoc
  860. distclean-asciidoc:
  861. $(gen_verbose) rm -rf doc/html/ doc/guide.pdf doc/man3/ doc/man7/
  862. # Copyright (c) 2014-2015, Loïc Hoguin <essen@ninenines.eu>
  863. # This file is part of erlang.mk and subject to the terms of the ISC License.
  864. .PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates
  865. # Core targets.
  866. help::
  867. @printf "%s\n" "" \
  868. "Bootstrap targets:" \
  869. " bootstrap Generate a skeleton of an OTP application" \
  870. " bootstrap-lib Generate a skeleton of an OTP library" \
  871. " bootstrap-rel Generate the files needed to build a release" \
  872. " new t=TPL n=NAME Generate a module NAME based on the template TPL" \
  873. " list-templates List available templates"
  874. # Bootstrap templates.
  875. define bs_appsrc
  876. {application, $(PROJECT), [
  877. {description, ""},
  878. {vsn, "0.1.0"},
  879. {id, "git"},
  880. {modules, []},
  881. {registered, []},
  882. {applications, [
  883. kernel,
  884. stdlib
  885. ]},
  886. {mod, {$(PROJECT)_app, []}},
  887. {env, []}
  888. ]}.
  889. endef
  890. define bs_appsrc_lib
  891. {application, $(PROJECT), [
  892. {description, ""},
  893. {vsn, "0.1.0"},
  894. {id, "git"},
  895. {modules, []},
  896. {registered, []},
  897. {applications, [
  898. kernel,
  899. stdlib
  900. ]}
  901. ]}.
  902. endef
  903. define bs_Makefile
  904. PROJECT = $(PROJECT)
  905. include erlang.mk
  906. endef
  907. define bs_app
  908. -module($(PROJECT)_app).
  909. -behaviour(application).
  910. -export([start/2]).
  911. -export([stop/1]).
  912. start(_Type, _Args) ->
  913. $(PROJECT)_sup:start_link().
  914. stop(_State) ->
  915. ok.
  916. endef
  917. define bs_relx_config
  918. {release, {$(PROJECT)_release, "1"}, [$(PROJECT)]}.
  919. {extended_start_script, true}.
  920. {sys_config, "rel/sys.config"}.
  921. {vm_args, "rel/vm.args"}.
  922. endef
  923. define bs_sys_config
  924. [
  925. ].
  926. endef
  927. define bs_vm_args
  928. -name $(PROJECT)@127.0.0.1
  929. -setcookie $(PROJECT)
  930. -heart
  931. endef
  932. # Normal templates.
  933. define tpl_supervisor
  934. -module($(n)).
  935. -behaviour(supervisor).
  936. -export([start_link/0]).
  937. -export([init/1]).
  938. start_link() ->
  939. supervisor:start_link({local, ?MODULE}, ?MODULE, []).
  940. init([]) ->
  941. Procs = [],
  942. {ok, {{one_for_one, 1, 5}, Procs}}.
  943. endef
  944. define tpl_gen_server
  945. -module($(n)).
  946. -behaviour(gen_server).
  947. %% API.
  948. -export([start_link/0]).
  949. %% gen_server.
  950. -export([init/1]).
  951. -export([handle_call/3]).
  952. -export([handle_cast/2]).
  953. -export([handle_info/2]).
  954. -export([terminate/2]).
  955. -export([code_change/3]).
  956. -record(state, {
  957. }).
  958. %% API.
  959. -spec start_link() -> {ok, pid()}.
  960. start_link() ->
  961. gen_server:start_link(?MODULE, [], []).
  962. %% gen_server.
  963. init([]) ->
  964. {ok, #state{}}.
  965. handle_call(_Request, _From, State) ->
  966. {reply, ignored, State}.
  967. handle_cast(_Msg, State) ->
  968. {noreply, State}.
  969. handle_info(_Info, State) ->
  970. {noreply, State}.
  971. terminate(_Reason, _State) ->
  972. ok.
  973. code_change(_OldVsn, State, _Extra) ->
  974. {ok, State}.
  975. endef
  976. define tpl_cowboy_http
  977. -module($(n)).
  978. -behaviour(cowboy_http_handler).
  979. -export([init/3]).
  980. -export([handle/2]).
  981. -export([terminate/3]).
  982. -record(state, {
  983. }).
  984. init(_, Req, _Opts) ->
  985. {ok, Req, #state{}}.
  986. handle(Req, State=#state{}) ->
  987. {ok, Req2} = cowboy_req:reply(200, Req),
  988. {ok, Req2, State}.
  989. terminate(_Reason, _Req, _State) ->
  990. ok.
  991. endef
  992. define tpl_gen_fsm
  993. -module($(n)).
  994. -behaviour(gen_fsm).
  995. %% API.
  996. -export([start_link/0]).
  997. %% gen_fsm.
  998. -export([init/1]).
  999. -export([state_name/2]).
  1000. -export([handle_event/3]).
  1001. -export([state_name/3]).
  1002. -export([handle_sync_event/4]).
  1003. -export([handle_info/3]).
  1004. -export([terminate/3]).
  1005. -export([code_change/4]).
  1006. -record(state, {
  1007. }).
  1008. %% API.
  1009. -spec start_link() -> {ok, pid()}.
  1010. start_link() ->
  1011. gen_fsm:start_link(?MODULE, [], []).
  1012. %% gen_fsm.
  1013. init([]) ->
  1014. {ok, state_name, #state{}}.
  1015. state_name(_Event, StateData) ->
  1016. {next_state, state_name, StateData}.
  1017. handle_event(_Event, StateName, StateData) ->
  1018. {next_state, StateName, StateData}.
  1019. state_name(_Event, _From, StateData) ->
  1020. {reply, ignored, state_name, StateData}.
  1021. handle_sync_event(_Event, _From, StateName, StateData) ->
  1022. {reply, ignored, StateName, StateData}.
  1023. handle_info(_Info, StateName, StateData) ->
  1024. {next_state, StateName, StateData}.
  1025. terminate(_Reason, _StateName, _StateData) ->
  1026. ok.
  1027. code_change(_OldVsn, StateName, StateData, _Extra) ->
  1028. {ok, StateName, StateData}.
  1029. endef
  1030. define tpl_cowboy_loop
  1031. -module($(n)).
  1032. -behaviour(cowboy_loop_handler).
  1033. -export([init/3]).
  1034. -export([info/3]).
  1035. -export([terminate/3]).
  1036. -record(state, {
  1037. }).
  1038. init(_, Req, _Opts) ->
  1039. {loop, Req, #state{}, 5000, hibernate}.
  1040. info(_Info, Req, State) ->
  1041. {loop, Req, State, hibernate}.
  1042. terminate(_Reason, _Req, _State) ->
  1043. ok.
  1044. endef
  1045. define tpl_cowboy_rest
  1046. -module($(n)).
  1047. -export([init/3]).
  1048. -export([content_types_provided/2]).
  1049. -export([get_html/2]).
  1050. init(_, _Req, _Opts) ->
  1051. {upgrade, protocol, cowboy_rest}.
  1052. content_types_provided(Req, State) ->
  1053. {[{{<<"text">>, <<"html">>, '*'}, get_html}], Req, State}.
  1054. get_html(Req, State) ->
  1055. {<<"<html><body>This is REST!</body></html>">>, Req, State}.
  1056. endef
  1057. define tpl_cowboy_ws
  1058. -module($(n)).
  1059. -behaviour(cowboy_websocket_handler).
  1060. -export([init/3]).
  1061. -export([websocket_init/3]).
  1062. -export([websocket_handle/3]).
  1063. -export([websocket_info/3]).
  1064. -export([websocket_terminate/3]).
  1065. -record(state, {
  1066. }).
  1067. init(_, _, _) ->
  1068. {upgrade, protocol, cowboy_websocket}.
  1069. websocket_init(_, Req, _Opts) ->
  1070. Req2 = cowboy_req:compact(Req),
  1071. {ok, Req2, #state{}}.
  1072. websocket_handle({text, Data}, Req, State) ->
  1073. {reply, {text, Data}, Req, State};
  1074. websocket_handle({binary, Data}, Req, State) ->
  1075. {reply, {binary, Data}, Req, State};
  1076. websocket_handle(_Frame, Req, State) ->
  1077. {ok, Req, State}.
  1078. websocket_info(_Info, Req, State) ->
  1079. {ok, Req, State}.
  1080. websocket_terminate(_Reason, _Req, _State) ->
  1081. ok.
  1082. endef
  1083. define tpl_ranch_protocol
  1084. -module($(n)).
  1085. -behaviour(ranch_protocol).
  1086. -export([start_link/4]).
  1087. -export([init/4]).
  1088. -type opts() :: [].
  1089. -export_type([opts/0]).
  1090. -record(state, {
  1091. socket :: inet:socket(),
  1092. transport :: module()
  1093. }).
  1094. start_link(Ref, Socket, Transport, Opts) ->
  1095. Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
  1096. {ok, Pid}.
  1097. -spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok.
  1098. init(Ref, Socket, Transport, _Opts) ->
  1099. ok = ranch:accept_ack(Ref),
  1100. loop(#state{socket=Socket, transport=Transport}).
  1101. loop(State) ->
  1102. loop(State).
  1103. endef
  1104. # Plugin-specific targets.
  1105. define render_template
  1106. @echo "$${$(1)}" > $(2)
  1107. endef
  1108. $(foreach template,$(filter bs_%,$(.VARIABLES)),$(eval export $(template)))
  1109. $(foreach template,$(filter tpl_%,$(.VARIABLES)),$(eval export $(template)))
  1110. bootstrap:
  1111. ifneq ($(wildcard src/),)
  1112. $(error Error: src/ directory already exists)
  1113. endif
  1114. $(call render_template,bs_Makefile,Makefile)
  1115. @mkdir src/
  1116. $(call render_template,bs_appsrc,src/$(PROJECT).app.src)
  1117. $(call render_template,bs_app,src/$(PROJECT)_app.erl)
  1118. $(eval n := $(PROJECT)_sup)
  1119. $(call render_template,tpl_supervisor,src/$(PROJECT)_sup.erl)
  1120. bootstrap-lib:
  1121. ifneq ($(wildcard src/),)
  1122. $(error Error: src/ directory already exists)
  1123. endif
  1124. $(call render_template,bs_Makefile,Makefile)
  1125. @mkdir src/
  1126. $(call render_template,bs_appsrc_lib,src/$(PROJECT).app.src)
  1127. bootstrap-rel:
  1128. ifneq ($(wildcard relx.config),)
  1129. $(error Error: relx.config already exists)
  1130. endif
  1131. ifneq ($(wildcard rel/),)
  1132. $(error Error: rel/ directory already exists)
  1133. endif
  1134. $(call render_template,bs_relx_config,relx.config)
  1135. @mkdir rel/
  1136. $(call render_template,bs_sys_config,rel/sys.config)
  1137. $(call render_template,bs_vm_args,rel/vm.args)
  1138. new:
  1139. ifeq ($(wildcard src/),)
  1140. $(error Error: src/ directory does not exist)
  1141. endif
  1142. ifndef t
  1143. $(error Usage: $(MAKE) new t=TEMPLATE n=NAME)
  1144. endif
  1145. ifndef tpl_$(t)
  1146. $(error Unknown template)
  1147. endif
  1148. ifndef n
  1149. $(error Usage: $(MAKE) new t=TEMPLATE n=NAME)
  1150. endif
  1151. $(call render_template,tpl_$(t),src/$(n).erl)
  1152. list-templates:
  1153. @echo Available templates: $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES))))
  1154. # Copyright (c) 2014-2015, Loïc Hoguin <essen@ninenines.eu>
  1155. # This file is part of erlang.mk and subject to the terms of the ISC License.
  1156. .PHONY: clean-c_src distclean-c_src-env
  1157. # Configuration.
  1158. C_SRC_DIR ?= $(CURDIR)/c_src
  1159. C_SRC_ENV ?= $(C_SRC_DIR)/env.mk
  1160. C_SRC_OUTPUT ?= $(CURDIR)/priv/$(PROJECT).so
  1161. C_SRC_TYPE ?= shared
  1162. # System type and C compiler/flags.
  1163. ifeq ($(PLATFORM),darwin)
  1164. CC ?= cc
  1165. CFLAGS ?= -O3 -std=c99 -arch x86_64 -finline-functions -Wall -Wmissing-prototypes
  1166. CXXFLAGS ?= -O3 -arch x86_64 -finline-functions -Wall
  1167. LDFLAGS ?= -arch x86_64 -flat_namespace -undefined suppress
  1168. else ifeq ($(PLATFORM),freebsd)
  1169. CC ?= cc
  1170. CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
  1171. CXXFLAGS ?= -O3 -finline-functions -Wall
  1172. else ifeq ($(PLATFORM),linux)
  1173. CC ?= gcc
  1174. CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
  1175. CXXFLAGS ?= -O3 -finline-functions -Wall
  1176. endif
  1177. CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
  1178. CXXFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
  1179. LDLIBS += -L $(ERL_INTERFACE_LIB_DIR) -lerl_interface -lei
  1180. ifeq ($(C_SRC_TYPE),shared)
  1181. LDFLAGS += -shared
  1182. endif
  1183. # Verbosity.
  1184. c_verbose_0 = @echo " C " $(?F);
  1185. c_verbose = $(c_verbose_$(V))
  1186. cpp_verbose_0 = @echo " CPP " $(?F);
  1187. cpp_verbose = $(cpp_verbose_$(V))
  1188. link_verbose_0 = @echo " LD " $(@F);
  1189. link_verbose = $(link_verbose_$(V))
  1190. # Targets.
  1191. ifeq ($(wildcard $(C_SRC_DIR)),)
  1192. else ifneq ($(wildcard $(C_SRC_DIR)/Makefile),)
  1193. app:: app-c_src
  1194. test-build:: app-c_src
  1195. app-c_src:
  1196. $(MAKE) -C $(C_SRC_DIR)
  1197. clean::
  1198. $(MAKE) -C $(C_SRC_DIR) clean
  1199. else
  1200. ifeq ($(SOURCES),)
  1201. SOURCES := $(shell find $(C_SRC_DIR) -type f \( -name "*.c" -o -name "*.C" -o -name "*.cc" -o -name "*.cpp" \))
  1202. endif
  1203. OBJECTS = $(addsuffix .o, $(basename $(SOURCES)))
  1204. COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c
  1205. COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c
  1206. app:: $(C_SRC_ENV) $(C_SRC_OUTPUT)
  1207. test-build:: $(C_SRC_ENV) $(C_SRC_OUTPUT)
  1208. $(C_SRC_OUTPUT): $(OBJECTS)
  1209. @mkdir -p priv/
  1210. $(link_verbose) $(CC) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $(C_SRC_OUTPUT)
  1211. %.o: %.c
  1212. $(COMPILE_C) $(OUTPUT_OPTION) $<
  1213. %.o: %.cc
  1214. $(COMPILE_CPP) $(OUTPUT_OPTION) $<
  1215. %.o: %.C
  1216. $(COMPILE_CPP) $(OUTPUT_OPTION) $<
  1217. %.o: %.cpp
  1218. $(COMPILE_CPP) $(OUTPUT_OPTION) $<
  1219. clean:: clean-c_src
  1220. clean-c_src:
  1221. $(gen_verbose) rm -f $(C_SRC_OUTPUT) $(OBJECTS)
  1222. endif
  1223. ifneq ($(wildcard $(C_SRC_DIR)),)
  1224. $(C_SRC_ENV):
  1225. @$(ERL) -eval "file:write_file(\"$(C_SRC_ENV)\", \
  1226. io_lib:format( \
  1227. \"ERTS_INCLUDE_DIR ?= ~s/erts-~s/include/~n\" \
  1228. \"ERL_INTERFACE_INCLUDE_DIR ?= ~s~n\" \
  1229. \"ERL_INTERFACE_LIB_DIR ?= ~s~n\", \
  1230. [code:root_dir(), erlang:system_info(version), \
  1231. code:lib_dir(erl_interface, include), \
  1232. code:lib_dir(erl_interface, lib)])), \
  1233. halt()."
  1234. distclean:: distclean-c_src-env
  1235. distclean-c_src-env:
  1236. $(gen_verbose) rm -f $(C_SRC_ENV)
  1237. -include $(C_SRC_ENV)
  1238. endif
  1239. # Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
  1240. # This file is part of erlang.mk and subject to the terms of the ISC License.
  1241. .PHONY: ci ci-setup distclean-kerl
  1242. KERL ?= $(CURDIR)/kerl
  1243. export KERL
  1244. KERL_URL ?= https://raw.githubusercontent.com/yrashk/kerl/master/kerl
  1245. OTP_GIT ?= https://github.com/erlang/otp
  1246. CI_INSTALL_DIR ?= $(HOME)/erlang
  1247. CI_OTP ?=
  1248. ifeq ($(strip $(CI_OTP)),)
  1249. ci::
  1250. else
  1251. ci:: $(KERL) $(addprefix ci-,$(CI_OTP))
  1252. ci-setup::
  1253. ci_verbose_0 = @echo " CI " $(1);
  1254. ci_verbose = $(ci_verbose_$(V))
  1255. define ci_target
  1256. ci-$(1): $(CI_INSTALL_DIR)/$(1)
  1257. -$(ci_verbose) \
  1258. PATH="$(CI_INSTALL_DIR)/$(1)/bin:$(PATH)" \
  1259. CI_OTP_RELEASE="$(1)" \
  1260. CT_OPTS="-label $(1)" \
  1261. $(MAKE) clean ci-setup tests
  1262. endef
  1263. $(foreach otp,$(CI_OTP),$(eval $(call ci_target,$(otp))))
  1264. define ci_otp_target
  1265. $(CI_INSTALL_DIR)/$(1):
  1266. $(KERL) build git $(OTP_GIT) $(1) $(1)
  1267. $(KERL) install $(1) $(CI_INSTALL_DIR)/$(1)
  1268. endef
  1269. $(foreach otp,$(CI_OTP),$(eval $(call ci_otp_target,$(otp))))
  1270. define kerl_fetch
  1271. $(call core_http_get,$(KERL),$(KERL_URL))
  1272. chmod +x $(KERL)
  1273. endef
  1274. $(KERL):
  1275. @$(call kerl_fetch)
  1276. help::
  1277. @printf "%s\n" "" \
  1278. "Continuous Integration targets:" \
  1279. " ci Run '$(MAKE) tests' on all configured Erlang versions." \
  1280. "" \
  1281. "The CI_OTP variable must be defined with the Erlang versions" \
  1282. "that must be tested. For example: CI_OTP = OTP-17.3.4 OTP-17.5.3"
  1283. distclean:: distclean-kerl
  1284. distclean-kerl:
  1285. $(gen_verbose) rm -rf $(KERL)
  1286. endif
  1287. # Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
  1288. # This file is part of erlang.mk and subject to the terms of the ISC License.
  1289. .PHONY: ct distclean-ct
  1290. # Configuration.
  1291. CT_OPTS ?=
  1292. ifneq ($(wildcard $(TEST_DIR)),)
  1293. CT_SUITES ?= $(sort $(subst _SUITE.erl,,$(shell find $(TEST_DIR) -type f -name \*_SUITE.erl -exec basename {} \;)))
  1294. else
  1295. CT_SUITES ?=
  1296. endif
  1297. # Core targets.
  1298. tests:: ct
  1299. distclean:: distclean-ct
  1300. help::
  1301. @printf "%s\n" "" \
  1302. "Common_test targets:" \
  1303. " ct Run all the common_test suites for this project" \
  1304. "" \
  1305. "All your common_test suites have their associated targets." \
  1306. "A suite named http_SUITE can be ran using the ct-http target."
  1307. # Plugin-specific targets.
  1308. CT_RUN = ct_run \
  1309. -no_auto_compile \
  1310. -noinput \
  1311. -pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin $(TEST_DIR) \
  1312. -dir $(TEST_DIR) \
  1313. -logdir $(CURDIR)/logs
  1314. ifeq ($(CT_SUITES),)
  1315. ct:
  1316. else
  1317. ct: test-build
  1318. @mkdir -p $(CURDIR)/logs/
  1319. $(gen_verbose) $(CT_RUN) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS)
  1320. endif
  1321. define ct_suite_target
  1322. ct-$(1): test-build
  1323. @mkdir -p $(CURDIR)/logs/
  1324. $(gen_verbose) $(CT_RUN) -suite $(addsuffix _SUITE,$(1)) $(CT_OPTS)
  1325. endef
  1326. $(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test))))
  1327. distclean-ct:
  1328. $(gen_verbose) rm -rf $(CURDIR)/logs/
  1329. # Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
  1330. # This file is part of erlang.mk and subject to the terms of the ISC License.
  1331. .PHONY: plt distclean-plt dialyze
  1332. # Configuration.
  1333. DIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt
  1334. export DIALYZER_PLT
  1335. PLT_APPS ?=
  1336. DIALYZER_DIRS ?= --src -r src
  1337. DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions \
  1338. -Wunmatched_returns # -Wunderspecs
  1339. # Core targets.
  1340. check:: dialyze
  1341. distclean:: distclean-plt
  1342. help::
  1343. @printf "%s\n" "" \
  1344. "Dialyzer targets:" \
  1345. " plt Build a PLT file for this project" \
  1346. " dialyze Analyze the project using Dialyzer"
  1347. # Plugin-specific targets.
  1348. $(DIALYZER_PLT): deps app
  1349. @dialyzer --build_plt --apps erts kernel stdlib $(PLT_APPS) $(ALL_DEPS_DIRS)
  1350. plt: $(DIALYZER_PLT)
  1351. distclean-plt:
  1352. $(gen_verbose) rm -f $(DIALYZER_PLT)
  1353. ifneq ($(wildcard $(DIALYZER_PLT)),)
  1354. dialyze:
  1355. else
  1356. dialyze: $(DIALYZER_PLT)
  1357. endif
  1358. @dialyzer --no_native $(DIALYZER_DIRS) $(DIALYZER_OPTS)
  1359. # Copyright (c) 2014, Juan Facorro <juan@inaka.net>
  1360. # This file is part of erlang.mk and subject to the terms of the ISC License.
  1361. .PHONY: elvis distclean-elvis
  1362. # Configuration.
  1363. ELVIS_CONFIG ?= $(CURDIR)/elvis.config
  1364. ELVIS ?= $(CURDIR)/elvis
  1365. export ELVIS
  1366. ELVIS_URL ?= https://github.com/inaka/elvis/releases/download/0.2.3/elvis
  1367. ELVIS_CONFIG_URL ?= https://github.com/inaka/elvis/releases/download/0.2.3/elvis.config
  1368. ELVIS_OPTS ?=
  1369. # Core targets.
  1370. help::
  1371. @printf "%s\n" "" \
  1372. "Elvis targets:" \
  1373. " elvis Run Elvis using the local elvis.config or download the default otherwise"
  1374. distclean:: distclean-elvis
  1375. # Plugin-specific targets.
  1376. $(ELVIS):
  1377. @$(call core_http_get,$(ELVIS),$(ELVIS_URL))
  1378. @chmod +x $(ELVIS)
  1379. $(ELVIS_CONFIG):
  1380. @$(call core_http_get,$(ELVIS_CONFIG),$(ELVIS_CONFIG_URL))
  1381. elvis: $(ELVIS) $(ELVIS_CONFIG)
  1382. @$(ELVIS) rock -c $(ELVIS_CONFIG) $(ELVIS_OPTS)
  1383. distclean-elvis:
  1384. $(gen_verbose) rm -rf $(ELVIS)
  1385. # Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
  1386. # This file is part of erlang.mk and subject to the terms of the ISC License.
  1387. # Configuration.
  1388. DTL_FULL_PATH ?= 0
  1389. # Verbosity.
  1390. dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F));
  1391. dtl_verbose = $(dtl_verbose_$(V))
  1392. # Core targets.
  1393. define compile_erlydtl
  1394. $(dtl_verbose) $(ERL) -pa ebin/ $(DEPS_DIR)/erlydtl/ebin/ -eval ' \
  1395. Compile = fun(F) -> \
  1396. S = fun (1) -> re:replace(filename:rootname(string:sub_string(F, 11), ".dtl"), "/", "_", [{return, list}, global]); \
  1397. (0) -> filename:basename(F, ".dtl") \
  1398. end, \
  1399. Module = list_to_atom(string:to_lower(S($(DTL_FULL_PATH))) ++ "_dtl"), \
  1400. {ok, _} = erlydtl:compile(F, Module, [{out_dir, "ebin/"}, return_errors, {doc_root, "templates"}]) \
  1401. end, \
  1402. _ = [Compile(F) || F <- string:tokens("$(1)", " ")], \
  1403. halt().'
  1404. endef
  1405. ifneq ($(wildcard src/),)
  1406. ebin/$(PROJECT).app:: $(shell find templates -type f -name \*.dtl 2>/dev/null)
  1407. $(if $(strip $?),$(call compile_erlydtl,$?))
  1408. endif
  1409. # Copyright (c) 2014 Dave Cottlehuber <dch@skunkwerks.at>
  1410. # This file is part of erlang.mk and subject to the terms of the ISC License.
  1411. .PHONY: distclean-escript escript
  1412. # Configuration.
  1413. ESCRIPT_NAME ?= $(PROJECT)
  1414. ESCRIPT_COMMENT ?= This is an -*- erlang -*- file
  1415. ESCRIPT_BEAMS ?= "ebin/*", "deps/*/ebin/*"
  1416. ESCRIPT_SYS_CONFIG ?= "rel/sys.config"
  1417. ESCRIPT_EMU_ARGS ?= -pa . \
  1418. -sasl errlog_type error \
  1419. -escript main $(ESCRIPT_NAME)
  1420. ESCRIPT_SHEBANG ?= /usr/bin/env escript
  1421. ESCRIPT_STATIC ?= "deps/*/priv/**", "priv/**"
  1422. # Core targets.
  1423. distclean:: distclean-escript
  1424. help::
  1425. @printf "%s\n" "" \
  1426. "Escript targets:" \
  1427. " escript Build an executable escript archive" \
  1428. # Plugin-specific targets.
  1429. # Based on https://github.com/synrc/mad/blob/master/src/mad_bundle.erl
  1430. # Copyright (c) 2013 Maxim Sokhatsky, Synrc Research Center
  1431. # Modified MIT License, https://github.com/synrc/mad/blob/master/LICENSE :
  1432. # Software may only be used for the great good and the true happiness of all
  1433. # sentient beings.
  1434. define ESCRIPT_RAW
  1435. 'Read = fun(F) -> {ok, B} = file:read_file(filename:absname(F)), B end,'\
  1436. 'Files = fun(L) -> A = lists:concat([filelib:wildcard(X)||X<- L ]),'\
  1437. ' [F || F <- A, not filelib:is_dir(F) ] end,'\
  1438. 'Squash = fun(L) -> [{filename:basename(F), Read(F) } || F <- L ] end,'\
  1439. 'Zip = fun(A, L) -> {ok,{_,Z}} = zip:create(A, L, [{compress,all},memory]), Z end,'\
  1440. 'Ez = fun(Escript) ->'\
  1441. ' Static = Files([$(ESCRIPT_STATIC)]),'\
  1442. ' Beams = Squash(Files([$(ESCRIPT_BEAMS), $(ESCRIPT_SYS_CONFIG)])),'\
  1443. ' Archive = Beams ++ [{ "static.gz", Zip("static.gz", Static)}],'\
  1444. ' escript:create(Escript, [ $(ESCRIPT_OPTIONS)'\
  1445. ' {archive, Archive, [memory]},'\
  1446. ' {shebang, "$(ESCRIPT_SHEBANG)"},'\
  1447. ' {comment, "$(ESCRIPT_COMMENT)"},'\
  1448. ' {emu_args, " $(ESCRIPT_EMU_ARGS)"}'\
  1449. ' ]),'\
  1450. ' file:change_mode(Escript, 8#755)'\
  1451. 'end,'\
  1452. 'Ez("$(ESCRIPT_NAME)"),'\
  1453. 'halt().'
  1454. endef
  1455. ESCRIPT_COMMAND = $(subst ' ',,$(ESCRIPT_RAW))
  1456. escript:: distclean-escript deps app
  1457. $(gen_verbose) $(ERL) -eval $(ESCRIPT_COMMAND)
  1458. distclean-escript:
  1459. $(gen_verbose) rm -f $(ESCRIPT_NAME)
  1460. # Copyright (c) 2014, Enrique Fernandez <enrique.fernandez@erlang-solutions.com>
  1461. # Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
  1462. # This file is contributed to erlang.mk and subject to the terms of the ISC License.
  1463. .PHONY: eunit
  1464. # Configuration
  1465. # All modules in TEST_DIR
  1466. ifeq ($(strip $(TEST_DIR)),)
  1467. TEST_DIR_MODS =
  1468. else
  1469. TEST_DIR_MODS = $(notdir $(basename $(shell find $(TEST_DIR) -type f -name *.beam)))
  1470. endif
  1471. # All modules in 'ebin'
  1472. EUNIT_EBIN_MODS = $(notdir $(basename $(shell find ebin -type f -name *.beam)))
  1473. # Only those modules in TEST_DIR with no matching module in 'ebin'.
  1474. # This is done to avoid some tests being executed twice.
  1475. EUNIT_MODS = $(filter-out $(patsubst %,%_tests,$(EUNIT_EBIN_MODS)),$(TEST_DIR_MODS))
  1476. TAGGED_EUNIT_TESTS = $(foreach mod,$(EUNIT_EBIN_MODS) $(EUNIT_MODS),{module,$(mod)})
  1477. EUNIT_OPTS ?=
  1478. # Utility functions
  1479. define str-join
  1480. $(shell echo '$(strip $(1))' | sed -e "s/ /,/g")
  1481. endef
  1482. # Core targets.
  1483. tests:: eunit
  1484. help::
  1485. @printf "%s\n" "" \
  1486. "EUnit targets:" \
  1487. " eunit Run all the EUnit tests for this project"
  1488. # Plugin-specific targets.
  1489. EUNIT_RUN_BEFORE ?=
  1490. EUNIT_RUN_AFTER ?=
  1491. EUNIT_RUN = $(ERL) \
  1492. -pa $(TEST_DIR) $(DEPS_DIR)/*/ebin \
  1493. -pz ebin \
  1494. $(EUNIT_RUN_BEFORE) \
  1495. -eval 'case eunit:test([$(call str-join,$(TAGGED_EUNIT_TESTS))],\
  1496. [$(EUNIT_OPTS)]) of ok -> ok; error -> halt(1) end.' \
  1497. $(EUNIT_RUN_AFTER) \
  1498. -eval 'halt(0).'
  1499. eunit: test-build
  1500. $(gen_verbose) $(EUNIT_RUN)
  1501. # Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
  1502. # This file is part of erlang.mk and subject to the terms of the ISC License.
  1503. .PHONY: relx-rel distclean-relx-rel distclean-relx run
  1504. # Configuration.
  1505. RELX_CONFIG ?= $(CURDIR)/relx.config
  1506. RELX ?= $(CURDIR)/relx
  1507. export RELX
  1508. RELX_URL ?= https://github.com/erlware/relx/releases/download/v2.0.0/relx
  1509. RELX_OPTS ?=
  1510. RELX_OUTPUT_DIR ?= _rel
  1511. ifeq ($(firstword $(RELX_OPTS)),-o)
  1512. RELX_OUTPUT_DIR = $(word 2,$(RELX_OPTS))
  1513. else
  1514. RELX_OPTS += -o $(RELX_OUTPUT_DIR)
  1515. endif
  1516. # Core targets.
  1517. ifeq ($(IS_DEP),)
  1518. ifneq ($(wildcard $(RELX_CONFIG)),)
  1519. rel:: distclean-relx-rel relx-rel
  1520. endif
  1521. endif
  1522. distclean:: distclean-relx-rel distclean-relx
  1523. # Plugin-specific targets.
  1524. define relx_fetch
  1525. $(call core_http_get,$(RELX),$(RELX_URL))
  1526. chmod +x $(RELX)
  1527. endef
  1528. $(RELX):
  1529. @$(call relx_fetch)
  1530. relx-rel: $(RELX)
  1531. @$(RELX) -c $(RELX_CONFIG) $(RELX_OPTS)
  1532. distclean-relx-rel:
  1533. $(gen_verbose) rm -rf $(RELX_OUTPUT_DIR)
  1534. distclean-relx:
  1535. $(gen_verbose) rm -rf $(RELX)
  1536. # Run target.
  1537. ifeq ($(wildcard $(RELX_CONFIG)),)
  1538. run:
  1539. else
  1540. define get_relx_release.erl
  1541. {ok, Config} = file:consult("$(RELX_CONFIG)"),
  1542. {release, {Name, _}, _} = lists:keyfind(release, 1, Config),
  1543. io:format("~s", [Name]),
  1544. halt(0).
  1545. endef
  1546. RELX_RELEASE = `$(call erlang,$(get_relx_release.erl))`
  1547. run: all
  1548. @$(RELX_OUTPUT_DIR)/$(RELX_RELEASE)/bin/$(RELX_RELEASE) console
  1549. help::
  1550. @printf "%s\n" "" \
  1551. "Relx targets:" \
  1552. " run Compile the project, build the release and run it"
  1553. endif
  1554. # Copyright (c) 2014, M Robert Martin <rob@version2beta.com>
  1555. # This file is contributed to erlang.mk and subject to the terms of the ISC License.
  1556. .PHONY: shell
  1557. # Configuration.
  1558. SHELL_PATH ?= -pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin
  1559. SHELL_OPTS ?=
  1560. ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS))
  1561. # Core targets
  1562. help::
  1563. @printf "%s\n" "" \
  1564. "Shell targets:" \
  1565. " shell Run an erlang shell with SHELL_OPTS or reasonable default"
  1566. # Plugin-specific targets.
  1567. $(foreach dep,$(SHELL_DEPS),$(eval $(call dep_target,$(dep))))
  1568. build-shell-deps: $(ALL_SHELL_DEPS_DIRS)
  1569. @for dep in $(ALL_SHELL_DEPS_DIRS) ; do $(MAKE) -C $$dep ; done
  1570. shell: build-shell-deps
  1571. $(gen_verbose) erl $(SHELL_PATH) $(SHELL_OPTS)
  1572. # Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
  1573. # This file is part of erlang.mk and subject to the terms of the ISC License.
  1574. ifneq ($(wildcard $(DEPS_DIR)/triq),)
  1575. .PHONY: triq
  1576. # Targets.
  1577. tests:: triq
  1578. define triq_check.erl
  1579. code:add_pathsa(["$(CURDIR)/ebin", "$(DEPS_DIR)/*/ebin"]),
  1580. try
  1581. case $(1) of
  1582. all -> [true] =:= lists:usort([triq:check(M) || M <- [$(MODULES)]]);
  1583. module -> triq:check($(2));
  1584. function -> triq:check($(2))
  1585. end
  1586. of
  1587. true -> halt(0);
  1588. _ -> halt(1)
  1589. catch error:undef ->
  1590. io:format("Undefined property or module~n"),
  1591. halt(0)
  1592. end.
  1593. endef
  1594. ifdef t
  1595. ifeq (,$(findstring :,$(t)))
  1596. triq: test-build
  1597. @$(call erlang,$(call triq_check.erl,module,$(t)))
  1598. else
  1599. triq: test-build
  1600. @echo Testing $(t)/0
  1601. @$(call erlang,$(call triq_check.erl,function,$(t)()))
  1602. endif
  1603. else
  1604. triq: test-build
  1605. $(eval MODULES := $(shell find ebin -type f -name \*.beam \
  1606. | sed "s/ebin\//'/;s/\.beam/',/" | sed '$$s/.$$//'))
  1607. $(gen_verbose) $(call erlang,$(call triq_check.erl,all,undefined))
  1608. endif
  1609. endif
  1610. # Copyright (c) 2015, Euen Lopez <euen@inakanetworks.com>
  1611. # This file is part of erlang.mk and subject to the terms of the ISC License.
  1612. .PHONY: xref distclean-xref
  1613. # Configuration.
  1614. ifeq ($(XREF_CONFIG),)
  1615. XREF_ARGS :=
  1616. else
  1617. XREF_ARGS := -c $(XREF_CONFIG)
  1618. endif
  1619. XREFR ?= $(CURDIR)/xrefr
  1620. export XREFR
  1621. XREFR_URL ?= https://github.com/inaka/xref_runner/releases/download/0.2.0/xrefr
  1622. # Core targets.
  1623. help::
  1624. @printf "%s\n" "" \
  1625. "Xref targets:" \
  1626. " xref Run Xrefr using $XREF_CONFIG as config file if defined"
  1627. distclean:: distclean-xref
  1628. # Plugin-specific targets.
  1629. $(XREFR):
  1630. @$(call core_http_get,$(XREFR),$(XREFR_URL))
  1631. @chmod +x $(XREFR)
  1632. xref: deps app $(XREFR)
  1633. $(gen_verbose) $(XREFR) $(XREFR_ARGS)
  1634. distclean-xref:
  1635. $(gen_verbose) rm -rf $(XREFR)
  1636. # Copyright 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
  1637. # This file is part of erlang.mk and subject to the terms of the ISC License.
  1638. COVER_REPORT_DIR = cover
  1639. # utility variables for representing special symbols
  1640. empty :=
  1641. space := $(empty) $(empty)
  1642. comma := ,
  1643. # Hook in coverage to eunit
  1644. ifdef COVER
  1645. ifdef EUNIT_RUN
  1646. EUNIT_RUN_BEFORE += -eval \
  1647. 'case cover:compile_beam_directory("ebin") of \
  1648. {error, _} -> halt(1); \
  1649. _ -> ok \
  1650. end.'
  1651. EUNIT_RUN_AFTER += -eval 'cover:export("eunit.coverdata").'
  1652. endif
  1653. endif
  1654. # Hook in coverage to ct
  1655. ifdef COVER
  1656. ifdef CT_RUN
  1657. # All modules in 'ebin'
  1658. COVER_MODS = $(notdir $(basename $(shell echo ebin/*.beam)))
  1659. test-build:: $(TEST_DIR)/ct.cover.spec
  1660. $(TEST_DIR)/ct.cover.spec:
  1661. @echo Cover mods: $(COVER_MODS)
  1662. $(gen_verbose) printf "%s\n" \
  1663. '{incl_mods,[$(subst $(space),$(comma),$(COVER_MODS))]}.' \
  1664. '{export,"$(CURDIR)/ct.coverdata"}.' > $@
  1665. CT_RUN += -cover $(TEST_DIR)/ct.cover.spec
  1666. endif
  1667. endif
  1668. # Core targets
  1669. ifdef COVER
  1670. ifneq ($(COVER_REPORT_DIR),)
  1671. tests::
  1672. @$(MAKE) --no-print-directory cover-report
  1673. endif
  1674. endif
  1675. clean:: coverdata-clean
  1676. ifneq ($(COVER_REPORT_DIR),)
  1677. distclean:: cover-report-clean
  1678. endif
  1679. help::
  1680. @printf "%s\n" "" \
  1681. "Cover targets:" \
  1682. " cover-report Generate a HTML coverage report from previously collected" \
  1683. " cover data." \
  1684. " all.coverdata Merge {eunit,ct}.coverdata into one coverdata file." \
  1685. "" \
  1686. "If COVER=1 is set, coverage data is generated by the targets eunit and ct. The" \
  1687. "target tests additionally generates a HTML coverage report from the combined" \
  1688. "coverdata files from each of these testing tools. HTML reports can be disabled" \
  1689. "by setting COVER_REPORT_DIR to empty."
  1690. # Plugin specific targets
  1691. COVERDATA = $(filter-out all.coverdata,$(wildcard *.coverdata))
  1692. .PHONY: coverdata-clean
  1693. coverdata-clean:
  1694. $(gen_verbose) rm -f *.coverdata ct.cover.spec
  1695. # Merge all coverdata files into one.
  1696. all.coverdata: $(COVERDATA)
  1697. $(gen_verbose) $(ERL) -eval ' \
  1698. $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),) \
  1699. cover:export("$@"), halt(0).'
  1700. # These are only defined if COVER_REPORT_DIR is non-empty. Set COVER_REPORT_DIR to
  1701. # empty if you want the coverdata files but not the HTML report.
  1702. ifneq ($(COVER_REPORT_DIR),)
  1703. .PHONY: cover-report-clean cover-report
  1704. cover-report-clean:
  1705. $(gen_verbose) rm -rf $(COVER_REPORT_DIR)
  1706. ifeq ($(COVERDATA),)
  1707. cover-report:
  1708. else
  1709. # Modules which include eunit.hrl always contain one line without coverage
  1710. # because eunit defines test/0 which is never called. We compensate for this.
  1711. EUNIT_HRL_MODS = $(subst $(space),$(comma),$(shell \
  1712. grep -e '^\s*-include.*include/eunit\.hrl"' src/*.erl \
  1713. | sed "s/^src\/\(.*\)\.erl:.*/'\1'/" | uniq))
  1714. define cover_report.erl
  1715. $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),)
  1716. Ms = cover:imported_modules(),
  1717. [cover:analyse_to_file(M, "$(COVER_REPORT_DIR)/" ++ atom_to_list(M)
  1718. ++ ".COVER.html", [html]) || M <- Ms],
  1719. Report = [begin {ok, R} = cover:analyse(M, module), R end || M <- Ms],
  1720. EunitHrlMods = [$(EUNIT_HRL_MODS)],
  1721. Report1 = [{M, {Y, case lists:member(M, EunitHrlMods) of
  1722. true -> N - 1; false -> N end}} || {M, {Y, N}} <- Report],
  1723. TotalY = lists:sum([Y || {_, {Y, _}} <- Report1]),
  1724. TotalN = lists:sum([N || {_, {_, N}} <- Report1]),
  1725. TotalPerc = round(100 * TotalY / (TotalY + TotalN)),
  1726. {ok, F} = file:open("$(COVER_REPORT_DIR)/index.html", [write]),
  1727. io:format(F, "<!DOCTYPE html><html>~n"
  1728. "<head><meta charset=\"UTF-8\">~n"
  1729. "<title>Coverage report</title></head>~n"
  1730. "<body>~n", []),
  1731. io:format(F, "<h1>Coverage</h1>~n<p>Total: ~p%</p>~n", [TotalPerc]),
  1732. io:format(F, "<table><tr><th>Module</th><th>Coverage</th></tr>~n", []),
  1733. [io:format(F, "<tr><td><a href=\"~p.COVER.html\">~p</a></td>"
  1734. "<td>~p%</td></tr>~n",
  1735. [M, M, round(100 * Y / (Y + N))]) || {M, {Y, N}} <- Report1],
  1736. How = "$(subst $(space),$(comma)$(space),$(basename $(COVERDATA)))",
  1737. Date = "$(shell date -u "+%Y-%m-%dT%H:%M:%SZ")",
  1738. io:format(F, "</table>~n"
  1739. "<p>Generated using ~s and erlang.mk on ~s.</p>~n"
  1740. "</body></html>", [How, Date]),
  1741. halt().
  1742. endef
  1743. cover-report:
  1744. $(gen_verbose) mkdir -p $(COVER_REPORT_DIR)
  1745. $(gen_verbose) $(call erlang,$(cover_report.erl))
  1746. endif
  1747. endif # ifneq ($(COVER_REPORT_DIR),)