You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ma...@apache.org on 2026/03/11 07:48:04 UTC

(nifi-minifi-cpp) branch controller_c_api_requirements updated (2c10e0290 -> e83dbf2d7)

This is an automated email from the ASF dual-hosted git repository.

martinzink pushed a change to branch controller_c_api_requirements
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git


    omit 2c10e0290 MINIFICPP-2715 - Use symbols to check api compatibility
    omit 0e9b52a31 MINIFICPP-2710 Refactor ControllerServiceNodeMap
    omit fa362fa87 MINIFICPP-2669 - Reduce controller service api
     add d24d32400 MINIFICPP-2683 Move C2 docker tests to modular docker tests
     add e730451c9 MINIFICPP-2685 Move MQTT tests to modular docker tests
     add ebc89c186 MINIFICPP-2256 Remove LoggerConfiguration::newInstance
     add 3b1334649 MINIFICPP-2722 C API enum explicit type
     add 3190ca615 MINIFICPP-2724 Add MinifiStatus return type to C api function that might fail
     add b525cc861 MINIFICPP-2725 python: fix EL with ENVIRONMENT scope
     add e58d829b1 MINIFICPP-2728 upgrade openssl to 3.3.6
     add 71908d19e MINIFICPP-2727 Stabilize verify package tests
     add 7c18354a8 MINIFICPP-2729 Increase timeout for MacOS jobs
     add f856bb60f MINIFICPP-2730 Check if config.yml exists when creating the debug bundle
     add 30fa5d153 MINIFICPP-2726 Fix HTTP S2S no data and error handling
     add 162ccfb55 MINIFICPP-2732 Clear class descriptions when extensions are cleared
     add 4096b8637 MINIFICPP-2733 Fix compilation on Windows in debug mode with Ninja
     add 24933300e MINIFICPP-2734 Remove BinFiles from processor list
     add 2f26cb92f MINIFICPP-2723 Add log and properties.d dir property files to debug bundle
     add b1137d2b7 MINIFICPP-2736 Add checks of library unload result for class description removal
     add e2de4ca72 MINIFICPP-2686 Move Prometheus tests to modular docker tests
     add c460780a9 MINIFICPP-2688 Move site-to-site tests to modular docker tests
     add d39671da8 MINIFICPP-2687 Move Python extension docker tests to modular docker tests
     add 91481eff7 MINIFICPP-2716 Refactor and stabilize modular docker tests
     add 7ec2fd7c1 MINIFICPP-2714 Move Kubernetes to modular docker tests
     add 572832b7e MINIFICPP-2717 Remove old docker testing framework
     add 30c2434bd MINIFICPP-2739 Add test scenario info to behave logs
     add 30493bf09 MINIFICPP-2738 Suppress false positive warning on GCC 12
     add 1d764d7a7 MINIFICPP-2744 Fix ubuntu_22_04_clang_arm CI job
     add 022bd6ffd MINIFICPP-2745 Upgrade OPC UA library to v1.5.2
     add c81f8652f MINIFICPP-2669 - Reduce controller service api
     add 65c0fc4a5 Remove some virtual inhertiance fix linux build
     add 198dafc63 MINIFICPP-2669 - Clang tidy fix
     add 75cd1c54a MINIFICPP-2669 - Test fix
     add e4e42d7cc MINIFICPP-2669 - Remove invalid logger
     add d52c339b3 MINIFICPP-2669 - Formatting fix
     add 60661cd0f MINIFICPP-2669 - Rebase fix
     add 46851fc10 MINIFICPP-2669 - Remove ThreadManagementService, review changes
     add f438cd830 MINIFICPP-2669 - Fix build
     add d014310b2 MINIFICPP-2669 - Fix build
     add 0efd385ca Update core-framework/include/utils/ThreadPool.h
     add 87519ca34 MINIFICPP-2669 - Review changes
     add 89eadbe8c Update libminifi/test/libtest/unit/ControllerServiceUtils.h
     add 9b299b82a MINIFICPP-2669 - Rename to ControllerServiceHandle
     add 9daceaa70 MINIFICPP-2669 - Fix
     add 3a133e8fb MINIFICPP-2669 - Mark property deprecated
     add d34f998e7 MINIFICPP-2669 - Update docs
     add ecda194ec MINIFICPP-2710 Refactor ControllerServiceNodeMap
     add 97686cc27 review changes
     new a91703120 MINIFICPP-2715 - Use symbols to check api compatibility
     new 5983cf7fc MINIFICPP-2715 - Add missing symbol
     new 04c8c2fae MINIFICPP-2715 - Windows fix
     new b240279dc MINIFICPP-2715 - Move version information to MinifiCreateExtension
     new a8128b9f8 MINIFICPP-2715 - Use symbol lookup to check version
     new 85abf463c MINIFICPP-2715 - Add extension loading tests for version verification
     new e83dbf2d7 MINIFICPP-2715 - Use a single integer as c api version

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (2c10e0290)
            \
             N -- N -- N   refs/heads/controller_c_api_requirements (e83dbf2d7)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/ci.yml                           |  62 +-
 .github/workflows/verify-package.yml               |  74 +-
 .gitignore                                         |   1 +
 CMakeLists.txt                                     |   2 +-
 CONTROLLERS.md                                     |   2 +-
 Extensions.md                                      |  10 +-
 PROCESSORS.md                                      |  29 -
 README.md                                          |  20 +-
 .../minifi_test_framework/containers/container.py  |  29 +-
 .../containers/http_proxy_container.py             |   3 +-
 .../containers/minifi_container.py                 |  32 +-
 .../containers/nifi_container.py                   |  25 +-
 .../resources/nifi}/convert_cert_to_jks.sh         |   0
 .../src/minifi_test_framework/core/hooks.py        |  17 +-
 .../src/minifi_test_framework/minifi/connection.py |   1 +
 .../minifi/flow_definition.py                      |  60 ++
 .../minifi/{parameter.py => input_port.py}         |  29 +-
 .../minifi/minifi_flow_definition.py               |   9 +-
 .../minifi/nifi_flow_definition.py                 |  22 +-
 .../minifi/{parameter.py => output_port.py}        |  29 +-
 .../{controller_service.py => remote_port.py}      |  16 +-
 .../minifi/remote_process_group.py                 |  44 +
 .../minifi_test_framework/steps/checking_steps.py  |  75 +-
 .../steps/configuration_steps.py                   |  23 +-
 .../src/minifi_test_framework/steps/core_steps.py  | 102 ++-
 .../steps/flow_building_steps.py                   | 101 ++-
 cmake/BundledOpen62541.cmake                       |   6 +-
 cmake/BundledOpenSSL.cmake                         |  12 +-
 cmake/DockerConfig.cmake                           |  33 +-
 cmake/Extensions.cmake                             |   3 +-
 cmake/VerifyPackageWithDocker.cmake                |  31 +-
 .../core/controller/ControllerServiceBase.h        |  16 +-
 core-framework/include/http/HTTPClient.h           |   1 +
 core-framework/src/http/HTTPClient.cpp             |   5 +-
 docker/DockerVerify.sh                             | 195 -----
 docker/RunBehaveTests.sh                           |  19 +-
 docker/requirements.txt                            |   2 -
 docker/test/integration/.gitignore                 |   2 -
 docker/test/integration/__init__.py                |   0
 docker/test/integration/cluster/ContainerStore.py  | 311 -------
 .../test/integration/cluster/DockerCommunicator.py |  86 --
 .../test/integration/cluster/DockerTestCluster.py  | 283 ------
 .../cluster/DockerTestDirectoryBindings.py         | 221 -----
 docker/test/integration/cluster/FeatureContext.py  |  21 -
 docker/test/integration/cluster/ImageStore.py      | 361 --------
 docker/test/integration/cluster/KubernetesProxy.py | 161 ----
 docker/test/integration/cluster/LogSource.py       |  19 -
 docker/test/integration/cluster/__init__.py        |   0
 .../integration/cluster/checkers/AzureChecker.py   |  59 --
 .../integration/cluster/checkers/ModbusChecker.py  |  24 -
 .../cluster/checkers/PostgresChecker.py            |  27 -
 .../cluster/checkers/PrometheusChecker.py          | 136 ---
 .../test/integration/cluster/checkers/__init__.py  |   0
 .../containers/AzureStorageServerContainer.py      |  40 -
 .../integration/cluster/containers/Container.py    |  90 --
 .../cluster/containers/FlowContainer.py            |  63 --
 .../cluster/containers/HttpProxyContainer.py       |  39 -
 .../containers/MinifiAsPodInKubernetesCluster.py   |  73 --
 .../cluster/containers/MinifiC2ServerContainer.py  | 112 ---
 .../cluster/containers/MinifiContainer.py          | 244 ------
 .../cluster/containers/MqttBrokerContainer.py      |  40 -
 .../cluster/containers/NifiContainer.py            | 100 ---
 .../containers/PostgreSQLServerContainer.py        |  37 -
 .../cluster/containers/PrometheusContainer.py      | 100 ---
 .../cluster/containers/SyslogTcpClientContainer.py |  38 -
 .../cluster/containers/SyslogUdpClientContainer.py |  38 -
 .../cluster/containers/ZookeeperContainer.py       |  39 -
 .../integration/cluster/containers/__init__.py     |   0
 .../features/MiNiFi_integration_test_driver.py     | 399 ---------
 docker/test/integration/features/README.md         |  43 -
 docker/test/integration/features/environment.py    | 105 ---
 .../integration/features/python_script.feature     |  30 -
 docker/test/integration/features/steps/steps.py    | 953 ---------------------
 docker/test/integration/features/utils.py          |  92 --
 .../filesystem_validation/FileSystemObserver.py    |  69 --
 .../filesystem_validation/OutputEventHandler.py    |  64 --
 .../integration/filesystem_validation/__init__.py  |   0
 docker/test/integration/minifi/__init__.py         |   0
 .../minifi/controllers/JsonRecordSetWriter.py      |  24 -
 .../minifi/controllers/JsonTreeReader.py           |  23 -
 .../controllers/KubernetesControllerService.py     |  28 -
 .../integration/minifi/controllers/ODBCService.py  |  27 -
 .../minifi/controllers/SSLContextService.py        |  39 -
 .../integration/minifi/controllers/XMLReader.py    |  21 -
 .../minifi/controllers/XMLRecordSetWriter.py       |  23 -
 .../integration/minifi/controllers/__init__.py     |   0
 docker/test/integration/minifi/core/Connectable.py |  70 --
 .../integration/minifi/core/ControllerService.py   |  40 -
 docker/test/integration/minifi/core/Funnel.py      |  22 -
 docker/test/integration/minifi/core/InputPort.py   |  34 -
 docker/test/integration/minifi/core/OutputPort.py  |  34 -
 docker/test/integration/minifi/core/Processor.py   |  85 --
 .../integration/minifi/core/RemoteProcessGroup.py  |  40 -
 docker/test/integration/minifi/core/__init__.py    |   0
 .../Minifi_flow_json_serializer.py                 | 209 -----
 .../Minifi_flow_yaml_serializer.py                 | 244 ------
 .../Nifi_flow_json_serializer.py                   | 250 ------
 .../minifi/flow_serialization/__init__.py          |   0
 .../minifi/processors/AddPythonAttribute.py        |  22 -
 .../processors/AzureBlobStorageProcessorBase.py    |  33 -
 .../integration/minifi/processors/ChunkDocument.py |  26 -
 .../processors/CollectKubernetesPodMetrics.py      |  25 -
 .../integration/minifi/processors/ConsumeMQTT.py   |  30 -
 .../integration/minifi/processors/ConvertRecord.py |  23 -
 .../minifi/processors/CountingProcessor.py         |  24 -
 .../minifi/processors/CreateFlowFile.py            |  25 -
 .../integration/minifi/processors/CreateNothing.py |  25 -
 .../minifi/processors/DeleteAzureBlobStorage.py    |  25 -
 .../EvaluateExpressionLanguageChecker.py           |  26 -
 .../integration/minifi/processors/ExecuteSQL.py    |  26 -
 .../integration/minifi/processors/ExecuteScript.py |  25 -
 .../ExpressionLanguagePropertyWithValidator.py     |  26 -
 .../minifi/processors/FailureWithAttributes.py     |  26 -
 .../minifi/processors/FailureWithContent.py        |  26 -
 .../minifi/processors/FetchAzureBlobStorage.py     |  25 -
 .../integration/minifi/processors/FetchFile.py     |  26 -
 .../minifi/processors/FetchOPCProcessor.py         |  25 -
 .../processors/GaussianDistributionWithNumpy.py    |  24 -
 .../minifi/processors/GenerateFlowFile.py          |  26 -
 .../test/integration/minifi/processors/GetFile.py  |  29 -
 .../integration/minifi/processors/HashContent.py   |  27 -
 .../integration/minifi/processors/InvokeHTTP.py    |  46 -
 .../minifi/processors/ListAzureBlobStorage.py      |  27 -
 .../test/integration/minifi/processors/ListFile.py |  26 -
 .../integration/minifi/processors/ListenHTTP.py    |  33 -
 .../integration/minifi/processors/ListenSyslog.py  |  27 -
 .../integration/minifi/processors/LogAttribute.py  |  26 -
 .../minifi/processors/LogDynamicProperties.py      |  20 -
 .../minifi/processors/LogOnDestructionProcessor.py |  23 -
 .../minifi/processors/MoveContentToJson.py         |  24 -
 .../processors/NifiStyleLogDynamicProperties.py    |  25 -
 .../integration/minifi/processors/ParseDocument.py |  26 -
 .../processors/ProcessContextInterfaceChecker.py   |  26 -
 .../integration/minifi/processors/PublishMQTT.py   |  30 -
 .../minifi/processors/PutAzureBlobStorage.py       |  25 -
 .../test/integration/minifi/processors/PutFile.py  |  33 -
 .../minifi/processors/PutOPCProcessor.py           |  26 -
 .../test/integration/minifi/processors/PutSQL.py   |  26 -
 .../minifi/processors/QueryDatabaseTable.py        |  25 -
 .../minifi/processors/RelativeImporterProcessor.py |  26 -
 .../minifi/processors/RemoveFlowFile.py            |  24 -
 .../integration/minifi/processors/ReplaceText.py   |  27 -
 .../minifi/processors/RotatingForwarder.py         |  26 -
 .../minifi/processors/RouteOnAttribute.py          |  27 -
 .../integration/minifi/processors/RouteText.py     |  26 -
 .../minifi/processors/RunLlamaCppInference.py      |  26 -
 .../minifi/processors/SetRecordField.py            |  26 -
 .../processors/SpecialPropertyTypeChecker.py       |  26 -
 .../integration/minifi/processors/SplitText.py     |  24 -
 .../test/integration/minifi/processors/TailFile.py |  25 -
 .../minifi/processors/TestStateManager.py          |  25 -
 .../minifi/processors/TransferToOriginal.py        |  26 -
 .../minifi/processors/UpdateAttribute.py           |  26 -
 .../test/integration/minifi/processors/__init__.py |   0
 .../minifi/validators/EmptyFilesOutPutValidator.py |  44 -
 .../minifi/validators/FileOutputValidator.py       |  95 --
 .../minifi/validators/MultiFileOutputValidator.py  |  81 --
 .../NoContentCheckFileNumberValidator.py           |  38 -
 .../minifi/validators/NoFileOutPutValidator.py     |  31 -
 .../validators/NumFileRangeAndFileSizeValidator.py |  36 -
 .../minifi/validators/NumFileRangeValidator.py     |  38 -
 .../minifi/validators/OutputValidator.py           |  27 -
 .../minifi/validators/SegfaultValidator.py         |  25 -
 .../minifi/validators/SingleFileOutputValidator.py |  38 -
 .../validators/SingleJSONFileOutputValidator.py    |  55 --
 .../SingleOrMultiFileOutputRegexValidator.py       |  38 -
 .../validators/SingleOrMultiFileOutputValidator.py |  38 -
 .../test/integration/minifi/validators/__init__.py |   0
 .../kubernetes/minifi-conf/minifi-log.properties   |   3 -
 .../kubernetes/minifi-conf/minifi.properties       |  10 -
 .../resources/minifi/minifi-log.properties         |   4 -
 .../integration/resources/minifi/minifi.properties |   6 -
 .../integration/resources/python/sleep_forever.py  |  23 -
 .../test/integration/ssl_utils/SSL_cert_utils.py   | 161 ----
 docker/test/integration/ssl_utils/__init__.py      |   0
 .../include/api/core/ProcessContext.h              |   2 -
 .../include/api/utils/ProcessorConfigUtils.h       |  24 +-
 .../cpp-extension-lib/src/core/ProcessContext.cpp  |  10 -
 .../cpp-extension-lib/src/core/ProcessSession.cpp  |  22 +-
 .../include/controllers/AttributeProviderService.h |   2 +-
 .../include/controllers/RecordSetReader.h          |   2 +-
 .../include/controllers/RecordSetWriter.h          |   2 +-
 .../controllers/keyvalue/KeyValueStateStorage.h    |   4 +-
 .../include/utils/ExtensionInitUtils.h             |   4 +-
 .../include/utils/ProcessorConfigUtils.h           |  26 +-
 extensions/ExtensionInitializer.cpp                |   4 +-
 .../aws/controllerservices/AWSCredentialsService.h |   5 +-
 .../kinesis_server_container.py                    |   0
 .../{steps => containers}/s3_server_container.py   |   2 +-
 extensions/aws/tests/features/kinesis.feature      |   2 +-
 extensions/aws/tests/features/s3.feature           |  38 +-
 extensions/aws/tests/features/steps/steps.py       |  10 +-
 .../AzureStorageCredentialsService.h               |   5 +-
 .../azure/processors/AzureStorageProcessorBase.cpp |   2 +-
 .../azure/tests/FetchAzureDataLakeStorageTests.cpp |   2 +-
 .../azure_server_container.py                      |   0
 extensions/azure/tests/features/steps/steps.py     |   2 +-
 extensions/civetweb/tests/features/http.feature    | 125 +--
 extensions/civetweb/tests/features/https.feature   | 117 +--
 .../controllerservices/CouchbaseClusterService.h   |   5 +-
 .../couchbase/tests/MockCouchbaseClusterService.h  |   1 +
 .../couchbase_server_container.py                  |   6 +-
 extensions/couchbase/tests/features/steps/steps.py |   2 +-
 .../ElasticsearchCredentialsControllerService.h    |   5 +-
 .../elastic_base_container.py                      |   0
 .../elasticsearch_container.py                     |   0
 .../{steps => containers}/opensearch_container.py  |   0
 .../tests/features/elasticsearch.feature           |   6 +-
 .../tests/features/opensearch.feature              |   6 +-
 .../elasticsearch/tests/features/steps/steps.py    |   4 +-
 .../GCPCredentialsControllerService.h              |   5 +-
 .../fake_gcs_server_container.py                   |   0
 .../tests/features/google_cloud_storage.feature    |   2 +-
 extensions/gcp/tests/features/steps/steps.py       |   2 +-
 .../grafana_loki_container.py                      |   4 +-
 .../reverse_proxy_container.py                     |   0
 .../grafana-loki/tests/features/steps/steps.py     |   4 +-
 .../kafka_server_container.py                      |   0
 extensions/kafka/tests/features/steps/steps.py     |   2 +-
 .../KubernetesControllerService.h                  |   1 +
 .../tests/features/environment.py                  |  21 +-
 .../kubernetes/tests}/features/kubernetes.feature  |  30 +-
 .../tests/features/resources}/daemon.namespace.yml |   0
 .../resources}/hello-world-one.helper-pod.yml      |   0
 .../resources}/hello-world-two.helper-pod.yml      |   0
 .../resources}/metrics-server.dependencies.yml     |   0
 .../tests/features/resources}/minifi.test-pod.yml  |   6 +-
 .../resources}/namespace-reader.clusterrole.yml    |   0
 .../namespace-reader.clusterrolebinding.yml        |   0
 .../features/resources}/pod-reader.clusterrole.yml |   0
 .../resources}/pod-reader.clusterrolebinding.yml   |   0
 .../tests/features/steps/kubernetes_proxy.py       | 164 ++++
 .../steps/minifi_as_pod_in_kubernetes_cluster.py   |  35 +
 .../kubernetes/tests/features/steps/steps.py       |  59 ++
 extensions/libarchive/BinFiles.cpp                 |   2 -
 .../llamacpp/processors/ExtensionInitializer.cpp   |   8 +-
 .../llamacpp/tests/RunLlamaCppInferenceTests.cpp   |   4 +-
 extensions/mqtt/tests/ConsumeMQTTTests.cpp         |   4 +-
 extensions/mqtt/tests/PublishMQTTTests.cpp         |   2 +-
 .../features/containers/mqtt_broker_container.py   |  63 ++
 .../tests/features/environment.py                  |  21 +-
 .../mqtt/tests}/features/mqtt.feature              | 406 +++++----
 .../features/resources/publish_mqtt_message.py     |  20 +-
 extensions/mqtt/tests/features/steps/steps.py      |  76 ++
 extensions/opc/include/opc.h                       |   1 +
 extensions/opc/src/opc.cpp                         |  13 +-
 extensions/opc/tests/OpcUaTestServer.h             |   4 +-
 .../opc_ua_server_container.py                     |   6 +-
 extensions/opc/tests/features/opcua.feature        |  65 +-
 .../tests/features/resources/opcua_client_cert.der | Bin 1040 -> 0 bytes
 .../tests/features/resources/opcua_client_key.der  | Bin 1191 -> 0 bytes
 extensions/opc/tests/features/steps/steps.py       |  43 +-
 extensions/opencv/OpenCVLoader.cpp                 |   6 +-
 .../features/containers/prometheus_container.py    |  92 ++
 .../tests/features/environment.py                  |  22 +-
 .../prometheus/tests}/features/prometheus.feature  |  14 +-
 .../tests/features/resources/prometheus_checker.py | 156 ++++
 .../prometheus/tests/features/steps/steps.py       |  73 ++
 .../python/pythonlibloader/PythonLibLoader.cpp     |   4 +-
 extensions/python/pythonloader/PyProcLoader.cpp    |   4 +-
 .../python/pythonprocessors/nifiapi/properties.py  |   8 +-
 extensions/python/tests/features/environment.py    | 124 +++
 .../python/tests}/features/python.feature          | 181 ++--
 .../tests/features/resources}/CreateFlowFile.py    |   0
 .../tests/features/resources}/CreateNothing.py     |   0
 .../EvaluateExpressionLanguageChecker.py           |   0
 .../ExpressionLanguagePropertyWithValidator.py     |   0
 .../features/resources}/FailureWithAttributes.py   |   0
 .../features/resources}/FailureWithContent.py      |   0
 .../features/resources}/LogDynamicProperties.py    |   0
 .../resources}/NifiStyleLogDynamicProperties.py    |   0
 .../resources}/ProcessContextInterfaceChecker.py   |   0
 .../resources}/RelativeImporterProcessor.py        |   0
 .../tests/features/resources}/RotatingForwarder.py |   0
 .../tests/features/resources}/SetRecordField.py    |   0
 .../resources}/SpecialPropertyTypeChecker.py       |   0
 .../tests/features/resources}/TestStateManager.py  |   0
 .../features/resources}/TransferToOriginal.py      |   0
 .../tests/features/resources}/multiplierutils.py   |   0
 .../tests/features/resources}/subtractutils.py     |   0
 extensions/python/tests/features/steps/steps.py    | 122 +++
 .../controllers/RocksDbStateStorage.h              |   1 +
 extensions/sftp/SFTPLoader.cpp                     |   8 +-
 extensions/smb/SmbConnectionControllerService.h    |   5 +-
 .../utils/MockSmbConnectionControllerService.h     |   1 +
 .../{steps => containers}/splunk_container.py      |   0
 extensions/splunk/tests/features/splunk.feature    |   4 +-
 extensions/splunk/tests/features/steps/steps.py    |   2 +-
 extensions/sql/services/DatabaseService.h          |   4 +-
 extensions/sql/services/ODBCConnector.h            |   1 +
 .../postgress_server_container.py                  |   2 +-
 extensions/sql/tests/features/steps/steps.py       |   2 +-
 extensions/sql/tests/mocks/MockODBCService.h       |   1 +
 .../controllers/JsonRecordSetWriter.h              |   1 +
 .../controllers/JsonTreeReader.cpp                 |   8 +
 .../controllers/JsonTreeReader.h                   |   1 +
 .../controllers/PersistentMapStateStorage.h        |   1 +
 .../controllers/VolatileMapStateStorage.h          |   1 +
 .../standard-processors/controllers/XMLReader.h    |   1 +
 .../controllers/XMLRecordSetWriter.h               |   1 +
 .../tests/features/attributes_to_json.feature      |   2 +-
 .../{steps => containers}/diag_slave_container.py  |   2 +-
 .../{steps => containers}/syslog_container.py      |   0
 .../{steps => containers}/tcp_client_container.py  |   2 +-
 .../tests/features/core_functionality.feature      |   2 +-
 .../tests/features/environment.py                  |  21 +
 .../tests/features/evaluate_json_path.feature      |   4 +-
 .../tests/features/file_system_operations.feature  |   4 +-
 .../tests/features/hashcontent.feature             |  12 +-
 .../tests}/features/minifi_c2_server.feature       |  23 +-
 .../tests/features/minifi_controller.feature       |  12 +-
 .../tests/features/repository.feature              |   1 +
 .../tests}/features/s2s.feature                    | 292 +++----
 .../tests/features/split_json.feature              |   2 +-
 .../features/steps/minifi_c2_server_container.py   |  76 ++
 .../resources/minifi-c2-server/authorizations.yaml |   0
 .../resources/minifi-c2-server/c2.properties       |   0
 .../steps}/resources/minifi-c2-server/config.yml   |   0
 .../tests/features/steps/steps.py                  |  60 +-
 .../tests/unit/SegmentContentTests.cpp             |   4 +-
 .../tests/unit/SplitContentTests.cpp               |   2 +-
 .../tests/unit/SplitTextTests.cpp                  |   2 +-
 .../tests/unit/TailFileTests.cpp                   |   1 +
 .../controllers/NetworkPrioritizerService.h        |   7 +-
 libminifi/include/controllers/SSLContextService.h  |   3 +-
 .../controllers/UpdatePolicyControllerService.h    |   5 +-
 libminifi/include/core/ProcessContextImpl.h        |   2 +-
 .../include/core/controller/ControllerService.h    |   8 +-
 .../include/core/extension/ApiVersion.h            |  18 +-
 libminifi/include/core/extension/Extension.h       |  15 +-
 .../include/core/extension/ExtensionManager.h      |   6 +
 .../include/core/logging/LoggerConfiguration.h     |   9 +-
 libminifi/include/properties/Configure.h           |   3 +
 libminifi/include/properties/Properties.h          |   3 +-
 libminifi/src/Configure.cpp                        |  14 +
 libminifi/src/FlowController.cpp                   |  22 +-
 libminifi/src/RemoteProcessGroupPort.cpp           |   2 +-
 libminifi/src/agent/agent_docs.cpp                 |   5 +
 .../extension/ApiVersion.cpp}                      |  29 +-
 libminifi/src/core/extension/Extension.cpp         |  93 +-
 libminifi/src/core/extension/ExtensionManager.cpp  |   1 +
 libminifi/src/minifi-c.cpp                         |  46 +-
 libminifi/src/properties/Properties.cpp            |   1 +
 libminifi/src/sitetosite/HttpSiteToSiteClient.cpp  |  65 +-
 .../integration/C2ControllerEnableFailureTest.cpp  |   5 +-
 libminifi/test/integration/C2DebugBundleTest.cpp   |  63 +-
 libminifi/test/integration/CMakeLists.txt          |   2 +
 .../extension-verification-test/CApiExtension.cpp  |  26 +-
 .../extension-verification-test/CMakeLists.txt     |  81 ++
 .../CppApiExtension.cpp                            |  16 +-
 .../CreateNotCalled.cpp                            |  16 +-
 .../ExtensionVerificationTests.cpp                 |  91 ++
 .../InvalidMissingInitExtension.cpp                |   8 +-
 .../extension-verification-test/dummy-cpp-api.def  |   3 +
 .../test/libtest/integration/IntegrationBase.cpp   |   3 +-
 .../test/libtest/integration/IntegrationBase.h     |   9 +-
 .../test/libtest/unit/ControllerServiceUtils.h     |   4 +-
 libminifi/test/libtest/unit/MockClasses.h          |   7 +-
 libminifi/test/libtest/unit/TestBase.cpp           |   7 +-
 libminifi/test/unit/ComponentManifestTests.cpp     |   5 +-
 libminifi/test/unit/LoggerTests.cpp                |  84 +-
 libminifi/test/unit/ProcessorConfigUtilsTests.cpp  |   5 +-
 minifi-api/include/minifi-c/minifi-c.h             |  26 +-
 minifi-api/include/minifi-cpp/agent/agent_docs.h   |   1 +
 .../controllers/AttributeProviderService.h         |   4 +-
 .../minifi-cpp/controllers/RecordSetReader.h       |   4 +-
 .../minifi-cpp/controllers/RecordSetWriter.h       |   4 +-
 .../controllers/SSLContextServiceInterface.h       |   5 +-
 .../include/minifi-cpp/core/ProcessContext.h       |   4 +-
 .../core/controller/ControllerServiceApi.h         |   8 +-
 .../core/controller/ControllerServiceHandle.h      |  10 +-
 .../core/controller/ControllerServiceInterface.h   |  42 -
 .../include/minifi-cpp/properties/Configure.h      |   3 +
 .../include/minifi-cpp/properties/Properties.h     |   1 +
 minifi-api/minifi-c-api.def                        |   1 -
 thirdparty/open62541/open62541.patch               |  75 +-
 376 files changed, 3329 insertions(+), 9658 deletions(-)
 rename {docker/test/integration => behave_framework/src/minifi_test_framework/containers/resources/nifi}/convert_cert_to_jks.sh (100%)
 copy behave_framework/src/minifi_test_framework/minifi/{parameter.py => input_port.py} (55%)
 copy behave_framework/src/minifi_test_framework/minifi/{parameter.py => output_port.py} (55%)
 copy behave_framework/src/minifi_test_framework/minifi/{controller_service.py => remote_port.py} (74%)
 create mode 100644 behave_framework/src/minifi_test_framework/minifi/remote_process_group.py
 delete mode 100755 docker/DockerVerify.sh
 delete mode 100644 docker/test/integration/.gitignore
 delete mode 100644 docker/test/integration/__init__.py
 delete mode 100644 docker/test/integration/cluster/ContainerStore.py
 delete mode 100644 docker/test/integration/cluster/DockerCommunicator.py
 delete mode 100644 docker/test/integration/cluster/DockerTestCluster.py
 delete mode 100644 docker/test/integration/cluster/DockerTestDirectoryBindings.py
 delete mode 100644 docker/test/integration/cluster/FeatureContext.py
 delete mode 100644 docker/test/integration/cluster/ImageStore.py
 delete mode 100644 docker/test/integration/cluster/KubernetesProxy.py
 delete mode 100644 docker/test/integration/cluster/LogSource.py
 delete mode 100644 docker/test/integration/cluster/__init__.py
 delete mode 100644 docker/test/integration/cluster/checkers/AzureChecker.py
 delete mode 100644 docker/test/integration/cluster/checkers/ModbusChecker.py
 delete mode 100644 docker/test/integration/cluster/checkers/PostgresChecker.py
 delete mode 100644 docker/test/integration/cluster/checkers/PrometheusChecker.py
 delete mode 100644 docker/test/integration/cluster/checkers/__init__.py
 delete mode 100644 docker/test/integration/cluster/containers/AzureStorageServerContainer.py
 delete mode 100644 docker/test/integration/cluster/containers/Container.py
 delete mode 100644 docker/test/integration/cluster/containers/FlowContainer.py
 delete mode 100644 docker/test/integration/cluster/containers/HttpProxyContainer.py
 delete mode 100644 docker/test/integration/cluster/containers/MinifiAsPodInKubernetesCluster.py
 delete mode 100644 docker/test/integration/cluster/containers/MinifiC2ServerContainer.py
 delete mode 100644 docker/test/integration/cluster/containers/MinifiContainer.py
 delete mode 100644 docker/test/integration/cluster/containers/MqttBrokerContainer.py
 delete mode 100644 docker/test/integration/cluster/containers/NifiContainer.py
 delete mode 100644 docker/test/integration/cluster/containers/PostgreSQLServerContainer.py
 delete mode 100644 docker/test/integration/cluster/containers/PrometheusContainer.py
 delete mode 100644 docker/test/integration/cluster/containers/SyslogTcpClientContainer.py
 delete mode 100644 docker/test/integration/cluster/containers/SyslogUdpClientContainer.py
 delete mode 100644 docker/test/integration/cluster/containers/ZookeeperContainer.py
 delete mode 100644 docker/test/integration/cluster/containers/__init__.py
 delete mode 100644 docker/test/integration/features/MiNiFi_integration_test_driver.py
 delete mode 100644 docker/test/integration/features/README.md
 delete mode 100644 docker/test/integration/features/environment.py
 delete mode 100644 docker/test/integration/features/python_script.feature
 delete mode 100644 docker/test/integration/features/steps/steps.py
 delete mode 100644 docker/test/integration/features/utils.py
 delete mode 100644 docker/test/integration/filesystem_validation/FileSystemObserver.py
 delete mode 100644 docker/test/integration/filesystem_validation/OutputEventHandler.py
 delete mode 100644 docker/test/integration/filesystem_validation/__init__.py
 delete mode 100644 docker/test/integration/minifi/__init__.py
 delete mode 100644 docker/test/integration/minifi/controllers/JsonRecordSetWriter.py
 delete mode 100644 docker/test/integration/minifi/controllers/JsonTreeReader.py
 delete mode 100644 docker/test/integration/minifi/controllers/KubernetesControllerService.py
 delete mode 100644 docker/test/integration/minifi/controllers/ODBCService.py
 delete mode 100644 docker/test/integration/minifi/controllers/SSLContextService.py
 delete mode 100644 docker/test/integration/minifi/controllers/XMLReader.py
 delete mode 100644 docker/test/integration/minifi/controllers/XMLRecordSetWriter.py
 delete mode 100644 docker/test/integration/minifi/controllers/__init__.py
 delete mode 100644 docker/test/integration/minifi/core/Connectable.py
 delete mode 100644 docker/test/integration/minifi/core/ControllerService.py
 delete mode 100644 docker/test/integration/minifi/core/Funnel.py
 delete mode 100644 docker/test/integration/minifi/core/InputPort.py
 delete mode 100644 docker/test/integration/minifi/core/OutputPort.py
 delete mode 100644 docker/test/integration/minifi/core/Processor.py
 delete mode 100644 docker/test/integration/minifi/core/RemoteProcessGroup.py
 delete mode 100644 docker/test/integration/minifi/core/__init__.py
 delete mode 100644 docker/test/integration/minifi/flow_serialization/Minifi_flow_json_serializer.py
 delete mode 100644 docker/test/integration/minifi/flow_serialization/Minifi_flow_yaml_serializer.py
 delete mode 100644 docker/test/integration/minifi/flow_serialization/Nifi_flow_json_serializer.py
 delete mode 100644 docker/test/integration/minifi/flow_serialization/__init__.py
 delete mode 100644 docker/test/integration/minifi/processors/AddPythonAttribute.py
 delete mode 100644 docker/test/integration/minifi/processors/AzureBlobStorageProcessorBase.py
 delete mode 100644 docker/test/integration/minifi/processors/ChunkDocument.py
 delete mode 100644 docker/test/integration/minifi/processors/CollectKubernetesPodMetrics.py
 delete mode 100644 docker/test/integration/minifi/processors/ConsumeMQTT.py
 delete mode 100644 docker/test/integration/minifi/processors/ConvertRecord.py
 delete mode 100644 docker/test/integration/minifi/processors/CountingProcessor.py
 delete mode 100644 docker/test/integration/minifi/processors/CreateFlowFile.py
 delete mode 100644 docker/test/integration/minifi/processors/CreateNothing.py
 delete mode 100644 docker/test/integration/minifi/processors/DeleteAzureBlobStorage.py
 delete mode 100644 docker/test/integration/minifi/processors/EvaluateExpressionLanguageChecker.py
 delete mode 100644 docker/test/integration/minifi/processors/ExecuteSQL.py
 delete mode 100644 docker/test/integration/minifi/processors/ExecuteScript.py
 delete mode 100644 docker/test/integration/minifi/processors/ExpressionLanguagePropertyWithValidator.py
 delete mode 100644 docker/test/integration/minifi/processors/FailureWithAttributes.py
 delete mode 100644 docker/test/integration/minifi/processors/FailureWithContent.py
 delete mode 100644 docker/test/integration/minifi/processors/FetchAzureBlobStorage.py
 delete mode 100644 docker/test/integration/minifi/processors/FetchFile.py
 delete mode 100644 docker/test/integration/minifi/processors/FetchOPCProcessor.py
 delete mode 100644 docker/test/integration/minifi/processors/GaussianDistributionWithNumpy.py
 delete mode 100644 docker/test/integration/minifi/processors/GenerateFlowFile.py
 delete mode 100644 docker/test/integration/minifi/processors/GetFile.py
 delete mode 100644 docker/test/integration/minifi/processors/HashContent.py
 delete mode 100644 docker/test/integration/minifi/processors/InvokeHTTP.py
 delete mode 100644 docker/test/integration/minifi/processors/ListAzureBlobStorage.py
 delete mode 100644 docker/test/integration/minifi/processors/ListFile.py
 delete mode 100644 docker/test/integration/minifi/processors/ListenHTTP.py
 delete mode 100644 docker/test/integration/minifi/processors/ListenSyslog.py
 delete mode 100644 docker/test/integration/minifi/processors/LogAttribute.py
 delete mode 100644 docker/test/integration/minifi/processors/LogDynamicProperties.py
 delete mode 100644 docker/test/integration/minifi/processors/LogOnDestructionProcessor.py
 delete mode 100644 docker/test/integration/minifi/processors/MoveContentToJson.py
 delete mode 100644 docker/test/integration/minifi/processors/NifiStyleLogDynamicProperties.py
 delete mode 100644 docker/test/integration/minifi/processors/ParseDocument.py
 delete mode 100644 docker/test/integration/minifi/processors/ProcessContextInterfaceChecker.py
 delete mode 100644 docker/test/integration/minifi/processors/PublishMQTT.py
 delete mode 100644 docker/test/integration/minifi/processors/PutAzureBlobStorage.py
 delete mode 100644 docker/test/integration/minifi/processors/PutFile.py
 delete mode 100644 docker/test/integration/minifi/processors/PutOPCProcessor.py
 delete mode 100644 docker/test/integration/minifi/processors/PutSQL.py
 delete mode 100644 docker/test/integration/minifi/processors/QueryDatabaseTable.py
 delete mode 100644 docker/test/integration/minifi/processors/RelativeImporterProcessor.py
 delete mode 100644 docker/test/integration/minifi/processors/RemoveFlowFile.py
 delete mode 100644 docker/test/integration/minifi/processors/ReplaceText.py
 delete mode 100644 docker/test/integration/minifi/processors/RotatingForwarder.py
 delete mode 100644 docker/test/integration/minifi/processors/RouteOnAttribute.py
 delete mode 100644 docker/test/integration/minifi/processors/RouteText.py
 delete mode 100644 docker/test/integration/minifi/processors/RunLlamaCppInference.py
 delete mode 100644 docker/test/integration/minifi/processors/SetRecordField.py
 delete mode 100644 docker/test/integration/minifi/processors/SpecialPropertyTypeChecker.py
 delete mode 100644 docker/test/integration/minifi/processors/SplitText.py
 delete mode 100644 docker/test/integration/minifi/processors/TailFile.py
 delete mode 100644 docker/test/integration/minifi/processors/TestStateManager.py
 delete mode 100644 docker/test/integration/minifi/processors/TransferToOriginal.py
 delete mode 100644 docker/test/integration/minifi/processors/UpdateAttribute.py
 delete mode 100644 docker/test/integration/minifi/processors/__init__.py
 delete mode 100644 docker/test/integration/minifi/validators/EmptyFilesOutPutValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/FileOutputValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/MultiFileOutputValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/NoContentCheckFileNumberValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/NoFileOutPutValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/NumFileRangeAndFileSizeValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/NumFileRangeValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/OutputValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/SegfaultValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/SingleFileOutputValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/SingleJSONFileOutputValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/SingleOrMultiFileOutputRegexValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/SingleOrMultiFileOutputValidator.py
 delete mode 100644 docker/test/integration/minifi/validators/__init__.py
 delete mode 100644 docker/test/integration/resources/kubernetes/minifi-conf/minifi-log.properties
 delete mode 100644 docker/test/integration/resources/kubernetes/minifi-conf/minifi.properties
 delete mode 100644 docker/test/integration/resources/minifi/minifi-log.properties
 delete mode 100644 docker/test/integration/resources/minifi/minifi.properties
 delete mode 100644 docker/test/integration/resources/python/sleep_forever.py
 delete mode 100644 docker/test/integration/ssl_utils/SSL_cert_utils.py
 delete mode 100644 docker/test/integration/ssl_utils/__init__.py
 rename extensions/aws/tests/features/{steps => containers}/kinesis_server_container.py (100%)
 rename extensions/aws/tests/features/{steps => containers}/s3_server_container.py (99%)
 rename extensions/azure/tests/features/{steps => containers}/azure_server_container.py (100%)
 rename extensions/couchbase/tests/features/{steps => containers}/couchbase_server_container.py (98%)
 rename extensions/elasticsearch/tests/features/{steps => containers}/elastic_base_container.py (100%)
 rename extensions/elasticsearch/tests/features/{steps => containers}/elasticsearch_container.py (100%)
 rename extensions/elasticsearch/tests/features/{steps => containers}/opensearch_container.py (100%)
 rename extensions/gcp/tests/features/{steps => containers}/fake_gcs_server_container.py (100%)
 rename extensions/grafana-loki/tests/features/{steps => containers}/grafana_loki_container.py (98%)
 rename extensions/grafana-loki/tests/features/{steps => containers}/reverse_proxy_container.py (100%)
 rename extensions/kafka/tests/features/{steps => containers}/kafka_server_container.py (100%)
 copy extensions/{splunk => kubernetes}/tests/features/environment.py (64%)
 rename {docker/test/integration => extensions/kubernetes/tests}/features/kubernetes.feature (76%)
 rename {docker/test/integration/resources/kubernetes/pods-etc => extensions/kubernetes/tests/features/resources}/daemon.namespace.yml (100%)
 rename {docker/test/integration/resources/kubernetes/pods-etc => extensions/kubernetes/tests/features/resources}/hello-world-one.helper-pod.yml (100%)
 rename {docker/test/integration/resources/kubernetes/pods-etc => extensions/kubernetes/tests/features/resources}/hello-world-two.helper-pod.yml (100%)
 rename {docker/test/integration/resources/kubernetes/pods-etc => extensions/kubernetes/tests/features/resources}/metrics-server.dependencies.yml (100%)
 rename {docker/test/integration/resources/kubernetes/pods-etc => extensions/kubernetes/tests/features/resources}/minifi.test-pod.yml (87%)
 rename {docker/test/integration/resources/kubernetes/pods-etc => extensions/kubernetes/tests/features/resources}/namespace-reader.clusterrole.yml (100%)
 rename {docker/test/integration/resources/kubernetes/pods-etc => extensions/kubernetes/tests/features/resources}/namespace-reader.clusterrolebinding.yml (100%)
 rename {docker/test/integration/resources/kubernetes/pods-etc => extensions/kubernetes/tests/features/resources}/pod-reader.clusterrole.yml (100%)
 rename {docker/test/integration/resources/kubernetes/pods-etc => extensions/kubernetes/tests/features/resources}/pod-reader.clusterrolebinding.yml (100%)
 create mode 100644 extensions/kubernetes/tests/features/steps/kubernetes_proxy.py
 create mode 100644 extensions/kubernetes/tests/features/steps/minifi_as_pod_in_kubernetes_cluster.py
 create mode 100644 extensions/kubernetes/tests/features/steps/steps.py
 create mode 100644 extensions/mqtt/tests/features/containers/mqtt_broker_container.py
 copy extensions/{grafana-loki => mqtt}/tests/features/environment.py (71%)
 rename {docker/test/integration => extensions/mqtt/tests}/features/mqtt.feature (59%)
 rename docker/test/integration/cluster/checkers/MqttHelper.py => extensions/mqtt/tests/features/resources/publish_mqtt_message.py (62%)
 create mode 100644 extensions/mqtt/tests/features/steps/steps.py
 rename extensions/opc/tests/features/{steps => containers}/opc_ua_server_container.py (87%)
 delete mode 100644 extensions/opc/tests/features/resources/opcua_client_cert.der
 delete mode 100644 extensions/opc/tests/features/resources/opcua_client_key.der
 create mode 100644 extensions/prometheus/tests/features/containers/prometheus_container.py
 copy extensions/{grafana-loki => prometheus}/tests/features/environment.py (70%)
 rename {docker/test/integration => extensions/prometheus/tests}/features/prometheus.feature (91%)
 create mode 100644 extensions/prometheus/tests/features/resources/prometheus_checker.py
 create mode 100644 extensions/prometheus/tests/features/steps/steps.py
 create mode 100644 extensions/python/tests/features/environment.py
 rename {docker/test/integration => extensions/python/tests}/features/python.feature (62%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/CreateFlowFile.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/CreateNothing.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/EvaluateExpressionLanguageChecker.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/ExpressionLanguagePropertyWithValidator.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/FailureWithAttributes.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/FailureWithContent.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/LogDynamicProperties.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/NifiStyleLogDynamicProperties.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/ProcessContextInterfaceChecker.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/RelativeImporterProcessor.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/RotatingForwarder.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/SetRecordField.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/SpecialPropertyTypeChecker.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/TestStateManager.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/TransferToOriginal.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/multiplierutils.py (100%)
 rename {docker/test/integration/resources/python => extensions/python/tests/features/resources}/subtractutils.py (100%)
 create mode 100644 extensions/python/tests/features/steps/steps.py
 rename extensions/splunk/tests/features/{steps => containers}/splunk_container.py (100%)
 rename extensions/sql/tests/features/{steps => containers}/postgress_server_container.py (99%)
 rename extensions/standard-processors/tests/features/{steps => containers}/diag_slave_container.py (98%)
 rename extensions/standard-processors/tests/features/{steps => containers}/syslog_container.py (100%)
 rename extensions/standard-processors/tests/features/{steps => containers}/tcp_client_container.py (98%)
 rename {docker/test/integration => extensions/standard-processors/tests}/features/minifi_c2_server.feature (75%)
 rename {docker/test/integration => extensions/standard-processors/tests}/features/s2s.feature (50%)
 create mode 100644 extensions/standard-processors/tests/features/steps/minifi_c2_server_container.py
 rename {docker/test/integration => extensions/standard-processors/tests/features/steps}/resources/minifi-c2-server/authorizations.yaml (100%)
 rename {docker/test/integration => extensions/standard-processors/tests/features/steps}/resources/minifi-c2-server/c2.properties (100%)
 rename {docker/test/integration => extensions/standard-processors/tests/features/steps}/resources/minifi-c2-server/config.yml (100%)
 copy extension-framework/cpp-extension-lib/include/api/core/PublishedMetrics.h => libminifi/include/core/extension/ApiVersion.h (72%)
 copy libminifi/src/{utils/AttributeErrors.cpp => core/extension/ApiVersion.cpp} (55%)
 copy extensions/ExtensionInitializer.cpp => libminifi/test/integration/extension-verification-test/CApiExtension.cpp (63%)
 create mode 100644 libminifi/test/integration/extension-verification-test/CMakeLists.txt
 copy extensions/ExtensionInitializer.cpp => libminifi/test/integration/extension-verification-test/CppApiExtension.cpp (76%)
 copy extension-framework/cpp-extension-lib/include/api/utils/Export.h => libminifi/test/integration/extension-verification-test/CreateNotCalled.cpp (79%)
 create mode 100644 libminifi/test/integration/extension-verification-test/ExtensionVerificationTests.cpp
 copy core-framework/src/io/CRCStream.cpp => libminifi/test/integration/extension-verification-test/InvalidMissingInitExtension.cpp (91%)
 create mode 100644 libminifi/test/integration/extension-verification-test/dummy-cpp-api.def
 copy extensions/systemd/Common.h => minifi-api/include/minifi-cpp/core/controller/ControllerServiceHandle.h (79%)
 delete mode 100644 minifi-api/include/minifi-cpp/core/controller/ControllerServiceInterface.h


(nifi-minifi-cpp) 02/07: MINIFICPP-2715 - Add missing symbol

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

martinzink pushed a commit to branch controller_c_api_requirements
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit 5983cf7fccc90dbbcb8cd16ae83a1966ec7b240f
Author: Adam Debreceni <ad...@apache.org>
AuthorDate: Wed Feb 11 14:41:38 2026 +0100

    MINIFICPP-2715 - Add missing symbol
---
 minifi-api/minifi-c-api.def | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/minifi-api/minifi-c-api.def b/minifi-api/minifi-c-api.def
index ee225e7c1..ee763264d 100644
--- a/minifi-api/minifi-c-api.def
+++ b/minifi-api/minifi-c-api.def
@@ -21,4 +21,5 @@ EXPORTS
   MinifiStatusToString
   MinifiFlowFileSetAttribute
   MinifiFlowFileGetAttribute
-  MinifiFlowFileGetAttributes
\ No newline at end of file
+  MinifiFlowFileGetAttributes
+  MINIFI_C_API_VERSION_0_1


(nifi-minifi-cpp) 04/07: MINIFICPP-2715 - Move version information to MinifiCreateExtension

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

martinzink pushed a commit to branch controller_c_api_requirements
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit b240279dc43ee0bca72b419b330270c0b31c2c0a
Author: Adam Debreceni <ad...@apache.org>
AuthorDate: Thu Feb 12 14:45:15 2026 +0100

    MINIFICPP-2715 - Move version information to MinifiCreateExtension
---
 extensions/ExtensionInitializer.cpp                |  2 +-
 .../llamacpp/processors/ExtensionInitializer.cpp   |  2 +-
 extensions/opencv/OpenCVLoader.cpp                 |  2 +-
 .../python/pythonlibloader/PythonLibLoader.cpp     |  2 +-
 extensions/python/pythonloader/PyProcLoader.cpp    |  2 +-
 extensions/sftp/SFTPLoader.cpp                     |  2 +-
 libminifi/src/ApiVersions.cpp                      | 34 -------------
 libminifi/src/minifi-c.cpp                         | 55 +++++++++++++---------
 minifi-api/CMakeLists.txt                          |  2 +-
 minifi-api/include/minifi-c/minifi-c.h             | 20 ++++----
 minifi-api/minifi-c-api.def                        |  3 +-
 11 files changed, 52 insertions(+), 74 deletions(-)

diff --git a/extensions/ExtensionInitializer.cpp b/extensions/ExtensionInitializer.cpp
index 4044e15c2..3caaaeed0 100644
--- a/extensions/ExtensionInitializer.cpp
+++ b/extensions/ExtensionInitializer.cpp
@@ -31,5 +31,5 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return MinifiCreateExtension(MINIFI_API_VERSION, &ext_create_info);
+  return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
 }
