hex.mk 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. # Copyright (c) 2020, Loïc Hoguin <essen@ninenines.eu>
  2. # This file is part of erlang.mk and subject to the terms of the ISC License.
  3. HEX_CORE_GIT ?= https://github.com/hexpm/hex_core
  4. HEX_CORE_COMMIT ?= v0.7.0
  5. PACKAGES += hex_core
  6. pkg_hex_core_name = hex_core
  7. pkg_hex_core_description = Reference implementation of Hex specifications
  8. pkg_hex_core_homepage = $(HEX_CORE_GIT)
  9. pkg_hex_core_fetch = git
  10. pkg_hex_core_repo = $(HEX_CORE_GIT)
  11. pkg_hex_core_commit = $(HEX_CORE_COMMIT)
  12. # We automatically depend on hex_core when the project isn't already.
  13. $(if $(filter hex_core,$(DEPS) $(BUILD_DEPS) $(DOC_DEPS) $(REL_DEPS) $(TEST_DEPS)),,\
  14. $(eval $(call dep_target,hex_core)))
  15. hex-core: $(DEPS_DIR)/hex_core
  16. $(verbose) if [ ! -e $(DEPS_DIR)/hex_core/ebin/dep_built ]; then \
  17. $(MAKE) -C $(DEPS_DIR)/hex_core IS_DEP=1; \
  18. touch $(DEPS_DIR)/hex_core/ebin/dep_built; \
  19. fi
  20. # @todo This must also apply to fetching.
  21. HEX_CONFIG ?=
  22. define hex_config.erl
  23. begin
  24. Config0 = hex_core:default_config(),
  25. Config0$(HEX_CONFIG)
  26. end
  27. endef
  28. define hex_user_create.erl
  29. {ok, _} = application:ensure_all_started(ssl),
  30. {ok, _} = application:ensure_all_started(inets),
  31. Config = $(hex_config.erl),
  32. case hex_api_user:create(Config, <<"$(strip $1)">>, <<"$(strip $2)">>, <<"$(strip $3)">>) of
  33. {ok, {201, _, #{<<"email">> := Email, <<"url">> := URL, <<"username">> := Username}}} ->
  34. io:format("User ~s (~s) created at ~s~n"
  35. "Please check your inbox for a confirmation email.~n"
  36. "You must confirm before you are allowed to publish packages.~n",
  37. [Username, Email, URL]),
  38. halt(0);
  39. {ok, {Status, _, Errors}} ->
  40. io:format("Error ~b: ~0p~n", [Status, Errors]),
  41. halt(80)
  42. end
  43. endef
  44. # The $(info ) call inserts a new line after the password prompt.
  45. hex-user-create: hex-core
  46. $(if $(HEX_USERNAME),,$(eval HEX_USERNAME := $(shell read -p "Username: " username; echo $$username)))
  47. $(if $(HEX_PASSWORD),,$(eval HEX_PASSWORD := $(shell stty -echo; read -p "Password: " password; stty echo; echo $$password) $(info )))
  48. $(if $(HEX_EMAIL),,$(eval HEX_EMAIL := $(shell read -p "Email: " email; echo $$email)))
  49. $(gen_verbose) $(call erlang,$(call hex_user_create.erl,$(HEX_USERNAME),$(HEX_PASSWORD),$(HEX_EMAIL)))
  50. define hex_key_add.erl
  51. {ok, _} = application:ensure_all_started(ssl),
  52. {ok, _} = application:ensure_all_started(inets),
  53. Config = $(hex_config.erl),
  54. ConfigF = Config#{api_key => iolist_to_binary([<<"Basic ">>, base64:encode(<<"$(strip $1):$(strip $2)">>)])},
  55. Permissions = [
  56. case string:split(P, <<":">>) of
  57. [D] -> #{domain => D};
  58. [D, R] -> #{domain => D, resource => R}
  59. end
  60. || P <- string:split(<<"$(strip $4)">>, <<",">>, all)],
  61. case hex_api_key:add(ConfigF, <<"$(strip $3)">>, Permissions) of
  62. {ok, {201, _, #{<<"secret">> := Secret}}} ->
  63. io:format("Key ~s created for user ~s~nSecret: ~s~n"
  64. "Please store the secret in a secure location, such as a password store.~n"
  65. "The secret will be requested for most Hex-related operations.~n",
  66. [<<"$(strip $3)">>, <<"$(strip $1)">>, Secret]),
  67. halt(0);
  68. {ok, {Status, _, Errors}} ->
  69. io:format("Error ~b: ~0p~n", [Status, Errors]),
  70. halt(81)
  71. end
  72. endef
  73. hex-key-add: hex-core
  74. $(if $(HEX_USERNAME),,$(eval HEX_USERNAME := $(shell read -p "Username: " username; echo $$username)))
  75. $(if $(HEX_PASSWORD),,$(eval HEX_PASSWORD := $(shell stty -echo; read -p "Password: " password; stty echo; echo $$password) $(info )))
  76. $(gen_verbose) $(call erlang,$(call hex_key_add.erl,$(HEX_USERNAME),$(HEX_PASSWORD),\
  77. $(if $(name),$(name),$(shell hostname)-erlang-mk),\
  78. $(if $(perm),$(perm),api)))
  79. HEX_TARBALL_EXTRA_METADATA ?=
  80. # @todo Check that we can += files
  81. HEX_TARBALL_FILES ?= \
  82. $(wildcard early-plugins.mk) \
  83. $(wildcard ebin/$(PROJECT).app) \
  84. $(wildcard ebin/$(PROJECT).appup) \
  85. $(wildcard $(notdir $(ERLANG_MK_FILENAME))) \
  86. $(sort $(call core_find,include/,*.hrl)) \
  87. $(wildcard LICENSE*) \
  88. $(wildcard Makefile) \
  89. $(wildcard plugins.mk) \
  90. $(sort $(call core_find,priv/,*)) \
  91. $(wildcard README*) \
  92. $(wildcard rebar.config) \
  93. $(sort $(call core_find,src/,*))
  94. HEX_TARBALL_OUTPUT_FILE ?= $(ERLANG_MK_TMP)/$(PROJECT).tar
  95. # @todo Need to check for rebar.config and/or the absence of DEPS to know
  96. # whether a project will work with Rebar.
  97. #
  98. # @todo contributors licenses links in HEX_TARBALL_EXTRA_METADATA
  99. # In order to build the requirements metadata we look into DEPS.
  100. # We do not require that the project use Hex dependencies, however
  101. # Hex.pm does require that the package name and version numbers
  102. # correspond to a real Hex package.
  103. define hex_tarball_create.erl
  104. Files0 = [$(call comma_list,$(patsubst %,"%",$(HEX_TARBALL_FILES)))],
  105. Requirements0 = #{
  106. $(foreach d,$(DEPS),
  107. <<"$(if $(subst hex,,$(call query_fetch_method,$d)),$d,$(if $(word 3,$(dep_$d)),$(word 3,$(dep_$d)),$d))">> => #{
  108. <<"app">> => <<"$d">>,
  109. <<"optional">> => false,
  110. <<"requirement">> => <<"$(call query_version,$d)">>
  111. },)
  112. $(if $(DEPS),dummy => dummy)
  113. },
  114. Requirements = maps:remove(dummy, Requirements0),
  115. Metadata0 = #{
  116. app => <<"$(strip $(PROJECT))">>,
  117. build_tools => [<<"make">>, <<"rebar3">>],
  118. description => <<"$(strip $(PROJECT_DESCRIPTION))">>,
  119. files => [unicode:characters_to_binary(F) || F <- Files0],
  120. name => <<"$(strip $(PROJECT))">>,
  121. requirements => Requirements,
  122. version => <<"$(strip $(PROJECT_VERSION))">>
  123. },
  124. Metadata = Metadata0$(HEX_TARBALL_EXTRA_METADATA),
  125. Files = [case file:read_file(F) of
  126. {ok, Bin} ->
  127. {F, Bin};
  128. {error, Reason} ->
  129. io:format("Error trying to open file ~0p: ~0p~n", [F, Reason]),
  130. halt(82)
  131. end || F <- Files0],
  132. case hex_tarball:create(Metadata, Files) of
  133. {ok, #{tarball := Tarball}} ->
  134. ok = file:write_file("$(strip $(HEX_TARBALL_OUTPUT_FILE))", Tarball),
  135. halt(0);
  136. {error, Reason} ->
  137. io:format("Error ~0p~n", [Reason]),
  138. halt(83)
  139. end
  140. endef
  141. hex_tar_verbose_0 = @echo " TAR $(notdir $(ERLANG_MK_TMP))/$(@F)";
  142. hex_tar_verbose_2 = set -x;
  143. hex_tar_verbose = $(hex_tar_verbose_$(V))
  144. $(HEX_TARBALL_OUTPUT_FILE): hex-core app
  145. $(hex_tar_verbose) $(call erlang,$(call hex_tarball_create.erl))
  146. hex-tarball-create: $(HEX_TARBALL_OUTPUT_FILE)
  147. define hex_release_publish_summary.erl
  148. {ok, Tarball} = erl_tar:open("$(strip $(HEX_TARBALL_OUTPUT_FILE))", [read]),
  149. ok = erl_tar:extract(Tarball, [{cwd, "$(ERLANG_MK_TMP)"}, {files, ["metadata.config"]}]),
  150. {ok, Metadata} = file:consult("$(ERLANG_MK_TMP)/metadata.config"),
  151. #{
  152. <<"name">> := Name,
  153. <<"version">> := Version,
  154. <<"files">> := Files,
  155. <<"requirements">> := Deps
  156. } = maps:from_list(Metadata),
  157. io:format("Publishing ~s ~s~n Dependencies:~n", [Name, Version]),
  158. case Deps of
  159. [] ->
  160. io:format(" (none)~n");
  161. _ ->
  162. [begin
  163. #{<<"app">> := DA, <<"requirement">> := DR} = maps:from_list(D),
  164. io:format(" ~s ~s~n", [DA, DR])
  165. end || {_, D} <- Deps]
  166. end,
  167. io:format(" Included files:~n"),
  168. [io:format(" ~s~n", [F]) || F <- Files],
  169. io:format("You may also review the contents of the tarball file.~n"
  170. "Please enter your secret key to proceed.~n"),
  171. halt(0)
  172. endef
  173. define hex_release_publish.erl
  174. {ok, _} = application:ensure_all_started(ssl),
  175. {ok, _} = application:ensure_all_started(inets),
  176. Config = $(hex_config.erl),
  177. ConfigF = Config#{api_key => <<"$(strip $1)">>},
  178. {ok, Tarball} = file:read_file("$(strip $(HEX_TARBALL_OUTPUT_FILE))"),
  179. case hex_api_release:publish(ConfigF, Tarball, [{replace, $2}]) of
  180. {ok, {200, _, #{}}} ->
  181. io:format("Release replaced~n"),
  182. halt(0);
  183. {ok, {201, _, #{}}} ->
  184. io:format("Release published~n"),
  185. halt(0);
  186. {ok, {Status, _, Errors}} ->
  187. io:format("Error ~b: ~0p~n", [Status, Errors]),
  188. halt(84)
  189. end
  190. endef
  191. hex-release-tarball: hex-core $(HEX_TARBALL_OUTPUT_FILE)
  192. $(verbose) $(call erlang,$(call hex_release_publish_summary.erl))
  193. hex-release-publish: hex-core hex-release-tarball
  194. $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
  195. $(gen_verbose) $(call erlang,$(call hex_release_publish.erl,$(HEX_SECRET),false))
  196. hex-release-replace: hex-core hex-release-tarball
  197. $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
  198. $(gen_verbose) $(call erlang,$(call hex_release_publish.erl,$(HEX_SECRET),true))
  199. define hex_release_delete.erl
  200. {ok, _} = application:ensure_all_started(ssl),
  201. {ok, _} = application:ensure_all_started(inets),
  202. Config = $(hex_config.erl),
  203. ConfigF = Config#{api_key => <<"$(strip $1)">>},
  204. case hex_api_release:delete(ConfigF, <<"$(strip $(PROJECT))">>, <<"$(strip $(PROJECT_VERSION))">>) of
  205. {ok, {204, _, _}} ->
  206. io:format("Release $(strip $(PROJECT_VERSION)) deleted~n"),
  207. halt(0);
  208. {ok, {Status, _, Errors}} ->
  209. io:format("Error ~b: ~0p~n", [Status, Errors]),
  210. halt(85)
  211. end
  212. endef
  213. hex-release-delete: hex-core
  214. $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
  215. $(gen_verbose) $(call erlang,$(call hex_release_delete.erl,$(HEX_SECRET)))
  216. define hex_release_retire.erl
  217. {ok, _} = application:ensure_all_started(ssl),
  218. {ok, _} = application:ensure_all_started(inets),
  219. Config = $(hex_config.erl),
  220. ConfigF = Config#{api_key => <<"$(strip $1)">>},
  221. Params = #{<<"reason">> => <<"$(strip $3)">>, <<"message">> => <<"$(strip $4)">>},
  222. case hex_api_release:retire(ConfigF, <<"$(strip $(PROJECT))">>, <<"$(strip $2)">>, Params) of
  223. {ok, {204, _, _}} ->
  224. io:format("Release $(strip $2) has been retired~n"),
  225. halt(0);
  226. {ok, {Status, _, Errors}} ->
  227. io:format("Error ~b: ~0p~n", [Status, Errors]),
  228. halt(86)
  229. end
  230. endef
  231. hex-release-retire: hex-core
  232. $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
  233. $(gen_verbose) $(call erlang,$(call hex_release_retire.erl,$(HEX_SECRET),\
  234. $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION)),\
  235. $(if $(HEX_REASON),$(HEX_REASON),invalid),\
  236. $(HEX_MESSAGE)))
  237. define hex_release_unretire.erl
  238. {ok, _} = application:ensure_all_started(ssl),
  239. {ok, _} = application:ensure_all_started(inets),
  240. Config = $(hex_config.erl),
  241. ConfigF = Config#{api_key => <<"$(strip $1)">>},
  242. case hex_api_release:unretire(ConfigF, <<"$(strip $(PROJECT))">>, <<"$(strip $2)">>) of
  243. {ok, {204, _, _}} ->
  244. io:format("Release $(strip $2) is not retired anymore~n"),
  245. halt(0);
  246. {ok, {Status, _, Errors}} ->
  247. io:format("Error ~b: ~0p~n", [Status, Errors]),
  248. halt(87)
  249. end
  250. endef
  251. hex-release-unretire: hex-core
  252. $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
  253. $(gen_verbose) $(call erlang,$(call hex_release_unretire.erl,$(HEX_SECRET),\
  254. $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION))))
  255. HEX_DOCS_DOC_DIR ?= doc/
  256. HEX_DOCS_TARBALL_FILES ?= $(sort $(call core_find,$(HEX_DOCS_DOC_DIR),*))
  257. HEX_DOCS_TARBALL_OUTPUT_FILE ?= $(ERLANG_MK_TMP)/$(PROJECT)-docs.tar.gz
  258. $(HEX_DOCS_TARBALL_OUTPUT_FILE): hex-core app docs
  259. $(hex_tar_verbose) tar czf $(HEX_DOCS_TARBALL_OUTPUT_FILE) -C $(HEX_DOCS_DOC_DIR) \
  260. $(HEX_DOCS_TARBALL_FILES:$(HEX_DOCS_DOC_DIR)%=%)
  261. hex-docs-tarball-create: $(HEX_DOCS_TARBALL_OUTPUT_FILE)
  262. define hex_docs_publish.erl
  263. {ok, _} = application:ensure_all_started(ssl),
  264. {ok, _} = application:ensure_all_started(inets),
  265. Config = $(hex_config.erl),
  266. ConfigF = Config#{api_key => <<"$(strip $1)">>},
  267. {ok, Tarball} = file:read_file("$(strip $(HEX_DOCS_TARBALL_OUTPUT_FILE))"),
  268. case hex_api:post(ConfigF,
  269. ["packages", "$(strip $(PROJECT))", "releases", "$(strip $(PROJECT_VERSION))", "docs"],
  270. {"application/octet-stream", Tarball}) of
  271. {ok, {Status, _, _}} when Status >= 200, Status < 300 ->
  272. io:format("Docs published~n"),
  273. halt(0);
  274. {ok, {Status, _, Errors}} ->
  275. io:format("Error ~b: ~0p~n", [Status, Errors]),
  276. halt(88)
  277. end
  278. endef
  279. hex-docs-publish: hex-core hex-docs-tarball-create
  280. $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
  281. $(gen_verbose) $(call erlang,$(call hex_docs_publish.erl,$(HEX_SECRET)))
  282. define hex_docs_delete.erl
  283. {ok, _} = application:ensure_all_started(ssl),
  284. {ok, _} = application:ensure_all_started(inets),
  285. Config = $(hex_config.erl),
  286. ConfigF = Config#{api_key => <<"$(strip $1)">>},
  287. case hex_api:delete(ConfigF,
  288. ["packages", "$(strip $(PROJECT))", "releases", "$(strip $2)", "docs"]) of
  289. {ok, {Status, _, _}} when Status >= 200, Status < 300 ->
  290. io:format("Docs removed~n"),
  291. halt(0);
  292. {ok, {Status, _, Errors}} ->
  293. io:format("Error ~b: ~0p~n", [Status, Errors]),
  294. halt(89)
  295. end
  296. endef
  297. hex-docs-delete: hex-core
  298. $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info )))
  299. $(gen_verbose) $(call erlang,$(call hex_docs_delete.erl,$(HEX_SECRET),\
  300. $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION))))