diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index ee1570e3..2fa96c11 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -24,14 +24,17 @@ jobs: run: make install-rebar-template - name: Build test plugin run: | + git clone -b release-60 "https://github.com/emqx/emqx.git" /tmp/emqx + export ERL_LIBS=/tmp/emqx/apps ./scripts/build-sample-plugin.sh \ --tag 1.0.0 \ --name my_emqx_plugin_avsc \ --with-avsc \ - --output-dir /tmp \ + --build-dir /tmp/emqx/plugins \ + --output-dir /tmp/emqx/plugins/ \ --no-cleanup-build-dir - - name: Run dialyzer - run: make -C my_emqx_plugin_avsc dialyzer + cd /tmp/emqx/plugins/my_emqx_plugin_avsc + make dialyzer test: runs-on: ubuntu-latest @@ -46,8 +49,13 @@ jobs: version-type: strict - name: Install rebar3 template run: make install-rebar-template + - name: Prepare EMQX monorepo layout + run: | + git clone -b release-60 "https://github.com/emqx/emqx.git" /tmp/emqx - name: Build test plugins - run: make build-test-plugins + run: | + export ERL_LIBS=/tmp/emqx/apps + make build-test-plugins PLUGIN_BUILD_DIR=/tmp/emqx/plugins - name: Start EMQX run: make up - name: Run tests @@ -55,4 +63,3 @@ jobs: - name: Stop EMQX if: always() run: make down - diff --git a/Makefile b/Makefile index b5e00a20..8f493330 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ REBAR = $(CURDIR)/rebar3 SCRIPTS = $(CURDIR)/scripts TEST_ASSETS_DIR = $(CURDIR)/_build/test/lib/emqx_pt/test/assets +PLUGIN_BUILD_DIR ?= .PHONY: all all: compile @@ -36,8 +37,8 @@ install-rebar-template: .PHONY: build-test-plugins build-test-plugins: $(REBAR) - $(SCRIPTS)/build-sample-plugin.sh --tag 1.0.0 --name my_emqx_plugin_avsc --with-avsc --output-dir $(TEST_ASSETS_DIR) - $(SCRIPTS)/build-sample-plugin.sh --tag 1.0.0 --name my_emqx_plugin --output-dir $(TEST_ASSETS_DIR) + $(SCRIPTS)/build-sample-plugin.sh --tag 1.0.0 --name my_emqx_plugin_avsc --with-avsc --output-dir $(TEST_ASSETS_DIR) $(if $(PLUGIN_BUILD_DIR),--build-dir $(PLUGIN_BUILD_DIR)) + $(SCRIPTS)/build-sample-plugin.sh --tag 1.0.0 --name my_emqx_plugin --output-dir $(TEST_ASSETS_DIR) $(if $(PLUGIN_BUILD_DIR),--build-dir $(PLUGIN_BUILD_DIR)) .PHONY: fmt fmt: $(REBAR) @@ -62,4 +63,3 @@ up: .PHONY: down down: docker compose -f .ci/docker-compose.yml down --volumes - diff --git a/README.md b/README.md index 3afa84e5..bc9598d0 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,18 @@ This is a [rebar3 template](https://rebar3.org/docs/tutorials/templates/#custom-templates) to ease creation of [EMQX](https://github.com/emqx/emqx) v5 Plugins in [Erlang](https://www.erlang.org/). -The documentation refers to the EMQX of the versions `~> 5.9`. +Depending on for which EMQX version you are building the plugin, please follow the instructions respectively: -For EMQX `~> 4.3`, please see branch `emqx-v4`. - -For older EMQX versions, plugin development is no longer maintained. +- EMQX v4: Checkout branch `emqx-v4`. +- EMQX v5: Checkout branch `emqx-v5`. +- EMQX v6: Checkout branch `master`. A plugin template for Elixir (experimental) can be found at https://github.com/emqx/emqx-elixir-plugin. +> [!IMPORTANT] +> Starting from EMQX v6, plugin development is encouraged in the EMQX monorepo. +> See the guide in `PLUGIN.md`: https://github.com/emqx/emqx/blob/release-60/PLUGIN.md + ## What is a Plugin? A plugin is an Erlang application that runs inside the EMQX nodes. @@ -102,30 +106,30 @@ The `rebar.config` file is used to build the plugin and pack it into a release. The most important sections are * dependencies (`deps`) section; -* build plugins (`plugins`) section; +* rebar plugins (`plugins`) section; * release section (`relx`); -* plugin description (`emqx_plugin`) section. +* plugin metadata (`emqx_plugrel`) section. In the `deps` section, you can add dependencies to other OTP applications that your plugin depends on. ```erlang {deps, [ - {emqx_plugin_helper, {git, "https://github.com/emqx/emqx-plugin-helper.git", {tag, "v5.9.0"}}} - %% more dependencies + %% add your dependencies here ]}. ``` -The skeleton adds an extra dependency to the plugin: `emqx_plugin_helper`. -It is usually needed for plugin code to make use of the record definitions and macros provided in the header files. -See [`rebar3` dependency documentation](https://www.rebar3.org/docs/configuration/dependencies/) for more details. +> NOTE +> A plugin release is a self-contained `.tar.gz` package that includes all apps listed in `relx`. +> If a dependency app is already provided by EMQX, the copy from the plugin package is not loaded. +> Avoid shipping incompatible dependency versions. In the `plugins` section, you add rebar3 providers used for packaging. ```erlang {plugins, [ - {emqx_plugrel, {git, "https://github.com/emqx/emqx_plugrel.git", {tag, "0.6.3"}}} + {emqx_plugrel, {git, "https://github.com/emqx/emqx_plugrel.git", {tag, "0.6.4"}}} ]}. ``` @@ -134,7 +138,6 @@ In the `relx` section, you specify the release name and version, and the list of ```erlang {relx, [ {release, {my_emqx_plugin, "1.0.0"}, [ my_emqx_plugin - , emqx_plugin_helper ]} ... ]}. @@ -174,7 +177,7 @@ The `src` directory contains the code of the plugin's OTP application. Note the following: * The skeleton uses `{vsn, {file, "VERSION"}}` in `.app.src`. - A `compile` pre-hook writes this `VERSION` file from the `relx` release version in `rebar.config`. + A `compile` pre-hook writes `VERSION` from the `relx` release version in `rebar.config`. * Pay attention to the `applications` section. Since the plugin is an OTP application, plugin's start/stop/restart is the respective operation on the plugin's application. So, if the plugin's application depends on other applications, it should list them in the `applications` section. @@ -249,7 +252,6 @@ When a plugin is built into a release, the package structure is as follows: ``` └── my_emqx_plugin-1.1.0.tar.gz -    ├── emqx_plugin_helper-5.9.1    ├── my_emqx_plugin-0.1.0    ├── README.md    └── release.json @@ -279,8 +281,7 @@ I.e. the tarball contains the compiled applications (listed in the `relx` sectio "git_commit_or_build_date": "2025-04-29", "metadata_vsn": "0.2.0", "rel_apps": [ - "my_emqx_plugin-0.1.0", - "emqx_plugin_helper-5.9.1" + "my_emqx_plugin-0.1.0" ], "rel_vsn": "1.1.0", "with_config_schema": true @@ -635,5 +636,3 @@ So, to install a new version of the plugin, * The new version installed. The configuration is preserved between the installations. - - diff --git a/emqx-plugin-templates/rebar.config b/emqx-plugin-templates/rebar.config index 11da2921..47aa4f13 100644 --- a/emqx-plugin-templates/rebar.config +++ b/emqx-plugin-templates/rebar.config @@ -1,16 +1,19 @@ {{=@@ @@=}} %% -*- mode: erlang -*- {deps, [ - {emqx_plugin_helper, {git, "https://github.com/emqx/emqx-plugin-helper.git", {tag, "@@emqx_plugin_helper_vsn@@"}}} + % you will likely need emqx_plugin_helper as dependency when building the plugin standalone + % not required when building the plugin under the 'plugins' dir in emqx monorepo + % {emqx_plugin_helper, {git, "https://github.com/emqx/emqx-plugin-helper.git", {tag, "@@emqx_plugin_helper_vsn@@"}}} ]}. {plugins, [ - {emqx_plugrel, {git, "https://github.com/emqx/emqx_plugrel.git", {tag, "0.6.3"}}} + {emqx_plugrel, {git, "https://github.com/emqx/emqx_plugrel.git", {tag, "0.6.4"}}} ]}. {project_plugins, [{erlfmt, "1.6.0"}]}. -{erl_opts, [debug_info]}. +%% Add EMQX header include path when developing inside the EMQX monorepo. +{erl_opts, [debug_info, {i, "../../apps/emqx/include"}]}. %% Sync release version to app version in VERSION file. %% app.src uses: {vsn, {file, "VERSION"}}. diff --git a/emqx-plugin-templates/src/emqx_plugin_template.erl b/emqx-plugin-templates/src/emqx_plugin_template.erl index 697f1314..401a6263 100644 --- a/emqx-plugin-templates/src/emqx_plugin_template.erl +++ b/emqx-plugin-templates/src/emqx_plugin_template.erl @@ -6,13 +6,13 @@ %% for #message{} record %% no need for this include if we call emqx_message:to_map/1 to convert it to a map --include_lib("emqx_plugin_helper/include/emqx.hrl"). +-include("emqx.hrl"). %% for hook priority constants --include_lib("emqx_plugin_helper/include/emqx_hooks.hrl"). +-include("emqx_hooks.hrl"). %% for logging --include_lib("emqx_plugin_helper/include/logger.hrl"). +-include("logger.hrl"). -export([ hook/0, diff --git a/scripts/build-sample-plugin.sh b/scripts/build-sample-plugin.sh index 53aee15a..ec7b4db3 100755 --- a/scripts/build-sample-plugin.sh +++ b/scripts/build-sample-plugin.sh @@ -6,6 +6,7 @@ set -euo pipefail TAG="" NAME="" OUTPUT_DIR="" +BUILD_DIR="" WITH_AVSC=false CLEANUP_BUILD_DIR=true @@ -24,6 +25,10 @@ while [[ $# -gt 0 ]]; do OUTPUT_DIR="$2" shift 2 ;; + --build-dir) + BUILD_DIR="$2" + shift 2 + ;; --with-avsc) WITH_AVSC=true shift @@ -50,6 +55,7 @@ if [ -z "$NAME" ]; then fi cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")/.." +ROOT_DIR="$(pwd)" echo "Installing rebar template" @@ -58,30 +64,37 @@ echo "Installing rebar template" echo "Creating plugin" rm -rf "$NAME" -./rebar3 new emqx-plugin "$NAME" version="$TAG" +"$ROOT_DIR/rebar3" new emqx-plugin "$NAME" version="$TAG" + +PLUGIN_DIR="$ROOT_DIR/$NAME" +if [ -n "$BUILD_DIR" ]; then + mkdir -p "$BUILD_DIR" + rm -rf "$BUILD_DIR/$NAME" + mv "$NAME" "$BUILD_DIR/" + PLUGIN_DIR="$BUILD_DIR/$NAME" +fi -mv "$NAME/priv/config.hocon.example" "$NAME/priv/config.hocon" +mv "$PLUGIN_DIR/priv/config.hocon.example" "$PLUGIN_DIR/priv/config.hocon" if [ "$WITH_AVSC" = true ]; then - mv "$NAME/priv/config_schema.avsc.enterprise.example" "$NAME/priv/config_schema.avsc" - mv "$NAME/priv/config_i18n.json.example" "$NAME/priv/config_i18n.json" + mv "$PLUGIN_DIR/priv/config_schema.avsc.enterprise.example" "$PLUGIN_DIR/priv/config_schema.avsc" + mv "$PLUGIN_DIR/priv/config_i18n.json.example" "$PLUGIN_DIR/priv/config_i18n.json" fi echo "Building plugin" export BUILD_WITHOUT_QUIC=1 -make -C "$NAME" rel +make -C "$PLUGIN_DIR" rel echo "Copying plugin to $OUTPUT_DIR" if [ -n "$OUTPUT_DIR" ]; then mkdir -p "$OUTPUT_DIR" - cp "$NAME"/_build/default/emqx_plugrel/*.tar.gz "$OUTPUT_DIR" + cp "$PLUGIN_DIR"/_build/default/emqx_plugrel/*.tar.gz "$OUTPUT_DIR" fi if [ "$CLEANUP_BUILD_DIR" = true ]; then echo "Cleaning up" - rm -rf "$NAME" + rm -rf "$PLUGIN_DIR" else echo "Skipping cleanup" fi -