Search code examples
scons

Additional, specific source and target for a Builder


I'm new to Scons and I'm trying to figure out if I could use it for my use-case. I have a script whose main actoin is to take a single input and produces multiple output files in a given directory. However, it also needs one additional input and one additional output, as in

script --special-in some.foo --special-in some.bar input.foo output.dir/

The names of some.* files can be computed from the input file name (here input.foo). And the some.* files produced by one rule are consumed by other rules.

In the documentation I found that one can create custom builders as in

bld = Builder(action = 'foobuild $TARGETS - $SOURCES',
              suffix = '.foo',
              src_suffix = '.input',
              emitter = modify_targets)

where the emitter adds the additional target and source. However, I couldn't find how should I distinguish the main source/target from the special ones, which need to be passed using specific options - I can't use $TARGETS and $SOURCES as in the above example. I could probably use a generator and index into source and target, but this seems a bit hacky. I there a better way?


Solution

  • From what you describe, you should be using both an emitter and a generator, just as you state at the end of your question. The "main" source/target will be the first element in the source/target lists. This doesn't seem hacky to me, but I may just be used to it...

    Answers are always better with a working example...

    Here is the SConstruct to do what you describe. I'm not exactly sure how you plan to compute some.foo and some.bar from input.foo, so in this example I compute input.bar and input.baz from input.foo, and just append output.dir to the list of targets.

    import os
    
    def my_generator(source, target, env, for_signature):
        command = './script '
        command += ' '.join(['--special-in %s' % str(i) for i in source[1:]])
        command += ' '
        command += ' '.join([str(t) for t in target])
        return command
    
    def my_emitter(target, source, env):
        source += ['%s%s' % (os.path.splitext(
            str(source[0]))[0], ext) for ext in ['.bar', '.baz']]
        target += ['output.dir']
        return target, source
    
    bld = Builder(generator=my_generator,
                  emitter=my_emitter)
    
    env = Environment(BUILDERS={'Foo':bld})
    env.Foo('output.foo', 'input.foo')
    

    When run on linux...

    >> touch input.bar input.baz input.foo
    
    >> echo "#\!/bin/sh" > script && chmod +x script
    
    >> tree
    .
    ├── input.bar
    ├── input.baz
    ├── input.foo
    ├── SConstruct
    └── script
    
    0 directories, 5 files
    
    >> scons --version
    SCons by Steven Knight et al.:
        script: v2.3.4, 2014/09/27 12:51:43, by garyo on lubuntu
        engine: v2.3.4, 2014/09/27 12:51:43, by garyo on lubuntu
        engine path: ['/usr/lib/scons/SCons']
    Copyright (c) 2001 - 2014 The SCons Foundation
    
    >> scons
    scons: Reading SConscript files ...
    scons: done reading SConscript files.
    scons: Building targets ...
    ./script --special-in input.bar --special-in input.baz output.foo output.dir
    scons: done building targets.
    

    All dependencies/targets will be maintained, if you need to feed the outputs from one builder like this into another.

    If this doesn't answer your question, please clarify what more you are trying to do.