UPDATE: SCons 1.3.0 and later ships with textfile.Substfile builder which works like this tool.

Here's a tool that does substitutions from a dictionary on a file. This code is freely available for your use. --GaryOberbrunner

This should work with 0.96.91 or later; earlier versions may or may not work due to the import statement (but the rest should be OK).

   1 import re
   2 from SCons.Script import *  # the usual scons stuff you get in a SConscript
   4 def TOOL_SUBST(env):
   5     """Adds SubstInFile builder, which substitutes the keys->values of SUBST_DICT
   6     from the source to the target.
   7     The values of SUBST_DICT first have any construction variables expanded
   8     (its keys are not expanded).
   9     If a value of SUBST_DICT is a python callable function, it is called and
  10     the result is expanded as the value.
  11     If there's more than one source and more than one target, each target gets
  12     substituted from the corresponding source.
  13     """
  14     env.Append(TOOLS = 'SUBST')
  15     def do_subst_in_file(targetfile, sourcefile, dict):
  16         """Replace all instances of the keys of dict with their values.
  17         For example, if dict is {'%VERSION%': '1.2345', '%BASE%': 'MyProg'},
  18         then all instances of %VERSION% in the file will be replaced with 1.2345 etc.
  19         """
  20         try:
  21             f = open(sourcefile, 'rb')
  22             contents = f.read()
  23             f.close()
  24         except:
  25             raise SCons.Errors.UserError, "Can't read source file %s"%sourcefile
  26         for (k,v) in dict.items():
  27             contents = re.sub(k, v, contents)
  28         try:
  29             f = open(targetfile, 'wb')
  30             f.write(contents)
  31             f.close()
  32         except:
  33             raise SCons.Errors.UserError, "Can't write target file %s"%targetfile
  34         return 0 # success
  36     def subst_in_file(target, source, env):
  37         if 'SUBST_DICT' not in env:
  38             raise SCons.Errors.UserError, "SubstInFile requires SUBST_DICT to be set."
  39         d = dict(env['SUBST_DICT']) # copy it
  40         for (k,v) in d.items():
  41             if callable(v):
  42                 d[k] = env.subst(v())
  43             elif SCons.Util.is_String(v):
  44                 d[k]=env.subst(v)
  45             else:
  46                 raise SCons.Errors.UserError, "SubstInFile: key %s: %s must be a string or callable"%(k, repr(v))
  47         for (t,s) in zip(target, source):
  48             return do_subst_in_file(str(t), str(s), d)
  50     def subst_in_file_string(target, source, env):
  51         """This is what gets printed on the console."""
  52         return '\n'.join(['Substituting vars from %s into %s'%(str(s), str(t))
  53                           for (t,s) in zip(target, source)])
  55     def subst_emitter(target, source, env):
  56         """Add dependency from substituted SUBST_DICT to target.
  57         Returns original target, source tuple unchanged.
  58         """
  59         d = env['SUBST_DICT'].copy() # copy it
  60         for (k,v) in d.items():
  61             if callable(v):
  62                 d[k] = env.subst(v())
  63             elif SCons.Util.is_String(v):
  64                 d[k]=env.subst(v)
  65         Depends(target, SCons.Node.Python.Value(d))
  66         return target, source
  68     subst_action=SCons.Action.Action(subst_in_file, subst_in_file_string)
  69     env['BUILDERS']['SubstInFile'] = Builder(action=subst_action, emitter=subst_emitter)

Here's how to use it:

   1 # Load the above tool either by importing it or just including the text in this file
   2 env=Environment(tools=('default', TOOL_SUBST))
   3 # or you could just call TOOL_SUBST(env), same thing.
   5 # This will replace all %FOO% with FooValue in foo.in, creating foo.out.
   6 env.SubstInFile('foo.out', 'foo.in',
   7                 SUBST_DICT={'%FOO%': 'FooValue'})

I found I needed to add varlist=['SUBST_DICT'] as a keyword argument to the subst_action for Scons to regenerate the output file if the SUBST_DICT has changed and the output file already exists. --TimPotter

Hi Tim; that's what the subst_emitter is supposed to do; I wonder why it didn't work for you? If you send me a testcase (on dev@scons.tigris.org) I'll look into it. I'd like to get this into SCons sometime, so I'm interested in improving it. --GaryOberbrunner

SubstInFileBuilder (last edited 2014-02-27 04:22:43 by AnatolyTechtonik)