This is caused by what I would consider a design bug in SCons: directory nodes are always considered up-to-date if the directory exists.
Relevant section from the SCons FAQ:
Why is my directory only updated the first time?
Like every other build system, SCons considers a directory used as a target as up-to-date if it exists. The first time you built, the directory wasn't there, so SCons ran the update command. Each time after that, the directory already existed, so SCons considered it up-to-date.
You can work around this, thought it's a bit of a pain. For every directory that you want to participate in the dependency graph, you need to create a dummy file that "represents" that directory. Write to the file whenever you generate the directory. Depend on the file instead of on the directory.
Your code can be updated to do this, thusly:
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