diff --git a/.gitignore b/.gitignore index 8024cc9..7097f31 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ eunit.coverdata logs/ test/ct.cover.spec .DS_Store +_build/ +rebar.lock +erlang.mk diff --git a/Makefile b/Makefile index 8240b5d..f8d7725 100644 --- a/Makefile +++ b/Makefile @@ -2,18 +2,16 @@ PROJECT = emqx_auth_ldap PROJECT_DESCRIPTION = EMQ X Authentication/ACL with LDAP PROJECT_VERSION = 3.0 -LOCAL_DEPS = eldap - -DEPS = ecpool clique emqx_passwd -dep_ecpool = git-emqx https://github.com/emqx/ecpool master -dep_clique = git-emqx https://github.com/emqx/clique develop +DEPS = ecpool emqx_passwd eldap2 +dep_eldap2 = git-emqx https://github.com/emqx/eldap2 eldap2 +dep_ecpool = git-emqx https://github.com/emqx/ecpool master dep_emqx_passwd = git-emqx https://github.com/emqx/emqx-passwd emqx30 BUILD_DEPS = emqx cuttlefish -dep_emqx = git-emqx https://github.com/emqx/emqx emqx30 -dep_cuttlefish = git-emqx https://github.com/emqx/cuttlefish +dep_emqx = git-emqx https://github.com/emqx/emqx emqx30 +dep_cuttlefish = git-emqx https://github.com/emqx/cuttlefish v2.1.1 -NO_AUTOPATCH = cuttlefish +NO_AUTOPATCH = cuttlefish eldap2 ERLC_OPTS += +debug_info @@ -24,14 +22,55 @@ TEST_ERLC_OPTS += +debug_info COVER = true -define dep_fetch_git-emqx - git clone -q --depth 1 -b $(call dep_commit,$(1)) -- $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)) > /dev/null 2>&1; \ - cd $(DEPS_DIR)/$(call dep_name,$(1)); -endef +$(shell [ -f erlang.mk ] || curl -s -o erlang.mk https://raw.githubusercontent.com/emqx/erlmk/master/erlang.mk) include erlang.mk -app:: rebar.config +CUTTLEFISH_SCRIPT = _build/default/lib/cuttlefish/cuttlefish + +app.config: $(CUTTLEFISH_SCRIPT) etc/emqx_auth_ldap.conf + $(verbose) $(CUTTLEFISH_SCRIPT) -l info -e etc/ -c etc/emqx_auth_ldap.conf -i priv/emqx_auth_ldap.schema -d data + +$(CUTTLEFISH_SCRIPT): rebar-deps + @if [ ! -f cuttlefish ]; then make -C _build/default/lib/cuttlefish; fi + +distclean:: + @rm -rf _build cover deps logs log data + @rm -f rebar.lock compile_commands.json cuttlefish + +rebar-deps: + rebar3 get-deps + +rebar-clean: + @rebar3 clean + +rebar-compile: rebar-deps + rebar3 compile + +rebar-ct: app.config + rebar3 ct + +rebar-xref: + @rebar3 xref + +## Below are for version consistency check during erlang.mk and rebar3 dual mode support +none= +space = $(none) $(none) +comma = , +quote = \" +curly_l = "{" +curly_r = "}" +dep-versions = [$(foreach dep,$(DEPS) $(BUILD_DEPS) $(TEST_DEPS),$(curly_l)$(dep),$(quote)$(word 3,$(dep_$(dep)))$(quote)$(curly_r)$(comma))[]] -app.config:: - ./deps/cuttlefish/cuttlefish -l info -e etc/ -c etc/emqx_auth_ldap.conf -i priv/emqx_auth_ldap.schema -d data +.PHONY: dep-vsn-check +dep-vsn-check: + $(verbose) erl -noshell -eval \ + "MkVsns = lists:sort(lists:flatten($(dep-versions))), \ + {ok, Conf} = file:consult('rebar.config'), \ + {_, Deps} = lists:keyfind(deps, 1, Conf), \ + F = fun({N, V}) when is_list(V) -> {N, V}; ({N, {git, _, {branch, V}}}) -> {N, V} end, \ + RebarVsns = lists:sort(lists:map(F, Deps)), \ + case {RebarVsns -- MkVsns, MkVsns -- RebarVsns} of \ + {[], []} -> halt(0); \ + {Rebar, Mk} -> erlang:error({deps_version_discrepancy, [{rebar, Rebar}, {mk, Mk}]}) \ + end." diff --git a/README.md b/README.md index 33bcb0b..85e4349 100644 --- a/README.md +++ b/README.md @@ -7,89 +7,69 @@ Build ----- ``` -make && make tests +make ``` -Configuration -------------- - -File: etc/emqx_auth_ldap.conf +Load the Plugin +--------------- ``` -auth.ldap.servers = 127.0.0.1 - -auth.ldap.port = 389 - -auth.ldap.timeout = 30 - -auth.ldap.bind_dn = cn=root,dc=emqtt,dc=com - -auth.ldap.bind_password = public - -auth.ldap.ssl = false - -## TODO: SSL Support - -#auth.ldap.ssl.certfile = etc/certs/cert.pem - -#auth.ldap.ssl.keyfile = etc/certs/key.pem - -#auth.ldap.ssl.cacertfile = etc/certs/cacert.pem - -#auth.ldap.ssl.verify = verify_peer - -#auth.ldap.ssl.fail_if_no_peer_cert = true - -## Variables: %u = username, %c = clientid -auth.ldap.auth_dn = cn=%u,ou=auth,dc=emqtt,dc=com - -## Password hash: plain, md5, sha, sha256 -auth.ldap.password_hash = sha256 - -## Temporarily unavailable -## auth.ldap.acl_dn = cn=%u,ou=acl,dc=emqtt,dc=com - +# ./bin/emqx_ctl plugins load emqx_auth_ldap ``` -Load the Plugin +Generate Password --------------- ``` -./bin/emqx_ctl plugins load emqx_auth_ldap +slappasswd -h '{ssha}' -s public ``` + Configuration Open LDAP ----------------------- vim /etc/openldap/slapd.conf ``` +include /etc/openldap/schema/core.schema +include /etc/openldap/schema/cosine.schema +include /etc/openldap/schema/inetorgperson.schema +include /etc/openldap/schema/ppolicy.schema +include /etc/openldap/schema/emqx.schema + database bdb -suffix "dc=emqtt,dc=com" -rootdn "cn=root,dc=emqtt,dc=com" -rootpw {SSHA}xvvgeQvLGZzHrCzFTfAOkL1gkHQrJX59 +suffix "dc=emqx,dc=io" +rootdn "cn=root,dc=emqx,dc=io" +rootpw {SSHA}eoF7NhNrejVYYyGHqnt+MdKNBh4r1w3W +directory /etc/openldap/data ``` +Import EMQX User Data +---------------------- -Include Emqtt Schema --------------------- - -vim /etc/openldap/slapd.conf +Use ldapadd ``` -include emqtt.schema +# ldapadd -x -D "cn=root,dc=emqx,dc=io" -w public -f emqx.com.ldif ``` -Create Emqtt User Data ----------------------- +Use slapadd +``` +# sudo slapadd -l schema/emqx.io.ldif -f slapd.conf +``` +Launch slapd ``` -# ldapadd -x -D "cn=root,dc=emqtt,dc=com" -w public -f emqtt.com.ldif +# sudo slapd -d 3 ``` -TODO ----- +Test +------- +After configure slapd correctly and launch slapd successfully. +You could execute -Support SSL Options +``` bash +# make tests +``` License ------- diff --git a/emqtt.com.ldif b/emqtt.com.ldif deleted file mode 100644 index 0e5717e..0000000 --- a/emqtt.com.ldif +++ /dev/null @@ -1,33 +0,0 @@ -## create emqtt.com -dn:dc=emqtt, dc=com -objectclass:top -objectclass:dcobject -objectclass:organization -dc:emqtt -o:emqtt,Inc. - -# craete auth.emqtt.com -dn:ou=auth, dc=emqtt, dc=com -ou:auth -objectclass:organizationalUnit - -# craete acl.emqtt.com -#dn:ou=acl, dc=emqtt, dc=com -#ou:acl -#objectclass:organizationalUnit - -# craete test.auth.emqtt.com -dn:cn=test,ou=auth,dc=emqtt,dc=com -cn:test -username:test -password:8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92 -objectclass:mqttUser - -# craete test.acl.emqtt.com -#dn:cn=test,ou=acl,dc=emqtt,dc=com -#cn:test -#username:test -#topic:test/# -#allow:1 -#access:3 -#objectclass:mqttAcl \ No newline at end of file diff --git a/emqtt.schema b/emqtt.schema deleted file mode 100644 index e656895..0000000 --- a/emqtt.schema +++ /dev/null @@ -1,45 +0,0 @@ -# -# Preliminary Apple OS X Native LDAP Schema -# This file is subject to change. -# -attributetype ( 1.3.6.1.4.1.7914.1.2.1.1 NAME 'username' - DESC 'name of the user on the mailsystem' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-value ) - -attributetype ( 1.3.6.1.4.1.7914.1.2.1.2 NAME 'password' - DESC 'name of the user on the mailsystem' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-value ) - -objectclass ( 1.3.6.1.4.1.7914.1.2.2.1 NAME 'mqttUser' - DESC 'Emqtt User' - SUP top STRUCTURAL - MUST ( cn $ username $ password ) - MAY ( description ) ) - -## attributetype ( 1.3.6.1.4.1.7914.1.2.1.3 NAME 'topic' -## DESC 'name of the user on the mailsystem' -## EQUALITY caseIgnoreIA5Match -## SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 -## SINGLE-value ) - -## attributetype ( 1.3.6.1.4.1.7914.1.2.1.4 NAME 'allow' -## DESC 'name of the user on the mailsystem' -## EQUALITY integerMatch -## SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 -## SINGLE-value ) - -## attributetype ( 1.3.6.1.4.1.7914.1.2.1.5 NAME 'access' -## DESC 'name of the user on the mailsystem' -## EQUALITY integerMatch -## SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 -## SINGLE-value ) - -## objectclass ( 1.3.6.1.4.1.7914.1.2.2.2 NAME 'mqttAcl' -## DESC 'Emqtt Acl' -## SUP top STRUCTURAL -## MUST ( cn $ username $ allow $ topic $ access ) -## MAY ( description ) ) \ No newline at end of file diff --git a/emqx.io.ldif b/emqx.io.ldif new file mode 100644 index 0000000..b694408 --- /dev/null +++ b/emqx.io.ldif @@ -0,0 +1,133 @@ +## create emqx.io + +dn:dc=emqx,dc=io +objectclass: top +objectclass: dcobject +objectclass: organization +dc:emqx +o:emqx,Inc. + +# create testdevice.emqx.io +dn:ou=testdevice,dc=emqx,dc=io +objectClass: top +objectclass:organizationalUnit +ou:testdevice + +# create user admin +dn:uid=admin,ou=testdevice,dc=emqx,dc=io +objectClass: top +objectClass: simpleSecurityObject +objectClass: account +userPassword:: e1NIQX1XNnBoNU1tNVB6OEdnaVVMYlBnekczN21qOWc9 +uid: admin + +## create user=mqttuser0001, +# password=mqttuser0001, +# passhash={SHA}mlb3fat40MKBTXUVZwCKmL73R/0= +# base64passhash=e1NIQX1tbGIzZmF0NDBNS0JUWFVWWndDS21MNzNSLzA9 +dn:uid=mqttuser0001,ou=testdevice,dc=emqx,dc=io +objectClass: top +objectClass: mqttUser +objectClass: mqttDevice +objectClass: mqttSecurity +uid: mqttuser0001 +isEnabled: TRUE +mqttPublishTopic: mqttuser0001/pub/1 +mqttPublishTopic: mqttuser0001/pub/+ +mqttPublishTopic: mqttuser0001/pub/# +mqttSubscriptionTopic: mqttuser0001/sub/1 +mqttSubscriptionTopic: mqttuser0001/sub/+ +mqttSubscriptionTopic: mqttuser0001/sub/# +mqttPubSubTopic: mqttuser0001/pubsub/1 +mqttPubSubTopic: mqttuser0001/pubsub/+ +mqttPubSubTopic: mqttuser0001/pubsub/# +userPassword:: e1NIQX1tbGIzZmF0NDBNS0JUWFVWWndDS21MNzNSLzA9 + +## create user=mqttuser0002 +# password=mqttuser0002, +# passhash={SSHA}n9XdtoG4Q/TQ3TQF4Y+khJbMBH4qXj4M +# base64passhash=e1NTSEF9bjlYZHRvRzRRL1RRM1RRRjRZK2toSmJNQkg0cVhqNE0= +dn:uid=mqttuser0002,ou=testdevice,dc=emqx,dc=io +objectClass: top +objectClass: mqttUser +objectClass: mqttDevice +objectClass: mqttSecurity +uid: mqttuser0002 +isEnabled: TRUE +mqttPublishTopic: mqttuser0002/pub/1 +mqttPublishTopic: mqttuser0002/pub/+ +mqttPublishTopic: mqttuser0002/pub/# +mqttSubscriptionTopic: mqttuser0002/sub/1 +mqttSubscriptionTopic: mqttuser0002/sub/+ +mqttSubscriptionTopic: mqttuser0002/sub/# +mqttPubSubTopic: mqttuser0002/pubsub/1 +mqttPubSubTopic: mqttuser0002/pubsub/+ +mqttPubSubTopic: mqttuser0002/pubsub/# +userPassword:: e1NTSEF9bjlYZHRvRzRRL1RRM1RRRjRZK2toSmJNQkg0cVhqNE0= + +## create user mqttuser0003 +# password=mqttuser0003, +# passhash={MD5}ybsPGoaK3nDyiQvveiCOIw== +# base64passhash=e01ENX15YnNQR29hSzNuRHlpUXZ2ZWlDT0l3PT0= +dn:uid=mqttuser0003,ou=testdevice,dc=emqx,dc=io +objectClass: top +objectClass: mqttUser +objectClass: mqttDevice +objectClass: mqttSecurity +uid: mqttuser0003 +isEnabled: TRUE +mqttPublishTopic: mqttuser0003/pub/1 +mqttPublishTopic: mqttuser0003/pub/+ +mqttPublishTopic: mqttuser0003/pub/# +mqttSubscriptionTopic: mqttuser0003/sub/1 +mqttSubscriptionTopic: mqttuser0003/sub/+ +mqttSubscriptionTopic: mqttuser0003/sub/# +mqttPubSubTopic: mqttuser0003/pubsub/1 +mqttPubSubTopic: mqttuser0003/pubsub/+ +mqttPubSubTopic: mqttuser0003/pubsub/# +userPassword:: e01ENX15YnNQR29hSzNuRHlpUXZ2ZWlDT0l3PT0= + +## create user mqttuser0004 +# password=mqttuser0004, +# passhash={MD5}2Br6pPDSEDIEvUlu9+s+MA== +# base64passhash=e01ENX0yQnI2cFBEU0VESUV2VWx1OStzK01BPT0= +dn:uid=mqttuser0004,ou=testdevice,dc=emqx,dc=io +objectClass: top +objectClass: mqttUser +objectClass: mqttDevice +objectClass: mqttSecurity +uid: mqttuser0004 +isEnabled: TRUE +mqttPublishTopic: mqttuser0004/pub/1 +mqttPublishTopic: mqttuser0004/pub/+ +mqttPublishTopic: mqttuser0004/pub/# +mqttSubscriptionTopic: mqttuser0004/sub/1 +mqttSubscriptionTopic: mqttuser0004/sub/+ +mqttSubscriptionTopic: mqttuser0004/sub/# +mqttPubSubTopic: mqttuser0004/pubsub/1 +mqttPubSubTopic: mqttuser0004/pubsub/+ +mqttPubSubTopic: mqttuser0004/pubsub/# +userPassword: {MD5}2Br6pPDSEDIEvUlu9+s+MA== + +## create user mqttuser0005 +# password=mqttuser0005, +# passhash={SHA}jKnxeEDGR14kE8AR7yuVFOelhz4= +# base64passhash=e1NIQX1qS254ZUVER1IxNGtFOEFSN3l1VkZPZWxoejQ9 +objectClass: top +dn:uid=mqttuser0005,ou=testdevice,dc=emqx,dc=io +objectClass: mqttUser +objectClass: mqttDevice +objectClass: mqttSecurity +uid: mqttuser0005 +isEnabled: TRUE +mqttPublishTopic: mqttuser0005/pub/1 +mqttPublishTopic: mqttuser0005/pub/+ +mqttPublishTopic: mqttuser0005/pub/# +mqttSubscriptionTopic: mqttuser0005/sub/1 +mqttSubscriptionTopic: mqttuser0005/sub/+ +mqttSubscriptionTopic: mqttuser0005/sub/# +mqttPubSubTopic: mqttuser0005/pubsub/1 +mqttPubSubTopic: mqttuser0005/pubsub/+ +mqttPubSubTopic: mqttuser0005/pubsub/# +userPassword: {SHA}jKnxeEDGR14kE8AR7yuVFOelhz4= + diff --git a/emqx.schema b/emqx.schema new file mode 100644 index 0000000..ea961db --- /dev/null +++ b/emqx.schema @@ -0,0 +1,40 @@ +# +# Preliminary Apple OS X Native LDAP Schema +# This file is subject to change. +# +attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.1.3 NAME 'isEnabled' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + SINGLE-VALUE + USAGE userApplications ) + +attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.1 NAME ( 'mqttPublishTopic' 'mpt' ) + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.2 NAME ( 'mqttSubscriptionTopic' 'mst' ) + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.3 NAME ( 'mqttPubSubTopic' 'mpst' ) + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + USAGE userApplications ) + +objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4 NAME 'mqttUser' + AUXILIARY + MAY ( mqttPublishTopic $ mqttSubscriptionTopic $ mqttPubSubTopic) ) + +objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.2 NAME 'mqttDevice' + SUP top + STRUCTURAL + MUST ( uid ) + MAY ( isEnabled ) ) + +objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.3 NAME 'mqttSecurity' + SUP top + AUXILIARY + MAY ( userPassword $ userPKCS12 $ pwdAttribute $ pwdLockout ) ) diff --git a/erlang.mk b/erlang.mk deleted file mode 100644 index 2bbffac..0000000 --- a/erlang.mk +++ /dev/null @@ -1,2728 +0,0 @@ -# Copyright (c) 2013-2015, 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 -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# 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 deps search rel docs install-docs check tests clean distclean help erlang-mk - -ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST))) - -ERLANG_MK_VERSION = 2.0.0-pre.2-128-gb48618d - -# Core configuration. - -PROJECT ?= $(notdir $(CURDIR)) -PROJECT := $(strip $(PROJECT)) - -PROJECT_VERSION ?= rolling -PROJECT_MOD ?= $(PROJECT)_app - -# Verbosity. - -V ?= 0 - -verbose_0 = @ -verbose_2 = set -x; -verbose = $(verbose_$(V)) - -gen_verbose_0 = @echo " GEN " $@; -gen_verbose_2 = set -x; -gen_verbose = $(gen_verbose_$(V)) - -# Temporary files directory. - -ERLANG_MK_TMP ?= $(CURDIR)/.erlang.mk -export ERLANG_MK_TMP - -# "erl" command. - -ERL = erl +A0 -noinput -boot start_clean - -# Platform detection. - -ifeq ($(PLATFORM),) -UNAME_S := $(shell uname -s) - -ifeq ($(UNAME_S),Linux) -PLATFORM = linux -else ifeq ($(UNAME_S),Darwin) -PLATFORM = darwin -else ifeq ($(UNAME_S),SunOS) -PLATFORM = solaris -else ifeq ($(UNAME_S),GNU) -PLATFORM = gnu -else ifeq ($(UNAME_S),FreeBSD) -PLATFORM = freebsd -else ifeq ($(UNAME_S),NetBSD) -PLATFORM = netbsd -else ifeq ($(UNAME_S),OpenBSD) -PLATFORM = openbsd -else ifeq ($(UNAME_S),DragonFly) -PLATFORM = dragonfly -else ifeq ($(shell uname -o),Msys) -PLATFORM = msys2 -else -$(error Unable to detect platform. Please open a ticket with the output of uname -a.) -endif - -export PLATFORM -endif - -# Core targets. - -all:: deps app rel - -# Noop to avoid a Make warning when there's nothing to do. -rel:: - $(verbose) : - -check:: tests - -clean:: clean-crashdump - -clean-crashdump: -ifneq ($(wildcard erl_crash.dump),) - $(gen_verbose) rm -f erl_crash.dump -endif - -distclean:: clean distclean-tmp - -distclean-tmp: - $(gen_verbose) rm -rf $(ERLANG_MK_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 " \ - "" \ - "Usage: [V=1] $(MAKE) [target]..." \ - "" \ - "Core targets:" \ - " all Run deps, app and rel targets in that order" \ - " app Compile the project" \ - " deps Fetch dependencies (if needed) and compile them" \ - " 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" \ - " install-docs Install the man pages for this project" \ - " check Compile and run all tests and analysis for this project" \ - " tests Run the tests for this project" \ - " clean Delete temporary and output files from most targets" \ - " distclean Delete all temporary and output files" \ - " help Display this help and exit" \ - " erlang-mk Update erlang.mk to the latest version" - -# Core functions. - -empty := -space := $(empty) $(empty) -tab := $(empty) $(empty) -comma := , - -define newline - - -endef - -define comma_list -$(subst $(space),$(comma),$(strip $(1))) -endef - -# Adding erlang.mk to make Erlang scripts who call init:get_plain_arguments() happy. -define erlang -$(ERL) $(2) -pz $(ERLANG_MK_TMP)/rebar/ebin -eval "$(subst $(newline),,$(subst ",\",$(1)))" -- erlang.mk -endef - -ifeq ($(PLATFORM),msys2) -core_native_path = $(subst \,\\\\,$(shell cygpath -w $1)) -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_eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))) - -core_find = $(if $(wildcard $1),$(shell find $(1:%/=%) -type f -name $(subst *,\*,$2))) - -core_lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(1))))))))))))))))))))))))))) - -core_ls = $(filter-out $(1),$(shell echo $(1))) - -# @todo Use a solution that does not require using perl. -core_relpath = $(shell perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' $1 $2) - -# Automated update. - -ERLANG_MK_REPO ?= https://github.com/ninenines/erlang.mk -ERLANG_MK_COMMIT ?= -ERLANG_MK_BUILD_CONFIG ?= build.config -ERLANG_MK_BUILD_DIR ?= .erlang.mk.build - -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) - cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk - rm -rf $(ERLANG_MK_BUILD_DIR) - -# 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, Loïc Hoguin -# This file is part of erlang.mk and subject to the terms of the ISC License. - -.PHONY: search - -define pkg_print - $(verbose) printf "%s\n" \ - $(if $(call core_eq,$(1),$(pkg_$(1)_name)),,"Pkg name: $(1)") \ - "App name: $(pkg_$(1)_name)" \ - "Description: $(pkg_$(1)_description)" \ - "Home page: $(pkg_$(1)_homepage)" \ - "Fetch with: $(pkg_$(1)_fetch)" \ - "Repository: $(pkg_$(1)_repo)" \ - "Commit: $(pkg_$(1)_commit)" \ - "" - -endef - -search: -ifdef q - $(foreach p,$(PACKAGES), \ - $(if $(findstring $(call core_lc,$(q)),$(call core_lc,$(pkg_$(p)_name) $(pkg_$(p)_description))), \ - $(call pkg_print,$(p)))) -else - $(foreach p,$(PACKAGES),$(call pkg_print,$(p))) -endif - -# Copyright (c) 2013-2015, Loïc Hoguin -# This file is part of erlang.mk and subject to the terms of the ISC License. - -.PHONY: distclean-deps - -# Configuration. - -ifdef OTP_DEPS -$(warning The variable OTP_DEPS is deprecated in favor of LOCAL_DEPS.) -endif - -IGNORE_DEPS ?= -export IGNORE_DEPS - -APPS_DIR ?= $(CURDIR)/apps -export APPS_DIR - -DEPS_DIR ?= $(CURDIR)/deps -export DEPS_DIR - -REBAR_DEPS_DIR = $(DEPS_DIR) -export REBAR_DEPS_DIR - -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))) - -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)))) - -ifeq ($(filter $(APPS_DIR) $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),) -ifeq ($(ERL_LIBS),) - ERL_LIBS = $(APPS_DIR):$(DEPS_DIR) -else - ERL_LIBS := $(ERL_LIBS):$(APPS_DIR):$(DEPS_DIR) -endif -endif -export ERL_LIBS - -export NO_AUTOPATCH - -# Verbosity. - -dep_verbose_0 = @echo " DEP " $(1); -dep_verbose_2 = set -x; -dep_verbose = $(dep_verbose_$(V)) - -# Core targets. - -ifneq ($(SKIP_DEPS),) -deps:: -else -deps:: $(ALL_DEPS_DIRS) -ifndef IS_APP - $(verbose) for dep in $(ALL_APPS_DIRS) ; do \ - mkdir -p $$dep/ebin; \ - done - $(verbose) for dep in $(ALL_APPS_DIRS) ; do \ - $(MAKE) -C $$dep IS_APP=1 || exit $$?; \ - done -endif -ifneq ($(IS_DEP),1) - $(verbose) rm -f $(ERLANG_MK_TMP)/deps.log -endif - $(verbose) mkdir -p $(ERLANG_MK_TMP) - $(verbose) 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 $$?; \ - else \ - echo "Error: No Makefile to build dependency $$dep."; \ - exit 2; \ - fi \ - fi \ - done -endif - -# Deps related targets. - -# @todo rename GNUmakefile and makefile into Makefile first, if they exist -# While Makefile file could be GNUmakefile or makefile, -# in practice only Makefile is needed so far. -define dep_autopatch - if [ -f $(DEPS_DIR)/$(1)/erlang.mk ]; then \ - $(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 \ - $(call dep_autopatch2,$(1)); \ - 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 \ - $(call dep_autopatch2,$(1)); \ - else \ - $(call erlang,$(call dep_autopatch_app.erl,$(1))); \ - fi \ - else \ - if [ ! -d $(DEPS_DIR)/$(1)/src/ ]; then \ - $(call dep_autopatch_noop,$(1)); \ - else \ - $(call dep_autopatch2,$(1)); \ - fi \ - fi -endef - -define dep_autopatch2 - 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 \ - $(call dep_autopatch_fetch_rebar); \ - $(call dep_autopatch_rebar,$(1)); \ - else \ - $(call dep_autopatch_gen,$(1)); \ - fi -endef - -define dep_autopatch_noop - printf "noop:\n" > $(DEPS_DIR)/$(1)/Makefile -endef - -# Overwrite erlang.mk with the current file by default. -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 -endef -else -define dep_autopatch_erlang_mk - : -endef -endif - -define dep_autopatch_gen - printf "%s\n" \ - "ERLC_OPTS = +debug_info" \ - "include ../../erlang.mk" > $(DEPS_DIR)/$(1)/Makefile -endef - -define dep_autopatch_fetch_rebar - mkdir -p $(ERLANG_MK_TMP); \ - 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; \ - $(MAKE); \ - cd -; \ - fi -endef - -define dep_autopatch_rebar - if [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \ - mv $(DEPS_DIR)/$(1)/Makefile $(DEPS_DIR)/$(1)/Makefile.orig.mk; \ - fi; \ - $(call erlang,$(call dep_autopatch_rebar.erl,$(1))); \ - rm -f $(DEPS_DIR)/$(1)/ebin/$(1).app -endef - -define dep_autopatch_rebar.erl - application:load(rebar), - application:set_env(rebar, log_level, debug), - Conf1 = case file:consult("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config)") of - {ok, Conf0} -> Conf0; - _ -> [] - end, - {Conf, OsEnv} = fun() -> - case filelib:is_file("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)") of - false -> {Conf1, []}; - true -> - Bindings0 = erl_eval:new_bindings(), - Bindings1 = erl_eval:add_binding('CONFIG', Conf1, Bindings0), - Bindings = erl_eval:add_binding('SCRIPT', "$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)", Bindings1), - Before = os:getenv(), - {ok, Conf2} = file:script("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)", Bindings), - {Conf2, lists:foldl(fun(E, Acc) -> lists:delete(E, Acc) end, os:getenv(), Before)} - end - end(), - Write = fun (Text) -> - file:write_file("$(call core_native_path,$(DEPS_DIR)/$1/Makefile)", Text, [append]) - end, - Escape = fun (Text) -> - re:replace(Text, "\\\\$$", "\$$$$", [global, {return, list}]) - end, - Write("IGNORE_DEPS += edown eper eunit_formatters meck node_package " - "rebar_lock_deps_plugin rebar_vsn_plugin reltool_util\n"), - Write("C_SRC_DIR = /path/do/not/exist\n"), - Write("C_SRC_TYPE = rebar\n"), - Write("DRV_CFLAGS = -fPIC\nexport DRV_CFLAGS\n"), - Write(["ERLANG_ARCH = ", rebar_utils:wordsize(), "\nexport ERLANG_ARCH\n"]), - fun() -> - Write("ERLC_OPTS = +debug_info\nexport ERLC_OPTS\n"), - case lists:keyfind(erl_opts, 1, Conf) of - false -> ok; - {_, ErlOpts} -> - lists:foreach(fun - ({d, D}) -> - Write("ERLC_OPTS += -D" ++ atom_to_list(D) ++ "=1\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"); - false -> ok - end; - ({parse_transform, PT}) -> - Write("ERLC_OPTS += +'{parse_transform, " ++ atom_to_list(PT) ++ "}'\n"); - (_) -> ok - end, ErlOpts) - end, - Write("\n") - end(), - fun() -> - File = case lists:keyfind(deps, 1, Conf) of - false -> []; - {_, Deps} -> - [begin case case Dep of - {N, S} when is_atom(N), is_list(S) -> {N, {hex, S}}; - {N, S} when is_tuple(S) -> {N, S}; - {N, _, S} -> {N, S}; - {N, _, S, _} -> {N, S}; - _ -> false - end of - false -> ok; - {Name, Source} -> - {Method, Repo, Commit} = case Source of - {hex, V} -> {hex, V, undefined}; - {git, R} -> {git, R, master}; - {M, R, {branch, C}} -> {M, R, C}; - {M, R, {ref, C}} -> {M, R, C}; - {M, R, {tag, C}} -> {M, R, C}; - {M, R, C} -> {M, R, C} - end, - Write(io_lib:format("DEPS += ~s\ndep_~s = ~s ~s ~s~n", [Name, Name, Method, Repo, Commit])) - end end || Dep <- Deps] - end - end(), - fun() -> - case lists:keyfind(erl_first_files, 1, Conf) of - false -> ok; - {_, Files} -> - Names = [[" ", case lists:reverse(F) of - "lre." ++ Elif -> lists:reverse(Elif); - Elif -> lists:reverse(Elif) - end] || "src/" ++ F <- Files], - Write(io_lib:format("COMPILE_FIRST +=~s\n", [Names])) - end - end(), - Write("\n\nrebar_dep: preprocess pre-deps deps pre-app app\n"), - Write("\npreprocess::\n"), - Write("\npre-deps::\n"), - Write("\npre-app::\n"), - PatchHook = fun(Cmd) -> - case Cmd of - "make -C" ++ Cmd1 -> "$$\(MAKE) -C" ++ Escape(Cmd1); - "gmake -C" ++ Cmd1 -> "$$\(MAKE) -C" ++ Escape(Cmd1); - "make " ++ Cmd1 -> "$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1); - "gmake " ++ Cmd1 -> "$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1); - _ -> Escape(Cmd) - end - end, - fun() -> - case lists:keyfind(pre_hooks, 1, Conf) of - false -> ok; - {_, Hooks} -> - [case H of - {'get-deps', Cmd} -> - Write("\npre-deps::\n\t" ++ PatchHook(Cmd) ++ "\n"); - {compile, Cmd} -> - Write("\npre-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n"); - {Regex, compile, Cmd} -> - case rebar_utils:is_arch(Regex) of - true -> Write("\npre-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n"); - false -> ok - end; - _ -> ok - end || H <- Hooks] - end - end(), - ShellToMk = fun(V) -> - re:replace(re:replace(V, "(\\\\$$)(\\\\w*)", "\\\\1(\\\\2)", [global]), - "-Werror\\\\b", "", [{return, list}, global]) - end, - PortSpecs = fun() -> - case lists:keyfind(port_specs, 1, Conf) of - false -> - case filelib:is_dir("$(call core_native_path,$(DEPS_DIR)/$1/c_src)") of - false -> []; - true -> - [{"priv/" ++ proplists:get_value(so_name, Conf, "$(1)_drv.so"), - proplists:get_value(port_sources, Conf, ["c_src/*.c"]), []}] - end; - {_, Specs} -> - lists:flatten([case S of - {Output, Input} -> {ShellToMk(Output), Input, []}; - {Regex, Output, Input} -> - case rebar_utils:is_arch(Regex) of - true -> {ShellToMk(Output), Input, []}; - false -> [] - end; - {Regex, Output, Input, [{env, Env}]} -> - case rebar_utils:is_arch(Regex) of - true -> {ShellToMk(Output), Input, Env}; - false -> [] - end - end || S <- Specs]) - end - end(), - PortSpecWrite = fun (Text) -> - file:write_file("$(call core_native_path,$(DEPS_DIR)/$1/c_src/Makefile.erlang.mk)", Text, [append]) - end, - case PortSpecs of - [] -> 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", - [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", - [code:lib_dir(erl_interface, lib)])), - [PortSpecWrite(["\n", E, "\n"]) || E <- OsEnv], - FilterEnv = fun(Env) -> - lists:flatten([case E of - {_, _} -> E; - {Regex, K, V} -> - case rebar_utils:is_arch(Regex) of - true -> {K, V}; - false -> [] - end - end || E <- Env]) - end, - MergeEnv = fun(Env) -> - lists:foldl(fun ({K, V}, Acc) -> - case lists:keyfind(K, 1, Acc) of - false -> [{K, rebar_utils:expand_env_variable(V, K, "")}|Acc]; - {_, V0} -> [{K, rebar_utils:expand_env_variable(V, K, V0)}|Acc] - end - end, [], Env) - end, - PortEnv = case lists:keyfind(port_env, 1, Conf) of - false -> []; - {_, PortEnv0} -> FilterEnv(PortEnv0) - end, - PortSpec = fun ({Output, Input0, Env}) -> - filelib:ensure_dir("$(call core_native_path,$(DEPS_DIR)/$1/)" ++ Output), - Input = [[" ", I] || I <- Input0], - PortSpecWrite([ - [["\n", K, " = ", ShellToMk(V)] || {K, V} <- lists:reverse(MergeEnv(PortEnv))], - case $(PLATFORM) of - darwin -> "\n\nLDFLAGS += -flat_namespace -undefined suppress"; - _ -> "" - end, - "\n\nall:: ", Output, "\n\n", - "%.o: %.c\n\t$$\(CC) -c -o $$\@ $$\< $$\(CFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n", - "%.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, ": $$\(foreach ext,.c .C .cc .cpp,", - "$$\(patsubst %$$\(ext),%.o,$$\(filter %$$\(ext),$$\(wildcard", Input, "))))\n", - "\t$$\(CC) -o $$\@ $$\? $$\(LDFLAGS) $$\(ERL_LDFLAGS) $$\(DRV_LDFLAGS) $$\(EXE_LDFLAGS)", - case {filename:extension(Output), $(PLATFORM)} of - {[], _} -> "\n"; - {_, darwin} -> "\n"; - _ -> " -shared\n" - end]) - end, - [PortSpec(S) || S <- PortSpecs] - end, - Write("\ninclude $(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(DEPS_DIR)/app)/erlang.mk"), - RunPlugin = fun(Plugin, Step) -> - case erlang:function_exported(Plugin, Step, 2) of - false -> ok; - true -> - c:cd("$(call core_native_path,$(DEPS_DIR)/$1/)"), - Ret = Plugin:Step({config, "", Conf, dict:new(), dict:new(), dict:new(), - dict:store(base_dir, "", dict:new())}, undefined), - io:format("rebar plugin ~p step ~p ret ~p~n", [Plugin, Step, Ret]) - end - end, - fun() -> - case lists:keyfind(plugins, 1, Conf) of - false -> ok; - {_, Plugins} -> - [begin - case lists:keyfind(deps, 1, Conf) of - false -> ok; - {_, Deps} -> - case lists:keyfind(P, 1, Deps) of - false -> ok; - _ -> - Path = "$(call core_native_path,$(DEPS_DIR)/)" ++ atom_to_list(P), - io:format("~s", [os:cmd("$(MAKE) -C $(call core_native_path,$(DEPS_DIR)/$1) " ++ Path)]), - io:format("~s", [os:cmd("$(MAKE) -C " ++ Path ++ " IS_DEP=1")]), - code:add_patha(Path ++ "/ebin") - end - end - end || P <- Plugins], - [case code:load_file(P) of - {module, P} -> ok; - _ -> - case lists:keyfind(plugin_dir, 1, Conf) of - false -> ok; - {_, PluginsDir} -> - ErlFile = "$(call core_native_path,$(DEPS_DIR)/$1/)" ++ PluginsDir ++ "/" ++ atom_to_list(P) ++ ".erl", - {ok, P, Bin} = compile:file(ErlFile, [binary]), - {module, P} = code:load_binary(P, ErlFile, Bin) - end - end || P <- Plugins], - [RunPlugin(P, preprocess) || P <- Plugins], - [RunPlugin(P, pre_compile) || P <- Plugins], - [RunPlugin(P, compile) || P <- Plugins] - end - end(), - 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 = file:write_file(AppSrc, io_lib:format("~p.~n", [Conf])), - halt() -endef - -define dep_autopatch_appsrc.erl - AppSrcOut = "$(call core_native_path,$(DEPS_DIR)/$1/src/$1.app.src)", - AppSrcIn = case filelib:is_regular(AppSrcOut) of false -> "$(call core_native_path,$(DEPS_DIR)/$1/ebin/$1.app)"; true -> AppSrcOut end, - case filelib:is_regular(AppSrcIn) of - false -> ok; - 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, - 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 - end, - halt() -endef - -define dep_fetch_git - git clone -q -n -- $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); \ - cd $(DEPS_DIR)/$(call dep_name,$(1)) && git checkout -q $(call dep_commit,$(1)); -endef - -define dep_fetch_git-submodule - git submodule update --init -- $(DEPS_DIR)/$1; -endef - -define dep_fetch_hg - hg clone -q -U $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); \ - cd $(DEPS_DIR)/$(call dep_name,$(1)) && hg update -q $(call dep_commit,$(1)); -endef - -define dep_fetch_svn - svn checkout -q $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); -endef - -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() -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)))))); -endef - -define dep_fetch_fail - echo "Error: Unknown or invalid dependency: $(1)." >&2; \ - exit 78; -endef - -# Kept for compatibility purposes with older Erlang.mk configuration. -define dep_fetch_legacy - $(warning WARNING: '$(1)' dependency configuration uses deprecated format.) \ - git clone -q -n -- $(word 1,$(dep_$(1))) $(DEPS_DIR)/$(1); \ - cd $(DEPS_DIR)/$(1) && git checkout -q $(if $(word 2,$(dep_$(1))),$(word 2,$(dep_$(1))),master); -endef - -define dep_fetch - $(if $(dep_$(1)), \ - $(if $(dep_fetch_$(word 1,$(dep_$(1)))), \ - $(word 1,$(dep_$(1))), \ - $(if $(IS_DEP),legacy,fail)), \ - $(if $(filter $(1),$(PACKAGES)), \ - $(pkg_$(1)_fetch), \ - fail)) -endef - -define dep_target -$(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)."; \ - exit 17; \ - fi - $(verbose) mkdir -p $(DEPS_DIR) - $(dep_verbose) $(call dep_fetch_$(strip $(call dep_fetch,$(1))),$(1)) - $(verbose) if [ -f $(DEPS_DIR)/$(1)/configure.ac -o -f $(DEPS_DIR)/$(1)/configure.in ] \ - && [ ! -f $(DEPS_DIR)/$(1)/configure ]; then \ - echo " AUTO " $(1); \ - cd $(DEPS_DIR)/$(1) && autoreconf -Wall -vif -I m4; \ - fi - - $(verbose) if [ -f $(DEPS_DIR)/$(DEP_NAME)/configure ]; then \ - echo " CONF " $(DEP_STR); \ - cd $(DEPS_DIR)/$(DEP_NAME) && ./configure; \ - fi -ifeq ($(filter $(1),$(NO_AUTOPATCH)),) - $(verbose) if [ "$(1)" = "amqp_client" -a "$(RABBITMQ_CLIENT_PATCH)" ]; then \ - if [ ! -d $(DEPS_DIR)/rabbitmq-codegen ]; then \ - echo " PATCH Downloading rabbitmq-codegen"; \ - git clone https://github.com/rabbitmq/rabbitmq-codegen.git $(DEPS_DIR)/rabbitmq-codegen; \ - fi; \ - if [ ! -d $(DEPS_DIR)/rabbitmq-server ]; then \ - echo " PATCH Downloading rabbitmq-server"; \ - git clone https://github.com/rabbitmq/rabbitmq-server.git $(DEPS_DIR)/rabbitmq-server; \ - fi; \ - ln -s $(DEPS_DIR)/amqp_client/deps/rabbit_common-0.0.0 $(DEPS_DIR)/rabbit_common; \ - elif [ "$(1)" = "rabbit" -a "$(RABBITMQ_SERVER_PATCH)" ]; then \ - if [ ! -d $(DEPS_DIR)/rabbitmq-codegen ]; then \ - echo " PATCH Downloading rabbitmq-codegen"; \ - git clone https://github.com/rabbitmq/rabbitmq-codegen.git $(DEPS_DIR)/rabbitmq-codegen; \ - fi \ - else \ - $$(call dep_autopatch,$(DEP_NAME)) \ - fi -endif -endef - -$(foreach dep,$(BUILD_DEPS) $(DEPS),$(eval $(call dep_target,$(dep)))) - -ifndef IS_APP -clean:: clean-apps - -clean-apps: - $(verbose) for dep in $(ALL_APPS_DIRS) ; do \ - $(MAKE) -C $$dep clean IS_APP=1 || exit $$?; \ - done - -distclean:: distclean-apps - -distclean-apps: - $(verbose) for dep in $(ALL_APPS_DIRS) ; do \ - $(MAKE) -C $$dep distclean IS_APP=1 || exit $$?; \ - done -endif - -ifndef SKIP_DEPS -distclean:: distclean-deps - -distclean-deps: - $(gen_verbose) rm -rf $(DEPS_DIR) -endif - -# External plugins. - -DEP_PLUGINS ?= - -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 -# This file is part of erlang.mk and subject to the terms of the ISC License. - -# Verbosity. - -proto_verbose_0 = @echo " PROTO " $(filter %.proto,$(?F)); -proto_verbose = $(proto_verbose_$(V)) - -# Core targets. - -define compile_proto - $(verbose) mkdir -p ebin/ include/ - $(proto_verbose) $(call erlang,$(call compile_proto.erl,$(1))) - $(proto_verbose) erlc +debug_info -o ebin/ ebin/*.erl - $(verbose) rm ebin/*.erl -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"}]) - end || F <- string:tokens("$(1)", " ")], - halt(). -endef - -ifneq ($(wildcard src/),) -ebin/$(PROJECT).app:: $(sort $(call core_find,src/,*.proto)) - $(if $(strip $?),$(call compile_proto,$?)) -endif - -# Copyright (c) 2013-2015, Loïc Hoguin -# This file is part of erlang.mk and subject to the terms of the ISC License. - -.PHONY: clean-app - -# Configuration. - -ERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \ - +warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec -COMPILE_FIRST ?= -COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST))) -ERLC_EXCLUDE ?= -ERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE))) - -ERLC_MIB_OPTS ?= -COMPILE_MIB_FIRST ?= -COMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST))) - -# Verbosity. - -app_verbose_0 = @echo " APP " $(PROJECT); -app_verbose_2 = set -x; -app_verbose = $(app_verbose_$(V)) - -appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src; -appsrc_verbose_2 = set -x; -appsrc_verbose = $(appsrc_verbose_$(V)) - -makedep_verbose_0 = @echo " DEPEND" $(PROJECT).d; -makedep_verbose_2 = set -x; -makedep_verbose = $(makedep_verbose_$(V)) - -erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\ - $(filter %.erl %.core,$(?F))); -erlc_verbose_2 = set -x; -erlc_verbose = $(erlc_verbose_$(V)) - -xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F)); -xyrl_verbose_2 = set -x; -xyrl_verbose = $(xyrl_verbose_$(V)) - -asn1_verbose_0 = @echo " ASN1 " $(filter %.asn1,$(?F)); -asn1_verbose_2 = set -x; -asn1_verbose = $(asn1_verbose_$(V)) - -mib_verbose_0 = @echo " MIB " $(filter %.bin %.mib,$(?F)); -mib_verbose_2 = set -x; -mib_verbose = $(mib_verbose_$(V)) - -ifneq ($(wildcard src/),) - -# Targets. - -ifeq ($(wildcard ebin/test),) -app:: deps $(PROJECT).d - $(verbose) $(MAKE) --no-print-directory app-build -else -app:: clean deps $(PROJECT).d - $(verbose) $(MAKE) --no-print-directory app-build -endif - -ifeq ($(wildcard src/$(PROJECT_MOD).erl),) -define app_file -{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))]} -]}. -endef -else -define app_file -{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), []}} -]}. -endef -endif - -app-build: ebin/$(PROJECT).app - $(verbose) : - -# Source files. - -ERL_FILES = $(sort $(call core_find,src/,*.erl)) -CORE_FILES = $(sort $(call core_find,src/,*.core)) - -# ASN.1 files. - -ifneq ($(wildcard asn1/),) -ASN1_FILES = $(sort $(call core_find,asn1/,*.asn1)) -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) - $(verbose) mv asn1/*.erl src/ - $(verbose) mv asn1/*.hrl include/ - $(verbose) mv asn1/*.asn1db include/ -endef - -$(PROJECT).d:: $(ASN1_FILES) - $(if $(strip $?),$(call compile_asn1,$?)) -endif - -# SNMP MIB files. - -ifneq ($(wildcard mibs/),) -MIB_FILES = $(sort $(call core_find,mibs/,*.mib)) - -$(PROJECT).d:: $(COMPILE_MIB_FIRST_PATHS) $(MIB_FILES) - $(verbose) mkdir -p include/ priv/mibs/ - $(mib_verbose) erlc -v $(ERLC_MIB_OPTS) -o priv/mibs/ -I priv/mibs/ $? - $(mib_verbose) erlc -o include/ -- $(addprefix priv/mibs/,$(patsubst %.mib,%.bin,$(notdir $?))) -endif - -# Leex and Yecc files. - -XRL_FILES = $(sort $(call core_find,src/,*.xrl)) -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_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/ $?) - -# Erlang and Core Erlang files. - -define makedep.erl - E = ets:new(makedep, [bag]), - G = digraph:new([acyclic]), - ErlFiles = lists:usort(string:tokens("$(ERL_FILES)", " ")), - Modules = [{list_to_atom(filename:basename(F, ".erl")), F} || F <- ErlFiles], - Add = fun (Mod, Dep) -> - case lists:keyfind(Dep, 1, Modules) of - false -> ok; - {_, DepFile} -> - {_, ModFile} = lists:keyfind(Mod, 1, Modules), - ets:insert(E, {ModFile, DepFile}), - digraph:add_vertex(G, Mod), - digraph:add_vertex(G, Dep), - digraph:add_edge(G, Mod, Dep) - end - end, - AddHd = fun (F, Mod, DepFile) -> - case file:open(DepFile, [read]) of - {error, enoent} -> ok; - {ok, Fd} -> - F(F, Fd, Mod), - {_, ModFile} = lists:keyfind(Mod, 1, Modules), - ets:insert(E, {ModFile, DepFile}) - end - end, - Attr = fun - (F, Mod, behavior, Dep) -> Add(Mod, Dep); - (F, Mod, behaviour, Dep) -> Add(Mod, Dep); - (F, Mod, compile, {parse_transform, Dep}) -> Add(Mod, Dep); - (F, Mod, compile, Opts) when is_list(Opts) -> - case proplists:get_value(parse_transform, Opts) of - undefined -> ok; - Dep -> Add(Mod, Dep) - end; - (F, Mod, include, Hrl) -> - case filelib:is_file("include/" ++ Hrl) of - true -> AddHd(F, Mod, "include/" ++ Hrl); - false -> - case filelib:is_file("src/" ++ Hrl) of - true -> AddHd(F, Mod, "src/" ++ Hrl); - false -> false - end - end; - (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 - false -> ok; - true -> Add(Mod, Imp) - end; - (_, _, _, _) -> ok - end, - MakeDepend = fun(F, Fd, Mod) -> - case io:parse_erl_form(Fd, undefined) of - {ok, {attribute, _, Key, Value}, _} -> - Attr(F, Mod, Key, Value), - F(F, Fd, Mod); - {eof, _} -> - file:close(Fd); - _ -> - F(F, Fd, Mod) - end - end, - [begin - Mod = list_to_atom(filename:basename(F, ".erl")), - {ok, Fd} = file:open(F, [read]), - MakeDepend(MakeDepend, Fd, Mod) - 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)], - ok = file:write_file("$(1)", [ - [[F, "::", [[" ", D] || D <- Deps], "; @touch \$$@\n"] || {F, Deps} <- Depend], - "\nCOMPILE_FIRST +=", [[" ", atom_to_list(CF)] || CF <- CompileFirst], "\n" - ]), - halt() -endef - -ifeq ($(if $(NO_MAKEDEP),$(wildcard $(PROJECT).d),),) -$(PROJECT).d:: $(ERL_FILES) $(call core_find,include/,*.hrl) - $(makedep_verbose) $(call erlang,$(call makedep.erl,$@)) -endif - -# Rebuild everything when the Makefile changes. -$(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES):: $(MAKEFILE_LIST) - @touch $@ - --include $(PROJECT).d - -ebin/$(PROJECT).app:: ebin/ - -ebin/: - $(verbose) mkdir -p ebin/ - -define compile_erl - $(erlc_verbose) erlc -v $(if $(IS_DEP),$(filter-out -Werror,$(ERLC_OPTS)),$(ERLC_OPTS)) -o ebin/ \ - -pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),$(COMPILE_FIRST_PATHS) $(1)) -endef - -ebin/$(PROJECT).app:: $(ERL_FILES) $(CORE_FILES) $(wildcard src/$(PROJECT).app.src) - $(eval FILES_TO_COMPILE := $(filter-out src/$(PROJECT).app.src,$?)) - $(if $(strip $(FILES_TO_COMPILE)),$(call compile_erl,$(FILES_TO_COMPILE))) - $(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null || true)) - $(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))))" \ - > ebin/$(PROJECT).app -else - $(verbose) if [ -z "$$(grep -e '^[^%]*{\s*modules\s*,' src/$(PROJECT).app.src)" ]; then \ - echo "Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk README for instructions." >&2; \ - exit 1; \ - fi - $(appsrc_verbose) cat src/$(PROJECT).app.src \ - | sed "s/{[[:space:]]*modules[[:space:]]*,[[:space:]]*\[\]}/{modules, \[$(call comma_list,$(MODULES))\]}/" \ - | sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(subst /,\/,$(GITDESCRIBE))\"}/" \ - > ebin/$(PROJECT).app -endif - -clean:: clean-app - -clean-app: - $(gen_verbose) rm -rf $(PROJECT).d ebin/ priv/mibs/ $(XRL_ERL_FILES) $(YRL_ERL_FILES) \ - $(addprefix include/,$(patsubst %.mib,%.hrl,$(notdir $(MIB_FILES)))) \ - $(addprefix include/,$(patsubst %.asn1,%.hrl,$(notdir $(ASN1_FILES)))) \ - $(addprefix include/,$(patsubst %.asn1,%.asn1db,$(notdir $(ASN1_FILES)))) \ - $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES)))) - -endif - -# Copyright (c) 2015, Viktor Söderqvist -# This file is part of erlang.mk and subject to the terms of the ISC License. - -.PHONY: docs-deps - -# Configuration. - -ALL_DOC_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DOC_DEPS)) - -# Targets. - -$(foreach dep,$(DOC_DEPS),$(eval $(call dep_target,$(dep)))) - -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 -endif - -# Copyright (c) 2015, Loïc Hoguin -# This file is part of erlang.mk and subject to the terms of the ISC License. - -.PHONY: rel-deps - -# Configuration. - -ALL_REL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(REL_DEPS)) - -# Targets. - -$(foreach dep,$(REL_DEPS),$(eval $(call dep_target,$(dep)))) - -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 -endif - -# Copyright (c) 2015, 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 - -# Configuration. - -TEST_DIR ?= $(CURDIR)/test - -ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS)) - -TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard -TEST_ERLC_OPTS += -DTEST=1 - -# Targets. - -$(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep)))) - -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 -endif - -ifneq ($(wildcard $(TEST_DIR)),) -test-dir: - $(gen_verbose) erlc -v $(TEST_ERLC_OPTS) -I include/ -o $(TEST_DIR) \ - $(call core_find,$(TEST_DIR)/,*.erl) -pa ebin/ -endif - -ifeq ($(wildcard src),) -test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS) -test-build:: clean deps test-deps - $(verbose) $(MAKE) --no-print-directory test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)" -else -ifeq ($(wildcard ebin/test),) -test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS) -test-build:: clean deps test-deps $(PROJECT).d - $(verbose) $(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)" - $(gen_verbose) touch ebin/test -else -test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS) -test-build:: deps test-deps $(PROJECT).d - $(verbose) $(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)" -endif - -clean:: clean-test-dir - -clean-test-dir: -ifneq ($(wildcard $(TEST_DIR)/*.beam),) - $(gen_verbose) rm -f $(TEST_DIR)/*.beam -endif -endif - -# Copyright (c) 2015, Loïc Hoguin -# This file is part of erlang.mk and subject to the terms of the ISC License. - -.PHONY: rebar.config - -# We strip out -Werror because we don't want to fail due to -# warnings when used as a dependency. - -compat_prepare_erlc_opts = $(shell echo "$1" | sed 's/, */,/g') - -define compat_convert_erlc_opts -$(if $(filter-out -Werror,$1),\ - $(if $(findstring +,$1),\ - $(shell echo $1 | cut -b 2-))) -endef - -define compat_erlc_opts_to_list -[$(call comma_list,$(foreach o,$(call compat_prepare_erlc_opts,$1),$(call compat_convert_erlc_opts,$o)))] -endef - -define compat_rebar_config -{deps, [ -$(call comma_list,$(foreach d,$(DEPS),\ - $(if $(filter hex,$(call dep_fetch,$d)),\ - {$(call dep_name,$d)$(comma)"$(call dep_repo,$d)"},\ - {$(call dep_name,$d)$(comma)".*"$(comma){git,"$(call dep_repo,$d)"$(comma)"$(call dep_commit,$d)"}}))) -]}. -{erl_opts, $(call compat_erlc_opts_to_list,$(ERLC_OPTS))}. -endef - -$(eval _compat_rebar_config = $$(compat_rebar_config)) -$(eval export _compat_rebar_config) - -rebar.config: - $(gen_verbose) echo "$${_compat_rebar_config}" > rebar.config - -# Copyright (c) 2015, 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 - -MAN_INSTALL_PATH ?= /usr/local/share/man -MAN_SECTIONS ?= 3 7 - -docs:: asciidoc - -asciidoc: asciidoc-guide asciidoc-manual - -ifeq ($(wildcard doc/src/guide/book.asciidoc),) -asciidoc-guide: -else -asciidoc-guide: distclean-asciidoc 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/ -endif - -ifeq ($(wildcard doc/src/manual/*.asciidoc),) -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 - -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 -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 -# 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 - -# Core targets. - -help:: - $(verbose) printf "%s\n" "" \ - "Bootstrap targets:" \ - " bootstrap Generate a skeleton of an OTP application" \ - " bootstrap-lib Generate a skeleton of an OTP library" \ - " bootstrap-rel Generate the files needed to build a release" \ - " new-app in=NAME Create a new local OTP application NAME" \ - " new-lib in=NAME Create a new local OTP library NAME" \ - " new t=TPL n=NAME Generate a module NAME based on the template TPL" \ - " new t=T n=N in=APP Generate a module NAME based on the template TPL in APP" \ - " list-templates List available templates" - -# Bootstrap templates. - -define bs_appsrc -{application, $p, [ - {description, ""}, - {vsn, "0.1.0"}, - {id, "git"}, - {modules, []}, - {registered, []}, - {applications, [ - kernel, - stdlib - ]}, - {mod, {$p_app, []}}, - {env, []} -]}. -endef - -define bs_appsrc_lib -{application, $p, [ - {description, ""}, - {vsn, "0.1.0"}, - {id, "git"}, - {modules, []}, - {registered, []}, - {applications, [ - kernel, - stdlib - ]} -]}. -endef - -# To prevent autocompletion issues with ZSH, we add "include erlang.mk" -# separately during the actual bootstrap. -ifdef SP -define bs_Makefile -PROJECT = $p -PROJECT_DESCRIPTION = New project -PROJECT_VERSION = 0.0.1 - -# Whitespace to be used when creating files from templates. -SP = $(SP) - -endef -else -define bs_Makefile -PROJECT = $p -PROJECT_DESCRIPTION = New project -PROJECT_VERSION = 0.0.1 - -endef -endif - -define bs_apps_Makefile -PROJECT = $p -PROJECT_DESCRIPTION = New project -PROJECT_VERSION = 0.0.1 - -include $(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(APPS_DIR)/app)/erlang.mk -endef - -define bs_app --module($p_app). --behaviour(application). - --export([start/2]). --export([stop/1]). - -start(_Type, _Args) -> - $p_sup:start_link(). - -stop(_State) -> - ok. -endef - -define bs_relx_config -{release, {$p_release, "1"}, [$p]}. -{extended_start_script, true}. -{sys_config, "rel/sys.config"}. -{vm_args, "rel/vm.args"}. -endef - -define bs_sys_config -[ -]. -endef - -define bs_vm_args --name $p@127.0.0.1 --setcookie $p --heart -endef - -# Normal templates. - -define tpl_supervisor --module($(n)). --behaviour(supervisor). - --export([start_link/0]). --export([init/1]). - -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -init([]) -> - Procs = [], - {ok, {{one_for_one, 1, 5}, Procs}}. -endef - -define tpl_gen_server --module($(n)). --behaviour(gen_server). - -%% API. --export([start_link/0]). - -%% gen_server. --export([init/1]). --export([handle_call/3]). --export([handle_cast/2]). --export([handle_info/2]). --export([terminate/2]). --export([code_change/3]). - --record(state, { -}). - -%% API. - --spec start_link() -> {ok, pid()}. -start_link() -> - gen_server:start_link(?MODULE, [], []). - -%% gen_server. - -init([]) -> - {ok, #state{}}. - -handle_call(_Request, _From, State) -> - {reply, ignored, State}. - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. -endef - -define tpl_module --module($(n)). --export([]). -endef - -define tpl_cowboy_http --module($(n)). --behaviour(cowboy_http_handler). - --export([init/3]). --export([handle/2]). --export([terminate/3]). - --record(state, { -}). - -init(_, Req, _Opts) -> - {ok, Req, #state{}}. - -handle(Req, State=#state{}) -> - {ok, Req2} = cowboy_req:reply(200, Req), - {ok, Req2, State}. - -terminate(_Reason, _Req, _State) -> - ok. -endef - -define tpl_gen_fsm --module($(n)). --behaviour(gen_fsm). - -%% API. --export([start_link/0]). - -%% gen_fsm. --export([init/1]). --export([state_name/2]). --export([handle_event/3]). --export([state_name/3]). --export([handle_sync_event/4]). --export([handle_info/3]). --export([terminate/3]). --export([code_change/4]). - --record(state, { -}). - -%% API. - --spec start_link() -> {ok, pid()}. -start_link() -> - gen_fsm:start_link(?MODULE, [], []). - -%% gen_fsm. - -init([]) -> - {ok, state_name, #state{}}. - -state_name(_Event, StateData) -> - {next_state, state_name, StateData}. - -handle_event(_Event, StateName, StateData) -> - {next_state, StateName, StateData}. - -state_name(_Event, _From, StateData) -> - {reply, ignored, state_name, StateData}. - -handle_sync_event(_Event, _From, StateName, StateData) -> - {reply, ignored, StateName, StateData}. - -handle_info(_Info, StateName, StateData) -> - {next_state, StateName, StateData}. - -terminate(_Reason, _StateName, _StateData) -> - ok. - -code_change(_OldVsn, StateName, StateData, _Extra) -> - {ok, StateName, StateData}. -endef - -define tpl_cowboy_loop --module($(n)). --behaviour(cowboy_loop_handler). - --export([init/3]). --export([info/3]). --export([terminate/3]). - --record(state, { -}). - -init(_, Req, _Opts) -> - {loop, Req, #state{}, 5000, hibernate}. - -info(_Info, Req, State) -> - {loop, Req, State, hibernate}. - -terminate(_Reason, _Req, _State) -> - ok. -endef - -define tpl_cowboy_rest --module($(n)). - --export([init/3]). --export([content_types_provided/2]). --export([get_html/2]). - -init(_, _Req, _Opts) -> - {upgrade, protocol, cowboy_rest}. - -content_types_provided(Req, State) -> - {[{{<<"text">>, <<"html">>, '*'}, get_html}], Req, State}. - -get_html(Req, State) -> - {<<"This is REST!">>, Req, State}. -endef - -define tpl_cowboy_ws --module($(n)). --behaviour(cowboy_websocket_handler). - --export([init/3]). --export([websocket_init/3]). --export([websocket_handle/3]). --export([websocket_info/3]). --export([websocket_terminate/3]). - --record(state, { -}). - -init(_, _, _) -> - {upgrade, protocol, cowboy_websocket}. - -websocket_init(_, Req, _Opts) -> - Req2 = cowboy_req:compact(Req), - {ok, Req2, #state{}}. - -websocket_handle({text, Data}, Req, State) -> - {reply, {text, Data}, Req, State}; -websocket_handle({binary, Data}, Req, State) -> - {reply, {binary, Data}, Req, State}; -websocket_handle(_Frame, Req, State) -> - {ok, Req, State}. - -websocket_info(_Info, Req, State) -> - {ok, Req, State}. - -websocket_terminate(_Reason, _Req, _State) -> - ok. -endef - -define tpl_ranch_protocol --module($(n)). --behaviour(ranch_protocol). - --export([start_link/4]). --export([init/4]). - --type opts() :: []. --export_type([opts/0]). - --record(state, { - socket :: inet:socket(), - transport :: module() -}). - -start_link(Ref, Socket, Transport, Opts) -> - Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), - {ok, Pid}. - --spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok. -init(Ref, Socket, Transport, _Opts) -> - ok = ranch:accept_ack(Ref), - loop(#state{socket=Socket, transport=Transport}). - -loop(State) -> - loop(State). -endef - -# Plugin-specific targets. - -define render_template - $(verbose) printf -- '$(subst $(newline),\n,$(subst %,%%,$(subst ','\'',$(subst $(tab),$(WS),$(call $(1))))))\n' > $(2) -endef - -ifndef WS -ifdef SP -WS = $(subst a,,a $(wordlist 1,$(SP),a a a a a a a a a a a a a a a a a a a a)) -else -WS = $(tab) -endif -endif - -bootstrap: -ifneq ($(wildcard src/),) - $(error Error: src/ directory already exists) -endif - $(eval p := $(PROJECT)) - $(eval n := $(PROJECT)_sup) - $(call render_template,bs_Makefile,Makefile) - $(verbose) echo "include erlang.mk" >> Makefile - $(verbose) mkdir src/ -ifdef LEGACY - $(call render_template,bs_appsrc,src/$(PROJECT).app.src) -endif - $(call render_template,bs_app,src/$(PROJECT)_app.erl) - $(call render_template,tpl_supervisor,src/$(PROJECT)_sup.erl) - -bootstrap-lib: -ifneq ($(wildcard src/),) - $(error Error: src/ directory already exists) -endif - $(eval p := $(PROJECT)) - $(call render_template,bs_Makefile,Makefile) - $(verbose) echo "include erlang.mk" >> Makefile - $(verbose) mkdir src/ -ifdef LEGACY - $(call render_template,bs_appsrc_lib,src/$(PROJECT).app.src) -endif - -bootstrap-rel: -ifneq ($(wildcard relx.config),) - $(error Error: relx.config already exists) -endif -ifneq ($(wildcard rel/),) - $(error Error: rel/ directory already exists) -endif - $(eval p := $(PROJECT)) - $(call render_template,bs_relx_config,relx.config) - $(verbose) mkdir rel/ - $(call render_template,bs_sys_config,rel/sys.config) - $(call render_template,bs_vm_args,rel/vm.args) - -new-app: -ifndef in - $(error Usage: $(MAKE) new-app in=APP) -endif -ifneq ($(wildcard $(APPS_DIR)/$in),) - $(error Error: Application $in already exists) -endif - $(eval p := $(in)) - $(eval n := $(in)_sup) - $(verbose) mkdir -p $(APPS_DIR)/$p/src/ - $(call render_template,bs_apps_Makefile,$(APPS_DIR)/$p/Makefile) -ifdef LEGACY - $(call render_template,bs_appsrc,$(APPS_DIR)/$p/src/$p.app.src) -endif - $(call render_template,bs_app,$(APPS_DIR)/$p/src/$p_app.erl) - $(call render_template,tpl_supervisor,$(APPS_DIR)/$p/src/$p_sup.erl) - -new-lib: -ifndef in - $(error Usage: $(MAKE) new-lib in=APP) -endif -ifneq ($(wildcard $(APPS_DIR)/$in),) - $(error Error: Application $in already exists) -endif - $(eval p := $(in)) - $(verbose) mkdir -p $(APPS_DIR)/$p/src/ - $(call render_template,bs_apps_Makefile,$(APPS_DIR)/$p/Makefile) -ifdef LEGACY - $(call render_template,bs_appsrc_lib,$(APPS_DIR)/$p/src/$p.app.src) -endif - -new: -ifeq ($(wildcard src/)$(in),) - $(error Error: src/ directory does not exist) -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 -ifdef in - $(verbose) $(MAKE) -C $(APPS_DIR)/$(in)/ new t=$t n=$n in= -else - $(call render_template,tpl_$(t),src/$(n).erl) -endif - -list-templates: - $(verbose) echo Available templates: $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES)))) - -# Copyright (c) 2014-2015, 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 - -# Configuration. - -C_SRC_DIR ?= $(CURDIR)/c_src -C_SRC_ENV ?= $(C_SRC_DIR)/env.mk -C_SRC_OUTPUT ?= $(CURDIR)/priv/$(PROJECT) -C_SRC_TYPE ?= shared - -# System type and C compiler/flags. - -ifeq ($(PLATFORM),msys2) - C_SRC_OUTPUT_EXECUTABLE_EXTENSION ?= .exe - C_SRC_OUTPUT_SHARED_EXTENSION ?= .dll -else - C_SRC_OUTPUT_EXECUTABLE_EXTENSION ?= - C_SRC_OUTPUT_SHARED_EXTENSION ?= .so -endif - -ifeq ($(C_SRC_TYPE),shared) - C_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_SHARED_EXTENSION) -else - C_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_EXECUTABLE_EXTENSION) -endif - -ifeq ($(PLATFORM),msys2) -# We hardcode the compiler used on MSYS2. The default CC=cc does -# not produce working code. The "gcc" MSYS2 package also doesn't. - CC = /mingw64/bin/gcc - export CC - CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes - CXXFLAGS ?= -O3 -finline-functions -Wall -else ifeq ($(PLATFORM),darwin) - CC ?= cc - CFLAGS ?= -O3 -std=c99 -arch x86_64 -finline-functions -Wall -Wmissing-prototypes - CXXFLAGS ?= -O3 -arch x86_64 -finline-functions -Wall - LDFLAGS ?= -arch x86_64 -flat_namespace -undefined suppress -else ifeq ($(PLATFORM),freebsd) - CC ?= cc - CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes - CXXFLAGS ?= -O3 -finline-functions -Wall -else ifeq ($(PLATFORM),linux) - CC ?= gcc - CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes - CXXFLAGS ?= -O3 -finline-functions -Wall -endif - -ifneq ($(PLATFORM),msys2) - CFLAGS += -fPIC - CXXFLAGS += -fPIC -endif - -CFLAGS += -I"$(ERTS_INCLUDE_DIR)" -I"$(ERL_INTERFACE_INCLUDE_DIR)" -CXXFLAGS += -I"$(ERTS_INCLUDE_DIR)" -I"$(ERL_INTERFACE_INCLUDE_DIR)" - -LDLIBS += -L"$(ERL_INTERFACE_LIB_DIR)" -lerl_interface -lei - -# Verbosity. - -c_verbose_0 = @echo " C " $(?F); -c_verbose = $(c_verbose_$(V)) - -cpp_verbose_0 = @echo " CPP " $(?F); -cpp_verbose = $(cpp_verbose_$(V)) - -link_verbose_0 = @echo " LD " $(@F); -link_verbose = $(link_verbose_$(V)) - -# Targets. - -ifeq ($(wildcard $(C_SRC_DIR)),) -else ifneq ($(wildcard $(C_SRC_DIR)/Makefile),) -app:: app-c_src - -test-build:: app-c_src - -app-c_src: - $(MAKE) -C $(C_SRC_DIR) - -clean:: - $(MAKE) -C $(C_SRC_DIR) clean - -else - -ifeq ($(SOURCES),) -SOURCES := $(sort $(foreach pat,*.c *.C *.cc *.cpp,$(call core_find,$(C_SRC_DIR)/,$(pat)))) -endif -OBJECTS = $(addsuffix .o, $(basename $(SOURCES))) - -COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c -COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c - -app:: $(C_SRC_ENV) $(C_SRC_OUTPUT_FILE) - -test-build:: $(C_SRC_ENV) $(C_SRC_OUTPUT_FILE) - -$(C_SRC_OUTPUT_FILE): $(OBJECTS) - $(verbose) mkdir -p priv/ - $(link_verbose) $(CC) $(OBJECTS) \ - $(LDFLAGS) $(if $(filter $(C_SRC_TYPE),shared),-shared) $(LDLIBS) \ - -o $(C_SRC_OUTPUT_FILE) - -%.o: %.c - $(COMPILE_C) $(OUTPUT_OPTION) $< - -%.o: %.cc - $(COMPILE_CPP) $(OUTPUT_OPTION) $< - -%.o: %.C - $(COMPILE_CPP) $(OUTPUT_OPTION) $< - -%.o: %.cpp - $(COMPILE_CPP) $(OUTPUT_OPTION) $< - -clean:: clean-c_src - -clean-c_src: - $(gen_verbose) rm -f $(C_SRC_OUTPUT_FILE) $(OBJECTS) - -endif - -ifneq ($(wildcard $(C_SRC_DIR)),) -$(C_SRC_ENV): - $(verbose) $(ERL) -eval "file:write_file(\"$(call core_native_path,$(C_SRC_ENV))\", \ - io_lib:format( \ - \"ERTS_INCLUDE_DIR ?= ~s/erts-~s/include/~n\" \ - \"ERL_INTERFACE_INCLUDE_DIR ?= ~s~n\" \ - \"ERL_INTERFACE_LIB_DIR ?= ~s~n\", \ - [code:root_dir(), erlang:system_info(version), \ - code:lib_dir(erl_interface, include), \ - code:lib_dir(erl_interface, lib)])), \ - halt()." - -distclean:: distclean-c_src-env - -distclean-c_src-env: - $(gen_verbose) rm -f $(C_SRC_ENV) - --include $(C_SRC_ENV) -endif - -# Templates. - -define bs_c_nif -#include "erl_nif.h" - -static int loads = 0; - -static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) -{ - /* Initialize private data. */ - *priv_data = NULL; - - loads++; - - return 0; -} - -static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info) -{ - /* Convert the private data to the new version. */ - *priv_data = *old_priv_data; - - loads++; - - return 0; -} - -static void unload(ErlNifEnv* env, void* priv_data) -{ - if (loads == 1) { - /* Destroy the private data. */ - } - - loads--; -} - -static ERL_NIF_TERM hello(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{ - if (enif_is_atom(env, argv[0])) { - return enif_make_tuple2(env, - enif_make_atom(env, "hello"), - argv[0]); - } - - return enif_make_tuple2(env, - enif_make_atom(env, "error"), - enif_make_atom(env, "badarg")); -} - -static ErlNifFunc nif_funcs[] = { - {"hello", 1, hello} -}; - -ERL_NIF_INIT($n, nif_funcs, load, NULL, upgrade, unload) -endef - -define bs_erl_nif --module($n). - --export([hello/1]). - --on_load(on_load/0). -on_load() -> - PrivDir = case code:priv_dir(?MODULE) of - {error, _} -> - AppPath = filename:dirname(filename:dirname(code:which(?MODULE))), - filename:join(AppPath, "priv"); - Path -> - Path - end, - erlang:load_nif(filename:join(PrivDir, atom_to_list(?MODULE)), 0). - -hello(_) -> - erlang:nif_error({not_loaded, ?MODULE}). -endef - -new-nif: -ifneq ($(wildcard $(C_SRC_DIR)/$n.c),) - $(error Error: $(C_SRC_DIR)/$n.c already exists) -endif -ifneq ($(wildcard src/$n.erl),) - $(error Error: src/$n.erl already exists) -endif -ifdef in - $(verbose) $(MAKE) -C $(APPS_DIR)/$(in)/ new-nif n=$n in= -else - $(verbose) mkdir -p $(C_SRC_DIR) src/ - $(call render_template,bs_c_nif,$(C_SRC_DIR)/$n.c) - $(call render_template,bs_erl_nif,src/$n.erl) -endif - -# Copyright (c) 2015, 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 - -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 ?= - -ifeq ($(strip $(CI_OTP)),) -ci:: -else -ci:: $(addprefix ci-,$(CI_OTP)) - -ci-prepare: $(addprefix $(CI_INSTALL_DIR)/,$(CI_OTP)) - -ci-setup:: - -ci_verbose_0 = @echo " CI " $(1); -ci_verbose = $(ci_verbose_$(V)) - -define ci_target -ci-$(1): $(CI_INSTALL_DIR)/$(1) - $(ci_verbose) \ - PATH="$(CI_INSTALL_DIR)/$(1)/bin:$(PATH)" \ - CI_OTP_RELEASE="$(1)" \ - CT_OPTS="-label $(1)" \ - $(MAKE) clean ci-setup tests -endef - -$(foreach otp,$(CI_OTP),$(eval $(call ci_target,$(otp)))) - -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) - -help:: - $(verbose) printf "%s\n" "" \ - "Continuous Integration targets:" \ - " ci Run '$(MAKE) tests' on all configured Erlang versions." \ - "" \ - "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 -# This file is part of erlang.mk and subject to the terms of the ISC License. - -.PHONY: ct apps-ct distclean-ct - -# Configuration. - -CT_OPTS ?= -ifneq ($(wildcard $(TEST_DIR)),) - CT_SUITES ?= $(sort $(subst _SUITE.erl,,$(notdir $(call core_find,$(TEST_DIR)/,*_SUITE.erl)))) -else - CT_SUITES ?= -endif - -# Core targets. - -tests:: ct - -distclean:: distclean-ct - -help:: - $(verbose) printf "%s\n" "" \ - "Common_test targets:" \ - " ct Run all the common_test suites for this project" \ - "" \ - "All your common_test suites have their associated targets." \ - "A suite named http_SUITE can be ran using the ct-http target." - -# Plugin-specific targets. - -CT_RUN = ct_run \ - -no_auto_compile \ - -noinput \ - -pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin $(APPS_DIR)/*/ebin $(TEST_DIR) \ - -dir $(TEST_DIR) \ - -logdir $(CURDIR)/logs - -ifeq ($(CT_SUITES),) -ct: $(if $(IS_APP),,apps-ct) -else -ct: test-build $(if $(IS_APP),,apps-ct) - $(verbose) mkdir -p $(CURDIR)/logs/ - $(gen_verbose) $(CT_RUN) -sname ct_$(PROJECT) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS) -endif - -ifneq ($(ALL_APPS_DIRS),) -define ct_app_target -apps-ct-$1: - $(MAKE) -C $1 ct IS_APP=1 -endef - -$(foreach app,$(ALL_APPS_DIRS),$(eval $(call ct_app_target,$(app)))) - -apps-ct: test-build $(addprefix apps-ct-,$(ALL_APPS_DIRS)) -endif - -ifndef t -CT_EXTRA = -else -ifeq (,$(findstring :,$t)) -CT_EXTRA = -group $t -else -t_words = $(subst :, ,$t) -CT_EXTRA = -group $(firstword $(t_words)) -case $(lastword $(t_words)) -endif -endif - -define ct_suite_target -ct-$(1): test-build - $(verbose) mkdir -p $(CURDIR)/logs/ - $(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/ - -# Copyright (c) 2013-2015, Loïc Hoguin -# This file is part of erlang.mk and subject to the terms of the ISC License. - -.PHONY: plt distclean-plt dialyze - -# Configuration. - -DIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt -export DIALYZER_PLT - -PLT_APPS ?= -DIALYZER_DIRS ?= --src -r $(wildcard src) $(ALL_APPS_DIRS) -DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions -Wunmatched_returns # -Wunderspecs - -# Core targets. - -check:: dialyze - -distclean:: distclean-plt - -help:: - $(verbose) printf "%s\n" "" \ - "Dialyzer targets:" \ - " plt Build a PLT file for this project" \ - " dialyze Analyze the project using Dialyzer" - -# 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]]), - halt(). -endef - -$(DIALYZER_PLT): deps app - $(verbose) dialyzer --build_plt --apps erts kernel stdlib $(PLT_APPS) $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS) - -plt: $(DIALYZER_PLT) - -distclean-plt: - $(gen_verbose) rm -f $(DIALYZER_PLT) - -ifneq ($(wildcard $(DIALYZER_PLT)),) -dialyze: -else -dialyze: $(DIALYZER_PLT) -endif - $(verbose) dialyzer --no_native `$(call erlang,$(call filter_opts.erl,$(ERLC_OPTS)))` $(DIALYZER_DIRS) $(DIALYZER_OPTS) - -# Copyright (c) 2013-2015, Loïc Hoguin -# This file is part of erlang.mk and subject to the terms of the ISC License. - -.PHONY: distclean-edoc edoc - -# Configuration. - -EDOC_OPTS ?= - -# Core targets. - -ifneq ($(wildcard doc/overview.edoc),) -docs:: edoc -endif - -distclean:: distclean-edoc - -# Plugin-specific targets. - -edoc: distclean-edoc doc-deps - $(gen_verbose) $(ERL) -eval 'edoc:application($(PROJECT), ".", [$(EDOC_OPTS)]), halt().' - -distclean-edoc: - $(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info - -# 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 - -# 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/**" - -# Core targets. - -distclean:: distclean-escript - -help:: - $(verbose) printf "%s\n" "" \ - "Escript targets:" \ - " escript Build an executable escript archive" \ - -# 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. - -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) - -distclean-escript: - $(gen_verbose) rm -f $(ESCRIPT_NAME) - -# 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 - -# Configuration - -EUNIT_OPTS ?= -EUNIT_ERL_OPTS ?= - -# Core targets. - -tests:: eunit - -help:: - $(verbose) printf "%s\n" "" \ - "EUnit targets:" \ - " eunit Run all the EUnit tests for this project" - -# Plugin-specific targets. - -define eunit.erl - case "$(COVER)" of - "" -> ok; - _ -> - case cover:compile_beam_directory("ebin") of - {error, _} -> halt(1); - _ -> ok - end - end, - case eunit:test($1, [$(EUNIT_OPTS)]) of - ok -> ok; - error -> halt(2) - end, - case "$(COVER)" of - "" -> ok; - _ -> - cover:export("eunit.coverdata") - end, - halt() -endef - -EUNIT_ERL_OPTS += -pa $(TEST_DIR) $(DEPS_DIR)/*/ebin $(APPS_DIR)/*/ebin $(CURDIR)/ebin - -ifdef t -ifeq (,$(findstring :,$(t))) -eunit: test-build - $(gen_verbose) $(call erlang,$(call eunit.erl,['$(t)']),$(EUNIT_ERL_OPTS)) -else -eunit: test-build - $(gen_verbose) $(call erlang,$(call eunit.erl,fun $(t)/0),$(EUNIT_ERL_OPTS)) -endif -else -EUNIT_EBIN_MODS = $(notdir $(basename $(ERL_FILES) $(BEAM_FILES))) -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) - $(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 -endif -endif - -# Copyright (c) 2013-2015, 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 - -# Configuration. - -RELX ?= $(CURDIR)/relx -RELX_CONFIG ?= $(CURDIR)/relx.config - -RELX_URL ?= https://github.com/erlware/relx/releases/download/v3.19.0/relx -RELX_OPTS ?= -RELX_OUTPUT_DIR ?= _rel - -ifeq ($(firstword $(RELX_OPTS)),-o) - RELX_OUTPUT_DIR = $(word 2,$(RELX_OPTS)) -else - RELX_OPTS += -o $(RELX_OUTPUT_DIR) -endif - -# Core targets. - -ifeq ($(IS_DEP),) -ifneq ($(wildcard $(RELX_CONFIG)),) -rel:: relx-rel -endif -endif - -distclean:: distclean-relx-rel distclean-relx - -# Plugin-specific targets. - -$(RELX): - $(gen_verbose) $(call core_http_get,$(RELX),$(RELX_URL)) - $(verbose) chmod +x $(RELX) - -relx-rel: $(RELX) rel-deps app - $(verbose) $(RELX) -c $(RELX_CONFIG) $(RELX_OPTS) - -distclean-relx-rel: - $(gen_verbose) rm -rf $(RELX_OUTPUT_DIR) - -distclean-relx: - $(gen_verbose) rm -rf $(RELX) - -# Run target. - -ifeq ($(wildcard $(RELX_CONFIG)),) -run: -else - -define get_relx_release.erl - {ok, Config} = file:consult("$(RELX_CONFIG)"), - {release, {Name, _}, _} = lists:keyfind(release, 1, Config), - io:format("~s", [Name]), - halt(0). -endef - -RELX_RELEASE = `$(call erlang,$(get_relx_release.erl))` - -run: all - $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_RELEASE)/bin/$(RELX_RELEASE) console - -help:: - $(verbose) printf "%s\n" "" \ - "Relx targets:" \ - " run Compile the project, build the release and run it" - -endif - -# 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 - -# Configuration. - -SHELL_ERL ?= erl -SHELL_PATHS ?= $(CURDIR)/ebin $(APPS_DIR)/*/ebin $(DEPS_DIR)/*/ebin -SHELL_OPTS ?= - -ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS)) - -# Core targets - -help:: - $(verbose) printf "%s\n" "" \ - "Shell targets:" \ - " shell Run an erlang shell with SHELL_OPTS or reasonable default" - -# Plugin-specific targets. - -$(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 - -shell: build-shell-deps - $(gen_verbose) $(SHELL_ERL) -pa $(SHELL_PATHS) $(SHELL_OPTS) - -# Copyright (c) 2015, 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) -.PHONY: triq - -# Targets. - -tests:: triq - -define triq_check.erl - code:add_pathsa(["$(CURDIR)/ebin", "$(DEPS_DIR)/*/ebin"]), - try - case $(1) of - all -> [true] =:= lists:usort([triq:check(M) || M <- [$(call comma_list,$(3))]]); - module -> triq:check($(2)); - function -> triq:check($(2)) - end - of - true -> halt(0); - _ -> halt(1) - catch error:undef -> - io:format("Undefined property or module~n"), - halt(0) - end. -endef - -ifdef t -ifeq (,$(findstring :,$(t))) -triq: test-build - $(verbose) $(call erlang,$(call triq_check.erl,module,$(t))) -else -triq: test-build - $(verbose) echo Testing $(t)/0 - $(verbose) $(call erlang,$(call triq_check.erl,function,$(t)())) -endif -else -triq: test-build - $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename $(wildcard ebin/*.beam)))))) - $(gen_verbose) $(call erlang,$(call triq_check.erl,all,undefined,$(MODULES))) -endif -endif - -# Copyright (c) 2015, Erlang Solutions Ltd. -# This file is part of erlang.mk and subject to the terms of the ISC License. - -.PHONY: xref distclean-xref - -# Configuration. - -ifeq ($(XREF_CONFIG),) - XREF_ARGS := -else - XREF_ARGS := -c $(XREF_CONFIG) -endif - -XREFR ?= $(CURDIR)/xrefr -export XREFR - -XREFR_URL ?= https://github.com/inaka/xref_runner/releases/download/0.2.2/xrefr - -# Core targets. - -help:: - $(verbose) printf "%s\n" "" \ - "Xref targets:" \ - " xref Run Xrefr using $XREF_CONFIG as config file if defined" - -distclean:: distclean-xref - -# Plugin-specific targets. - -$(XREFR): - $(gen_verbose) $(call core_http_get,$(XREFR),$(XREFR_URL)) - $(verbose) chmod +x $(XREFR) - -xref: deps app $(XREFR) - $(gen_verbose) $(XREFR) $(XREFR_ARGS) - -distclean-xref: - $(gen_verbose) rm -rf $(XREFR) - -# Copyright 2015, Viktor Söderqvist -# This file is part of erlang.mk and subject to the terms of the ISC License. - -COVER_REPORT_DIR = cover - -# Hook in coverage to ct - -ifdef COVER -ifdef CT_RUN -# All modules in 'ebin' -COVER_MODS = $(notdir $(basename $(call core_ls,ebin/*.beam))) - -test-build:: $(TEST_DIR)/ct.cover.spec - -$(TEST_DIR)/ct.cover.spec: - $(verbose) echo Cover mods: $(COVER_MODS) - $(gen_verbose) printf "%s\n" \ - '{incl_mods,[$(subst $(space),$(comma),$(COVER_MODS))]}.' \ - '{export,"$(CURDIR)/ct.coverdata"}.' > $@ - -CT_RUN += -cover $(TEST_DIR)/ct.cover.spec -endif -endif - -# Core targets - -ifdef COVER -ifneq ($(COVER_REPORT_DIR),) -tests:: - $(verbose) $(MAKE) --no-print-directory cover-report -endif -endif - -clean:: coverdata-clean - -ifneq ($(COVER_REPORT_DIR),) -distclean:: cover-report-clean -endif - -help:: - $(verbose) printf "%s\n" "" \ - "Cover targets:" \ - " cover-report Generate a HTML coverage report from previously collected" \ - " cover data." \ - " all.coverdata Merge {eunit,ct}.coverdata into one coverdata file." \ - "" \ - "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" \ - "coverdata files from each of these testing tools. HTML reports can be disabled" \ - "by setting COVER_REPORT_DIR to empty." - -# Plugin specific targets - -COVERDATA = $(filter-out all.coverdata,$(wildcard *.coverdata)) - -.PHONY: coverdata-clean -coverdata-clean: - $(gen_verbose) rm -f *.coverdata 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).' - -# 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. -ifneq ($(COVER_REPORT_DIR),) - -.PHONY: cover-report-clean cover-report - -cover-report-clean: - $(gen_verbose) rm -rf $(COVER_REPORT_DIR) - -ifeq ($(COVERDATA),) -cover-report: -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 \ - | sed "s/^src\/\(.*\)\.erl:.*/'\1'/" | uniq)) - -define cover_report.erl - $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),) - Ms = cover:imported_modules(), - [cover:analyse_to_file(M, "$(COVER_REPORT_DIR)/" ++ atom_to_list(M) - ++ ".COVER.html", [html]) || M <- Ms], - Report = [begin {ok, R} = cover:analyse(M, module), R end || M <- Ms], - EunitHrlMods = [$(EUNIT_HRL_MODS)], - Report1 = [{M, {Y, case lists:member(M, EunitHrlMods) of - true -> N - 1; false -> N end}} || {M, {Y, N}} <- Report], - TotalY = lists:sum([Y || {_, {Y, _}} <- Report1]), - TotalN = lists:sum([N || {_, {_, N}} <- Report1]), - Perc = fun(Y, N) -> case Y + N of 0 -> 100; S -> round(100 * Y / S) end end, - TotalPerc = Perc(TotalY, TotalN), - {ok, F} = file:open("$(COVER_REPORT_DIR)/index.html", [write]), - io:format(F, "~n" - "~n" - "Coverage report~n" - "~n", []), - io:format(F, "

