ReplacingBuilder

Description

ReplacingBuilder is a wrapper for SCons builders, used to call the wrapped builders with certain construction variables replaced. A mapping of original variable names into their replacements is provided via constructor arguments.

obj = ReplacingBuilder(env['BUILDERS']['Object'], CFLAGS='MY_CFLAGS')
env['BUILDERS']['MyObject'] = obj

In the above example, a builder named MyObject is created which passes the value of MY_CFLAGS instead of CFLAGS to the Object builder. When used as follows with gcc

env.Replace(MY_CFLAGS=['-Wall', '-Wextra'])
env.MyObject('test.c')

it will invoke compiler with -Wall and -Wextra flags

gcc -c -o test.o -Wall -Wextra test.c

Replacements are applied also to variables passed via builder’s keyword arguments

env.MyObject('test.c', MY_CFLAGS=['-Wall', '-Wextra'])

The ReplacingBuilder wrapper exposes inject_replacements() method which may be used to set the replacement variables in environment to their default values.

obj = ReplacingBuilder(env['BUILDERS']['Object'], CFLAGS='MY_CFLAGS')
env['BUILDERS']['MyObject'] = obj
obj.inject_replacements(env)
assert env['MY_CFLAGS'] == '$CFLAGS'

Note, that replacements also alter source and target prefixes/suffixes. Let’s redefine MyObject builder as follows

obj = ReplacingBuilder(env['BUILDERS']['Object'], OBJSUFFIX='MY_OBJSUFFIX')
env['BUILDERS']['MyObject'] = obj
env['MY_OBJSUFFIX'] = '.my$OBJSUFFIX'

this builder will produce files with suffix '.my.o' if the original $OBJSUFFIX is '.o'. Note, that when you wrap your own builders, they should use original variables, like $OBJSUFFIX for suffixes, not their replacements.

obj = SCons.Builder.Builder(action=SCons.Defaults.CXXAction,
                            emitter={},
                            prefix='$OBJPREFIX',   # <- not $MY_OBJPREFIX
                            suffix='$OBJSUFFIX',   # <- not $MY_OBJSUFFIX
                            src_builder=['MyCXXFile'],
                            src_suffix='$MY_CXXSUFFIX',
                            source_scanner=SCons.Tool.SourceFileScanner,
                            single_source=1)
obj = ReplacingBuilder(obj, OBJPREFIX='MY_OBJPREFIX',
                            OBJSUFFIX='MY_OBJSUFFIX')
env['BUILDERS']['MyObject'] = obj
# ...
env.SetDefault(MY_OBJSUFFIX='.my$OBJSUFFIX')

If we used suffix='$MY_OBJSUFFIX' in the above example, variable substitution would be performed twice, and the actual suffix woule be '.my.my.o' instead of '.my.o'.

Examples

Shared library builder for SWIG-generated python modules

The following example is a modified extract from scons-tool-swigpy tool. The presented code implements a shared library builder named SwigPyShlib which generates shared library (or dll) with prefix '_' (SWIG convention for generated Python modules) and suffix '.pyd' (Windows convention for Python extension modules). The infixes for SwigPyShlib, as well as few other values will be provided via SWIGPY_* variables. Instead of using SWIG to generate hello_wrap.c file, we write such a file by hand. Instead of loading _hello.pyd as a python module, we’ll write simple test.c program that will load _hello.pyd at runtime.

Example:Tool implementation
Tool module: site_scons/site_tools/swigpy.py
# -*- coding: utf-8 -*-
from sconstool.util import *
import SCons.Tool
import SCons.Builder

SwigPyVars = [
  'LINK',
  'LINKFLAGS',
  'LIBPATH',
  'LIBS',
  'SHOBJPREFIX',
  'SHOBJSUFFIX',
  'LIBPREFIX',
  'LIBSUFFIX',
  'SHLIBPREFIX',
  'SHLIBSUFFIX',
  'IMPLIBPREFIX',
  'IMPLIBSUFFIX',
  'WINDOWSEXPPREFIX',
  'WINDOWSEXPSUFFIX'
]


SwigPyReplacements = Replacements({k: 'SWIGPY_%s' % k for k in SwigPyVars })


class SwigPyShlibBuilder(ReplacingBuilder):
    def __call__(self, env, target, source, **kw):
        # preserve original 'LIBSUFFIXES' and 'LIBPREFIXES', such that
        # libraries having original 'LIBPREFIX', 'LIBSUFFIX', 'SHLIBPREFIX',
        # etc. will be found when required by the linker.
        ovr = {'LIBPREFIXES': [env.subst(x) for x in env['LIBPREFIXES']],
               'LIBSUFFIXES': [env.subst(x) for x in env['LIBSUFFIXES']]}
        return ReplacingBuilder.__call__(self, env, target, source, **dict(ovr, **kw))


def createSwigPyShlibBuilder(env):
    try:
        swigpy_shlib = env['BUILDERS']['SwigPyShlib']
    except KeyError:
        shlib = SCons.Tool.createSharedLibBuilder(env)
        swigpy_shlib = SwigPyShlibBuilder(shlib, SwigPyReplacements)
        env['BUILDERS']['SwigPyShlib'] = swigpy_shlib
    return swigpy_shlib