diff --git a/extensions/llamacpp/processors/ExtensionInitializer.cpp b/extensions/llamacpp/processors/ExtensionInitializer.cpp
index 18f5ff58e..b90b8b133 100644
--- a/extensions/llamacpp/processors/ExtensionInitializer.cpp
+++ b/extensions/llamacpp/processors/ExtensionInitializer.cpp
@@ -35,7 +35,7 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
       .processors_count = 1,
       .processors_ptr = &description,
     };
-    extension = MinifiCreateExtension(MINIFI_API_VERSION, &ext_create_info);
+    extension = MinifiCreateExtension(minifi::api::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
   });
   return extension;
 }
diff --git a/extensions/opencv/OpenCVLoader.cpp b/extensions/opencv/OpenCVLoader.cpp
index 83e75bdbd..3f91395c4 100644
--- a/extensions/opencv/OpenCVLoader.cpp
+++ b/extensions/opencv/OpenCVLoader.cpp
@@ -42,5 +42,5 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return MinifiCreateExtension(MINIFI_API_VERSION, &ext_create_info);
+  return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
 }
diff --git a/extensions/python/pythonlibloader/PythonLibLoader.cpp b/extensions/python/pythonlibloader/PythonLibLoader.cpp
index 522c43633..cdf0356b7 100644
--- a/extensions/python/pythonlibloader/PythonLibLoader.cpp
+++ b/extensions/python/pythonlibloader/PythonLibLoader.cpp
@@ -114,5 +114,5 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* config) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return MinifiCreateExtension(MINIFI_API_VERSION, &ext_create_info);
+  return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
 }