Coverage

~n

Total: ~p%

~n", [TotalPerc]), - io:format(F, "~n", []), - [io:format(F, "" - "~n", - [M, M, Perc(Y, N)]) || {M, {Y, N}} <- Report1], - How = "$(subst $(space),$(comma)$(space),$(basename $(COVERDATA)))", - Date = "$(shell date -u "+%Y-%m-%dT%H:%M:%SZ")", - io:format(F, "
ModuleCoverage
~p~p%
~n" - "

Generated using ~s and erlang.mk on ~s.

~n" - "", [How, Date]), - halt(). -endef - -cover-report: - $(gen_verbose) mkdir -p $(COVER_REPORT_DIR) - $(gen_verbose) $(call erlang,$(cover_report.erl)) - -endif -endif # ifneq ($(COVER_REPORT_DIR),) diff --git a/etc/emqx_auth_ldap.conf b/etc/emqx_auth_ldap.conf index 01dca5f..3eb88bf 100644 --- a/etc/emqx_auth_ldap.conf +++ b/etc/emqx_auth_ldap.conf @@ -5,41 +5,60 @@ ## LDAP server list, seperated by ','. ## ## Value: String -auth.ldap.servers = ldap_server +auth.ldap.servers = 127.0.0.1 ## LDAP server port. ## ## Value: Port auth.ldap.port = 389 +## LDAP pool size +## +## Value: String +auth.ldap.pool = 8 + ## LDAP Bind DN. ## ## Value: DN -auth.ldap.bind_dn = cn=admin,dc=emqtt,dc=com +auth.ldap.bind_dn = cn=root,dc=emqx,dc=io ## LDAP Bind Password. ## ## Value: String -auth.ldap.bind_password = admin +auth.ldap.bind_password = public ## LDAP query timeout. ## ## Value: Number -auth.ldap.timeout = 60 +auth.ldap.timeout = 30s -## Authentication DN. +## Device DN. ## ## Variables: -## -%u: username -## -%c: clientid ## ## Value: DN -auth.ldap.auth_dn = cn=%u,ou=auth,dc=emqtt,dc=com +auth.ldap.device_dn = ou=device,dc=emqx,dc=io + +## Specified ObjectClass +## +## Variables: +## +## Value: string +auth.ldap.match_objectclass = mqttUser -## Password hash. +## attributetype for username ## -## Value: plain | md5 | sha | sha256 -auth.ldap.password_hash = sha256 +## Variables: +## +## Value: string +auth.ldap.username.attributetype = uid + +## attributetype for password +## +## Variables: +## +## Value: string +auth.ldap.password.attributetype = userPassword ## Whether to enable SSL. ## @@ -57,4 +76,3 @@ auth.ldap.ssl = false ## auth.ldap.ssl.verify = verify_peer ## auth.ldap.ssl.fail_if_no_peer_cert = true - diff --git a/priv/emqx_auth_ldap.schema b/priv/emqx_auth_ldap.schema index d033eeb..89168cd 100644 --- a/priv/emqx_auth_ldap.schema +++ b/priv/emqx_auth_ldap.schema @@ -11,9 +11,14 @@ {datatype, integer} ]}. +{mapping, "auth.ldap.pool", "emqx_auth_ldap.ldap", [ + {default, 8}, + {datatype, integer} +]}. + {mapping, "auth.ldap.bind_dn", "emqx_auth_ldap.ldap", [ {datatype, string}, - {default, "cn=root,dc=emqtt,dc=com"} + {default, "cn=root,dc=emqx,dc=io"} ]}. {mapping, "auth.ldap.bind_password", "emqx_auth_ldap.ldap", [ @@ -22,8 +27,8 @@ ]}. {mapping, "auth.ldap.timeout", "emqx_auth_ldap.ldap", [ - {default, 30}, - {datatype, integer} + {default, "30s"}, + {datatype, {duration, ms}} ]}. {mapping, "auth.ldap.ssl", "emqx_auth_ldap.ldap", [ @@ -57,35 +62,39 @@ {translation, "emqx_auth_ldap.ldap", fun(Conf) -> Servers = string:tokens(cuttlefish:conf_get("auth.ldap.servers", Conf), ","), Port = cuttlefish:conf_get("auth.ldap.port", Conf), + Pool = cuttlefish:conf_get("auth.ldap.pool", Conf), BindDN = cuttlefish:conf_get("auth.ldap.bind_dn", Conf), BindPassword = cuttlefish:conf_get("auth.ldap.bind_password", Conf), Timeout = cuttlefish:conf_get("auth.ldap.timeout", Conf), - Opts = [{servers, Servers}, {port, Port}, {timeout, Timeout}, {bind_dn, BindDN}, {bind_password, BindPassword}], + Opts = [{servers, Servers}, + {port, Port}, + {timeout, Timeout}, + {bind_dn, BindDN}, + {bind_password, BindPassword}, + {pool, Pool}, + {auto_reconnect, 2}], case cuttlefish:conf_get("auth.ldap.ssl", Conf) of true -> [{ssl, true}|Opts]; false -> [{ssl, false}|Opts] end end}. -{mapping, "auth.ldap.auth_dn", "emqx_auth_ldap.auth_dn", [ +{mapping, "auth.ldap.device_dn", "emqx_auth_ldap.device_dn", [ + {default, "ou=device,dc=emqx,dc=i"}, {datatype, string} ]}. -{mapping, "auth.ldap.password_hash", "emqx_auth_ldap.password_hash", [ +{mapping, "auth.ldap.match_objectclass", "emqx_auth_ldap.match_objectclass", [ + {default, "mqttUser"}, {datatype, string} ]}. -{mapping, "auth.ldap.acl_dn", "emqx_auth_ldap.acl_dn", [ +{mapping, "auth.ldap.username.attributetype", "emqx_auth_ldap.username_attr", [ + {default, "uid"}, {datatype, string} ]}. -{translation, "emqx_auth_ldap.password_hash", fun(Conf) -> - HashValue = cuttlefish:conf_get("auth.ldap.password_hash", Conf), - case string:tokens(HashValue, " ") of - [Hash] -> list_to_atom(Hash); - [Prefix, Suffix] -> {list_to_atom(Prefix), list_to_atom(Suffix)}; - [Hash, MacFun, Iterations, Dklen] -> {list_to_atom(Hash), list_to_atom(MacFun), list_to_integer(Iterations), list_to_integer(Dklen)}; - _ -> plain - end -end}. - +{mapping, "auth.ldap.password.attributetype", "emqx_auth_ldap.password_attr", [ + {default, "userPassword"}, + {datatype, string} +]}. diff --git a/rebar.config b/rebar.config index 9f550c3..7a7c3b3 100644 --- a/rebar.config +++ b/rebar.config @@ -1,4 +1,24 @@ {deps, [ -{ecpool,".*",{git,"https://github.com/emqtt/ecpool","master"}},{clique,".*",{git,"https://github.com/emqx/clique",""}},{emqx_passwd,".*",{git,"https://github.com/emqx/emqx-passwd",""}} + {ecpool, {git, "git@github.com:emqx/ecpool.git", {branch, "master"}}}, + {clique, {git, "git@github.com:emqx/clique.git", {branch, "develop"}}}, + {emqx_passwd, {git,"git@github.com:emqx/emqx-passwd.git", {branch, "emqx30"}}}, + {emqx, {git, "git@github.com:emqx/emqx.git", {branch, "emqx30"}}}, + {cuttlefish, {git, "git@github.com:emqx/cuttlefish.git", {branch, "v2.1.1"}}}, + {eldap2, {git, "git@github.com:emqx/eldap2.git", {branch, "eldap2"}}}, + {emqx_auth_username, {git, "git@github.com:emqx/emqx-auth-username.git", {branch, "emqx30"}}} ]}. -{erl_opts, [debug_info]}. + +{edoc_opts, [{preprocess, true}]}. +{erl_opts, [warn_unused_vars, + warn_shadow_vars, + warn_unused_import, + warn_obsolete_guard, + debug_info, + {parse_transform}]}. + +{xref_checks, [undefined_function_calls, undefined_functions, + locals_not_used, deprecated_function_calls, + warnings_as_errors, deprecated_functions]}. +{cover_enabled, true}. +{cover_opts, [verbose]}. +{cover_export_enabled, true}. diff --git a/src/emqx_acl_ldap.erl b/src/emqx_acl_ldap.erl index 2f44123..9cb9624 100644 --- a/src/emqx_acl_ldap.erl +++ b/src/emqx_acl_ldap.erl @@ -19,63 +19,55 @@ -include_lib("emqx/include/emqx.hrl"). -include_lib("eldap/include/eldap.hrl"). --import(proplists, [get_value/2, get_value/3]). --import(emqx_auth_ldap_cli, [search/2, fill/2, gen_filter/2]). - %% ACL Callbacks -export([init/1, check_acl/2, reload_acl/1, description/0]). -init(AclDn) -> - {ok, #{acl_dn => AclDn}}. +-import(proplists, [get_value/2]). + +-import(lists, [concat/1]). + +-import(emqx_auth_ldap_cli, [search/3, init_args/1]). + +init(ENVS) -> + init_args(ENVS). check_acl({#{username := <<$$, _/binary>>}, _PubSub, _Topic}, _State) -> ignore; -check_acl({Credentials, PubSub, Topic}, #{acl_dn := AclDn}) -> - Filter = gen_filter(Credentials, AclDn), - case search(fill(Credentials, AclDn), Filter) of +check_acl({#{username := Username}, PubSub, Topic}, + #{device_dn := DeviceDn, + match_objectclass := ObjectClass, + username_attr := UidAttr}) -> + Filter = eldap2:equalityMatch("objectClass", ObjectClass), + Attribute = case PubSub of + publish -> "mqttPublishTopic"; + subscribe -> "mqttSubscriptionTopic" + end, + Attribute1 = "mqttPubSubTopic", + logger:debug("search dn:~p filter:~p, attribute:~p", [DeviceDn, Filter, Attribute]), + case search(concat([UidAttr,"=", binary_to_list(Username), ",", DeviceDn]), Filter, [Attribute, Attribute1]) of + {error, noSuchObject} -> + ignore; {ok, #eldap_search_result{entries = []}} -> ignore; {ok, #eldap_search_result{entries = [Entry]}} -> - Rules = filter(PubSub, compile(Entry#eldap_entry.attributes)), - case match(Credentials, Topic, Rules) of - {matched, allow} -> allow; - {matched, deny} -> deny; - nomatch -> ignore - end + Topics = get_value(Attribute, Entry#eldap_entry.attributes) + ++ get_value(Attribute1, Entry#eldap_entry.attributes), + match(Topic, Topics); + Error -> + logger:error("LDAP search error:~p", [Error]), + deny end. -match(_Credentials, _Topic, []) -> - nomatch; +match(_Topic, []) -> + ignore; -match(Credentials, Topic, [Rule|Rules]) -> - case emqx_access_rule:match(Credentials, Topic, Rule) of - nomatch -> match(Credentials, Topic, Rules); - {matched, AllowDeny} -> {matched, AllowDeny} +match(Topic, [Filter | Topics]) -> + case emqx_topic:match(Topic, list_to_binary(Filter)) of + true -> allow; + false -> match(Topic, Topics) end. -compile(Attributes) -> - Topic = list_to_binary(get_value("topic", Attributes)), - Allow = allow(list_to_binary(get_value("allow", Attributes))), - Access = access(list_to_binary(get_value("access", Attributes))), - [emqx_access_rule:compile({Allow, all, Access, [topic(Topic)]})]. - -filter(PubSub, Rules) -> - [Term || Term = {_, _, Access, _} <- Rules, - Access =:= PubSub orelse Access =:= pubsub]. - -allow(<<"1">>) -> allow; -allow(<<"0">>) -> deny. - -access(<<"1">>) -> subscribe; -access(<<"2">>) -> publish; -access(<<"3">>) -> pubsub. - -topic(<<"eq ", Topic/binary>>) -> - {eq, Topic}; -topic(Topic) -> - Topic. - reload_acl(_State) -> ok. diff --git a/src/emqx_auth_ldap.app.src b/src/emqx_auth_ldap.app.src new file mode 100644 index 0000000..1da5e35 --- /dev/null +++ b/src/emqx_auth_ldap.app.src @@ -0,0 +1,8 @@ +{application, emqx_auth_ldap, [ + {description, "EMQ X Authentication/ACL with LDAP"}, + {vsn, "3.0"}, + {modules, ['emqx_acl_ldap','emqx_auth_ldap','emqx_auth_ldap_app','emqx_auth_ldap_cfg','emqx_auth_ldap_cli','emqx_auth_ldap_sup']}, + {registered, [emqx_auth_ldap_sup]}, + {applications, [kernel,stdlib,eldap2,ecpool,clique,emqx_passwd]}, + {mod, {emqx_auth_ldap_app, []}} +]}. diff --git a/src/emqx_auth_ldap.erl b/src/emqx_auth_ldap.erl index c309930..5d944c8 100644 --- a/src/emqx_auth_ldap.erl +++ b/src/emqx_auth_ldap.erl @@ -17,31 +17,105 @@ -include_lib("emqx/include/emqx.hrl"). -include_lib("eldap/include/eldap.hrl"). --import(proplists, [get_value/2, get_value/3]). +-behaviour(emqx_auth_mod). --import(emqx_auth_ldap_cli, [search/2, fill/2, gen_filter/2]). +-import(proplists, [get_value/2]). + +-import(emqx_auth_ldap_cli, [search/2, init_args/1]). -export([init/1, check/3, description/0]). --define(EMPTY(Username), (Username =:= undefined orelse Username =:= <<>>)). +-define(UNDEFINED(Username), (Username =:= undefined orelse Username =:= <<>>)). + +init(ENVS) -> + init_args(ENVS). -init({AuthDn, HashType}) -> - {ok, #{auth_dn => AuthDn, hash_type => HashType}}. +check(#{username := Username}, _Password, _State) when ?UNDEFINED(Username) -> + {error, username_undefined}; -check(#{username := Username}, Password, _State) when ?EMPTY(Username); ?EMPTY(Password) -> - {error, username_or_password_undefined}; +check(#{username := Username}, Password, State = #{password_attr := PasswdAttr}) -> + case lookup_user(Username, State) of + undefined -> ignore; + {error, Error} -> {error, Error}; + Attributes -> + case get_value(PasswdAttr, Attributes) of + undefined -> + logger:error("LDAP Search State: ~p, uid: ~p, result:~p", [State, Username, Attributes]), + ok; + [Passhash1] -> + format_password(Passhash1, Password) + end + end. -check(Credentials, Password, #{auth_dn := AuthDn, hash_type := HashType}) -> - Filter = gen_filter(Credentials, AuthDn), - case search(fill(Credentials, AuthDn), Filter) of - {ok, #eldap_search_result{entries = []}} -> - ignore; +lookup_user(Username, #{username_attr := UidAttr, + match_objectclass := ObjectClass, + device_dn := DeviceDn}) -> + Filter = eldap2:equalityMatch("objectClass", ObjectClass), + case search(lists:concat([UidAttr,"=", binary_to_list(Username), ",", DeviceDn]), Filter) of + {error, noSuchObject} -> + undefined; {ok, #eldap_search_result{entries = [Entry]}} -> Attributes = Entry#eldap_entry.attributes, - emqx_passwd:check_pass({list_to_binary(proplists:get_value("password", Attributes)), Password}, HashType); - {error, Reason} -> - {error, Reason} + case get_value("isEnabled", Attributes) of + undefined -> + Attributes; + [Val] -> + case list_to_atom(string:to_lower(Val)) of + true -> Attributes; + false -> {error, username_disabled} + end + end; + {error, Error} -> + logger:error("LDAP Search dn: ~p, filter: ~p, fail:~p", [DeviceDn, Filter, Error]), + {error, username_or_password_error} end. -description() -> "LDAP Authentication Plugin". +check_pass(Password, Password) -> ok; +check_pass(_, _) -> {error, password_error}. + +format_password(Passhash, Password) -> + case do_format_password(Passhash, Password) of + {error, Error2} -> + {error, Error2}; + + {Passhash1, Password1} -> + check_pass(Passhash1, Password1) + end. +do_format_password(Passhash, Password) -> + Base64PasshashHandler = handle_passhash(fun(HashType, Passhash1, Password1) -> + Passhash2 = binary_to_list(base64:decode(Passhash1)), + resolve_passhash(HashType, Passhash2, Password1) + end, + fun(_Passhash, _Password) -> + {error, password_error} + end), + PasshashHandler = handle_passhash(fun resolve_passhash/3, + Base64PasshashHandler), + PasshashHandler(Passhash, Password). + +resolve_passhash(HashType, Passhash, Password) -> + [_, Passhash1] = string:tokens(Passhash, "}"), + do_resolve(HashType, Passhash1, Password). + +handle_passhash(HandleMatch, HandleNoMatch) -> + fun(Passhash, Password) -> + case re:run(Passhash, "(?<={)[^{}]+(?=})", [{capture, all, list}, global]) of + {match, [[HashType]]} -> + io:format("~n Passhash:~p HashType:~p ~n", [Passhash, HashType]), + HandleMatch(list_to_atom(string:to_lower(HashType)), Passhash, Password); + _ -> + HandleNoMatch(Passhash, Password) + end + end. + +do_resolve(ssha, Passhash, Password) -> + D64 = base64:decode(Passhash), + {HashedData, Salt} = lists:split(20, binary_to_list(D64)), + NewHash = crypto:hash(sha, <>), + {list_to_binary(HashedData), NewHash}; +do_resolve(HashType, Passhash, Password) -> + Password1 = base64:encode(crypto:hash(HashType, Password)), + {list_to_binary(Passhash), Password1}. + +description() -> "LDAP Authentication Plugin". diff --git a/src/emqx_auth_ldap_app.erl b/src/emqx_auth_ldap_app.erl index 35c60cf..2e78754 100644 --- a/src/emqx_auth_ldap_app.erl +++ b/src/emqx_auth_ldap_app.erl @@ -23,31 +23,43 @@ start(_StartType, _StartArgs) -> {ok, Sup} = emqx_auth_ldap_sup:start_link(), - if_enabled(auth_dn, fun reg_authmod/1), - if_enabled(acl_dn, fun reg_aclmod/1), - emqx_auth_ldap_cfg:register(), + if_enabled([device_dn, match_objectclass, + username_attr, password_attr], + fun reg_authmod/1), + if_enabled([device_dn, match_objectclass, + username_attr, password_attr], + fun reg_aclmod/1), {ok, Sup}. prep_stop(State) -> emqx_access_control:unregister_mod(auth, emqx_auth_ldap), emqx_access_control:unregister_mod(acl, emqx_acl_ldap), - emqx_auth_ldap_cfg:unregister(), State. stop(_State) -> ok. -reg_authmod(AuthDn) -> - {ok, HashType} = application:get_env(?APP, password_hash), - AuthEnv = {AuthDn, HashType}, - emqx_access_control:register_mod(auth, emqx_auth_ldap, AuthEnv). +reg_authmod(DeviceDn) -> + emqx_access_control:register_mod(auth, emqx_auth_ldap, DeviceDn). -reg_aclmod(AclDn) -> - emqx_access_control:register_mod(acl, emqx_acl_ldap, AclDn). +reg_aclmod(DeviceDn) -> + emqx_access_control:register_mod(acl, emqx_acl_ldap, DeviceDn). -if_enabled(Cfg, Fun) -> - case application:get_env(?APP, Cfg) of - {ok, Dn} -> Fun(Dn); - undefined -> ok +if_enabled(Cfgs, Fun) -> + case get_env(Cfgs) of + {ok, InitArgs} -> Fun(InitArgs); + [] -> ok end. +get_env(Cfgs) -> + get_env(Cfgs, []). + +get_env([Cfg | LeftCfgs], ENVS) -> + case application:get_env(?APP, Cfg) of + {ok, ENV} -> + get_env(LeftCfgs, [{Cfg, ENV} | ENVS]); + undefined -> + get_env(LeftCfgs, ENVS) + end; +get_env([], ENVS) -> + {ok, ENVS}. diff --git a/src/emqx_auth_ldap_cfg.erl b/src/emqx_auth_ldap_cfg.erl deleted file mode 100644 index f473d0f..0000000 --- a/src/emqx_auth_ldap_cfg.erl +++ /dev/null @@ -1,72 +0,0 @@ -%% Copyright (c) 2018 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% 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_auth_ldap_cfg). - --include("emqx_auth_ldap.hrl"). - --export([register/0, unregister/0]). - -register() -> - clique_config:load_schema([code:priv_dir(?APP)], ?APP), - register_formatter(), - register_config(). - -unregister() -> - unregister_formatter(), - unregister_config(), - clique_config:unload_schema(?APP). - -register_formatter() -> - Ignore = ["auth.ldap.auth_dn", "auth.ldap.password_hash"], - [clique:register_formatter(cuttlefish_variable:tokenize(Key), fun formatter_callback/2) || Key <- keys() -- Ignore]. - -formatter_callback([_, _, Key], Params) -> - proplists:get_value(list_to_atom(Key), Params). - -unregister_formatter() -> - [clique:unregister_formatter(cuttlefish_variable:tokenize(Key)) || Key <- keys()]. - -register_config() -> - Keys = keys(), - [clique:register_config(Key , fun config_callback/2) || Key <- Keys], - clique:register_config_whitelist(Keys, ?APP). - -config_callback([_, _, "auth_dn"], Value) -> - application:set_env(?APP, auth_dn, Value), - " successfully\n"; -config_callback([_, _, "password_hash"], Value) -> - application:set_env(?APP, password_hash, Value), - " successfully\n"; - -config_callback([_, _, Key0], Value) -> - Key = list_to_atom(Key0), - {ok, Env} = application:get_env(?APP, ldap), - application:set_env(?APP, ldap, lists:keyreplace(Key, 1, Env, {Key, Value})), - " successfully\n". - -unregister_config() -> - Keys = keys(), - [clique:unregister_config(Key) || Key <- Keys], - clique:unregister_config_whitelist(Keys, ?APP). - -keys() -> - ["auth.ldap.servers", - "auth.ldap.port", - "auth.ldap.bind_dn", - "auth.ldap.bind_password", - "auth.ldap.timeout", - "auth.ldap.auth_dn", - "auth.ldap.password_hash"]. - diff --git a/src/emqx_auth_ldap_cli.erl b/src/emqx_auth_ldap_cli.erl index 444f8e3..6041dc8 100644 --- a/src/emqx_auth_ldap_cli.erl +++ b/src/emqx_auth_ldap_cli.erl @@ -22,24 +22,10 @@ -import(proplists, [get_value/2, get_value/3]). --export([connect/1, search/2, fill/2, gen_filter/2]). +%% ecpool callback +-export([connect/1]). -fill(#{client_id := ClientId, username := Username}, AuthDn) -> - case re:run(AuthDn, "%[uc]", [global, {capture, all, list}]) of - {match, [["%u"]]} -> - re:replace(AuthDn, "%u", binary_to_list(Username), [global, {return, list}]); - {match, [["%c"]]} -> - re:replace(AuthDn, "%c", binary_to_list(ClientId), [global, {return, list}]); - nomatch -> - AuthDn - end. - -gen_filter(#{client_id := ClientId, username := Username}, Dn) -> - case re:run(Dn, "%[uc]", [global, {capture, all, list}]) of - {match, [["%u"]]} -> eldap:equalityMatch("username", Username); - {match, [["%c"]]} -> eldap:equalityMatch("username", ClientId); - nomatch -> eldap:equalityMatch("username", Username) - end. +-export([search/2, search/3, init_args/1]). %%-------------------------------------------------------------------- %% LDAP Connect/Search @@ -51,21 +37,23 @@ connect(Opts) -> Timeout = get_value(timeout, Opts, 30), BindDn = get_value(bind_dn, Opts), BindPassword = get_value(bind_password, Opts), - LdapOpts = case get_value(ssl, Opts, false) of - true -> - SslOpts = get_value(sslopts, Opts), - [{port, Port}, {timeout, Timeout}, {sslopts, SslOpts}]; - false -> - [{port, Port}, {timeout, Timeout}] - end, - - case eldap:open(Servers, LdapOpts) of + LdapOpts = case get_value(ssl, Opts, false) of + true -> + SslOpts = get_value(sslopts, Opts), + [{port, Port}, {timeout, Timeout}, {sslopts, SslOpts}]; + false -> + [{port, Port}, {timeout, Timeout}] + end, + logger:debug("Connecting to OpenLDAP server: ~p, Opts:~p ...", [Servers, LdapOpts]), + case eldap2:open(Servers, LdapOpts) of {ok, LDAP} -> - case catch eldap:simple_bind(LDAP, BindDn, BindPassword) of - ok -> {ok, LDAP}; + try eldap2:simple_bind(LDAP, BindDn, BindPassword) of + ok -> + {ok, LDAP}; {error, Error} -> - {error, Error}; - {'EXIT', Reason} -> + {error, Error} + catch + error : Reason -> {error, Reason} end; {error, Reason} -> @@ -73,5 +61,26 @@ connect(Opts) -> end. search(Base, Filter) -> - ecpool:with_client(?APP, fun(C) -> eldap:search(C, [{base, Base}, {filter, Filter}]) end). + ecpool:with_client(?APP, fun(C) -> + eldap2:search(C, [{base, Base}, + {filter, Filter}, + {deref, eldap2:derefFindingBaseObj()}]) + end). + +search(Base, Filter, Attributes) -> + ecpool:with_client(?APP, fun(C) -> + eldap2:search(C, [{base, Base}, + {filter, Filter}, + {attributes, Attributes}, + {deref, eldap2:derefFindingBaseObj()}]) + end). +init_args(ENVS) -> + DeviceDn = get_value(device_dn, ENVS), + ObjectClass = get_value(match_objectclass, ENVS), + UidAttr = get_value(username_attr, ENVS), + PasswdAttr = get_value(password_attr, ENVS), + {ok, #{device_dn => DeviceDn, + match_objectclass => ObjectClass, + username_attr => UidAttr, + password_attr => PasswdAttr}}. diff --git a/test/emqx_auth_ldap_SUITE.erl b/test/emqx_auth_ldap_SUITE.erl index 563400c..c3811e5 100644 --- a/test/emqx_auth_ldap_SUITE.erl +++ b/test/emqx_auth_ldap_SUITE.erl @@ -16,11 +16,15 @@ -compile(export_all). --define(POOL, emqx_auth_ldap). +-compile(no_warning_export). --define(APP, ?POOL). +-define(PID, emqx_auth_ldap). --define(AuthDN, "ou=test_auth,dc=emqtt,dc=com"). +-define(APP, emqx_auth_ldap). + +-define(DeviceDN, "ou=test_device,dc=emqx,dc=io"). + +-define(AuthDN, "ou=test_auth,dc=emqx,dc=io"). -include_lib("emqx/include/emqx.hrl"). @@ -29,132 +33,100 @@ -include_lib("common_test/include/ct.hrl"). all() -> - [{group, emqx_auth_ldap_auth}, - {group, emqx_auth_ldap}]. - -groups() -> - [{emqx_auth_ldap_auth, [sequence], [check_auth, list_auth]}, - {emqx_auth_ldap, [sequence], [comment_config]} + [ + check_auth, + check_acl ]. init_per_suite(Config) -> - [start_apps(App) || App <- [emqx, emqx_auth_ldap]], - {ok, Handle} = ecpool_worker:client(gproc_pool:pick_worker({ecpool, ?POOL})), - cleanup(Handle), - prepare(Handle), + [start_apps(App, SchemaFile, ConfigFile) || + {App, SchemaFile, ConfigFile} + <- [{emqx, deps_path(emqx, "priv/emqx.schema"), + deps_path(emqx, "etc/emqx.conf")}, + {emqx_auth_ldap, local_path("priv/emqx_auth_ldap.schema"), + local_path("etc/emqx_auth_ldap.conf")}]], Config. end_per_suite(_Config) -> - {ok, Handle} = ecpool_worker:client(gproc_pool:pick_worker({ecpool, ?POOL})), - cleanup(Handle), [application:stop(App) || App <- [emqx_auth_ldap, emqx]]. check_auth(_) -> - Plain = #{client_id => <<"client1">>, username => <<"plain">>}, - Md5 = #{client_id => <<"md5">>, username => <<"md5">>}, - Sha = #{client_id => <<"sha">>, username => <<"sha">>}, - Sha256 = #{client_id => <<"sha256">>, username => <<"sha256">>}, - reload([{password_hash, plain}]), - ok = emqx_access_control:authenticate(Plain, <<"plain">>), - reload([{password_hash, md5}]), - ok = emqx_access_control:authenticate(Md5, <<"md5">>), - reload([{password_hash, sha}]), - ok = emqx_access_control:authenticate(Sha, <<"sha">>), - reload([{password_hash, sha256}]), - ok = emqx_access_control:authenticate(Sha256, <<"sha256">>). - -list_auth(_Config) -> - application:start(emqx_auth_username), - emqx_auth_username:add_user(<<"user1">>, <<"password1">>), - User1 = #{client_id => <<"client1">>, username => <<"user1">>}, - ok = emqx_access_control:authenticate(User1, <<"password1">>), - reload([{password_hash, plain}]), - Plain = #{client_id => <<"client1">>, username => <<"plain">>}, - ok = emqx_access_control:authenticate(Plain, <<"plain">>), - application:stop(emqx_auth_username). - -comment_config(_) -> - application:stop(?APP), - [application:unset_env(?APP, Par) || Par <- [auth_dn]], - application:start(?APP), - ?assertEqual([], emqx_access_control:lookup_mods(auth)). - -reload(Config) when is_list(Config) -> - application:stop(?APP), - [application:set_env(?APP, K, V) || {K, V} <- Config], - application:start(?APP). - -start_apps(App) -> - NewConfig = generate_config(App), - lists:foreach(fun set_app_env/1, NewConfig). - -generate_config(emqx) -> - Schema = cuttlefish_schema:files([local_path(["deps", "emqx", "priv", "emqx.schema"])]), - Conf = conf_parse:file([local_path(["deps", "emqx", "etc", "emqx.conf"])]), - cuttlefish_generator:map(Schema, Conf); - -generate_config(emqx_auth_ldap) -> - Schema = cuttlefish_schema:files([local_path(["priv", "emqx_auth_ldap.schema"])]), - Conf = conf_parse:file([local_path(["etc", "emqx_auth_ldap.conf"])]), - cuttlefish_generator:map(Schema, Conf). - - -get_base_dir(Module) -> - {file, Here} = code:is_loaded(Module), - filename:dirname(filename:dirname(Here)). - -get_base_dir() -> - get_base_dir(?MODULE). - -local_path(Components, Module) -> - filename:join([get_base_dir(Module) | Components]). - -local_path(Components) -> - local_path(Components, ?MODULE). - -set_app_env({App, Lists}) -> - F = fun ({acl_file, _Var}) -> - application:set_env(App, acl_file, local_path(["deps", "emqx", "etc", "acl.conf"])); - ({auth_dn, _Var}) -> - application:set_env(App, auth_dn, "cn=%u,ou=test_auth,dc=emqtt,dc=com"); - ({Par, Var}) -> - application:set_env(App, Par, Var) - end, - lists:foreach(F, Lists), + MqttUser1 = #{client_id => <<"mqttuser1">>, username => <<"mqttuser0001">>}, + MqttUser2 = #{client_id => <<"mqttuser2">>, username => <<"mqttuser0002">>}, + MqttUser3 = #{client_id => <<"mqttuser3">>, username => <<"mqttuser0003">>}, + MqttUser4 = #{client_id => <<"mqttuser4">>, username => <<"mqttuser0004">>}, + MqttUser5 = #{client_id => <<"mqttuser5">>, username => <<"mqttuser0005">>}, + NonExistUser1 = #{client_id => <<"mqttuser6">>, username => <<"mqttuser0006">>}, + NonExistUser2 = #{client_id => <<"mqttuser7">>, username => <<"mqttuser0005">>}, + + ok = emqx_access_control:authenticate(MqttUser1, <<"mqttuser0001">>), + + ok = emqx_access_control:authenticate(MqttUser2, <<"mqttuser0002">>), + + ok = emqx_access_control:authenticate(MqttUser3, <<"mqttuser0003">>), + + ok = emqx_access_control:authenticate(MqttUser4, <<"mqttuser0004">>), + + ok = emqx_access_control:authenticate(MqttUser5, <<"mqttuser0005">>), + + {error, auth_modules_not_found} = emqx_access_control:authenticate(NonExistUser1, <<"mqttuser0006">>), + + {error, password_error} = emqx_access_control:authenticate(NonExistUser2, <<"mqttuser0006">>). + +check_acl(_) -> + MqttUser = #{client_id => <<"mqttuser1">>, username => <<"mqttuser0001">>, zone => undefined}, + NoMqttUser = #{client_id => <<"mqttuser2">>, username => <<"mqttuser0007">>, zone => undefined}, + allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pub/1">>), + allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pub/+">>), + allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pub/#">>), + + allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/sub/1">>), + allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/sub/+">>), + allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/sub/#">>), + + allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pubsub/1">>), + allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pubsub/+">>), + allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pubsub/#">>), + allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/pubsub/1">>), + allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/pubsub/+">>), + allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/pubsub/#">>), + + deny = emqx_access_control:check_acl(NoMqttUser, publish, <<"mqttuser0001/req/mqttuser0001/+">>), + deny = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/req/mqttuser0002/+">>), + deny = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/req/+/mqttuser0002">>), + ok. + +start_apps(App, SchemaFile, ConfigFile) -> + read_schema_configs(App, SchemaFile, ConfigFile), + set_special_configs(App), application:ensure_all_started(App). -prepare(Handle) -> - eldap:add(Handle, "dc=emqtt,dc=com", - [{"objectclass", ["dcObject", "organization"]}, - {"dc", ["emqtt"]}, {"o", ["emqtt,Inc."]}]), - - ok = eldap:add(Handle, ?AuthDN, - [{"objectclass", ["organizationalUnit"]}, - {"ou", ["test_auth"]}]), - %% Add - ok = eldap:add(Handle, "cn=plain," ++ ?AuthDN, - [{"objectclass", ["mqttUser"]}, - {"cn", ["plain"]}, - {"username", ["plain"]}, - {"password", ["plain"]}]), - - ok = eldap:add(Handle, "cn=md5," ++ ?AuthDN, - [{"objectclass", ["mqttUser"]}, - {"cn", ["md5"]}, {"username", ["md5"]}, {"password", ["1bc29b36f623ba82aaf6724fd3b16718"]}]), - ok = eldap:add(Handle, "cn=sha," ++ ?AuthDN, - [{"objectclass", ["mqttUser"]}, - {"cn", ["sha"]}, {"username", ["sha"]}, {"password", ["d8f4590320e1343a915b6394170650a8f35d6926"]}]), - ok = eldap:add(Handle, "cn=sha256," ++ ?AuthDN, - [{"objectclass", ["mqttUser"]}, - {"cn", ["sha256"]}, {"username", ["sha256"]}, {"password", ["5d5b09f6dcb2d53a5fffc60c4ac0d55fabdf556069d6631545f42aa6e3500f2e"]}]). - -cleanup(Handle) -> - case eldap:search(Handle, [{base, ?AuthDN}, - {filter, eldap:present("objectclass")}, - {scope, eldap:wholeSubtree()}]) - of - {ok, {eldap_search_result, Entries, _}} -> - [ok = eldap:delete(Handle, Entry) || {eldap_entry, Entry, _} <- Entries]; - _ -> ignore - end, - ok. +read_schema_configs(App, SchemaFile, ConfigFile) -> + ct:pal("Read configs - SchemaFile: ~p, ConfigFile: ~p", [SchemaFile, ConfigFile]), + Schema = cuttlefish_schema:files([SchemaFile]), + Conf = conf_parse:file(ConfigFile), + NewConfig = cuttlefish_generator:map(Schema, Conf), + Vals = proplists:get_value(App, NewConfig, []), + [application:set_env(App, Par, Value) || {Par, Value} <- Vals]. + +set_special_configs(emqx) -> + application:set_env(emqx, allow_anonymous, false), + application:set_env(emqx, enable_acl_cache, false), + application:set_env(emqx, acl_nomatch, deny), + application:set_env(emqx, plugins_loaded_file, deps_path(emqx, "test/emqx_SUITE_data/loaded_plugins")); + +set_special_configs(emqx_auth_ldap) -> + application:set_env(emqx_auth_ldap, device_dn, "ou=testdevice, dc=emqx, dc=io"); +set_special_configs(_App) -> + ok. + +local_path(RelativePath) -> + deps_path(emqx_auth_ldap, RelativePath). + +deps_path(App, RelativePath) -> + Path0 = code:priv_dir(App), + Path = case file:read_link(Path0) of + {ok, Resolved} -> Resolved; + {error, _} -> Path0 + end, + filename:join([Path, "..", RelativePath]).