【问题标题】:Custom SCons Builder reports dependencies as current even when files have been changed即使文件已更改,自定义 SCons Builder 也会将依赖项报告为当前的
【发布时间】:2013-10-10 18:27:20
【问题描述】:

我有一个自定义构建器,它使用一个工具来构建文档,使用源代码和带有 Markdown 文档的文本文件。

该工具采用指定所有输入文件和输出选项的配置文件。

运行时,它会在标记为 html 的文件夹中生成文档。

我的构建器有一个扫描仪来查找所有输入文件

和一个发射器来设置输出目录。

扫描仪和发射器找到所有需要的文件。但是,当我重建时,它不会检测到输入文件的更改。

我制作了一个重现问题的构建器,将以下内容放在一个目录中:

gen_doc.py

import SCons.Builder
import os
import ConfigParser

def _doc_build_function(target, source, env):
    #print '***** Builder *****'
    config = ConfigParser.SafeConfigParser()
    try:
        fp = open(str(source[0]), 'r')
        config.readfp(fp)
    finally:
        fp.close()
    output_dir = ''
    if config.has_option('output_options', 'output_dir'):
        output_dir = config.get('output_options', 'output_dir')
    input_files = []
    if config.has_option('input_options', 'input'):
         input_files = config.get('input_options', 'input').split()
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    with open(output_dir + os.sep + 'index.html', 'wb') as out_file:
        for file in input_files:
            try:
                in_file = open(file, 'r')
                out_file.write(in_file.read())
            finally:
                in_file.close()


def _doc_scanner(node, env, path):
    source = []
    config = ConfigParser.SafeConfigParser()
    try:
        fp = open(str(node), 'r')
        config.readfp(fp)
    finally:
        fp.close()
    if config.has_option('input_options', 'input'):
        for i in config.get('input_options', 'input').split():
            source.append(os.path.abspath(i))
    return source

def _doc_emitter(target, source, env):
    target = []
    config = ConfigParser.SafeConfigParser()
    try:
        fp = open(str(source[0]), 'r')
        config.readfp(fp)
    finally:
        fp.close()
    if config.has_option('output_options', 'output_dir'):
        target.append(env.Dir(os.path.abspath(config.get('output_options', 'output_dir'))))
        env.Clean(source, env.Dir(os.path.abspath(config.get('output_options', 'output_dir'))))

    return target, source


def generate(env):
    doc_scanner = env.Scanner(function = _doc_scanner)

    doc_builder = SCons.Builder.Builder(
        action = _doc_build_function,
        emitter = _doc_emitter,
        source_scanner = doc_scanner,
        single_source = 1
    )

    env.Append(BUILDERS = {
        'gen_doc': doc_builder,
    })

def exists(env):
    '''Using internal builder'''
    return True

SConstruct

env = Environment()
env.Tool('gen_doc', toolpath=['.'])
env.gen_doc('config_doc')

config_doc

[input_options]
input = a.md b.md

[output_options]
output_dir = html

a.md

Hello

b.md

 world

当我运行它时,它会产生正确的输出

html 文件夹中的一个文件,其中包含一个名为“index.html”的文件

带有Hello world字样

当我跑步时

scons -n tree=status html

我得到以下内容

scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
scons: `html' is up to date.
 E         = exists
  R        = exists in repository only
   b       = implicit builder
   B       = explicit builder
    S      = side effect
     P     = precious
      A    = always build
       C   = current
        N  = no clean
         H = no cache

[E B   C  ]+-html
[E     C  ]  +-config_doc
[E     C  ]  +-a.md
[E     C  ]  +-b.md
scons: done building targets.

我进去修改b.md文件重新运行

scons -n tree=status html

输出相同,但仍将 b.md 报告为当前,因此不会重新构建文档。

有没有办法让 scons 看到扫描仪看到的源文件的更改并在文件更改时重建?

更新

我做了一点尝试,我创建了一个虚拟决策者,看看我是否能找出这些文件没有被添加的原因。

def foo(dependency, target, prev_ni):
    print 'dependency = %s' % (dependency)
    print 'target = %s' % (target)
    return True

在 'generate(env)' 中添加行 'env.Decider(foo)'

_doc_scanner 添加到树中的文件没有调用 Decider 函数,因此永远不会计算 MD5 哈希。

我该怎么做才能让这些文件调用决策者?

更新2:

发帖时忘记添加发射器的返回值。

更新 3

修改了代码,使其不再调用外部构建器。它现在调用模拟构建器的内部构建器函数。这只是模拟外部构建器的行为。最初的建造者行动是action = 'cd ${SOURCE.dir} && gen_docs ${SOURCE.file}

【问题讨论】:

    标签: dependencies build-automation scons


    【解决方案1】:

    这是由我认为 SCons 中的设计错误引起的:如果目录存在,目录节点总是被认为是最新的。

    来自SCons FAQ的相关部分:

    为什么我的目录只是第一次更新?

    与所有其他构建系统一样,SCons 将用作目标的目录视为最新的(如果存在)。第一次构建时,该目录不存在,因此 SCons 运行了更新命令。此后的每一次,该目录都已经存在,因此 SCons 认为它​​是最新的。

    您可以解决这个问题,认为这有点痛苦。对于您想要参与依赖关系图的每个目录,您需要创建一个“表示”该目录的虚拟文件。每当您生成目录时写入文件。依赖文件而不是目录。

    您的代码可以更新以执行此操作,因此:

    import SCons.Builder
    import os
    import ConfigParser
    import datetime
    
    def _manifest(target):
        return os.path.join('.manifest', str(target))
    
    def _touch(path):
        dirname = os.path.dirname(path)
        if not os.path.exists(dirname):
            os.makedirs(dirname)
        with open(path, 'wt') as f:
            f.write(str(datetime.datetime.now()))
    
    def _doc_build_function(target, source, env):
        #print '***** Builder *****'
        config = ConfigParser.SafeConfigParser()
        try:
            fp = open(str(source[0]), 'r')
            config.readfp(fp)
        finally:
            fp.close()
        output_dir = ''
        if config.has_option('output_options', 'output_dir'):
            output_dir = config.get('output_options', 'output_dir')
        input_files = []
        if config.has_option('input_options', 'input'):
             input_files = config.get('input_options', 'input').split()
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)
    
        with open(output_dir + os.sep + 'index.html', 'wb') as out_file:
            for file in input_files:
                try:
                    in_file = open(file, 'r')
                    out_file.write(in_file.read())
                finally:
                    in_file.close()
    
        for t in target:
            _touch(_manifest(t))
    
    
    def _doc_scanner(node, env, path):
        source = []
        config = ConfigParser.SafeConfigParser()
        try:
            fp = open(str(node), 'r')
            config.readfp(fp)
        finally:
            fp.close()
        if config.has_option('input_options', 'input'):
            for i in config.get('input_options', 'input').split():
                source.append(os.path.abspath(i))
        return source
    
    def _doc_emitter(target, source, env):
        target = []
        config = ConfigParser.SafeConfigParser()
        try:
            fp = open(str(source[0]), 'r')
            config.readfp(fp)
        finally:
            fp.close()
        if config.has_option('output_options', 'output_dir'):
            target.append(env.Dir(os.path.abspath(config.get('output_options', 'output_dir'))))
            env.Clean(source, env.Dir(os.path.abspath(config.get('output_options', 'output_dir'))))
    
        target.extend(map(_manifest, target))
    
        return target, source
    
    
    def generate(env):
        doc_scanner = env.Scanner(function = _doc_scanner)
    
        doc_builder = SCons.Builder.Builder(
            action = _doc_build_function,
            emitter = _doc_emitter,
            source_scanner = doc_scanner,
            single_source = 1
        )
    
        env.Append(BUILDERS = {
            'gen_doc': doc_builder,
        })
    
    def exists(env):
        '''Using internal builder'''
        return True
    

    【讨论】:

    • 我离开我的电脑有一段时间了,所以我可能需要一段时间才能尝试一下。一直以来,我一直认为这是一个源问题,即使我知道您指出的常见问题解答,我也从未考虑过目录节点。将尝试并让您知道我的结果。我认为 gen_docs 工具已经创建了一个带有日期的文件,所以我可以绕过创建自己的文件。
    【解决方案2】:

    您的发射器未返回修改后的目标、源列表。

    更多信息请见http://www.scons.org/doc/HTML/scons-user/x3798.html

    发射器函数应该返回修改后的目标列表 应该构建以及构建目标的来源。

    【讨论】:

    • 您对我提出的代码是正确的。这是重新解决问题的错误。 (正如我在注释中所说)我的原始代码确实返回了目标和源。我会更新问题谢谢。
    猜你喜欢
    • 2018-01-30
    • 2015-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-10
    • 1970-01-01
    • 2016-11-30
    相关资源
    最近更新 更多