erlc.mk 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. # Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
  2. # This file is part of erlang.mk and subject to the terms of the ISC License.
  3. .PHONY: clean-app
  4. # Configuration.
  5. ERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \
  6. +warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec
  7. COMPILE_FIRST ?=
  8. COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST)))
  9. ERLC_EXCLUDE ?=
  10. ERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE)))
  11. ERLC_MIB_OPTS ?=
  12. COMPILE_MIB_FIRST ?=
  13. COMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST)))
  14. # Verbosity.
  15. app_verbose_0 = @echo " APP " $(PROJECT);
  16. app_verbose_2 = set -x;
  17. app_verbose = $(app_verbose_$(V))
  18. appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src;
  19. appsrc_verbose_2 = set -x;
  20. appsrc_verbose = $(appsrc_verbose_$(V))
  21. makedep_verbose_0 = @echo " DEPEND" $(PROJECT).d;
  22. makedep_verbose_2 = set -x;
  23. makedep_verbose = $(makedep_verbose_$(V))
  24. erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\
  25. $(filter %.erl %.core,$(?F)));
  26. erlc_verbose_2 = set -x;
  27. erlc_verbose = $(erlc_verbose_$(V))
  28. xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F));
  29. xyrl_verbose_2 = set -x;
  30. xyrl_verbose = $(xyrl_verbose_$(V))
  31. asn1_verbose_0 = @echo " ASN1 " $(filter %.asn1,$(?F));
  32. asn1_verbose_2 = set -x;
  33. asn1_verbose = $(asn1_verbose_$(V))
  34. mib_verbose_0 = @echo " MIB " $(filter %.bin %.mib,$(?F));
  35. mib_verbose_2 = set -x;
  36. mib_verbose = $(mib_verbose_$(V))
  37. ifneq ($(wildcard src/),)
  38. # Targets.
  39. ifeq ($(wildcard ebin/test),)
  40. app:: deps $(PROJECT).d
  41. $(verbose) $(MAKE) --no-print-directory app-build
  42. else
  43. app:: clean deps $(PROJECT).d
  44. $(verbose) $(MAKE) --no-print-directory app-build
  45. endif
  46. ifeq ($(wildcard src/$(PROJECT_MOD).erl),)
  47. define app_file
  48. {application, $(PROJECT), [
  49. {description, "$(PROJECT_DESCRIPTION)"},
  50. {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP),
  51. {id$(comma)$(space)"$(1)"}$(comma))
  52. {modules, [$(call comma_list,$(2))]},
  53. {registered, []},
  54. {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS))]},
  55. {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),)
  56. ]}.
  57. endef
  58. else
  59. define app_file
  60. {application, $(PROJECT), [
  61. {description, "$(PROJECT_DESCRIPTION)"},
  62. {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP),
  63. {id$(comma)$(space)"$(1)"}$(comma))
  64. {modules, [$(call comma_list,$(2))]},
  65. {registered, [$(call comma_list,$(PROJECT)_sup $(PROJECT_REGISTERED))]},
  66. {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS))]},
  67. {mod, {$(PROJECT_MOD), []}},
  68. {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),)
  69. ]}.
  70. endef
  71. endif
  72. app-build: ebin/$(PROJECT).app
  73. $(verbose) :
  74. # Source files.
  75. ALL_SRC_FILES := $(sort $(call core_find,src/,*))
  76. ERL_FILES := $(filter %.erl,$(ALL_SRC_FILES))
  77. CORE_FILES := $(filter %.core,$(ALL_SRC_FILES))
  78. # ASN.1 files.
  79. ifneq ($(wildcard asn1/),)
  80. ASN1_FILES = $(sort $(call core_find,asn1/,*.asn1))
  81. ERL_FILES += $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES))))
  82. define compile_asn1
  83. $(verbose) mkdir -p include/
  84. $(asn1_verbose) erlc -v -I include/ -o asn1/ +noobj $(1)
  85. $(verbose) mv asn1/*.erl src/
  86. $(verbose) mv asn1/*.hrl include/
  87. $(verbose) mv asn1/*.asn1db include/
  88. endef
  89. $(PROJECT).d:: $(ASN1_FILES)
  90. $(if $(strip $?),$(call compile_asn1,$?))
  91. endif
  92. # SNMP MIB files.
  93. ifneq ($(wildcard mibs/),)
  94. MIB_FILES = $(sort $(call core_find,mibs/,*.mib))
  95. $(PROJECT).d:: $(COMPILE_MIB_FIRST_PATHS) $(MIB_FILES)
  96. $(verbose) mkdir -p include/ priv/mibs/
  97. $(mib_verbose) erlc -v $(ERLC_MIB_OPTS) -o priv/mibs/ -I priv/mibs/ $?
  98. $(mib_verbose) erlc -o include/ -- $(addprefix priv/mibs/,$(patsubst %.mib,%.bin,$(notdir $?)))
  99. endif
  100. # Leex and Yecc files.
  101. XRL_FILES := $(filter %.xrl,$(ALL_SRC_FILES))
  102. XRL_ERL_FILES = $(addprefix src/,$(patsubst %.xrl,%.erl,$(notdir $(XRL_FILES))))
  103. ERL_FILES += $(XRL_ERL_FILES)
  104. YRL_FILES := $(filter %.yrl,$(ALL_SRC_FILES))
  105. YRL_ERL_FILES = $(addprefix src/,$(patsubst %.yrl,%.erl,$(notdir $(YRL_FILES))))
  106. ERL_FILES += $(YRL_ERL_FILES)
  107. $(PROJECT).d:: $(XRL_FILES) $(YRL_FILES)
  108. $(if $(strip $?),$(xyrl_verbose) erlc -v -o src/ $(YRL_ERLC_OPTS) $?)
  109. # Erlang and Core Erlang files.
  110. define makedep.erl
  111. E = ets:new(makedep, [bag]),
  112. G = digraph:new([acyclic]),
  113. ErlFiles = lists:usort(string:tokens("$(ERL_FILES)", " ")),
  114. Modules = [{list_to_atom(filename:basename(F, ".erl")), F} || F <- ErlFiles],
  115. Add = fun (Mod, Dep) ->
  116. case lists:keyfind(Dep, 1, Modules) of
  117. false -> ok;
  118. {_, DepFile} ->
  119. {_, ModFile} = lists:keyfind(Mod, 1, Modules),
  120. ets:insert(E, {ModFile, DepFile}),
  121. digraph:add_vertex(G, Mod),
  122. digraph:add_vertex(G, Dep),
  123. digraph:add_edge(G, Mod, Dep)
  124. end
  125. end,
  126. AddHd = fun (F, Mod, DepFile) ->
  127. case file:open(DepFile, [read]) of
  128. {error, enoent} -> ok;
  129. {ok, Fd} ->
  130. F(F, Fd, Mod),
  131. {_, ModFile} = lists:keyfind(Mod, 1, Modules),
  132. ets:insert(E, {ModFile, DepFile})
  133. end
  134. end,
  135. Attr = fun
  136. (F, Mod, behavior, Dep) -> Add(Mod, Dep);
  137. (F, Mod, behaviour, Dep) -> Add(Mod, Dep);
  138. (F, Mod, compile, {parse_transform, Dep}) -> Add(Mod, Dep);
  139. (F, Mod, compile, Opts) when is_list(Opts) ->
  140. case proplists:get_value(parse_transform, Opts) of
  141. undefined -> ok;
  142. Dep -> Add(Mod, Dep)
  143. end;
  144. (F, Mod, include, Hrl) ->
  145. case filelib:is_file("include/" ++ Hrl) of
  146. true -> AddHd(F, Mod, "include/" ++ Hrl);
  147. false ->
  148. case filelib:is_file("src/" ++ Hrl) of
  149. true -> AddHd(F, Mod, "src/" ++ Hrl);
  150. false -> false
  151. end
  152. end;
  153. (F, Mod, include_lib, "$1/include/" ++ Hrl) -> AddHd(F, Mod, "include/" ++ Hrl);
  154. (F, Mod, include_lib, Hrl) -> AddHd(F, Mod, "include/" ++ Hrl);
  155. (F, Mod, import, {Imp, _}) ->
  156. case filelib:is_file("src/" ++ atom_to_list(Imp) ++ ".erl") of
  157. false -> ok;
  158. true -> Add(Mod, Imp)
  159. end;
  160. (_, _, _, _) -> ok
  161. end,
  162. MakeDepend = fun(F, Fd, Mod) ->
  163. case io:parse_erl_form(Fd, undefined) of
  164. {ok, {attribute, _, Key, Value}, _} ->
  165. Attr(F, Mod, Key, Value),
  166. F(F, Fd, Mod);
  167. {eof, _} ->
  168. file:close(Fd);
  169. _ ->
  170. F(F, Fd, Mod)
  171. end
  172. end,
  173. [begin
  174. Mod = list_to_atom(filename:basename(F, ".erl")),
  175. {ok, Fd} = file:open(F, [read]),
  176. MakeDepend(MakeDepend, Fd, Mod)
  177. end || F <- ErlFiles],
  178. Depend = sofs:to_external(sofs:relation_to_family(sofs:relation(ets:tab2list(E)))),
  179. CompileFirst = [X || X <- lists:reverse(digraph_utils:topsort(G)), [] =/= digraph:in_neighbours(G, X)],
  180. ok = file:write_file("$(1)", [
  181. [[F, "::", [[" ", D] || D <- Deps], "; @touch \$$@\n"] || {F, Deps} <- Depend],
  182. "\nCOMPILE_FIRST +=", [[" ", atom_to_list(CF)] || CF <- CompileFirst], "\n"
  183. ]),
  184. halt()
  185. endef
  186. ifeq ($(if $(NO_MAKEDEP),$(wildcard $(PROJECT).d),),)
  187. $(PROJECT).d:: $(ERL_FILES) $(call core_find,include/,*.hrl) $(MAKEFILE_LIST)
  188. $(makedep_verbose) $(call erlang,$(call makedep.erl,$@))
  189. endif
  190. ifneq ($(words $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES)),0)
  191. # Rebuild everything when the Makefile changes.
  192. $(ERLANG_MK_TMP)/last-makefile-change: $(MAKEFILE_LIST)
  193. $(verbose) mkdir -p $(ERLANG_MK_TMP)
  194. $(verbose) if test -f $@; then \
  195. touch $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES); \
  196. touch -c $(PROJECT).d; \
  197. fi
  198. $(verbose) touch $@
  199. $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES):: $(ERLANG_MK_TMP)/last-makefile-change
  200. ebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change
  201. endif
  202. -include $(PROJECT).d
  203. ebin/$(PROJECT).app:: ebin/
  204. ebin/:
  205. $(verbose) mkdir -p ebin/
  206. define compile_erl
  207. $(erlc_verbose) erlc -v $(if $(IS_DEP),$(filter-out -Werror,$(ERLC_OPTS)),$(ERLC_OPTS)) -o ebin/ \
  208. -pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),$(COMPILE_FIRST_PATHS) $(1))
  209. endef
  210. ebin/$(PROJECT).app:: $(ERL_FILES) $(CORE_FILES) $(wildcard src/$(PROJECT).app.src)
  211. $(eval FILES_TO_COMPILE := $(filter-out src/$(PROJECT).app.src,$?))
  212. $(if $(strip $(FILES_TO_COMPILE)),$(call compile_erl,$(FILES_TO_COMPILE)))
  213. $(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null || true))
  214. $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \
  215. $(filter-out $(ERLC_EXCLUDE_PATHS),$(ERL_FILES) $(CORE_FILES) $(BEAM_FILES)))))))
  216. ifeq ($(wildcard src/$(PROJECT).app.src),)
  217. $(app_verbose) printf '$(subst %,%%,$(subst $(newline),\n,$(subst ','\'',$(call app_file,$(GITDESCRIBE),$(MODULES)))))' \
  218. > ebin/$(PROJECT).app
  219. else
  220. $(verbose) if [ -z "$$(grep -e '^[^%]*{\s*modules\s*,' src/$(PROJECT).app.src)" ]; then \
  221. echo "Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk README for instructions." >&2; \
  222. exit 1; \
  223. fi
  224. $(appsrc_verbose) cat src/$(PROJECT).app.src \
  225. | sed "s/{[[:space:]]*modules[[:space:]]*,[[:space:]]*\[\]}/{modules, \[$(call comma_list,$(MODULES))\]}/" \
  226. | sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(subst /,\/,$(GITDESCRIBE))\"}/" \
  227. > ebin/$(PROJECT).app
  228. endif
  229. clean:: clean-app
  230. clean-app:
  231. $(gen_verbose) rm -rf $(PROJECT).d ebin/ priv/mibs/ $(XRL_ERL_FILES) $(YRL_ERL_FILES) \
  232. $(addprefix include/,$(patsubst %.mib,%.hrl,$(notdir $(MIB_FILES)))) \
  233. $(addprefix include/,$(patsubst %.asn1,%.hrl,$(notdir $(ASN1_FILES)))) \
  234. $(addprefix include/,$(patsubst %.asn1,%.asn1db,$(notdir $(ASN1_FILES)))) \
  235. $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES))))
  236. endif