可执行文件和应用程序 (包括共享库) 也可以与Conan一起分发、部署和运行。与部署其他系统相比,这可能具有一些优势:

  • 适用于所有系统和平台的统一开发和分发工具。
  • 以与管理开发配置相同的方式管理任意数量的不同部署配置。
  • 使用Conan服务器远程存储所有操作系统、平台和目标的所有应用程序和运行时间。

有不同的方法:

使用部署生成器

部署生成器用于将应用程序的所有依赖项复制到一个位置。然后,所有文件都可以重新打包为所选的分发格式。
例如,如果应用程序依赖于boost,我们可能不知道它还需要许多其他的第三方库,如zlib、bzip2 、lzma、zstd、iconv等。

  1. $ conan install . -g deploy

这有助于将所有依赖项收集到一个位置,将它们移出Conan缓存。

使用json生成器

更高级的方法是使用json生成器。该生成器的工作方式与部署生成器类似,尽管它不会将文件复制到目录。相反,它生成一个JSON文件,其中包含有关依赖项的所有信息,包括文件在Conan缓存中的位置。

  1. $ conan install . -g json

生成的conanbuildinfo.json文件是完全机器可读的,脚本可以使用它来准备文件并重新创建适当的格式以进行分发。以下代码显示如何从conanbuildinfo.json中读取库和二进制目录:

  1. import os
  2. import json
  3. data = json.load(open("conanbuildinfo.json"))
  4. dep_lib_dirs = dict()
  5. dep_bin_dirs = dict()
  6. for dep in data["dependencies"]:
  7. root = dep["rootpath"]
  8. lib_paths = dep["lib_paths"]
  9. bin_paths = dep["bin_paths"]
  10. for lib_path in lib_paths:
  11. if os.listdir(lib_path):
  12. lib_dir = os.path.relpath(lib_path, root)
  13. dep_lib_dirs[lib_path] = lib_dir
  14. for bin_path in bin_paths:
  15. if os.listdir(bin_path):
  16. bin_dir = os.path.relpath(bin_path, root)
  17. dep_bin_dirs[bin_path] = bin_dir

使用部署生成器时,所有文件都被复制到一个文件夹中。Json one的优势在于,您可以对文件进行细粒度控制,并且这些文件可以直接复制到所需的布局。
从这个意义上说,上面的脚本可以很容易地修改以应用某种过滤 (例如g.仅复制共享库,并省略任何静态库或辅助文件,如pkg-config。电脑文件)。
此外,您还可以使用提取的信息为应用程序编写一个简单的启动脚本,如下所示:

  1. executable = "MyApp" # just an example
  2. varname = "$APPDIR"
  3. def _format_dirs(dirs):
  4. return ":".join(["%s/%s" % (varname, d) for d in dirs])
  5. path = _format_dirs(set(dep_bin_dirs.values()))
  6. ld_library_path = _format_dirs(set(dep_lib_dirs.values()))
  7. exe = varname + "/" + executable
  8. content = """#!/usr/bin/env bash
  9. set -ex
  10. export PATH=$PATH:{path}
  11. export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{ld_library_path}
  12. pushd $(dirname {exe})
  13. $(basename {exe})
  14. popd
  15. """.format(path=path,
  16. ld_library_path=ld_library_path,
  17. exe=exe)

通过包运行

如果依赖项具有要在conanfile中运行的可执行文件,则可以使用run_environment = True参数直接在代码中完成此操作。它在内部使用RunEnvironment() 助手。例如,如果我们要在构建消费者包时执行问候语应用程序:

  1. from conans import ConanFile, tools, RunEnvironment
  2. class ConsumerConan(ConanFile):
  3. name = "Consumer"
  4. version = "0.1"
  5. settings = "os", "compiler", "build_type", "arch"
  6. requires = "hello/0.1@user/testing"
  7. def build(self):
  8. self.run("greet", run_environment=True)

现在运行此消费配方的conan install和conan build:

  1. $ conan install . && conan build .
  2. ...
  3. Project: Running build()
  4. Hello World Release!

除了使用环境,还可以显式访问依赖项的路径:

  1. def build(self):
  2. path = os.path.join(self.deps_cpp_info["Hello"].rootpath, "bin")
  3. self.run(["%s/greet" % path])

请注意,如果存在共享库,这可能还不够。使用上面的run_environment = True help是一个更完整的解决方案。
此示例还演示了如何使用list指定要运行的命令。这绕过了系统外壳,即使路径包含特殊字符,如空格或引号,否则将由外壳解释,它也能正常工作。但是,这也意味着在使用此方法时,替换环境变量或其他命令 (通常由shell完成) 的输出将不起作用。当您需要此功能时,使用上面所示的纯字符串指定您的命令。
最后,还有另一种方法: 包含可执行文件的包可以将其bin文件夹直接添加到路径中。在这种情况下,Hello包conanfile将包含:

  1. def package_info(self):
  2. self.cpp_info.libs = ["hello"]
  3. self.env_info.PATH = os.path.join(self.package_folder, "bin")

如果可执行文件需要DYLD_LIBRARY_PATH和LD_LIBRARY_PATH,我们也可以定义它们。
消费者包很简单,因为PATH环境变量包含问候语可执行文件:

  1. def build(self):
  2. self.run("greet")

请阅读下一节,了解有关在配方方法中使用打包可执行文件的更全面的解释。

运行时包和重新打包

