Skip to content
Open
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
43 changes: 38 additions & 5 deletions Extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,41 @@
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.
One possible example of this is:

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

extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
MinifiExtension* extension = nullptr;
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,
};
extension = MinifiCreateExtension(&ext_create_info);
});
return extension;
}
```

## 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 +66,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" MinifiExtension* MinifiInitCppExtension(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 +82,7 @@ extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
.processors_count = 0,
.processors_ptr = nullptr
};
return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
return MinifiCreateCppExtension(&ext_create_info);
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 function call should be minifi::utils::MinifiCreateCppExtension(&ext_create_info) to be consistent with the actual implementation. The MinifiCreateCppExtension function is in the minifi::utils namespace (see extension-framework/include/utils/ExtensionInitUtils.h:33-35), and all other C++ extension loaders use the fully qualified name (see SFTPLoader.cpp:47, PyProcLoader.cpp:52, PythonLibLoader.cpp:117, OpenCVLoader.cpp:45, ExtensionInitializer.cpp:34).

Suggested change
return MinifiCreateCppExtension(&ext_create_info);
return minifi::utils::MinifiCreateCppExtension(&ext_create_info);

Copilot uses AI. Check for mistakes.
}
```

Expand Down
23 changes: 4 additions & 19 deletions cmake/Extensions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,14 @@ 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}")
Comment on lines 34 to 35
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 reference to build-id-variable-name on line 35 is problematic. This variable is never defined after the removal of the get_build_id_variable_name function. Since the BUILD_ID_VARIABLE_NAME compile definition is no longer used in the new symbol-based approach for API compatibility checking, this entire line (line 35) should be removed.

Suggested change
PRIVATE "MODULE_NAME=${extension-name}"
PRIVATE "BUILD_ID_VARIABLE_NAME=${build-id-variable-name}")
PRIVATE "MODULE_NAME=${extension-name}")

Copilot uses AI. Check for mistakes.
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 MinifiExtension* MinifiCreateCppExtension(const MinifiExtensionCreateInfo* create_info) {
return MINIFI_CREATE_EXTENSION_FN(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" MinifiExtension* MinifiInitCppExtension(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
};
return minifi::utils::MinifiCreateCppExtension(&ext_create_info);
}
8 changes: 4 additions & 4 deletions extensions/llamacpp/processors/ExtensionInitializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@

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

extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
extern "C" const char* const MinifiApiVersion = MINIFI_API_VERSION;

extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
MinifiExtension* extension = nullptr;
minifi::api::core::useProcessorClassDescription<minifi::extensions::llamacpp::processors::RunLlamaCppInference>([&] (const MinifiProcessorClassDefinition& description) {
MinifiExtensionCreateInfo ext_create_info{
Expand All @@ -35,9 +37,7 @@ extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
.processors_count = 1,
.processors_ptr = &description,
};
extension = MinifiCreateExtension(minifi::api::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
extension = MinifiCreateExtension(&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)

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

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

extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
extern "C" MinifiExtension* MinifiInitCppExtension(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:
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);
return minifi::utils::MinifiCreateCppExtension(&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" MinifiExtension* MinifiInitCppExtension(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);
return minifi::utils::MinifiCreateCppExtension(&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" MinifiExtension* MinifiInitCppExtension(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);
return minifi::utils::MinifiCreateCppExtension( &ext_create_info);
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.

Extra space before the opening parenthesis in the function call. This should be MinifiCreateCppExtension(&ext_create_info) without the space, consistent with all other calls to this function in the codebase (see SFTPLoader.cpp:47, PythonLibLoader.cpp:117, OpenCVLoader.cpp:45, ExtensionInitializer.cpp:34).

Suggested change
return minifi::utils::MinifiCreateCppExtension( &ext_create_info);
return minifi::utils::MinifiCreateCppExtension(&ext_create_info);

Copilot uses AI. Check for mistakes.
}
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
4 changes: 2 additions & 2 deletions extensions/sftp/SFTPLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

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

extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
if (libssh2_init(0) != 0) {
return nullptr;
}
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);
return minifi::utils::MinifiCreateCppExtension(&ext_create_info);
}
14 changes: 12 additions & 2 deletions libminifi/include/core/extension/Extension.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@

#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"

Expand All @@ -38,6 +39,12 @@ class Extension {
void* user_data;
};

struct Version {
int major{MINIFI_API_MAJOR_VERSION};
int minor{MINIFI_API_MINOR_VERSION};
int patch{MINIFI_API_PATCH_VERSION};
};

Extension(std::string name, std::filesystem::path library_path);

Extension(const Extension&) = delete;
Expand All @@ -49,6 +56,8 @@ class Extension {

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

Version version() const {return version_;}

private:
#ifdef WIN32
std::map<void*, std::string> resource_mapping_;
Expand All @@ -71,6 +80,7 @@ class Extension {
std::filesystem::path library_path_;
gsl::owner<void*> handle_ = nullptr;

Version version_{};
std::unique_ptr<Info> info_;

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 @@ -31,6 +31,8 @@ class ExtensionManager {
public:
explicit ExtensionManager(const std::shared_ptr<Configure>& config);

static Extension* getExtensionBeingInitialized();

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

Expand Down
26 changes: 0 additions & 26 deletions libminifi/include/core/extension/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,37 +44,11 @@ class Timer {
Callback cb_;
};

enum LibraryType {
Cpp,
CApi,
Invalid
};

struct LibraryDescriptor {
std::string name;
std::filesystem::path dir;
std::string filename;


[[nodiscard]] bool verify_as_cpp_extension() const;

[[nodiscard]] bool verify_as_c_extension(const std::shared_ptr<logging::Logger>& logger) const;

[[nodiscard]]
LibraryType verify(const std::shared_ptr<logging::Logger>& logger) const {
const auto path = getFullPath();
const Timer timer{[&](const std::chrono::milliseconds elapsed) {
logger->log_debug("Verification for '{}' took {}", path, elapsed);
}};
if (verify_as_cpp_extension()) {
return Cpp;
}
if (verify_as_c_extension(logger)) {
return CApi;
}
return Invalid;
}

[[nodiscard]]
std::filesystem::path getFullPath() const {
return dir / filename;
Expand Down
Loading
Loading