如何配置柯南不链接一切可能?

时间:2020-10-02 20:31:29

标签: c++ poco-libraries conan

我正在试用柯南软件包管理器,并开始编写一个使用Poco libraries的测试C ++项目。我制作了一个简单的程序,仅使用AES-256-CBC解密字符串。使用Conan和Cmake构建后,生成的二进制文件将近4 MB令我感到非常惊讶。我尝试调整conanfile.txtCmakeLists.txt文件以仅链接必要的库,但是我要么无法编译项目,要么无法减小已编译二进制文件的大小。

我非常确定PCRE,bzip2,SQLlite等将链接到我的二进制文件中,因为Poco依赖于它们。我对为什么gcc不够聪明无法弄清楚我正在调用的Poco代码仅使用少量的OpenSSL代码感到非常困惑。

如何只编译/链接所需的内容,并使二进制文件保持合理的大小?

conanfile.txt:

[requires]
poco/1.10.1

[generators]
cmake

CmakeLists.txt:

cmake_minimum_required(VERSION 3.7...3.18)

if(${CMAKE_VERSION} VERSION_LESS 3.12)
    cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
endif()

project(main)

add_definitions("-std=c++17")

include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()

add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS})

main.cpp:

#include <cstdlib>
#include <iostream>
#include <sstream>

#include "Poco/Base64Decoder.h"
#include "Poco/Crypto/Cipher.h"
#include "Poco/Crypto/CipherFactory.h"
#include "Poco/Crypto/CipherKey.h"
#include "Poco/DigestStream.h"
#include "Poco/SHA2Engine.h"


std::string sha512(std::string value);
std::string aesDecrypt(const std::string ciphertext, const std::string key, const std::string iv);
std::string getEnvVar(const std::string key);
std::string base64Decode(const std::string encoded);

int main(int argc, char** argv) {
    std::string enc = "Ug7R5BQIosmn1yPeawSUIzY8N9wzASmI/w0Wz/xX7Yw=";
    std::cout << aesDecrypt(enc, "admin", "7K/OkQIrl4rqUk8/1h+uuQ==") << "\n";

    std::cout << sha512("Hello there") << "\n";
    std::cout << getEnvVar("USER") << "\n";

    return 0;
}

std::string aesDecrypt(const std::string ciphertext, const std::string key, const std::string iv) {
    auto keyHash = sha512(key);
    Poco::Crypto::Cipher::ByteVec keyBytes{keyHash.begin(), keyHash.end()};
    auto rawIV = base64Decode(iv);
    Poco::Crypto::Cipher::ByteVec ivBytes{rawIV.begin(), rawIV.end()};

    auto &factory = Poco::Crypto::CipherFactory::defaultFactory();
    auto pCipher = factory.createCipher(Poco::Crypto::CipherKey("aes-256-cbc", keyBytes, ivBytes));

    return pCipher->decryptString(ciphertext, Poco::Crypto::Cipher::ENC_BASE64);
}

std::string sha512(const std::string value) {
    Poco::SHA2Engine sha256(Poco::SHA2Engine::SHA_512);
    Poco::DigestOutputStream ds(sha256);
    ds << value;
    ds.close();

    return Poco::DigestEngine::digestToHex(sha256.digest());
}

std::string getEnvVar(const std::string key) {
    char * val = getenv(key.c_str());
    return val == NULL ? std::string("") : std::string(val);
}

std::string base64Decode(const std::string encoded) {
    std::istringstream istr(encoded);
    std::ostringstream ostr;
    Poco::Base64Decoder b64in(istr);

    copy(std::istreambuf_iterator<char>(b64in),
    std::istreambuf_iterator<char>(),
    std::ostreambuf_iterator<char>(ostr));

    return ostr.str();
}

我如何构建代码:

#!/bin/bash

set -e
set -x

rm -rf build
mkdir build
pushd build

conan install ..
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build .

ls -lah bin/main
bin/main

2 个答案:

答案 0 :(得分:0)

您所做的没有任何问题。 poco可能有很多依赖性和功能。您可以在CMakeLists.txt中添加message(STATUS "Linkd libraries: " ${CONAN_LIBS}),然后再次运行cmake来查看使用${CONAN_LIBS}时当前与之链接的库。