diff --git a/extensions/python/pythonloader/PyProcLoader.cpp b/extensions/python/pythonloader/PyProcLoader.cpp
index f283847ff..55ad4ebef 100644
--- a/extensions/python/pythonloader/PyProcLoader.cpp
+++ b/extensions/python/pythonloader/PyProcLoader.cpp
@@ -49,5 +49,5 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* config) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return MinifiCreateExtension(MINIFI_API_VERSION, &ext_create_info);
+  return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
 }
diff --git a/extensions/sftp/SFTPLoader.cpp b/extensions/sftp/SFTPLoader.cpp
index 3cf12aedd..bb23e9998 100644
--- a/extensions/sftp/SFTPLoader.cpp
+++ b/extensions/sftp/SFTPLoader.cpp
@@ -44,5 +44,5 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return MinifiCreateExtension(MINIFI_API_VERSION, &ext_create_info);
+  return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
 }
diff --git a/libminifi/src/ApiVersions.cpp b/libminifi/src/ApiVersions.cpp
deleted file mode 100644
index 76364c0f0..000000000
--- a/libminifi/src/ApiVersions.cpp
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
-* 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 "minifi-cpp/utils/Export.h"
-
-extern "C" {
-
-const MinifiApiVersion* MINIFI_API_VERSION_FN() {
-  return reinterpret_cast<const MinifiApiVersion*>(MINIFI_PRIVATE_STRINGIFY(MINIFI_API_VERSION_FN));
-}
-
-#define REGISTER_C_API_VERSION(major, minor)  \
-    const MinifiApiVersion* MINIFI_PRIVATE_JOIN(MinifiCApiVersion, MINIFI_PRIVATE_JOIN(major, minor))() {  \
-      return reinterpret_cast<const MinifiApiVersion*>(MINIFI_PRIVATE_STRINGIFY(MINIFI_C_API_MAJOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_C_API_MINOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_C_API_PATCH_VERSION));  \
-    }
-
-REGISTER_C_API_VERSION(0, 1)
-
-}  // extern "C"
diff --git a/libminifi/src/minifi-c.cpp b/libminifi/src/minifi-c.cpp
index 2abff81d0..0e4144c33 100644
--- a/libminifi/src/minifi-c.cpp
+++ b/libminifi/src/minifi-c.cpp
@@ -146,6 +146,30 @@ class CProcessorFactory : public minifi::core::ProcessorFactory {
   minifi::utils::CProcessorClassDescription class_description_;
 };
 
