blob: 398322f8fba4f6044fffe30973f3f8f3cb98acb8 [file] [log] [blame]
/**
*
* 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 <dlfcn.h>
#include <cstdio>
#include <iostream>
#include <string>
#include <array>
#include "utils/StringUtils.h"
#include "core/logging/LoggerFactory.h"
#include "minifi-cpp/agent/agent_version.h"
#include "minifi-c/minifi-c.h"
#include "utils/ExtensionInitUtils.h"
#include "core/Resource.h"
#if defined(WIN32)
static_assert(false, "The Python library loader should only be used on Linux or macOS.");
#endif
namespace minifi = org::apache::nifi::minifi;
class PythonLibLoader {
public:
explicit PythonLibLoader(const minifi::utils::ConfigReader& config_reader) {
std::string python_command = "python3";
if (auto python_binary = config_reader(minifi::Configure::nifi_python_env_setup_binary)) {
python_command = python_binary.value();
}
#if defined(__APPLE__)
std::string command = python_command +
R"###( -c "import sysconfig, os, glob; v = sysconfig.get_config_vars(); lib_dir = v['LIBDIR']; version = v['VERSION']; so_paths = glob.glob(f'{lib_dir}/libpython{version}.dylib'); print(list(filter(os.path.exists, so_paths))[0])")###";
#else
std::string command = python_command +
R"###( -c "import sysconfig, os, glob; v = sysconfig.get_config_vars(); lib_dir = v['LIBDIR']; ld_lib = v['LDLIBRARY']; so_paths = glob.glob(f'{lib_dir}/*{ld_lib}*'); so_paths.extend(glob.glob(f'{lib_dir}/*/*{ld_lib}*')); print(list(filter(os.path.exists, so_paths))[0])")###";
#endif
auto lib_python_path = execCommand(command);
if (lib_python_path.empty()) {
logger_->log_error("Failed to find libpython path from specified python binary: {}", python_command);
throw std::runtime_error("Failed to find libpython path");
}
lib_python_handle_ = dlopen(lib_python_path.c_str(), RTLD_NOW | RTLD_GLOBAL);
if (!lib_python_handle_) {
logger_->log_error("Failed to load libpython from path '{}' with error: {}", lib_python_path, dlerror());
throw std::runtime_error("Failed to load libpython");
}
logger_->log_info("Loaded libpython from path '{}'", lib_python_path);
command = python_command + " -c \"import sys; print(sys.version_info.major << 24 | sys.version_info.minor << 16)\"";
auto loaded_version = std::stoi(execCommand(command));
if (loaded_version < Py_LIMITED_API) {
logger_->log_error("Loaded python version is not compatible with minimum python version, loaded version: {:#08X}, minimum python version: {:#08X}", loaded_version, Py_LIMITED_API);
throw std::runtime_error("Loaded python version is not compatible with minimum python version");
}
}
PythonLibLoader(PythonLibLoader&&) = delete;
PythonLibLoader(const PythonLibLoader&) = delete;
PythonLibLoader& operator=(PythonLibLoader&&) = delete;
PythonLibLoader& operator=(const PythonLibLoader&) = delete;
~PythonLibLoader() {
if (lib_python_handle_) {
dlclose(lib_python_handle_);
}
}
private:
static std::string execCommand(const std::string& cmd) {
std::array<char, 128> buffer{};
std::string result;
struct pclose_deleter { void operator()(FILE* file) noexcept { pclose(file); } };
std::unique_ptr<FILE, pclose_deleter> pipe{popen(cmd.c_str(), "r")};
if (!pipe) {
return "";
}
while (fgets(buffer.data(), static_cast<int>(buffer.size()), pipe.get()) != nullptr) {
result += buffer.data();
}
return minifi::utils::string::trim(result);
}
void* lib_python_handle_ = nullptr;
std::shared_ptr<minifi::core::logging::Logger> logger_ = minifi::core::logging::LoggerFactory<PythonLibLoader>::getLogger();
};
extern "C" MinifiExtension* InitExtension(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) {
*static_cast<std::optional<std::string>*>(user_data) = std::string{value.data, value.length};
}, &result);
return result;
});
MinifiExtensionCreateInfo ext_create_info{
.name = minifi::utils::toStringView(MAKESTRING(MODULE_NAME)),
.version = minifi::utils::toStringView(minifi::AgentBuild::VERSION),
.deinit = nullptr,
.user_data = nullptr,
.processors_count = 0,
.processors_ptr = nullptr
};
return MinifiCreateExtension(minifi::utils::toStringView(MINIFI_API_VERSION), &ext_create_info);
}