├── .gitattributes ├── .dockerignore ├── Dockerfile.build ├── bin └── wait-for-success ├── .gitignore ├── Dockerfile.service ├── erlang.mk ├── Makefile ├── package.json ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── src └── rabbit_exchange_type_delimiter.erl ├── README.md ├── test └── suite.js └── rabbitmq-components.mk /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !.git 3 | !src/rabbit_exchange_type_delimiter.erl 4 | !erlang.mk 5 | !rabbitmq-components.mk 6 | !Makefile 7 | -------------------------------------------------------------------------------- /Dockerfile.build: -------------------------------------------------------------------------------- 1 | FROM elixir:1.16-otp-26 as base 2 | 3 | ENV LANG=C.UTF-8 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | 6 | RUN apt-get -y update && \ 7 | apt-get -y install rsync 8 | -------------------------------------------------------------------------------- /bin/wait-for-success: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Stupid little script to execute a shell command 3 | # passed in via ARGV until it succeeds. Used by 4 | # the CI loop in order to poll for services. 5 | while true 6 | if system("#{ARGV * ' '} > /dev/null 2>&1") 7 | exit 0 8 | end 9 | sleep 1 10 | end 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sw? 2 | .*.sw? 3 | *.beam 4 | .erlang.mk/ 5 | /builds/ 6 | /cover/ 7 | /deps/ 8 | /doc/ 9 | /ebin/ 10 | /escript/ 11 | /logs/ 12 | /node_modules/ 13 | /plugins/ 14 | /sbin/ 15 | /xrefr/ 16 | *.lock 17 | .tool-versions 18 | .tool-versions-e 19 | erlang.mk 20 | package-lock.json 21 | rabbitmq_delimiter_exchange.d 22 | -------------------------------------------------------------------------------- /Dockerfile.service: -------------------------------------------------------------------------------- 1 | FROM base as build 2 | 3 | ADD . /rabbitmq-delimiter-exchange 4 | WORKDIR /rabbitmq-delimiter-exchange 5 | 6 | RUN make && \ 7 | make dist 8 | 9 | FROM rabbitmq:3.13-management 10 | 11 | COPY --from=build \ 12 | /rabbitmq-delimiter-exchange/plugins/rabbitmq_delimiter_exchange-* \ 13 | ./plugins/rabbitmq_delimiter_exchange 14 | 15 | RUN rabbitmq-plugins enable --offline \ 16 | rabbitmq_delimiter_exchange 17 | 18 | EXPOSE 15671 15672 19 | -------------------------------------------------------------------------------- /erlang.mk: -------------------------------------------------------------------------------- 1 | # Automated update. 2 | 3 | ERLANG_MK_BUILD_CONFIG ?= build.config 4 | ERLANG_MK_BUILD_DIR ?= .erlang.mk.build 5 | 6 | erlang.mk: bootstrap 7 | git clone https://github.com/ninenines/erlang.mk $(ERLANG_MK_BUILD_DIR) 8 | if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR); fi 9 | cd $(ERLANG_MK_BUILD_DIR) && $(MAKE) 10 | cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk 11 | rm -rf $(ERLANG_MK_BUILD_DIR) 12 | 13 | .PHONY: bootstrap 14 | bootstrap: ; 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT = rabbitmq_delimiter_exchange 2 | PROJECT_DESCRIPTION = RabbitMQ Delimiter Exchange Type 3 | 4 | DEPS = rabbit_common rabbit 5 | DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk 6 | DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-plugin.mk 7 | 8 | # FIXME: Use erlang.mk patched for RabbitMQ, while waiting for PRs to be 9 | # reviewed and merged. 10 | 11 | ERLANG_MK_REPO = https://github.com/ninenines/erlang.mk.git 12 | ERLANG_MK_COMMIT = master 13 | 14 | include rabbitmq-components.mk 15 | include erlang.mk 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rabbitmq-delimiter-exchange", 3 | "version": "1.0.0", 4 | "description": "RabbitMQ exchange with support for multiple routing keys per message", 5 | "private": true, 6 | "scripts": { 7 | "test": "mocha -b -R spec -u tdd test/" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/whitfin/rabbitmq-delimiter-exchange.git" 12 | }, 13 | "author": "Isaac Whitfield", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/whitfin/rabbitmq-delimiter-exchange/issues" 17 | }, 18 | "homepage": "https://github.com/whitfin/rabbitmq-delimiter-exchange#readme", 19 | "devDependencies": { 20 | "amqplib": "^0.10.3", 21 | "mocha": "^10.3.0", 22 | "should": "^13.2.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | name: Run Test Suite 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Code Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Setup Environment 18 | run: | 19 | cat Dockerfile.build Dockerfile.service | docker build -t plugin_test -f - . 20 | docker network create test_network 21 | docker run --rm -d --name rabbit --net test_network -p 5672:5672 -p 15672:15672 plugin_test 22 | docker run --rm -v $PWD:/workdir --net test_network -w /workdir node:16-bullseye npm install 23 | ./bin/wait-for-success curl -o /dev/null -sIf http://localhost:15672 24 | 25 | - name: Run Tests 26 | run: | 27 | docker run --rm -v $PWD:/workdir --net test_network -w /workdir -e CLUSTER_URI=amqp://guest:guest@rabbit:5672 node:16-bullseye npm test 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Isaac Whitfield 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/rabbit_exchange_type_delimiter.erl: -------------------------------------------------------------------------------- 1 | %% This exchange implementation is essentially just a direct exchange 2 | %% allowing for multiple routing keys per message. There appears to be 3 | %% some form of support for multiple routing keys within rabbitmq-server, 4 | %% but appears unsupported by clients in general. 5 | %% 6 | %% In order to maximise performance, your routing keys must be self-describing 7 | %% by providing the delimiter you wish to use as the first character. This aids 8 | %% performance by avoiding a lookup in the bindings table, or in the message 9 | %% headers, but with the limitation that (currently) you can only have single 10 | %% character delimiters. 11 | %% 12 | %% Although discouraged by the documentation on the RabbitMQ site, we re-use 13 | %% internal functionality by way of the rabbit_router. This should be safe 14 | %% as the implementation of the router has not changed in the last 5 years; 15 | %% and even if it did it would be trivial to lift the logic out in an update 16 | %% to this exchange (which would even prove backwards compatible). Due to the 17 | %% requirement of both ETS and binding registration, it seems more logical to 18 | %% re-use the existing tables to reduce memory and boilerplate. 19 | 20 | -module(rabbit_exchange_type_delimiter). 21 | -behaviour(rabbit_exchange_type). 22 | 23 | -include_lib("rabbit_common/include/rabbit.hrl"). 24 | 25 | -export([description/0, serialise_events/0, route/2, route/3]). 26 | -export([info/1, info/2, validate/1, validate_binding/2, 27 | create/2, delete/2, policy_changed/2, add_binding/3, 28 | remove_bindings/3, assert_args_equivalence/2]). 29 | 30 | -rabbit_boot_step({?MODULE, 31 | [{description, "Exchange type to allow multiple routing keys on a message"}, 32 | {mfa, {rabbit_registry, register, [exchange, <<"x-delimiter">>, ?MODULE]}}, 33 | {requires, rabbit_registry}, 34 | {enables, kernel_ready}]}). 35 | 36 | %%----------------------------------------------- 37 | %% Implemented custom behaviour for this exchange 38 | %%----------------------------------------------- 39 | 40 | description() -> 41 | [{description, <<"AMQP direct-style exchange, allowing for multiple routing keys per message">>}]. 42 | 43 | route(Exchange, Msg) -> 44 | route(Exchange, Msg, #{}). 45 | 46 | route(#exchange{name = Name}, Msg, _Opts) -> 47 | % Walk the key list, splitting our delimited routes as necessary and then forward our new key 48 | % list through to the same implementation backing the direct exchange in order to gain 49 | % performance via ETS. We call rabbit_router directly to stay compatible with v3.11.x. 50 | Routes = mc:routing_keys(Msg), 51 | case split_routes(Routes, []) of 52 | [] -> []; 53 | Keys -> rabbit_router:match_routing_key(Name, Keys) 54 | end. 55 | 56 | serialise_events() -> false. 57 | 58 | %%---------------------------------------------------- 59 | %% Default behaviour implementations for this exchange 60 | %%---------------------------------------------------- 61 | 62 | info(_X) -> []. 63 | info(_X, _Is) -> []. 64 | validate(_X) -> ok. 65 | validate_binding(_X, _B) -> ok. 66 | create(_Tx, _X) -> ok. 67 | delete(_Tx, _X) -> ok. 68 | policy_changed(_X1, _X2) -> ok. 69 | add_binding(_Tx, _X, _B) -> ok. 70 | remove_bindings(_Tx, _X, _Bs) -> ok. 71 | assert_args_equivalence(X, Args) -> 72 | rabbit_exchange:assert_args_equivalence(X, Args). 73 | 74 | %%--------------------------------------------------- 75 | %% Private function implementations for this exchange 76 | %%--------------------------------------------------- 77 | 78 | %% Simple splitting algorithm to walk the list of routing keys, 79 | %% (usually of length <= 1), pull the delimiter back using the 80 | %% first character in the key, then split the rest of the key 81 | %% using the delimiter and add it to the buffer. Your key is 82 | %% simply ignored in the case it's empty or malformed. 83 | split_routes([], Routes) -> 84 | Routes; 85 | split_routes([<> | RKeys], Routes) -> 86 | split_routes(RKeys, binary:split(Route, Delim, [global]) ++ Routes); 87 | split_routes([_Route | RKeys], Routes) -> 88 | split_routes(RKeys, Routes). 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ Delimiter Exchange Type 2 | 3 | _Update 2024: it looks like as of RabbitMQ v3.13 there is no long any 4 | meaningful difference between the use of `x-delimiter` exchanges and 5 | the `CC` header inside a `direct` exchange. The assumption is that this 6 | is a result of the [performance optimizations](https://www.rabbitmq.com/blog/2024/01/11/3.13-release#caveat) 7 | made in the v3.13 release. For this reason if you are targeting RabbitMQ 8 | with a version >= v3.13, I would suggest using `direct` exchanges with 9 | `CC` instead._ 10 | 11 | ## What It Does 12 | 13 | This plugin adds an exchange type allowing for multiple routing keys 14 | on a message via delimiter based separation. 15 | 16 | This covers the situation in which you want messages to be routable 17 | via multiple fields, without having to resort to using the headers 18 | exchange (which has bad performance, comparitively). You can listen 19 | on any routing key in the delimited list, and only receive a single 20 | copy of the message into your queue/exchange even if multiple bindings 21 | match. 22 | 23 | It's backed by the same logic used by the direct exchange, just with 24 | support for delimiters. Performance tests with the official RabbitMQ 25 | [benchmarking tool](https://github.com/rabbitmq/rabbitmq-perf-test) 26 | show almost exactly the same throughput as direct exchanges (with 27 | the same number of total matching bindings). 28 | 29 | ## How It Works 30 | 31 | Due to RabbitMQ messages only allowing for a single routing key, this 32 | plugin just splits on a delimiter to enable multiple routing keys in 33 | the single routing key field. From that point it just routes in the 34 | same way that a direct exchange would, thus resulting in roughly the 35 | same throughput and performance. 36 | 37 | When you bind to an exchange you simply specify your routing key to 38 | listen on as usual, even if it's a sub-key in the routing key split. 39 | When you publish a message you should start your key with a colon, 40 | (`:`) and then also separate any routing keys with a colon. 41 | 42 | You can control the delimiter being used by changing the first character 43 | inside the routing key; it's recommended to use the colon, but in case 44 | of clashes you can pick your own. In the case below, the message would 45 | be routed to any bindings for both `one` and `two`: 46 | 47 | ```erlang 48 | % gets routed to "one" and "two" bindings 49 | #basic_message{routing_keys = [<<":one:two">>]} 50 | 51 | % same routing with a "," as delimiter 52 | #basic_message{routing_keys = [<<",one,two">>]} 53 | ``` 54 | 55 | The delimiter is included in the routing key itself in order to make the 56 | key self-describing, meaning that we don't have to a) look for the delimiter 57 | in the bindings, b) look for the delimiter in the headers, and c) we can 58 | allow different delimiters on a per-message basis (for example if you have 59 | arbitrary routing keys inside your system). 60 | 61 | To use the exchange the type is "x-delimiter" and there are currently no 62 | arguments being used, so any provided are ignored. It should work with many 63 | versions of RabbitMQ as it's backed by the same implementation as the direct 64 | exchange. If you see any incompatibility, please file an issue. 65 | 66 | In v3.11.x, RabbitMQ introduced performance improvements for the base direct 67 | exchange type. This is currently incompatible with this plugin, due to some 68 | internal namespacing (it's only enabled on "direct" exchanges). I have filed 69 | for some customization here, but in the meantime this plugin will use the 70 | implementation of direct exchanges included in <= 3.10.x. Hopefully this can 71 | be changed in future to also see the same improvements :). 72 | 73 | ## Installation 74 | 75 | The [RabbitMQ documentation](https://www.rabbitmq.com/installing-plugins.html) 76 | explains how to install plugins into your server application. Every plugin is 77 | packaged as either a `.ez` file or a plain directory, based on your version 78 | of RabbitMQ. Just build it and drop the output plugin into your server plugins 79 | directory. 80 | 81 | You don't need to clone the RabbitMQ umbrella project; just clone 82 | this repository, check out the branch you want (i.e. `v3.8.x`), and run `make`. 83 | If there is no existing branch for your version, just create it from `main`; 84 | RabbitMQ checks this version when pulling and pinning libraries. This plugin 85 | targets RabbitMQ 3.6.0 and later versions. 86 | 87 | ## Development 88 | 89 | This repository includes some Docker setup to make it easier to test the plugin, 90 | and run a server with the plugin installed. Packaging the plugin is pretty simple 91 | using Docker: 92 | 93 | ```bash 94 | # build a development image with dependencies 95 | $ cat Dockerfile.build | \ 96 | docker build -t rabbitmq-build -f - . 97 | 98 | # attach to the container 99 | $ docker run -it --rm \ 100 | -v $PWD:/opt/rabbitmq \ 101 | -w /opt/rabbitmq \ 102 | rabbitmq-build 103 | 104 | # build and package 105 | $ make 106 | $ make dist 107 | ``` 108 | 109 | If you want to start a RabbitMQ server with this plugin enabled, you can use 110 | the server Dockerfile to let you run the tests against it: 111 | 112 | ```bash 113 | # build a development image with dependencies 114 | $ cat Dockerfile.build Dockerfile.service | \ 115 | docker build -t rabbitmq-server -f - . 116 | 117 | # start running to the container 118 | $ docker run -it --rm \ 119 | -p 15672:15672 \ 120 | -p 5672:5672 \ 121 | rabbitmq-server 122 | 123 | # test the plugin 124 | $ npm test 125 | ``` 126 | 127 | There are other ways to embed your workflow into the main server tree, but this 128 | seemed complicated for how simple this plugin is, so the above worked for me. 129 | -------------------------------------------------------------------------------- /test/suite.js: -------------------------------------------------------------------------------- 1 | const amqp = require('amqplib'); 2 | const should = require('should'); 3 | 4 | let _connection; 5 | let _cleanupTasks = []; 6 | 7 | suite('RabbitMQ Delimiter Exchange', function () { 8 | 9 | suiteSetup('start connection', async function () { 10 | _connection = await amqp.connect( 11 | process.env.CLUSTER_URI 12 | ); 13 | 14 | _scheduleForCleanup(async function () { 15 | await _connection.close(); 16 | }); 17 | }); 18 | 19 | test('creating an x-delimiter exchange', async function () { 20 | let { name, channel } = await _createExchange(); 21 | }); 22 | 23 | test('receiving messages matching a binding', async function () { 24 | let { name, channel } = await _createExchange(); 25 | 26 | let exchange = name; 27 | let queue_name = _name(); 28 | let routing_key = _name(); 29 | let message_bytes = Buffer.from(_name()); 30 | 31 | await _createQueue(channel, queue_name); 32 | await _bindQueue(channel, queue_name, exchange, routing_key); 33 | await _publish(channel, exchange, _join(routing_key), message_bytes); 34 | await _consume(channel, queue_name, function validate(message) { 35 | should(message.content).eql(message_bytes); 36 | }); 37 | }); 38 | 39 | test('receiving messages matching a binding after a delimiter', async function () { 40 | let { name, channel } = await _createExchange(); 41 | 42 | let exchange = name; 43 | let queue_name = _name(); 44 | let routing_key1 = _name(); 45 | let routing_key2 = _name(); 46 | let publish_key = _join(routing_key1, routing_key2); 47 | let message_bytes = Buffer.from(_name()); 48 | 49 | await _createQueue(channel, queue_name); 50 | await _bindQueue(channel, queue_name, exchange, routing_key2); 51 | await _publish(channel, exchange, publish_key, message_bytes); 52 | await _consume(channel, queue_name, function validate(message) { 53 | should(message.content).eql(message_bytes); 54 | }); 55 | }); 56 | 57 | test('receiving only a single copy with multiple binding matches', async function () { 58 | let { name, channel } = await _createExchange(); 59 | 60 | let exchange = name; 61 | let queue_name = _name(); 62 | let routing_key1 = _name(); 63 | let routing_key2 = _name(); 64 | let publish_key = _join(routing_key1, routing_key2); 65 | let message_bytes = Buffer.from(_name()); 66 | 67 | await _createQueue(channel, queue_name); 68 | await _bindQueue(channel, queue_name, exchange, routing_key1); 69 | await _bindQueue(channel, queue_name, exchange, routing_key2); 70 | await _publish(channel, exchange, publish_key, message_bytes); 71 | await _consume(channel, queue_name, function validate(message) { 72 | should(message.content).eql(message_bytes); 73 | }); 74 | }); 75 | 76 | test('splitting on the delimiter runs a global scan', async function () { 77 | let { name, channel } = await _createExchange(); 78 | 79 | let exchange = name; 80 | let queue_name = _name(); 81 | let routing_key1 = _name(); 82 | let routing_key2 = _name(); 83 | let routing_key3 = _name(); 84 | let publish_key = _join(routing_key1, routing_key2, routing_key3); 85 | let message_bytes = Buffer.from(_name()); 86 | 87 | await _createQueue(channel, queue_name); 88 | await _bindQueue(channel, queue_name, exchange, routing_key3); 89 | await _publish(channel, exchange, publish_key, message_bytes); 90 | await _consume(channel, queue_name, function validate(message) { 91 | should(message.content).eql(message_bytes); 92 | }); 93 | }); 94 | 95 | test('treating a trailing delimiter as an empty routing match', async function () { 96 | let { name, channel } = await _createExchange(); 97 | 98 | let exchange = name; 99 | let queue_name = _name(); 100 | let message_bytes = Buffer.from(_name()); 101 | 102 | await _createQueue(channel, queue_name); 103 | await _bindQueue(channel, queue_name, exchange, ''); 104 | await _publish(channel, exchange, ':', message_bytes); 105 | await _consume(channel, queue_name, function validate(message) { 106 | should(message.content).eql(message_bytes); 107 | }); 108 | }); 109 | 110 | test('gracefully failing when no routing key is provided', async function () { 111 | let { name, channel } = await _createExchange(); 112 | 113 | let exchange = name; 114 | let queue_name = _name(); 115 | let message_bytes = Buffer.from(_name()); 116 | 117 | await _createQueue(channel, queue_name); 118 | await _bindQueue(channel, queue_name, exchange, ''); 119 | await _publish(channel, exchange, '', message_bytes); 120 | await _consume(channel, queue_name, function validate() { 121 | should.fail('Message should never be received!'); 122 | }, true); 123 | }); 124 | 125 | test('gracefully failing when no bindings match the routing key', async function () { 126 | let { name, channel } = await _createExchange(); 127 | 128 | let exchange = name; 129 | let queue_name = _name(); 130 | let routing_key = _name(); 131 | let message_bytes = Buffer.from(_name()); 132 | 133 | await _createQueue(channel, queue_name); 134 | await _publish(channel, exchange, _join(routing_key), message_bytes); 135 | await _consume(channel, queue_name, function validate() { 136 | should.fail('Message should never be received!'); 137 | }, true); 138 | }); 139 | 140 | suiteTeardown('close connection', async function () { 141 | for (let task of _cleanupTasks) { 142 | await task(); 143 | } 144 | }); 145 | 146 | }); 147 | 148 | /* Private helpers */ 149 | 150 | async function _bindQueue(channel, queue, exchange, key) { 151 | await channel.bindQueue(queue, exchange, key, {}); 152 | } 153 | 154 | async function _createExchange() { 155 | let channel = await _connection.createChannel(); 156 | 157 | _scheduleForCleanup(async function () { 158 | await channel.close(); 159 | }); 160 | 161 | let name = _name(); 162 | let result = await channel.assertExchange(name, 'x-delimiter', {}); 163 | 164 | should(result).be.an.Object(); 165 | should(result).have.property('exchange'); 166 | should(result.exchange).eql(name); 167 | 168 | _scheduleForCleanup(async function () { 169 | return channel.deleteExchange(name, {}); 170 | }); 171 | 172 | return { name, channel }; 173 | } 174 | 175 | async function _createQueue(channel, queue) { 176 | let result = await channel.assertQueue(queue, { durable: false }); 177 | 178 | should(result).be.an.Object(); 179 | should(result).have.property('queue'); 180 | should(result.queue).eql(queue); 181 | } 182 | 183 | async function _consume(channel, queue, validator, exit) { 184 | return new Promise(function (resolve, reject) { 185 | channel 186 | .consume(queue, function (message) { 187 | validator(message); 188 | resolve(); 189 | }, {}) 190 | .then(function (result) { 191 | try { 192 | should(result).be.an.Object(); 193 | should(result).have.property('consumerTag'); 194 | } catch (err) { 195 | reject(err); 196 | } 197 | exit && resolve(); 198 | }) 199 | .catch(function (err) { 200 | reject(err); 201 | }); 202 | }); 203 | } 204 | 205 | async function _publish(channel, exchange, key, message) { 206 | await channel.publish(exchange, key, message, {}); 207 | } 208 | 209 | function _join() { 210 | let key = ''; 211 | for (var i = 0, j = arguments.length; i < j; i++) { 212 | key += ':' + arguments[i]; 213 | } 214 | return key; 215 | } 216 | 217 | function _name() { 218 | return Math.random().toString(36).substring(7); 219 | } 220 | 221 | function _scheduleForCleanup(task) { 222 | _cleanupTasks.unshift(task); 223 | } 224 | -------------------------------------------------------------------------------- /rabbitmq-components.mk: -------------------------------------------------------------------------------- 1 | ifeq ($(.DEFAULT_GOAL),) 2 | # Define default goal to `all` because this file defines some targets 3 | # before the inclusion of erlang.mk leading to the wrong target becoming 4 | # the default. 5 | .DEFAULT_GOAL = all 6 | endif 7 | 8 | # PROJECT_VERSION defaults to: 9 | # 1. the version exported by rabbitmq-server-release; 10 | # 2. the version stored in `git-revisions.txt`, if it exists; 11 | # 3. a version based on git-describe(1), if it is a Git clone; 12 | # 4. 0.0.0 13 | 14 | PROJECT_VERSION := $(RABBITMQ_VERSION) 15 | 16 | ifeq ($(PROJECT_VERSION),) 17 | PROJECT_VERSION := $(shell \ 18 | if test -f git-revisions.txt; then \ 19 | head -n1 git-revisions.txt | \ 20 | awk '{print $$$(words $(PROJECT_DESCRIPTION) version);}'; \ 21 | else \ 22 | (git describe --dirty --abbrev=7 --tags --always --first-parent \ 23 | 2>/dev/null || echo rabbitmq_v0_0_0) | \ 24 | sed -e 's/^rabbitmq_v//' -e 's/^v//' -e 's/_/./g' -e 's/-/+/' \ 25 | -e 's/-/./g'; \ 26 | fi) 27 | endif 28 | 29 | # -------------------------------------------------------------------- 30 | # RabbitMQ components. 31 | # -------------------------------------------------------------------- 32 | 33 | # For RabbitMQ repositories, we want to checkout branches which match 34 | # the parent project. For instance, if the parent project is on a 35 | # release tag, dependencies must be on the same release tag. If the 36 | # parent project is on a topic branch, dependencies must be on the same 37 | # topic branch or fallback to `stable` or `master` whichever was the 38 | # base of the topic branch. 39 | 40 | dep_amqp_client = git_rmq-subfolder rabbitmq-erlang-client $(current_rmq_ref) $(base_rmq_ref) master 41 | dep_amqp10_client = git_rmq-subfolder rabbitmq-amqp1.0-client $(current_rmq_ref) $(base_rmq_ref) master 42 | dep_amqp10_common = git_rmq-subfolder rabbitmq-amqp1.0-common $(current_rmq_ref) $(base_rmq_ref) master 43 | dep_rabbit = git_rmq-subfolder rabbitmq-server $(current_rmq_ref) $(base_rmq_ref) master 44 | dep_rabbit_common = git_rmq-subfolder rabbitmq-common $(current_rmq_ref) $(base_rmq_ref) master 45 | dep_rabbitmq_amqp1_0 = git_rmq-subfolder rabbitmq-amqp1.0 $(current_rmq_ref) $(base_rmq_ref) master 46 | dep_rabbitmq_auth_backend_amqp = git_rmq rabbitmq-auth-backend-amqp $(current_rmq_ref) $(base_rmq_ref) master 47 | dep_rabbitmq_auth_backend_cache = git_rmq-subfolder rabbitmq-auth-backend-cache $(current_rmq_ref) $(base_rmq_ref) master 48 | dep_rabbitmq_auth_backend_http = git_rmq-subfolder rabbitmq-auth-backend-http $(current_rmq_ref) $(base_rmq_ref) master 49 | dep_rabbitmq_auth_backend_ldap = git_rmq-subfolder rabbitmq-auth-backend-ldap $(current_rmq_ref) $(base_rmq_ref) master 50 | dep_rabbitmq_auth_backend_oauth2 = git_rmq-subfolder rabbitmq-auth-backend-oauth2 $(current_rmq_ref) $(base_rmq_ref) master 51 | dep_rabbitmq_auth_mechanism_ssl = git_rmq-subfolder rabbitmq-auth-mechanism-ssl $(current_rmq_ref) $(base_rmq_ref) master 52 | dep_rabbitmq_aws = git_rmq-subfolder rabbitmq-aws $(current_rmq_ref) $(base_rmq_ref) master 53 | dep_rabbitmq_boot_steps_visualiser = git_rmq rabbitmq-boot-steps-visualiser $(current_rmq_ref) $(base_rmq_ref) master 54 | dep_rabbitmq_cli = git_rmq-subfolder rabbitmq-cli $(current_rmq_ref) $(base_rmq_ref) master 55 | dep_rabbitmq_codegen = git_rmq-subfolder rabbitmq-codegen $(current_rmq_ref) $(base_rmq_ref) master 56 | dep_rabbitmq_consistent_hash_exchange = git_rmq-subfolder rabbitmq-consistent-hash-exchange $(current_rmq_ref) $(base_rmq_ref) master 57 | dep_rabbitmq_ct_client_helpers = git_rmq-subfolder rabbitmq-ct-client-helpers $(current_rmq_ref) $(base_rmq_ref) master 58 | dep_rabbitmq_ct_helpers = git_rmq-subfolder rabbitmq-ct-helpers $(current_rmq_ref) $(base_rmq_ref) master 59 | dep_rabbitmq_delayed_message_exchange = git_rmq rabbitmq-delayed-message-exchange $(current_rmq_ref) $(base_rmq_ref) master 60 | dep_rabbitmq_dotnet_client = git_rmq rabbitmq-dotnet-client $(current_rmq_ref) $(base_rmq_ref) master 61 | dep_rabbitmq_event_exchange = git_rmq-subfolder rabbitmq-event-exchange $(current_rmq_ref) $(base_rmq_ref) master 62 | dep_rabbitmq_federation = git_rmq-subfolder rabbitmq-federation $(current_rmq_ref) $(base_rmq_ref) master 63 | dep_rabbitmq_federation_management = git_rmq-subfolder rabbitmq-federation-management $(current_rmq_ref) $(base_rmq_ref) master 64 | dep_rabbitmq_java_client = git_rmq rabbitmq-java-client $(current_rmq_ref) $(base_rmq_ref) master 65 | dep_rabbitmq_jms_client = git_rmq rabbitmq-jms-client $(current_rmq_ref) $(base_rmq_ref) master 66 | dep_rabbitmq_jms_cts = git_rmq rabbitmq-jms-cts $(current_rmq_ref) $(base_rmq_ref) master 67 | dep_rabbitmq_jms_topic_exchange = git_rmq-subfolder rabbitmq-jms-topic-exchange $(current_rmq_ref) $(base_rmq_ref) master 68 | dep_rabbitmq_lvc_exchange = git_rmq rabbitmq-lvc-exchange $(current_rmq_ref) $(base_rmq_ref) master 69 | dep_rabbitmq_management = git_rmq-subfolder rabbitmq-management $(current_rmq_ref) $(base_rmq_ref) master 70 | dep_rabbitmq_management_agent = git_rmq-subfolder rabbitmq-management-agent $(current_rmq_ref) $(base_rmq_ref) master 71 | dep_rabbitmq_management_exchange = git_rmq rabbitmq-management-exchange $(current_rmq_ref) $(base_rmq_ref) master 72 | dep_rabbitmq_management_themes = git_rmq rabbitmq-management-themes $(current_rmq_ref) $(base_rmq_ref) master 73 | dep_rabbitmq_message_timestamp = git_rmq rabbitmq-message-timestamp $(current_rmq_ref) $(base_rmq_ref) master 74 | dep_rabbitmq_metronome = git_rmq rabbitmq-metronome $(current_rmq_ref) $(base_rmq_ref) master 75 | dep_rabbitmq_mqtt = git_rmq-subfolder rabbitmq-mqtt $(current_rmq_ref) $(base_rmq_ref) master 76 | dep_rabbitmq_objc_client = git_rmq rabbitmq-objc-client $(current_rmq_ref) $(base_rmq_ref) master 77 | dep_rabbitmq_peer_discovery_aws = git_rmq-subfolder rabbitmq-peer-discovery-aws $(current_rmq_ref) $(base_rmq_ref) master 78 | dep_rabbitmq_peer_discovery_common = git_rmq-subfolder rabbitmq-peer-discovery-common $(current_rmq_ref) $(base_rmq_ref) master 79 | dep_rabbitmq_peer_discovery_consul = git_rmq-subfolder rabbitmq-peer-discovery-consul $(current_rmq_ref) $(base_rmq_ref) master 80 | dep_rabbitmq_peer_discovery_etcd = git_rmq-subfolder rabbitmq-peer-discovery-etcd $(current_rmq_ref) $(base_rmq_ref) master 81 | dep_rabbitmq_peer_discovery_k8s = git_rmq-subfolder rabbitmq-peer-discovery-k8s $(current_rmq_ref) $(base_rmq_ref) master 82 | dep_rabbitmq_prometheus = git_rmq-subfolder rabbitmq-prometheus $(current_rmq_ref) $(base_rmq_ref) master 83 | dep_rabbitmq_random_exchange = git_rmq-subfolder rabbitmq-random-exchange $(current_rmq_ref) $(base_rmq_ref) master 84 | dep_rabbitmq_recent_history_exchange = git_rmq-subfolder rabbitmq-recent-history-exchange $(current_rmq_ref) $(base_rmq_ref) master 85 | dep_rabbitmq_routing_node_stamp = git_rmq rabbitmq-routing-node-stamp $(current_rmq_ref) $(base_rmq_ref) master 86 | dep_rabbitmq_rtopic_exchange = git_rmq rabbitmq-rtopic-exchange $(current_rmq_ref) $(base_rmq_ref) master 87 | dep_rabbitmq_sharding = git_rmq-subfolder rabbitmq-sharding $(current_rmq_ref) $(base_rmq_ref) master 88 | dep_rabbitmq_shovel = git_rmq-subfolder rabbitmq-shovel $(current_rmq_ref) $(base_rmq_ref) master 89 | dep_rabbitmq_shovel_management = git_rmq-subfolder rabbitmq-shovel-management $(current_rmq_ref) $(base_rmq_ref) master 90 | dep_rabbitmq_stomp = git_rmq-subfolder rabbitmq-stomp $(current_rmq_ref) $(base_rmq_ref) master 91 | dep_rabbitmq_stream = git_rmq-subfolder rabbitmq-stream $(current_rmq_ref) $(base_rmq_ref) master 92 | dep_rabbitmq_stream_common = git_rmq-subfolder rabbitmq-stream-common $(current_rmq_ref) $(base_rmq_ref) master 93 | dep_rabbitmq_stream_management = git_rmq-subfolder rabbitmq-stream-management $(current_rmq_ref) $(base_rmq_ref) master 94 | dep_rabbitmq_toke = git_rmq rabbitmq-toke $(current_rmq_ref) $(base_rmq_ref) master 95 | dep_rabbitmq_top = git_rmq-subfolder rabbitmq-top $(current_rmq_ref) $(base_rmq_ref) master 96 | dep_rabbitmq_tracing = git_rmq-subfolder rabbitmq-tracing $(current_rmq_ref) $(base_rmq_ref) master 97 | dep_rabbitmq_trust_store = git_rmq-subfolder rabbitmq-trust-store $(current_rmq_ref) $(base_rmq_ref) master 98 | dep_rabbitmq_test = git_rmq rabbitmq-test $(current_rmq_ref) $(base_rmq_ref) master 99 | dep_rabbitmq_web_dispatch = git_rmq-subfolder rabbitmq-web-dispatch $(current_rmq_ref) $(base_rmq_ref) master 100 | dep_rabbitmq_web_stomp = git_rmq-subfolder rabbitmq-web-stomp $(current_rmq_ref) $(base_rmq_ref) master 101 | dep_rabbitmq_web_stomp_examples = git_rmq-subfolder rabbitmq-web-stomp-examples $(current_rmq_ref) $(base_rmq_ref) master 102 | dep_rabbitmq_web_mqtt = git_rmq-subfolder rabbitmq-web-mqtt $(current_rmq_ref) $(base_rmq_ref) master 103 | dep_rabbitmq_web_mqtt_examples = git_rmq-subfolder rabbitmq-web-mqtt-examples $(current_rmq_ref) $(base_rmq_ref) master 104 | dep_rabbitmq_website = git_rmq rabbitmq-website $(current_rmq_ref) $(base_rmq_ref) live master 105 | dep_toke = git_rmq toke $(current_rmq_ref) $(base_rmq_ref) master 106 | 107 | # Third-party dependencies version pinning. 108 | # 109 | # We do that in this file, which is copied in all projects, to ensure 110 | # all projects use the same versions. It avoids conflicts and makes it 111 | # possible to work with rabbitmq-public-umbrella. 112 | 113 | dep_accept = hex 0.3.5 114 | dep_cowboy = hex 2.8.0 115 | dep_cowlib = hex 2.9.1 116 | dep_thoas = hex 0.3.0 117 | dep_looking_glass = git https://github.com/rabbitmq/looking_glass master 118 | dep_prometheus = hex 4.9.1 119 | dep_ra = git https://github.com/rabbitmq/ra.git main 120 | dep_ranch = hex 2.1.0 121 | dep_recon = hex 2.5.2 122 | dep_redbug = hex 2.0.7 123 | dep_thoas = hex 0.4.0 124 | dep_observer_cli = hex 1.7.3 125 | dep_stdout_formatter = hex 0.2.4 126 | dep_sysmon_handler = hex 1.3.0 127 | 128 | RABBITMQ_COMPONENTS = amqp_client \ 129 | amqp10_common \ 130 | amqp10_client \ 131 | rabbit \ 132 | rabbit_common \ 133 | rabbitmq_amqp1_0 \ 134 | rabbitmq_auth_backend_amqp \ 135 | rabbitmq_auth_backend_cache \ 136 | rabbitmq_auth_backend_http \ 137 | rabbitmq_auth_backend_ldap \ 138 | rabbitmq_auth_backend_oauth2 \ 139 | rabbitmq_auth_mechanism_ssl \ 140 | rabbitmq_aws \ 141 | rabbitmq_boot_steps_visualiser \ 142 | rabbitmq_cli \ 143 | rabbitmq_codegen \ 144 | rabbitmq_consistent_hash_exchange \ 145 | rabbitmq_ct_client_helpers \ 146 | rabbitmq_ct_helpers \ 147 | rabbitmq_delayed_message_exchange \ 148 | rabbitmq_dotnet_client \ 149 | rabbitmq_event_exchange \ 150 | rabbitmq_federation \ 151 | rabbitmq_federation_management \ 152 | rabbitmq_java_client \ 153 | rabbitmq_jms_client \ 154 | rabbitmq_jms_cts \ 155 | rabbitmq_jms_topic_exchange \ 156 | rabbitmq_lvc_exchange \ 157 | rabbitmq_management \ 158 | rabbitmq_management_agent \ 159 | rabbitmq_management_exchange \ 160 | rabbitmq_management_themes \ 161 | rabbitmq_message_timestamp \ 162 | rabbitmq_metronome \ 163 | rabbitmq_mqtt \ 164 | rabbitmq_objc_client \ 165 | rabbitmq_peer_discovery_aws \ 166 | rabbitmq_peer_discovery_common \ 167 | rabbitmq_peer_discovery_consul \ 168 | rabbitmq_peer_discovery_etcd \ 169 | rabbitmq_peer_discovery_k8s \ 170 | rabbitmq_prometheus \ 171 | rabbitmq_random_exchange \ 172 | rabbitmq_recent_history_exchange \ 173 | rabbitmq_routing_node_stamp \ 174 | rabbitmq_rtopic_exchange \ 175 | rabbitmq_server_release \ 176 | rabbitmq_sharding \ 177 | rabbitmq_shovel \ 178 | rabbitmq_shovel_management \ 179 | rabbitmq_stomp \ 180 | rabbitmq_stream \ 181 | rabbitmq_stream_common \ 182 | rabbitmq_stream_management \ 183 | rabbitmq_toke \ 184 | rabbitmq_top \ 185 | rabbitmq_tracing \ 186 | rabbitmq_trust_store \ 187 | rabbitmq_web_dispatch \ 188 | rabbitmq_web_mqtt \ 189 | rabbitmq_web_mqtt_examples \ 190 | rabbitmq_web_stomp \ 191 | rabbitmq_web_stomp_examples \ 192 | rabbitmq_website 193 | 194 | # Erlang.mk does not rebuild dependencies by default, once they were 195 | # compiled once, except for those listed in the `$(FORCE_REBUILD)` 196 | # variable. 197 | # 198 | # We want all RabbitMQ components to always be rebuilt: this eases 199 | # the work on several components at the same time. 200 | 201 | FORCE_REBUILD = $(RABBITMQ_COMPONENTS) 202 | 203 | # Several components have a custom erlang.mk/build.config, mainly 204 | # to disable eunit. Therefore, we can't use the top-level project's 205 | # erlang.mk copy. 206 | NO_AUTOPATCH += $(RABBITMQ_COMPONENTS) 207 | 208 | ifeq ($(origin current_rmq_ref),undefined) 209 | ifneq ($(wildcard .git),) 210 | current_rmq_ref := $(shell (\ 211 | ref=$$(LANG=C git branch --list | awk '/^\* \(.*detached / {ref=$$0; sub(/.*detached [^ ]+ /, "", ref); sub(/\)$$/, "", ref); print ref; exit;} /^\* / {ref=$$0; sub(/^\* /, "", ref); print ref; exit}');\ 212 | if test "$$(git rev-parse --short HEAD)" != "$$ref"; then echo "$$ref"; fi)) 213 | else 214 | current_rmq_ref := master 215 | endif 216 | endif 217 | export current_rmq_ref 218 | 219 | ifeq ($(origin base_rmq_ref),undefined) 220 | ifneq ($(wildcard .git),) 221 | possible_base_rmq_ref := master 222 | ifeq ($(possible_base_rmq_ref),$(current_rmq_ref)) 223 | base_rmq_ref := $(current_rmq_ref) 224 | else 225 | base_rmq_ref := $(shell \ 226 | (git rev-parse --verify -q master >/dev/null && \ 227 | git rev-parse --verify -q $(possible_base_rmq_ref) >/dev/null && \ 228 | git merge-base --is-ancestor $$(git merge-base master HEAD) $(possible_base_rmq_ref) && \ 229 | echo $(possible_base_rmq_ref)) || \ 230 | echo master) 231 | endif 232 | else 233 | base_rmq_ref := master 234 | endif 235 | endif 236 | export base_rmq_ref 237 | 238 | # Repository URL selection. 239 | # 240 | # First, we infer other components' location from the current project 241 | # repository URL, if it's a Git repository: 242 | # - We take the "origin" remote URL as the base 243 | # - The current project name and repository name is replaced by the 244 | # target's properties: 245 | # eg. rabbitmq-common is replaced by rabbitmq-codegen 246 | # eg. rabbit_common is replaced by rabbitmq_codegen 247 | # 248 | # If cloning from this computed location fails, we fallback to RabbitMQ 249 | # upstream which is GitHub. 250 | 251 | # Macro to transform eg. "rabbit_common" to "rabbitmq-common". 252 | rmq_cmp_repo_name = $(word 2,$(dep_$(1))) 253 | 254 | # Upstream URL for the current project. 255 | RABBITMQ_COMPONENT_REPO_NAME := $(call rmq_cmp_repo_name,$(PROJECT)) 256 | RABBITMQ_UPSTREAM_FETCH_URL ?= https://github.com/rabbitmq/$(RABBITMQ_COMPONENT_REPO_NAME).git 257 | RABBITMQ_UPSTREAM_PUSH_URL ?= git@github.com:rabbitmq/$(RABBITMQ_COMPONENT_REPO_NAME).git 258 | 259 | # Current URL for the current project. If this is not a Git clone, 260 | # default to the upstream Git repository. 261 | ifneq ($(wildcard .git),) 262 | git_origin_fetch_url := $(shell git config remote.origin.url) 263 | git_origin_push_url := $(shell git config remote.origin.pushurl || git config remote.origin.url) 264 | RABBITMQ_CURRENT_FETCH_URL ?= $(git_origin_fetch_url) 265 | RABBITMQ_CURRENT_PUSH_URL ?= $(git_origin_push_url) 266 | else 267 | RABBITMQ_CURRENT_FETCH_URL ?= $(RABBITMQ_UPSTREAM_FETCH_URL) 268 | RABBITMQ_CURRENT_PUSH_URL ?= $(RABBITMQ_UPSTREAM_PUSH_URL) 269 | endif 270 | 271 | # Macro to replace the following pattern: 272 | # 1. /foo.git -> /bar.git 273 | # 2. /foo -> /bar 274 | # 3. /foo/ -> /bar/ 275 | subst_repo_name = $(patsubst %/$(1)/%,%/$(2)/%,$(patsubst %/$(1),%/$(2),$(patsubst %/$(1).git,%/$(2).git,$(3)))) 276 | 277 | # Macro to replace both the project's name (eg. "rabbit_common") and 278 | # repository name (eg. "rabbitmq-common") by the target's equivalent. 279 | # 280 | # This macro is kept on one line because we don't want whitespaces in 281 | # the returned value, as it's used in $(dep_fetch_git_rmq) in a shell 282 | # single-quoted string. 283 | dep_rmq_repo = $(if $(dep_$(2)),$(call subst_repo_name,$(PROJECT),$(2),$(call subst_repo_name,$(RABBITMQ_COMPONENT_REPO_NAME),$(call rmq_cmp_repo_name,$(2)),$(1))),$(pkg_$(1)_repo)) 284 | 285 | dep_rmq_commits = $(if $(dep_$(1)), \ 286 | $(wordlist 3,$(words $(dep_$(1))),$(dep_$(1))), \ 287 | $(pkg_$(1)_commit)) 288 | 289 | define dep_fetch_git_rmq 290 | fetch_url1='$(call dep_rmq_repo,$(RABBITMQ_CURRENT_FETCH_URL),$(1))'; \ 291 | fetch_url2='$(call dep_rmq_repo,$(RABBITMQ_UPSTREAM_FETCH_URL),$(1))'; \ 292 | if test "$$$$fetch_url1" != '$(RABBITMQ_CURRENT_FETCH_URL)' && \ 293 | git clone -q -n -- "$$$$fetch_url1" $(DEPS_DIR)/$(call dep_name,$(1)); then \ 294 | fetch_url="$$$$fetch_url1"; \ 295 | push_url='$(call dep_rmq_repo,$(RABBITMQ_CURRENT_PUSH_URL),$(1))'; \ 296 | elif git clone -q -n -- "$$$$fetch_url2" $(DEPS_DIR)/$(call dep_name,$(1)); then \ 297 | fetch_url="$$$$fetch_url2"; \ 298 | push_url='$(call dep_rmq_repo,$(RABBITMQ_UPSTREAM_PUSH_URL),$(1))'; \ 299 | fi; \ 300 | cd $(DEPS_DIR)/$(call dep_name,$(1)) && ( \ 301 | $(foreach ref,$(call dep_rmq_commits,$(1)), \ 302 | git checkout -q $(ref) >/dev/null 2>&1 || \ 303 | ) \ 304 | (echo "error: no valid pathspec among: $(call dep_rmq_commits,$(1))" \ 305 | 1>&2 && false) ) && \ 306 | (test "$$$$fetch_url" = "$$$$push_url" || \ 307 | git remote set-url --push origin "$$$$push_url") 308 | endef 309 | 310 | define dep_fetch_git_rmq-subfolder 311 | fetch_url1='https://github.com/rabbitmq/rabbitmq-server.git'; \ 312 | fetch_url2='git@github.com:rabbitmq/rabbitmq-server.git'; \ 313 | if [ ! -d $(ERLANG_MK_TMP)/rabbitmq-server ]; then \ 314 | if test "$$$$fetch_url1" != '$(RABBITMQ_CURRENT_FETCH_URL)' && \ 315 | git clone -q -n -- "$$$$fetch_url1" $(ERLANG_MK_TMP)/rabbitmq-server; then \ 316 | fetch_url="$$$$fetch_url1"; \ 317 | push_url='$(call dep_rmq_repo,$(RABBITMQ_CURRENT_PUSH_URL),rabbitmq-server)'; \ 318 | elif git clone -q -n -- "$$$$fetch_url2" $(ERLANG_MK_TMP)/rabbitmq-server; then \ 319 | fetch_url="$$$$fetch_url2"; \ 320 | push_url='$(call dep_rmq_repo,$(RABBITMQ_UPSTREAM_PUSH_URL),rabbitmq-server)'; \ 321 | fi; \ 322 | fi; \ 323 | cd $(ERLANG_MK_TMP)/rabbitmq-server && ( \ 324 | $(foreach ref,$(call dep_rmq_commits,$(1)), \ 325 | git checkout -q $(ref) >/dev/null 2>&1 || \ 326 | ) \ 327 | (echo "error: no valid pathspec among: $(call dep_rmq_commits,$(1))" \ 328 | 1>&2 && false) ) && \ 329 | (test "$$$$fetch_url" = "$$$$push_url" || \ 330 | git remote set-url --push origin "$$$$push_url") 331 | ln -s $(ERLANG_MK_TMP)/rabbitmq-server/deps/$(call dep_name,$(1)) \ 332 | $(DEPS_DIR)/$(call dep_name,$(1)); 333 | endef 334 | 335 | # -------------------------------------------------------------------- 336 | # Component distribution. 337 | # -------------------------------------------------------------------- 338 | 339 | list-dist-deps:: 340 | @: 341 | 342 | prepare-dist:: 343 | @: 344 | 345 | # -------------------------------------------------------------------- 346 | # Umbrella-specific settings. 347 | # -------------------------------------------------------------------- 348 | 349 | # If the top-level project is a RabbitMQ component, we override 350 | # $(DEPS_DIR) for this project to point to the top-level's one. 351 | # 352 | # We also verify that the guessed DEPS_DIR is actually named `deps`, 353 | # to rule out any situation where it is a coincidence that we found a 354 | # `rabbitmq-components.mk` up upper directories. 355 | 356 | possible_deps_dir_1 = $(abspath ..) 357 | possible_deps_dir_2 = $(abspath ../../..) 358 | 359 | ifeq ($(notdir $(possible_deps_dir_1)),deps) 360 | ifneq ($(wildcard $(possible_deps_dir_1)/../rabbitmq-components.mk),) 361 | deps_dir_overriden = 1 362 | DEPS_DIR ?= $(possible_deps_dir_1) 363 | DISABLE_DISTCLEAN = 1 364 | endif 365 | endif 366 | 367 | ifeq ($(deps_dir_overriden),) 368 | ifeq ($(notdir $(possible_deps_dir_2)),deps) 369 | ifneq ($(wildcard $(possible_deps_dir_2)/../rabbitmq-components.mk),) 370 | deps_dir_overriden = 1 371 | DEPS_DIR ?= $(possible_deps_dir_2) 372 | DISABLE_DISTCLEAN = 1 373 | endif 374 | endif 375 | endif 376 | 377 | ifneq ($(wildcard UMBRELLA.md),) 378 | DISABLE_DISTCLEAN = 1 379 | endif 380 | 381 | # We disable `make distclean` so $(DEPS_DIR) is not accidentally removed. 382 | 383 | ifeq ($(DISABLE_DISTCLEAN),1) 384 | ifneq ($(filter distclean distclean-deps,$(MAKECMDGOALS)),) 385 | SKIP_DEPS = 1 386 | endif 387 | endif 388 | --------------------------------------------------------------------------------