【问题标题】:SCons libraries and sub-librariesSCons 库和子库
【发布时间】:2012-07-19 06:30:51
【问题描述】:

我有一个基于 SCons 的分层构建系统。我有一个根 SConstruct,它调用一个构建共享库的 SConscript,然后调用另一个构建依赖于共享库的可执行文件的 SConscript。

所以这是我的问题:我对 linux 上的共享库的理解是,当您想要为将使用共享库的可执行文件执行最终的 ld 链接时,共享库必须包含在可执行文件的 @ 987654322@ 命令行作为引用它的源(除非它位于标准位置,在这种情况下-l 选项有效)。

这就是我的 SCons 文件的样子:

=== rootdir/SConstruct

env=DefaultEnvironment()
shared_lib = SConscript('foolib/SConscript')
env.Append( LIBS=[shared_lib] )
executable = SConscript('barexec/SConscript')

=== rootdir/foolib/SConscript

env=DefaultEnvironment()
env.Append(CPPPATH=Glob('inc'))
penv = env.Clone()
penv.Append(CPPPATH=Glob('internal/inc'))
lib = penv.SharedLibrary( 'foo', source=['foo.c', 'morefoo.c']
Return("lib")

=== rootdir/barexec/SConscript

env=DefaultEnvironment()
exe = env.Program( 'bar', source=['main.c', 'bar.c', 'rod.c'] )
Return("exe")

所以这里的问题是这一行:

env.Append( LIBS=[shared_lib] )

这将是为需要它们的任何其他库添加生成的库到命令行的好方法,除了因为 SCons 正在通过 SConscripts 进行两次运行(首先生成它的依赖关系树,然后执行工作),rootdir/foolib/libfoo.so 最终出现在所有产品的命令行上,甚至 libfoo.so 本身:

gcc -g -Wall -Werror -o libfoo.so foo.o morefoo.o libfoo.so

那么如何最好地使用 SCons 呢?现在我已经使用了这个技巧:

=== rootdir/SConstruct

env=DefaultEnvironment()
shared_lib = SConscript('foolib/SConscript')
env['shared_lib'] = shared_lib
executable = SConscript('barexec/SConscript')

...

=== rootdir/barexec/SConscript

env=DefaultEnvironment()
exe = env.Program( 'bar', source=['main.c', 'bar.c', 'rod.c'] + env['shared_lib'] )
Return("exe")

有没有更多的 SCons-y 方式来做到这一点?

【问题讨论】:

    标签: c++ c build build-process scons


    【解决方案1】:

    您应该允许构建找到共享库。

    SCons 文档中查找LIBPATHRPATH 变量;这些是设置搜索路径的“Scons-y”方式,以便任何生成的-l 选项都能正确找到库。

    上面已经提到了,这就是您应该看到gcc 根据 SCons 的设置执行的操作(如果没有,您可能必须手动执行)。

    -l 选项始终可以找到共享库,前提是您还向编译器提供了库的位置。有两次需要这样做:编译时(-L 选项)和运行时(-rpath 生成的链接器选项)。

    LIBPATH SCons 设置应该为编译时搜索路径生成类似于 -L/some/directory/path 的内容。

    RPATH SCons 设置应生成链接器选项以嵌入搜索路径;例如-Wl,-rpath -Wl,\$ORIGIN/../lib 将嵌入一个相对于可执行文件进行搜索的搜索路径,以便放置在bin 中的可执行文件在安装的并行lib 目录中搜索。

    【讨论】:

    • 这听起来是个好主意,但这不是颠覆了 SCons 的依赖检查吗?如果我将 LIBS=foo 添加到 bar 程序环境,那么 SCons 不一定知道 gcc 从哪里获取 libfoo.so(可能是我的项目,可能是 /usr/lib 或任何 SCons 知道的),并且不会t 检测 libfoo.so 和 barexec 之间的依赖关系。我必须用 env.Depends() 破解 foo 和 bar 之间的依赖关系,不是吗?
    • @TedMiddleton,您必须告诉 SCons 可执行文件需要哪些库(通过 LIBS 构造环境),以及它们在哪里(通过 LIBPATH 构造环境),然后基于此创建依赖关系树. SCons 确实会知道它们,因为它具有这些变量,随后将其传递给 gcc 或任何编译器。而且您不需要使用 env.Depends() 函数破解任何依赖项。试试看,你会看到:)
    【解决方案2】:

    这是一种更好的方式来组织您的 SConstruct/SConscript 文件。通常对于分层构建,您应该与其余的子目录共享 env。请注意,我也克隆了 barexec 目录中的主 env,因此傻瓜仅用于链接该二进制文件。

    === rootdir/SConstruct

    import os
    
    env=DefaultEnvironment()
    
    subdirs = [
        'foolib',
        'barexec'
    ]
    
    # The exports attribute allows you to pass variables to the subdir SConscripts
    for dir in subdirs:
        SConscript( os.path.join(dir, 'SConscript'), exports = ['env'])
    

    === rootdir/foolib/SConscript

    # inports the env created in the root SConstruct
    #
    # Any changes made to 'env' here will be reflected in
    # the root/SConstruct and in the barexec/SConscript
    #
    Import('env')
    
    # Adding this 'inc' dir to the include path for all users of this 'env'
    env.Append(CPPPATH=Glob('inc'))
    
    penv = env.Clone()
    # Adding this include only for targets built with penv
    penv.Append(CPPPATH=Glob('internal/inc'))
    penv.SharedLibrary( 'foo', source=['foo.c', 'morefoo.c'])
    

    === rootdir/barexec/SConscript

    Import('env')
    
    clonedEnv = env.Clone()
    
    # The foo lib will only be used for targets compiled with the clonedEnv env
    # Notice that specifying '#' in a path means relative to the root SConstruct
    # for each [item] in LIBS, you will get -llib on the compilation line
    # for each [item] in LIBPATH, you will get -Lpath on the compilation line
    clonedEnv.Append(LIBS=['foo'], LIBPATH=['#foolib'])
    
    clonedEnv.Program( 'bar', source=['main.c', 'bar.c', 'rod.c'] )
    

    【讨论】:

    • 在问了这个问题之后我发现在玩这个的东西是 DefaultEnvironment() 访问跨 SConscripts 和子 SConscripts 的全局变量,所以除非你想传递一个不同的环境, Import()和 Export() 不是绝对必要的。
    • 另外,请参阅上面我对 Kevin Grant 关于 LIBS=foo 的评论 - 如果您这样做,SCons 是否会检测到 libfoo.so 和 barexec 之间的构建依赖关系?
    • @TedMiddleton,我在我的构建系统中进行了这种环境克隆。不同的环境在变量等方面确实是隔离的,但是目标(libfoo.so 和 barexec)仍然以这种方式“检测到”。我不确定,但我认为目标是全球性的,它们必须为此工作,而且确实有效:)
    【解决方案3】:

    除了 Brady 决定之外,我还使用静态/全局变量来存储目标名称和路径。它让我可以更好地控制构建。

    # site_scons/project.py
    class Project:
      APP1_NAME = "app1_name"
      APP2_NAME = "app2_name"
      MYLIB1_NAME = "mylib1_name"
      # etc
      APP_PATH = "#build/$BuildMode/bin" # BuildMode - commonly in my projects debug or release, `#` - root of project dir
      LIB_PATH = "#build/$BuildMode/lib"
      @staticmethod
      def appPath(name) :
         return os.path.join(APP_PATH, name)
      @staticmethod
      def libPath(name) :
         return os.path.join(LIB_PATH, name)
    

    定义目标:

    from project import Project
    ...
    env.SharedLibrary(Project.libPath(Project.MYLIB1_NAME), source=['foo.c', 'morefoo.c'])
    

    应用:

    from project import Project
    ...
    env.Append(LIBPATH = [Project.LIB_PATH])
    env.Append(LIBS = [Project.MYLIB1_NAME])
    env.Program(Project.appPath(Project.MYAPP1_NAME), source=[...])
    

    在我的项目中它运行良好,scons 自动查找依赖于库而无需任何额外的命令。如果我想更改库的名称,我只需更改我的项目类。

    【讨论】:

      【解决方案4】:

      Brady 的回答没有解决的一个问题是如何在使用变体目录构建源外时获取正确的库路径。这是一个非常相似的方法,它构建了两个不同的变体:

      SConstruct

      # Common environment for all build modes.
      common = Environment(CCFLAGS=["-Wall"], CPPPATH=["#foolib/inc"])
      
      # Build-mode specific environments.
      debug = common.Clone()
      debug.Append(CCFLAGS=["-O0"])
      release = common.Clone()
      release.Append(CCFLAGS=["-O"], CPPDEFINES=["NDEBUG"])
      
      # Run all builds.
      SConscript("SConscript", exports={"env": debug}, variant_dir="debug")
      SConscript("SConscript", exports={"env": release}, variant_dir="release")
      
      • CPPPATH 值中的 # 使包含路径相对于项目根目录而不是变体目录。

      SConscript

      Import("env")
      
      subdirs=["barexec", "foolib"]
      senv = env.Clone(FOOLIBDIR=Dir("foolib"))
      SConscript(dirs=subdirs, exports={"env": senv})
      
      • 需要此根级SConscript 来构建每个variant_dir 中的子目录。
      • 通过在设置FOOLIBDIR 时使用函数Dir(),库的变体构建目录相对于该文件而不是它的使用位置进行解析。

      foolib/SConscript

      Import("env")
      
      penv = env.Clone()
      penv.Append(CPPPATH=["internal/inc"])
      penv.SharedLibrary("foo", source=["foo.c", "morefoo.c"])
      
      • 请务必在进行任何更改之前克隆环境以避免影响其他目录。

      barexec/SConscript

      Import("env")
      
      clonedEnv = env.Clone()
      clonedEnv.Append(LIBPATH=["$FOOLIBDIR"], LIBS=["foo"])
      clonedEnv.Program("bar", source=["main.c", "bar.c", "rod.c"])
      
      • 库的变体构建目录被添加到LIBPATH,因此SCons 链接器都可以找到正确的库。
      • "foo" 添加到LIBS 会通知SCons barexec 依赖于必须首先构建的foolib,并将库添加到链接器命令行。
      • $FOOLIBDIR 应该只在 "foo" 也添加到 LIBS 时添加到 LIBPATH - 如果没有,barexec 可能会在 foolib 之前构建,导致链接器错误,因为指定的库路径没有(尚未) 存在。

      【讨论】:

      • $LIBPATH 不应该被评估,直到依赖项被遍历(在所有 SConscript/SConstruct 评估之后)。所以 barexec 和愚蠢的顺序应该无关紧要。
      • @bdbaddog 我也期望子目录的顺序无关紧要,但实际上当我在上面的示例中切换顺序时,链接器在没有-L 开关的情况下被调用(SCons 3.0.1) ——你知道为什么会这样吗? (我刚刚在最初的答案中修复了一些其他错误)
      • 添加 print("LIBPATH=%s"%clonedEnv['LIBPATH']) 并粘贴上面的输出。 (就在您的 Append(LIBPATH..) 之后)
      • @bdbaddog 在这两种情况下都会打印LIBPATH=['$FOOLIBDIR'] - 但我注意到了其他一些事情:当foolibbarexec 之前并且我使用--debug=tree 时,我看到了一个依赖关系. → @ 987654352@→debug/barexecdebug/barexec/bardebug/foolib/libfoo.dylib。当我更改顺序时,barexec 的依赖项中缺少foolib!因此,SCons 在barexec 之后构建foolib,而$FOOLIBDIR 仅在构建barexec 之后设置。什么是正确的解决方案?
      • 啊.. 问题是您的 env.Clone() 在子目录 SConscripts 中。特别是barexec的。查看最新推送到 github。因此,如果您首先拥有 barexec 的 SConscript,它 Clones() 将 FOOLIBDIR 之前导入的环境设置在传递的“全局”环境中。因此,当实际评估它时,克隆的“clonedEnv”中没有 FOOLIBDIR。在这些示例中,无需克隆。您可以在构建器调用中专门化 LIBPATH 和 LIBS。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-20
      • 1970-01-01
      • 2011-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多