您也可以尝试在CMakeLists.txt中使用conan_basic_setup(TARGETS),而不仅仅是conan_basic_setup()。如果这样做,则需要将target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS})更改为target_link_libraries(${PROJECT_NAME} CONAN_PKG::poco)。这样,您就可以更好地控制与conanfile.txt中的每个目标链接的CMaksLists.txt中的哪些库。但是,由于您的conanfile.txt仅具有poco作为依赖项,因此不应进行任何更改。

您可以尝试的另一件事是检查柯南的poco配方是否具有可以设置为包含/排除poco库部分的任何选项。运行以下命令(假设您的柯南缓存中已经安装了poco/1.10.1

conan get poco/1.10.1

这将向您显示柯南正在使用的poco的完整食谱。我到了

from conans import ConanFile, CMake, tools
from conans.errors import ConanException, ConanInvalidConfiguration
from collections import namedtuple, OrderedDict
import os


class PocoConan(ConanFile):
    name = "poco"
    url = "https://github.com/conan-io/conan-center-index"
    homepage = "https://pocoproject.org"
    topics = ("conan", "poco", "building", "networking", "server", "mobile", "embedded")
    exports_sources = "CMakeLists.txt", "patches/**"
    generators = "cmake", "cmake_find_package"
    settings = "os", "arch", "compiler", "build_type"
    license = "BSL-1.0"
    description = "Modern, powerful open source C++ class libraries for building network- and internet-based " \
                  "applications that run on desktop, server, mobile and embedded systems."
    options = {
        "shared": [True, False],
        "fPIC": [True, False],
    }
    default_options = {
        "shared": False,
        "fPIC": True,
    }

    _PocoComponent = namedtuple("_PocoComponent", ("option", "default_option", "dependencies", "is_lib"))
    _poco_component_tree = {
        "mod_poco": _PocoComponent("enable_apacheconnector", False, ("PocoUtil", "PocoNet", ), False),  # also external apr and apr-util
        "PocoCppParser": _PocoComponent("enable_cppparser", False, ("PocoFoundation", ), False),
        # "PocoCppUnit": _PocoComponent("enable_cppunit", False, ("PocoFoundation", ), False)),
        "PocoCrypto": _PocoComponent("enable_crypto", True, ("PocoFoundation", ), True),    # also external openssl
        "PocoData": _PocoComponent("enable_data", True, ("PocoFoundation", ), True),
        "PocoDataMySQL": _PocoComponent("enable_data_mysql", False, ("PocoData", ), True),
        "PocoDataODBC": _PocoComponent("enable_data_odbc", False, ("PocoData", ), True),
        "PocoDataPostgreSQL": _PocoComponent("enable_data_postgresql", False, ("PocoData", ), True),    # also external postgresql
        "PocoDataSQLite": _PocoComponent("enable_data_sqlite", True, ("PocoData", ), True),  # also external sqlite3
        "PocoEncodings": _PocoComponent("enable_encodings", True, ("PocoFoundation", ), True),
        # "PocoEncodingsCompiler": _PocoComponent("enable_encodingscompiler", False, ("PocoNet", "PocoUtil", ), False),
        "PocoFoundation": _PocoComponent(None, "PocoFoundation", (), True),
        "PocoJSON": _PocoComponent("enable_json", True, ("PocoFoundation", ), True),
        "PocoJWT": _PocoComponent("enable_jwt", True, ("PocoJSON", "PocoCrypto", ), True),
        "PocoMongoDB": _PocoComponent("enable_mongodb", True, ("PocoNet", ), True),
        "PocoNet": _PocoComponent("enable_net", True, ("PocoFoundation", ), True),
        "PocoNetSSL": _PocoComponent("enable_netssl", True, ("PocoCrypto", "PocoUtil", "PocoNet", ), True),    # also external openssl
        "PocoNetSSLWin": _PocoComponent("enable_netssl_win", True, ("PocoNet", "PocoUtil", ), True),
        "PocoPDF": _PocoComponent("enable_pdf", False, ("PocoXML", "PocoUtil", ), True),
        "PocoPageCompiler": _PocoComponent("enable_pagecompiler", False, ("PocoNet", "PocoUtil", ), False),
        "PocoFile2Page": _PocoComponent("enable_pagecompiler_file2page", False, ("PocoNet", "PocoUtil", "PocoXML", "PocoJSON", ), False),
        "PocoPocoDoc": _PocoComponent("enable_pocodoc", False, ("PocoUtil", "PocoXML", "PocoCppParser", ), False),
        "PocoRedis": _PocoComponent("enable_redis", True, ("PocoNet", ), True),
        "PocoSevenZip": _PocoComponent("enable_sevenzip", False, ("PocoUtil", "PocoXML", ), True),
        "PocoUtil": _PocoComponent("enable_util", True, ("PocoFoundation", "PocoXML", "PocoJSON", ), True),
        "PocoXML": _PocoComponent("enable_xml", True, ("PocoFoundation", ), True),
        "PocoZip": _PocoComponent("enable_zip", True, ("PocoUtil", "PocoXML", ), True),
    }
    
    for comp in _poco_component_tree.values():
        if comp.option:
            options[comp.option] = [True, False]
            default_options[comp.option] = comp.default_option
    del comp

    @property
    def _poco_ordered_components(self):
        remaining_components = dict((compname, set(compopts.dependencies)) for compname, compopts in self._poco_component_tree.items())
        ordered_components = []
        while remaining_components:
            components_no_deps = set(compname for compname, compopts in remaining_components.items() if not compopts)
            if not components_no_deps:
                raise ConanException("The poco dependency tree is invalid and contains a cycle")
            for c in components_no_deps:
                remaining_components.pop(c)
            ordered_components.extend(components_no_deps)
            for rname in remaining_components.keys():
                remaining_components[rname] = remaining_components[rname].difference(components_no_deps)
        ordered_components.reverse()
        return ordered_components

    _cmake = None

    @property
    def _source_subfolder(self):
        return "source_subfolder"

    @property
    def _build_subfolder(self):
        return "build_subfolder"

    def source(self):
        tools.get(**self.conan_data["sources"][self.version])
        extracted_folder = "poco-poco-{}-release".format(self.version)
        os.rename(extracted_folder, self._source_subfolder)

    def config_options(self):
        if self.settings.os == "Windows":
            del self.options.fPIC
        else:
            del self.options.enable_netssl_win
        if tools.Version(self.version) < "1.9":
            del self.options.enable_encodings
        if tools.Version(self.version) < "1.10":
            del self.options.enable_data_postgresql
            del self.options.enable_jwt

    def configure(self):
        if self.options.enable_apacheconnector:
            raise ConanInvalidConfiguration("Apache connector not supported: https://github.com/pocoproject/poco/issues/1764")
        if self.options.enable_data_mysql:
            raise ConanInvalidConfiguration("MySQL not supported yet, open an issue here please: %s" % self.url)
        if self.options.get_safe("enable_data_postgresql", False):
            raise ConanInvalidConfiguration("PostgreSQL not supported yet, open an issue here please: %s" % self.url)
        for compopt in self._poco_component_tree.values():
            if not compopt.option:
                continue
            if self.options.get_safe(compopt.option, False):
                for compdep in compopt.dependencies:
                    if not self._poco_component_tree[compdep].option:
                        continue
                    if not self.options.get_safe(self._poco_component_tree[compdep].option, False):
                        raise ConanInvalidConfiguration("option {} requires also option {}".format(compopt.option, self._poco_component_tree[compdep].option))

    def requirements(self):
        self.requires("pcre/8.41")
        self.requires("zlib/1.2.11")
        if self.options.enable_xml:
            self.requires("expat/2.2.9")
        if self.options.enable_data_sqlite:
            self.requires("sqlite3/3.31.1")
        if self.options.enable_apacheconnector:
            self.requires("apr/1.7.0")
            self.requires("apr-util/1.6.1")
            raise ConanInvalidConfiguration("apache2 is not (yet) available on CCI")
            self.requires("apache2/x.y.z")
        if self.options.enable_netssl or \
                self.options.enable_crypto or \
                self.options.get_safe("enable_jwt", False):
            self.requires("openssl/1.1.1g")

    def _patch_sources(self):
        for patch in self.conan_data.get("patches", {}).get(self.version, []):
            tools.patch(**patch)

    def _configure_cmake(self):
        if self._cmake:
            return self._cmake
        self._cmake = CMake(self)
        if tools.Version(self.version) < "1.10.1":
            self._cmake.definitions["POCO_STATIC"] = not self.options.shared
        for comp in self._poco_component_tree.values():
            if not comp.option:
                continue
            self._cmake.definitions[comp.option.upper()] = self.options.get_safe(comp.option, False)
        self._cmake.definitions["POCO_UNBUNDLED"] = True
        self._cmake.definitions["CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP"] = True
        if self.settings.os == "Windows" and self.settings.compiler == "Visual Studio":  # MT or MTd
            self._cmake.definitions["POCO_MT"] = "ON" if "MT" in str(self.settings.compiler.runtime) else "OFF"
        self.output.info(self._cmake.definitions)
        # On Windows, Poco needs a message (MC) compiler.
        with tools.vcvars(self.settings) if self.settings.compiler == "Visual Studio" else tools.no_op():
            self._cmake.configure(build_dir=self._build_subfolder)
        return self._cmake

    def build(self):
        if self.options.enable_data_sqlite:
            if self.options["sqlite3"].threadsafe == 0:
                raise ConanInvalidConfiguration("sqlite3 must be built with threadsafe enabled")
        self._patch_sources()
        cmake = self._configure_cmake()
        cmake.build()

    def package(self):
        self.copy("LICENSE", dst="licenses", src=self._source_subfolder)
        cmake = self._configure_cmake()
        cmake.install()
        tools.rmdir(os.path.join(self.package_folder, "lib", "cmake"))
        tools.rmdir(os.path.join(self.package_folder, "cmake"))

    @property
    def _ordered_libs(self):
        libs = []
        for compname in self._poco_ordered_components:
            comp_options = self._poco_component_tree[compname]
            if comp_options.is_lib:
                if not comp_options.option:
                    libs.append(compname)
                elif self.options.get_safe(comp_options.option, False):
                    libs.append(compname)
        return libs

    def package_info(self):
        suffix = str(self.settings.compiler.runtime).lower()  \
                 if self.settings.compiler == "Visual Studio" and not self.options.shared \
                 else ("d" if self.settings.build_type == "Debug" else "")

        self.cpp_info.libs = list("{}{}".format(lib, suffix) for lib in self._ordered_libs)
        
        if self.settings.os == "Linux":
            self.cpp_info.system_libs.extend(["pthread", "dl", "rt"])

        if self.settings.compiler == "Visual Studio":
            self.cpp_info.defines.append("POCO_NO_AUTOMATIC_LIBS")
        if not self.options.shared:
            self.cpp_info.defines.append("POCO_STATIC=ON")
            if self.settings.compiler == "Visual Studio":
                self.cpp_info.system_libs.extend(["ws2_32", "iphlpapi", "crypt32"])
        self.cpp_info.names["cmake_find_package"] = "Poco"
        self.cpp_info.names["cmake_find_package_multi"] = "Poco"

您要查看的是optionsdefault_options中的食谱。 据我所知,除了查看像这样的实际食谱源代码外,没有办法查询食谱提供的选项以及它们的作用。

似乎poco食谱从此_poco_component_tree字典中添加了很多选项。您要检查的是名称为enable_something且值为True的选项。由于这些是作为选项添加的,因此这意味着客户端(您正在运行conan)可以在运行conan install时控制它们。例如,请尝试下面的命令(您可以添加多个-o poco:something来设置多个选项)

conan install .. -o poco:enable_data_sqlite=False

我们在食谱的requirements方法中看到,只有当enable_data_sqliteTrue时,柯南才会添加“ sqlite3 / 3.31.1”是poco依赖项。这意味着,如果将enable_data_sqlite设置为False,则根本不应该包含它,并且二进制文件应该变小。

由于柯南(以及poco开发人员或创建poco秘诀的人)希望尽可能轻松地使用conan安装poco,因此默认情况下包含poco的最常见部分是有意义的。使用柯南选项禁用它的某些部分是您可以控制它的方法。您将不得不尝试这些选项中的一些选项,以查看得到的结果。如果禁用了您真正需要的功能,则在编译和/或链接实际代码时会出错。

答案 1 :(得分:0)

请仔细查看Poco/Config.h,因为其中有几个宏可让您禁用Poco的某些部分。这些宏可以帮助您轻松地删除不需要的二进制文件(例如:XML,JSON,INI配置文件,也为POCO_NO_AUTOMATIC_LIBS)。我希望这些和其他可以减少目标文件的大小。

我知道这已经有一段时间了,但是Poco已被用于在非常小型“面板”上运行Web服务器。参见https://pocoproject.org/blog/?p=193