别名目标
我们已经看到如何使用 Alias 函数创建一个名为 install 的目标:
env = Environment()
hello = env.Program('hello.c')
env.Install('/usr/bin', hello)
env .Alias('install', '/usr/bin')
然后你可以在命令行上使用这个别名来更自然地告诉 SCons 你想要安装文件
不过,与其他 Builder 方法一样,Alias 方法返回一个表示正在构建的别名的对象。然后,您可以将此对象用作另一个生成器的输入。如果您使用此类对象作为对别名生成器的另一个调用的输入,这将特别有用,从而允许您创建嵌套别名的层次结构
env = Environment()
p = env.Program('foo.c')
l = env.Library('bar.c')
env.Install('/usr/bin', p)
env.Install('/usr/lib', l)
ib = env.Alias('install-bin', '/usr/bin')
il = env.Alias('install-lib', '/usr/lib')
env.Alias('install', [ib, il])
此示例定义了单独的 install、install-bin 和 install-lib 别名,使您可以更好地控制安装的内容:
使用 gettext 进行国际化和本地化
gettext 工具集支持基于 SCons 的项目的国际化和本地化。 gettext 提供的构建器可自动生成和更新翻译文件。您可以像使用 autotools 一样管理翻译和翻译模板
预备知识
按照本章中提供的示例设置您的操作系统以支持两种或多种语言。在以下示例中,我们使用语言环境 en_US、de_DE 和 pl_PL。
确保您的系统上安装了 GNU gettext 实用程序
简单项目
让我们从一个非常简单的项目开始,例如“Hello world”程序
/* hello.c */
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("Hello world\\n");
return 0;
}
准备一个 SConstruct 照常编译程序
# SConstruct
env = Environment()
hello = Program(["hello.c"])
现在我们将项目转换为多语言项目。如果您还没有安装 GNU gettext 实用程序 [http://www.gnu.org/software/gettext/manual/gettext.html],请从您的首选软件包存储库安装它们,或从 http://ftp 下载。 gnu.org/gnu/gettext/ [http://ftp.gnu.org/gnu/gettext/]。出于本示例的目的,您应该在系统上安装以下三个语言环境:en_US、de_DE 和 pl_PL。例如,在 debian 上,您可以通过 dpkg-reconfigure locales 启用某些语言环境。
首先准备国际化的hello.c程序。更改之前的代码,使其内容如下
/* hello.c */
#include <stdio.h>
#include <libintl.h>
#include <locale.h>
int main(int argc, char* argv[])
{
bindtextdomain("hello", "locale");
setlocale(LC_ALL, "");
textdomain("hello");
printf(gettext("Hello world\\n"));
return 0;
}
可以在 http://www.gnu.org/software/gettext/manual/gettext.html#Sources [http://www.gnu.org/software/gettext/manual/gettext.html #来源]。 gettext("...") 有两个目的。首先,它为 xgettext(1) 程序标记消息,我们将使用它从源中提取消息以进行本地化。其次,它在运行时调用 gettext 库内部机制来翻译消息。
现在我们将指导 SCons 如何生成和维护翻译文件。为此,请使用翻译构建器和 MOFiles 构建器。第一个获取源文件,从中提取国际化消息,创建所谓的 POT 文件(翻译模板),然后创建 PO 翻译文件,每个请求的语言一个。后来,在开发生命周期中,构建器使所有这些文件保持最新。 MOFiles 构建器将 PO 文件编译为二进制形式。然后在名为 locale 的目录下安装 MO 文件。
完成的SConstruct如下:
# SConstruct
env = Environment( tools = ['default', 'gettext'] )
hello = env.Program(["hello.c"])
env['XGETTEXTFLAGS'] = [
'--package-name=%s' % 'hello',
'--package-version=%s' % '1.0',
]
po = env.Translate(["pl","en", "de"], ["hello.c"], POAUTOINIT = 1)
mo = env.MOFiles(po)
InstallAs(["locale/en/LC_MESSAGES/hello.mo"], ["en.mo"])
InstallAs(["locale/pl/LC_MESSAGES/hello.mo"], ["pl.mo"])
InstallAs(["locale/de/LC_MESSAGES/hello.mo"], ["de.mo"])
使用 scons po-update 生成翻译文件。您应该看到 SCons 的输出与此类似
现在通过执行 scons 来编译项目。输出应该与此类似
SCons 自动将 PO 文件编译为二进制格式 MO,InstallAs 行将这些文件安装在 locale 文件夹下
你的程序现在应该准备好了。您可以按如下方式尝试(linux)
下一个示例演示如果我们以国际化消息不变的方式更改源代码会发生什么。答案是没有任何翻译文件(POT、PO)被触及(即没有内容更改、没有创建/修改时间更改等)。让我们在程序中追加另一行(在最后一个 printf 之后),因此它的代码变为
杂项功能
SCons 支持许多其他章节无法轻松介绍的附加功能
验证 Python 版本:EnsurePythonVersion 函数
尽管 SCons 代码本身可以在任何 2.x Python 版本 2.7 或更高版本上运行,但在编写 SConscript 文件或您自己的本地模块时,您可以完全自由地使用更高版本的 Python 语法和模块。
如果你这样做,如果它正在运行的 Python 版本根本无法与你的代码一起运行,那么将 SCons 配置为正常退出并显示一条错误消息通常会很有帮助。如果您打算使用 SCons 构建您计划公开分发的源代码,则尤其如此,因为您无法确定匿名远程用户可能用来尝试构建您的软件的 Python 版本。
SCons 为此提供了 EnsurePythonVersion 函数。您只需将您需要的 Python 版本的主要和次要版本号传递给它:
EnsurePythonVersion(2, 5)
然后,当用户使用不受支持的早期 Python 版本运行 SCons 时,SCons 将退出并显示以下错误消息
验证 SCons 版本:EnsureSConsVersion 函数
当然,您可以编写 SConscript 文件以使用仅在最新版本的 SCons 中添加的功能。
当您公开分发使用 SCons 构建的软件时,让 SCons 验证正在使用的版本并在用户的 SCons 版本无法与您的 SConscript 一起使用时优雅地退出并显示错误消息会很有帮助文件。 SCons 提供了一个 EnsureSConsVersion 函数来验证 SCons 的版本,EnsurePythonVersion 函数通过传入您需要的 SCons 版本的主要和次要版本号来验证 Python 的版本。
SCons 为此提供了 EnsurePythonVersion 函数。您只需将您需要的 Python 版本的主要和次要版本号传递给它:
EnsurePythonVersion(2, 5)
然后,当用户使用不受支持的早期 Python 版本运行 SCons 时,SCons 将退出并显示以下错误消息
验证 SCons 版本:EnsureSConsVersion 函数
当然,您可以编写 SConscript 文件以使用仅在最新版本的 SCons 中添加的功能。
当您公开分发使用 SCons 构建的软件时,让 SCons 验证正在使用的版本并在用户的 SCons 版本无法与您的 SConscript 一起使用时优雅地退出并显示错误消息会很有帮助文件。 SCons 提供了一个 EnsureSConsVersion 函数来验证 SCons 的版本,EnsurePythonVersion 函数通过传入您需要的 SCons 版本的主要和次要版本号来验证 Python 的版本
EnsureSConsVersion(1, 0)
然后当用户使用不受支持的早期版本的 SCons 运行 SCons 时,SCons 将退出并显示以下错误消息
读取 SConscript 文件时显式终止 SCons:Exit函数
SCons 支持 Exit 函数,可用于在读取 SConscript 文件时终止 SCons,通常是因为您检测到无法继续进行的情况
if ARGUMENTS.get('FUTURE'):
print("The FUTURE option is not supported yet!")
Exit(2)
env = Environment()
env.Program('hello.c')
Exit 函数将您希望 SCons 退出的(数字)退出状态作为参数。如果不指定值,默认以0退出,表示执行成功。
请注意,Exit 函数等效于调用 Python sys.exit 函数(它实际调用的函数),但由于 Exit 是 SCons 函数,因此您不必导入 Python sys 模块即可使用它
搜索文件:FindFile 函数
FindFile 函数在目录列表中搜索文件。如果只有一个目录,它可以作为一个简单的字符串给出。如果匹配文件存在,该函数返回一个 File 节点,如果没有找到文件,则返回 None。 (有关在目录中搜索条目的替代方法,请参阅 Glob 函数的文档。)
# one directory
print("%s"%FindFile('missing', '.'))
t = FindFile('exists', '.')
print("%s %s"%(t.__class__, t))
# several directories
includes = [ '.', 'include', 'src/include']
headers = [ 'nonesuch.h', 'config.h', 'private.h', 'dist.h']
for hdr in headers:
print('%-12s: %s'%(hdr, FindFile(hdr, includes)))
如果文件存在于多个目录中,则只返回第一个
print(FindFile('multiple', ['sub1', 'sub2', 'sub3']))
print(FindFile('multiple', ['sub2', 'sub3', 'sub1']))
print(FindFile('multiple', ['sub3', 'sub1', 'sub2']))
除了现有文件外,FindFile 还会查找尚未构建的派生文件(即非叶文件)。
(叶子文件应该已经存在,否则构建将失败!)
# Neither file exists, so build will fail
Command('derived', 'leaf', 'cat >$TARGET $SOURCE')
print(FindFile('leaf', '.'))
print(FindFile('derived', '.'))
如果源文件存在,FindFile 将正确返回构建目录中的名称。
# Only 'src/leaf' exists
VariantDir('build', 'src')
print(FindFile('leaf', 'build'))
处理嵌套列表:Flatten 函数
SCons 支持 Flatten 函数,该函数接受一个输入 Python 序列(列表或元组)并返回一个仅包含序列中各个元素的扁平化列表。在尝试检查由调用各种构建器返回的列表组成的列表时,这会很方便。例如,您可以将以不同方式构建的目标文件收集到对 Program Builder 的一次调用中,只需将它们包含在一个列表中,如下所示
objects = [
Object('prog1.c'),
Object('prog2.c', CCFLAGS='-DFOO'),
]
Program(objects)
因为 SCons 中的 Builder 调用展平了它们的输入列表,所以这对于构建程序来说效果很好
但是如果你正在调试你的构建并想打印对象列表中每个对象文件的绝对路径,你可以尝试以下简单的方法,尝试打印每个节点的 abspath 属性:
objects = [
Object('prog1.c'),
Object('prog2.c', CCFLAGS='-DFOO'),
]
Program(objects)
for object_file in objects:
print(object_file.abspath)
这不会按预期工作,因为对 str 的每次调用都在操作每个 Object 调用返回的嵌入式列表,而不是在这些列表中的底层节点上:
解决方案是使用 Flatten 函数,这样您就可以将每个 Node 分别传递给 str
objects = [
Object('prog1.c'),
Object('prog2.c', CCFLAGS='-DFOO'),
]
Program(objects)
for object_file in Flatten(objects):
print(object_file.abspath)
查找调用目录:GetLaunchDir 函数
如果需要查找用户调用 scons 命令的目录,可以使用 GetLaunchDir 函数
env = Environment(
LAUNCHDIR = GetLaunchDir(),
)
env.Command('directory_build_info',
'$LAUNCHDIR/build_info'
Copy('$TARGET', '$SOURCE'))
因为 SCons 通常是从 SConstruct 文件所在的顶级目录调用的,所以 Python os.getcwd() 通常是等效的。但是,当从子目录调用 SCons -u、-U 和 -D 命令行选项时,将导致 SCons 更改为在其中找到 SConstruct 文件的目录。当那些选项使用时,GetLaunchDir 仍将返回用户调用子目录的路径,从而允许 SConscript 配置仍然从原始目录获取配置(或其他)文件。
声明附加输出:SideEffect 函数
有时,定义操作的方式会对 SCons 无法识别为目标的文件产生影响。 SideEffect 方法可用于通知 SCons 有关此类文件的信息。这可以用来标记一个依赖项以便在后续构建步骤中使用,尽管通常有更好的方法来做到这一点。 SideEffect 方法的主要用途是防止两个构建步骤以可能相互影响的方式同时修改或访问同一文件。
在此示例中,构建 file1 的规则还将数据放入日志中,日志用作生成文件 2 的命令的来源,但在干净构建中,SCons 不知道日志:它既不存在,也不是目标输出由任何建设者。 SConscript 使用 SideEffect 通知 SCons 关于额外的输出文件
env = Environment()
f2 = env.Command(
target='file2',
source='log',
action=Copy('$TARGET', '$SOURCE')
)
f1 = env.Command(
target='file1',
source=[],
action='echo >$TARGET data1; echo >log updated file1'
)
env.SideEffect('log', f1)
如果没有 SideEffect,此构建将失败并显示一条消息,即未找到目标“file2”所需的源“log”,但现在它可以继续
但是,最好将日志实际标识为目标,因为在这种情况下它就是这样的:
env = Environment()
f2 = env.Command(
target='file2',
source='log',
action=Copy('$TARGET', '$SOURCE')
)
f1 = env.Command(
target=['file1', 'log'],
source=[],
action='echo >$TARGET data1; echo >log updated file1'
通常,SideEffect 不适用于命令生成额外目标文件(即,将用作其他构建步骤的源的文件)的情况。例如,Microsoft Visual C/C++ 编译器能够执行增量链接,为此它使用一个状态文件——这样链接 foo.exe 也会生成一个 foo.ilk,或者如果它已经存在则使用它,如果提供了 /INCREMENTAL 选项。将 foo.ilk 指定为 foo.exe 的副作用不推荐使用 SideEffect,因为链接使用了 foo.ilk。 SCons 在对依赖图的分析中处理副作用文件的方式略有不同。当一个命令产生多个输出文件时,它们应该被指定为调用相关构建器函数的多个目标。 SideEffect 函数本身实际上只应在确保命令不并行执行很重要时使用,例如当“外围”文件(如日志文件)实际上可能被多个命令调用更新时。
不幸的是,为 MSVC 编译器链设置程序构建器的工具并没有预先构建对 .ilk 示例细节的理解——目标列表需要在存在该特定选项标志的情况下进行更改。与上面我们可以简单地告诉命令构建器有两个操作目标的简单示例不同,为像 Program 这样的构建器修改事件链虽然本质上并不复杂,但绝对是一个高级 SCons 主题。在这里开始使用 SideEffect 是可以的,只要理解它“不太正确”。也许在文件中留下评论作为提醒,如果它确实会在以后引起问题。
所以如果主要用途是为了防止并行问题,这里举个例子来说明。假设您需要调用以构建目标文件的程序还将更新描述该程序在构建目标时执行的操作的日志文件。
以下配置将使 SCons 调用一个名为 build 的假设脚本(在本地目录中),并使用命令行参数告诉它把日志信息写入一个通用的 logfile.txt 文件
env = Environment()
env.Command(
target='file1.out',
source='file1.in',
action='./build --log logfile.txt $SOURCE $TARGET'
)
env.Command(
target='file2.out',
source='file2.in',
action='./build --log logfile.txt $SOURCE $TARGET'
)
如果 SCons 决定通过同时运行两个程序调用来更新两个目标,那么在并行运行构建时可能会导致问题。多个程序调用可能会相互干扰写入公共日志文件,最好的情况是导致日志文件中的混合输出,最坏的情况是导致实际构建失败(例如,在像 Windows 这样的系统上,只有一个进程在一次可以打开日志文件进行写入)。
我们可以确保 SCons 不会同时运行这些构建命令,方法是使用 SideEffect 函数指定更新 logfile.txt 文件是构建指定的 file1 和 file2 目标文件的副作用:
env = Environment()
f1 = env.Command(
target='file1.out',
source='file1.in',
action='./build --log logfile.txt $SOURCE $TARGET'
)
f2 = env.Command(
target='file2.out',
source='file2.in',
action='./build --log logfile.txt $SOURCE $TARGET'
)
env.SideEffect('logfile.txt', f1 + f2)
这确保了两个 ./build 步骤按顺序运行,即使在命令行中使用 --jobs=2 也是如此:
可以为同一个副作用文件多次调用 SideEffect 函数。事实上,用作 SideEffect 的名称甚至不需要作为文件实际存在于磁盘上——SCons 仍将确保相关目标将按顺序执行,而不是并行执行。副作用其实是一个伪目标,SCons 主要关心节点是否被列为依赖它,而不关心它的内容
env = Environment()
f1 = env.Command('file1.out', [], action='echo >$TARGET data1')
env.SideEffect('not_really_updated', f1)
f2 = env.Command('file2.out', [], action='echo >$TARGET data2')
env.SideEffect('not_really_updated', f2)
虚拟环境(virtualenvs)
Virtualenv 是一个创建隔离 Python 环境的工具。 python 应用程序(例如 SCons)可以在激活的 virtualenv 中执行。 virtualenv 的激活通过定义一些 virtualenv 特定变量和修改搜索 PATH 来修改当前环境,这样安装在 virtualenv 主目录中的可执行文件优先于安装在其外部的可执行文件。
通常,SCons 在搜索外部可执行文件时使用硬编码 PATH,因此它总是从这些预定义的位置获取可执行文件。这也适用于 python 解释器,它由一些自定义 SCons 工具或测试套件调用。这意味着,当在 virtualenv 中运行 SCons 时,最终从 SCons 脚本调用 python 解释器很可能会跳出 virtualenv 并执行在硬编码 SCons PATH 中找到的 python 可执行文件,而不是正在执行 SCons 的路径。一些用户可能认为这是不一致的。
这个问题可以通过使用 --enable-virtualenv 选项来解决。该选项自动将 virtualenv 相关的环境变量导入所有创建的构建环境 env['ENV'],并适当修改 SCons PATH 以优先使用 virtualenv 的可执行文件。设置环境变量 SCONS_ENABLE_VIRTUALENV=1 将具有相同的效果。如果环境变量启用了 virtualenv 支持,它可能会被 --ignore-virtualenv 选项抑制。
在 SConscript 内部,全局函数 Virtualenv 可用。它返回 virtualenv 主目录的路径,如果 scons 没有从 virtualenv 运行,则返回 None。请注意,即使 scons 从未激活的 virtualenv 运行,此函数也会返回路径
将 SCons 与其他构建工具一起使用
有时一个项目需要以各种方式与其他项目进行交互。例如,许多开源项目使用来自其他开源项目的组件,并希望以已发布的形式使用这些组件,而不是将它们的构建重新编码到 SCons 中。再举一个例子,有时 SCons 的灵活性和强大功能对于管理整个项目很有用,但是开发人员在使用不同的工具进行小的更改时可能更喜欢更快的增量构建。
本章展示了一些在 SCons 中有效地与其他项目和工具进行交互的技术。
创建编译数据库
执行源代码分析和修改的工具通常不仅需要知道源代码本身,还需要了解源代码将如何编译,因为编译行会影响宏、包含等的行为。SCons 会记录一次此信息它已经以与源关联的操作的形式运行,并且可以发出此信息以便工具可以使用它。
Clang 项目定义了一个 JSON 编译数据库。该数据库通常用作 Clang 工具以及许多 IDE 和编辑器的输入。有关完整信息,请参阅 JSON 编译数据库格式规范 [https://clang.llvm.org/docs/JSONCompilationDatabase.html]。通过启用 compilation_db 工具并调用 CompilationDatabase 构建器(自 scons 4.0 起可用),SCons 可以发出这种格式的编译数据库。
编译数据库可以使用源文件和输出文件填充,或者使用相对于构建顶部的路径,或者使用绝对路径。这由默认为 False 的 COMPILATIONDB_USE_ABSPATH=(True|False) 控制。可以使用 COMPILATIONDB_PATH_FILTER='pattern' 过滤此文件中的条目,其中过滤模式是遵循 Python fnmatch [https://docs.python.org/3/library/fnmatch.html] 语法的字符串。
此过滤可用于将不同的构建变体输出到不同的编译数据库文件。
以下示例说明了生成包含绝对路径的编译数据库:
env = Environment(COMPILATIONDB_USE_ABSPATH=True) env.Tool('compilation_db') env.CompilationDatabase() env.Program('hello.c')
compile_commands.json 包含
[ { "command": "gcc -o hello.o -c hello.c", "directory": "/home/user/sandbox", "file": "/home/user/sandbox/hello.c", "output": "/home/user/sandbox/hello.o" } ]
请注意,生成的数据库仅包含 hello.c/hello.o 配对的条目,而没有生成最终可执行文件 hello 的条目 - hello.o 到 hello 的转换没有任何影响源解释的信息代码,所以它对编译数据库不感兴趣。
虽然乍一看可能有点令人惊讶,但编译数据库目标与任何其他目标一样,受 scons 目标选择规则的约束。这意味着如果您设置默认目标(不包括编译数据库)或使用命令行目标,则可能不会选择它进行构建。这实际上是一个优势,因为您不一定要在每次构建时都重新生成编译数据库。以下示例显示为输出和源选择相对路径(默认),并为数据库提供非默认名称。为了能够与构建分开生成数据库,设置了一个引用数据库的别名,然后可以将其用作目标 - 这里我们只是构建编译数据库目标,而不是代码。
env = Environment() env.Tool('compilation_db') cdb = env.CompilationDatabase('compile_database.json') Alias('cdb', cdb) env.Program('test_main.c')
以下(不完整)示例显示了使用过滤来分离构建变体。在使用变体的情况下,你想要每个不同的编译数据库,因为构建参数不同,所以代码分析需要看到此处提示的 32 位构建和 64 位构建的正确构建行。为简单起见,该示例省略了变体目录的设置细节
env = Environment() env.Tool("compilation_db") env1 = env.Clone() env1["COMPILATIONDB_PATH_FILTER"] = "build/linux32/*" env1.CompilationDatabase("compile_commands-linux32.json") env2 = env.Clone() env2["COMPILATIONDB_PATH_FILTER"] = "build/linux64/*" env2.CompilationDatabase('compile_commands-linux64.json')
Ninja构建生成器
注意
这是一项实验性新功能。它可能会在没有折旧周期的情况下发生变化和/或移除。
将 ninja 工具加载到 SCons 中将对 SCons 的正常运行产生重大改变。
- SCons 将不再直接执行任何命令,只会创建build.ninja 并运行ninja。
- 命令行中指定的任何目标都将传递给 ninja
要启用此功能,您需要使用以下选项之一:
# On the command line --experimental=ninja
# Or in your SConstruct
SetOption('experimental', 'ninja') Ninja 是一个小型构建系统,它试图通过不做决定来提高速度。 SCons 有时会很慢,因为它会做出很多决定来实现其“正确性”的目标。这两个工具可以配对使用,使某些构建场景受益:通过使用 ninja 工具,SCons 可以生成 ninja 使用的构建文件(基本上是提前做出决策并为 ninja 记录下来),并且可以调用 ninja 执行建造。对于关系没有改变的情况,例如编辑/构建/调试迭代,这工作正常并且应该为更复杂的构建提供相当大的加速。这意味着如果发生较大的变化,ninja 就不合适了——但您始终可以使用 SCons 重新生成构建文件。不建议您将其用于生产构建。
要使用 ninja 工具,您需要先安装 Python ninja 包,因为该工具依赖于能够导入包。这可以通过以下方式完成
# In a virtualenv, or "python" is the native executable:
python -m pip install ninja
# Windows using Python launcher:
py -m pip install ninja
# Anaconda:
conda install -c conda-forge ninja 提醒一下,与任何非默认工具一样,您需要在使用前对其进行初始化(例如 env.Tool('ninja'))。
预计此时 Ninja 构建器不会适用于所有构建。它仍在积极开发中。
如果您发现您的构建不适用于 ninja,请将它带到用户邮件列表 [https://pairlist4.pair.net/mailman/listinfo/scons-users] 或#scons-help [https://discord .gg/bXVpWAy] 我们的 Discord 服务器上的频道。
具体来说,如果您的构建有很多(甚至任何)Python 函数操作,您可能会发现 ninja 构建会变慢,因为它将运行 ninja,然后 ninja 将为 Python 操作创建的每个目标运行 SCons。为了缓解其中一些问题,尤其是 SCons 中内置的那些基于 Python 的操作,有特殊的逻辑可以通过 ninja 构建文件中的 shell 命令实现这些操作。
当 ninja 运行生成的 ninja 构建文件时,ninja 将启动 scons 作为守护进程并将命令提供给 ninja 无法直接构建的 scons 进程。该守护进程将一直存在,直到被明确杀死或超时。超时由 $NINJA_SCONS_DAEMON_KEEP_ALIVE 设置。
如果任何 SConscript 文件更改或构建以 ninja 确定需要重新生成 build.ninja 文件的方式更改,守护程序将重新启动
解决纷争
配置任何软件构建工具以构建大型代码库的经验通常在某些时候涉及试图弄清楚为什么该工具以某种方式运行,以及如何让它以您想要的方式运行。 SCons 也不例外。
本附录包含许多不同的方法,您可以通过这些方法进一步了解 SCons 的行为。
请注意,我们始终有兴趣尝试改进您解决配置问题的方式。如果您遇到一个让您挠头的问题,而且似乎没有很好的调试方法,那么其他人也会遇到同样的问题的可能性很大。如果是这样,请使用 https://scons.org/contact.html 上的联系信息告知 SCons 开发团队,以便我们可以使用您的反馈来尝试想出更好的方法来帮助您和其他人获得对 SCons 行为的必要洞察,以帮助识别和修复配置问题
为什么要重建该目标? --debug=explain 选项
让我们看一个简单的错误配置示例,该示例导致每次运行 SCons 时都重新构建目标
Intentionally misspell the output file name in the
command used to create the file:
Command('file.out', 'file.in', 'cp $SOURCE file.oout')
在此示例中,根本原因很明显:我们故意拼错了 cp 命令中的输出文件名,因此该命令实际上并未构建我们告诉 SCons 期望的 file.out 文件。但如果问题并不明显,在命令行上指定 --debug=explain 选项会让 SCons 非常具体地告诉我们它决定重建目标的原因:
如果这是一个涉及大量构建输出的更复杂的示例,让 SCons 告诉我们它正在尝试重建目标文件,因为它不存在,这将是一个重要线索,表明我们调用构建的命令有问题它。
请注意,您还可以使用 --warn=target-not-built 来检查在执行构建规则后预期目标是否存在
- -debug=explain 选项也可以派上用场,以帮助找出更改了哪些输入文件。给定一个从三个源文件构建程序的简单配置,更改其中一个源文件并使用 --debug=explain 选项重建非常具体地显示了 SCons 重建文件的原因
这对于识别何时由于隐式依赖项(例如包含的 .h 文件)的更改而重建文件变得更有帮助。如果我们示例中的 file1.c 和 file3.c 文件都包含一个 hello.h 文件,那么更改该包含文件并使用 --debug=explain 选项重新运行 SCons 将查明这是对包含文件的更改开始重建链
那个建筑环境里有什么?dump方法
当您创建构造环境时,SCons 会使用构造变量填充它,这些变量是为它在您的系统上找到的各种编译器、链接器和实用程序设置的。虽然这通常很有帮助并且是您想要的,但如果 SCons 没有设置您期望设置的某些变量,则可能会令人沮丧。在这种情况下,有时使用构建环境 Dump 方法打印所有或部分构建变量会很有帮助。请注意,Dump 方法返回环境中变量的表示形式,供您打印(或以其他方式操作)
env = Environment() print(env.Dump())
这些示例中的构建环境实际上分别仅限于 gcc 和 Visual C++。
在现实生活中,施工环境可能包含更多的变量。另请注意,我们修改了上面的示例输出,使所有对象的内存地址都为常量 0x700000。实际上,您会看到每个对象都有不同的十六进制数。
为了更容易地看到您感兴趣的内容,Dump 方法允许您指定要显示的特定构造变量。例如,通常需要验证用于执行构建命令的外部环境,以确保 PATH 和其他环境变量已按应有的方式设置。您可以按如下方式执行此操作
env = Environment() print(env.Dump('ENV'))
SCons 知道哪些依赖项? --tree 选项
有时,要想弄清楚 SCons 正在做什么,最好的方法就是简单地看一下它根据您的 SConscript 文件构建的依赖关系图。 --tree 选项将以显示依赖层次结构的“ASCII 艺术”图形格式显示全部或部分 SCons 依赖关系图。
例如,给定以下输入 SConstruct 文件:
env = Environment(CPPPATH = ['.']) env.Program('prog', ['f1.c', 'f2.c', 'f3.c'])
当使用 -n(不执行)选项时也会打印树,这允许您检查配置的依赖关系图而无需实际重建树中的任何内容
默认情况下,SCons 使用“ASCII 艺术”来绘制树。可以使用画线字符(Unicode 称之为方框绘图)来进行更好的显示。为此,添加 linedraw 限定符
—tree 选项只打印指定目标的依赖图(如果没有在命令行上指定,则打印默认目标)。因此,如果您在命令行上指定一个目标,如 f2.o,--tree 选项将只打印该文件的依赖关系图:
status 参数可用于告诉 SCons 打印依赖图中每个文件的状态信息
注意 --tree=all,status 是等价的;如果仅存在状态,则假定所有。作为替代方案,您可以指定 --tree=derived 让 SCons 仅在树输出中打印派生目标,跳过源文件(如 .c 和 .h 文件)
您也可以将状态修饰符与派生一起使用
请注意 --tree= 参数的顺序无关紧要; --tree=status,derived 是完全等价的。
- -tree 选项的默认行为是每次在树中遇到库依赖项(或任何其他依赖项文件)时重复所有依赖项。如果某些目标文件共享其他目标文件,例如两个程序使用相同的库
在具有许多内部库和包含文件的大型配置中,这会很快导致巨大的输出树。
为了帮助使其更易于管理,可以将修剪修饰符添加到选项列表中,在这种情况下,SCons 将在方括号 ([]) 中打印在树打印期间已经访问过的目标的名称,以指示可以通过向上查找树找到目标文件的依赖关系
SCons 如何构建它执行的命令行? --debug=presub 选项
有时,SCons 执行的命令行看起来不像您预期的那样。在这种情况下,在 SCons 对它们执行替换之前查看字符串可能很有用。这可以通过 --debug=presub 选项来完成:
SCons 在哪里搜索库? --debug=findlibs 选项
要深入了解 SCons 正在搜索的库名称以及它正在搜索的目录,请使用 --debug=findlibs 选项。给定以下输入 SConstruct 文件:
env = Environment(LIBPATH = ['libs1', 'libs2']) env.Program('prog.c', LIBS=['foo', 'bar'])
libs1 和 libs2 中的库 libfoo.a 和 libbar.a 分别使用 --debug=findlibs 选项产生
SCons 在哪里爆发? -debug=stacktrace 选项
通常,SCons 会尝试使其错误消息简短且内容丰富。这意味着我们通常会尽量避免显示经验丰富的 Python 程序员所熟悉的堆栈跟踪,因为它们通常包含的信息比对大多数人有用的信息要多得多。
例如,以下 SConstruct 文件:
Program('prog.c')
如果 prog.c 文件不存在,则会生成以下错误
在这种情况下,错误非常明显。但如果不是,并且您想尝试获取有关该错误的更多信息,则 --debug=stacktrace 选项会向您显示问题在 SCons 源代码中的确切位置
当然,如果您确实需要深入研究 SCons 源代码,我们想知道是否或如何改进错误消息或故障排除选项以避免这种情况。不是每个人都有必要的时间或 Python 技能来深入研究源代码,我们也想为这些人改进 SCons
SCons 如何做出决定? --taskmastertrace 选项
内部 SCons 子系统处理遍历依赖图并控制关于重建什么的决策是 Taskmaster。 SCons 支持 --taskmastertrace 选项,该选项告诉 Taskmaster 打印有关各个节点的子节点(依赖项)的信息,这些节点沿着图形走,正在评估哪些特定的依赖节点,以及以什么顺序。
—taskmastertrace 选项将放置跟踪输出的文件名作为参数,其中(单个连字符)表示应将跟踪消息打印到标准输出
env = Environment(CPPPATH = ['.']) env.Program('prog.c')
—taskmastertrace 选项不提供有关决定文件是否是最新的实际计算的信息,但它确实显示了它所知道的每个节点的所有依赖项,以及这些依赖项的顺序评估。这可以作为一种替代方法来确定您的 SCons 配置或隐式依赖扫描是否已经真正识别出您想要它的所有正确依赖项。
观看 SCons 为构建准备目标:--debug=prepare 选项
有时 SCons 不会构建您想要的目标,而且很难找出原因。您可以使用 --debug=prepare 选项来查看 SCons 正在考虑的所有目标,以及它们是否已经是最新的。
在 SCons 决定是否构建目标之前打印该消息。
为什么文件消失了? -debug=duplicate 选项
当使用 Duplicate 选项创建变体目录时,有时您可能会发现文件没有链接或复制到您期望的位置(或根本没有),或者文件神秘地消失了。这些通常是由于 SConscript 文件中的某种错误配置所致,但调试起来可能很棘手。 --debug=duplicate 选项每次从源文件取消链接和重新链接(或复制,取决于设置)时都会显示,还会显示一条消息,用于删除不再具有相应源的“陈旧”变体目录文件文件。它还为在构建之前删除的每个目标打印一行,因为这也可能被误认为是同一件事
保持简单
多年来,许多开发人员选择深入研究并使用 SCons 构建极其复杂的构建系统,但有时并不能像预期的那样工作。作为一般规则,请确保在执行此操作之前需要找到复杂的解决方案。 SCons 是成熟的软件,并且随着时间的推移不断发展以满足许多功能请求,因此如果您能找到它,通常会有更简单的方法来做某事。 SCons 社区在这里可以提供帮助 - 讨论列表和聊天频道可以成为一种在着手实施之前找出是否可以更轻松地完成某些事情的方法。
当出现问题时,尝试将问题隔离到一个简单的测试用例中确实有帮助。创建复制器的工作通常可以帮助您自己发现问题,而一个简单的示例更容易让其他人查看并可能发现逻辑缺陷、API 滥用或其他可以完成某事的方式。另外,如果转实际上有一个真正的 SCons 错误(我们相信它是一个高质量的软件,但所有软件都有一些错误),很可能错误归档将导致请求一个简单的复制器