From f4fd6efe1605245be80019480412592d5dc18e5e Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Mon, 26 Feb 2018 23:29:53 +0800 Subject: [PATCH] Merge the emqx-common, emqx-router libraries --- erlang.mk | 1203 ++++++++++++++++++++++----------- etc/emqx.conf | 49 +- include/emqx.hrl | 9 +- include/emqx_common.hrl | 13 + include/emqx_trie.hrl | 4 +- priv/emqx.schema | 75 +- src/emqx_app.erl | 2 + src/emqx_base62.erl | 4 +- src/emqx_boot.erl | 2 - src/emqx_gc.erl | 2 - src/emqx_keepalive.erl | 6 +- src/emqx_misc.erl | 4 +- src/emqx_mod_presence.erl | 73 ++ src/emqx_mod_rewrite.erl | 91 +++ src/emqx_mod_subscription.erl | 61 ++ src/emqx_modules.erl | 33 + src/emqx_net.erl | 2 - src/emqx_pmon.erl | 4 +- src/emqx_router.erl | 100 ++- src/emqx_router_sup.erl | 38 ++ src/emqx_time.erl | 4 +- src/emqx_topic.erl | 14 +- src/emqx_trie.erl | 38 +- test/emqx_base62_SUITE.erl | 35 + test/emqx_guid_SUITE.erl | 41 ++ test/emqx_inflight_SUITE.erl | 2 - test/emqx_keepalive_SUITE.erl | 43 ++ test/emqx_misc_SUITE.erl | 46 ++ test/emqx_pqueue_SUITE.erl | 69 ++ test/emqx_router_SUITE.erl | 43 +- test/emqx_time_SUITE.erl | 28 + test/emqx_trie_SUITE.erl | 2 +- 32 files changed, 1621 insertions(+), 519 deletions(-) create mode 100644 include/emqx_common.hrl create mode 100644 src/emqx_mod_presence.erl create mode 100644 src/emqx_mod_rewrite.erl create mode 100644 src/emqx_mod_subscription.erl create mode 100644 src/emqx_modules.erl create mode 100644 src/emqx_router_sup.erl create mode 100644 test/emqx_base62_SUITE.erl create mode 100644 test/emqx_guid_SUITE.erl create mode 100644 test/emqx_keepalive_SUITE.erl create mode 100644 test/emqx_misc_SUITE.erl create mode 100644 test/emqx_pqueue_SUITE.erl create mode 100644 test/emqx_time_SUITE.erl diff --git a/erlang.mk b/erlang.mk index f38d22653..adf9d98b8 100644 --- a/erlang.mk +++ b/erlang.mk @@ -1,4 +1,4 @@ -# Copyright (c) 2013-2015, Loïc Hoguin +# Copyright (c) 2013-2016, Loïc Hoguin # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -12,11 +12,23 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.PHONY: all app apps deps search rel docs install-docs check tests clean distclean help erlang-mk +.PHONY: all app apps deps search rel relup docs install-docs check tests clean distclean help erlang-mk ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST))) +export ERLANG_MK_FILENAME -ERLANG_MK_VERSION = 2.0.0-pre.2-130-gc6fe5ea +ERLANG_MK_VERSION = 2017.08.28-22-gf545564 +ERLANG_MK_WITHOUT = + +# Make 3.81 and 3.82 are deprecated. + +ifeq ($(MAKE_VERSION),3.81) +$(warning Please upgrade to GNU Make 4 or later: https://erlang.mk/guide/installation.html) +endif + +ifeq ($(MAKE_VERSION),3.82) +$(warning Please upgrade to GNU Make 4 or later: https://erlang.mk/guide/installation.html) +endif # Core configuration. @@ -25,6 +37,7 @@ PROJECT := $(strip $(PROJECT)) PROJECT_VERSION ?= rolling PROJECT_MOD ?= $(PROJECT)_app +PROJECT_ENV ?= [] # Verbosity. @@ -85,6 +98,8 @@ all:: deps app rel rel:: $(verbose) : +relup:: deps app + check:: tests clean:: clean-crashdump @@ -102,7 +117,7 @@ distclean-tmp: help:: $(verbose) printf "%s\n" \ "erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \ - "Copyright (c) 2013-2015 Loïc Hoguin " \ + "Copyright (c) 2013-2016 Loïc Hoguin " \ "" \ "Usage: [V=1] $(MAKE) [target]..." \ "" \ @@ -110,6 +125,8 @@ help:: " all Run deps, app and rel targets in that order" \ " app Compile the project" \ " deps Fetch dependencies (if needed) and compile them" \ + " fetch-deps Fetch dependencies recursively (if needed) without compiling them" \ + " list-deps List dependencies recursively on stdout" \ " search q=... Search for a package in the built-in index" \ " rel Build a release for this project, if applicable" \ " docs Build the documentation for this project" \ @@ -148,30 +165,7 @@ else core_native_path = $1 endif -ifeq ($(shell which wget 2>/dev/null | wc -l), 1) -define core_http_get - wget --no-check-certificate -O $(1) $(2)|| rm $(1) -endef -else -define core_http_get.erl - ssl:start(), - inets:start(), - case httpc:request(get, {"$(2)", []}, [{autoredirect, true}], []) of - {ok, {{_, 200, _}, _, Body}} -> - case file:write_file("$(1)", Body) of - ok -> ok; - {error, R1} -> halt(R1) - end; - {error, R2} -> - halt(R2) - end, - halt(0). -endef - -define core_http_get - $(call erlang,$(call core_http_get.erl,$(call core_native_path,$1),$2)) -endef -endif +core_http_get = curl -Lf$(if $(filter-out 0,$(V)),,s)o $(call core_native_path,$1) $2 core_eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))) @@ -191,17 +185,102 @@ ERLANG_MK_COMMIT ?= ERLANG_MK_BUILD_CONFIG ?= build.config ERLANG_MK_BUILD_DIR ?= .erlang.mk.build +erlang-mk: WITHOUT ?= $(ERLANG_MK_WITHOUT) erlang-mk: git clone $(ERLANG_MK_REPO) $(ERLANG_MK_BUILD_DIR) ifdef ERLANG_MK_COMMIT cd $(ERLANG_MK_BUILD_DIR) && git checkout $(ERLANG_MK_COMMIT) endif if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR)/build.config; fi - $(MAKE) -C $(ERLANG_MK_BUILD_DIR) + $(MAKE) -C $(ERLANG_MK_BUILD_DIR) WITHOUT='$(strip $(WITHOUT))' cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk rm -rf $(ERLANG_MK_BUILD_DIR) -# Copyright (c) 2015, Loïc Hoguin +# The erlang.mk package index is bundled in the default erlang.mk build. +# Search for the string "copyright" to skip to the rest of the code. + +# Copyright (c) 2015-2017, Loïc Hoguin +# This file is part of erlang.mk and subject to the terms of the ISC License. + +.PHONY: distclean-kerl + +KERL_INSTALL_DIR ?= $(HOME)/erlang + +ifeq ($(strip $(KERL)),) +KERL := $(ERLANG_MK_TMP)/kerl/kerl +endif + +export KERL + +KERL_GIT ?= https://github.com/kerl/kerl +KERL_COMMIT ?= master + +KERL_MAKEFLAGS ?= + +OTP_GIT ?= https://github.com/erlang/otp + +define kerl_otp_target +ifeq ($(wildcard $(KERL_INSTALL_DIR)/$(1)),) +$(KERL_INSTALL_DIR)/$(1): $(KERL) + MAKEFLAGS="$(KERL_MAKEFLAGS)" $(KERL) build git $(OTP_GIT) $(1) $(1) + $(KERL) install $(1) $(KERL_INSTALL_DIR)/$(1) +endif +endef + +define kerl_hipe_target +ifeq ($(wildcard $(KERL_INSTALL_DIR)/$1-native),) +$(KERL_INSTALL_DIR)/$1-native: $(KERL) + KERL_CONFIGURE_OPTIONS=--enable-native-libs \ + MAKEFLAGS="$(KERL_MAKEFLAGS)" $(KERL) build git $(OTP_GIT) $1 $1-native + $(KERL) install $1-native $(KERL_INSTALL_DIR)/$1-native +endif +endef + +$(KERL): + $(verbose) mkdir -p $(ERLANG_MK_TMP) + $(gen_verbose) git clone --depth 1 $(KERL_GIT) $(ERLANG_MK_TMP)/kerl + $(verbose) cd $(ERLANG_MK_TMP)/kerl && git checkout $(KERL_COMMIT) + $(verbose) chmod +x $(KERL) + +distclean:: distclean-kerl + +distclean-kerl: + $(gen_verbose) rm -rf $(KERL) + +# Allow users to select which version of Erlang/OTP to use for a project. + +ERLANG_OTP ?= +ERLANG_HIPE ?= + +# Use kerl to enforce a specific Erlang/OTP version for a project. +ifneq ($(strip $(ERLANG_OTP)),) +export PATH := $(KERL_INSTALL_DIR)/$(ERLANG_OTP)/bin:$(PATH) +SHELL := env PATH=$(PATH) $(SHELL) +$(eval $(call kerl_otp_target,$(ERLANG_OTP))) + +# Build Erlang/OTP only if it doesn't already exist. +ifeq ($(wildcard $(KERL_INSTALL_DIR)/$(ERLANG_OTP))$(BUILD_ERLANG_OTP),) +$(info Building Erlang/OTP $(ERLANG_OTP)... Please wait...) +$(shell $(MAKE) $(KERL_INSTALL_DIR)/$(ERLANG_OTP) ERLANG_OTP=$(ERLANG_OTP) BUILD_ERLANG_OTP=1 >&2) +endif + +else +# Same for a HiPE enabled VM. +ifneq ($(strip $(ERLANG_HIPE)),) +export PATH := $(KERL_INSTALL_DIR)/$(ERLANG_HIPE)-native/bin:$(PATH) +SHELL := env PATH=$(PATH) $(SHELL) +$(eval $(call kerl_hipe_target,$(ERLANG_HIPE))) + +# Build Erlang/OTP only if it doesn't already exist. +ifeq ($(wildcard $(KERL_INSTALL_DIR)/$(ERLANG_HIPE))$(BUILD_ERLANG_OTP),) +$(info Building HiPE-enabled Erlang/OTP $(ERLANG_OTP)... Please wait...) +$(shell $(MAKE) $(KERL_INSTALL_DIR)/$(ERLANG_HIPE) ERLANG_HIPE=$(ERLANG_HIPE) BUILD_ERLANG_OTP=1 >&2) +endif + +endif +endif + +# Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: search @@ -228,10 +307,10 @@ else $(foreach p,$(PACKAGES),$(call pkg_print,$(p))) endif -# Copyright (c) 2013-2015, Loïc Hoguin +# Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. -.PHONY: distclean-deps +.PHONY: distclean-deps clean-tmp-deps.log # Configuration. @@ -251,11 +330,32 @@ export DEPS_DIR REBAR_DEPS_DIR = $(DEPS_DIR) export REBAR_DEPS_DIR +# External "early" plugins (see core/plugins.mk for regular plugins). +# They both use the core_dep_plugin macro. + +define core_dep_plugin +ifeq ($(2),$(PROJECT)) +-include $$(patsubst $(PROJECT)/%,%,$(1)) +else +-include $(DEPS_DIR)/$(1) + +$(DEPS_DIR)/$(1): $(DEPS_DIR)/$(2) ; +endif +endef + +DEP_EARLY_PLUGINS ?= + +$(foreach p,$(DEP_EARLY_PLUGINS),\ + $(eval $(if $(findstring /,$p),\ + $(call core_dep_plugin,$p,$(firstword $(subst /, ,$p))),\ + $(call core_dep_plugin,$p/early-plugins.mk,$p)))) + dep_name = $(if $(dep_$(1)),$(1),$(if $(pkg_$(1)_name),$(pkg_$(1)_name),$(1))) dep_repo = $(patsubst git://github.com/%,https://github.com/%, \ $(if $(dep_$(1)),$(word 2,$(dep_$(1))),$(pkg_$(1)_repo))) dep_commit = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(word 3,$(dep_$(1))),$(pkg_$(1)_commit))) +LOCAL_DEPS_DIRS = $(foreach a,$(LOCAL_DEPS),$(if $(wildcard $(APPS_DIR)/$(a)),$(APPS_DIR)/$(a))) ALL_APPS_DIRS = $(if $(wildcard $(APPS_DIR)/),$(filter-out $(APPS_DIR),$(shell find $(APPS_DIR) -maxdepth 1 -type d))) ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(foreach dep,$(filter-out $(IGNORE_DEPS),$(BUILD_DEPS) $(DEPS)),$(call dep_name,$(dep)))) @@ -278,10 +378,7 @@ dep_verbose = $(dep_verbose_$(V)) # Core targets. -ifdef IS_APP -apps:: -else -apps:: $(ALL_APPS_DIRS) +apps:: $(ALL_APPS_DIRS) clean-tmp-deps.log ifeq ($(IS_APP)$(IS_DEP),) $(verbose) rm -f $(ERLANG_MK_TMP)/apps.log endif @@ -289,36 +386,42 @@ endif # Create ebin directory for all apps to make sure Erlang recognizes them # as proper OTP applications when using -include_lib. This is a temporary # fix, a proper fix would be to compile apps/* in the right order. - $(verbose) for dep in $(ALL_APPS_DIRS) ; do \ - mkdir -p $$dep/ebin || exit $$?; \ +ifndef IS_APP + $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \ + mkdir -p $$dep/ebin; \ done - $(verbose) for dep in $(ALL_APPS_DIRS) ; do \ +endif +# at the toplevel: if LOCAL_DEPS is defined with at least one local app, only +# compile that list of apps. otherwise, compile everything. +# within an app: compile all LOCAL_DEPS that are (uncompiled) local apps + $(verbose) set -e; for dep in $(if $(LOCAL_DEPS_DIRS)$(IS_APP),$(LOCAL_DEPS_DIRS),$(ALL_APPS_DIRS)) ; do \ if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/apps.log; then \ :; \ else \ echo $$dep >> $(ERLANG_MK_TMP)/apps.log; \ - $(MAKE) -C $$dep IS_APP=1 || exit $$?; \ + $(MAKE) -C $$dep IS_APP=1; \ fi \ done + +clean-tmp-deps.log: +ifeq ($(IS_APP)$(IS_DEP),) + $(verbose) rm -f $(ERLANG_MK_TMP)/deps.log endif ifneq ($(SKIP_DEPS),) deps:: else -deps:: $(ALL_DEPS_DIRS) apps -ifeq ($(IS_APP)$(IS_DEP),) - $(verbose) rm -f $(ERLANG_MK_TMP)/deps.log -endif +deps:: $(ALL_DEPS_DIRS) apps clean-tmp-deps.log $(verbose) mkdir -p $(ERLANG_MK_TMP) - $(verbose) for dep in $(ALL_DEPS_DIRS) ; do \ + $(verbose) set -e; for dep in $(ALL_DEPS_DIRS) ; do \ if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/deps.log; then \ :; \ else \ echo $$dep >> $(ERLANG_MK_TMP)/deps.log; \ if [ -f $$dep/GNUmakefile ] || [ -f $$dep/makefile ] || [ -f $$dep/Makefile ]; then \ - $(MAKE) -C $$dep IS_DEP=1 || exit $$?; \ + $(MAKE) -C $$dep IS_DEP=1; \ else \ - echo "Error: No Makefile to build dependency $$dep."; \ + echo "Error: No Makefile to build dependency $$dep." >&2; \ exit 2; \ fi \ fi \ @@ -332,17 +435,18 @@ endif # in practice only Makefile is needed so far. define dep_autopatch if [ -f $(DEPS_DIR)/$(1)/erlang.mk ]; then \ + rm -rf $(DEPS_DIR)/$1/ebin/; \ $(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \ $(call dep_autopatch_erlang_mk,$(1)); \ elif [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \ - if [ 0 != `grep -c "include ../\w*\.mk" $(DEPS_DIR)/$(1)/Makefile` ]; then \ + if [ -f $(DEPS_DIR)/$1/rebar.lock ]; then \ + $(call dep_autopatch2,$1); \ + elif [ 0 != `grep -c "include ../\w*\.mk" $(DEPS_DIR)/$(1)/Makefile` ]; then \ $(call dep_autopatch2,$(1)); \ - elif [ 0 != `grep -ci rebar $(DEPS_DIR)/$(1)/Makefile` ]; then \ + elif [ 0 != `grep -ci "^[^#].*rebar" $(DEPS_DIR)/$(1)/Makefile` ]; then \ $(call dep_autopatch2,$(1)); \ - elif [ -n "`find $(DEPS_DIR)/$(1)/ -type f -name \*.mk -not -name erlang.mk -exec grep -i rebar '{}' \;`" ]; then \ + elif [ -n "`find $(DEPS_DIR)/$(1)/ -type f -name \*.mk -not -name erlang.mk -exec grep -i "^[^#].*rebar" '{}' \;`" ]; then \ $(call dep_autopatch2,$(1)); \ - else \ - $(call erlang,$(call dep_autopatch_app.erl,$(1))); \ fi \ else \ if [ ! -d $(DEPS_DIR)/$(1)/src/ ]; then \ @@ -354,11 +458,14 @@ define dep_autopatch endef define dep_autopatch2 + ! test -f $(DEPS_DIR)/$1/ebin/$1.app || \ + mv -n $(DEPS_DIR)/$1/ebin/$1.app $(DEPS_DIR)/$1/src/$1.app.src; \ + rm -f $(DEPS_DIR)/$1/ebin/$1.app; \ if [ -f $(DEPS_DIR)/$1/src/$1.app.src.script ]; then \ $(call erlang,$(call dep_autopatch_appsrc_script.erl,$(1))); \ fi; \ $(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \ - if [ -f $(DEPS_DIR)/$(1)/rebar -o -f $(DEPS_DIR)/$(1)/rebar.config -o -f $(DEPS_DIR)/$(1)/rebar.config.script ]; then \ + if [ -f $(DEPS_DIR)/$(1)/rebar -o -f $(DEPS_DIR)/$(1)/rebar.config -o -f $(DEPS_DIR)/$(1)/rebar.config.script -o -f $(DEPS_DIR)/$1/rebar.lock ]; then \ $(call dep_autopatch_fetch_rebar); \ $(call dep_autopatch_rebar,$(1)); \ else \ @@ -370,11 +477,15 @@ define dep_autopatch_noop printf "noop:\n" > $(DEPS_DIR)/$(1)/Makefile endef -# Overwrite erlang.mk with the current file by default. +# Replace "include erlang.mk" with a line that will load the parent Erlang.mk +# if given. Do it for all 3 possible Makefile file names. ifeq ($(NO_AUTOPATCH_ERLANG_MK),) define dep_autopatch_erlang_mk - echo "include $(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(DEPS_DIR)/app)/erlang.mk" \ - > $(DEPS_DIR)/$1/erlang.mk + for f in Makefile makefile GNUmakefile; do \ + if [ -f $(DEPS_DIR)/$1/$$f ]; then \ + sed -i.bak s/'include *erlang.mk'/'include $$(if $$(ERLANG_MK_FILENAME),$$(ERLANG_MK_FILENAME),erlang.mk)'/ $(DEPS_DIR)/$1/$$f; \ + fi \ + done endef else define dep_autopatch_erlang_mk @@ -393,7 +504,7 @@ define dep_autopatch_fetch_rebar if [ ! -d $(ERLANG_MK_TMP)/rebar ]; then \ git clone -q -n -- https://github.com/rebar/rebar $(ERLANG_MK_TMP)/rebar; \ cd $(ERLANG_MK_TMP)/rebar; \ - git checkout -q 791db716b5a3a7671e0b351f95ddf24b848ee173; \ + git checkout -q 576e12171ab8d69b048b827b92aa65d067deea01; \ $(MAKE); \ cd -; \ fi @@ -410,6 +521,7 @@ endef define dep_autopatch_rebar.erl application:load(rebar), application:set_env(rebar, log_level, debug), + rmemo:start(), Conf1 = case file:consult("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config)") of {ok, Conf0} -> Conf0; _ -> [] @@ -438,6 +550,10 @@ define dep_autopatch_rebar.erl Write("C_SRC_TYPE = rebar\n"), Write("DRV_CFLAGS = -fPIC\nexport DRV_CFLAGS\n"), Write(["ERLANG_ARCH = ", rebar_utils:wordsize(), "\nexport ERLANG_ARCH\n"]), + ToList = fun + (V) when is_atom(V) -> atom_to_list(V); + (V) when is_list(V) -> "'\\"" ++ V ++ "\\"'" + end, fun() -> Write("ERLC_OPTS = +debug_info\nexport ERLC_OPTS\n"), case lists:keyfind(erl_opts, 1, Conf) of @@ -445,16 +561,18 @@ define dep_autopatch_rebar.erl {_, ErlOpts} -> lists:foreach(fun ({d, D}) -> - Write("ERLC_OPTS += -D" ++ atom_to_list(D) ++ "=1\n"); + Write("ERLC_OPTS += -D" ++ ToList(D) ++ "=1\n"); + ({d, DKey, DVal}) -> + Write("ERLC_OPTS += -D" ++ ToList(DKey) ++ "=" ++ ToList(DVal) ++ "\n"); ({i, I}) -> Write(["ERLC_OPTS += -I ", I, "\n"]); ({platform_define, Regex, D}) -> case rebar_utils:is_arch(Regex) of - true -> Write("ERLC_OPTS += -D" ++ atom_to_list(D) ++ "=1\n"); + true -> Write("ERLC_OPTS += -D" ++ ToList(D) ++ "=1\n"); false -> ok end; ({parse_transform, PT}) -> - Write("ERLC_OPTS += +'{parse_transform, " ++ atom_to_list(PT) ++ "}'\n"); + Write("ERLC_OPTS += +'{parse_transform, " ++ ToList(PT) ++ "}'\n"); (_) -> ok end, ErlOpts) end, @@ -563,9 +681,9 @@ define dep_autopatch_rebar.erl [] -> ok; _ -> Write("\npre-app::\n\t$$\(MAKE) -f c_src/Makefile.erlang.mk\n"), - PortSpecWrite(io_lib:format("ERL_CFLAGS = -finline-functions -Wall -fPIC -I \\"~s/erts-~s/include\\" -I \\"~s\\"\n", + PortSpecWrite(io_lib:format("ERL_CFLAGS ?= -finline-functions -Wall -fPIC -I \\"~s/erts-~s/include\\" -I \\"~s\\"\n", [code:root_dir(), erlang:system_info(version), code:lib_dir(erl_interface, include)])), - PortSpecWrite(io_lib:format("ERL_LDFLAGS = -L \\"~s\\" -lerl_interface -lei\n", + PortSpecWrite(io_lib:format("ERL_LDFLAGS ?= -L \\"~s\\" -lerl_interface -lei\n", [code:lib_dir(erl_interface, lib)])), [PortSpecWrite(["\n", E, "\n"]) || E <- OsEnv], FilterEnv = fun(Env) -> @@ -604,7 +722,7 @@ define dep_autopatch_rebar.erl "%.o: %.C\n\t$$\(CXX) -c -o $$\@ $$\< $$\(CXXFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n", "%.o: %.cc\n\t$$\(CXX) -c -o $$\@ $$\< $$\(CXXFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n", "%.o: %.cpp\n\t$$\(CXX) -c -o $$\@ $$\< $$\(CXXFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n", - [[Output, ": ", K, " = ", ShellToMk(V), "\n"] || {K, V} <- lists:reverse(MergeEnv(FilterEnv(Env)))], + [[Output, ": ", K, " += ", ShellToMk(V), "\n"] || {K, V} <- lists:reverse(MergeEnv(FilterEnv(Env)))], Output, ": $$\(foreach ext,.c .C .cc .cpp,", "$$\(patsubst %$$\(ext),%.o,$$\(filter %$$\(ext),$$\(wildcard", Input, "))))\n", "\t$$\(CC) -o $$\@ $$\? $$\(LDFLAGS) $$\(ERL_LDFLAGS) $$\(DRV_LDFLAGS) $$\(EXE_LDFLAGS)", @@ -616,7 +734,7 @@ define dep_autopatch_rebar.erl end, [PortSpec(S) || S <- PortSpecs] end, - Write("\ninclude $(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(DEPS_DIR)/app)/erlang.mk"), + Write("\ninclude $$\(if $$\(ERLANG_MK_FILENAME),$$\(ERLANG_MK_FILENAME),erlang.mk)"), RunPlugin = fun(Plugin, Step) -> case erlang:function_exported(Plugin, Step, 2) of false -> ok; @@ -664,27 +782,14 @@ define dep_autopatch_rebar.erl halt() endef -define dep_autopatch_app.erl - UpdateModules = fun(App) -> - case filelib:is_regular(App) of - false -> ok; - true -> - {ok, [{application, '$(1)', L0}]} = file:consult(App), - Mods = filelib:fold_files("$(call core_native_path,$(DEPS_DIR)/$1/src)", "\\\\.erl$$", true, - fun (F, Acc) -> [list_to_atom(filename:rootname(filename:basename(F)))|Acc] end, []), - L = lists:keystore(modules, 1, L0, {modules, Mods}), - ok = file:write_file(App, io_lib:format("~p.~n", [{application, '$(1)', L}])) - end - end, - UpdateModules("$(call core_native_path,$(DEPS_DIR)/$1/ebin/$1.app)"), - halt() -endef - define dep_autopatch_appsrc_script.erl AppSrc = "$(call core_native_path,$(DEPS_DIR)/$1/src/$1.app.src)", AppSrcScript = AppSrc ++ ".script", - Bindings = erl_eval:new_bindings(), - {ok, Conf} = file:script(AppSrcScript, Bindings), + {ok, Conf0} = file:consult(AppSrc), + Bindings0 = erl_eval:new_bindings(), + Bindings1 = erl_eval:add_binding('CONFIG', Conf0, Bindings0), + Bindings = erl_eval:add_binding('SCRIPT', AppSrcScript, Bindings1), + {ok, [Conf]} = file:script(AppSrcScript, Bindings), ok = file:write_file(AppSrc, io_lib:format("~p.~n", [Conf])), halt() endef @@ -697,7 +802,11 @@ define dep_autopatch_appsrc.erl true -> {ok, [{application, $(1), L0}]} = file:consult(AppSrcIn), L1 = lists:keystore(modules, 1, L0, {modules, []}), - L2 = case lists:keyfind(vsn, 1, L1) of {_, git} -> lists:keyreplace(vsn, 1, L1, {vsn, "git"}); _ -> L1 end, + L2 = case lists:keyfind(vsn, 1, L1) of + {_, git} -> lists:keyreplace(vsn, 1, L1, {vsn, "git"}); + {_, {cmd, _}} -> lists:keyreplace(vsn, 1, L1, {vsn, "cmd"}); + _ -> L1 + end, L3 = case lists:keyfind(registered, 1, L2) of false -> [{registered, []}|L2]; _ -> L2 end, ok = file:write_file(AppSrcOut, io_lib:format("~p.~n", [{application, $(1), L3}])), case AppSrcOut of AppSrcIn -> ok; _ -> ok = file:delete(AppSrcIn) end @@ -727,21 +836,16 @@ define dep_fetch_cp cp -R $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); endef -define dep_fetch_hex.erl - ssl:start(), - inets:start(), - {ok, {{_, 200, _}, _, Body}} = httpc:request(get, - {"https://s3.amazonaws.com/s3.hex.pm/tarballs/$(1)-$(2).tar", []}, - [], [{body_format, binary}]), - {ok, Files} = erl_tar:extract({binary, Body}, [memory]), - {_, Source} = lists:keyfind("contents.tar.gz", 1, Files), - ok = erl_tar:extract({binary, Source}, [{cwd, "$(call core_native_path,$(DEPS_DIR)/$1)"}, compressed]), - halt() +define dep_fetch_ln + ln -s $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); endef # Hex only has a package version. No need to look in the Erlang.mk packages. define dep_fetch_hex - $(call erlang,$(call dep_fetch_hex.erl,$(1),$(strip $(word 2,$(dep_$(1)))))); + mkdir -p $(ERLANG_MK_TMP)/hex $(DEPS_DIR)/$1; \ + $(call core_http_get,$(ERLANG_MK_TMP)/hex/$1.tar,\ + https://s3.amazonaws.com/s3.hex.pm/tarballs/$1-$(strip $(word 2,$(dep_$1))).tar); \ + tar -xOf $(ERLANG_MK_TMP)/hex/$1.tar contents.tar.gz | tar -C $(DEPS_DIR)/$1 -xzf -; endef define dep_fetch_fail @@ -771,7 +875,7 @@ $(DEPS_DIR)/$(call dep_name,$1): $(eval DEP_NAME := $(call dep_name,$1)) $(eval DEP_STR := $(if $(filter-out $1,$(DEP_NAME)),$1,"$1 ($(DEP_NAME))")) $(verbose) if test -d $(APPS_DIR)/$(DEP_NAME); then \ - echo "Error: Dependency" $(DEP_STR) "conflicts with application found in $(APPS_DIR)/$(DEP_NAME)."; \ + echo "Error: Dependency" $(DEP_STR) "conflicts with application found in $(APPS_DIR)/$(DEP_NAME)." >&2; \ exit 17; \ fi $(verbose) mkdir -p $(DEPS_DIR) @@ -813,15 +917,15 @@ ifndef IS_APP clean:: clean-apps clean-apps: - $(verbose) for dep in $(ALL_APPS_DIRS) ; do \ - $(MAKE) -C $$dep clean IS_APP=1 || exit $$?; \ + $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \ + $(MAKE) -C $$dep clean IS_APP=1; \ done distclean:: distclean-apps distclean-apps: - $(verbose) for dep in $(ALL_APPS_DIRS) ; do \ - $(MAKE) -C $$dep distclean IS_APP=1 || exit $$?; \ + $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \ + $(MAKE) -C $$dep distclean IS_APP=1; \ done endif @@ -832,77 +936,16 @@ distclean-deps: $(gen_verbose) rm -rf $(DEPS_DIR) endif -# External plugins. +# Forward-declare variables used in core/deps-tools.mk. This is required +# in case plugins use them. -DEP_PLUGINS ?= +ERLANG_MK_RECURSIVE_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-deps-list.log +ERLANG_MK_RECURSIVE_DOC_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-doc-deps-list.log +ERLANG_MK_RECURSIVE_REL_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-rel-deps-list.log +ERLANG_MK_RECURSIVE_TEST_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-test-deps-list.log +ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-shell-deps-list.log -define core_dep_plugin --include $(DEPS_DIR)/$(1) - -$(DEPS_DIR)/$(1): $(DEPS_DIR)/$(2) ; -endef - -$(foreach p,$(DEP_PLUGINS),\ - $(eval $(if $(findstring /,$p),\ - $(call core_dep_plugin,$p,$(firstword $(subst /, ,$p))),\ - $(call core_dep_plugin,$p/plugins.mk,$p)))) - -# Copyright (c) 2013-2015, Loïc Hoguin -# This file is part of erlang.mk and subject to the terms of the ISC License. - -# Configuration. - -DTL_FULL_PATH ?= -DTL_PATH ?= templates/ -DTL_SUFFIX ?= _dtl -DTL_OPTS ?= - -# Verbosity. - -dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F)); -dtl_verbose = $(dtl_verbose_$(V)) - -# Core targets. - -DTL_FILES = $(sort $(call core_find,$(DTL_PATH),*.dtl)) - -ifneq ($(DTL_FILES),) - -ifdef DTL_FULL_PATH -BEAM_FILES += $(addprefix ebin/,$(patsubst %.dtl,%_dtl.beam,$(subst /,_,$(DTL_FILES:$(DTL_PATH)%=%)))) -else -BEAM_FILES += $(addprefix ebin/,$(patsubst %.dtl,%_dtl.beam,$(notdir $(DTL_FILES)))) -endif - -# Rebuild templates when the Makefile changes. -$(DTL_FILES): $(MAKEFILE_LIST) - @touch $@ - -define erlydtl_compile.erl - [begin - Module0 = case "$(strip $(DTL_FULL_PATH))" of - "" -> - filename:basename(F, ".dtl"); - _ -> - "$(DTL_PATH)" ++ F2 = filename:rootname(F, ".dtl"), - re:replace(F2, "/", "_", [{return, list}, global]) - end, - Module = list_to_atom(string:to_lower(Module0) ++ "$(DTL_SUFFIX)"), - case erlydtl:compile(F, Module, [$(DTL_OPTS)] ++ [{out_dir, "ebin/"}, return_errors, {doc_root, "templates"}]) of - ok -> ok; - {ok, _} -> ok - end - end || F <- string:tokens("$(1)", " ")], - halt(). -endef - -ebin/$(PROJECT).app:: $(DTL_FILES) | ebin/ - $(if $(strip $?),\ - $(dtl_verbose) $(call erlang,$(call erlydtl_compile.erl,$?),-pa ebin/ $(DEPS_DIR)/erlydtl/ebin/)) - -endif - -# Copyright (c) 2015, Loïc Hoguin +# Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. # Verbosity. @@ -921,10 +964,9 @@ endef define compile_proto.erl [begin - Dir = filename:dirname(filename:dirname(F)), protobuffs_compile:generate_source(F, - [{output_include_dir, Dir ++ "/include"}, - {output_src_dir, Dir ++ "/ebin"}]) + [{output_include_dir, "./include"}, + {output_src_dir, "./ebin"}]) end || F <- string:tokens("$(1)", " ")], halt(). endef @@ -934,7 +976,7 @@ ebin/$(PROJECT).app:: $(sort $(call core_find,src/,*.proto)) $(if $(strip $?),$(call compile_proto,$?)) endif -# Copyright (c) 2013-2015, Loïc Hoguin +# Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: clean-app @@ -948,6 +990,8 @@ COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST))) ERLC_EXCLUDE ?= ERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE))) +ERLC_ASN1_OPTS ?= + ERLC_MIB_OPTS ?= COMPILE_MIB_FIRST ?= COMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST))) @@ -997,25 +1041,27 @@ endif ifeq ($(wildcard src/$(PROJECT_MOD).erl),) define app_file -{application, $(PROJECT), [ +{application, '$(PROJECT)', [ {description, "$(PROJECT_DESCRIPTION)"}, {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP), {id$(comma)$(space)"$(1)"}$(comma)) {modules, [$(call comma_list,$(2))]}, {registered, []}, - {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS))]} + {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(foreach dep,$(DEPS),$(call dep_name,$(dep))))]}, + {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),) ]}. endef else define app_file -{application, $(PROJECT), [ +{application, '$(PROJECT)', [ {description, "$(PROJECT_DESCRIPTION)"}, {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP), {id$(comma)$(space)"$(1)"}$(comma)) {modules, [$(call comma_list,$(2))]}, {registered, [$(call comma_list,$(PROJECT)_sup $(PROJECT_REGISTERED))]}, - {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS))]}, - {mod, {$(PROJECT_MOD), []}} + {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(foreach dep,$(DEPS),$(call dep_name,$(dep))))]}, + {mod, {$(PROJECT_MOD), []}}, + {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),) ]}. endef endif @@ -1025,8 +1071,10 @@ app-build: ebin/$(PROJECT).app # Source files. -ERL_FILES = $(sort $(call core_find,src/,*.erl)) -CORE_FILES = $(sort $(call core_find,src/,*.core)) +ALL_SRC_FILES := $(sort $(call core_find,src/,*)) + +ERL_FILES := $(filter %.erl,$(ALL_SRC_FILES)) +CORE_FILES := $(filter %.core,$(ALL_SRC_FILES)) # ASN.1 files. @@ -1036,7 +1084,7 @@ ERL_FILES += $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES)))) define compile_asn1 $(verbose) mkdir -p include/ - $(asn1_verbose) erlc -v -I include/ -o asn1/ +noobj $(1) + $(asn1_verbose) erlc -v -I include/ -o asn1/ +noobj $(ERLC_ASN1_OPTS) $(1) $(verbose) mv asn1/*.erl src/ $(verbose) mv asn1/*.hrl include/ $(verbose) mv asn1/*.asn1db include/ @@ -1059,16 +1107,16 @@ endif # Leex and Yecc files. -XRL_FILES = $(sort $(call core_find,src/,*.xrl)) +XRL_FILES := $(filter %.xrl,$(ALL_SRC_FILES)) XRL_ERL_FILES = $(addprefix src/,$(patsubst %.xrl,%.erl,$(notdir $(XRL_FILES)))) ERL_FILES += $(XRL_ERL_FILES) -YRL_FILES = $(sort $(call core_find,src/,*.yrl)) +YRL_FILES := $(filter %.yrl,$(ALL_SRC_FILES)) YRL_ERL_FILES = $(addprefix src/,$(patsubst %.yrl,%.erl,$(notdir $(YRL_FILES)))) ERL_FILES += $(YRL_ERL_FILES) $(PROJECT).d:: $(XRL_FILES) $(YRL_FILES) - $(if $(strip $?),$(xyrl_verbose) erlc -v -o src/ $?) + $(if $(strip $?),$(xyrl_verbose) erlc -v -o src/ $(YRL_ERLC_OPTS) $?) # Erlang and Core Erlang files. @@ -1118,7 +1166,12 @@ define makedep.erl (F, Mod, include_lib, "$1/include/" ++ Hrl) -> AddHd(F, Mod, "include/" ++ Hrl); (F, Mod, include_lib, Hrl) -> AddHd(F, Mod, "include/" ++ Hrl); (F, Mod, import, {Imp, _}) -> - case filelib:is_file("src/" ++ atom_to_list(Imp) ++ ".erl") of + IsFile = + case lists:keyfind(Imp, 1, Modules) of + false -> false; + {_, FilePath} -> filelib:is_file(FilePath) + end, + case IsFile of false -> ok; true -> Add(Mod, Imp) end; @@ -1142,23 +1195,41 @@ define makedep.erl end || F <- ErlFiles], Depend = sofs:to_external(sofs:relation_to_family(sofs:relation(ets:tab2list(E)))), CompileFirst = [X || X <- lists:reverse(digraph_utils:topsort(G)), [] =/= digraph:in_neighbours(G, X)], + TargetPath = fun(Target) -> + case lists:keyfind(Target, 1, Modules) of + false -> ""; + {_, DepFile} -> + DirSubname = tl(string:tokens(filename:dirname(DepFile), "/")), + string:join(DirSubname ++ [atom_to_list(Target)], "/") + end + end, ok = file:write_file("$(1)", [ [[F, "::", [[" ", D] || D <- Deps], "; @touch \$$@\n"] || {F, Deps} <- Depend], - "\nCOMPILE_FIRST +=", [[" ", atom_to_list(CF)] || CF <- CompileFirst], "\n" + "\nCOMPILE_FIRST +=", [[" ", TargetPath(CF)] || CF <- CompileFirst], "\n" ]), halt() endef ifeq ($(if $(NO_MAKEDEP),$(wildcard $(PROJECT).d),),) -$(PROJECT).d:: $(ERL_FILES) $(call core_find,include/,*.hrl) +$(PROJECT).d:: $(ERL_FILES) $(call core_find,include/,*.hrl) $(MAKEFILE_LIST) $(makedep_verbose) $(call erlang,$(call makedep.erl,$@)) endif +ifneq ($(words $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES)),0) # Rebuild everything when the Makefile changes. -$(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES):: $(MAKEFILE_LIST) - @touch $@ +$(ERLANG_MK_TMP)/last-makefile-change: $(MAKEFILE_LIST) + $(verbose) mkdir -p $(ERLANG_MK_TMP) + $(verbose) if test -f $@; then \ + touch $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES); \ + touch -c $(PROJECT).d; \ + fi + $(verbose) touch $@ --include $(PROJECT).d +$(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES):: $(ERLANG_MK_TMP)/last-makefile-change +ebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change +endif + +include $(wildcard $(PROJECT).d) ebin/$(PROJECT).app:: ebin/ @@ -1177,7 +1248,7 @@ ebin/$(PROJECT).app:: $(ERL_FILES) $(CORE_FILES) $(wildcard src/$(PROJECT).app.s $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \ $(filter-out $(ERLC_EXCLUDE_PATHS),$(ERL_FILES) $(CORE_FILES) $(BEAM_FILES))))))) ifeq ($(wildcard src/$(PROJECT).app.src),) - $(app_verbose) printf "$(subst $(newline),\n,$(subst ",\",$(call app_file,$(GITDESCRIBE),$(MODULES))))" \ + $(app_verbose) printf '$(subst %,%%,$(subst $(newline),\n,$(subst ','\'',$(call app_file,$(GITDESCRIBE),$(MODULES)))))' \ > ebin/$(PROJECT).app else $(verbose) if [ -z "$$(grep -e '^[^%]*{\s*modules\s*,' src/$(PROJECT).app.src)" ]; then \ @@ -1201,6 +1272,7 @@ clean-app: endif +# Copyright (c) 2016, Loïc Hoguin # Copyright (c) 2015, Viktor Söderqvist # This file is part of erlang.mk and subject to the terms of the ISC License. @@ -1218,10 +1290,10 @@ ifneq ($(SKIP_DEPS),) doc-deps: else doc-deps: $(ALL_DOC_DEPS_DIRS) - $(verbose) for dep in $(ALL_DOC_DEPS_DIRS) ; do $(MAKE) -C $$dep; done + $(verbose) set -e; for dep in $(ALL_DOC_DEPS_DIRS) ; do $(MAKE) -C $$dep; done endif -# Copyright (c) 2015, Loïc Hoguin +# Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: rel-deps @@ -1238,10 +1310,10 @@ ifneq ($(SKIP_DEPS),) rel-deps: else rel-deps: $(ALL_REL_DEPS_DIRS) - $(verbose) for dep in $(ALL_REL_DEPS_DIRS) ; do $(MAKE) -C $$dep; done + $(verbose) set -e; for dep in $(ALL_REL_DEPS_DIRS) ; do $(MAKE) -C $$dep; done endif -# Copyright (c) 2015, Loïc Hoguin +# Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: test-deps test-dir test-build clean-test-dir @@ -1263,7 +1335,7 @@ ifneq ($(SKIP_DEPS),) test-deps: else test-deps: $(ALL_TEST_DEPS_DIRS) - $(verbose) for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep IS_DEP=1; done + $(verbose) set -e; for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep IS_DEP=1; done endif ifneq ($(wildcard $(TEST_DIR)),) @@ -1296,7 +1368,7 @@ ifneq ($(wildcard $(TEST_DIR)/*.beam),) endif endif -# Copyright (c) 2015, Loïc Hoguin +# Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: rebar.config @@ -1332,54 +1404,90 @@ $(eval export _compat_rebar_config) rebar.config: $(gen_verbose) echo "$${_compat_rebar_config}" > rebar.config -# Copyright (c) 2015, Loïc Hoguin +# Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. -.PHONY: asciidoc asciidoc-guide asciidoc-manual install-asciidoc distclean-asciidoc +ifeq ($(filter asciideck,$(DEPS) $(DOC_DEPS)),asciideck) -MAN_INSTALL_PATH ?= /usr/local/share/man -MAN_SECTIONS ?= 3 7 +.PHONY: asciidoc asciidoc-guide asciidoc-manual install-asciidoc distclean-asciidoc-guide distclean-asciidoc-manual + +# Core targets. docs:: asciidoc +distclean:: distclean-asciidoc-guide distclean-asciidoc-manual + +# Plugin-specific targets. + asciidoc: asciidoc-guide asciidoc-manual +# User guide. + ifeq ($(wildcard doc/src/guide/book.asciidoc),) asciidoc-guide: else -asciidoc-guide: distclean-asciidoc doc-deps +asciidoc-guide: distclean-asciidoc-guide doc-deps a2x -v -f pdf doc/src/guide/book.asciidoc && mv doc/src/guide/book.pdf doc/guide.pdf a2x -v -f chunked doc/src/guide/book.asciidoc && mv doc/src/guide/book.chunked/ doc/html/ + +distclean-asciidoc-guide: + $(gen_verbose) rm -rf doc/html/ doc/guide.pdf endif -ifeq ($(wildcard doc/src/manual/*.asciidoc),) +# Man pages. + +ASCIIDOC_MANUAL_FILES := $(wildcard doc/src/manual/*.asciidoc) + +ifeq ($(ASCIIDOC_MANUAL_FILES),) asciidoc-manual: else -asciidoc-manual: distclean-asciidoc doc-deps - for f in doc/src/manual/*.asciidoc ; do \ - a2x -v -f manpage $$f ; \ - done - for s in $(MAN_SECTIONS); do \ - mkdir -p doc/man$$s/ ; \ - mv doc/src/manual/*.$$s doc/man$$s/ ; \ - gzip doc/man$$s/*.$$s ; \ - done + +# Configuration. + +MAN_INSTALL_PATH ?= /usr/local/share/man +MAN_SECTIONS ?= 3 7 +MAN_PROJECT ?= $(shell echo $(PROJECT) | sed 's/^./\U&\E/') +MAN_VERSION ?= $(PROJECT_VERSION) + +# Plugin-specific targets. + +define asciidoc2man.erl +try + [begin + io:format(" ADOC ~s~n", [F]), + ok = asciideck:to_manpage(asciideck:parse_file(F), #{ + compress => gzip, + outdir => filename:dirname(F), + extra2 => "$(MAN_PROJECT) $(MAN_VERSION)", + extra3 => "$(MAN_PROJECT) Function Reference" + }) + end || F <- [$(shell echo $(addprefix $(comma)\",$(addsuffix \",$1)) | sed 's/^.//')]], + halt(0) +catch C:E -> + io:format("Exception ~p:~p~nStacktrace: ~p~n", [C, E, erlang:get_stacktrace()]), + halt(1) +end. +endef + +asciidoc-manual:: doc-deps + +asciidoc-manual:: $(ASCIIDOC_MANUAL_FILES) + $(call erlang,$(call asciidoc2man.erl,$?)) + $(foreach s,$(MAN_SECTIONS),mkdir -p doc/man$s/ && mv doc/src/manual/*.$s.gz doc/man$s/;) install-docs:: install-asciidoc install-asciidoc: asciidoc-manual - for s in $(MAN_SECTIONS); do \ - mkdir -p $(MAN_INSTALL_PATH)/man$$s/ ; \ - install -g `id -u` -o `id -g` -m 0644 doc/man$$s/*.gz $(MAN_INSTALL_PATH)/man$$s/ ; \ - done + $(foreach s,$(MAN_SECTIONS),\ + mkdir -p $(MAN_INSTALL_PATH)/man$s/ && \ + install -g `id -g` -o `id -u` -m 0644 doc/man$s/*.gz $(MAN_INSTALL_PATH)/man$s/;) + +distclean-asciidoc-manual: + $(gen_verbose) rm -rf $(addprefix doc/man,$(MAN_SECTIONS)) +endif endif -distclean:: distclean-asciidoc - -distclean-asciidoc: - $(gen_verbose) rm -rf doc/html/ doc/guide.pdf doc/man3/ doc/man7/ - -# Copyright (c) 2014-2015, Loïc Hoguin +# Copyright (c) 2014-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates @@ -1436,7 +1544,7 @@ ifdef SP define bs_Makefile PROJECT = $p PROJECT_DESCRIPTION = New project -PROJECT_VERSION = 0.0.1 +PROJECT_VERSION = 0.1.0 # Whitespace to be used when creating files from templates. SP = $(SP) @@ -1446,7 +1554,7 @@ else define bs_Makefile PROJECT = $p PROJECT_DESCRIPTION = New project -PROJECT_VERSION = 0.0.1 +PROJECT_VERSION = 0.1.0 endef endif @@ -1454,7 +1562,7 @@ endif define bs_apps_Makefile PROJECT = $p PROJECT_DESCRIPTION = New project -PROJECT_VERSION = 0.0.1 +PROJECT_VERSION = 0.1.0 include $(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(APPS_DIR)/app)/erlang.mk endef @@ -1474,7 +1582,7 @@ stop(_State) -> endef define bs_relx_config -{release, {$p_release, "1"}, [$p]}. +{release, {$p_release, "1"}, [$p, sasl, runtime_tools]}. {extended_start_script, true}. {sys_config, "rel/sys.config"}. {vm_args, "rel/vm.args"}. @@ -1825,9 +1933,6 @@ endif ifndef t $(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP]) endif -ifndef tpl_$(t) - $(error Unknown template) -endif ifndef n $(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP]) endif @@ -1840,7 +1945,7 @@ endif list-templates: $(verbose) echo Available templates: $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES)))) -# Copyright (c) 2014-2015, Loïc Hoguin +# Copyright (c) 2014-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: clean-c_src distclean-c_src-env @@ -2075,57 +2180,56 @@ else $(call render_template,bs_erl_nif,src/$n.erl) endif -# Copyright (c) 2015, Loïc Hoguin +# Copyright (c) 2015-2017, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. -.PHONY: ci ci-setup distclean-kerl +.PHONY: ci ci-prepare ci-setup -KERL ?= $(CURDIR)/kerl -export KERL - -KERL_URL ?= https://raw.githubusercontent.com/yrashk/kerl/master/kerl - -OTP_GIT ?= https://github.com/erlang/otp - -CI_INSTALL_DIR ?= $(HOME)/erlang CI_OTP ?= +CI_HIPE ?= +CI_ERLLVM ?= -ifeq ($(strip $(CI_OTP)),) +ifeq ($(CI_VM),native) +ERLC_OPTS += +native +TEST_ERLC_OPTS += +native +else ifeq ($(CI_VM),erllvm) +ERLC_OPTS += +native +'{hipe, [to_llvm]}' +TEST_ERLC_OPTS += +native +'{hipe, [to_llvm]}' +endif + +ifeq ($(strip $(CI_OTP) $(CI_HIPE) $(CI_ERLLVM)),) ci:: else -ci:: $(addprefix ci-,$(CI_OTP)) -ci-prepare: $(addprefix $(CI_INSTALL_DIR)/,$(CI_OTP)) +ci:: $(addprefix ci-,$(CI_OTP) $(addsuffix -native,$(CI_HIPE)) $(addsuffix -erllvm,$(CI_ERLLVM))) + +ci-prepare: $(addprefix $(KERL_INSTALL_DIR)/,$(CI_OTP) $(addsuffix -native,$(CI_HIPE))) ci-setup:: +ci-extra:: + ci_verbose_0 = @echo " CI " $(1); ci_verbose = $(ci_verbose_$(V)) define ci_target -ci-$(1): $(CI_INSTALL_DIR)/$(1) +ci-$1: $(KERL_INSTALL_DIR)/$2 + $(verbose) $(MAKE) --no-print-directory clean $(ci_verbose) \ - PATH="$(CI_INSTALL_DIR)/$(1)/bin:$(PATH)" \ - CI_OTP_RELEASE="$(1)" \ - CT_OPTS="-label $(1)" \ - $(MAKE) clean ci-setup tests + PATH="$(KERL_INSTALL_DIR)/$2/bin:$(PATH)" \ + CI_OTP_RELEASE="$1" \ + CT_OPTS="-label $1" \ + CI_VM="$3" \ + $(MAKE) ci-setup tests + $(verbose) $(MAKE) --no-print-directory ci-extra endef -$(foreach otp,$(CI_OTP),$(eval $(call ci_target,$(otp)))) +$(foreach otp,$(CI_OTP),$(eval $(call ci_target,$(otp),$(otp),otp))) +$(foreach otp,$(CI_HIPE),$(eval $(call ci_target,$(otp)-native,$(otp)-native,native))) +$(foreach otp,$(CI_ERLLVM),$(eval $(call ci_target,$(otp)-erllvm,$(otp)-native,erllvm))) -define ci_otp_target -ifeq ($(wildcard $(CI_INSTALL_DIR)/$(1)),) -$(CI_INSTALL_DIR)/$(1): $(KERL) - $(KERL) build git $(OTP_GIT) $(1) $(1) - $(KERL) install $(1) $(CI_INSTALL_DIR)/$(1) -endif -endef - -$(foreach otp,$(CI_OTP),$(eval $(call ci_otp_target,$(otp)))) - -$(KERL): - $(gen_verbose) $(call core_http_get,$(KERL),$(KERL_URL)) - $(verbose) chmod +x $(KERL) +$(foreach otp,$(CI_OTP),$(eval $(call kerl_otp_target,$(otp)))) +$(foreach otp,$(sort $(CI_HIPE) $(CI_ERLLLVM)),$(eval $(call kerl_hipe_target,$(otp)))) help:: $(verbose) printf "%s\n" "" \ @@ -2135,13 +2239,9 @@ help:: "The CI_OTP variable must be defined with the Erlang versions" \ "that must be tested. For example: CI_OTP = OTP-17.3.4 OTP-17.5.3" -distclean:: distclean-kerl - -distclean-kerl: - $(gen_verbose) rm -rf $(KERL) endif -# Copyright (c) 2013-2015, Loïc Hoguin +# Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: ct apps-ct distclean-ct @@ -2149,11 +2249,14 @@ endif # Configuration. CT_OPTS ?= + ifneq ($(wildcard $(TEST_DIR)),) - CT_SUITES ?= $(sort $(subst _SUITE.erl,,$(notdir $(call core_find,$(TEST_DIR)/,*_SUITE.erl)))) -else - CT_SUITES ?= +ifndef CT_SUITES +CT_SUITES := $(sort $(subst _SUITE.erl,,$(notdir $(call core_find,$(TEST_DIR)/,*_SUITE.erl)))) endif +endif +CT_SUITES ?= +CT_LOGS_DIR ?= $(CURDIR)/logs # Core targets. @@ -2174,17 +2277,20 @@ help:: CT_RUN = ct_run \ -no_auto_compile \ -noinput \ - -pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin $(DEPS_DIR)/gen_rpc/_build/dev/lib/*/ebin $(APPS_DIR)/*/ebin $(TEST_DIR) \ + -pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin $(APPS_DIR)/*/ebin $(TEST_DIR) \ -dir $(TEST_DIR) \ - -logdir $(CURDIR)/logs + -logdir $(CT_LOGS_DIR) ifeq ($(CT_SUITES),) ct: $(if $(IS_APP),,apps-ct) else +# We do not run tests if we are in an apps/* with no test directory. +ifneq ($(IS_APP)$(wildcard $(TEST_DIR)),1) ct: test-build $(if $(IS_APP),,apps-ct) - $(verbose) mkdir -p $(CURDIR)/logs/ + $(verbose) mkdir -p $(CT_LOGS_DIR) $(gen_verbose) $(CT_RUN) -sname ct_$(PROJECT) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS) endif +endif ifneq ($(ALL_APPS_DIRS),) define ct_app_target @@ -2210,16 +2316,16 @@ endif define ct_suite_target ct-$(1): test-build - $(verbose) mkdir -p $(CURDIR)/logs/ + $(verbose) mkdir -p $(CT_LOGS_DIR) $(gen_verbose) $(CT_RUN) -sname ct_$(PROJECT) -suite $(addsuffix _SUITE,$(1)) $(CT_EXTRA) $(CT_OPTS) endef $(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test)))) distclean-ct: - $(gen_verbose) rm -rf $(CURDIR)/logs/ + $(gen_verbose) rm -rf $(CT_LOGS_DIR) -# Copyright (c) 2013-2015, Loïc Hoguin +# Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: plt distclean-plt dialyze @@ -2248,19 +2354,25 @@ help:: # Plugin-specific targets. define filter_opts.erl - Opts = binary:split(<<"$1">>, <<"-">>, [global]), - Filtered = lists:reverse(lists:foldl(fun - (O = <<"pa ", _/bits>>, Acc) -> [O|Acc]; - (O = <<"D ", _/bits>>, Acc) -> [O|Acc]; - (O = <<"I ", _/bits>>, Acc) -> [O|Acc]; - (_, Acc) -> Acc - end, [], Opts)), - io:format("~s~n", [[["-", O] || O <- Filtered]]), + Opts = init:get_plain_arguments(), + {Filtered, _} = lists:foldl(fun + (O, {Os, true}) -> {[O|Os], false}; + (O = "-D", {Os, _}) -> {[O|Os], true}; + (O = [\\$$-, \\$$D, _ | _], {Os, _}) -> {[O|Os], false}; + (O = "-I", {Os, _}) -> {[O|Os], true}; + (O = [\\$$-, \\$$I, _ | _], {Os, _}) -> {[O|Os], false}; + (O = "-pa", {Os, _}) -> {[O|Os], true}; + (_, Acc) -> Acc + end, {[], false}, Opts), + io:format("~s~n", [string:join(lists:reverse(Filtered), " ")]), halt(). endef $(DIALYZER_PLT): deps app - $(verbose) dialyzer --build_plt --apps erts kernel stdlib $(PLT_APPS) $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS) + $(eval DEPS_LOG := $(shell test -f $(ERLANG_MK_TMP)/deps.log && \ + while read p; do test -d $$p/ebin && echo $$p/ebin; done <$(ERLANG_MK_TMP)/deps.log)) + $(verbose) dialyzer --build_plt --apps erts kernel stdlib \ + $(PLT_APPS) $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS_LOG) plt: $(DIALYZER_PLT) @@ -2272,9 +2384,9 @@ dialyze: else dialyze: $(DIALYZER_PLT) endif - $(verbose) dialyzer --no_native `$(call erlang,$(call filter_opts.erl,$(ERLC_OPTS)))` $(DIALYZER_DIRS) $(DIALYZER_OPTS) + $(verbose) dialyzer --no_native `$(ERL) -eval "$(subst $(newline),,$(subst ",\",$(call filter_opts.erl)))" -extra $(ERLC_OPTS)` $(DIALYZER_DIRS) $(DIALYZER_OPTS) -# Copyright (c) 2013-2015, Loïc Hoguin +# Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: distclean-edoc edoc @@ -2282,10 +2394,21 @@ endif # Configuration. EDOC_OPTS ?= +EDOC_SRC_DIRS ?= +EDOC_OUTPUT ?= doc + +define edoc.erl + SrcPaths = lists:foldl(fun(P, Acc) -> + filelib:wildcard(atom_to_list(P) ++ "/{src,c_src}") ++ Acc + end, [], [$(call comma_list,$(patsubst %,'%',$(EDOC_SRC_DIRS)))]), + DefaultOpts = [{dir, "$(EDOC_OUTPUT)"}, {source_path, SrcPaths}, {subpackages, false}], + edoc:application($(1), ".", [$(2)] ++ DefaultOpts), + halt(0). +endef # Core targets. -ifneq ($(wildcard doc/overview.edoc),) +ifneq ($(strip $(EDOC_SRC_DIRS)$(wildcard doc/overview.edoc)),) docs:: edoc endif @@ -2294,30 +2417,91 @@ distclean:: distclean-edoc # Plugin-specific targets. edoc: distclean-edoc doc-deps - $(gen_verbose) $(ERL) -eval 'edoc:application($(PROJECT), ".", [$(EDOC_OPTS)]), halt().' + $(gen_verbose) $(call erlang,$(call edoc.erl,$(PROJECT),$(EDOC_OPTS))) distclean-edoc: - $(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info + $(gen_verbose) rm -f $(EDOC_OUTPUT)/*.css $(EDOC_OUTPUT)/*.html $(EDOC_OUTPUT)/*.png $(EDOC_OUTPUT)/edoc-info -# Copyright (c) 2014 Dave Cottlehuber +# Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. -.PHONY: distclean-escript escript +# Configuration. + +DTL_FULL_PATH ?= +DTL_PATH ?= templates/ +DTL_SUFFIX ?= _dtl +DTL_OPTS ?= + +# Verbosity. + +dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F)); +dtl_verbose = $(dtl_verbose_$(V)) + +# Core targets. + +DTL_PATH := $(abspath $(DTL_PATH)) +DTL_FILES := $(sort $(call core_find,$(DTL_PATH),*.dtl)) + +ifneq ($(DTL_FILES),) + +DTL_NAMES = $(addsuffix $(DTL_SUFFIX),$(DTL_FILES:$(DTL_PATH)/%.dtl=%)) +DTL_MODULES = $(if $(DTL_FULL_PATH),$(subst /,_,$(DTL_NAMES)),$(notdir $(DTL_NAMES))) +BEAM_FILES += $(addsuffix .beam,$(addprefix ebin/,$(DTL_MODULES))) + +ifneq ($(words $(DTL_FILES)),0) +# Rebuild templates when the Makefile changes. +$(ERLANG_MK_TMP)/last-makefile-change-erlydtl: $(MAKEFILE_LIST) + @mkdir -p $(ERLANG_MK_TMP) + @if test -f $@; then \ + touch $(DTL_FILES); \ + fi + @touch $@ + +ebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change-erlydtl +endif + +define erlydtl_compile.erl + [begin + Module0 = case "$(strip $(DTL_FULL_PATH))" of + "" -> + filename:basename(F, ".dtl"); + _ -> + "$(DTL_PATH)/" ++ F2 = filename:rootname(F, ".dtl"), + re:replace(F2, "/", "_", [{return, list}, global]) + end, + Module = list_to_atom(string:to_lower(Module0) ++ "$(DTL_SUFFIX)"), + case erlydtl:compile(F, Module, [$(DTL_OPTS)] ++ [{out_dir, "ebin/"}, return_errors]) of + ok -> ok; + {ok, _} -> ok + end + end || F <- string:tokens("$(1)", " ")], + halt(). +endef + +ebin/$(PROJECT).app:: $(DTL_FILES) | ebin/ + $(if $(strip $?),\ + $(dtl_verbose) $(call erlang,$(call erlydtl_compile.erl,$(call core_native_path,$?)),\ + -pa ebin/ $(DEPS_DIR)/erlydtl/ebin/)) + +endif + +# Copyright (c) 2016, Loïc Hoguin +# Copyright (c) 2014, Dave Cottlehuber +# This file is part of erlang.mk and subject to the terms of the ISC License. + +.PHONY: distclean-escript escript escript-zip # Configuration. ESCRIPT_NAME ?= $(PROJECT) ESCRIPT_FILE ?= $(ESCRIPT_NAME) -ESCRIPT_COMMENT ?= This is an -*- erlang -*- file - -ESCRIPT_BEAMS ?= "ebin/*", "deps/*/ebin/*" -ESCRIPT_SYS_CONFIG ?= "rel/sys.config" -ESCRIPT_EMU_ARGS ?= -pa . \ - -sasl errlog_type error \ - -escript main $(ESCRIPT_NAME) ESCRIPT_SHEBANG ?= /usr/bin/env escript -ESCRIPT_STATIC ?= "deps/*/priv/**", "priv/**" +ESCRIPT_COMMENT ?= This is an -*- erlang -*- file +ESCRIPT_EMU_ARGS ?= -escript main $(ESCRIPT_NAME) + +ESCRIPT_ZIP ?= 7z a -tzip -mx=9 -mtc=off $(if $(filter-out 0,$(V)),,> /dev/null) +ESCRIPT_ZIP_FILE ?= $(ERLANG_MK_TMP)/escript.zip # Core targets. @@ -2330,44 +2514,28 @@ help:: # Plugin-specific targets. -# Based on https://github.com/synrc/mad/blob/master/src/mad_bundle.erl -# Copyright (c) 2013 Maxim Sokhatsky, Synrc Research Center -# Modified MIT License, https://github.com/synrc/mad/blob/master/LICENSE : -# Software may only be used for the great good and the true happiness of all -# sentient beings. +escript-zip:: deps app + $(verbose) mkdir -p $(dir $(ESCRIPT_ZIP)) + $(verbose) rm -f $(ESCRIPT_ZIP_FILE) + $(gen_verbose) cd .. && $(ESCRIPT_ZIP) $(ESCRIPT_ZIP_FILE) $(PROJECT)/ebin/* +ifneq ($(DEPS),) + $(verbose) cd $(DEPS_DIR) && $(ESCRIPT_ZIP) $(ESCRIPT_ZIP_FILE) \ + `cat $(ERLANG_MK_TMP)/deps.log | sed 's/^$(subst /,\/,$(DEPS_DIR))\///' | sed 's/$$/\/ebin\/\*/'` +endif -define ESCRIPT_RAW -'Read = fun(F) -> {ok, B} = file:read_file(filename:absname(F)), B end,'\ -'Files = fun(L) -> A = lists:concat([filelib:wildcard(X)||X<- L ]),'\ -' [F || F <- A, not filelib:is_dir(F) ] end,'\ -'Squash = fun(L) -> [{filename:basename(F), Read(F) } || F <- L ] end,'\ -'Zip = fun(A, L) -> {ok,{_,Z}} = zip:create(A, L, [{compress,all},memory]), Z end,'\ -'Ez = fun(Escript) ->'\ -' Static = Files([$(ESCRIPT_STATIC)]),'\ -' Beams = Squash(Files([$(ESCRIPT_BEAMS), $(ESCRIPT_SYS_CONFIG)])),'\ -' Archive = Beams ++ [{ "static.gz", Zip("static.gz", Static)}],'\ -' escript:create(Escript, [ $(ESCRIPT_OPTIONS)'\ -' {archive, Archive, [memory]},'\ -' {shebang, "$(ESCRIPT_SHEBANG)"},'\ -' {comment, "$(ESCRIPT_COMMENT)"},'\ -' {emu_args, " $(ESCRIPT_EMU_ARGS)"}'\ -' ]),'\ -' file:change_mode(Escript, 8#755)'\ -'end,'\ -'Ez("$(ESCRIPT_FILE)"),'\ -'halt().' -endef - -ESCRIPT_COMMAND = $(subst ' ',,$(ESCRIPT_RAW)) - -escript:: distclean-escript deps app - $(gen_verbose) $(ERL) -eval $(ESCRIPT_COMMAND) +escript:: escript-zip + $(gen_verbose) printf "%s\n" \ + "#!$(ESCRIPT_SHEBANG)" \ + "%% $(ESCRIPT_COMMENT)" \ + "%%! $(ESCRIPT_EMU_ARGS)" > $(ESCRIPT_FILE) + $(verbose) cat $(ESCRIPT_ZIP_FILE) >> $(ESCRIPT_FILE) + $(verbose) chmod +x $(ESCRIPT_FILE) distclean-escript: - $(gen_verbose) rm -f $(ESCRIPT_NAME) + $(gen_verbose) rm -f $(ESCRIPT_FILE) +# Copyright (c) 2015-2016, Loïc Hoguin # Copyright (c) 2014, Enrique Fernandez -# Copyright (c) 2015, Loïc Hoguin # This file is contributed to erlang.mk and subject to the terms of the ISC License. .PHONY: eunit apps-eunit @@ -2404,7 +2572,7 @@ define eunit.erl case "$(COVER)" of "" -> ok; _ -> - cover:export("eunit.coverdata") + cover:export("$(COVER_DATA_DIR)/eunit.coverdata") end, halt() endef @@ -2413,10 +2581,10 @@ EUNIT_ERL_OPTS += -pa $(TEST_DIR) $(DEPS_DIR)/*/ebin $(APPS_DIR)/*/ebin $(CURDIR ifdef t ifeq (,$(findstring :,$(t))) -eunit: test-build +eunit: test-build cover-data-dir $(gen_verbose) $(call erlang,$(call eunit.erl,['$(t)']),$(EUNIT_ERL_OPTS)) else -eunit: test-build +eunit: test-build cover-data-dir $(gen_verbose) $(call erlang,$(call eunit.erl,fun $(t)/0),$(EUNIT_ERL_OPTS)) endif else @@ -2426,28 +2594,90 @@ EUNIT_TEST_MODS = $(notdir $(basename $(call core_find,$(TEST_DIR)/,*.erl))) EUNIT_MODS = $(foreach mod,$(EUNIT_EBIN_MODS) $(filter-out \ $(patsubst %,%_tests,$(EUNIT_EBIN_MODS)),$(EUNIT_TEST_MODS)),'$(mod)') -eunit: test-build $(if $(IS_APP),,apps-eunit) +eunit: test-build $(if $(IS_APP),,apps-eunit) cover-data-dir $(gen_verbose) $(call erlang,$(call eunit.erl,[$(call comma_list,$(EUNIT_MODS))]),$(EUNIT_ERL_OPTS)) ifneq ($(ALL_APPS_DIRS),) apps-eunit: - $(verbose) for app in $(ALL_APPS_DIRS); do $(MAKE) -C $$app eunit IS_APP=1; done + $(verbose) eunit_retcode=0 ; for app in $(ALL_APPS_DIRS); do $(MAKE) -C $$app eunit IS_APP=1; \ + [ $$? -ne 0 ] && eunit_retcode=1 ; done ; \ + exit $$eunit_retcode endif endif -# Copyright (c) 2013-2015, Loïc Hoguin +# Copyright (c) 2015-2017, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. -.PHONY: relx-rel distclean-relx-rel distclean-relx run +ifeq ($(filter proper,$(DEPS) $(TEST_DEPS)),proper) +.PHONY: proper + +# Targets. + +tests:: proper + +define proper_check.erl + code:add_pathsa(["$(call core_native_path,$(CURDIR)/ebin)", "$(call core_native_path,$(DEPS_DIR)/*/ebin)"]), + Module = fun(M) -> + [true] =:= lists:usort([ + case atom_to_list(F) of + "prop_" ++ _ -> + io:format("Testing ~p:~p/0~n", [M, F]), + proper:quickcheck(M:F(), nocolors); + _ -> + true + end + || {F, 0} <- M:module_info(exports)]) + end, + try + case $(1) of + all -> [true] =:= lists:usort([Module(M) || M <- [$(call comma_list,$(3))]]); + module -> Module($(2)); + function -> proper:quickcheck($(2), nocolors) + end + of + true -> halt(0); + _ -> halt(1) + catch error:undef -> + io:format("Undefined property or module?~n~p~n", [erlang:get_stacktrace()]), + halt(0) + end. +endef + +ifdef t +ifeq (,$(findstring :,$(t))) +proper: test-build + $(verbose) $(call erlang,$(call proper_check.erl,module,$(t))) +else +proper: test-build + $(verbose) echo Testing $(t)/0 + $(verbose) $(call erlang,$(call proper_check.erl,function,$(t)())) +endif +else +proper: test-build + $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename $(wildcard ebin/*.beam)))))) + $(gen_verbose) $(call erlang,$(call proper_check.erl,all,undefined,$(MODULES))) +endif +endif + +# Copyright (c) 2013-2016, Loïc Hoguin +# This file is part of erlang.mk and subject to the terms of the ISC License. + +.PHONY: relx-rel relx-relup distclean-relx-rel run # Configuration. -RELX ?= $(CURDIR)/relx +RELX ?= $(ERLANG_MK_TMP)/relx RELX_CONFIG ?= $(CURDIR)/relx.config -RELX_URL ?= https://github.com/erlware/relx/releases/download/v3.19.0/relx +RELX_URL ?= https://github.com/erlware/relx/releases/download/v3.23.0/relx RELX_OPTS ?= RELX_OUTPUT_DIR ?= _rel +RELX_REL_EXT ?= +RELX_TAR ?= 1 + +ifdef SFX + RELX_TAR = 1 +endif ifeq ($(firstword $(RELX_OPTS)),-o) RELX_OUTPUT_DIR = $(word 2,$(RELX_OPTS)) @@ -2460,10 +2690,12 @@ endif ifeq ($(IS_DEP),) ifneq ($(wildcard $(RELX_CONFIG)),) rel:: relx-rel + +relup:: relx-relup endif endif -distclean:: distclean-relx-rel distclean-relx +distclean:: distclean-relx-rel # Plugin-specific targets. @@ -2472,31 +2704,43 @@ $(RELX): $(verbose) chmod +x $(RELX) relx-rel: $(RELX) rel-deps app - $(verbose) $(RELX) -c $(RELX_CONFIG) $(RELX_OPTS) + $(verbose) $(RELX) -c $(RELX_CONFIG) $(RELX_OPTS) release $(if $(filter 1,$(RELX_TAR)),tar) + +relx-relup: $(RELX) rel-deps app + $(verbose) $(RELX) -c $(RELX_CONFIG) $(RELX_OPTS) release relup $(if $(filter 1,$(RELX_TAR)),tar) distclean-relx-rel: $(gen_verbose) rm -rf $(RELX_OUTPUT_DIR) -distclean-relx: - $(gen_verbose) rm -rf $(RELX) - # Run target. ifeq ($(wildcard $(RELX_CONFIG)),) -run: +run:: else define get_relx_release.erl - {ok, Config} = file:consult("$(RELX_CONFIG)"), - {release, {Name, _}, _} = lists:keyfind(release, 1, Config), - io:format("~s", [Name]), + {ok, Config} = file:consult("$(call core_native_path,$(RELX_CONFIG))"), + {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config), + Vsn = case Vsn0 of + {cmd, Cmd} -> os:cmd(Cmd); + semver -> ""; + {semver, _} -> ""; + VsnStr -> Vsn0 + end, + io:format("~s ~s", [Name, Vsn]), halt(0). endef -RELX_RELEASE = `$(call erlang,$(get_relx_release.erl))` +RELX_REL := $(shell $(call erlang,$(get_relx_release.erl))) +RELX_REL_NAME := $(word 1,$(RELX_REL)) +RELX_REL_VSN := $(word 2,$(RELX_REL)) -run: all - $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_RELEASE)/bin/$(RELX_RELEASE) console +ifeq ($(PLATFORM),msys2) +RELX_REL_EXT := .cmd +endif + +run:: all + $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) console help:: $(verbose) printf "%s\n" "" \ @@ -2505,8 +2749,8 @@ help:: endif +# Copyright (c) 2015-2016, Loïc Hoguin # Copyright (c) 2014, M Robert Martin -# Copyright (c) 2015, Loïc Hoguin # This file is contributed to erlang.mk and subject to the terms of the ISC License. .PHONY: shell @@ -2531,12 +2775,26 @@ help:: $(foreach dep,$(SHELL_DEPS),$(eval $(call dep_target,$(dep)))) build-shell-deps: $(ALL_SHELL_DEPS_DIRS) - $(verbose) for dep in $(ALL_SHELL_DEPS_DIRS) ; do $(MAKE) -C $$dep ; done + $(verbose) set -e; for dep in $(ALL_SHELL_DEPS_DIRS) ; do $(MAKE) -C $$dep ; done shell: build-shell-deps $(gen_verbose) $(SHELL_ERL) -pa $(SHELL_PATHS) $(SHELL_OPTS) -# Copyright (c) 2015, Loïc Hoguin +# Copyright (c) 2017, Jean-Sébastien Pédron +# This file is contributed to erlang.mk and subject to the terms of the ISC License. + +.PHONY: show-ERL_LIBS show-ERLC_OPTS show-TEST_ERLC_OPTS + +show-ERL_LIBS: + @echo $(ERL_LIBS) + +show-ERLC_OPTS: + @$(foreach opt,$(ERLC_OPTS) -pa ebin -I include,echo "$(opt)";) + +show-TEST_ERLC_OPTS: + @$(foreach opt,$(TEST_ERLC_OPTS) -pa ebin -I include,echo "$(opt)";) + +# Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. ifeq ($(filter triq,$(DEPS) $(TEST_DEPS)),triq) @@ -2547,7 +2805,10 @@ ifeq ($(filter triq,$(DEPS) $(TEST_DEPS)),triq) tests:: triq define triq_check.erl - code:add_pathsa(["$(CURDIR)/ebin", "$(DEPS_DIR)/*/ebin"]), + code:add_pathsa([ + "$(call core_native_path,$(CURDIR)/ebin)", + "$(call core_native_path,$(DEPS_DIR)/*/ebin)", + "$(call core_native_path,$(TEST_DIR))"]), try case $(1) of all -> [true] =:= lists:usort([triq:check(M) || M <- [$(call comma_list,$(3))]]); @@ -2558,7 +2819,7 @@ define triq_check.erl true -> halt(0); _ -> halt(1) catch error:undef -> - io:format("Undefined property or module~n"), + io:format("Undefined property or module?~n~p~n", [erlang:get_stacktrace()]), halt(0) end. endef @@ -2574,11 +2835,13 @@ triq: test-build endif else triq: test-build - $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename $(wildcard ebin/*.beam)))))) + $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \ + $(wildcard ebin/*.beam) $(call core_find,$(TEST_DIR)/,*.beam)))))) $(gen_verbose) $(call erlang,$(call triq_check.erl,all,undefined,$(MODULES))) endif endif +# Copyright (c) 2016, Loïc Hoguin # Copyright (c) 2015, Erlang Solutions Ltd. # This file is part of erlang.mk and subject to the terms of the ISC License. @@ -2587,22 +2850,22 @@ endif # Configuration. ifeq ($(XREF_CONFIG),) - XREF_ARGS := + XREFR_ARGS := else - XREF_ARGS := -c $(XREF_CONFIG) + XREFR_ARGS := -c $(XREF_CONFIG) endif XREFR ?= $(CURDIR)/xrefr export XREFR -XREFR_URL ?= https://github.com/inaka/xref_runner/releases/download/0.2.2/xrefr +XREFR_URL ?= https://github.com/inaka/xref_runner/releases/download/1.1.0/xrefr # Core targets. help:: - $(verbose) printf "%s\n" "" \ - "Xref targets:" \ - " xref Run Xrefr using $XREF_CONFIG as config file if defined" + $(verbose) printf '%s\n' '' \ + 'Xref targets:' \ + ' xref Run Xrefr using $$XREF_CONFIG as config file if defined' distclean:: distclean-xref @@ -2618,29 +2881,29 @@ xref: deps app $(XREFR) distclean-xref: $(gen_verbose) rm -rf $(XREFR) -# Copyright 2015, Viktor Söderqvist +# Copyright (c) 2016, Loïc Hoguin +# Copyright (c) 2015, Viktor Söderqvist # This file is part of erlang.mk and subject to the terms of the ISC License. -COVER_REPORT_DIR = cover +COVER_REPORT_DIR ?= cover +COVER_DATA_DIR ?= $(CURDIR) # Hook in coverage to ct ifdef COVER ifdef CT_RUN -# All modules in 'ebin' -COVER_MODS = $(notdir $(basename $(call core_ls,ebin/*.beam))) - +ifneq ($(wildcard $(TEST_DIR)),) test-build:: $(TEST_DIR)/ct.cover.spec -$(TEST_DIR)/ct.cover.spec: - $(verbose) echo Cover mods: $(COVER_MODS) +$(TEST_DIR)/ct.cover.spec: cover-data-dir $(gen_verbose) printf "%s\n" \ - '{incl_mods,[$(subst $(space),$(comma),$(COVER_MODS))]}.' \ - '{export,"$(CURDIR)/ct.coverdata"}.' > $@ + "{incl_app, '$(PROJECT)', details}." \ + '{export,"$(abspath $(COVER_DATA_DIR))/ct.coverdata"}.' > $@ CT_RUN += -cover $(TEST_DIR)/ct.cover.spec endif endif +endif # Core targets @@ -2649,6 +2912,13 @@ ifneq ($(COVER_REPORT_DIR),) tests:: $(verbose) $(MAKE) --no-print-directory cover-report endif + +cover-data-dir: | $(COVER_DATA_DIR) + +$(COVER_DATA_DIR): + $(verbose) mkdir -p $(COVER_DATA_DIR) +else +cover-data-dir: endif clean:: coverdata-clean @@ -2662,7 +2932,7 @@ help:: "Cover targets:" \ " cover-report Generate a HTML coverage report from previously collected" \ " cover data." \ - " all.coverdata Merge {eunit,ct}.coverdata into one coverdata file." \ + " all.coverdata Merge all coverdata files into all.coverdata." \ "" \ "If COVER=1 is set, coverage data is generated by the targets eunit and ct. The" \ "target tests additionally generates a HTML coverage report from the combined" \ @@ -2671,17 +2941,20 @@ help:: # Plugin specific targets -COVERDATA = $(filter-out all.coverdata,$(wildcard *.coverdata)) +COVERDATA = $(filter-out $(COVER_DATA_DIR)/all.coverdata,$(wildcard $(COVER_DATA_DIR)/*.coverdata)) .PHONY: coverdata-clean coverdata-clean: - $(gen_verbose) rm -f *.coverdata ct.cover.spec + $(gen_verbose) rm -f $(COVER_DATA_DIR)/*.coverdata $(TEST_DIR)/ct.cover.spec # Merge all coverdata files into one. -all.coverdata: $(COVERDATA) - $(gen_verbose) $(ERL) -eval ' \ - $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),) \ - cover:export("$@"), halt(0).' +define cover_export.erl + $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),) + cover:export("$(COVER_DATA_DIR)/$@"), halt(0). +endef + +all.coverdata: $(COVERDATA) cover-data-dir + $(gen_verbose) $(call erlang,$(cover_export.erl)) # These are only defined if COVER_REPORT_DIR is non-empty. Set COVER_REPORT_DIR to # empty if you want the coverdata files but not the HTML report. @@ -2691,6 +2964,7 @@ ifneq ($(COVER_REPORT_DIR),) cover-report-clean: $(gen_verbose) rm -rf $(COVER_REPORT_DIR) + $(if $(shell ls -A $(COVER_DATA_DIR)/),,$(verbose) rmdir $(COVER_DATA_DIR)) ifeq ($(COVERDATA),) cover-report: @@ -2699,7 +2973,7 @@ else # Modules which include eunit.hrl always contain one line without coverage # because eunit defines test/0 which is never called. We compensate for this. EUNIT_HRL_MODS = $(subst $(space),$(comma),$(shell \ - grep -e '^\s*-include.*include/eunit\.hrl"' src/*.erl \ + grep -H -e '^\s*-include.*include/eunit\.hrl"' src/*.erl \ | sed "s/^src\/\(.*\)\.erl:.*/'\1'/" | uniq)) define cover_report.erl @@ -2734,8 +3008,171 @@ define cover_report.erl endef cover-report: - $(gen_verbose) mkdir -p $(COVER_REPORT_DIR) + $(verbose) mkdir -p $(COVER_REPORT_DIR) $(gen_verbose) $(call erlang,$(cover_report.erl)) endif endif # ifneq ($(COVER_REPORT_DIR),) + +# Copyright (c) 2016, Loïc Hoguin +# This file is part of erlang.mk and subject to the terms of the ISC License. + +.PHONY: sfx + +ifdef RELX_REL +ifdef SFX + +# Configuration. + +SFX_ARCHIVE ?= $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/$(RELX_REL_NAME)-$(RELX_REL_VSN).tar.gz +SFX_OUTPUT_FILE ?= $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME).run + +# Core targets. + +rel:: sfx + +# Plugin-specific targets. + +define sfx_stub +#!/bin/sh + +TMPDIR=`mktemp -d` +ARCHIVE=`awk '/^__ARCHIVE_BELOW__$$/ {print NR + 1; exit 0;}' $$0` +FILENAME=$$(basename $$0) +REL=$${FILENAME%.*} + +tail -n+$$ARCHIVE $$0 | tar -xzf - -C $$TMPDIR + +$$TMPDIR/bin/$$REL console +RET=$$? + +rm -rf $$TMPDIR + +exit $$RET + +__ARCHIVE_BELOW__ +endef + +sfx: + $(call render_template,sfx_stub,$(SFX_OUTPUT_FILE)) + $(gen_verbose) cat $(SFX_ARCHIVE) >> $(SFX_OUTPUT_FILE) + $(verbose) chmod +x $(SFX_OUTPUT_FILE) + +endif +endif + +# Copyright (c) 2013-2017, Loïc Hoguin +# This file is part of erlang.mk and subject to the terms of the ISC License. + +# External plugins. + +DEP_PLUGINS ?= + +$(foreach p,$(DEP_PLUGINS),\ + $(eval $(if $(findstring /,$p),\ + $(call core_dep_plugin,$p,$(firstword $(subst /, ,$p))),\ + $(call core_dep_plugin,$p/plugins.mk,$p)))) + +# Copyright (c) 2013-2015, Loïc Hoguin +# Copyright (c) 2015-2016, Jean-Sébastien Pédron +# This file is part of erlang.mk and subject to the terms of the ISC License. + +# Fetch dependencies recursively (without building them). + +.PHONY: fetch-deps fetch-doc-deps fetch-rel-deps fetch-test-deps \ + fetch-shell-deps + +.PHONY: $(ERLANG_MK_RECURSIVE_DEPS_LIST) \ + $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \ + $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \ + $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \ + $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST) + +fetch-deps: $(ERLANG_MK_RECURSIVE_DEPS_LIST) +fetch-doc-deps: $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) +fetch-rel-deps: $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) +fetch-test-deps: $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) +fetch-shell-deps: $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST) + +ifneq ($(SKIP_DEPS),) +$(ERLANG_MK_RECURSIVE_DEPS_LIST) \ +$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \ +$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \ +$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \ +$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): + $(verbose) :> $@ +else +# By default, we fetch "normal" dependencies. They are also included no +# matter the type of requested dependencies. +# +# $(ALL_DEPS_DIRS) includes $(BUILD_DEPS). + +$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_DEPS_DIRS) +$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST): $(ALL_DEPS_DIRS) $(ALL_DOC_DEPS_DIRS) +$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST): $(ALL_DEPS_DIRS) $(ALL_REL_DEPS_DIRS) +$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST): $(ALL_DEPS_DIRS) $(ALL_TEST_DEPS_DIRS) +$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): $(ALL_DEPS_DIRS) $(ALL_SHELL_DEPS_DIRS) + +# Allow to use fetch-deps and $(DEP_TYPES) to fetch multiple types of +# dependencies with a single target. +ifneq ($(filter doc,$(DEP_TYPES)),) +$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_DOC_DEPS_DIRS) +endif +ifneq ($(filter rel,$(DEP_TYPES)),) +$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_REL_DEPS_DIRS) +endif +ifneq ($(filter test,$(DEP_TYPES)),) +$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_TEST_DEPS_DIRS) +endif +ifneq ($(filter shell,$(DEP_TYPES)),) +$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_SHELL_DEPS_DIRS) +endif + +ERLANG_MK_RECURSIVE_TMP_LIST := $(abspath $(ERLANG_MK_TMP)/recursive-tmp-deps.log) + +$(ERLANG_MK_RECURSIVE_DEPS_LIST) \ +$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \ +$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \ +$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \ +$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): +ifeq ($(IS_APP)$(IS_DEP),) + $(verbose) mkdir -p $(ERLANG_MK_TMP) + $(verbose) rm -f $(ERLANG_MK_RECURSIVE_TMP_LIST) +endif +ifndef IS_APP + $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \ + $(MAKE) -C $$dep $@ \ + IS_APP=1 \ + ERLANG_MK_RECURSIVE_TMP_LIST=$(ERLANG_MK_RECURSIVE_TMP_LIST); \ + done +endif + $(verbose) set -e; for dep in $^ ; do \ + if ! grep -qs ^$$dep$$ $(ERLANG_MK_RECURSIVE_TMP_LIST); then \ + echo $$dep >> $(ERLANG_MK_RECURSIVE_TMP_LIST); \ + if grep -qs -E "^[[:blank:]]*include[[:blank:]]+(erlang\.mk|.*/erlang\.mk|.*ERLANG_MK_FILENAME.*)$$" \ + $$dep/GNUmakefile $$dep/makefile $$dep/Makefile; then \ + $(MAKE) -C $$dep fetch-deps \ + IS_DEP=1 \ + ERLANG_MK_RECURSIVE_TMP_LIST=$(ERLANG_MK_RECURSIVE_TMP_LIST); \ + fi \ + fi \ + done +ifeq ($(IS_APP)$(IS_DEP),) + $(verbose) sort < $(ERLANG_MK_RECURSIVE_TMP_LIST) | uniq > $@ + $(verbose) rm $(ERLANG_MK_RECURSIVE_TMP_LIST) +endif +endif # ifneq ($(SKIP_DEPS),) + +# List dependencies recursively. + +.PHONY: list-deps list-doc-deps list-rel-deps list-test-deps \ + list-shell-deps + +list-deps: $(ERLANG_MK_RECURSIVE_DEPS_LIST) +list-doc-deps: $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) +list-rel-deps: $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) +list-test-deps: $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) +list-shell-deps: $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST) + +list-deps list-doc-deps list-rel-deps list-test-deps list-shell-deps: + $(verbose) cat $^ diff --git a/etc/emqx.conf b/etc/emqx.conf index beb8995e1..78aa90215 100644 --- a/etc/emqx.conf +++ b/etc/emqx.conf @@ -578,7 +578,7 @@ mqtt.bridge.max_queue_len = 10000 mqtt.bridge.ping_down_interval = 1s ##------------------------------------------------------------------- -## MQTT Plugins +## Plugins ##------------------------------------------------------------------- ## The etc dir for plugins' config. @@ -595,7 +595,52 @@ mqtt.plugins.loaded_file = {{ platform_data_dir }}/loaded_plugins mqtt.plugins.expand_plugins_dir = {{ platform_plugins_dir }}/ ##-------------------------------------------------------------------- -## MQTT Listeners +## Modules +##-------------------------------------------------------------------- + +##-------------------------------------------------------------------- +## Presence Module + +## Enable Presence Module. +## +## Value: on | off +module.presence = on + +## Sets the QoS for presence MQTT message. +## +## Value: 0 | 1 | 2 +module.presence.qos = 1 + +##-------------------------------------------------------------------- +## Subscription Module + +## Enable Subscription Module. +## +## Value: on | off +module.subscription = off + +## Subscribe the Topics automatically when client connected. +## module.subscription.1.topic = $client/%c +## Qos of the subscription: 0 | 1 | 2 +## module.subscription.1.qos = 1 + +## module.subscription.2.topic = $user/%u +## module.subscription.2.qos = 1 + +##-------------------------------------------------------------------- +## Rewrite Module + +## Enable Rewrite Module. +## +## Value: on | off +module.rewrite = off + +## {rewrite, Topic, Re, Dest} +## module.rewrite.rule.1 = x/# ^x/y/(.+)$ z/y/$1 +## module.rewrite.rule.2 = y/+/z/# ^y/(.+)/z/(.+)$ y/z/$2 + +##-------------------------------------------------------------------- +## Listeners ##-------------------------------------------------------------------- ##-------------------------------------------------------------------- diff --git a/include/emqx.hrl b/include/emqx.hrl index 7476446f1..1d80f009e 100644 --- a/include/emqx.hrl +++ b/include/emqx.hrl @@ -156,15 +156,12 @@ -type(mqtt_delivery() :: #mqtt_delivery{}). %%-------------------------------------------------------------------- -%% MQTT Route +%% Route %%-------------------------------------------------------------------- --record(mqtt_route, - { topic :: binary(), - node :: node() - }). +-record(route, { topic :: binary(), node :: node() }). --type(mqtt_route() :: #mqtt_route{}). +-type(route() :: #route{}). %%-------------------------------------------------------------------- %% MQTT Alarm diff --git a/include/emqx_common.hrl b/include/emqx_common.hrl new file mode 100644 index 000000000..f5ec9fbb2 --- /dev/null +++ b/include/emqx_common.hrl @@ -0,0 +1,13 @@ + +-define(record_to_map(Def, Rec), + maps:from_list(?record_to_proplist(Def, Rec))). + +-define(record_to_map(Def, Rec, Fields), + maps:from_list(?record_to_proplist(Def, Rec, Fields))). + +-define(record_to_proplist(Def, Rec), + lists:zip(record_info(fields, Def), tl(tuple_to_list(Rec)))). + +-define(record_to_proplist(Def, Rec, Fields), + [{K, V} || {K, V} <- ?record_to_proplist(Def, Rec), lists:member(K, Fields)]). + diff --git a/include/emqx_trie.hrl b/include/emqx_trie.hrl index ffd2acebc..ce57b4438 100644 --- a/include/emqx_trie.hrl +++ b/include/emqx_trie.hrl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ { node_id :: trie_node_id(), edge_count = 0 :: non_neg_integer(), topic :: binary() | undefined, - flags :: [retained | static] + flags :: list(atom()) }). -record(trie_edge, diff --git a/priv/emqx.schema b/priv/emqx.schema index dd539bffe..8c0a35397 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -762,7 +762,7 @@ end}. end}. %%------------------------------------------------------------------- -%% MQTT Plugins +%% Plugins %%------------------------------------------------------------------- {mapping, "mqtt.plugins.etc_dir", "emqx.plugins_etc_dir", [ @@ -778,7 +778,78 @@ end}. ]}. %%-------------------------------------------------------------------- -%% MQTT Listeners +%% Modules +%%-------------------------------------------------------------------- + +{mapping, "module.presence", "emqx.modules", [ + {default, off}, + {datatype, flag} +]}. + +{mapping, "module.presence.qos", "emqx.modules", [ + {default, 1}, + {datatype, integer}, + {validators, ["range:0-2"]} +]}. + +{mapping, "module.subscription", "emqx.modules", [ + {default, off}, + {datatype, flag} +]}. + +{mapping, "module.subscription.$id.topic", "emqx.modules", [ + {datatype, string} +]}. + +{mapping, "module.subscription.$id.qos", "emqx.modules", [ + {default, 1}, + {datatype, integer}, + {validators, ["range:0-2"]} +]}. + +{mapping, "module.rewrite", "emqx.modules", [ + {default, off}, + {datatype, flag} +]}. + +{mapping, "module.rewrite.rule.$id", "emqx.modules", [ + {datatype, string} +]}. + +{translation, "emqx.modules", fun(Conf) -> + Subscriptions = fun() -> + List = cuttlefish_variable:filter_by_prefix("module.subscription", Conf), + QosList = [Qos || {_, Qos} <- lists:sort([{I, Qos} || {[_,"subscription", I,"qos"], Qos} <- List])], + TopicList = [iolist_to_binary(Topic) || {_, Topic} <- + lists:sort([{I, Topic} || {[_,"subscription", I, "topic"], Topic} <- List])], + lists:zip(TopicList, QosList) + end, + Rewrites = fun() -> + Rules = cuttlefish_variable:filter_by_prefix("module.rewrite.rule", Conf), + lists:map(fun({[_, "rewrite", "rule", I], Rule}) -> + [Topic, Re, Dest] = string:tokens(Rule, " "), + {rewrite, list_to_binary(Topic), list_to_binary(Re), list_to_binary(Dest)} + end, Rules) + end, + lists:append([ + case cuttlefish:conf_get("module.presence", Conf) of %% Presence + true -> [{emqx_mod_presence, [{qos, cuttlefish:conf_get("module.presence.qos", Conf, 1)}]}]; + false -> [] + end, + case cuttlefish:conf_get("module.subscription", Conf) of %% Subscription + true -> [{emqx_mod_subscription, Subscriptions()}]; + false -> [] + end, + case cuttlefish:conf_get("module.rewrite", Conf) of %% Rewrite + true -> [{emqx_mod_rewrite, Rewrites()}]; + false -> [] + end + ]) +end}. + + +%%-------------------------------------------------------------------- +%% Listeners %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- diff --git a/src/emqx_app.erl b/src/emqx_app.erl index 8b31f669f..ab83e870b 100644 --- a/src/emqx_app.erl +++ b/src/emqx_app.erl @@ -38,6 +38,7 @@ start(_Type, _Args) -> ekka:start(), {ok, Sup} = emqx_sup:start_link(), ok = register_acl_mod(), + emqx_modules:load(), start_autocluster(), register(emqx, self()), print_vsn(), @@ -45,6 +46,7 @@ start(_Type, _Args) -> -spec(stop(State :: term()) -> term()). stop(_State) -> + emqx_modules:unload(), catch emqx:stop_listeners(). %%-------------------------------------------------------------------- diff --git a/src/emqx_base62.erl b/src/emqx_base62.erl index f1fb44e7a..a3c00a979 100644 --- a/src/emqx_base62.erl +++ b/src/emqx_base62.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ -module(emqx_base62). --author("Feng Lee "). - -export([encode/1, decode/1]). %% @doc Encode an integer to base62 string diff --git a/src/emqx_boot.erl b/src/emqx_boot.erl index cea7f577a..6a2d76b8c 100644 --- a/src/emqx_boot.erl +++ b/src/emqx_boot.erl @@ -16,8 +16,6 @@ -module(emqx_boot). --author("Feng Lee "). - -export([apply_module_attributes/1, all_module_attributes/1]). %% only {F, Args}... diff --git a/src/emqx_gc.erl b/src/emqx_gc.erl index a252e4876..4bcfb4bc8 100644 --- a/src/emqx_gc.erl +++ b/src/emqx_gc.erl @@ -18,8 +18,6 @@ -module(emqx_gc). --author("Feng Lee "). - -export([conn_max_gc_count/0, reset_conn_gc_count/2, maybe_force_gc/2, maybe_force_gc/3]). diff --git a/src/emqx_keepalive.erl b/src/emqx_keepalive.erl index a3c1b44c1..7cfc4f48d 100644 --- a/src/emqx_keepalive.erl +++ b/src/emqx_keepalive.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -14,12 +14,8 @@ %% limitations under the License. %%-------------------------------------------------------------------- -%% @doc Client Keepalive - -module(emqx_keepalive). --author("Feng Lee "). - -export([start/3, check/1, cancel/1]). -record(keepalive, {statfun, statval, tsec, tmsg, tref, repeat = 0}). diff --git a/src/emqx_misc.erl b/src/emqx_misc.erl index 713e449d6..54a5bbd01 100644 --- a/src/emqx_misc.erl +++ b/src/emqx_misc.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ -module(emqx_misc). --author("Feng Lee "). - -export([merge_opts/2, start_timer/2, start_timer/3, cancel_timer/1, proc_stats/0, proc_stats/1]). diff --git a/src/emqx_mod_presence.erl b/src/emqx_mod_presence.erl new file mode 100644 index 000000000..347213e31 --- /dev/null +++ b/src/emqx_mod_presence.erl @@ -0,0 +1,73 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_mod_presence). + +-behaviour(emqx_gen_mod). + +-include("emqx.hrl"). + +-export([load/1, unload/1]). + +-export([on_client_connected/3, on_client_disconnected/3]). + +load(Env) -> + emqx:hook('client.connected', fun ?MODULE:on_client_connected/3, [Env]), + emqx:hook('client.disconnected', fun ?MODULE:on_client_disconnected/3, [Env]). + +on_client_connected(ConnAck, Client = #mqtt_client{client_id = ClientId, + username = Username, + peername = {IpAddr, _}, + clean_sess = CleanSess, + proto_ver = ProtoVer}, Env) -> + Payload = mochijson2:encode([{clientid, ClientId}, + {username, Username}, + {ipaddress, iolist_to_binary(emqx_net:ntoa(IpAddr))}, + {clean_sess, CleanSess}, + {protocol, ProtoVer}, + {connack, ConnAck}, + {ts, emqx_time:now_secs()}]), + Msg = message(qos(Env), topic(connected, ClientId), Payload), + emqx:publish(emqx_message:set_flag(sys, Msg)), + {ok, Client}. + +on_client_disconnected(Reason, #mqtt_client{client_id = ClientId, + username = Username}, Env) -> + Payload = mochijson2:encode([{clientid, ClientId}, + {username, Username}, + {reason, reason(Reason)}, + {ts, emqx_time:now_secs()}]), + Msg = message(qos(Env), topic(disconnected, ClientId), Payload), + emqx:publish(emqx_message:set_flag(sys, Msg)), ok. + +unload(_Env) -> + emqx:unhook('client.connected', fun ?MODULE:on_client_connected/3), + emqx:unhook('client.disconnected', fun ?MODULE:on_client_disconnected/3). + +message(Qos, Topic, Payload) -> + emqx_message:make(presence, Qos, Topic, iolist_to_binary(Payload)). + +topic(connected, ClientId) -> + emqx_topic:systop(list_to_binary(["clients/", ClientId, "/connected"])); +topic(disconnected, ClientId) -> + emqx_topic:systop(list_to_binary(["clients/", ClientId, "/disconnected"])). + +qos(Env) -> proplists:get_value(qos, Env, 0). + +reason(Reason) when is_atom(Reason) -> Reason; +reason({Error, _}) when is_atom(Error) -> Error; +reason(_) -> internal_error. + diff --git a/src/emqx_mod_rewrite.erl b/src/emqx_mod_rewrite.erl new file mode 100644 index 000000000..ca8d02551 --- /dev/null +++ b/src/emqx_mod_rewrite.erl @@ -0,0 +1,91 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_mod_rewrite). + +-include_lib("emqx.hrl"). + +-export([load/1, unload/1]). + +-export([rewrite_subscribe/4, rewrite_unsubscribe/4, rewrite_publish/2]). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +load(Rules0) -> + Rules = compile(Rules0), + emqx:hook('client.subscribe', fun ?MODULE:rewrite_subscribe/4, [Rules]), + emqx:hook('client.unsubscribe',fun ?MODULE:rewrite_unsubscribe/4, [Rules]), + emqx:hook('message.publish', fun ?MODULE:rewrite_publish/2, [Rules]). + +rewrite_subscribe(_ClientId, _Username, TopicTable, Rules) -> + lager:info("Rewrite subscribe: ~p", [TopicTable]), + {ok, [{match_rule(Topic, Rules), Opts} || {Topic, Opts} <- TopicTable]}. + +rewrite_unsubscribe(_ClientId, _Username, TopicTable, Rules) -> + lager:info("Rewrite unsubscribe: ~p", [TopicTable]), + {ok, [{match_rule(Topic, Rules), Opts} || {Topic, Opts} <- TopicTable]}. + +rewrite_publish(Message=#mqtt_message{topic = Topic}, Rules) -> + %%TODO: this will not work if the client is always online. + RewriteTopic = + case get({rewrite, Topic}) of + undefined -> + DestTopic = match_rule(Topic, Rules), + put({rewrite, Topic}, DestTopic), DestTopic; + DestTopic -> + DestTopic + end, + {ok, Message#mqtt_message{topic = RewriteTopic}}. + +unload(_) -> + emqx:unhook('client.subscribe', fun ?MODULE:rewrite_subscribe/4), + emqx:unhook('client.unsubscribe',fun ?MODULE:rewrite_unsubscribe/4), + emqx:unhook('message.publish', fun ?MODULE:rewrite_publish/2). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +match_rule(Topic, []) -> + Topic; + +match_rule(Topic, [{rewrite, Filter, MP, Dest} | Rules]) -> + case emqx_topic:match(Topic, Filter) of + true -> match_regx(Topic, MP, Dest); + false -> match_rule(Topic, Rules) + end. + +match_regx(Topic, MP, Dest) -> + case re:run(Topic, MP, [{capture, all_but_first, list}]) of + {match, Captured} -> + Vars = lists:zip(["\\$" ++ integer_to_list(I) + || I <- lists:seq(1, length(Captured))], Captured), + iolist_to_binary(lists:foldl( + fun({Var, Val}, Acc) -> + re:replace(Acc, Var, Val, [global]) + end, Dest, Vars)); + nomatch -> + Topic + end. + +compile(Rules) -> + lists:map(fun({rewrite, Topic, Re, Dest}) -> + {ok, MP} = re:compile(Re), + {rewrite, Topic, MP, Dest} + end, Rules). + diff --git a/src/emqx_mod_subscription.erl b/src/emqx_mod_subscription.erl new file mode 100644 index 000000000..cae25842a --- /dev/null +++ b/src/emqx_mod_subscription.erl @@ -0,0 +1,61 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_mod_subscription). + +-behaviour(emqx_gen_mod). + +-include_lib("emqx.hrl"). + +-include_lib("emqx_mqtt.hrl"). + +-export([load/1, on_client_connected/3, unload/1]). + +-define(TAB, ?MODULE). + +%%-------------------------------------------------------------------- +%% Load/Unload Hook +%%-------------------------------------------------------------------- + +load(Topics) -> + emqx:hook('client.connected', fun ?MODULE:on_client_connected/3, [Topics]). + +on_client_connected(?CONNACK_ACCEPT, Client = #mqtt_client{client_id = ClientId, + client_pid = ClientPid, + username = Username}, Topics) -> + + Replace = fun(Topic) -> rep(<<"%u">>, Username, rep(<<"%c">>, ClientId, Topic)) end, + TopicTable = [{Replace(Topic), Qos} || {Topic, Qos} <- Topics], + ClientPid ! {subscribe, TopicTable}, + {ok, Client}; + +on_client_connected(_ConnAck, _Client, _State) -> + ok. + +unload(_) -> + emqx:unhook('client.connected', fun ?MODULE:on_client_connected/3). + +%%-------------------------------------------------------------------- +%% Internal Functions +%%-------------------------------------------------------------------- + +rep(<<"%c">>, ClientId, Topic) -> + emqx_topic:feed_var(<<"%c">>, ClientId, Topic); +rep(<<"%u">>, undefined, Topic) -> + Topic; +rep(<<"%u">>, Username, Topic) -> + emqx_topic:feed_var(<<"%u">>, Username, Topic). + diff --git a/src/emqx_modules.erl b/src/emqx_modules.erl new file mode 100644 index 000000000..3f51dbcdb --- /dev/null +++ b/src/emqx_modules.erl @@ -0,0 +1,33 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_modules). + +-export([load/0, unload/0]). + +load() -> + lists:foreach( + fun({Mod, Env}) -> + ok = Mod:load(Env), + io:format("Load ~s module successfully.~n", [Mod]) + end, emqx:env(modules, [])). + +unload() -> + lists:foreach( + fun({Mod, Env}) -> + Mod:unload(Env) end, + emqx:env(modules, [])). + diff --git a/src/emqx_net.erl b/src/emqx_net.erl index a2f38012e..0e666dfb1 100644 --- a/src/emqx_net.erl +++ b/src/emqx_net.erl @@ -16,8 +16,6 @@ -module(emqx_net). --author("Feng Lee "). - -include_lib("kernel/include/inet.hrl"). -export([tcp_name/3, tcp_host/1, getopts/2, setopts/2, getaddr/2, diff --git a/src/emqx_pmon.erl b/src/emqx_pmon.erl index 8c8857fea..e65f34c6c 100644 --- a/src/emqx_pmon.erl +++ b/src/emqx_pmon.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ -module(emqx_pmon). --author("Feng Lee "). - -export([new/0, monitor/2, demonitor/2, erase/2]). -type(pmon() :: {?MODULE, map()}). diff --git a/src/emqx_router.erl b/src/emqx_router.erl index 81631c022..c07d9759d 100644 --- a/src/emqx_router.erl +++ b/src/emqx_router.erl @@ -28,11 +28,14 @@ -boot_mnesia({mnesia, [boot]}). -copy_mnesia({mnesia, [copy]}). --export([start_link/0, topics/0, local_topics/0]). +-export([start_link/1]). %% For eunit tests -export([start/0, stop/0]). +%% Topics +-export([topics/0, local_topics/0]). + %% Route APIs -export([add_route/1, get_routes/1, del_route/1, has_route/1]). @@ -49,7 +52,7 @@ -export([dump/0]). --record(state, {stats_timer}). +-record(state, {stats_fun, stats_timer}). -define(ROUTER, ?MODULE). @@ -60,21 +63,21 @@ %%-------------------------------------------------------------------- mnesia(boot) -> - ok = ekka_mnesia:create_table(mqtt_route, [ + ok = ekka_mnesia:create_table(route, [ {type, bag}, {ram_copies, [node()]}, - {record_name, mqtt_route}, - {attributes, record_info(fields, mqtt_route)}]); + {record_name, route}, + {attributes, record_info(fields, route)}]); mnesia(copy) -> - ok = ekka_mnesia:copy_table(mqtt_route, ram_copies). + ok = ekka_mnesia:copy_table(route, ram_copies). %%-------------------------------------------------------------------- %% Start the Router %%-------------------------------------------------------------------- -start_link() -> - gen_server:start_link({local, ?ROUTER}, ?MODULE, [], []). +start_link(StatsFun) -> + gen_server:start_link({local, ?ROUTER}, ?MODULE, [StatsFun], []). %%-------------------------------------------------------------------- %% Topics @@ -82,39 +85,39 @@ start_link() -> -spec(topics() -> list(binary())). topics() -> - mnesia:dirty_all_keys(mqtt_route). + mnesia:dirty_all_keys(route). -spec(local_topics() -> list(binary())). local_topics() -> - ets:select(mqtt_local_route, [{{'$1', '_'}, [], ['$1']}]). + ets:select(local_route, [{{'$1', '_'}, [], ['$1']}]). %%-------------------------------------------------------------------- %% Match API %%-------------------------------------------------------------------- %% @doc Match Routes. --spec(match(Topic:: binary()) -> [mqtt_route()]). +-spec(match(Topic:: binary()) -> [route()]). match(Topic) when is_binary(Topic) -> %% Optimize: ets??? Matched = mnesia:ets(fun emqx_trie:match/1, [Topic]), %% Optimize: route table will be replicated to all nodes. - lists:append([ets:lookup(mqtt_route, To) || To <- [Topic | Matched]]). + lists:append([ets:lookup(route, To) || To <- [Topic | Matched]]). %% @doc Print Routes. -spec(print(Topic :: binary()) -> [ok]). print(Topic) -> [io:format("~s -> ~s~n", [To, Node]) || - #mqtt_route{topic = To, node = Node} <- match(Topic)]. + #route{topic = To, node = Node} <- match(Topic)]. %%-------------------------------------------------------------------- %% Route Management API %%-------------------------------------------------------------------- %% @doc Add Route. --spec(add_route(binary() | mqtt_route()) -> ok | {error, Reason :: term()}). +-spec(add_route(binary() | route()) -> ok | {error, Reason :: term()}). add_route(Topic) when is_binary(Topic) -> - add_route(#mqtt_route{topic = Topic, node = node()}); -add_route(Route = #mqtt_route{topic = Topic}) -> + add_route(#route{topic = Topic, node = node()}); +add_route(Route = #route{topic = Topic}) -> case emqx_topic:wildcard(Topic) of true -> case mnesia:is_transaction() of true -> add_trie_route(Route); @@ -126,23 +129,23 @@ add_route(Route = #mqtt_route{topic = Topic}) -> add_direct_route(Route) -> mnesia:async_dirty(fun mnesia:write/1, [Route]). -add_trie_route(Route = #mqtt_route{topic = Topic}) -> - case mnesia:wread({mqtt_route, Topic}) of +add_trie_route(Route = #route{topic = Topic}) -> + case mnesia:wread({route, Topic}) of [] -> emqx_trie:insert(Topic); _ -> ok end, mnesia:write(Route). %% @doc Lookup Routes --spec(get_routes(binary()) -> [mqtt_route()]). +-spec(get_routes(binary()) -> [route()]). get_routes(Topic) -> - ets:lookup(mqtt_route, Topic). + ets:lookup(route, Topic). %% @doc Delete Route --spec(del_route(binary() | mqtt_route()) -> ok | {error, Reason :: term()}). +-spec(del_route(binary() | route()) -> ok | {error, Reason :: term()}). del_route(Topic) when is_binary(Topic) -> - del_route(#mqtt_route{topic = Topic, node = node()}); -del_route(Route = #mqtt_route{topic = Topic}) -> + del_route(#route{topic = Topic, node = node()}); +del_route(Route = #route{topic = Topic}) -> case emqx_topic:wildcard(Topic) of true -> case mnesia:is_transaction() of true -> del_trie_route(Route); @@ -154,8 +157,8 @@ del_route(Route = #mqtt_route{topic = Topic}) -> del_direct_route(Route) -> mnesia:async_dirty(fun mnesia:delete_object/1, [Route]). -del_trie_route(Route = #mqtt_route{topic = Topic}) -> - case mnesia:wread({mqtt_route, Topic}) of +del_trie_route(Route = #route{topic = Topic}) -> + case mnesia:wread({route, Topic}) of [Route] -> %% Remove route and trie mnesia:delete_object(Route), emqx_trie:delete(Topic); @@ -167,7 +170,7 @@ del_trie_route(Route = #mqtt_route{topic = Topic}) -> %% @doc Has route? -spec(has_route(binary()) -> boolean()). has_route(Topic) when is_binary(Topic) -> - ets:member(mqtt_route, Topic). + ets:member(route, Topic). %% @private -spec(trans(function(), list(any())) -> ok | {error, term()}). @@ -183,7 +186,7 @@ trans(Fun, Args) -> -spec(get_local_routes() -> list({binary(), node()})). get_local_routes() -> - ets:tab2list(mqtt_local_route). + ets:tab2list(local_route). -spec(add_local_route(binary()) -> ok). add_local_route(Topic) -> @@ -195,15 +198,15 @@ del_local_route(Topic) -> -spec(match_local(binary()) -> [mqtt_route()]). match_local(Name) -> - case ets:info(mqtt_local_route, size) of + case ets:info(local_route, size) of 0 -> []; _ -> ets:foldl( fun({Filter, Node}, Matched) -> case emqx_topic:match(Name, Filter) of - true -> [#mqtt_route{topic = {local, Filter}, node = Node} | Matched]; + true -> [#route{topic = {local, Filter}, node = Node} | Matched]; false -> Matched end - end, [], mqtt_local_route) + end, [], local_route) end. -spec(clean_local_routes() -> ok). @@ -211,7 +214,7 @@ clean_local_routes() -> gen_server:call(?ROUTER, clean_local_routes). dump() -> - [{route, ets:tab2list(mqtt_route)}, {local_route, ets:tab2list(mqtt_local_route)}]. + [{route, ets:tab2list(route)}, {local_route, ets:tab2list(local_route)}]. %% For unit test. start() -> @@ -224,23 +227,23 @@ stop() -> %% gen_server Callbacks %%-------------------------------------------------------------------- -init([]) -> +init([StatsFun]) -> ekka:monitor(membership), - ets:new(mqtt_local_route, [set, named_table, protected]), + ets:new(local_route, [set, named_table, protected]), {ok, TRef} = timer:send_interval(timer:seconds(1), stats), - {ok, #state{stats_timer = TRef}}. + {ok, #state{stats_fun = StatsFun, stats_timer = TRef}}. handle_call({add_local_route, Topic}, _From, State) -> %% why node()...? - ets:insert(mqtt_local_route, {Topic, node()}), + ets:insert(local_route, {Topic, node()}), {reply, ok, State}; handle_call({del_local_route, Topic}, _From, State) -> - ets:delete(mqtt_local_route, Topic), + ets:delete(local_route, Topic), {reply, ok, State}; handle_call(clean_local_routes, _From, State) -> - ets:delete_all_objects(mqtt_local_route), + ets:delete_all_objects(local_route), {reply, ok, State}; handle_call(stop, _From, State) -> @@ -253,19 +256,15 @@ handle_cast(_Msg, State) -> {noreply, State}. handle_info({membership, {mnesia, down, Node}}, State) -> - global:trans({?LOCK, self()}, - fun() -> - clean_routes_(Node), - update_stats_() - end), - {noreply, State, hibernate}; + global:trans({?LOCK, self()}, fun() -> clean_routes_(Node) end), + handle_info(stats, State); handle_info({membership, _Event}, State) -> %% ignore {noreply, State}; -handle_info(stats, State) -> - update_stats_(), +handle_info(stats, State = #state{stats_fun = StatsFun}) -> + StatsFun(mnesia:table_info(route, size)), {noreply, State, hibernate}; handle_info(_Info, State) -> @@ -282,15 +281,12 @@ code_change(_OldVsn, State, _Extra) -> %% Internal Functions %%-------------------------------------------------------------------- -%% Clean Routes on Node +%% Clean routes on the down node. clean_routes_(Node) -> - Pattern = #mqtt_route{_ = '_', node = Node}, + Pattern = #route{_ = '_', node = Node}, Clean = fun() -> - [mnesia:delete_object(mqtt_route, R, write) || - R <- mnesia:match_object(mqtt_route, Pattern, write)] + [mnesia:delete_object(route, R, write) || + R <- mnesia:match_object(route, Pattern, write)] end, mnesia:transaction(Clean). -update_stats_() -> - emqx_stats:setstats('routes/count', 'routes/max', mnesia:table_info(mqtt_route, size)). - diff --git a/src/emqx_router_sup.erl b/src/emqx_router_sup.erl new file mode 100644 index 000000000..ddcbfe097 --- /dev/null +++ b/src/emqx_router_sup.erl @@ -0,0 +1,38 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_router_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + StatsFun = emqx_stats:statsfun('routes/count', 'routes/max'), + SupFlags = #{strategy => one_for_all, intensity => 1, period => 5}, + Router = #{id => emqx_router, + start => {emqx_router, start_link, [StatsFun]}, + restart => permanent, + shutdown => 30000, + type => worker, + modules => [emqx_router]}, + {ok, {SupFlags, [Router]}}. + diff --git a/src/emqx_time.erl b/src/emqx_time.erl index 4622c43db..d954c8204 100644 --- a/src/emqx_time.erl +++ b/src/emqx_time.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ -module(emqx_time). --author("Feng Lee "). - -export([seed/0, now_secs/0, now_secs/1, now_ms/0, now_ms/1, ts_from_ms/1]). seed() -> diff --git a/src/emqx_topic.erl b/src/emqx_topic.erl index a4f9fc4d8..7f177e8d0 100644 --- a/src/emqx_topic.erl +++ b/src/emqx_topic.erl @@ -16,12 +16,8 @@ -module(emqx_topic). --author("Feng Lee "). - -include("emqx_mqtt.hrl"). --include("emqx_internal.hrl"). - -import(lists, [reverse/1]). -export([match/2, validate/1, triples/1, words/1, wildcard/1]). @@ -206,8 +202,14 @@ parse(Topic, Options) -> {Topic, Options}. if_not_contain(Key, Options, Fun) when Key == local; Key == fastlane -> - ?IF(lists:member(Key, Options), error(invalid_topic), Fun()); + case lists:member(Key, Options) of + true -> error(invalid_topic); + false -> Fun() + end; if_not_contain(share, Options, Fun) -> - ?IF(lists:keyfind(share, 1, Options), error(invalid_topic), Fun()). + case lists:keyfind(share, 1, Options) of + true -> error(invalid_topic); + false -> Fun() + end. diff --git a/src/emqx_trie.erl b/src/emqx_trie.erl index 1b090a75f..f99bbb725 100644 --- a/src/emqx_trie.erl +++ b/src/emqx_trie.erl @@ -41,21 +41,21 @@ -spec(mnesia(boot | copy) -> ok). mnesia(boot) -> %% Trie Table - ok = ekka_mnesia:create_table(mqtt_trie, [ + ok = ekka_mnesia:create_table(trie, [ {ram_copies, [node()]}, {record_name, trie}, {attributes, record_info(fields, trie)}]), %% Trie Node Table - ok = ekka_mnesia:create_table(mqtt_trie_node, [ + ok = ekka_mnesia:create_table(trie_node, [ {ram_copies, [node()]}, {record_name, trie_node}, {attributes, record_info(fields, trie_node)}]); mnesia(copy) -> %% Copy Trie Table - ok = ekka_mnesia:copy_table(mqtt_trie), + ok = ekka_mnesia:copy_table(trie), %% Copy Trie Node Table - ok = ekka_mnesia:copy_table(mqtt_trie_node). + ok = ekka_mnesia:copy_table(trie_node). %%-------------------------------------------------------------------- %% Trie API @@ -64,7 +64,7 @@ mnesia(copy) -> %% @doc Insert topic to trie -spec(insert(Topic :: binary()) -> ok). insert(Topic) when is_binary(Topic) -> - case mnesia:read(mqtt_trie_node, Topic) of + case mnesia:read(trie_node, Topic) of [#trie_node{topic = Topic}] -> ok; [TrieNode = #trie_node{topic = undefined}] -> @@ -85,14 +85,14 @@ match(Topic) when is_binary(Topic) -> %% @doc Lookup a Trie Node -spec(lookup(NodeId :: binary()) -> [#trie_node{}]). lookup(NodeId) -> - mnesia:read(mqtt_trie_node, NodeId). + mnesia:read(trie_node, NodeId). %% @doc Delete topic from trie -spec(delete(Topic :: binary()) -> ok). delete(Topic) when is_binary(Topic) -> - case mnesia:read(mqtt_trie_node, Topic) of + case mnesia:read(trie_node, Topic) of [#trie_node{edge_count = 0}] -> - mnesia:delete({mqtt_trie_node, Topic}), + mnesia:delete({trie_node, Topic}), delete_path(lists:reverse(emqx_topic:triples(Topic))); [TrieNode] -> write_trie_node(TrieNode#trie_node{topic = undefined}); @@ -108,9 +108,9 @@ delete(Topic) when is_binary(Topic) -> %% @doc Add path to trie tree. add_path({Node, Word, Child}) -> Edge = #trie_edge{node_id = Node, word = Word}, - case mnesia:read(mqtt_trie_node, Node) of + case mnesia:read(trie_node, Node) of [TrieNode = #trie_node{edge_count = Count}] -> - case mnesia:wread({mqtt_trie, Edge}) of + case mnesia:wread({trie, Edge}) of [] -> write_trie_node(TrieNode#trie_node{edge_count = Count+1}), write_trie(#trie{edge = Edge, node_id = Child}); @@ -131,11 +131,11 @@ match_node(NodeId, Words) -> match_node(NodeId, Words, []). match_node(NodeId, [], ResAcc) -> - mnesia:read(mqtt_trie_node, NodeId) ++ 'match_#'(NodeId, ResAcc); + mnesia:read(trie_node, NodeId) ++ 'match_#'(NodeId, ResAcc); match_node(NodeId, [W|Words], ResAcc) -> lists:foldl(fun(WArg, Acc) -> - case mnesia:read(mqtt_trie, #trie_edge{node_id = NodeId, word = WArg}) of + case mnesia:read(trie, #trie_edge{node_id = NodeId, word = WArg}) of [#trie{node_id = ChildId}] -> match_node(ChildId, Words, Acc); [] -> Acc end @@ -144,9 +144,9 @@ match_node(NodeId, [W|Words], ResAcc) -> %% @private %% @doc Match node with '#'. 'match_#'(NodeId, ResAcc) -> - case mnesia:read(mqtt_trie, #trie_edge{node_id = NodeId, word = '#'}) of + case mnesia:read(trie, #trie_edge{node_id = NodeId, word = '#'}) of [#trie{node_id = ChildId}] -> - mnesia:read(mqtt_trie_node, ChildId) ++ ResAcc; + mnesia:read(trie_node, ChildId) ++ ResAcc; [] -> ResAcc end. @@ -156,10 +156,10 @@ match_node(NodeId, [W|Words], ResAcc) -> delete_path([]) -> ok; delete_path([{NodeId, Word, _} | RestPath]) -> - mnesia:delete({mqtt_trie, #trie_edge{node_id = NodeId, word = Word}}), - case mnesia:read(mqtt_trie_node, NodeId) of + mnesia:delete({trie, #trie_edge{node_id = NodeId, word = Word}}), + case mnesia:read(trie_node, NodeId) of [#trie_node{edge_count = 1, topic = undefined}] -> - mnesia:delete({mqtt_trie_node, NodeId}), + mnesia:delete({trie_node, NodeId}), delete_path(RestPath); [TrieNode = #trie_node{edge_count = 1, topic = _}] -> write_trie_node(TrieNode#trie_node{edge_count = 0}); @@ -171,9 +171,9 @@ delete_path([{NodeId, Word, _} | RestPath]) -> %% @private write_trie(Trie) -> - mnesia:write(mqtt_trie, Trie, write). + mnesia:write(trie, Trie, write). %% @private write_trie_node(TrieNode) -> - mnesia:write(mqtt_trie_node, TrieNode, write). + mnesia:write(trie_node, TrieNode, write). diff --git a/test/emqx_base62_SUITE.erl b/test/emqx_base62_SUITE.erl new file mode 100644 index 000000000..6baf724d1 --- /dev/null +++ b/test/emqx_base62_SUITE.erl @@ -0,0 +1,35 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_base62_SUITE). + +-include_lib("eunit/include/eunit.hrl"). + +-define(BASE62, emqx_base62). + +-compile(export_all). + +all() -> [t_base62_encode]. + +t_base62_encode(_) -> + 10 = ?BASE62:decode(?BASE62:encode(10)), + 100 = ?BASE62:decode(?BASE62:encode(100)), + 9999 = ?BASE62:decode(?BASE62:encode(9999)), + 65535 = ?BASE62:decode(?BASE62:encode(65535)), + <> = emqx_guid:gen(), + <> = emqx_guid:gen(), + X = ?BASE62:decode(?BASE62:encode(X)), + Y = ?BASE62:decode(?BASE62:encode(Y)). diff --git a/test/emqx_guid_SUITE.erl b/test/emqx_guid_SUITE.erl new file mode 100644 index 000000000..a6978bac8 --- /dev/null +++ b/test/emqx_guid_SUITE.erl @@ -0,0 +1,41 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_guid_SUITE). + +-include_lib("eunit/include/eunit.hrl"). + +-compile(export_all). + +all() -> [t_guid_gen, t_guid_hexstr, t_guid_base62]. + +t_guid_gen(_) -> + Guid1 = emqx_guid:gen(), + Guid2 = emqx_guid:gen(), + <<_:128>> = Guid1, + true = (Guid2 >= Guid1), + {Ts1, _, 0} = emqx_guid:new(), + Ts2 = emqx_guid:timestamp(emqx_guid:gen()), + true = Ts2 > Ts1. + +t_guid_hexstr(_) -> + Guid = emqx_guid:gen(), + ?assertEqual(Guid, emqx_guid:from_hexstr(emqx_guid:to_hexstr(Guid))). + +t_guid_base62(_) -> + Guid = emqx_guid:gen(), + ?assertEqual(Guid, emqx_guid:from_base62(emqx_guid:to_base62(Guid))). + diff --git a/test/emqx_inflight_SUITE.erl b/test/emqx_inflight_SUITE.erl index c391d08ea..903d5c7b5 100644 --- a/test/emqx_inflight_SUITE.erl +++ b/test/emqx_inflight_SUITE.erl @@ -16,8 +16,6 @@ -module(emqx_inflight_SUITE). --author("Feng Lee "). - -include_lib("eunit/include/eunit.hrl"). %% CT diff --git a/test/emqx_keepalive_SUITE.erl b/test/emqx_keepalive_SUITE.erl new file mode 100644 index 000000000..270d78830 --- /dev/null +++ b/test/emqx_keepalive_SUITE.erl @@ -0,0 +1,43 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_keepalive_SUITE). + +-compile(export_all). + +all() -> [{group, keepalive}]. + +groups() -> [{keepalive, [], [t_keepalive]}]. + +%%-------------------------------------------------------------------- +%% Keepalive +%%-------------------------------------------------------------------- + +t_keepalive(_) -> + {ok, KA} = emqx_keepalive:start(fun() -> {ok, 1} end, 1, {keepalive, timeout}), + [resumed, timeout] = lists:reverse(keepalive_recv(KA, [])). + +keepalive_recv(KA, Acc) -> + receive + {keepalive, timeout} -> + case emqx_keepalive:check(KA) of + {ok, KA1} -> keepalive_recv(KA1, [resumed | Acc]); + {error, timeout} -> [timeout | Acc] + end + after 4000 -> + Acc + end. + diff --git a/test/emqx_misc_SUITE.erl b/test/emqx_misc_SUITE.erl new file mode 100644 index 000000000..e1283a9d4 --- /dev/null +++ b/test/emqx_misc_SUITE.erl @@ -0,0 +1,46 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_misc_SUITE). + +-include_lib("eunit/include/eunit.hrl"). + +-compile(export_all). + +-define(SOCKOPTS, [binary, + {packet, raw}, + {reuseaddr, true}, + {backlog, 512}, + {nodelay, true}]). + +all() -> [t_merge_opts]. + +t_merge_opts(_) -> + Opts = emqx_misc:merge_opts(?SOCKOPTS, [raw, + binary, + {backlog, 1024}, + {nodelay, false}, + {max_clients, 1024}, + {acceptors, 16}]), + ?assertEqual(1024, proplists:get_value(backlog, Opts)), + ?assertEqual(1024, proplists:get_value(max_clients, Opts)), + [binary, raw, + {acceptors, 16}, + {backlog, 1024}, + {max_clients, 1024}, + {nodelay, false}, + {packet, raw}, + {reuseaddr, true}] = lists:sort(Opts). diff --git a/test/emqx_pqueue_SUITE.erl b/test/emqx_pqueue_SUITE.erl new file mode 100644 index 000000000..c798bf100 --- /dev/null +++ b/test/emqx_pqueue_SUITE.erl @@ -0,0 +1,69 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_pqueue_SUITE). + +-include_lib("eunit/include/eunit.hrl"). + +-compile(export_all). + +-define(PQ, emqx_pqueue). + +all() -> [t_priority_queue_plen, t_priority_queue_out2]. + +t_priority_queue_plen(_) -> + Q = ?PQ:new(), + 0 = ?PQ:plen(0, Q), + Q0 = ?PQ:in(z, Q), + 1 = ?PQ:plen(0, Q0), + Q1 = ?PQ:in(x, 1, Q0), + 1 = ?PQ:plen(1, Q1), + Q2 = ?PQ:in(y, 2, Q1), + 1 = ?PQ:plen(2, Q2), + Q3 = ?PQ:in(z, 2, Q2), + 2 = ?PQ:plen(2, Q3), + {_, Q4} = ?PQ:out(1, Q3), + 0 = ?PQ:plen(1, Q4), + {_, Q5} = ?PQ:out(Q4), + 1 = ?PQ:plen(2, Q5), + {_, Q6} = ?PQ:out(Q5), + 0 = ?PQ:plen(2, Q6), + 1 = ?PQ:len(Q6), + {_, Q7} = ?PQ:out(Q6), + 0 = ?PQ:len(Q7). + +t_priority_queue_out2(_) -> + Els = [a, {b, 1}, {c, 1}, {d, 2}, {e, 2}, {f, 2}], + Q = ?PQ:new(), + Q0 = lists:foldl( + fun({El, P}, Acc) -> + ?PQ:in(El, P, Acc); + (El, Acc) -> + ?PQ:in(El, Acc) + end, Q, Els), + {Val, Q1} = ?PQ:out(Q0), + {value, d} = Val, + {Val1, Q2} = ?PQ:out(2, Q1), + {value, e} = Val1, + {Val2, Q3} = ?PQ:out(1, Q2), + {value, b} = Val2, + {Val3, Q4} = ?PQ:out(Q3), + {value, f} = Val3, + {Val4, Q5} = ?PQ:out(Q4), + {value, c} = Val4, + {Val5, Q6} = ?PQ:out(Q5), + {value, a} = Val5, + {empty, _Q7} = ?PQ:out(Q6). diff --git a/test/emqx_router_SUITE.erl b/test/emqx_router_SUITE.erl index d48f643e1..c46ae27c0 100644 --- a/test/emqx_router_SUITE.erl +++ b/test/emqx_router_SUITE.erl @@ -16,12 +16,12 @@ -module(emqx_router_SUITE). --compile(export_all). - -include("emqx.hrl"). -include_lib("eunit/include/eunit.hrl"). +-compile(export_all). + -define(R, emqx_router). all() -> @@ -35,7 +35,7 @@ groups() -> t_match_route, t_print, t_has_route, - router_unused]}, + t_unused]}, {local_route, [sequence], [t_get_local_topics, t_add_del_local_route, @@ -44,7 +44,7 @@ groups() -> init_per_suite(Config) -> ekka:start(), ekka_mnesia:ensure_started(), - {ok, _R} = emqx_router:start(), + {ok, _} = emqx_router_sup:start_link(), Config. end_per_suite(_Config) -> @@ -81,10 +81,10 @@ t_match_route(_) -> ?R:add_route(<<"a/+/c">>), ?R:add_route(<<"a/b/#">>), ?R:add_route(<<"#">>), - ?assertEqual([#mqtt_route{topic = <<"#">>, node = Node}, - #mqtt_route{topic = <<"a/+/c">>, node = Node}, - #mqtt_route{topic = <<"a/b/#">>, node = Node}, - #mqtt_route{topic = <<"a/b/c">>, node = Node}], + ?assertEqual([#route{topic = <<"#">>, node = Node}, + #route{topic = <<"a/+/c">>, node = Node}, + #route{topic = <<"a/b/#">>, node = Node}, + #route{topic = <<"a/b/c">>, node = Node}], lists:sort(?R:match(<<"a/b/c">>))). t_has_route(_) -> @@ -119,12 +119,12 @@ t_match_local_route(_) -> ?R:add_local_route(<<"a/+/c">>), ?R:add_local_route(<<"a/b/#">>), ?R:add_local_route(<<"#">>), - Matched = [Topic || #mqtt_route{topic = {local, Topic}} <- ?R:match_local(<<"a/b/c">>)], + Matched = [Topic || #route{topic = {local, Topic}} <- ?R:match_local(<<"a/b/c">>)], ?assertEqual([<<"#">>, <<"a/+/c">>, <<"a/b/#">>, <<"a/b/c">>], lists:sort(Matched)). clear_tables() -> ?R:clean_local_routes(), - lists:foreach(fun mnesia:clear_table/1, [mqtt_route, mqtt_trie, mqtt_trie_node]). + lists:foreach(fun mnesia:clear_table/1, [route, trie, trie_node]). router_add_del(_) -> %% Add @@ -132,9 +132,9 @@ router_add_del(_) -> ?R:add_route(<<"a/b/c">>), ?R:add_route(<<"+/#">>), Routes = [R1, R2 | _] = [ - #mqtt_route{topic = <<"#">>, node = node()}, - #mqtt_route{topic = <<"+/#">>, node = node()}, - #mqtt_route{topic = <<"a/b/c">>, node = node()}], + #route{topic = <<"#">>, node = node()}, + #route{topic = <<"+/#">>, node = node()}, + #route{topic = <<"a/b/c">>, node = node()}], Routes = lists:sort(?R:match(<<"a/b/c">>)), %% Batch Add @@ -147,7 +147,7 @@ router_add_del(_) -> {atomic, []} = mnesia:transaction(fun emqx_trie:lookup/1, [<<"a/b/c">>]), %% Batch Del - R3 = #mqtt_route{topic = <<"#">>, node = 'a@127.0.0.1'}, + R3 = #route{topic = <<"#">>, node = 'a@127.0.0.1'}, ?R:add_route(R3), ?R:del_route(R1), ?R:del_route(R2), @@ -155,16 +155,17 @@ router_add_del(_) -> [] = lists:sort(?R:match(<<"a/b/c">>)). t_print(_) -> - Routes = [#mqtt_route{topic = <<"a/b/c">>, node = node()}, - #mqtt_route{topic = <<"#">>, node = node()}, - #mqtt_route{topic = <<"+/#">>, node = node()}], + Routes = [#route{topic = <<"a/b/c">>, node = node()}, + #route{topic = <<"#">>, node = node()}, + #route{topic = <<"+/#">>, node = node()}], lists:foreach(fun(R) -> ?R:add_route(R) end, Routes), ?R:print(<<"a/b/c">>), ?R:del_route(<<"+/#">>), ?R:del_route(<<"a/b/c">>), ?R:del_route(<<"#">>). -router_unused(_) -> - gen_server:call(emqx_router, bad_call), - gen_server:cast(emqx_router, bad_msg), - emqx_router ! bad_info. +t_unused(_) -> + gen_server:call(?R, bad_call), + gen_server:cast(?R, bad_msg), + ?R ! bad_info. + diff --git a/test/emqx_time_SUITE.erl b/test/emqx_time_SUITE.erl new file mode 100644 index 000000000..9a0514e74 --- /dev/null +++ b/test/emqx_time_SUITE.erl @@ -0,0 +1,28 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_time_SUITE). + +-include_lib("eunit/include/eunit.hrl"). + +-compile(export_all). + +all() -> [t_time_now_to]. + +t_time_now_to(_) -> + emqx_time:seed(), + emqx_time:now_secs(), + emqx_time:now_ms(). diff --git a/test/emqx_trie_SUITE.erl b/test/emqx_trie_SUITE.erl index fd7fe4e38..69f251372 100644 --- a/test/emqx_trie_SUITE.erl +++ b/test/emqx_trie_SUITE.erl @@ -133,5 +133,5 @@ t_delete3(_) -> end). clear_tables() -> - lists:foreach(fun mnesia:clear_table/1, [mqtt_trie, mqtt_trie_node]). + lists:foreach(fun mnesia:clear_table/1, [trie, trie_node]).