Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions Extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,39 @@
To enable all extensions for your platform, you may use -DENABLE_ALL=TRUE OR select the option to "Enable all Extensions" in the bootstrap script. [ReadMe](https://github.com/apache/nifi-minifi-cpp/#bootstrapping)

# Extension internals
Extensions are dynamic libraries loaded at runtime by the agent. An extension makes its
capabilities (classes) available to the system through registrars. Registration must happen in source files, not headers.
Extensions are dynamic libraries loaded at runtime by the agent.

## C extensions
You can build a shared library depending on the C capabilities of the agent as given in the `minifi-c.h` file.
For the shared library to be considered a valid extension, it has to have a global symbol with the name `MinifiCApiVersion`
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states the symbol should be named MinifiCApiVersion, but the actual code in Extension.cpp line 70 looks for MinifiApiVersion (without the "C"). The documentation should be corrected to match the implementation.

Suggested change
For the shared library to be considered a valid extension, it has to have a global symbol with the name `MinifiCApiVersion`
For the shared library to be considered a valid extension, it has to have a global symbol with the name `MinifiApiVersion`

Copilot uses AI. Check for mistakes.
with its value as a null terminated string (`const char*`) of the macro `MINIFI_API_VERSION` from `minifi-c.h`.

Moreover the actual resource registration (processors/controller services) has to happen during the `MinifiInitExtension` call.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this enforced?
Can we document the option that minifi calls this function multiple times, and it has to make the same registration calls in the same order every time? This would leave more room for changing behavior in a backwards-compatible way later, e.g. call once to track data size and allocate a buffer, call second time to actually copy all of the data to minifi buffers. Or unload and reload the extension without closing and reopening the library.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these kinds of questions are better addressed in PR #2150 as there have been some changes touching this part as well

One possible example of this is:

```C++
extern "C" const char* const MinifiApiVersion = MINIFI_API_VERSION;

extern "C" void MinifiInitExtension(MinifiExtension* extension, MinifiConfig* /*config*/) {
minifi::api::core::useProcessorClassDescription<minifi::extensions::llamacpp::processors::RunLlamaCppInference>([&] (const MinifiProcessorClassDefinition& description) {
MinifiExtensionCreateInfo ext_create_info{
.name = minifi::api::utils::toStringView(MAKESTRING(EXTENSION_NAME)),
.version = minifi::api::utils::toStringView(MAKESTRING(EXTENSION_VERSION)),
.deinit = nullptr,
.user_data = nullptr,
.processors_count = 1,
.processors_ptr = &description,
};
MinifiCreateExtension(extension, &ext_create_info);
});
}
```

## C++ extensions
You can utilize the C++ api, linking to `minifi-api` and possibly using the helpers in `extension-framework`.
No compatibilities are guaranteed beyond what extensions are built together with the agent at the same time.

An extension makes its capabilities (classes) available to the system through registrars. Registration must happen in source files, not headers.

```C++
// register user-facing classes as
Expand All @@ -33,10 +64,10 @@ REGISTER_RESOURCE(RESTSender, DescriptionOnly);
```

Some extensions (e.g. `OpenCVExtension`) require initialization before use.
You need to define an `InitExtension` function of type `MinifiExtension*(MinifiConfig*)` to be called.
You need to define an `MinifiInitCppExtension` function of type `MinifiExtension*(MinifiConfig*)` to be called.

```C++
extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
extern "C" void MinifiInitCppExtension(MinifiExtension* extension, MinifiConfig* /*config*/) {
const auto success = org::apache::nifi::minifi::utils::Environment::setEnvironmentVariable("OPENCV_FFMPEG_CAPTURE_OPTIONS", "rtsp_transport;udp", false /*overwrite*/);
if (!success) {
return nullptr;
Expand All @@ -49,7 +80,7 @@ extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
.processors_count = 0,
.processors_ptr = nullptr
};
return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
}
```

Expand Down
26 changes: 5 additions & 21 deletions cmake/Extensions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,16 @@ define_property(GLOBAL PROPERTY EXTENSION-OPTIONS

set_property(GLOBAL PROPERTY EXTENSION-OPTIONS "")

set(extension-build-info-file "${CMAKE_CURRENT_BINARY_DIR}/ExtensionBuildInfo.cpp")
file(GENERATE OUTPUT ${extension-build-info-file}
CONTENT "\
#include \"minifi-cpp/utils/Export.h\"\n\
#ifdef BUILD_ID_VARIABLE_NAME\n\
EXTENSIONAPI extern const char* const BUILD_ID_VARIABLE_NAME = \"__EXTENSION_BUILD_IDENTIFIER_BEGIN__${BUILD_IDENTIFIER}__EXTENSION_BUILD_IDENTIFIER_END__\";\n\
#else\n\
static_assert(false, \"BUILD_ID_VARIABLE_NAME is not defined\");\n\
#endif\n")

function(get_build_id_variable_name extension-name output)
string(REPLACE "-" "_" result ${extension-name})
string(APPEND result "_build_identifier")
set("${output}" "${result}" PARENT_SCOPE)
endfunction()

macro(register_extension extension-name extension-display-name extension-guard description)
set(${extension-guard} ${extension-name} PARENT_SCOPE)
get_property(extensions GLOBAL PROPERTY EXTENSION-OPTIONS)
set_property(GLOBAL APPEND PROPERTY EXTENSION-OPTIONS ${extension-name})
get_build_id_variable_name(${extension-name} build-id-variable-name)
set_source_files_properties(${extension-build-info-file} PROPERTIES GENERATED TRUE)
target_sources(${extension-name} PRIVATE ${extension-build-info-file})
get_target_property(has_custom_initializer ${extension-name} HAS_CUSTOM_INITIALIZER)
if (NOT has_custom_initializer)
target_sources(${extension-name} PRIVATE ${CMAKE_SOURCE_DIR}/extensions/ExtensionInitializer.cpp)
endif()
target_compile_definitions(${extension-name}
PRIVATE "MODULE_NAME=${extension-name}"
PRIVATE "BUILD_ID_VARIABLE_NAME=${build-id-variable-name}")
PRIVATE "MODULE_NAME=${extension-name}")
set_target_properties(${extension-name} PROPERTIES
ENABLE_EXPORTS True
POSITION_INDEPENDENT_CODE ON)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
#include "utils/StringUtils.h"
#include "minifi-cpp/core/PropertyDefinition.h"

#ifdef WIN32
#define CEXTENSIONAPI extern "C" __declspec(dllexport)
#else
#define CEXTENSIONAPI extern "C"
#endif

namespace org::apache::nifi::minifi::api::utils {

inline MinifiStringView toStringView(std::string_view str) {
Expand Down
4 changes: 4 additions & 0 deletions extension-framework/include/utils/ExtensionInitUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ inline MinifiStringView toStringView(std::string_view str) {

using ConfigReader = std::function<std::optional<std::string>(std::string_view key)>;

static inline void MinifiCreateCppExtension(MinifiExtension* extension, const MinifiExtensionCreateInfo* create_info) {
MINIFI_CREATE_EXTENSION_FN(extension, create_info);
}

} // namespace org::apache::nifi::minifi::utils
35 changes: 35 additions & 0 deletions extensions/ExtensionInitializer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
#include "minifi-c/minifi-c.h"
#include "utils/ExtensionInitUtils.h"
#include "minifi-cpp/agent/agent_version.h"
#include "core/Resource.h"

namespace minifi = org::apache::nifi::minifi;

extern "C" void MinifiInitCppExtension(MinifiExtension* extension, MinifiConfig* /*config*/) {
MinifiExtensionCreateInfo ext_create_info{
.name = minifi::utils::toStringView(MAKESTRING(MODULE_NAME)),
.version = minifi::utils::toStringView(minifi::AgentBuild::VERSION),
.deinit = nullptr,
.user_data = nullptr,
.processors_count = 0,
.processors_ptr = nullptr
};
minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
}
10 changes: 4 additions & 6 deletions extensions/llamacpp/processors/ExtensionInitializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@

namespace minifi = org::apache::nifi::minifi;

extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
MinifiExtension* extension = nullptr;
CEXTENSIONAPI const uint32_t MinifiApiVersion = MINIFI_API_VERSION;

CEXTENSIONAPI void MinifiInitExtension(MinifiExtension* extension, MinifiConfig* /*config*/) {
minifi::api::core::useProcessorClassDescription<minifi::extensions::llamacpp::processors::RunLlamaCppInference>([&] (const MinifiProcessorClassDefinition& description) {
MinifiExtensionCreateInfo ext_create_info{
.name = minifi::api::utils::toStringView(MAKESTRING(EXTENSION_NAME)),
Expand All @@ -35,9 +36,6 @@ extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
.processors_count = 1,
.processors_ptr = &description,
};
extension = MinifiCreateExtension(minifi::api::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
MinifiCreateExtension(extension, &ext_create_info);
});
return extension;
}

extern const char* const MINIFI_API_VERSION_TAG_var = MINIFI_API_VERSION_TAG;
1 change: 1 addition & 0 deletions extensions/opencv/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ include(${CMAKE_SOURCE_DIR}/extensions/ExtensionHeader.txt)
file(GLOB SOURCES "*.cpp")

add_minifi_library(minifi-opencv SHARED ${SOURCES})
set_target_properties(minifi-opencv PROPERTIES HAS_CUSTOM_INITIALIZER TRUE)
Comment thread
szaszm marked this conversation as resolved.

target_link_libraries(minifi-opencv ${LIBMINIFI})
target_link_libraries(minifi-opencv OPENCV::libopencv)
Expand Down
6 changes: 3 additions & 3 deletions extensions/opencv/OpenCVLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@

namespace minifi = org::apache::nifi::minifi;

extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
extern "C" void MinifiInitCppExtension(MinifiExtension* extension, MinifiConfig* /*config*/) {
// By default in OpenCV, ffmpeg capture is hardcoded to use TCP and this is a workaround
// also if UDP timeout, ffmpeg will retry with TCP
// Note:
// 1. OpenCV community are trying to find a better approach than setenv.
// 2. The command will not overwrite value if "OPENCV_FFMPEG_CAPTURE_OPTIONS" already exists.
const auto success = org::apache::nifi::minifi::utils::Environment::setEnvironmentVariable("OPENCV_FFMPEG_CAPTURE_OPTIONS", "rtsp_transport;udp", false /*overwrite*/);
if (!success) {
return nullptr;
return;
}
MinifiExtensionCreateInfo ext_create_info{
.name = minifi::utils::toStringView(MAKESTRING(MODULE_NAME)),
Expand All @@ -42,5 +42,5 @@ extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
.processors_count = 0,
.processors_ptr = nullptr
};
return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
}
2 changes: 2 additions & 0 deletions extensions/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ include(${CMAKE_SOURCE_DIR}/extensions/ExtensionHeader.txt)

if (NOT WIN32)
add_minifi_library(minifi-python-lib-loader-extension SHARED pythonlibloader/PythonLibLoader.cpp)
set_target_properties(minifi-python-lib-loader-extension PROPERTIES HAS_CUSTOM_INITIALIZER TRUE)
target_link_libraries(minifi-python-lib-loader-extension PRIVATE ${LIBMINIFI})
endif()

file(GLOB SOURCES "*.cpp" "types/*.cpp" "pythonloader/PyProcLoader.cpp")

add_minifi_library(minifi-python-script-extension SHARED ${SOURCES})
set_target_properties(minifi-python-script-extension PROPERTIES HAS_CUSTOM_INITIALIZER TRUE)

target_link_libraries(minifi-python-script-extension PRIVATE ${LIBMINIFI} Threads::Threads)

Expand Down
4 changes: 2 additions & 2 deletions extensions/python/pythonlibloader/PythonLibLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class PythonLibLoader {
std::shared_ptr<minifi::core::logging::Logger> logger_ = minifi::core::logging::LoggerFactory<PythonLibLoader>::getLogger();
};

extern "C" MinifiExtension* InitExtension(MinifiConfig* config) {
extern "C" void MinifiInitCppExtension(MinifiExtension* extension, MinifiConfig* config) {
static PythonLibLoader python_lib_loader([&] (std::string_view key) -> std::optional<std::string> {
std::optional<std::string> result;
MinifiConfigGet(config, minifi::utils::toStringView(key), [] (void* user_data, MinifiStringView value) {
Expand All @@ -114,5 +114,5 @@ extern "C" MinifiExtension* InitExtension(MinifiConfig* config) {
.processors_count = 0,
.processors_ptr = nullptr
};
return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
}
4 changes: 2 additions & 2 deletions extensions/python/pythonloader/PyProcLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ static minifi::extensions::python::PythonCreator& getPythonCreator() {
// the symbols of the python library
extern "C" const int LOAD_MODULE_AS_GLOBAL = 1;

extern "C" MinifiExtension* InitExtension(MinifiConfig* config) {
extern "C" void MinifiInitCppExtension(MinifiExtension* extension, MinifiConfig* config) {
getPythonCreator().configure([&] (std::string_view key) -> std::optional<std::string> {
std::optional<std::string> result;
MinifiConfigGet(config, minifi::utils::toStringView(key), [] (void* user_data, MinifiStringView value) {
Expand All @@ -49,5 +49,5 @@ extern "C" MinifiExtension* InitExtension(MinifiConfig* config) {
.processors_count = 0,
.processors_ptr = nullptr
};
return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
}
1 change: 1 addition & 0 deletions extensions/sftp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ include_directories(client processors)
file(GLOB SOURCES "*.cpp" "client/*.cpp" "processors/*.cpp")

add_minifi_library(minifi-sftp SHARED ${SOURCES})
set_target_properties(minifi-sftp PROPERTIES HAS_CUSTOM_INITIALIZER TRUE)

target_link_libraries(minifi-sftp ${LIBMINIFI} Threads::Threads)
target_link_libraries(minifi-sftp libssh2 RapidJSON)
Expand Down
8 changes: 4 additions & 4 deletions extensions/sftp/SFTPLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@

namespace minifi = org::apache::nifi::minifi;

extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
extern "C" void MinifiInitCppExtension(MinifiExtension* extension, MinifiConfig* /*config*/) {
if (libssh2_init(0) != 0) {
return nullptr;
return;
}
if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
libssh2_exit();
return nullptr;
return;
}
MinifiExtensionCreateInfo ext_create_info{
.name = minifi::utils::toStringView(MAKESTRING(MODULE_NAME)),
Expand All @@ -44,5 +44,5 @@ extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
.processors_count = 0,
.processors_ptr = nullptr
};
return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
}
30 changes: 30 additions & 0 deletions libminifi/include/core/extension/ApiVersion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/

#pragma once

#include <string>
#include <cinttypes>

namespace org::apache::nifi::minifi::core::extension {

uint32_t getAgentApiVersion();
uint32_t getMinSupportedApiVersion();
void test_setAgentApiVersion(uint32_t api_version);
void test_setMinSupportedApiVersion(uint32_t min_api_version);

} // namespace org::apache::nifi::minifi::core::extension
13 changes: 9 additions & 4 deletions libminifi/include/core/extension/Extension.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@

#pragma once

#include <memory>
#include <filesystem>
#include <map>
#include <memory>
#include <string>
#include <filesystem>

#include "minifi-c/minifi-c.h"
#include "minifi-cpp/core/logging/Logger.h"
#include "minifi-cpp/properties/Configure.h"
#include "ApiVersion.h"

namespace org::apache::nifi::minifi::core::extension {

Expand All @@ -49,6 +51,8 @@ class Extension {

bool initialize(const std::shared_ptr<minifi::Configure>& configure);

bool setInfo(Info info);

private:
#ifdef WIN32
std::map<void*, std::string> resource_mapping_;
Expand All @@ -67,11 +71,12 @@ class Extension {
bool unload();
void* findSymbol(const char* name);

std::string name_;
std::string library_name_;
std::filesystem::path library_path_;
gsl::owner<void*> handle_ = nullptr;

std::unique_ptr<Info> info_;
std::optional<Info> info_;
uint32_t api_version_{0};

const std::shared_ptr<logging::Logger> logger_;
};
Expand Down
2 changes: 2 additions & 0 deletions libminifi/include/core/extension/ExtensionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class ExtensionManager {
ExtensionManager& operator=(const ExtensionManager&) = delete;
ExtensionManager& operator=(ExtensionManager&&) = delete;

static Extension* getExtensionBeingInitialized();

private:
std::vector<std::unique_ptr<Extension>> extensions_;

Expand Down
Loading
Loading