scons:如何处理动态目标?

3
我正在尝试使用scons自动化将PDF转换为png文件的工作。我使用ImageMagick中的convert工具进行转换。
以下是原始命令行:
  1. convert input.pdf temp/temp.png
  2. convert temp/*.png -append output.png
第一条命令将为PDF文件中的每个页面生成一个PNG文件,因此第一条命令的目标是动态文件列表。
以下是我正在使用的SConstruct文件:
convert = Builder(action=[
    Delete("${TARGET.dir}"),
    Mkdir("${TARGET.dir}"),
    "convert $SOURCE $TARGET"])
combine = Builder(action="convert $SOURCE -append $TARGET")

env = Environment(BUILDERS={"Convert": convert, "Combine": combine})

pdf = env.PDF("input.tex")
pngs = env.Convert("temp/temp.png", pdf) # I don't know how to specify target in this line
png = env.Combine('output.png', pngs)
Default(png)

代码“pngs = env.Convert(“temp / temp.png”,pdf)”实际上是错误的,因为目标是多个文件,我不知道在执行“env.Convert”之前有多少个文件,因此最终的“output.png”仅包含PDF文件的第一页。
任何提示都将不胜感激。
更新:
我刚发现我可以使用命令“convert input.pdf -append output.png”来避免两步转换。
但我仍然好奇如何处理中间临时文件列表事先未知并需要动态目标列表的情况。
3个回答

2
如果你想知道如何完成你提出的原始(转换和合并)情况,我建议使用SCons Emitter创建一个构建器。该发射器允许您修改源文件和目标文件列表。这对于在干净的构建中不存在的生成文件非常有效。
正如你所提到的,转换步骤将生成多个目标,关键是你需要能够在发射器中基于源“计算”这些目标。例如,最近我创建了一个wsdl2java构建器,并能够在发射器中进行一些简单的wsdl解析,以计算要生成的所有目标java文件(源为wsdl)。
以下是构建脚本应该看起来的一般想法:
def convert_emitter(source, target, env):
    # both and source and target will be a list of nodes
    # in this case, the target will be empty, and you need
    # to calculate all of the generated targets based on the
    # source pdf file. You will need to open the source file 
    # with standard python code. All of the targets will be
    # removed when cleaned (scons -c)
    target = [] # fill in accordingly
    return (target, source)

# Optionally, you could supply a function for the action
# which would have the same signature as the emitter
convert = env.Builder(emitter=convert_emitter,
                      action=[
                         Delete("temp"),
                         Mkdir("temp"),
                         "convert $SOURCE $TARGET"])
env.Append(BUILDERS={'Convert' : convert})

combine = env.Builder(action=convert_action, emitter=combine_emitter)
env.Append(BUILDERS={'Combine' : combine})

pdf = env.PDF('input.tex')
# You can omit the target in this call, as it will be filled-in by the emitter
pngs = env.Convert(source=pdf)
png = env.Combine(target='output.png', source=pngs)

这里从未定义combine_emitter。您能否也描述一下它的概述? - metasoarous
此外...更重要的是...我正在尝试类似于这样的东西,但无法使其工作,因为当它到达emitter时,(相当于)pdf目标文件还不存在,因此它无法从该文件计算任何内容。我认为这种技术的重点在于它以某种方式推迟了发射器函数的执行,直到所有其他已知输入源都被构建完成,以便您可以根据这些输入动态计算目标。就我所知,您编写的内容不能“动态”地运行。如果我漏掉了什么,请告诉我。 - metasoarous

2
根据您对"动态"的定义,我认为正确的答案是:不可能。只要您想要“动态”计算目标集的源在SCons运行时存在,@Brady的解决方案就可以正常工作。但是,如果所讨论的源本身是其他某个命令的目标,则它将无法工作。这是SCons的基本限制,因为它假设可以从输入(非中间)源的基本集合静态确定构建目标集的集合。它一次性运行并计算构建/目标/依赖项图,然后在下一步执行它。它没有能力运行一些已知的构建图部分,停止以检查一些中间目标以动态计算构建图的其余部分,然后继续。在我使用SCons进行工作时,我确实希望具备这种能力,但恐怕这只是一个根本性的限制。
最好的方法是设置构建,以便在第一次运行时,它在PDF的构建处停止(如果在执行构建脚本时不存在PDF目标)。一旦构建了PDF,您可以重新运行构建,并设置其他构建步骤基于上一次运行生成的PDF执行。这个方法基本上可以很好地工作...除了一个问题。如果PDF发生更改(例如产生一些新页面),则实际上必须重新运行构建两次,以捕获PDF的更改,因为任何页面计数(等)都基于旧版本的PDF。
我希望有人能证明我在这里是错误的,但事情就是这样。

0
看起来,对于个别的temp/*png文件没有保留的要求 - 如果有的话,你不应该把它们放在临时目录中,而且无论如何,如果你想要确定生成哪些页面,你还需要做很多工作。
所以更明智的做法是将其作为一个步骤进行,就像这样:
png = env.Convert('output.png', 'input.pdf')

其中转换操作的函数为something,类似于这样:

Delete('temp'),
Mkdir('temp'),
'convert $SOURCE temp/$TARGET',
'for i in temp/*png; do convert $TARGET temp/$i',
Delete('temp')

老实说,你最好将整个内容写成一个可调用的脚本,以确保页面排序正确。

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