+MinifiExtension* MinifiCreateExtensionImpl(MinifiStringView /*api_version*/, const MinifiExtensionCreateInfo* extension_create_info) {
+  gsl_Assert(extension_create_info);
+  auto extension_name = toString(extension_create_info->name);
+  minifi::BundleIdentifier bundle{
+    .name = extension_name,
+    .version = toString(extension_create_info->version)
+  };
+  auto& bundle_components = minifi::ClassDescriptionRegistry::getMutableClassDescriptions()[bundle];
+  for (size_t proc_idx = 0; proc_idx < extension_create_info->processors_count; ++proc_idx) {
+    minifi::utils::useCProcessorClassDescription(extension_create_info->processors_ptr[proc_idx], [&] (const auto& description, const auto& c_class_description) {
+      minifi::core::ClassLoader::getDefaultClassLoader().getClassLoader(extension_name).registerClass(
+        c_class_description.name,
+        std::make_unique<CProcessorFactory>(extension_name, toString(extension_create_info->processors_ptr[proc_idx].full_name), c_class_description));
+      bundle_components.processors.emplace_back(description);
+    });
+  }
+  return reinterpret_cast<MinifiExtension*>(new org::apache::nifi::minifi::core::extension::Extension::Info{
+    .name = toString(extension_create_info->name),
+    .version = toString(extension_create_info->version),
+    .deinit = extension_create_info->deinit,
+    .user_data = extension_create_info->user_data
+  });
+}
+
 }  // namespace
 
 namespace org::apache::nifi::minifi::utils {
@@ -225,30 +249,17 @@ void useCProcessorClassDescription(const MinifiProcessorClassDefinition& class_d
 
 extern "C" {
 
-MinifiExtension* MinifiCreateExtension(const MinifiApiVersion* /*api_version*/, const MinifiExtensionCreateInfo* extension_create_info) {
-  gsl_Assert(extension_create_info);
-  auto extension_name = toString(extension_create_info->name);
-  minifi::BundleIdentifier bundle{
-    .name = extension_name,
-    .version = toString(extension_create_info->version)
-  };
-  auto& bundle_components = minifi::ClassDescriptionRegistry::getMutableClassDescriptions()[bundle];
-  for (size_t proc_idx = 0; proc_idx < extension_create_info->processors_count; ++proc_idx) {
-    minifi::utils::useCProcessorClassDescription(extension_create_info->processors_ptr[proc_idx], [&] (const auto& description, const auto& c_class_description) {
-      minifi::core::ClassLoader::getDefaultClassLoader().getClassLoader(extension_name).registerClass(
-        c_class_description.name,
-        std::make_unique<CProcessorFactory>(extension_name, toString(extension_create_info->processors_ptr[proc_idx].full_name), c_class_description));
-      bundle_components.processors.emplace_back(description);
-    });
-  }
-  return reinterpret_cast<MinifiExtension*>(new org::apache::nifi::minifi::core::extension::Extension::Info{
-    .name = toString(extension_create_info->name),
-    .version = toString(extension_create_info->version),
-    .deinit = extension_create_info->deinit,
-    .user_data = extension_create_info->user_data
-  });
+MinifiExtension* MINIFI_CREATE_EXTENSION_FN(MinifiStringView api_version, const MinifiExtensionCreateInfo* extension_create_info) {
+  return MinifiCreateExtensionImpl(api_version, extension_create_info);
 }
 
+#define REGISTER_C_API_VERSION(major, minor)  \
+  MinifiExtension* MINIFI_PRIVATE_JOIN(MinifiCreateExtension, MINIFI_PRIVATE_JOIN(major, minor))(MinifiStringView api_version, const MinifiExtensionCreateInfo* extension_create_info) {  \
+    return MinifiCreateExtensionImpl(api_version, extension_create_info);  \
+  }
+
+REGISTER_C_API_VERSION(0, 1)
+
 MinifiStatus MinifiProcessContextGetProperty(MinifiProcessContext* context, MinifiStringView property_name, MinifiFlowFile* flowfile,
     void (*result_cb)(void* user_ctx, MinifiStringView result), void* user_ctx) {
   gsl_Assert(context != MINIFI_NULL);
diff --git a/minifi-api/CMakeLists.txt b/minifi-api/CMakeLists.txt
index dfef95bc7..05f78fc99 100644
--- a/minifi-api/CMakeLists.txt
+++ b/minifi-api/CMakeLists.txt
@@ -6,7 +6,7 @@ target_compile_definitions(minifi-api-common INTERFACE MINIFI_VERSION_STR="${MIN
 add_library(minifi-api INTERFACE)
 target_include_directories(minifi-api INTERFACE include)
 target_link_libraries(minifi-api INTERFACE minifi-api-common)
-target_compile_definitions(minifi-api INTERFACE MINIFI_API_VERSION_FN=MinifiCppApiVersion_${BUILD_IDENTIFIER})
+target_compile_definitions(minifi-api INTERFACE MINIFI_CREATE_EXTENSION_FN=MinifiCreateExtension_${BUILD_IDENTIFIER})
 
 add_library(minifi-c-api INTERFACE)
 target_include_directories(minifi-c-api INTERFACE include/minifi-c)
diff --git a/minifi-api/include/minifi-c/minifi-c.h b/minifi-api/include/minifi-c/minifi-c.h
index d1f45ea28..d246b773b 100644
--- a/minifi-api/include/minifi-c/minifi-c.h
+++ b/minifi-api/include/minifi-c/minifi-c.h
@@ -34,14 +34,14 @@ extern "C" {
 #define MINIFI_PRIVATE_JOIN_HELPER(X, Y) X ## _ ## Y
 #define MINIFI_PRIVATE_JOIN(X, Y) MINIFI_PRIVATE_JOIN_HELPER(X, Y)
 
-#define MINIFI_C_API_MAJOR_VERSION 0
-#define MINIFI_C_API_MINOR_VERSION 1
-#define MINIFI_C_API_PATCH_VERSION 0
+#define MINIFI_API_MAJOR_VERSION 0
+#define MINIFI_API_MINOR_VERSION 1
+#define MINIFI_API_PATCH_VERSION 0
+#define MINIFI_API_VERSION MINIFI_PRIVATE_STRINGIFY(MINIFI_API_MAJOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_API_MINOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_API_PATCH_VERSION)
 
-#ifndef MINIFI_API_VERSION_FN
-#define MINIFI_API_VERSION_FN MINIFI_PRIVATE_JOIN(MinifiCApiVersion, MINIFI_PRIVATE_JOIN(MINIFI_C_API_MAJOR_VERSION, MINIFI_C_API_MINOR_VERSION))
+#ifndef MINIFI_CREATE_EXTENSION_FN
+#define MINIFI_CREATE_EXTENSION_FN MINIFI_PRIVATE_JOIN(MinifiCreateExtension, MINIFI_PRIVATE_JOIN(MINIFI_API_MAJOR_VERSION, MINIFI_API_MINOR_VERSION))
 #endif
-#define MINIFI_API_VERSION MINIFI_API_VERSION_FN()
 
 #define MINIFI_NULL nullptr
 #define MINIFI_OWNED
@@ -189,12 +189,10 @@ typedef struct MinifiExtensionCreateInfo {
   const MinifiProcessorClassDefinition* processors_ptr;
 } MinifiExtensionCreateInfo;
 
-const MinifiApiVersion* MINIFI_API_VERSION_FN();
-
 // api_version is used to provide backwards compatible changes to the MinifiExtensionCreateInfo structure,
 // e.g. if MinifiExtensionCreateInfo gets a new field in version 1.2.0, extensions built with api 1.1.0 do not
 // have to be rebuilt
-MinifiExtension* MinifiCreateExtension(const MinifiApiVersion* api_version, const MinifiExtensionCreateInfo*);
+MinifiExtension* MINIFI_CREATE_EXTENSION_FN(MinifiStringView api_version, const MinifiExtensionCreateInfo* create_info);
 
 MINIFI_OWNED MinifiPublishedMetrics* MinifiPublishedMetricsCreate(size_t count, const MinifiStringView* metric_names, const double* metric_values);
 
@@ -234,4 +232,8 @@ void MinifiFlowFileGetAttributes(MinifiProcessSession* session, MinifiFlowFile*
 }  // extern "C"
 #endif  // __cplusplus
 
+static inline MinifiExtension* MinifiCreateExtension(MinifiStringView api_version, const MinifiExtensionCreateInfo* create_info) {
+  return MINIFI_CREATE_EXTENSION_FN(api_version, create_info);
+}
+
 #endif  // MINIFI_API_INCLUDE_MINIFI_C_MINIFI_C_H_
diff --git a/minifi-api/minifi-c-api.def b/minifi-api/minifi-c-api.def
index 1e9f66ecd..a664bdd2d 100644
--- a/minifi-api/minifi-c-api.def
+++ b/minifi-api/minifi-c-api.def
@@ -1,6 +1,6 @@
 LIBRARY core-minifi.dll
 EXPORTS
-  MinifiCreateExtension
+  MinifiCreateExtension_0_1
   MinifiPublishedMetricsCreate
   MinifiProcessContextGetProperty
   MinifiProcessContextHasNonEmptyProperty
@@ -22,4 +22,3 @@ EXPORTS
   MinifiFlowFileSetAttribute
   MinifiFlowFileGetAttribute
   MinifiFlowFileGetAttributes
-  MinifiCApiVersion_0_1


(nifi-minifi-cpp) 06/07: MINIFICPP-2715 - Add extension loading tests for version verification

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

martinzink pushed a commit to branch controller_c_api_requirements
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit 85abf463c18c72c0b736fc24a6a2323fb6745985
Author: Adam Debreceni <ad...@apache.org>
AuthorDate: Mon Mar 2 14:03:04 2026 +0100

    MINIFICPP-2715 - Add extension loading tests for version verification
---
 Extensions.md                                      |  2 +-
 cmake/Extensions.cmake                             |  3 +-
 extensions/python/pythonloader/PyProcLoader.cpp    |  2 +-
 libminifi/include/core/extension/ApiVersion.h      | 37 +++++++++
 libminifi/include/core/extension/Extension.h       | 11 +--
 libminifi/src/core/extension/ApiVersion.cpp        | 33 ++++++++
 libminifi/src/core/extension/Extension.cpp         | 20 ++---
 libminifi/test/integration/CMakeLists.txt          |  2 +
 .../extension-verification-test/CApiExtension.cpp  | 39 ++++++++++
 .../extension-verification-test/CMakeLists.txt     | 79 +++++++++++++++++++
 .../CppApiExtension.cpp                            | 34 +++-----
 .../ExtensionVerificationTests.cpp                 | 90 ++++++++++++++++++++++
 .../InvalidMissingInitExtension.cpp                | 17 ++++
 .../extension-verification-test/dummy-cpp-api.def  |  3 +
 14 files changed, 326 insertions(+), 46 deletions(-)

diff --git a/Extensions.md b/Extensions.md
index 0bfbd5bd9..159232f9a 100644
--- a/Extensions.md
+++ b/Extensions.md
@@ -82,7 +82,7 @@ extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return MinifiCreateCppExtension(&ext_create_info);
+  return minifi::utils::MinifiCreateCppExtension(&ext_create_info);
 }
 ```
 
diff --git a/cmake/Extensions.cmake b/cmake/Extensions.cmake
index 6ea93f1cb..6e25d288b 100644
--- a/cmake/Extensions.cmake
+++ b/cmake/Extensions.cmake
@@ -31,8 +31,7 @@ macro(register_extension extension-name extension-display-name extension-guard d
         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)
diff --git a/extensions/python/pythonloader/PyProcLoader.cpp b/extensions/python/pythonloader/PyProcLoader.cpp
index cd9cbb998..d4edf9cf2 100644
--- a/extensions/python/pythonloader/PyProcLoader.cpp
+++ b/extensions/python/pythonloader/PyProcLoader.cpp
@@ -49,5 +49,5 @@ extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* config) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return minifi::utils::MinifiCreateCppExtension( &ext_create_info);
+  return minifi::utils::MinifiCreateCppExtension(&ext_create_info);
 }
diff --git a/libminifi/include/core/extension/ApiVersion.h b/libminifi/include/core/extension/ApiVersion.h
new file mode 100644
index 000000000..01acb82cb
--- /dev/null
+++ b/libminifi/include/core/extension/ApiVersion.h
@@ -0,0 +1,37 @@
+/**
+* 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>
+
+namespace org::apache::nifi::minifi::core::extension {
+
+struct ApiVersion {
+  int major;
+  int minor;
+  int patch;
+
+  std::string str() const {
+    return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch);
+  }
+};
+
+ApiVersion getAgentApiVersion();
+void setAgentApiVersion(ApiVersion api_version);
+
+}  // namespace org::apache::nifi::minifi::core::extension
diff --git a/libminifi/include/core/extension/Extension.h b/libminifi/include/core/extension/Extension.h
index 2a71a6fbc..356efed4c 100644
--- a/libminifi/include/core/extension/Extension.h
+++ b/libminifi/include/core/extension/Extension.h
@@ -25,6 +25,7 @@
 #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 {
 
@@ -39,12 +40,6 @@ 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;
@@ -56,7 +51,7 @@ class Extension {
 
   bool initialize(const std::shared_ptr<minifi::Configure>& configure);
 
-  Version version() const {return version_;}
+  [[nodiscard]] ApiVersion apiVersion() const {return api_version_;}
 
  private:
 #ifdef WIN32
@@ -80,7 +75,7 @@ class Extension {
   std::filesystem::path library_path_;
   gsl::owner<void*> handle_ = nullptr;
 
-  Version version_{};
+  ApiVersion api_version_;
   std::unique_ptr<Info> info_;
 
   const std::shared_ptr<logging::Logger> logger_;
diff --git a/libminifi/src/core/extension/ApiVersion.cpp b/libminifi/src/core/extension/ApiVersion.cpp
new file mode 100644
index 000000000..1f515b25b
--- /dev/null
+++ b/libminifi/src/core/extension/ApiVersion.cpp
@@ -0,0 +1,33 @@
+/**
+* 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 "core/extension/ApiVersion.h"
+#include "minifi-c/minifi-c.h"
+
+namespace org::apache::nifi::minifi::core::extension {
+
+static ApiVersion agent_api_version{.major = MINIFI_API_MAJOR_VERSION, .minor = MINIFI_API_MINOR_VERSION, .patch = MINIFI_API_PATCH_VERSION};
+
+ApiVersion getAgentApiVersion() {
+  return agent_api_version;
+}
+
+void setAgentApiVersion(ApiVersion api_version) {
+  agent_api_version = api_version;
+}
+
+}  // namespace org::apache::nifi::minifi::core::extension
\ No newline at end of file
diff --git a/libminifi/src/core/extension/Extension.cpp b/libminifi/src/core/extension/Extension.cpp
index 35d0634b9..9073b6c9a 100644
--- a/libminifi/src/core/extension/Extension.cpp
+++ b/libminifi/src/core/extension/Extension.cpp
@@ -45,6 +45,7 @@ namespace org::apache::nifi::minifi::core::extension {
 Extension::Extension(std::string name, std::filesystem::path library_path)
   : name_(std::move(name)),
     library_path_(std::move(library_path)),
+    api_version_(getAgentApiVersion()),
     logger_(logging::LoggerFactory<Extension>::getLogger()) {
 }
 
@@ -65,7 +66,7 @@ bool Extension::load(bool global) {
     return true;
   }
   if (!findSymbol("MinifiInitExtension")) {
-    logger_->log_error("Failed to load c extension '{}' at '{}': No initializer found", name_, library_path_);
+    logger_->log_error("Failed to load as c extension '{}' at '{}': No initializer found", name_, library_path_);
     return false;
   }
   auto api_version = reinterpret_cast<const char* const*>(findSymbol("MinifiApiVersion"));
@@ -79,21 +80,21 @@ bool Extension::load(bool global) {
     return false;
   }
   gsl_Assert(match.size() == 4);
-  version_.major = std::stoi(match[1]);
-  version_.minor = std::stoi(match[2]);
-  version_.patch = std::stoi(match[3]);
-  if (version_.major != MINIFI_API_MAJOR_VERSION) {
+  api_version_.major = std::stoi(match[1]);
+  api_version_.minor = std::stoi(match[2]);
+  api_version_.patch = std::stoi(match[3]);
+  if (api_version_.major != getAgentApiVersion().major) {
     logger_->log_error("Failed to load c extension '{}' at '{}': Api major version mismatch, application is '{}' while extension is '{}'",
-        name_, library_path_, MINIFI_API_VERSION, *api_version);
+        name_, library_path_, getAgentApiVersion().str(), *api_version);
     return false;
   }
-  if (version_.minor > MINIFI_API_MINOR_VERSION) {
+  if (api_version_.minor > getAgentApiVersion().minor) {
     logger_->log_error("Failed to load c extension '{}' at '{}': Extension is built for a newer version, application is '{}' while extension is '{}'",
-        name_, library_path_, MINIFI_API_VERSION, *api_version);
+        name_, library_path_, getAgentApiVersion().str(), *api_version);
     return false;
   }
   logger_->log_debug("Loaded c extension '{}' at '{}': Application version is '{}', extension version is '{}'",
-        name_, library_path_, MINIFI_API_VERSION, *api_version);
+        name_, library_path_, getAgentApiVersion().str(), *api_version);
   return true;
 }
 
@@ -163,6 +164,7 @@ bool Extension::initialize(const std::shared_ptr<minifi::Configure>& configure)
     logger_->log_error("Failed to initialize extension '{}'", name_);
     return false;
   }
+  logger_->log_debug("Initialized extension '{}'", name_);
   return true;
 }
 
diff --git a/libminifi/test/integration/CMakeLists.txt b/libminifi/test/integration/CMakeLists.txt
index 3fb3fd88a..44e98f76a 100644
--- a/libminifi/test/integration/CMakeLists.txt
+++ b/libminifi/test/integration/CMakeLists.txt
@@ -45,3 +45,5 @@ ENDFOREACH()
 target_link_libraries(OnScheduleErrorHandlingTests minifi-test-processors)
 
 message("-- Finished building ${INT_TEST_COUNT} integration test file(s)...")
+
+add_subdirectory(extension-verification-test)
diff --git a/libminifi/test/integration/extension-verification-test/CApiExtension.cpp b/libminifi/test/integration/extension-verification-test/CApiExtension.cpp
new file mode 100644
index 000000000..e6fd7cfbc
--- /dev/null
+++ b/libminifi/test/integration/extension-verification-test/CApiExtension.cpp
@@ -0,0 +1,39 @@
+/**
+*
+ * 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 "api/core/Resource.h"
+#include "api/utils/minifi-c-utils.h"
+
+#define MKSOC(x) #x
+#define MAKESTRING(x) MKSOC(x)  // NOLINT(cppcoreguidelines-macro-usage)
+
+namespace minifi = org::apache::nifi::minifi;
+
+extern "C" const char* const MinifiApiVersion = MINIFI_TEST_API_VERSION;
+
+extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
+  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 = 0,
+    .processors_ptr = nullptr,
+  };
+  return MinifiCreateExtension(&ext_create_info);
+}
\ No newline at end of file
diff --git a/libminifi/test/integration/extension-verification-test/CMakeLists.txt b/libminifi/test/integration/extension-verification-test/CMakeLists.txt
new file mode 100644
index 000000000..32c068106
--- /dev/null
+++ b/libminifi/test/integration/extension-verification-test/CMakeLists.txt
@@ -0,0 +1,79 @@
+# 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.
+#
+
+add_minifi_executable(ExtensionVerificationTests ExtensionVerificationTests.cpp)
+createIntegrationTests(ExtensionVerificationTests)
+target_link_libraries(${testfilename} core-minifi Catch2WithMain Threads::Threads)
+add_test(NAME ExtensionVerificationTests COMMAND ExtensionVerificationTests)
+
+function(create_loading_test_extension extension-name extension-main)
+    add_library(${extension-name} SHARED ${extension-main})
+    add_dependencies(ExtensionVerificationTests ${extension-name})
+    target_compile_definitions(${extension-name} PRIVATE MODULE_NAME=${extension-name})
+    set_target_properties(${extension-name} PROPERTIES ENABLE_EXPORTS True POSITION_INDEPENDENT_CODE ON)
+    if(WIN32)
+        set_target_properties(${extension-name} PROPERTIES
+            RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
+            ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
+            WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
+    else()
+        set_target_properties(${extension-name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
+    endif()
+endfunction()
+
+function(create_c_test_extension extension-name custom_api_version)
+    set(extension-name "test-extension-loading-${extension-name}")
+    create_loading_test_extension(${extension-name} CApiExtension.cpp)
+    target_link_libraries(${extension-name} minifi-cpp-extension-lib)
+    target_compile_definitions(${extension-name} PRIVATE MINIFI_TEST_API_VERSION="${custom_api_version}")
+    target_compile_definitions(${extension-name}
+            PRIVATE "EXTENSION_NAME=${extension-name}" "EXTENSION_VERSION=${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
+endfunction()
+
+add_library(dummy-core-minifi INTERFACE)
+target_include_directories(dummy-core-minifi INTERFACE $<TARGET_PROPERTY:minifi-extension-framework,INTERFACE_INCLUDE_DIRECTORIES>)
+if (WIN32)
+    set(DUMMY_IMPORT_LIB_FILE ${CMAKE_CURRENT_BINARY_DIR}/dummy-cpp-api.lib)
+    add_custom_target(dummy_cpp_api_import_lib
+            COMMAND lib.exe /def:${CMAKE_CURRENT_SOURCE_DIR}/dummy-cpp-api.def /out:${IMPORT_LIB_FILE} /machine:x64
+            BYPRODUCTS ${IMPORT_LIB_FILE}
+    )
+
+    add_dependencies(dummy-core-minifi dummy_cpp_api_import_lib)
+    target_link_libraries(dummy-core-minifi INTERFACE ${IMPORT_LIB_FILE})
+elseif (APPLE)
+    target_link_options(dummy-core-minifi INTERFACE -undefined dynamic_lookup)
+endif()
+
+create_loading_test_extension(test-extension-loading-invalid-build-id-cpp CppApiExtension.cpp)
+target_link_libraries(test-extension-loading-invalid-build-id-cpp dummy-core-minifi)
+target_compile_definitions(test-extension-loading-invalid-build-id-cpp PRIVATE MINIFI_CREATE_EXTENSION_FN=MinifiCreateCppExtension_NonMatchingBuildId)
+
+create_loading_test_extension(test-extension-loading-valid-build-id-cpp CppApiExtension.cpp)
+target_link_libraries(test-extension-loading-valid-build-id-cpp dummy-core-minifi)
+target_compile_definitions(test-extension-loading-valid-build-id-cpp PRIVATE MINIFI_CREATE_EXTENSION_FN=MinifiCreateCppExtension_${BUILD_IDENTIFIER})
+
+create_c_test_extension(same-version "1.1.1")
+create_c_test_extension(greater-major-version "2.0.0")
+create_c_test_extension(smaller-major-version "0.1.0")
+create_c_test_extension(greater-minor-version "1.2.0")
+create_c_test_extension(smaller-minor-version "1.0.1")
+create_c_test_extension(greater-patch-version "1.1.2")
+create_c_test_extension(smaller-patch-version "1.1.0")
+create_loading_test_extension(test-extension-loading-missing-init InvalidMissingInitExtension.cpp)
diff --git a/extensions/python/pythonloader/PyProcLoader.cpp b/libminifi/test/integration/extension-verification-test/CppApiExtension.cpp
similarity index 53%
copy from extensions/python/pythonloader/PyProcLoader.cpp
copy to libminifi/test/integration/extension-verification-test/CppApiExtension.cpp
index cd9cbb998..0503a8c17 100644
--- a/extensions/python/pythonloader/PyProcLoader.cpp
+++ b/libminifi/test/integration/extension-verification-test/CppApiExtension.cpp
@@ -1,5 +1,5 @@
 /**
- *
+*
  * 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.
@@ -15,39 +15,23 @@
  * 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 "PythonCreator.h"
-#include "minifi-cpp/agent/agent_version.h"
-#include "PythonBindings.h"
-#include "core/Resource.h"
 
-namespace minifi = org::apache::nifi::minifi;
+#include <string_view>
+#include "utils/ExtensionInitUtils.h"
 
-static minifi::extensions::python::PythonCreator& getPythonCreator() {
-  static minifi::extensions::python::PythonCreator instance("PythonCreator");
-  return instance;
-}
+#define MKSOC(x) #x
+#define MAKESTRING(x) MKSOC(x)  // NOLINT(cppcoreguidelines-macro-usage)
 
-// request this module (shared library) to be opened into the global namespace, exposing
-// the symbols of the python library
-extern "C" const int LOAD_MODULE_AS_GLOBAL = 1;
+namespace minifi = org::apache::nifi::minifi;
 
-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) {
-      *static_cast<std::optional<std::string>*>(user_data) = std::string{value.data, value.length};
-    }, &result);
-    return result;
-  });
+extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
   MinifiExtensionCreateInfo ext_create_info{
     .name = minifi::utils::toStringView(MAKESTRING(MODULE_NAME)),
-    .version = minifi::utils::toStringView(minifi::AgentBuild::VERSION),
+    .version = minifi::utils::toStringView("1.0.0"),
     .deinit = nullptr,
     .user_data = nullptr,
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return minifi::utils::MinifiCreateCppExtension( &ext_create_info);
+  return minifi::utils::MinifiCreateCppExtension(&ext_create_info);
 }
diff --git a/libminifi/test/integration/extension-verification-test/ExtensionVerificationTests.cpp b/libminifi/test/integration/extension-verification-test/ExtensionVerificationTests.cpp
new file mode 100644
index 000000000..6fa304691
--- /dev/null
+++ b/libminifi/test/integration/extension-verification-test/ExtensionVerificationTests.cpp
@@ -0,0 +1,90 @@
+/**
+*
+ * 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.
+ */
+
+#define CUSTOM_EXTENSION_INIT
+
+#include "unit/TestBase.h"
+#include "unit/Catch.h"
+#include "unit/TestUtils.h"
+#include "core/extension/ExtensionManager.h"
+#include "core/extension/ApiVersion.h"
+
+namespace minifi = org::apache::nifi::minifi;
+
+class ExtensionLoadingTestController {
+ public:
+  ExtensionLoadingTestController(std::string pattern): extension_manager_{[&] () {
+    LogTestController::getInstance().clear();
+    LogTestController::getInstance().setTrace<core::extension::ExtensionManager>();
+    LogTestController::getInstance().setTrace<core::extension::Extension>();
+    minifi::core::extension::setAgentApiVersion({.major = 1, .minor = 1, .patch = 1});
+    auto config = minifi::Configure::create();
+    config->set(minifi::Configuration::nifi_extension_path, pattern);
+    return config;
+  }()} {}
+
+ core::extension::ExtensionManager extension_manager_;
+};
+
+TEST_CASE("Can load cpp-api extensions with same build id") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-valid-build-id-cpp*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded cpp extension 'test-extension-loading-valid-build-id-cpp'"));
+}
+
+TEST_CASE("Can load c-api extensions with same version") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-same-version*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded c extension 'test-extension-loading-same-version'"));
+}
+
+TEST_CASE("Can't load cpp-api extensions with different build id") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-invalid-build-id-cpp*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Failed to load extension 'test-extension-loading-invalid-build-id-cpp'"));
+}
+
+TEST_CASE("Can't load c-api extensions with greater major version") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-greater-major-version*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Failed to load c extension 'test-extension-loading-greater-major-version'"));
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Api major version mismatch, application is '1.1.1' while extension is '2.0.0'"));
+}
+
+TEST_CASE("Can't load c-api extensions with smaller major version") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-smaller-major-version*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Failed to load c extension 'test-extension-loading-smaller-major-version'"));
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Api major version mismatch, application is '1.1.1' while extension is '0.1.0'"));
+}
+
+TEST_CASE("Can't load c-api extensions with greater minor version") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-greater-minor-version*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Failed to load c extension 'test-extension-loading-greater-minor-version'"));
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Extension is built for a newer version"));
+}
+
+TEST_CASE("Can load c-api extensions with smaller minor version") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-smaller-minor-version*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded c extension 'test-extension-loading-smaller-minor-version'"));
+}
+
+TEST_CASE("Can load c-api extensions with greater patch version") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-greater-patch-version*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded c extension 'test-extension-loading-greater-patch-version'"));
+}
+
+TEST_CASE("Can load c-api extensions with smaller patch version") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-smaller-patch-version*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded c extension 'test-extension-loading-smaller-patch-version'"));
+}
diff --git a/libminifi/test/integration/extension-verification-test/InvalidMissingInitExtension.cpp b/libminifi/test/integration/extension-verification-test/InvalidMissingInitExtension.cpp
new file mode 100644
index 000000000..3fbb5023e
--- /dev/null
+++ b/libminifi/test/integration/extension-verification-test/InvalidMissingInitExtension.cpp
@@ -0,0 +1,17 @@
+/**
+*
+ * 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.
+ */
\ No newline at end of file
diff --git a/libminifi/test/integration/extension-verification-test/dummy-cpp-api.def b/libminifi/test/integration/extension-verification-test/dummy-cpp-api.def
new file mode 100644
index 000000000..8da19c75e
--- /dev/null
+++ b/libminifi/test/integration/extension-verification-test/dummy-cpp-api.def
@@ -0,0 +1,3 @@
+LIBRARY core-minifi.dll
+EXPORTS
+  MinifiCreateCppExtension_NonMatchingBuildId


(nifi-minifi-cpp) 03/07: MINIFICPP-2715 - Windows fix

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

martinzink pushed a commit to branch controller_c_api_requirements
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit 04c8c2fae3e049dc4b28eb1d0f274e4970ce048f
Author: Adam Debreceni <ad...@apache.org>
AuthorDate: Thu Feb 12 11:17:30 2026 +0100

    MINIFICPP-2715 - Windows fix
---
 extensions/ExtensionInitializer.cpp                     |  2 +-
 extensions/llamacpp/processors/ExtensionInitializer.cpp |  2 +-
 extensions/opencv/OpenCVLoader.cpp                      |  2 +-
 extensions/python/pythonlibloader/PythonLibLoader.cpp   |  2 +-
 extensions/python/pythonloader/PyProcLoader.cpp         |  2 +-
 extensions/sftp/SFTPLoader.cpp                          |  2 +-
 libminifi/src/ApiVersions.cpp                           | 11 +++++++----
 libminifi/src/minifi-c.cpp                              |  2 +-
 minifi-api/CMakeLists.txt                               |  2 +-
 minifi-api/include/minifi-c/minifi-c.h                  | 11 +++++++----
 minifi-api/minifi-c-api.def                             |  2 +-
 11 files changed, 23 insertions(+), 17 deletions(-)

diff --git a/extensions/ExtensionInitializer.cpp b/extensions/ExtensionInitializer.cpp
index 3caaaeed0..4044e15c2 100644
--- a/extensions/ExtensionInitializer.cpp
+++ b/extensions/ExtensionInitializer.cpp
@@ -31,5 +31,5 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
+  return MinifiCreateExtension(MINIFI_API_VERSION, &ext_create_info);
 }
diff --git a/extensions/llamacpp/processors/ExtensionInitializer.cpp b/extensions/llamacpp/processors/ExtensionInitializer.cpp
index b90b8b133..18f5ff58e 100644
--- a/extensions/llamacpp/processors/ExtensionInitializer.cpp
+++ b/extensions/llamacpp/processors/ExtensionInitializer.cpp
@@ -35,7 +35,7 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
       .processors_count = 1,
       .processors_ptr = &description,
     };
-    extension = MinifiCreateExtension(minifi::api::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
+    extension = MinifiCreateExtension(MINIFI_API_VERSION, &ext_create_info);
   });
   return extension;
 }
diff --git a/extensions/opencv/OpenCVLoader.cpp b/extensions/opencv/OpenCVLoader.cpp
index 3f91395c4..83e75bdbd 100644
--- a/extensions/opencv/OpenCVLoader.cpp
+++ b/extensions/opencv/OpenCVLoader.cpp
@@ -42,5 +42,5 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
+  return MinifiCreateExtension(MINIFI_API_VERSION, &ext_create_info);
 }
diff --git a/extensions/python/pythonlibloader/PythonLibLoader.cpp b/extensions/python/pythonlibloader/PythonLibLoader.cpp
index cdf0356b7..522c43633 100644
--- a/extensions/python/pythonlibloader/PythonLibLoader.cpp
+++ b/extensions/python/pythonlibloader/PythonLibLoader.cpp
@@ -114,5 +114,5 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* config) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
+  return MinifiCreateExtension(MINIFI_API_VERSION, &ext_create_info);
 }
diff --git a/extensions/python/pythonloader/PyProcLoader.cpp b/extensions/python/pythonloader/PyProcLoader.cpp
index 55ad4ebef..f283847ff 100644
--- a/extensions/python/pythonloader/PyProcLoader.cpp
+++ b/extensions/python/pythonloader/PyProcLoader.cpp
@@ -49,5 +49,5 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* config) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
+  return MinifiCreateExtension(MINIFI_API_VERSION, &ext_create_info);
 }
diff --git a/extensions/sftp/SFTPLoader.cpp b/extensions/sftp/SFTPLoader.cpp
index bb23e9998..3cf12aedd 100644
--- a/extensions/sftp/SFTPLoader.cpp
+++ b/extensions/sftp/SFTPLoader.cpp
@@ -44,5 +44,5 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
+  return MinifiCreateExtension(MINIFI_API_VERSION, &ext_create_info);
 }
diff --git a/libminifi/src/ApiVersions.cpp b/libminifi/src/ApiVersions.cpp
index 5bff5c020..76364c0f0 100644
--- a/libminifi/src/ApiVersions.cpp
+++ b/libminifi/src/ApiVersions.cpp
@@ -20,11 +20,14 @@
 
 extern "C" {
 
-MINIFIAPI extern const char* const MINIFI_API_VERSION = MINIFI_PRIVATE_STRINGIFY(MINIFI_API_VERSION);
+const MinifiApiVersion* MINIFI_API_VERSION_FN() {
+  return reinterpret_cast<const MinifiApiVersion*>(MINIFI_PRIVATE_STRINGIFY(MINIFI_API_VERSION_FN));
+}
 
-#define REGISTER_C_API_VERSION(major, minor) \
-    MINIFIAPI extern const char* const MINIFI_PRIVATE_JOIN(MINIFI_C_API_VERSION, MINIFI_PRIVATE_JOIN(major, minor)) = \
-    MINIFI_PRIVATE_STRINGIFY(MINIFI_C_API_MAJOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_C_API_MINOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_C_API_PATCH_VERSION);
+#define REGISTER_C_API_VERSION(major, minor)  \
+    const MinifiApiVersion* MINIFI_PRIVATE_JOIN(MinifiCApiVersion, MINIFI_PRIVATE_JOIN(major, minor))() {  \
+      return reinterpret_cast<const MinifiApiVersion*>(MINIFI_PRIVATE_STRINGIFY(MINIFI_C_API_MAJOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_C_API_MINOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_C_API_PATCH_VERSION));  \
+    }
 
 REGISTER_C_API_VERSION(0, 1)
 
diff --git a/libminifi/src/minifi-c.cpp b/libminifi/src/minifi-c.cpp
index ebceb9f02..2abff81d0 100644
--- a/libminifi/src/minifi-c.cpp
+++ b/libminifi/src/minifi-c.cpp
@@ -225,7 +225,7 @@ void useCProcessorClassDescription(const MinifiProcessorClassDefinition& class_d
 
 extern "C" {
 
-MinifiExtension* MinifiCreateExtension(MinifiStringView /*api_version*/, const MinifiExtensionCreateInfo* extension_create_info) {
+MinifiExtension* MinifiCreateExtension(const MinifiApiVersion* /*api_version*/, const MinifiExtensionCreateInfo* extension_create_info) {
   gsl_Assert(extension_create_info);
   auto extension_name = toString(extension_create_info->name);
   minifi::BundleIdentifier bundle{
diff --git a/minifi-api/CMakeLists.txt b/minifi-api/CMakeLists.txt
index 7a758dbf8..dfef95bc7 100644
--- a/minifi-api/CMakeLists.txt
+++ b/minifi-api/CMakeLists.txt
@@ -6,7 +6,7 @@ target_compile_definitions(minifi-api-common INTERFACE MINIFI_VERSION_STR="${MIN
 add_library(minifi-api INTERFACE)
 target_include_directories(minifi-api INTERFACE include)
 target_link_libraries(minifi-api INTERFACE minifi-api-common)
-target_compile_definitions(minifi-api INTERFACE MINIFI_API_VERSION=MINIFI_CPP_API_VERSION_${BUILD_IDENTIFIER})
+target_compile_definitions(minifi-api INTERFACE MINIFI_API_VERSION_FN=MinifiCppApiVersion_${BUILD_IDENTIFIER})
 
 add_library(minifi-c-api INTERFACE)
 target_include_directories(minifi-c-api INTERFACE include/minifi-c)
diff --git a/minifi-api/include/minifi-c/minifi-c.h b/minifi-api/include/minifi-c/minifi-c.h
index afccd00c8..d1f45ea28 100644
--- a/minifi-api/include/minifi-c/minifi-c.h
+++ b/minifi-api/include/minifi-c/minifi-c.h
@@ -38,10 +38,10 @@ extern "C" {
 #define MINIFI_C_API_MINOR_VERSION 1
 #define MINIFI_C_API_PATCH_VERSION 0
 
-#ifndef MINIFI_API_VERSION
-#define MINIFI_API_VERSION MINIFI_PRIVATE_JOIN(MINIFI_C_API_VERSION, MINIFI_PRIVATE_JOIN(MINIFI_C_API_MAJOR_VERSION, MINIFI_C_API_MINOR_VERSION))
+#ifndef MINIFI_API_VERSION_FN
+#define MINIFI_API_VERSION_FN MINIFI_PRIVATE_JOIN(MinifiCApiVersion, MINIFI_PRIVATE_JOIN(MINIFI_C_API_MAJOR_VERSION, MINIFI_C_API_MINOR_VERSION))
 #endif
-extern const char* const MINIFI_API_VERSION;
+#define MINIFI_API_VERSION MINIFI_API_VERSION_FN()
 
 #define MINIFI_NULL nullptr
 #define MINIFI_OWNED
@@ -94,6 +94,7 @@ typedef struct MinifiOutputStream MinifiOutputStream;
 typedef struct MinifiConfig MinifiConfig;
 typedef struct MinifiExtension MinifiExtension;
 typedef struct MinifiPublishedMetrics MinifiPublishedMetrics;
+typedef struct MinifiApiVersion MinifiApiVersion;
 
 typedef enum MinifiStatus : uint32_t {
   MINIFI_STATUS_SUCCESS = 0,
@@ -188,10 +189,12 @@ typedef struct MinifiExtensionCreateInfo {
   const MinifiProcessorClassDefinition* processors_ptr;
 } MinifiExtensionCreateInfo;
 
+const MinifiApiVersion* MINIFI_API_VERSION_FN();
+
 // api_version is used to provide backwards compatible changes to the MinifiExtensionCreateInfo structure,
 // e.g. if MinifiExtensionCreateInfo gets a new field in version 1.2.0, extensions built with api 1.1.0 do not
 // have to be rebuilt
-MinifiExtension* MinifiCreateExtension(MinifiStringView api_version, const MinifiExtensionCreateInfo*);
+MinifiExtension* MinifiCreateExtension(const MinifiApiVersion* api_version, const MinifiExtensionCreateInfo*);
 
 MINIFI_OWNED MinifiPublishedMetrics* MinifiPublishedMetricsCreate(size_t count, const MinifiStringView* metric_names, const double* metric_values);
 
diff --git a/minifi-api/minifi-c-api.def b/minifi-api/minifi-c-api.def
index ee763264d..1e9f66ecd 100644
--- a/minifi-api/minifi-c-api.def
+++ b/minifi-api/minifi-c-api.def
@@ -22,4 +22,4 @@ EXPORTS
   MinifiFlowFileSetAttribute
   MinifiFlowFileGetAttribute
   MinifiFlowFileGetAttributes
-  MINIFI_C_API_VERSION_0_1
+  MinifiCApiVersion_0_1


(nifi-minifi-cpp) 05/07: MINIFICPP-2715 - Use symbol lookup to check version

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

martinzink pushed a commit to branch controller_c_api_requirements
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit a8128b9f81be218adffbdcb3beae16717c1497a5
Author: Adam Debreceni <ad...@apache.org>
AuthorDate: Thu Feb 19 13:52:50 2026 +0100

    MINIFICPP-2715 - Use symbol lookup to check version
---
 Extensions.md                                      | 43 ++++++++++++--
 .../include/utils/ExtensionInitUtils.h             |  4 ++
 extensions/ExtensionInitializer.cpp                |  4 +-
 .../llamacpp/processors/ExtensionInitializer.cpp   |  4 +-
 extensions/opencv/OpenCVLoader.cpp                 |  4 +-
 .../python/pythonlibloader/PythonLibLoader.cpp     |  4 +-
 extensions/python/pythonloader/PyProcLoader.cpp    |  4 +-
 extensions/sftp/SFTPLoader.cpp                     |  4 +-
 libminifi/include/core/extension/Extension.h       | 14 ++++-
 .../include/core/extension/ExtensionManager.h      |  2 +
 libminifi/src/core/extension/Extension.cpp         | 64 ++++++++++++++++-----
 libminifi/src/core/extension/ExtensionManager.cpp  | 22 ++++++--
 libminifi/src/minifi-c.cpp                         | 65 ++++++++++------------
 minifi-api/CMakeLists.txt                          |  2 +-
 minifi-api/include/minifi-c/minifi-c.h             | 19 ++-----
 minifi-api/minifi-c-api.def                        |  2 +-
 16 files changed, 176 insertions(+), 85 deletions(-)

diff --git a/Extensions.md b/Extensions.md
index 3a82e212f..0bfbd5bd9 100644
--- a/Extensions.md
+++ b/Extensions.md
@@ -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`
+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
@@ -33,10 +66,10 @@ REGISTER_RESOURCE(RESTSender, DescriptionOnly);
 ```
 
 Some extensions (e.g. `OpenCVExtension`) require initialization before use.
-You need to define an `MinifiInitExtension` 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* MinifiInitExtension(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;
@@ -49,7 +82,7 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
+  return MinifiCreateCppExtension(&ext_create_info);
 }
 ```
 
diff --git a/extension-framework/include/utils/ExtensionInitUtils.h b/extension-framework/include/utils/ExtensionInitUtils.h
index 1af7617e5..57f1d063f 100644
--- a/extension-framework/include/utils/ExtensionInitUtils.h
+++ b/extension-framework/include/utils/ExtensionInitUtils.h
@@ -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
diff --git a/extensions/ExtensionInitializer.cpp b/extensions/ExtensionInitializer.cpp
index 3caaaeed0..e735a78e7 100644
--- a/extensions/ExtensionInitializer.cpp
+++ b/extensions/ExtensionInitializer.cpp
@@ -22,7 +22,7 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
-extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
+extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
   MinifiExtensionCreateInfo ext_create_info{
     .name = minifi::utils::toStringView(MAKESTRING(MODULE_NAME)),
     .version = minifi::utils::toStringView(minifi::AgentBuild::VERSION),
@@ -31,5 +31,5 @@ extern "C" MinifiExtension* MinifiInitExtension(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);
 }
diff --git a/extensions/llamacpp/processors/ExtensionInitializer.cpp b/extensions/llamacpp/processors/ExtensionInitializer.cpp
index b90b8b133..29316f8b3 100644
--- a/extensions/llamacpp/processors/ExtensionInitializer.cpp
+++ b/extensions/llamacpp/processors/ExtensionInitializer.cpp
@@ -24,6 +24,8 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
+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) {
@@ -35,7 +37,7 @@ extern "C" MinifiExtension* MinifiInitExtension(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;
 }
diff --git a/extensions/opencv/OpenCVLoader.cpp b/extensions/opencv/OpenCVLoader.cpp
index 3f91395c4..53f61dbca 100644
--- a/extensions/opencv/OpenCVLoader.cpp
+++ b/extensions/opencv/OpenCVLoader.cpp
@@ -24,7 +24,7 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
-extern "C" MinifiExtension* MinifiInitExtension(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:
@@ -42,5 +42,5 @@ extern "C" MinifiExtension* MinifiInitExtension(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);
 }
diff --git a/extensions/python/pythonlibloader/PythonLibLoader.cpp b/extensions/python/pythonlibloader/PythonLibLoader.cpp
index cdf0356b7..9c9a35d28 100644
--- a/extensions/python/pythonlibloader/PythonLibLoader.cpp
+++ b/extensions/python/pythonlibloader/PythonLibLoader.cpp
@@ -98,7 +98,7 @@ class PythonLibLoader {
   std::shared_ptr<minifi::core::logging::Logger> logger_ = minifi::core::logging::LoggerFactory<PythonLibLoader>::getLogger();
 };
 
-extern "C" MinifiExtension* MinifiInitExtension(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) {
@@ -114,5 +114,5 @@ extern "C" MinifiExtension* MinifiInitExtension(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);
 }
diff --git a/extensions/python/pythonloader/PyProcLoader.cpp b/extensions/python/pythonloader/PyProcLoader.cpp
index 55ad4ebef..cd9cbb998 100644
--- a/extensions/python/pythonloader/PyProcLoader.cpp
+++ b/extensions/python/pythonloader/PyProcLoader.cpp
@@ -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* MinifiInitExtension(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) {
@@ -49,5 +49,5 @@ extern "C" MinifiExtension* MinifiInitExtension(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);
 }
diff --git a/extensions/sftp/SFTPLoader.cpp b/extensions/sftp/SFTPLoader.cpp
index bb23e9998..333ab173e 100644
--- a/extensions/sftp/SFTPLoader.cpp
+++ b/extensions/sftp/SFTPLoader.cpp
@@ -25,7 +25,7 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
-extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
+extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
   if (libssh2_init(0) != 0) {
     return nullptr;
   }
@@ -44,5 +44,5 @@ extern "C" MinifiExtension* MinifiInitExtension(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);
 }
diff --git a/libminifi/include/core/extension/Extension.h b/libminifi/include/core/extension/Extension.h
index e14cb97ed..2a71a6fbc 100644
--- a/libminifi/include/core/extension/Extension.h
+++ b/libminifi/include/core/extension/Extension.h
@@ -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"
 
@@ -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;
@@ -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_;
@@ -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_;
diff --git a/libminifi/include/core/extension/ExtensionManager.h b/libminifi/include/core/extension/ExtensionManager.h
index c610bd70a..7b312ae4b 100644
--- a/libminifi/include/core/extension/ExtensionManager.h
+++ b/libminifi/include/core/extension/ExtensionManager.h
@@ -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_;
 
diff --git a/libminifi/src/core/extension/Extension.cpp b/libminifi/src/core/extension/Extension.cpp
index 3034c8a8f..35d0634b9 100644
--- a/libminifi/src/core/extension/Extension.cpp
+++ b/libminifi/src/core/extension/Extension.cpp
@@ -38,6 +38,7 @@
 #include "core/logging/LoggerFactory.h"
 #include "minifi-c/minifi-c.h"
 #include "minifi-cpp/agent/agent_docs.h"
+#include "utils/RegexUtils.h"
 
 namespace org::apache::nifi::minifi::core::extension {
 
@@ -57,10 +58,43 @@ bool Extension::load(bool global) {
   if (!handle_) {
     logger_->log_error("Failed to load extension '{}' at '{}': {}", name_, library_path_, dlerror());
     return false;
-  } else {
-    logger_->log_trace("Loaded extension '{}' at '{}'", name_, library_path_);
+  }
+  logger_->log_trace("Dlopen succeeded for extension '{}' at '{}'", name_, library_path_);
+  if (findSymbol("MinifiInitCppExtension")) {
+    logger_->log_trace("Loaded cpp extension '{}' at '{}'", name_, library_path_);
     return true;
   }
+  if (!findSymbol("MinifiInitExtension")) {
+    logger_->log_error("Failed to load c extension '{}' at '{}': No initializer found", name_, library_path_);
+    return false;
+  }
+  auto api_version = reinterpret_cast<const char* const*>(findSymbol("MinifiApiVersion"));
+  if (!api_version) {
+    logger_->log_error("Failed to load c extension '{}' at '{}': No MinifiApiVersion symbol found", name_, library_path_);
+    return false;
+  }
+  utils::SVMatch match;
+  if (!utils::regexSearch(*api_version, match, utils::Regex{R"(^([0-9]+)\.([0-9]+)\.([0-9]+)$)"})) {
+    logger_->log_error("Failed to load c extension '{}' at '{}': Api version is in invalid format: '{}'", name_, library_path_, *api_version);
+    return false;
+  }
+  gsl_Assert(match.size() == 4);
+  version_.major = std::stoi(match[1]);
+  version_.minor = std::stoi(match[2]);
+  version_.patch = std::stoi(match[3]);
+  if (version_.major != MINIFI_API_MAJOR_VERSION) {
+    logger_->log_error("Failed to load c extension '{}' at '{}': Api major version mismatch, application is '{}' while extension is '{}'",
+        name_, library_path_, MINIFI_API_VERSION, *api_version);
+    return false;
+  }
+  if (version_.minor > MINIFI_API_MINOR_VERSION) {
+    logger_->log_error("Failed to load c extension '{}' at '{}': Extension is built for a newer version, application is '{}' while extension is '{}'",
+        name_, library_path_, MINIFI_API_VERSION, *api_version);
+    return false;
+  }
+  logger_->log_debug("Loaded c extension '{}' at '{}': Application version is '{}', extension version is '{}'",
+        name_, library_path_, MINIFI_API_VERSION, *api_version);
+  return true;
 }
 
 bool Extension::unload() {
@@ -113,17 +147,21 @@ Extension::~Extension() {
 
 bool Extension::initialize(const std::shared_ptr<minifi::Configure>& configure) {
   logger_->log_trace("Initializing extension '{}'", name_);
-  if (void* init_symbol_ptr = findSymbol("MinifiInitExtension")) {
-    logger_->log_debug("Found custom initializer for '{}'", name_);
-    auto init_fn = reinterpret_cast<MinifiExtension*(*)(MinifiConfig*)>(init_symbol_ptr);
-    auto config_handle = reinterpret_cast<MinifiConfig*>(configure.get());
-    info_.reset(reinterpret_cast<Info*>(init_fn(config_handle)));
-    if (!info_) {
-      logger_->log_error("Failed to initialize extension '{}'", name_);
-      return false;
-    }
-  } else {
-    logger_->log_debug("No custom initializer for '{}'", name_);
+  void* init_symbol_ptr = findSymbol("MinifiInitCppExtension");
+  if (!init_symbol_ptr) {
+    init_symbol_ptr = findSymbol("MinifiInitExtension");
+  }
+  if (!init_symbol_ptr) {
+    logger_->log_error("No initializer for '{}'", name_);
+    return false;
+  }
+  logger_->log_debug("Found initializer for '{}'", name_);
+  auto init_fn = reinterpret_cast<MinifiExtension*(*)(MinifiConfig*)>(init_symbol_ptr);
+  auto config_handle = reinterpret_cast<MinifiConfig*>(configure.get());
+  info_.reset(reinterpret_cast<Info*>(init_fn(config_handle)));
+  if (!info_) {
+    logger_->log_error("Failed to initialize extension '{}'", name_);
+    return false;
   }
   return true;
 }
diff --git a/libminifi/src/core/extension/ExtensionManager.cpp b/libminifi/src/core/extension/ExtensionManager.cpp
index 0b144a251..689a6cd42 100644
--- a/libminifi/src/core/extension/ExtensionManager.cpp
+++ b/libminifi/src/core/extension/ExtensionManager.cpp
@@ -30,6 +30,16 @@
 
 namespace org::apache::nifi::minifi::core::extension {
 
+namespace {
+
+std::atomic<Extension*> extension_being_initialized{nullptr};
+
+}  // namespace
+
+Extension* ExtensionManager::getExtensionBeingInitialized() {
+  return extension_being_initialized.load();
+}
+
 ExtensionManager::ExtensionManager(const std::shared_ptr<Configure>& config): logger_(logging::LoggerFactory<ExtensionManager>::getLogger()) {
   logger_->log_trace("Initializing extensions");
   if (!config) {
@@ -71,10 +81,14 @@ ExtensionManager::ExtensionManager(const std::shared_ptr<Configure>& config): lo
         continue;
       }
     }
-    if (!extension->initialize(config)) {
-      logger_->log_error("Failed to initialize extension '{}' at '{}'", library->name, library->getFullPath());
-    } else {
-      extensions_.push_back(std::move(extension));
+    {
+      auto extension_guard = gsl::finally([] {extension_being_initialized = nullptr;});
+      extension_being_initialized = extension.get();
+      if (!extension->initialize(config)) {
+        logger_->log_error("Failed to initialize extension '{}' at '{}'", library->name, library->getFullPath());
+      } else {
+        extensions_.push_back(std::move(extension));
+      }
     }
   }
 }
diff --git a/libminifi/src/minifi-c.cpp b/libminifi/src/minifi-c.cpp
index 0e4144c33..e670c4415 100644
--- a/libminifi/src/minifi-c.cpp
+++ b/libminifi/src/minifi-c.cpp
@@ -22,6 +22,8 @@
 
 #include "agent/agent_docs.h"
 #include "core/ProcessorMetrics.h"
+#include "core/extension/ExtensionManager.h"
+#include "minifi-cpp/Exception.h"
 #include "minifi-cpp/core/Annotation.h"
 #include "minifi-cpp/core/ClassLoader.h"
 #include "minifi-cpp/core/ProcessContext.h"
@@ -33,10 +35,8 @@
 #include "minifi-cpp/core/PropertyValidator.h"
 #include "minifi-cpp/core/logging/Logger.h"
 #include "minifi-cpp/core/state/PublishedMetricProvider.h"
-#include "minifi-cpp/Exception.h"
-#include "core/extension/ExtensionManager.h"
-#include "utils/PropertyErrors.h"
 #include "utils/CProcessor.h"
+#include "utils/PropertyErrors.h"
 
 namespace minifi = org::apache::nifi::minifi;
 
@@ -146,30 +146,6 @@ class CProcessorFactory : public minifi::core::ProcessorFactory {
   minifi::utils::CProcessorClassDescription class_description_;
 };
 
-MinifiExtension* MinifiCreateExtensionImpl(MinifiStringView /*api_version*/, const MinifiExtensionCreateInfo* extension_create_info) {
-  gsl_Assert(extension_create_info);
-  auto extension_name = toString(extension_create_info->name);
-  minifi::BundleIdentifier bundle{
-    .name = extension_name,
-    .version = toString(extension_create_info->version)
-  };
-  auto& bundle_components = minifi::ClassDescriptionRegistry::getMutableClassDescriptions()[bundle];
-  for (size_t proc_idx = 0; proc_idx < extension_create_info->processors_count; ++proc_idx) {
-    minifi::utils::useCProcessorClassDescription(extension_create_info->processors_ptr[proc_idx], [&] (const auto& description, const auto& c_class_description) {
-      minifi::core::ClassLoader::getDefaultClassLoader().getClassLoader(extension_name).registerClass(
-        c_class_description.name,
-        std::make_unique<CProcessorFactory>(extension_name, toString(extension_create_info->processors_ptr[proc_idx].full_name), c_class_description));
-      bundle_components.processors.emplace_back(description);
-    });
-  }
-  return reinterpret_cast<MinifiExtension*>(new org::apache::nifi::minifi::core::extension::Extension::Info{
-    .name = toString(extension_create_info->name),
-    .version = toString(extension_create_info->version),
-    .deinit = extension_create_info->deinit,
-    .user_data = extension_create_info->user_data
-  });
-}
-
 }  // namespace
 
 namespace org::apache::nifi::minifi::utils {
@@ -249,16 +225,35 @@ void useCProcessorClassDescription(const MinifiProcessorClassDefinition& class_d
 
 extern "C" {
 
-MinifiExtension* MINIFI_CREATE_EXTENSION_FN(MinifiStringView api_version, const MinifiExtensionCreateInfo* extension_create_info) {
-  return MinifiCreateExtensionImpl(api_version, extension_create_info);
-}
-
-#define REGISTER_C_API_VERSION(major, minor)  \
-  MinifiExtension* MINIFI_PRIVATE_JOIN(MinifiCreateExtension, MINIFI_PRIVATE_JOIN(major, minor))(MinifiStringView api_version, const MinifiExtensionCreateInfo* extension_create_info) {  \
-    return MinifiCreateExtensionImpl(api_version, extension_create_info);  \
+MinifiExtension* MinifiCreateExtension(const MinifiExtensionCreateInfo* extension_create_info) {
+  gsl_Assert(extension_create_info);
+  auto current_extension = minifi::core::extension::ExtensionManager::getExtensionBeingInitialized();
+  gsl_Assert(current_extension);
+  auto extension_name = toString(extension_create_info->name);
+  minifi::BundleIdentifier bundle{
+    .name = extension_name,
+    .version = toString(extension_create_info->version)
+  };
+  auto& bundle_components = minifi::ClassDescriptionRegistry::getMutableClassDescriptions()[bundle];
+  for (size_t proc_idx = 0; proc_idx < extension_create_info->processors_count; ++proc_idx) {
+    minifi::utils::useCProcessorClassDescription(extension_create_info->processors_ptr[proc_idx], [&] (const auto& description, const auto& c_class_description) {
+      minifi::core::ClassLoader::getDefaultClassLoader().getClassLoader(extension_name).registerClass(
+        c_class_description.name,
+        std::make_unique<CProcessorFactory>(extension_name, toString(extension_create_info->processors_ptr[proc_idx].full_name), c_class_description));
+      bundle_components.processors.emplace_back(description);
+    });
   }
+  return reinterpret_cast<MinifiExtension*>(new org::apache::nifi::minifi::core::extension::Extension::Info{
+    .name = toString(extension_create_info->name),
+    .version = toString(extension_create_info->version),
+    .deinit = extension_create_info->deinit,
+    .user_data = extension_create_info->user_data
+  });
+}
 
-REGISTER_C_API_VERSION(0, 1)
+MinifiExtension* MINIFI_CREATE_EXTENSION_FN(const MinifiExtensionCreateInfo* extension_create_info) {
+  return MinifiCreateExtension(extension_create_info);
+}
 
 MinifiStatus MinifiProcessContextGetProperty(MinifiProcessContext* context, MinifiStringView property_name, MinifiFlowFile* flowfile,
     void (*result_cb)(void* user_ctx, MinifiStringView result), void* user_ctx) {
diff --git a/minifi-api/CMakeLists.txt b/minifi-api/CMakeLists.txt
index 05f78fc99..ebdd239c3 100644
--- a/minifi-api/CMakeLists.txt
+++ b/minifi-api/CMakeLists.txt
@@ -6,7 +6,7 @@ target_compile_definitions(minifi-api-common INTERFACE MINIFI_VERSION_STR="${MIN
 add_library(minifi-api INTERFACE)
 target_include_directories(minifi-api INTERFACE include)
 target_link_libraries(minifi-api INTERFACE minifi-api-common)
-target_compile_definitions(minifi-api INTERFACE MINIFI_CREATE_EXTENSION_FN=MinifiCreateExtension_${BUILD_IDENTIFIER})
+target_compile_definitions(minifi-api INTERFACE MINIFI_CREATE_EXTENSION_FN=MinifiCreateCppExtension_${BUILD_IDENTIFIER})
 
 add_library(minifi-c-api INTERFACE)
 target_include_directories(minifi-c-api INTERFACE include/minifi-c)
diff --git a/minifi-api/include/minifi-c/minifi-c.h b/minifi-api/include/minifi-c/minifi-c.h
index d246b773b..5bab74941 100644
--- a/minifi-api/include/minifi-c/minifi-c.h
+++ b/minifi-api/include/minifi-c/minifi-c.h
@@ -39,13 +39,13 @@ extern "C" {
 #define MINIFI_API_PATCH_VERSION 0
 #define MINIFI_API_VERSION MINIFI_PRIVATE_STRINGIFY(MINIFI_API_MAJOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_API_MINOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_API_PATCH_VERSION)
 
-#ifndef MINIFI_CREATE_EXTENSION_FN
-#define MINIFI_CREATE_EXTENSION_FN MINIFI_PRIVATE_JOIN(MinifiCreateExtension, MINIFI_PRIVATE_JOIN(MINIFI_API_MAJOR_VERSION, MINIFI_API_MINOR_VERSION))
-#endif
-
 #define MINIFI_NULL nullptr
 #define MINIFI_OWNED
 
+#ifndef MINIFI_CREATE_EXTENSION_FN
+#define MINIFI_CREATE_EXTENSION_FN MinifiCreateExtension
+#endif
+
 typedef bool MinifiBool;
 
 typedef enum MinifiInputRequirement : uint32_t {
@@ -94,7 +94,7 @@ typedef struct MinifiOutputStream MinifiOutputStream;
 typedef struct MinifiConfig MinifiConfig;
 typedef struct MinifiExtension MinifiExtension;
 typedef struct MinifiPublishedMetrics MinifiPublishedMetrics;
-typedef struct MinifiApiVersion MinifiApiVersion;
+typedef struct MinifiAgent MinifiAgent;
 
 typedef enum MinifiStatus : uint32_t {
   MINIFI_STATUS_SUCCESS = 0,
@@ -189,10 +189,7 @@ typedef struct MinifiExtensionCreateInfo {
   const MinifiProcessorClassDefinition* processors_ptr;
 } MinifiExtensionCreateInfo;
 
-// api_version is used to provide backwards compatible changes to the MinifiExtensionCreateInfo structure,
-// e.g. if MinifiExtensionCreateInfo gets a new field in version 1.2.0, extensions built with api 1.1.0 do not
-// have to be rebuilt
-MinifiExtension* MINIFI_CREATE_EXTENSION_FN(MinifiStringView api_version, const MinifiExtensionCreateInfo* create_info);
+MinifiExtension* MINIFI_CREATE_EXTENSION_FN(const MinifiExtensionCreateInfo* create_info);
 
 MINIFI_OWNED MinifiPublishedMetrics* MinifiPublishedMetricsCreate(size_t count, const MinifiStringView* metric_names, const double* metric_values);
 
@@ -232,8 +229,4 @@ void MinifiFlowFileGetAttributes(MinifiProcessSession* session, MinifiFlowFile*
 }  // extern "C"
 #endif  // __cplusplus
 
-static inline MinifiExtension* MinifiCreateExtension(MinifiStringView api_version, const MinifiExtensionCreateInfo* create_info) {
-  return MINIFI_CREATE_EXTENSION_FN(api_version, create_info);
-}
-
 #endif  // MINIFI_API_INCLUDE_MINIFI_C_MINIFI_C_H_
diff --git a/minifi-api/minifi-c-api.def b/minifi-api/minifi-c-api.def
index a664bdd2d..fd303daee 100644
--- a/minifi-api/minifi-c-api.def
+++ b/minifi-api/minifi-c-api.def
@@ -1,6 +1,6 @@
 LIBRARY core-minifi.dll
 EXPORTS
-  MinifiCreateExtension_0_1
+  MinifiCreateExtension
   MinifiPublishedMetricsCreate
   MinifiProcessContextGetProperty
   MinifiProcessContextHasNonEmptyProperty


(nifi-minifi-cpp) 07/07: MINIFICPP-2715 - Use a single integer as c api version

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

martinzink pushed a commit to branch controller_c_api_requirements
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit e83dbf2d710de9bf1ee2e9382f63f6956f105f9d
Author: Adam Debreceni <ad...@apache.org>
AuthorDate: Mon Mar 2 17:23:32 2026 +0100

    MINIFICPP-2715 - Use a single integer as c api version
---
 Extensions.md                                      | 10 ++-
 .../include/utils/ExtensionInitUtils.h             |  4 +-
 extensions/ExtensionInitializer.cpp                |  4 +-
 .../llamacpp/processors/ExtensionInitializer.cpp   |  8 +--
 extensions/opencv/OpenCVLoader.cpp                 |  6 +-
 .../python/pythonlibloader/PythonLibLoader.cpp     |  4 +-
 extensions/python/pythonloader/PyProcLoader.cpp    |  4 +-
 extensions/sftp/SFTPLoader.cpp                     |  8 +--
 libminifi/include/core/extension/ApiVersion.h      | 16 ++---
 libminifi/include/core/extension/Extension.h       |  8 +--
 libminifi/src/core/extension/ApiVersion.cpp        | 15 ++++-
 libminifi/src/core/extension/Extension.cpp         | 76 +++++++++++-----------
 libminifi/src/minifi-c.cpp                         | 15 +++--
 .../extension-verification-test/CApiExtension.cpp  |  6 +-
 .../extension-verification-test/CMakeLists.txt     | 18 ++---
 .../CppApiExtension.cpp                            |  4 +-
 .../CreateNotCalled.cpp}                           | 26 ++------
 .../ExtensionVerificationTests.cpp                 | 49 +++++++-------
 minifi-api/include/minifi-c/minifi-c.h             | 11 ++--
 19 files changed, 141 insertions(+), 151 deletions(-)

diff --git a/Extensions.md b/Extensions.md
index 159232f9a..f75c068e6 100644
--- a/Extensions.md
+++ b/Extensions.md
@@ -30,8 +30,7 @@ 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;
+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)),
@@ -41,9 +40,8 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
       .processors_count = 1,
       .processors_ptr = &description,
     };
-    extension = MinifiCreateExtension(&ext_create_info);
+    MinifiCreateExtension(extension, &ext_create_info);
   });
-  return extension;
 }
 ```
 
@@ -69,7 +67,7 @@ Some extensions (e.g. `OpenCVExtension`) require initialization before use.
 You need to define an `MinifiInitCppExtension` function of type `MinifiExtension*(MinifiConfig*)` to be called.
 
 ```C++
-extern "C" MinifiExtension* MinifiInitCppExtension(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;
@@ -82,7 +80,7 @@ extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return minifi::utils::MinifiCreateCppExtension(&ext_create_info);
+  minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
 }
 ```
 
diff --git a/extension-framework/include/utils/ExtensionInitUtils.h b/extension-framework/include/utils/ExtensionInitUtils.h
index 57f1d063f..75da5c1ae 100644
--- a/extension-framework/include/utils/ExtensionInitUtils.h
+++ b/extension-framework/include/utils/ExtensionInitUtils.h
@@ -30,8 +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);
+static inline void MinifiCreateCppExtension(MinifiExtension* extension, const MinifiExtensionCreateInfo* create_info) {
+  MINIFI_CREATE_EXTENSION_FN(extension, create_info);
 }
 
 }  // namespace org::apache::nifi::minifi::utils
diff --git a/extensions/ExtensionInitializer.cpp b/extensions/ExtensionInitializer.cpp
index e735a78e7..47a4c2185 100644
--- a/extensions/ExtensionInitializer.cpp
+++ b/extensions/ExtensionInitializer.cpp
@@ -22,7 +22,7 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
-extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
+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),
@@ -31,5 +31,5 @@ extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return minifi::utils::MinifiCreateCppExtension(&ext_create_info);
+  minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
 }
diff --git a/extensions/llamacpp/processors/ExtensionInitializer.cpp b/extensions/llamacpp/processors/ExtensionInitializer.cpp
index 29316f8b3..8c223dc70 100644
--- a/extensions/llamacpp/processors/ExtensionInitializer.cpp
+++ b/extensions/llamacpp/processors/ExtensionInitializer.cpp
@@ -24,10 +24,9 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
-extern "C" const char* const MinifiApiVersion = MINIFI_API_VERSION;
+extern "C" const uint32_t MinifiApiVersion = MINIFI_API_VERSION;
 
-extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
-  MinifiExtension* extension = nullptr;
+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)),
@@ -37,7 +36,6 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
       .processors_count = 1,
       .processors_ptr = &description,
     };
-    extension = MinifiCreateExtension(&ext_create_info);
+    MinifiCreateExtension(extension, &ext_create_info);
   });
-  return extension;
 }
diff --git a/extensions/opencv/OpenCVLoader.cpp b/extensions/opencv/OpenCVLoader.cpp
index 53f61dbca..87c6615c5 100644
--- a/extensions/opencv/OpenCVLoader.cpp
+++ b/extensions/opencv/OpenCVLoader.cpp
@@ -24,7 +24,7 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
-extern "C" MinifiExtension* MinifiInitCppExtension(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:
@@ -32,7 +32,7 @@ extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
   // 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)),
@@ -42,5 +42,5 @@ extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return minifi::utils::MinifiCreateCppExtension(&ext_create_info);
+  minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
 }
diff --git a/extensions/python/pythonlibloader/PythonLibLoader.cpp b/extensions/python/pythonlibloader/PythonLibLoader.cpp
index 9c9a35d28..79ef21556 100644
--- a/extensions/python/pythonlibloader/PythonLibLoader.cpp
+++ b/extensions/python/pythonlibloader/PythonLibLoader.cpp
@@ -98,7 +98,7 @@ class PythonLibLoader {
   std::shared_ptr<minifi::core::logging::Logger> logger_ = minifi::core::logging::LoggerFactory<PythonLibLoader>::getLogger();
 };
 
-extern "C" MinifiExtension* MinifiInitCppExtension(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) {
@@ -114,5 +114,5 @@ extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* config) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return minifi::utils::MinifiCreateCppExtension(&ext_create_info);
+  minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
 }
diff --git a/extensions/python/pythonloader/PyProcLoader.cpp b/extensions/python/pythonloader/PyProcLoader.cpp
index d4edf9cf2..e92bbea41 100644
--- a/extensions/python/pythonloader/PyProcLoader.cpp
+++ b/extensions/python/pythonloader/PyProcLoader.cpp
@@ -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* MinifiInitCppExtension(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) {
@@ -49,5 +49,5 @@ extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* config) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return minifi::utils::MinifiCreateCppExtension(&ext_create_info);
+  minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
 }
diff --git a/extensions/sftp/SFTPLoader.cpp b/extensions/sftp/SFTPLoader.cpp
index 333ab173e..c8309cc60 100644
--- a/extensions/sftp/SFTPLoader.cpp
+++ b/extensions/sftp/SFTPLoader.cpp
@@ -25,13 +25,13 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
-extern "C" MinifiExtension* MinifiInitCppExtension(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)),
@@ -44,5 +44,5 @@ extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return minifi::utils::MinifiCreateCppExtension(&ext_create_info);
+  minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
 }
diff --git a/libminifi/include/core/extension/ApiVersion.h b/libminifi/include/core/extension/ApiVersion.h
index 01acb82cb..34b917a32 100644
--- a/libminifi/include/core/extension/ApiVersion.h
+++ b/libminifi/include/core/extension/ApiVersion.h
@@ -21,17 +21,9 @@
 
 namespace org::apache::nifi::minifi::core::extension {
 
-struct ApiVersion {
-  int major;
-  int minor;
-  int patch;
-
-  std::string str() const {
-    return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch);
-  }
-};
-
-ApiVersion getAgentApiVersion();
-void setAgentApiVersion(ApiVersion api_version);
+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
diff --git a/libminifi/include/core/extension/Extension.h b/libminifi/include/core/extension/Extension.h
index 356efed4c..85cf4fc60 100644
--- a/libminifi/include/core/extension/Extension.h
+++ b/libminifi/include/core/extension/Extension.h
@@ -51,7 +51,7 @@ class Extension {
 
   bool initialize(const std::shared_ptr<minifi::Configure>& configure);
 
-  [[nodiscard]] ApiVersion apiVersion() const {return api_version_;}
+  bool setInfo(Info info);
 
  private:
 #ifdef WIN32
@@ -71,12 +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;
 
-  ApiVersion api_version_;
-  std::unique_ptr<Info> info_;
+  std::optional<Info> info_;
+  uint32_t api_version_{0};
 
   const std::shared_ptr<logging::Logger> logger_;
 };
diff --git a/libminifi/src/core/extension/ApiVersion.cpp b/libminifi/src/core/extension/ApiVersion.cpp
index 1f515b25b..15f59d18b 100644
--- a/libminifi/src/core/extension/ApiVersion.cpp
+++ b/libminifi/src/core/extension/ApiVersion.cpp
@@ -20,14 +20,23 @@
 
 namespace org::apache::nifi::minifi::core::extension {
 
-static ApiVersion agent_api_version{.major = MINIFI_API_MAJOR_VERSION, .minor = MINIFI_API_MINOR_VERSION, .patch = MINIFI_API_PATCH_VERSION};
+static uint32_t agent_api_version{MINIFI_API_VERSION};
+static uint32_t min_supported_api_version{MINIFI_API_VERSION};
 
-ApiVersion getAgentApiVersion() {
+uint32_t getAgentApiVersion() {
   return agent_api_version;
 }
 
-void setAgentApiVersion(ApiVersion api_version) {
+void test_setAgentApiVersion(uint32_t api_version) {
   agent_api_version = api_version;
 }
 
+uint32_t getMinSupportedApiVersion() {
+  return min_supported_api_version;
+}
+
+void test_setMinSupportedApiVersion(uint32_t min_api_version) {
+  min_supported_api_version = min_api_version;
+}
+
 }  // namespace org::apache::nifi::minifi::core::extension
\ No newline at end of file
diff --git a/libminifi/src/core/extension/Extension.cpp b/libminifi/src/core/extension/Extension.cpp
index 9073b6c9a..9382da9d1 100644
--- a/libminifi/src/core/extension/Extension.cpp
+++ b/libminifi/src/core/extension/Extension.cpp
@@ -42,10 +42,9 @@
 
 namespace org::apache::nifi::minifi::core::extension {
 
-Extension::Extension(std::string name, std::filesystem::path library_path)
-  : name_(std::move(name)),
+Extension::Extension(std::string library_name, std::filesystem::path library_path)
+  : library_name_(std::move(library_name)),
     library_path_(std::move(library_path)),
-    api_version_(getAgentApiVersion()),
     logger_(logging::LoggerFactory<Extension>::getLogger()) {
 }
 
@@ -57,59 +56,51 @@ bool Extension::load(bool global) {
     handle_ = dlopen(library_path_.string().c_str(), RTLD_NOW | RTLD_LOCAL);  // NOLINT(cppcoreguidelines-owning-memory)
   }
   if (!handle_) {
-    logger_->log_error("Failed to load extension '{}' at '{}': {}", name_, library_path_, dlerror());
+    logger_->log_error("Failed to load extension '{}' at '{}': {}", library_name_, library_path_, dlerror());
     return false;
   }
-  logger_->log_trace("Dlopen succeeded for extension '{}' at '{}'", name_, library_path_);
+  logger_->log_trace("Dlopen succeeded for extension '{}' at '{}'", library_name_, library_path_);
   if (findSymbol("MinifiInitCppExtension")) {
-    logger_->log_trace("Loaded cpp extension '{}' at '{}'", name_, library_path_);
+    logger_->log_trace("Loaded cpp extension '{}' at '{}'", library_name_, library_path_);
     return true;
   }
   if (!findSymbol("MinifiInitExtension")) {
-    logger_->log_error("Failed to load as c extension '{}' at '{}': No initializer found", name_, library_path_);
+    logger_->log_error("Failed to load as c extension '{}' at '{}': No initializer found", library_name_, library_path_);
     return false;
   }
-  auto api_version = reinterpret_cast<const char* const*>(findSymbol("MinifiApiVersion"));
-  if (!api_version) {
-    logger_->log_error("Failed to load c extension '{}' at '{}': No MinifiApiVersion symbol found", name_, library_path_);
+  auto api_version_ptr = reinterpret_cast<const uint32_t*>(findSymbol("MinifiApiVersion"));
+  if (!api_version_ptr) {
+    logger_->log_error("Failed to load c extension '{}' at '{}': No MinifiApiVersion symbol found", library_name_, library_path_);
     return false;
   }
-  utils::SVMatch match;
-  if (!utils::regexSearch(*api_version, match, utils::Regex{R"(^([0-9]+)\.([0-9]+)\.([0-9]+)$)"})) {
-    logger_->log_error("Failed to load c extension '{}' at '{}': Api version is in invalid format: '{}'", name_, library_path_, *api_version);
+  api_version_ = *api_version_ptr;
+  if (api_version_ < getMinSupportedApiVersion()) {
+    logger_->log_error("Failed to load c extension '{}' at '{}': Api version is no longer supported, application supports {}-{} while extension is {}",
+        library_name_, library_path_, getMinSupportedApiVersion(), getAgentApiVersion(), api_version_);
     return false;
   }
-  gsl_Assert(match.size() == 4);
-  api_version_.major = std::stoi(match[1]);
-  api_version_.minor = std::stoi(match[2]);
-  api_version_.patch = std::stoi(match[3]);
-  if (api_version_.major != getAgentApiVersion().major) {
-    logger_->log_error("Failed to load c extension '{}' at '{}': Api major version mismatch, application is '{}' while extension is '{}'",
-        name_, library_path_, getAgentApiVersion().str(), *api_version);
+  if (api_version_ > getAgentApiVersion()) {
+    logger_->log_error("Failed to load c extension '{}' at '{}': Extension is built for a newer version, application supports {}-{} while extension is {}",
+        library_name_, library_path_, getMinSupportedApiVersion(), getAgentApiVersion(), api_version_);
     return false;
   }
-  if (api_version_.minor > getAgentApiVersion().minor) {
-    logger_->log_error("Failed to load c extension '{}' at '{}': Extension is built for a newer version, application is '{}' while extension is '{}'",
-        name_, library_path_, getAgentApiVersion().str(), *api_version);
-    return false;
-  }
-  logger_->log_debug("Loaded c extension '{}' at '{}': Application version is '{}', extension version is '{}'",
-        name_, library_path_, getAgentApiVersion().str(), *api_version);
+  logger_->log_debug("Loaded c extension '{}' at '{}': Application version is {}, extension version is {}",
+        library_name_, library_path_, getAgentApiVersion(), api_version_);
   return true;
 }
 
 bool Extension::unload() {
-  logger_->log_trace("Unloading library '{}' at '{}'", name_, library_path_);
+  logger_->log_trace("Unloading library '{}' at '{}'", library_name_, library_path_);
   if (!handle_) {
-    logger_->log_error("Extension does not have a handle_ '{}' at '{}'", name_, library_path_);
+    logger_->log_error("Extension does not have a handle_ '{}' at '{}'", library_name_, library_path_);
     return true;
   }
   dlerror();
   if (dlclose(handle_)) {
-    logger_->log_error("Failed to unload extension '{}' at '{}': {}", name_, library_path_, dlerror());
+    logger_->log_error("Failed to unload extension '{}' at '{}': {}", library_name_, library_path_, dlerror());
     return false;
   }
-  logger_->log_trace("Unloaded extension '{}' at '{}'", name_, library_path_);
+  logger_->log_trace("Unloaded extension '{}' at '{}'", library_name_, library_path_);
   handle_ = nullptr;
   return true;
 }
@@ -146,25 +137,34 @@ Extension::~Extension() {
 #endif
 }
 
+bool Extension::setInfo(Info info) {
+  if (info_) {
+    return false;
+  }
+  info_ = std::move(info);
+  return true;
+}
+
 bool Extension::initialize(const std::shared_ptr<minifi::Configure>& configure) {
-  logger_->log_trace("Initializing extension '{}'", name_);
+  logger_->log_trace("Initializing extension '{}'", library_name_);
   void* init_symbol_ptr = findSymbol("MinifiInitCppExtension");
   if (!init_symbol_ptr) {
     init_symbol_ptr = findSymbol("MinifiInitExtension");
   }
   if (!init_symbol_ptr) {
-    logger_->log_error("No initializer for '{}'", name_);
+    logger_->log_error("No initializer for '{}'", library_name_);
     return false;
   }
-  logger_->log_debug("Found initializer for '{}'", name_);
-  auto init_fn = reinterpret_cast<MinifiExtension*(*)(MinifiConfig*)>(init_symbol_ptr);
+  logger_->log_debug("Found initializer for '{}'", library_name_);
+  auto init_fn = reinterpret_cast<void(*)(MinifiExtension*, MinifiConfig*)>(init_symbol_ptr);
   auto config_handle = reinterpret_cast<MinifiConfig*>(configure.get());
-  info_.reset(reinterpret_cast<Info*>(init_fn(config_handle)));
+  auto extension_handle = reinterpret_cast<MinifiExtension*>(this);
+  init_fn(extension_handle, config_handle);
   if (!info_) {
-    logger_->log_error("Failed to initialize extension '{}'", name_);
+    logger_->log_error("Failed to initialize extension '{}'", library_name_);
     return false;
   }
-  logger_->log_debug("Initialized extension '{}'", name_);
+  logger_->log_debug("Initialized extension '{}', name = '{}', version = '{}'", library_name_, info_->name, info_->version);
   return true;
 }
 
diff --git a/libminifi/src/minifi-c.cpp b/libminifi/src/minifi-c.cpp
index e670c4415..0936f4808 100644
--- a/libminifi/src/minifi-c.cpp
+++ b/libminifi/src/minifi-c.cpp
@@ -225,10 +225,9 @@ void useCProcessorClassDescription(const MinifiProcessorClassDefinition& class_d
 
 extern "C" {
 
-MinifiExtension* MinifiCreateExtension(const MinifiExtensionCreateInfo* extension_create_info) {
+MinifiStatus MinifiCreateExtension(MinifiExtension* extension, const MinifiExtensionCreateInfo* extension_create_info) {
+  gsl_Assert(extension);
   gsl_Assert(extension_create_info);
-  auto current_extension = minifi::core::extension::ExtensionManager::getExtensionBeingInitialized();
-  gsl_Assert(current_extension);
   auto extension_name = toString(extension_create_info->name);
   minifi::BundleIdentifier bundle{
     .name = extension_name,
@@ -243,16 +242,20 @@ MinifiExtension* MinifiCreateExtension(const MinifiExtensionCreateInfo* extensio
       bundle_components.processors.emplace_back(description);
     });
   }
-  return reinterpret_cast<MinifiExtension*>(new org::apache::nifi::minifi::core::extension::Extension::Info{
+  bool success = reinterpret_cast<org::apache::nifi::minifi::core::extension::Extension*>(extension)->setInfo(org::apache::nifi::minifi::core::extension::Extension::Info{
     .name = toString(extension_create_info->name),
     .version = toString(extension_create_info->version),
     .deinit = extension_create_info->deinit,
     .user_data = extension_create_info->user_data
   });
+  if (success) {
+    return MINIFI_STATUS_SUCCESS;
+  }
+  return MINIFI_STATUS_UNKNOWN_ERROR;
 }
 
-MinifiExtension* MINIFI_CREATE_EXTENSION_FN(const MinifiExtensionCreateInfo* extension_create_info) {
-  return MinifiCreateExtension(extension_create_info);
+MinifiStatus MINIFI_CREATE_EXTENSION_FN(MinifiExtension* extension, const MinifiExtensionCreateInfo* extension_create_info) {
+  return MinifiCreateExtension(extension, extension_create_info);
 }
 
 MinifiStatus MinifiProcessContextGetProperty(MinifiProcessContext* context, MinifiStringView property_name, MinifiFlowFile* flowfile,
diff --git a/libminifi/test/integration/extension-verification-test/CApiExtension.cpp b/libminifi/test/integration/extension-verification-test/CApiExtension.cpp
index e6fd7cfbc..bbd51809a 100644
--- a/libminifi/test/integration/extension-verification-test/CApiExtension.cpp
+++ b/libminifi/test/integration/extension-verification-test/CApiExtension.cpp
@@ -24,9 +24,9 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
-extern "C" const char* const MinifiApiVersion = MINIFI_TEST_API_VERSION;
+extern "C" const uint32_t MinifiApiVersion = MINIFI_TEST_API_VERSION;
 
-extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
+extern "C" void MinifiInitExtension(MinifiExtension* extension, MinifiConfig* /*config*/) {
   MinifiExtensionCreateInfo ext_create_info{
     .name = minifi::api::utils::toStringView(MAKESTRING(EXTENSION_NAME)),
     .version = minifi::api::utils::toStringView(MAKESTRING(EXTENSION_VERSION)),
@@ -35,5 +35,5 @@ extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr,
   };
-  return MinifiCreateExtension(&ext_create_info);
+  MinifiCreateExtension(extension, &ext_create_info);
 }
\ No newline at end of file
diff --git a/libminifi/test/integration/extension-verification-test/CMakeLists.txt b/libminifi/test/integration/extension-verification-test/CMakeLists.txt
index 32c068106..fa428b044 100644
--- a/libminifi/test/integration/extension-verification-test/CMakeLists.txt
+++ b/libminifi/test/integration/extension-verification-test/CMakeLists.txt
@@ -41,7 +41,7 @@ function(create_c_test_extension extension-name custom_api_version)
     set(extension-name "test-extension-loading-${extension-name}")
     create_loading_test_extension(${extension-name} CApiExtension.cpp)
     target_link_libraries(${extension-name} minifi-cpp-extension-lib)
-    target_compile_definitions(${extension-name} PRIVATE MINIFI_TEST_API_VERSION="${custom_api_version}")
+    target_compile_definitions(${extension-name} PRIVATE MINIFI_TEST_API_VERSION=${custom_api_version})
     target_compile_definitions(${extension-name}
             PRIVATE "EXTENSION_NAME=${extension-name}" "EXTENSION_VERSION=${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
 endfunction()
@@ -69,11 +69,13 @@ create_loading_test_extension(test-extension-loading-valid-build-id-cpp CppApiEx
 target_link_libraries(test-extension-loading-valid-build-id-cpp dummy-core-minifi)
 target_compile_definitions(test-extension-loading-valid-build-id-cpp PRIVATE MINIFI_CREATE_EXTENSION_FN=MinifiCreateCppExtension_${BUILD_IDENTIFIER})
 
-create_c_test_extension(same-version "1.1.1")
-create_c_test_extension(greater-major-version "2.0.0")
-create_c_test_extension(smaller-major-version "0.1.0")
-create_c_test_extension(greater-minor-version "1.2.0")
-create_c_test_extension(smaller-minor-version "1.0.1")
-create_c_test_extension(greater-patch-version "1.1.2")
-create_c_test_extension(smaller-patch-version "1.1.0")
+create_c_test_extension(max-version 10)
+create_c_test_extension(valid-version 7)
+create_c_test_extension(min-version 5)
+create_c_test_extension(greater-version 11)
+create_c_test_extension(smaller-version 4)
 create_loading_test_extension(test-extension-loading-missing-init InvalidMissingInitExtension.cpp)
+
+create_loading_test_extension(test-extension-loading-create-not-called CreateNotCalled.cpp)
+target_link_libraries(test-extension-loading-create-not-called minifi-c-api)
+target_compile_definitions(test-extension-loading-create-not-called PRIVATE MINIFI_TEST_API_VERSION=10)
diff --git a/libminifi/test/integration/extension-verification-test/CppApiExtension.cpp b/libminifi/test/integration/extension-verification-test/CppApiExtension.cpp
index 0503a8c17..59daee45d 100644
--- a/libminifi/test/integration/extension-verification-test/CppApiExtension.cpp
+++ b/libminifi/test/integration/extension-verification-test/CppApiExtension.cpp
@@ -24,7 +24,7 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
-extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
+extern "C" void MinifiInitCppExtension(MinifiExtension* extension, MinifiConfig* /*config*/) {
   MinifiExtensionCreateInfo ext_create_info{
     .name = minifi::utils::toStringView(MAKESTRING(MODULE_NAME)),
     .version = minifi::utils::toStringView("1.0.0"),
@@ -33,5 +33,5 @@ extern "C" MinifiExtension* MinifiInitCppExtension(MinifiConfig* /*config*/) {
     .processors_count = 0,
     .processors_ptr = nullptr
   };
-  return minifi::utils::MinifiCreateCppExtension(&ext_create_info);
+  minifi::utils::MinifiCreateCppExtension(extension, &ext_create_info);
 }
diff --git a/libminifi/include/core/extension/ApiVersion.h b/libminifi/test/integration/extension-verification-test/CreateNotCalled.cpp
similarity index 60%
copy from libminifi/include/core/extension/ApiVersion.h
copy to libminifi/test/integration/extension-verification-test/CreateNotCalled.cpp
index 01acb82cb..ec6e5394d 100644
--- a/libminifi/include/core/extension/ApiVersion.h
+++ b/libminifi/test/integration/extension-verification-test/CreateNotCalled.cpp
@@ -1,5 +1,6 @@
 /**
-* Licensed to the Apache Software Foundation (ASF) under one or more
+*
+ * 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
@@ -15,23 +16,10 @@
  * limitations under the License.
  */
 
-#pragma once
+#include "minifi-c.h"
 
-#include  <string>
+extern "C" const uint32_t MinifiApiVersion = MINIFI_TEST_API_VERSION;
 
-namespace org::apache::nifi::minifi::core::extension {
-
-struct ApiVersion {
-  int major;
-  int minor;
-  int patch;
-
-  std::string str() const {
-    return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch);
-  }
-};
-
-ApiVersion getAgentApiVersion();
-void setAgentApiVersion(ApiVersion api_version);
-
-}  // namespace org::apache::nifi::minifi::core::extension
+extern "C" void MinifiInitExtension(MinifiExtension* extension, MinifiConfig* /*config*/) {
+  // NOT CALLING CREATE
+}
\ No newline at end of file
diff --git a/libminifi/test/integration/extension-verification-test/ExtensionVerificationTests.cpp b/libminifi/test/integration/extension-verification-test/ExtensionVerificationTests.cpp
index 6fa304691..a31700bf7 100644
--- a/libminifi/test/integration/extension-verification-test/ExtensionVerificationTests.cpp
+++ b/libminifi/test/integration/extension-verification-test/ExtensionVerificationTests.cpp
@@ -32,7 +32,8 @@ class ExtensionLoadingTestController {
     LogTestController::getInstance().clear();
     LogTestController::getInstance().setTrace<core::extension::ExtensionManager>();
     LogTestController::getInstance().setTrace<core::extension::Extension>();
-    minifi::core::extension::setAgentApiVersion({.major = 1, .minor = 1, .patch = 1});
+    minifi::core::extension::test_setAgentApiVersion(10);
+    minifi::core::extension::test_setMinSupportedApiVersion(5);
     auto config = minifi::Configure::create();
     config->set(minifi::Configuration::nifi_extension_path, pattern);
     return config;
@@ -46,9 +47,9 @@ TEST_CASE("Can load cpp-api extensions with same build id") {
   REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded cpp extension 'test-extension-loading-valid-build-id-cpp'"));
 }
 
-TEST_CASE("Can load c-api extensions with same version") {
-  ExtensionLoadingTestController controller{"*test-extension-loading-same-version*"};
-  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded c extension 'test-extension-loading-same-version'"));
+TEST_CASE("Can load c-api extensions with max version") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-max-version*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded c extension 'test-extension-loading-max-version'"));
 }
 
 TEST_CASE("Can't load cpp-api extensions with different build id") {
@@ -56,35 +57,35 @@ TEST_CASE("Can't load cpp-api extensions with different build id") {
   REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Failed to load extension 'test-extension-loading-invalid-build-id-cpp'"));
 }
 
-TEST_CASE("Can't load c-api extensions with greater major version") {
-  ExtensionLoadingTestController controller{"*test-extension-loading-greater-major-version*"};
-  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Failed to load c extension 'test-extension-loading-greater-major-version'"));
-  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Api major version mismatch, application is '1.1.1' while extension is '2.0.0'"));
+TEST_CASE("Can load c-api extensions with min version") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-min-version*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded c extension 'test-extension-loading-min-version'"));
 }
 
-TEST_CASE("Can't load c-api extensions with smaller major version") {
-  ExtensionLoadingTestController controller{"*test-extension-loading-smaller-major-version*"};
-  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Failed to load c extension 'test-extension-loading-smaller-major-version'"));
-  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Api major version mismatch, application is '1.1.1' while extension is '0.1.0'"));
+TEST_CASE("Can load c-api extensions with valid version") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-valid-version*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded c extension 'test-extension-loading-valid-version'"));
 }
 
-TEST_CASE("Can't load c-api extensions with greater minor version") {
-  ExtensionLoadingTestController controller{"*test-extension-loading-greater-minor-version*"};
-  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Failed to load c extension 'test-extension-loading-greater-minor-version'"));
+TEST_CASE("Can't load c-api extensions with greater version") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-greater-version*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Failed to load c extension 'test-extension-loading-greater-version'"));
   REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Extension is built for a newer version"));
 }
 
-TEST_CASE("Can load c-api extensions with smaller minor version") {
-  ExtensionLoadingTestController controller{"*test-extension-loading-smaller-minor-version*"};
-  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded c extension 'test-extension-loading-smaller-minor-version'"));
+TEST_CASE("Can't load c-api extensions with smaller version") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-smaller-version*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Failed to load c extension 'test-extension-loading-smaller-version'"));
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Api version is no longer supported, application supports 5-10 while extension is 4"));
 }
 
-TEST_CASE("Can load c-api extensions with greater patch version") {
-  ExtensionLoadingTestController controller{"*test-extension-loading-greater-patch-version*"};
-  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded c extension 'test-extension-loading-greater-patch-version'"));
+TEST_CASE("Can't load c-api extensions with no MinifiInitExtension function") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-missing-init*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Failed to load as c extension 'test-extension-loading-missing-init'"));
 }
 
-TEST_CASE("Can load c-api extensions with smaller patch version") {
-  ExtensionLoadingTestController controller{"*test-extension-loading-smaller-patch-version*"};
-  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Loaded c extension 'test-extension-loading-smaller-patch-version'"));
+TEST_CASE("Can't load c-api extensions with no MinifiCreateExtension call") {
+  ExtensionLoadingTestController controller{"*test-extension-loading-create-not-called*"};
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Failed to initialize extension 'test-extension-loading-create-not-called'"));
 }
+
diff --git a/minifi-api/include/minifi-c/minifi-c.h b/minifi-api/include/minifi-c/minifi-c.h
index 5bab74941..9a80b5791 100644
--- a/minifi-api/include/minifi-c/minifi-c.h
+++ b/minifi-api/include/minifi-c/minifi-c.h
@@ -34,11 +34,6 @@ extern "C" {
 #define MINIFI_PRIVATE_JOIN_HELPER(X, Y) X ## _ ## Y
 #define MINIFI_PRIVATE_JOIN(X, Y) MINIFI_PRIVATE_JOIN_HELPER(X, Y)
 
-#define MINIFI_API_MAJOR_VERSION 0
-#define MINIFI_API_MINOR_VERSION 1
-#define MINIFI_API_PATCH_VERSION 0
-#define MINIFI_API_VERSION MINIFI_PRIVATE_STRINGIFY(MINIFI_API_MAJOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_API_MINOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_API_PATCH_VERSION)
-
 #define MINIFI_NULL nullptr
 #define MINIFI_OWNED
 
@@ -46,6 +41,10 @@ extern "C" {
 #define MINIFI_CREATE_EXTENSION_FN MinifiCreateExtension
 #endif
 
+enum : uint32_t {
+  MINIFI_API_VERSION = 1
+};
+
 typedef bool MinifiBool;
 
 typedef enum MinifiInputRequirement : uint32_t {
@@ -189,7 +188,7 @@ typedef struct MinifiExtensionCreateInfo {
   const MinifiProcessorClassDefinition* processors_ptr;
 } MinifiExtensionCreateInfo;
 
-MinifiExtension* MINIFI_CREATE_EXTENSION_FN(const MinifiExtensionCreateInfo* create_info);
+MinifiStatus MINIFI_CREATE_EXTENSION_FN(MinifiExtension* extension, const MinifiExtensionCreateInfo* create_info);
 
 MINIFI_OWNED MinifiPublishedMetrics* MinifiPublishedMetricsCreate(size_t count, const MinifiStringView* metric_names, const double* metric_values);
 


(nifi-minifi-cpp) 01/07: MINIFICPP-2715 - Use symbols to check api compatibility

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

martinzink pushed a commit to branch controller_c_api_requirements
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit a91703120b436f5af131e4742b237712ccaa0bb5
Author: Adam Debreceni <ad...@apache.org>
AuthorDate: Wed Feb 11 14:39:34 2026 +0100

    MINIFICPP-2715 - Use symbols to check api compatibility
---
 Extensions.md                                      |   4 +-
 cmake/Extensions.cmake                             |  23 +----
 .../SFTPLoader.cpp => ExtensionInitializer.cpp}    |  19 +---
 .../llamacpp/processors/ExtensionInitializer.cpp   |   4 +-
 extensions/opencv/CMakeLists.txt                   |   1 +
 extensions/opencv/OpenCVLoader.cpp                 |   2 +-
 extensions/python/CMakeLists.txt                   |   2 +
 .../python/pythonlibloader/PythonLibLoader.cpp     |   2 +-
 extensions/python/pythonloader/PyProcLoader.cpp    |   2 +-
 extensions/sftp/CMakeLists.txt                     |   1 +
 extensions/sftp/SFTPLoader.cpp                     |   2 +-
 libminifi/include/core/extension/Utils.h           |  26 -----
 libminifi/src/ApiVersions.cpp                      |  31 ++++++
 libminifi/src/core/extension/Extension.cpp         |   2 +-
 libminifi/src/core/extension/ExtensionManager.cpp  |   8 --
 libminifi/src/core/extension/Utils.cpp             |  65 -------------
 libminifi/test/unit/ExtensionVerificationTests.cpp | 107 ---------------------
 minifi-api/CMakeLists.txt                          |   1 +
 minifi-api/include/minifi-c/minifi-c.h             |  17 +++-
 19 files changed, 63 insertions(+), 256 deletions(-)

diff --git a/Extensions.md b/Extensions.md
index 7d3fd5392..3a82e212f 100644
--- a/Extensions.md
+++ b/Extensions.md
@@ -33,10 +33,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 `MinifiInitExtension` function of type `MinifiExtension*(MinifiConfig*)` to be called.
 
 ```C++
-extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
+extern "C" MinifiExtension* MinifiInitExtension(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;
diff --git a/cmake/Extensions.cmake b/cmake/Extensions.cmake
index 5786967a8..6ea93f1cb 100644
--- a/cmake/Extensions.cmake
+++ b/cmake/Extensions.cmake
@@ -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}")
diff --git a/extensions/sftp/SFTPLoader.cpp b/extensions/ExtensionInitializer.cpp
similarity index 77%
copy from extensions/sftp/SFTPLoader.cpp
copy to extensions/ExtensionInitializer.cpp
index 8a67842de..3caaaeed0 100644
--- a/extensions/sftp/SFTPLoader.cpp
+++ b/extensions/ExtensionInitializer.cpp
@@ -15,31 +15,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-#include "minifi-cpp/agent/agent_version.h"
-#include "minifi-cpp/properties/Configure.h"
-#include "client/SFTPClient.h"
 #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* InitExtension(MinifiConfig* /*config*/) {
-  if (libssh2_init(0) != 0) {
-    return nullptr;
-  }
-  if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
-    libssh2_exit();
-    return nullptr;
-  }
+extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
   MinifiExtensionCreateInfo ext_create_info{
     .name = minifi::utils::toStringView(MAKESTRING(MODULE_NAME)),
     .version = minifi::utils::toStringView(minifi::AgentBuild::VERSION),
-    .deinit = [] (void* /*user_data*/) {
-      curl_global_cleanup();
-      libssh2_exit();
-    },
+    .deinit = nullptr,
     .user_data = nullptr,
     .processors_count = 0,
     .processors_ptr = nullptr
diff --git a/extensions/llamacpp/processors/ExtensionInitializer.cpp b/extensions/llamacpp/processors/ExtensionInitializer.cpp
index 40508955c..b90b8b133 100644
--- a/extensions/llamacpp/processors/ExtensionInitializer.cpp
+++ b/extensions/llamacpp/processors/ExtensionInitializer.cpp
@@ -24,7 +24,7 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
-extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
+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{
@@ -39,5 +39,3 @@ extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
   });
   return extension;
 }
-
-extern const char* const MINIFI_API_VERSION_TAG_var = MINIFI_API_VERSION_TAG;
diff --git a/extensions/opencv/CMakeLists.txt b/extensions/opencv/CMakeLists.txt
index ed812c1a6..3c80e3efd 100644
--- a/extensions/opencv/CMakeLists.txt
+++ b/extensions/opencv/CMakeLists.txt
@@ -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)
diff --git a/extensions/opencv/OpenCVLoader.cpp b/extensions/opencv/OpenCVLoader.cpp
index 79a437f75..3f91395c4 100644
--- a/extensions/opencv/OpenCVLoader.cpp
+++ b/extensions/opencv/OpenCVLoader.cpp
@@ -24,7 +24,7 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
-extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
+extern "C" MinifiExtension* MinifiInitExtension(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:
diff --git a/extensions/python/CMakeLists.txt b/extensions/python/CMakeLists.txt
index ac3f5fe57..34b1df673 100644
--- a/extensions/python/CMakeLists.txt
+++ b/extensions/python/CMakeLists.txt
@@ -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)
 
diff --git a/extensions/python/pythonlibloader/PythonLibLoader.cpp b/extensions/python/pythonlibloader/PythonLibLoader.cpp
index 398322f8f..cdf0356b7 100644
--- a/extensions/python/pythonlibloader/PythonLibLoader.cpp
+++ b/extensions/python/pythonlibloader/PythonLibLoader.cpp
@@ -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* MinifiInitExtension(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) {
diff --git a/extensions/python/pythonloader/PyProcLoader.cpp b/extensions/python/pythonloader/PyProcLoader.cpp
index 3778f15ec..55ad4ebef 100644
--- a/extensions/python/pythonloader/PyProcLoader.cpp
+++ b/extensions/python/pythonloader/PyProcLoader.cpp
@@ -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* MinifiInitExtension(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) {
diff --git a/extensions/sftp/CMakeLists.txt b/extensions/sftp/CMakeLists.txt
index c5c1cb6a7..7200b41ba 100644
--- a/extensions/sftp/CMakeLists.txt
+++ b/extensions/sftp/CMakeLists.txt
@@ -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)
diff --git a/extensions/sftp/SFTPLoader.cpp b/extensions/sftp/SFTPLoader.cpp
index 8a67842de..bb23e9998 100644
--- a/extensions/sftp/SFTPLoader.cpp
+++ b/extensions/sftp/SFTPLoader.cpp
@@ -25,7 +25,7 @@
 
 namespace minifi = org::apache::nifi::minifi;
 
-extern "C" MinifiExtension* InitExtension(MinifiConfig* /*config*/) {
+extern "C" MinifiExtension* MinifiInitExtension(MinifiConfig* /*config*/) {
   if (libssh2_init(0) != 0) {
     return nullptr;
   }
diff --git a/libminifi/include/core/extension/Utils.h b/libminifi/include/core/extension/Utils.h
index 520827193..c983a25f9 100644
--- a/libminifi/include/core/extension/Utils.h
+++ b/libminifi/include/core/extension/Utils.h
@@ -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;
diff --git a/libminifi/src/ApiVersions.cpp b/libminifi/src/ApiVersions.cpp
new file mode 100644
index 000000000..5bff5c020
--- /dev/null
+++ b/libminifi/src/ApiVersions.cpp
@@ -0,0 +1,31 @@
+/**
+* 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 "minifi-cpp/utils/Export.h"
+
+extern "C" {
+
+MINIFIAPI extern const char* const MINIFI_API_VERSION = MINIFI_PRIVATE_STRINGIFY(MINIFI_API_VERSION);
+
+#define REGISTER_C_API_VERSION(major, minor) \
+    MINIFIAPI extern const char* const MINIFI_PRIVATE_JOIN(MINIFI_C_API_VERSION, MINIFI_PRIVATE_JOIN(major, minor)) = \
+    MINIFI_PRIVATE_STRINGIFY(MINIFI_C_API_MAJOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_C_API_MINOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_C_API_PATCH_VERSION);
+
+REGISTER_C_API_VERSION(0, 1)
+
+}  // extern "C"
diff --git a/libminifi/src/core/extension/Extension.cpp b/libminifi/src/core/extension/Extension.cpp
index 8b7b7f740..3034c8a8f 100644
--- a/libminifi/src/core/extension/Extension.cpp
+++ b/libminifi/src/core/extension/Extension.cpp
@@ -113,7 +113,7 @@ Extension::~Extension() {
 
 bool Extension::initialize(const std::shared_ptr<minifi::Configure>& configure) {
   logger_->log_trace("Initializing extension '{}'", name_);
-  if (void* init_symbol_ptr = findSymbol("InitExtension")) {
+  if (void* init_symbol_ptr = findSymbol("MinifiInitExtension")) {
     logger_->log_debug("Found custom initializer for '{}'", name_);
     auto init_fn = reinterpret_cast<MinifiExtension*(*)(MinifiConfig*)>(init_symbol_ptr);
     auto config_handle = reinterpret_cast<MinifiConfig*>(configure.get());
diff --git a/libminifi/src/core/extension/ExtensionManager.cpp b/libminifi/src/core/extension/ExtensionManager.cpp
index b0ce8882f..0b144a251 100644
--- a/libminifi/src/core/extension/ExtensionManager.cpp
+++ b/libminifi/src/core/extension/ExtensionManager.cpp
@@ -58,14 +58,6 @@ ExtensionManager::ExtensionManager(const std::shared_ptr<Configure>& config): lo
     if (!library) {
       continue;
     }
-    const auto library_type = library->verify(logger_);
-    if (library_type == internal::Invalid) {
-      logger_->log_warn("Skipping library '{}' at '{}': failed verification, different build?",
-          library->name, library->getFullPath());
-      continue;
-    }
-
-    logger_->log_trace("Verified library {} at {} as {} extension", library->name, library->getFullPath(), magic_enum::enum_name(library_type));
     auto extension = std::make_unique<Extension>(library->name, library->getFullPath());
     if (!extension->load()) {
       // error already logged by method
diff --git a/libminifi/src/core/extension/Utils.cpp b/libminifi/src/core/extension/Utils.cpp
deleted file mode 100644
index 38e90ac15..000000000
--- a/libminifi/src/core/extension/Utils.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * 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 "core/extension/Utils.h"
-#include "minifi-c/minifi-c.h"
-#include "minifi-cpp/agent/agent_version.h"
-#include "utils/RegexUtils.h"
-
-namespace org::apache::nifi::minifi::core::extension::internal {
-
-[[nodiscard]] bool LibraryDescriptor::verify_as_cpp_extension() const {
-  const auto path = getFullPath();
-  if (!std::filesystem::exists(path)) {
-    throw std::runtime_error{"File not found: " + path.string()};
-  }
-  const std::string_view begin_marker = "__EXTENSION_BUILD_IDENTIFIER_BEGIN__";
-  const std::string_view end_marker = "__EXTENSION_BUILD_IDENTIFIER_END__";
-  const std::string magic_constant = utils::string::join_pack(begin_marker, AgentBuild::BUILD_IDENTIFIER, end_marker);
-  return utils::file::contains(path, magic_constant);
-}
-
-[[nodiscard]] bool LibraryDescriptor::verify_as_c_extension(const std::shared_ptr<logging::Logger>& logger) const {
-  const auto path = getFullPath();
-  if (!std::filesystem::exists(path)) {
-    throw std::runtime_error{"File not found: " + path.string()};
-  }
-  const std::string_view api_tag_prefix = "MINIFI_API_VERSION=[";
-  if (auto version = utils::file::findSubstringWithPrefix(path, api_tag_prefix, api_tag_prefix.size() + 20)) {
-    utils::SVMatch match;
-    if (!utils::regexSearch(version.value(), match, utils::Regex{R"(MINIFI_API_VERSION=\[([0-9]+)\.([0-9]+)\.([0-9]+)\])"})) {
-      logger->log_error("Found api version in invalid format: '{}'", version.value());
-      return false;
-    }
-    gsl_Assert(match.size() == 4);
-    const int major = std::stoi(match[1]);
-    const int minor = std::stoi(match[2]);
-    const int patch = std::stoi(match[3]);
-    if (major != MINIFI_API_MAJOR_VERSION) {
-      logger->log_error("API major version mismatch, application is '{}' while extension is '{}.{}.{}'", MINIFI_API_VERSION, major, minor, patch);
-      return false;
-    }
-    if (minor > MINIFI_API_MINOR_VERSION) {
-      logger->log_error("Extension is built for a newer version, application is '{}' while extension is '{}.{}.{}'", MINIFI_API_VERSION, major, minor, patch);
-      return false;
-    }
-    return true;
-  }
-  return false;
-}
-
-}  // namespace org::apache::nifi::minifi::core::extension::internal
diff --git a/libminifi/test/unit/ExtensionVerificationTests.cpp b/libminifi/test/unit/ExtensionVerificationTests.cpp
deleted file mode 100644
index 71a425ada..000000000
--- a/libminifi/test/unit/ExtensionVerificationTests.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * 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.
- */
-#define CUSTOM_EXTENSION_INIT
-
-#include <filesystem>
-#include "unit/TestBase.h"
-#include "unit/Catch.h"
-#include "minifi-cpp/agent/agent_version.h"
-#include "core/extension/Utils.h"
-#include "unit/TestUtils.h"
-#include "minifi-c/minifi-c.h"
-
-using namespace std::literals;
-
-namespace {
-
-#if defined(WIN32)
-const std::string extension_file = "extension.dll";
-#elif defined(__APPLE__)
-const std::string extension_file = "libextension.dylib";
-#else
-const std::string extension_file = "libextension.so";
-#endif
-
-
-struct Fixture : public TestController {
-  Fixture() {
-    extension_ = createTempDirectory() / extension_file;
-  }
-  std::filesystem::path extension_;
-};
-
-const std::shared_ptr<logging::Logger> logger{core::logging::LoggerFactory<Fixture>::getLogger()};
-
-}  // namespace
-
-TEST_CASE_METHOD(Fixture, "Could load extension with matching build id") {
-  std::ofstream{extension_} << "__EXTENSION_BUILD_IDENTIFIER_BEGIN__"
-      << minifi::AgentBuild::BUILD_IDENTIFIER << "__EXTENSION_BUILD_IDENTIFIER_END__";
-
-  auto lib = minifi::core::extension::internal::asDynamicLibrary(extension_);
-  REQUIRE(lib);
-  CHECK(lib->verify(logger) == core::extension::internal::Cpp);
-}
-
-TEST_CASE_METHOD(Fixture, "Could load extension with matching C api") {
-  std::ofstream{extension_} << MINIFI_API_VERSION_TAG;
-
-  auto lib = minifi::core::extension::internal::asDynamicLibrary(extension_);
-  REQUIRE(lib);
-  CHECK(lib->verify(logger) == core::extension::internal::CApi);
-}
-
-TEST_CASE_METHOD(Fixture, "Can't load extension if the build id begin marker is missing") {
-  std::ofstream{extension_} << "__MISSING_BEGIN__"
-      << minifi::AgentBuild::BUILD_IDENTIFIER << "__EXTENSION_BUILD_IDENTIFIER_END__";
-
-  auto lib = minifi::core::extension::internal::asDynamicLibrary(extension_);
-  REQUIRE(lib);
-  CHECK(lib->verify(logger) == core::extension::internal::Invalid);
-}
-
-TEST_CASE_METHOD(Fixture, "Can't load extension if the build id end marker is missing") {
-  std::ofstream{extension_} << "__EXTENSION_BUILD_IDENTIFIER_BEGIN__"
-      << minifi::AgentBuild::BUILD_IDENTIFIER << "__MISSING_END__";
-
-  auto lib = minifi::core::extension::internal::asDynamicLibrary(extension_);
-  REQUIRE(lib);
-  CHECK(lib->verify(logger) == core::extension::internal::Invalid);
-}
-
-TEST_CASE_METHOD(Fixture, "Can't load extension if the build id does not match") {
-  std::ofstream{extension_} << "__EXTENSION_BUILD_IDENTIFIER_BEGIN__"
-      << "not the build id" << "__EXTENSION_BUILD_IDENTIFIER_END__";
-
-  auto lib = minifi::core::extension::internal::asDynamicLibrary(extension_);
-  REQUIRE(lib);
-  CHECK(lib->verify(logger) == core::extension::internal::Invalid);
-}
-
-TEST_CASE_METHOD(Fixture, "Can't load extension if the file does not exist") {
-  auto lib = minifi::core::extension::internal::asDynamicLibrary(extension_);
-  REQUIRE(lib);
-  REQUIRE_THROWS_AS(lib->verify(logger), std::runtime_error);
-}
-
-TEST_CASE_METHOD(Fixture, "Can't load extension if the file has zero length") {
-  std::ofstream{extension_};  // NOLINT(bugprone-unused-raii)
-
-  auto lib = minifi::core::extension::internal::asDynamicLibrary(extension_);
-  REQUIRE(lib);
-  CHECK(lib->verify(logger) == core::extension::internal::Invalid);
-}
diff --git a/minifi-api/CMakeLists.txt b/minifi-api/CMakeLists.txt
index 95fbb885c..7a758dbf8 100644
--- a/minifi-api/CMakeLists.txt
+++ b/minifi-api/CMakeLists.txt
@@ -6,6 +6,7 @@ target_compile_definitions(minifi-api-common INTERFACE MINIFI_VERSION_STR="${MIN
 add_library(minifi-api INTERFACE)
 target_include_directories(minifi-api INTERFACE include)
 target_link_libraries(minifi-api INTERFACE minifi-api-common)
+target_compile_definitions(minifi-api INTERFACE MINIFI_API_VERSION=MINIFI_CPP_API_VERSION_${BUILD_IDENTIFIER})
 
 add_library(minifi-c-api INTERFACE)
 target_include_directories(minifi-c-api INTERFACE include/minifi-c)
diff --git a/minifi-api/include/minifi-c/minifi-c.h b/minifi-api/include/minifi-c/minifi-c.h
index 5be2bbb2f..afccd00c8 100644
--- a/minifi-api/include/minifi-c/minifi-c.h
+++ b/minifi-api/include/minifi-c/minifi-c.h
@@ -31,11 +31,18 @@ extern "C" {
 #define MINIFI_PRIVATE_STRINGIFY_HELPER(X) #X
 #define MINIFI_PRIVATE_STRINGIFY(X) MINIFI_PRIVATE_STRINGIFY_HELPER(X)
 
-#define MINIFI_API_MAJOR_VERSION 0
-#define MINIFI_API_MINOR_VERSION 1
-#define MINIFI_API_PATCH_VERSION 0
-#define MINIFI_API_VERSION MINIFI_PRIVATE_STRINGIFY(MINIFI_API_MAJOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_API_MINOR_VERSION) "." MINIFI_PRIVATE_STRINGIFY(MINIFI_API_PATCH_VERSION)
-#define MINIFI_API_VERSION_TAG "MINIFI_API_VERSION=[" MINIFI_API_VERSION "]"
+#define MINIFI_PRIVATE_JOIN_HELPER(X, Y) X ## _ ## Y
+#define MINIFI_PRIVATE_JOIN(X, Y) MINIFI_PRIVATE_JOIN_HELPER(X, Y)
+
+#define MINIFI_C_API_MAJOR_VERSION 0
+#define MINIFI_C_API_MINOR_VERSION 1
+#define MINIFI_C_API_PATCH_VERSION 0
+
+#ifndef MINIFI_API_VERSION
+#define MINIFI_API_VERSION MINIFI_PRIVATE_JOIN(MINIFI_C_API_VERSION, MINIFI_PRIVATE_JOIN(MINIFI_C_API_MAJOR_VERSION, MINIFI_C_API_MINOR_VERSION))
+#endif
+extern const char* const MINIFI_API_VERSION;
+
 #define MINIFI_NULL nullptr
 #define MINIFI_OWNED