def setSwigPyDefaults(env):
    env.SetDefault(SWIGPY_SHLIBPREFIX='_')
    env.SetDefault(SWIGPY_LIBPREFIX='_')
    env.SetDefault(SWIGPY_IMPLIBPREFIX='_')
    env.SetDefault(SWIGPY_WINDOWSEXPPREFIX='_')
    env.SetDefault(SWIGPY_SHLIBSUFFIX='.pyd')
    SwigPyReplacements.inject(env, 'SetDefault')


def generate(env):
    createSwigPyShlibBuilder(env)
    setSwigPyDefaults(env)


def exists(env):
    return 1
Example:A project using the swigpy tool
Project file: SConstruct
# SConstruct
import sys
env = Environment(tools=['default', 'swigpy'])
env.Append(SWIGPY_LIBPATH=['.'])
env.SharedLibrary('hello', 'hello.c', CPPDEFINES={'BUILDING_HELLO': 1})
env.SwigPyShlib('hello', 'hello_wrap.c', SWIGPY_LIBS=['hello'], CPPDEFINES={'BUILDING_HELLO_WRAP': 1})
if sys.platform == 'win32':
  libs = []
else:
  libs = ['dl']

env.Program('test_hello.c', LIBS=libs)
C file: hello.c
#include <stdio.h>
#include "hello.h"
void HELLO_API hello() {
  printf("hello" EOL); fflush(stdout);
}
Header file: hello.h
#ifndef HELLO_H
#define HELLO_H

#ifdef _WIN32
# ifdef BUILDING_HELLO
#  define HELLO_API __declspec(dllexport)
# else
#  define HELLO_API __declspec(dllimport)
# endif
# define EOL "\r\n"
#else
# define HELLO_API
# define EOL "\n"
#endif

#ifdef __cplusplus
extern "C" {
#endif

extern void HELLO_API hello();

#ifdef __cplusplus
}
#endif
#endif
Wrapper C file: hello_wrap.c
#include "hello.h"
#include "hello_wrap.h"
#include <stdio.h>
void HELLO_WRAP_API hello_wrap() {
  printf("wrap" EOL "  ");  fflush(stdout);
  hello();
  printf("unwrap" EOL);     fflush(stdout);
}
Header file: hello_wrap.h
#ifndef HELLO_WRAP_H
#define HELLO_WRAP_H

#ifdef _WIN32
# ifdef BUILDING_HELLO_WRAP
#  define HELLO_WRAP_API __declspec(dllexport)
# else
#  define HELLO_WRAP_API __declspec(dllimport)
# endif
#else
# define HELLO_WRAP_API
#endif

#ifdef __cplusplus
extern "C" {
#endif

extern void HELLO_WRAP_API hello_wrap();

#ifdef __cplusplus
}
#endif

#endif /* HELLO_WRAP_H */
Test program: test_hello.c
#ifdef _WIN32
# include <windows.h>
typedef HINSTANCE lib_t;
# define OpenLib(_s) LoadLibrary(_s)
# define OpenLib_str "LoadLibrary"
# define LoadLibSym(_lib,_s) GetProcAddress(_lib, _s)
# define LoadLibSym_str "GetProcAddress"
#else
# include <dlfcn.h>
typedef void* lib_t;
# define OpenLib(_s) dlopen(_s, RTLD_NOW)
# define OpenLib_str "dlopen"
# define LoadLibSym(_lib,_s) dlsym(_lib,_s)
# define LoadLibSym_str "dlsym"
#endif

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef _WIN32

void error_exit(LPTSTR func)
{
    // Retrieve the system error message for the last-error code

    LPVOID msg;

    FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER |
                    FORMAT_MESSAGE_FROM_SYSTEM |
                    FORMAT_MESSAGE_IGNORE_INSERTS,
                    NULL,
                    GetLastError(),
                    MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
                    (LPTSTR) &msg,
                    0, NULL );
    fprintf(stderr, "%s() failed: %s\r\n", func, (LPTSTR)msg);
    LocalFree(msg);

    exit(EXIT_FAILURE);
}

#else

void error_exit(char const* func)
{
    fprintf(stderr, "%s() failed: %s\n", func, dlerror());
    exit(EXIT_FAILURE);
}

#endif

typedef void(*void_fcn_t)();

void_fcn_t load_hello_wrap()
{
  void_fcn_t hello_wrap;
  lib_t pyd = OpenLib("_hello.pyd");
  if(!pyd) {
    error_exit(OpenLib_str);
  }
  hello_wrap = (void_fcn_t)LoadLibSym(pyd, "hello_wrap");
  if(!hello_wrap) {
    error_exit(LoadLibSym_str);
  }
  return hello_wrap;
}

int main(int argc, const char* argv[])
{
  void_fcn_t hello_wrap = load_hello_wrap();
  hello_wrap();
  return 0;
}
Testing on Linux
$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gcc -o hello_wrap.os -c -fPIC -DBUILDING_HELLO_WRAP=1 hello_wrap.c
gcc -o hello.os -c -fPIC -DBUILDING_HELLO=1 hello.c
gcc -o libhello.so -shared hello.os
gcc -o _hello.pyd -shared hello_wrap.os -L. -lhello
gcc -o test_hello.o -c test_hello.c
gcc -o test_hello test_hello.o -ldl
scons: done building targets.
$ LD_LIBRARY_PATH='.' ./test_hello
wrap
  hello
unwrap