我有一个自定义构建器,它使用工具来构建使用源代码和带有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看到扫描程序看到的源文件的更改,并在文件更改时重建?
更新
我做了一些游戏,我创建了一个虚拟Decider,看看我是否能找出为什么没有添加这些文件。
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哈希值。
我能做什么,这些文件会调用决策者?
update2:
在发帖时忘记添加发射器的返回。
更新3
修改代码,使其不再调用外部构建器。它现在调用一个模拟构建器的内部构建器函数。这只是模拟外部构建器的行为。原始构建器操作为action = 'cd ${SOURCE.dir} && gen_docs ${SOURCE.file}
答案 0 :(得分:3)
这是由我认为SCons中的设计错误导致的:如果目录存在,目录节点总是被认为是最新的。
SCons FAQ的相关部分:
为什么我的目录只是第一次更新?
与其他所有构建系统一样,SCons认为用作目标的目录是最新的(如果存在)。第一次构建时,目录不存在,因此SCons运行了update命令。每次之后,该目录已经存在,因此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
答案 1 :(得分:0)
您的发射器未返回修改后的目标,源列表。
有关详细信息,请参阅http://www.scons.org/doc/HTML/scons-user/x3798.html:
发射器功能应返回已修改的目标列表 应该建立和建立目标的来源。