可以创建仅包含运行时二进制文件的包,从而摆脱所有构建时依赖项。如果我们想从上面的 “你好” 创建一个包,但只包含可执行文件 (记住上面的包还包含一个库和标题),我们可以这样做:

  1. from conans import ConanFile
  2. class HellorunConan(ConanFile):
  3. name = "hello_run"
  4. version = "0.1"
  5. build_requires = "hello/0.1@user/testing"
  6. keep_imports = True
  7. def imports(self):
  8. self.copy("greet*", src="bin", dst="bin")
  9. def package(self):
  10. self.copy("*")

这个食谱有以下特点:

  • 它包括hello/0.1 @ user/testpackage (按build_要求)。这意味着它将用于构建hello_run包,但是一旦构建了hello_run包,就没有必要检索它。
  • 它使用import () 从依赖项复制,在这种情况下,可执行文件
  • 它使用keep_exports属性来定义在build() 步骤 (未定义,然后使用默认的空对象) 期间导入的工件在构建后保留且不删除
  • Package () 方法打包将在build文件夹中创建的导入工件。

创建此包并将其上传到远程:

  1. $ conan create . user/testing
  2. $ conan upload hello_run* --all -r=my-remote

可以使用上面介绍的任何方法来安装和运行此软件包。例如:

  1. $ conan install hello_run/0.1@user/testing -g virtualrunenv
  2. # You can specify the remote with -r=my-remote
  3. # It will not install hello/0.1@...
  4. $ activate_run.sh # $ source activate_run.sh in Unix/Linux
  5. $ greet
  6. > Hello World Release!
  7. $ deactivate_run.sh # $ source deactivate_run.sh in Unix/Linux

部署挑战

在部署C/C + + 应用程序时,在分发应用程序时,有一些特定的挑战需要解决。在这里你会找到最常见的和一些克服它们的建议。

The C standard library

对于所有应用程序来说,无论它们是用纯C还是用C + + 编写的,一个共同的挑战是对C标准库的依赖。这个库最广泛的变体是GNU C库或者只是glibc。
Glibc不仅仅是一个C标准库,因为它提供:

  • C函数 (如malloc() 、sin() 等) 用于各种语言标准,包括c99。
  • POSIX函数 (如pthread库中的posix线程)。
  • BSD功能 (如BSD套接字)。
  • 用于操作系统特定api的包装器 (如Linux系统调用)

即使您的应用程序不直接使用这些功能中的任何一个,它们也经常被其他库使用,因此,实际上,它几乎总是在实际使用中。
C标准库的其他实现也提出了同样的挑战,例如用于嵌入式开发的newlib或musl。
为了说明这个问题,在现代Ubuntu发行版中编译的一个简单的hello-world应用程序在Centos 6 版本中运行时会出现以下错误:

  1. $ /hello
  2. /hello: /lib64/libc.so.6: version 'GLIBC_2.14' not found (required by /hello)

这是因为这些Linux发行版之间的glibc版本不同。
这个问题有几种解决方法:

  • LibcWrapGenerator
  • Glibc_version_header
  • Bingcc

有些人也建议使用静态的glibc,但强烈不鼓励这样做。原因之一是较新的glibc可能正在使用以前版本中不可用的系统调用,因此它将在运行时随机失败,这很难调试 (系统调用的情况如下所述)。
通过在设置中定义自定义Conan子设置,可以在Conan中为glibc版本或Linux发行名称建模。yml文件 (签出添加新设置和添加新子设置的部分)。该过程将类似于:

  • 定义新的子设置,例如操作系统。发行版,如添加新的子设置一节中所述。
  • 定义兼容模式,如package_id() 和build_id() 部分所述 (例如,您可能认为某些Ubuntu和Debian软件包彼此兼容)
  • 为每个分发生成不同的包。
  • 为每个发行版生成可部署的工件。

C++ standard library

通常,默认的C + + 标准库是libstdc + +,但是libc + + 和stlport是其他众所周知的实现。
与标准C库glibc类似,在旧系统中运行与libstdc + + 链接的应用程序可能会导致错误:

  1. $ /hello
  2. /hello: /usr/lib64/libstdc++.so.6: version 'GLIBCXX_3.4.21' not found (required by /hello)
  3. /hello: /usr/lib64/libstdc++.so.6: version 'GLIBCXX_3.4.26' not found (required by /hello)

幸运的是,通过添加-static-libstdc + + 编译器标志,解决这个问题要容易得多。与C运行时不同,C + + 运行时可以静态安全地链接,因为它不直接使用系统调用,而是依赖libc来提供所需的包装器。

Compiler runtime

除了C和C + + 运行时库,编译器运行时库也被应用程序使用。这些库通常提供较低级别的函数,例如编译器内部函数或对异常处理的支持。来自这些运行时库的函数很少在代码中直接引用,并且大多由编译器本身隐式插入。

  1. $ ldd ./a.out
  2. libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f6626aee000)

您可以通过使用-static-libgcc编译器标志来避免这种依赖关系。然而,这并不总是明智的做法,因为在某些情况下,应用程序应该使用共享运行时。最常见的情况是应用程序希望跨不同共享库引发和捕获异常。查看GCC手册以获取详细信息。

System API (system calls)

新的系统调用通常在新版本的linux内核中引入。如果应用程序或第3 方库想要利用这些新功能,它们有时会直接引用此类系统调用 (而不是使用glibc提供的包装器)。
结果,如果应用程序是在具有较新内核的计算机上编译的,并且构建了用于自动检测可用系统调用的系统,则它可能无法在具有较旧内核的计算机上正确执行。
解决方案是使用具有最低支持内核的构建机器,或者使用模型支持的操作系统 (就像在glibc的情况下一样)。查看添加新设置和添加新子设置的部分,以获取有关如何在conan设置中建模分布的信息。