SCons库和子库

3
我有一个基于SCons的分层构建系统。我有一个根SConstruct,它调用一个SConscript来构建一个共享库,然后调用另一个SConscript来构建依赖于该共享库的可执行文件。
那么这里是我的问题:在Linux上,我对共享库的理解是,在您想要对将使用共享库的可执行文件进行最终的 ld 链接时,必须将共享库包含在可执行文件的 ld 命令行中,以作为引用它的源(除非它在标准位置中,在这种情况下, -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的方法来完成这个任务?
4个回答

3

这里有一种更好的方式来组织你的SConsctruct/SConscript文件。通常,在分层构建中,你应该与子目录共享env。请注意,我在barexec目录中克隆了主env,以便foolib仅用于链接该二进制文件。

=== 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()并不是必需的。 - Ted Middleton
另外,请看一下我对Kevin Grant的关于LIBS=foo的评论——如果你这样做,SCons是否会检测到libfoo.so和barexec之间的构建依赖关系? - Ted Middleton
@TedMiddleton,我在我的构建系统中进行了这种环境克隆。不同的环境确实在其变量等方面是隔离的,但是通过这种方式,“libfoo.so”和“barexec”目标仍然被“检测”到。我不确定,但我认为这些目标是全局的,它们必须是这样才能工作,而且它确实可以工作 :) - Brady

3
你应该允许编译找到共享库。
在SCons文档中查找LIBPATH和RPATH变量,这是设置搜索路径的"Scons-y"方式,以便任何生成的-l选项正确地找到库。
提到上面的内容后,基于SCons的设置,以下是您应该看到的gcc操作(如果没有,则可能需要手动执行)。
-l选项总是可以找到共享库,前提是您还向编译器提供库的位置。这需要两次:在编译时(-L选项)和在运行时(生成的-rpath链接器选项)。
LIBPATH SCons设置应该生成类似-L /some/directory/path的编译时搜索路径。
RPATH SCons设置应生成一个嵌入式查找路径的链接器选项;例如 -Wl,-rpath -Wl,\$ORIGIN/../lib会嵌入一个搜索路径,该路径相对于可执行文件进行搜索,以便放置在bin下的可执行文件能够在安装的平行lib目录中进行搜索。

这听起来是个好主意,但这不会破坏 SCons 的依赖检查吗?如果我在 bar 程序环境中添加 LIBS=foo,那么 SCons 不一定知道 gcc 正在获取 libfoo.so 的位置(可能是我的项目,也可能是 /usr/lib 或其他 SCons 不知道的地方),并且不会检测 libfoo.so 和 barexec 之间的依赖关系。我需要使用 env.Depends() 来在 foo 和 bar 之间建立依赖关系,对吗? - Ted Middleton
@TedMiddleton,你需要告诉SCons可执行文件需要哪些库(通过LIBS构建环境),以及它们在哪里(通过LIBPATH构建环境),然后基于此创建依赖树。由于SCons具有这些变量,并将其传递给gcc或其他编译器,因此它确实会知道它们。你不需要使用env.Depends()函数来进行任何依赖项的修改。试一试,你就会发现 :) - Brady

2

除了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可以自动查找库的依赖关系,无需任何额外的命令。如果我想更改库的名称,只需更改我的Project类即可。


0
Brady的回答没有解决一个问题,那就是在使用variant dirs进行out-of-source构建时如何获取正确的库路径。这里提供了一种非常相似的方法,可以构建两个不同的变体:

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})
  • 每个variant_dir中的子目录都需要此根级别的SConscript来构建。
  • 在设置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中,通知SConsbarexec依赖于必须先构建的foolib,并将该库添加到链接器命令行。
  • 只有在将"foo"添加到LIBS中时才应将$FOOLIBDIR添加到LIBPATH中——如果没有,则可能会在构建foolib之前构建barexec,导致链接器错误,因为指定的库路径尚不存在。

$LIBPATH 不应在依赖关系被扫描后(即所有 SConscript/SConstruct 被评估后)进行评估。因此,barexec 和 foolib 的顺序并不重要。 - bdbaddog
@bdbaddog 在两种情况下都打印出 LIBPATH=['$FOOLIBDIR'],但我注意到了另外一件事:当 foolib 位于 barexec 之前并且我使用 --debug=tree,我看到一个依赖关系是 .debugdebug/barexecdebug/barexec/bardebug/foolib/libfoo.dylib。当我更改顺序时,barexec 的依赖中缺少了 foolib!因此,SCons 在构建 barexec 后才编译 foolib,导致 $FOOLIBDIR 只有在 barexec 构建完成后才被设置。这个问题的正确解决方案是什么? - joki
@bdbaddog,非常感谢您的所有努力!除了.so.dylib之间的区别,因为我在macOS上,我得到了完全相同的日志(请参见我的第一个提交)。但是,当我更改barexecfoolib的顺序时,我会得到在我的存储库https://github.com/jkuebart/so-11571577/中显示的日志。 - joki
1
啊..问题在于你的env.Clone()在子目录SConscripts中。特别是barexec的。请参见最新的github推送。因此,如果您首先有barexec的SConscript,则会在“全局”环境中传递FOOLIBDIR设置之前克隆导入的env。因此,在实际评估时,在克隆的“clonedEnv”中没有FOOLIBDIR。在这些示例中,无需克隆。您可以在构建器调用中专门指定LIBPATH和LIBS。 - bdbaddog
不幸的是,"$SOME_VAR more stuff" 对我来说并不起作用 - 当 $SOME_VAR 为空时,结果值以空格开头。如果有更好的方法来编写 env.get("SOME_VAR", []) + ["more", "stuff"] 就太好了。 - joki
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接