mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2024-12-22 20:06:49 +01:00
1630 lines
64 KiB
Python
1630 lines
64 KiB
Python
# MIT License
|
|
#
|
|
# Copyright The SCons Foundation
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining
|
|
# a copy of this software and associated documentation files (the
|
|
# "Software"), to deal in the Software without restriction, including
|
|
# without limitation the rights to use, copy, modify, merge, publish,
|
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
# permit persons to whom the Software is furnished to do so, subject to
|
|
# the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included
|
|
# in all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
import SCons.compat
|
|
|
|
# Define a null function for use as a builder action.
|
|
# Where this is defined in the file seems to affect its
|
|
# byte-code contents, so try to minimize changes by
|
|
# defining it here, before we even import anything.
|
|
def Func():
|
|
pass
|
|
|
|
from collections import UserList
|
|
import io
|
|
import os.path
|
|
import re
|
|
import sys
|
|
import unittest
|
|
|
|
import TestCmd
|
|
|
|
import SCons.Action
|
|
import SCons.Builder
|
|
import SCons.Environment
|
|
import SCons.Errors
|
|
import SCons.Subst
|
|
import SCons.Util
|
|
|
|
sys.stdout = io.StringIO()
|
|
|
|
# Initial setup of the common environment for all tests,
|
|
# a temporary working directory containing a
|
|
# script for writing arguments to an output file.
|
|
#
|
|
# We don't do this as a setUp() method because it's
|
|
# unnecessary to create a separate directory and script
|
|
# for each test, they can just use the one.
|
|
test = TestCmd.TestCmd(workdir = '')
|
|
|
|
outfile = test.workpath('outfile')
|
|
outfile2 = test.workpath('outfile2')
|
|
|
|
infile = test.workpath('infile')
|
|
test.write(infile, "infile\n")
|
|
|
|
show_string = None
|
|
|
|
scons_env = SCons.Environment.Environment()
|
|
|
|
env_arg2nodes_called = None
|
|
|
|
class Environment:
|
|
def __init__(self, **kw):
|
|
self.d = {}
|
|
self.d['SHELL'] = scons_env['SHELL']
|
|
self.d['SPAWN'] = scons_env['SPAWN']
|
|
self.d['ESCAPE'] = scons_env['ESCAPE']
|
|
for k, v in kw.items():
|
|
self.d[k] = v
|
|
global env_arg2nodes_called
|
|
env_arg2nodes_called = None
|
|
self.scanner = None
|
|
self.fs = SCons.Node.FS.FS()
|
|
def subst(self, s):
|
|
if not SCons.Util.is_String(s):
|
|
return s
|
|
def substitute(m, d=self.d):
|
|
return d.get(m.group(1), '')
|
|
return re.sub(r'\$(\w+)', substitute, s)
|
|
def subst_target_source(self, string, raw=0, target=None,
|
|
source=None, dict=None, conv=None):
|
|
return SCons.Subst.scons_subst(string, self, raw, target,
|
|
source, dict, conv)
|
|
def subst_list(self, string, raw=0, target=None, source=None, conv=None):
|
|
return SCons.Subst.scons_subst_list(string, self, raw, target,
|
|
source, {}, {}, conv)
|
|
def arg2nodes(self, args, factory, **kw):
|
|
global env_arg2nodes_called
|
|
env_arg2nodes_called = 1
|
|
if not SCons.Util.is_List(args):
|
|
args = [args]
|
|
list = []
|
|
for a in args:
|
|
if SCons.Util.is_String(a):
|
|
a = factory(self.subst(a))
|
|
list.append(a)
|
|
return list
|
|
def get_factory(self, factory):
|
|
return factory or self.fs.File
|
|
def get_scanner(self, ext):
|
|
return self.scanner
|
|
def Dictionary(self):
|
|
return {}
|
|
def autogenerate(self, dir=''):
|
|
return {}
|
|
def __setitem__(self, item, var):
|
|
self.d[item] = var
|
|
def __getitem__(self, item):
|
|
return self.d[item]
|
|
def __contains__(self, key):
|
|
return key in self.d
|
|
def keys(self):
|
|
return list(self.d.keys())
|
|
def get(self, key, value=None):
|
|
return self.d.get(key, value)
|
|
def Override(self, overrides):
|
|
env = Environment(**self.d)
|
|
env.d.update(overrides)
|
|
env.scanner = self.scanner
|
|
return env
|
|
def _update(self, dict):
|
|
self.d.update(dict)
|
|
def items(self):
|
|
return list(self.d.items())
|
|
def sig_dict(self):
|
|
d = {}
|
|
for k,v in self.items(): d[k] = v
|
|
d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__']
|
|
d['TARGET'] = d['TARGETS'][0]
|
|
d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__']
|
|
d['SOURCE'] = d['SOURCES'][0]
|
|
return d
|
|
def __eq__(self, other):
|
|
return self.scanner == other.scanner or self.d == other.d
|
|
|
|
class MyAction:
|
|
def __init__(self, action):
|
|
self.action = action
|
|
def __call__(self, *args, **kw):
|
|
pass
|
|
def get_executor(self, env, overrides, tlist, slist, executor_kw):
|
|
return ['executor'] + [self.action]
|
|
|
|
class MyNode_without_target_from_source:
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.sources = []
|
|
self.builder = None
|
|
self.is_explicit = None
|
|
self.side_effect = 0
|
|
def get_suffix(self):
|
|
return os.path.splitext(self.name)[1]
|
|
def disambiguate(self):
|
|
return self
|
|
def __str__(self):
|
|
return self.name
|
|
def builder_set(self, builder):
|
|
self.builder = builder
|
|
def has_builder(self):
|
|
return self.builder is not None
|
|
def set_explicit(self, is_explicit):
|
|
self.is_explicit = is_explicit
|
|
def has_explicit_builder(self):
|
|
return self.is_explicit
|
|
def env_set(self, env, safe=0):
|
|
self.env = env
|
|
def add_source(self, source):
|
|
self.sources.extend(source)
|
|
def scanner_key(self):
|
|
return self.name
|
|
def is_derived(self):
|
|
return self.has_builder()
|
|
def generate_build_env(self, env):
|
|
return env
|
|
def get_build_env(self):
|
|
return self.executor.get_build_env()
|
|
def set_executor(self, executor):
|
|
self.executor = executor
|
|
def get_executor(self, create=1):
|
|
return self.executor
|
|
|
|
class MyNode(MyNode_without_target_from_source):
|
|
def target_from_source(self, prefix, suffix, stripext):
|
|
return MyNode(prefix + stripext(str(self))[0] + suffix)
|
|
|
|
class BuilderTestCase(unittest.TestCase):
|
|
|
|
def test__init__(self):
|
|
"""Test simple Builder creation
|
|
"""
|
|
builder = SCons.Builder.Builder(action="foo")
|
|
assert builder is not None, builder
|
|
builder = SCons.Builder.Builder(action="foo", OVERRIDE='x')
|
|
x = builder.overrides['OVERRIDE']
|
|
assert x == 'x', x
|
|
|
|
def test__bool__(self):
|
|
"""Test a builder raising an exception when __bool__ is called. """
|
|
|
|
# basic test: explicitly call it
|
|
builder = SCons.Builder.Builder(action="foo")
|
|
exc_caught = None
|
|
try:
|
|
builder.__bool__()
|
|
except SCons.Errors.InternalError:
|
|
exc_caught = 1
|
|
assert exc_caught, "did not catch expected InternalError exception"
|
|
|
|
class Node:
|
|
pass
|
|
|
|
# the interesting test: checking the attribute this way
|
|
# should call back to Builder.__bool__ - so we can tell
|
|
# people not to check that way (for performance).
|
|
# TODO: workaround #3860: with just a "pass" in the check body,
|
|
# py3.10a optimizes out the whole thing, so add a "real" stmt
|
|
n = Node()
|
|
n.builder = builder
|
|
exc_caught = None
|
|
try:
|
|
if n.builder:
|
|
#pass
|
|
_ = True
|
|
except SCons.Errors.InternalError:
|
|
exc_caught = 1
|
|
assert exc_caught, "did not catch expected InternalError exception"
|
|
|
|
def test__call__(self):
|
|
"""Test calling a builder to establish source dependencies
|
|
"""
|
|
env = Environment()
|
|
builder = SCons.Builder.Builder(action="foo",
|
|
target_factory=MyNode,
|
|
source_factory=MyNode)
|
|
|
|
tgt = builder(env, source=[])
|
|
assert tgt == [], tgt
|
|
|
|
n1 = MyNode("n1")
|
|
n2 = MyNode("n2")
|
|
builder(env, target = n1, source = n2)
|
|
assert env_arg2nodes_called
|
|
assert n1.env == env, n1.env
|
|
assert n1.builder == builder, n1.builder
|
|
assert n1.sources == [n2], n1.sources
|
|
assert n1.executor, "no executor found"
|
|
assert not hasattr(n2, 'env')
|
|
|
|
l = [1]
|
|
ul = UserList([2])
|
|
try:
|
|
l.extend(ul)
|
|
except TypeError:
|
|
def mystr(l):
|
|
return str(list(map(str, l)))
|
|
else:
|
|
mystr = str
|
|
|
|
nnn1 = MyNode("nnn1")
|
|
nnn2 = MyNode("nnn2")
|
|
tlist = builder(env, target = [nnn1, nnn2], source = [])
|
|
s = mystr(tlist)
|
|
assert s == "['nnn1', 'nnn2']", s
|
|
l = list(map(str, tlist))
|
|
assert l == ['nnn1', 'nnn2'], l
|
|
|
|
tlist = builder(env, target = 'n3', source = 'n4')
|
|
s = mystr(tlist)
|
|
assert s == "['n3']", s
|
|
target = tlist[0]
|
|
l = list(map(str, tlist))
|
|
assert l == ['n3'], l
|
|
assert target.name == 'n3'
|
|
assert target.sources[0].name == 'n4'
|
|
|
|
tlist = builder(env, target = 'n4 n5', source = ['n6 n7'])
|
|
s = mystr(tlist)
|
|
assert s == "['n4 n5']", s
|
|
l = list(map(str, tlist))
|
|
assert l == ['n4 n5'], l
|
|
target = tlist[0]
|
|
assert target.name == 'n4 n5'
|
|
assert target.sources[0].name == 'n6 n7'
|
|
|
|
tlist = builder(env, target = ['n8 n9'], source = 'n10 n11')
|
|
s = mystr(tlist)
|
|
assert s == "['n8 n9']", s
|
|
l = list(map(str, tlist))
|
|
assert l == ['n8 n9'], l
|
|
target = tlist[0]
|
|
assert target.name == 'n8 n9'
|
|
assert target.sources[0].name == 'n10 n11'
|
|
|
|
# A test to be uncommented when we freeze the environment
|
|
# as part of calling the builder.
|
|
#env1 = Environment(VAR='foo')
|
|
#target = builder(env1, target = 'n12', source = 'n13')
|
|
#env1['VAR'] = 'bar'
|
|
#be = target.get_build_env()
|
|
#assert be['VAR'] == 'foo', be['VAR']
|
|
|
|
n20 = MyNode_without_target_from_source('n20')
|
|
flag = 0
|
|
try:
|
|
target = builder(env, None, source=n20)
|
|
except SCons.Errors.UserError as e:
|
|
flag = 1
|
|
assert flag, "UserError should be thrown if a source node can't create a target."
|
|
|
|
builder = SCons.Builder.Builder(action="foo",
|
|
target_factory=MyNode,
|
|
source_factory=MyNode,
|
|
prefix='p-',
|
|
suffix='.s')
|
|
target = builder(env, None, source='n21')[0]
|
|
assert target.name == 'p-n21.s', target
|
|
|
|
builder = SCons.Builder.Builder(misspelled_action="foo",
|
|
suffix = '.s')
|
|
try:
|
|
builder(env, target = 'n22', source = 'n22')
|
|
except SCons.Errors.UserError as e:
|
|
pass
|
|
else:
|
|
raise Exception("Did not catch expected UserError.")
|
|
|
|
builder = SCons.Builder.Builder(action="foo")
|
|
target = builder(env, None, source='n22', srcdir='src_dir')[0]
|
|
p = target.sources[0].get_internal_path()
|
|
assert p == os.path.join('src_dir', 'n22'), p
|
|
|
|
def test_mistaken_variables(self):
|
|
"""Test keyword arguments that are often mistakes
|
|
"""
|
|
import SCons.Warnings
|
|
env = Environment()
|
|
builder = SCons.Builder.Builder(action="foo")
|
|
|
|
save_warn = SCons.Warnings.warn
|
|
warned = []
|
|
def my_warn(exception, warning, warned=warned):
|
|
warned.append(warning)
|
|
SCons.Warnings.warn = my_warn
|
|
|
|
try:
|
|
target = builder(env, 'mistaken1', sources='mistaken1.c')
|
|
assert warned == ["Did you mean to use `source' instead of `sources'?"], warned
|
|
del warned[:]
|
|
|
|
target = builder(env, 'mistaken2', targets='mistaken2.c')
|
|
assert warned == ["Did you mean to use `target' instead of `targets'?"], warned
|
|
del warned[:]
|
|
|
|
target = builder(env, 'mistaken3', targets='mistaken3', sources='mistaken3.c')
|
|
assert "Did you mean to use `source' instead of `sources'?" in warned, warned
|
|
assert "Did you mean to use `target' instead of `targets'?" in warned, warned
|
|
del warned[:]
|
|
finally:
|
|
SCons.Warnings.warn = save_warn
|
|
|
|
def test_action(self):
|
|
"""Test Builder creation
|
|
|
|
Verify that we can retrieve the supplied action attribute.
|
|
"""
|
|
builder = SCons.Builder.Builder(action="foo")
|
|
assert builder.action.cmd_list == "foo"
|
|
|
|
def func():
|
|
pass
|
|
builder = SCons.Builder.Builder(action=func)
|
|
assert isinstance(builder.action, SCons.Action.FunctionAction)
|
|
# Preserve the following so that the baseline test will fail.
|
|
# Remove it in favor of the previous test at some convenient
|
|
# point in the future.
|
|
assert builder.action.execfunction == func
|
|
|
|
def test_generator(self):
|
|
"""Test Builder creation given a generator function."""
|
|
|
|
def generator():
|
|
pass
|
|
|
|
builder = SCons.Builder.Builder(generator=generator)
|
|
assert builder.action.generator == generator
|
|
|
|
def test_cmp(self):
|
|
"""Test simple comparisons of Builder objects
|
|
"""
|
|
b1 = SCons.Builder.Builder(src_suffix = '.o')
|
|
b2 = SCons.Builder.Builder(src_suffix = '.o')
|
|
assert b1 == b2
|
|
b3 = SCons.Builder.Builder(src_suffix = '.x')
|
|
assert b1 != b3
|
|
assert b2 != b3
|
|
|
|
def test_target_factory(self):
|
|
"""Test a Builder that creates target nodes of a specified class
|
|
"""
|
|
class Foo:
|
|
pass
|
|
def FooFactory(target):
|
|
global Foo
|
|
return Foo(target)
|
|
builder = SCons.Builder.Builder(target_factory = FooFactory)
|
|
assert builder.target_factory is FooFactory
|
|
assert builder.source_factory is not FooFactory
|
|
|
|
def test_source_factory(self):
|
|
"""Test a Builder that creates source nodes of a specified class
|
|
"""
|
|
class Foo:
|
|
pass
|
|
def FooFactory(source):
|
|
global Foo
|
|
return Foo(source)
|
|
builder = SCons.Builder.Builder(source_factory = FooFactory)
|
|
assert builder.target_factory is not FooFactory
|
|
assert builder.source_factory is FooFactory
|
|
|
|
def test_splitext(self):
|
|
"""Test the splitext() method attached to a Builder."""
|
|
b = SCons.Builder.Builder()
|
|
assert b.splitext('foo') == ('foo','')
|
|
assert b.splitext('foo.bar') == ('foo','.bar')
|
|
assert b.splitext(os.path.join('foo.bar', 'blat')) == (os.path.join('foo.bar', 'blat'),'')
|
|
|
|
class MyBuilder(SCons.Builder.BuilderBase):
|
|
def splitext(self, path):
|
|
return "called splitext()"
|
|
|
|
b = MyBuilder()
|
|
ret = b.splitext('xyz.c')
|
|
assert ret == "called splitext()", ret
|
|
|
|
def test_adjust_suffix(self):
|
|
"""Test how a Builder adjusts file suffixes
|
|
"""
|
|
b = SCons.Builder.Builder()
|
|
assert b.adjust_suffix('.foo') == '.foo'
|
|
assert b.adjust_suffix('foo') == '.foo'
|
|
assert b.adjust_suffix('$foo') == '$foo'
|
|
|
|
class MyBuilder(SCons.Builder.BuilderBase):
|
|
def adjust_suffix(self, suff):
|
|
return "called adjust_suffix()"
|
|
|
|
b = MyBuilder()
|
|
ret = b.adjust_suffix('.foo')
|
|
assert ret == "called adjust_suffix()", ret
|
|
|
|
def test_prefix(self):
|
|
"""Test Builder creation with a specified target prefix
|
|
|
|
Make sure that there is no '.' separator appended.
|
|
"""
|
|
env = Environment()
|
|
builder = SCons.Builder.Builder(prefix = 'lib.')
|
|
assert builder.get_prefix(env) == 'lib.'
|
|
builder = SCons.Builder.Builder(prefix = 'lib', action='')
|
|
assert builder.get_prefix(env) == 'lib'
|
|
tgt = builder(env, target = 'tgt1', source = 'src1')[0]
|
|
assert tgt.get_internal_path() == 'libtgt1', \
|
|
"Target has unexpected name: %s" % tgt.get_internal_path()
|
|
tgt = builder(env, target = 'tgt2a tgt2b', source = 'src2')[0]
|
|
assert tgt.get_internal_path() == 'libtgt2a tgt2b', \
|
|
"Target has unexpected name: %s" % tgt.get_internal_path()
|
|
tgt = builder(env, target = None, source = 'src3')[0]
|
|
assert tgt.get_internal_path() == 'libsrc3', \
|
|
"Target has unexpected name: %s" % tgt.get_internal_path()
|
|
tgt = builder(env, target = None, source = 'lib/src4')[0]
|
|
assert tgt.get_internal_path() == os.path.join('lib', 'libsrc4'), \
|
|
"Target has unexpected name: %s" % tgt.get_internal_path()
|
|
tgt = builder(env, target = 'lib/tgt5', source = 'lib/src5')[0]
|
|
assert tgt.get_internal_path() == os.path.join('lib', 'libtgt5'), \
|
|
"Target has unexpected name: %s" % tgt.get_internal_path()
|
|
|
|
def gen_prefix(env, sources):
|
|
return "gen_prefix() says " + env['FOO']
|
|
my_env = Environment(FOO = 'xyzzy')
|
|
builder = SCons.Builder.Builder(prefix = gen_prefix)
|
|
assert builder.get_prefix(my_env) == "gen_prefix() says xyzzy"
|
|
my_env['FOO'] = 'abracadabra'
|
|
assert builder.get_prefix(my_env) == "gen_prefix() says abracadabra"
|
|
|
|
def my_emit(env, sources):
|
|
return env.subst('$EMIT')
|
|
my_env = Environment(FOO = '.foo', EMIT = 'emit-')
|
|
builder = SCons.Builder.Builder(prefix = {None : 'default-',
|
|
'.in' : 'out-',
|
|
'.x' : 'y-',
|
|
'$FOO' : 'foo-',
|
|
'.zzz' : my_emit},
|
|
action = '')
|
|
tgt = builder(my_env, target = None, source = 'f1')[0]
|
|
assert tgt.get_internal_path() == 'default-f1', tgt.get_internal_path()
|
|
tgt = builder(my_env, target = None, source = 'f2.c')[0]
|
|
assert tgt.get_internal_path() == 'default-f2', tgt.get_internal_path()
|
|
tgt = builder(my_env, target = None, source = 'f3.in')[0]
|
|
assert tgt.get_internal_path() == 'out-f3', tgt.get_internal_path()
|
|
tgt = builder(my_env, target = None, source = 'f4.x')[0]
|
|
assert tgt.get_internal_path() == 'y-f4', tgt.get_internal_path()
|
|
tgt = builder(my_env, target = None, source = 'f5.foo')[0]
|
|
assert tgt.get_internal_path() == 'foo-f5', tgt.get_internal_path()
|
|
tgt = builder(my_env, target = None, source = 'f6.zzz')[0]
|
|
assert tgt.get_internal_path() == 'emit-f6', tgt.get_internal_path()
|
|
|
|
def test_set_suffix(self):
|
|
"""Test the set_suffix() method"""
|
|
b = SCons.Builder.Builder(action='')
|
|
env = Environment(XSUFFIX = '.x')
|
|
|
|
s = b.get_suffix(env)
|
|
assert s == '', s
|
|
|
|
b.set_suffix('.foo')
|
|
s = b.get_suffix(env)
|
|
assert s == '.foo', s
|
|
|
|
b.set_suffix('$XSUFFIX')
|
|
s = b.get_suffix(env)
|
|
assert s == '.x', s
|
|
|
|
def test_src_suffix(self):
|
|
"""Test Builder creation with a specified source file suffix
|
|
|
|
Make sure that the '.' separator is appended to the
|
|
beginning if it isn't already present.
|
|
"""
|
|
env = Environment(XSUFFIX = '.x', YSUFFIX = '.y')
|
|
|
|
b1 = SCons.Builder.Builder(src_suffix = '.c', action='')
|
|
assert b1.src_suffixes(env) == ['.c'], b1.src_suffixes(env)
|
|
|
|
tgt = b1(env, target = 'tgt2', source = 'src2')[0]
|
|
assert tgt.sources[0].get_internal_path() == 'src2.c', \
|
|
"Source has unexpected name: %s" % tgt.sources[0].get_internal_path()
|
|
|
|
tgt = b1(env, target = 'tgt3', source = 'src3a src3b')[0]
|
|
assert len(tgt.sources) == 1
|
|
assert tgt.sources[0].get_internal_path() == 'src3a src3b.c', \
|
|
"Unexpected tgt.sources[0] name: %s" % tgt.sources[0].get_internal_path()
|
|
|
|
b2 = SCons.Builder.Builder(src_suffix = '.2', src_builder = b1)
|
|
r = sorted(b2.src_suffixes(env))
|
|
assert r == ['.2', '.c'], r
|
|
|
|
b3 = SCons.Builder.Builder(action = {'.3a' : '', '.3b' : ''})
|
|
s = sorted(b3.src_suffixes(env))
|
|
assert s == ['.3a', '.3b'], s
|
|
|
|
b4 = SCons.Builder.Builder(src_suffix = '$XSUFFIX')
|
|
assert b4.src_suffixes(env) == ['.x'], b4.src_suffixes(env)
|
|
|
|
b5 = SCons.Builder.Builder(action = { '.y' : ''})
|
|
assert b5.src_suffixes(env) == ['.y'], b5.src_suffixes(env)
|
|
|
|
def test_srcsuffix_nonext(self):
|
|
"""Test target generation from non-extension source suffixes"""
|
|
env = Environment()
|
|
b6 = SCons.Builder.Builder(action = '',
|
|
src_suffix='_src.a',
|
|
suffix='.b')
|
|
tgt = b6(env, target=None, source='foo_src.a')
|
|
assert str(tgt[0]) == 'foo.b', str(tgt[0])
|
|
|
|
b7 = SCons.Builder.Builder(action = '',
|
|
src_suffix='_source.a',
|
|
suffix='_obj.b')
|
|
b8 = SCons.Builder.Builder(action = '',
|
|
src_builder=b7,
|
|
suffix='.c')
|
|
tgt = b8(env, target=None, source='foo_source.a')
|
|
assert str(tgt[0]) == 'foo_obj.c', str(tgt[0])
|
|
src = env.fs.File('foo_source.a')
|
|
tgt = b8(env, target=None, source=src)
|
|
assert str(tgt[0]) == 'foo_obj.c', str(tgt[0])
|
|
|
|
b9 = SCons.Builder.Builder(action={'_src.a' : 'srcaction'},
|
|
suffix='.c')
|
|
b9.add_action('_altsrc.b', 'altaction')
|
|
tgt = b9(env, target=None, source='foo_altsrc.b')
|
|
assert str(tgt[0]) == 'foo.c', str(tgt[0])
|
|
|
|
def test_src_suffix_expansion(self):
|
|
"""Test handling source suffixes when an expansion is involved"""
|
|
env = Environment(OBJSUFFIX = '.obj')
|
|
|
|
b1 = SCons.Builder.Builder(action = '',
|
|
src_suffix='.c',
|
|
suffix='.obj')
|
|
b2 = SCons.Builder.Builder(action = '',
|
|
src_builder=b1,
|
|
src_suffix='.obj',
|
|
suffix='.exe')
|
|
tgt = b2(env, target=None, source=['foo$OBJSUFFIX'])
|
|
s = list(map(str, tgt[0].sources))
|
|
assert s == ['foo.obj'], s
|
|
|
|
def test_suffix(self):
|
|
"""Test Builder creation with a specified target suffix
|
|
|
|
Make sure that the '.' separator is appended to the
|
|
beginning if it isn't already present.
|
|
"""
|
|
env = Environment()
|
|
builder = SCons.Builder.Builder(suffix = '.o')
|
|
assert builder.get_suffix(env) == '.o', builder.get_suffix(env)
|
|
builder = SCons.Builder.Builder(suffix = 'o', action='')
|
|
assert builder.get_suffix(env) == '.o', builder.get_suffix(env)
|
|
tgt = builder(env, target = 'tgt3', source = 'src3')[0]
|
|
assert tgt.get_internal_path() == 'tgt3.o', \
|
|
"Target has unexpected name: %s" % tgt.get_internal_path()
|
|
tgt = builder(env, target = 'tgt4a tgt4b', source = 'src4')[0]
|
|
assert tgt.get_internal_path() == 'tgt4a tgt4b.o', \
|
|
"Target has unexpected name: %s" % tgt.get_internal_path()
|
|
tgt = builder(env, target = None, source = 'src5')[0]
|
|
assert tgt.get_internal_path() == 'src5.o', \
|
|
"Target has unexpected name: %s" % tgt.get_internal_path()
|
|
|
|
def gen_suffix(env, sources):
|
|
return "gen_suffix() says " + env['BAR']
|
|
my_env = Environment(BAR = 'hocus pocus')
|
|
builder = SCons.Builder.Builder(suffix = gen_suffix)
|
|
assert builder.get_suffix(my_env) == "gen_suffix() says hocus pocus", builder.get_suffix(my_env)
|
|
my_env['BAR'] = 'presto chango'
|
|
assert builder.get_suffix(my_env) == "gen_suffix() says presto chango"
|
|
|
|
def my_emit(env, sources):
|
|
return env.subst('$EMIT')
|
|
my_env = Environment(BAR = '.bar', EMIT = '.emit')
|
|
builder = SCons.Builder.Builder(suffix = {None : '.default',
|
|
'.in' : '.out',
|
|
'.x' : '.y',
|
|
'$BAR' : '.new',
|
|
'.zzz' : my_emit},
|
|
action='')
|
|
tgt = builder(my_env, target = None, source = 'f1')[0]
|
|
assert tgt.get_internal_path() == 'f1.default', tgt.get_internal_path()
|
|
tgt = builder(my_env, target = None, source = 'f2.c')[0]
|
|
assert tgt.get_internal_path() == 'f2.default', tgt.get_internal_path()
|
|
tgt = builder(my_env, target = None, source = 'f3.in')[0]
|
|
assert tgt.get_internal_path() == 'f3.out', tgt.get_internal_path()
|
|
tgt = builder(my_env, target = None, source = 'f4.x')[0]
|
|
assert tgt.get_internal_path() == 'f4.y', tgt.get_internal_path()
|
|
tgt = builder(my_env, target = None, source = 'f5.bar')[0]
|
|
assert tgt.get_internal_path() == 'f5.new', tgt.get_internal_path()
|
|
tgt = builder(my_env, target = None, source = 'f6.zzz')[0]
|
|
assert tgt.get_internal_path() == 'f6.emit', tgt.get_internal_path()
|
|
|
|
def test_single_source(self):
|
|
"""Test Builder with single_source flag set"""
|
|
def func(target, source, env):
|
|
"""create the file"""
|
|
with open(str(target[0]), "w"):
|
|
pass
|
|
if len(source) == 1 and len(target) == 1:
|
|
env['CNT'][0] = env['CNT'][0] + 1
|
|
|
|
env = Environment()
|
|
infiles = []
|
|
outfiles = []
|
|
for i in range(10):
|
|
infiles.append(test.workpath('%d.in' % i))
|
|
outfiles.append(test.workpath('%d.out' % i))
|
|
test.write(infiles[-1], "\n")
|
|
builder = SCons.Builder.Builder(action=SCons.Action.Action(func,None),
|
|
single_source = 1, suffix='.out')
|
|
env['CNT'] = [0]
|
|
tgt = builder(env, target=outfiles[0], source=infiles[0])[0]
|
|
s = str(tgt)
|
|
t = os.path.normcase(test.workpath('0.out'))
|
|
assert os.path.normcase(s) == t, s
|
|
tgt.prepare()
|
|
tgt.build()
|
|
assert env['CNT'][0] == 1, env['CNT'][0]
|
|
tgt = builder(env, outfiles[1], infiles[1])[0]
|
|
s = str(tgt)
|
|
t = os.path.normcase(test.workpath('1.out'))
|
|
assert os.path.normcase(s) == t, s
|
|
tgt.prepare()
|
|
tgt.build()
|
|
assert env['CNT'][0] == 2
|
|
tgts = builder(env, None, infiles[2:4])
|
|
s = list(map(str, tgts))
|
|
expect = [test.workpath('2.out'), test.workpath('3.out')]
|
|
expect = list(map(os.path.normcase, expect))
|
|
assert list(map(os.path.normcase, s)) == expect, s
|
|
for t in tgts: t.prepare()
|
|
tgts[0].build()
|
|
tgts[1].build()
|
|
assert env['CNT'][0] == 4
|
|
try:
|
|
tgt = builder(env, outfiles[4], infiles[4:6])
|
|
except SCons.Errors.UserError:
|
|
pass
|
|
else:
|
|
assert 0
|
|
try:
|
|
# The builder may output more than one target per input file.
|
|
tgt = builder(env, outfiles[4:6], infiles[4:6])
|
|
except SCons.Errors.UserError:
|
|
pass
|
|
else:
|
|
assert 0
|
|
|
|
|
|
def test_lists(self):
|
|
"""Testing handling lists of targets and source"""
|
|
def function2(target, source, env, tlist = [outfile, outfile2], **kw):
|
|
for t in target:
|
|
with open(str(t), 'w') as f:
|
|
f.write("function2\n")
|
|
for t in tlist:
|
|
if t not in list(map(str, target)):
|
|
with open(t, 'w') as f:
|
|
f.write("function2\n")
|
|
return 1
|
|
|
|
env = Environment()
|
|
builder = SCons.Builder.Builder(action = function2)
|
|
|
|
tgts = builder(env, source=[])
|
|
assert tgts == [], tgts
|
|
|
|
tgts = builder(env, target = [outfile, outfile2], source = infile)
|
|
for t in tgts:
|
|
t.prepare()
|
|
try:
|
|
tgts[0].build()
|
|
except SCons.Errors.BuildError:
|
|
pass
|
|
c = test.read(outfile, 'r')
|
|
assert c == "function2\n", c
|
|
c = test.read(outfile2, 'r')
|
|
assert c == "function2\n", c
|
|
|
|
sub1_out = test.workpath('sub1', 'out')
|
|
sub2_out = test.workpath('sub2', 'out')
|
|
|
|
def function3(target, source, env, tlist = [sub1_out, sub2_out]):
|
|
for t in target:
|
|
with open(str(t), 'w') as f:
|
|
f.write("function3\n")
|
|
for t in tlist:
|
|
if t not in list(map(str, target)):
|
|
with open(t, 'w') as f:
|
|
f.write("function3\n")
|
|
return 1
|
|
|
|
builder = SCons.Builder.Builder(action = function3)
|
|
tgts = builder(env, target = [sub1_out, sub2_out], source = infile)
|
|
for t in tgts:
|
|
t.prepare()
|
|
try:
|
|
tgts[0].build()
|
|
except SCons.Errors.BuildError:
|
|
pass
|
|
c = test.read(sub1_out, 'r')
|
|
assert c == "function3\n", c
|
|
c = test.read(sub2_out, 'r')
|
|
assert c == "function3\n", c
|
|
assert os.path.exists(test.workpath('sub1'))
|
|
assert os.path.exists(test.workpath('sub2'))
|
|
|
|
def test_src_builder(self):
|
|
"""Testing Builders with src_builder"""
|
|
# These used to be MultiStepBuilder objects until we
|
|
# eliminated it as a separate class
|
|
env = Environment()
|
|
builder1 = SCons.Builder.Builder(action='foo',
|
|
src_suffix='.bar',
|
|
suffix='.foo')
|
|
builder2 = SCons.Builder.Builder(action=MyAction('act'),
|
|
src_builder = builder1,
|
|
src_suffix = '.foo')
|
|
|
|
tgt = builder2(env, source=[])
|
|
assert tgt == [], tgt
|
|
|
|
sources = ['test.bar', 'test2.foo', 'test3.txt', 'test4']
|
|
tgt = builder2(env, target='baz', source=sources)[0]
|
|
s = str(tgt)
|
|
assert s == 'baz', s
|
|
s = list(map(str, tgt.sources))
|
|
assert s == ['test.foo', 'test2.foo', 'test3.txt', 'test4.foo'], s
|
|
s = list(map(str, tgt.sources[0].sources))
|
|
assert s == ['test.bar'], s
|
|
|
|
tgt = builder2(env, None, 'aaa.bar')[0]
|
|
s = str(tgt)
|
|
assert s == 'aaa', s
|
|
s = list(map(str, tgt.sources))
|
|
assert s == ['aaa.foo'], s
|
|
s = list(map(str, tgt.sources[0].sources))
|
|
assert s == ['aaa.bar'], s
|
|
|
|
builder3 = SCons.Builder.Builder(action='bld3')
|
|
assert builder3.src_builder is not builder1.src_builder
|
|
|
|
builder4 = SCons.Builder.Builder(action='bld4',
|
|
src_suffix='.i',
|
|
suffix='_wrap.c')
|
|
builder5 = SCons.Builder.Builder(action=MyAction('act'),
|
|
src_builder=builder4,
|
|
suffix='.obj',
|
|
src_suffix='.c')
|
|
builder6 = SCons.Builder.Builder(action=MyAction('act'),
|
|
src_builder=builder5,
|
|
suffix='.exe',
|
|
src_suffix='.obj')
|
|
tgt = builder6(env, 'test', 'test.i')[0]
|
|
s = str(tgt)
|
|
assert s == 'test.exe', s
|
|
s = list(map(str, tgt.sources))
|
|
assert s == ['test_wrap.obj'], s
|
|
s = list(map(str, tgt.sources[0].sources))
|
|
assert s == ['test_wrap.c'], s
|
|
s = list(map(str, tgt.sources[0].sources[0].sources))
|
|
assert s == ['test.i'], s
|
|
|
|
def test_target_scanner(self):
|
|
"""Testing ability to set target and source scanners through a builder."""
|
|
global instanced
|
|
class TestScanner:
|
|
pass
|
|
tscan = TestScanner()
|
|
sscan = TestScanner()
|
|
env = Environment()
|
|
builder = SCons.Builder.Builder(target_scanner=tscan,
|
|
source_scanner=sscan,
|
|
action='')
|
|
tgt = builder(env, target='foo2', source='bar')[0]
|
|
assert tgt.builder.target_scanner == tscan, tgt.builder.target_scanner
|
|
assert tgt.builder.source_scanner == sscan, tgt.builder.source_scanner
|
|
|
|
builder1 = SCons.Builder.Builder(action='foo',
|
|
src_suffix='.bar',
|
|
suffix='.foo')
|
|
builder2 = SCons.Builder.Builder(action='foo',
|
|
src_builder = builder1,
|
|
target_scanner = tscan,
|
|
source_scanner = tscan)
|
|
tgt = builder2(env, target='baz2', source='test.bar test2.foo test3.txt')[0]
|
|
assert tgt.builder.target_scanner == tscan, tgt.builder.target_scanner
|
|
assert tgt.builder.source_scanner == tscan, tgt.builder.source_scanner
|
|
|
|
def test_actual_scanner(self):
|
|
"""Test usage of actual Scanner objects."""
|
|
|
|
import SCons.Scanner
|
|
|
|
def func(self):
|
|
pass
|
|
|
|
scanner = SCons.Scanner.ScannerBase(func, name='fooscan')
|
|
|
|
b1 = SCons.Builder.Builder(action='bld', target_scanner=scanner)
|
|
b2 = SCons.Builder.Builder(action='bld', target_scanner=scanner)
|
|
b3 = SCons.Builder.Builder(action='bld')
|
|
|
|
assert b1 == b2
|
|
assert b1 != b3
|
|
|
|
def test_src_scanner(self):
|
|
"""Testing ability to set a source file scanner through a builder."""
|
|
class TestScanner:
|
|
def key(self, env):
|
|
return 'TestScannerkey'
|
|
def instance(self, env):
|
|
return self
|
|
def select(self, node):
|
|
return self
|
|
name = 'TestScanner'
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
scanner = TestScanner()
|
|
builder = SCons.Builder.Builder(action='action')
|
|
|
|
# With no scanner specified, source_scanner and
|
|
# backup_source_scanner are None.
|
|
bar_y = MyNode('bar.y')
|
|
env1 = Environment()
|
|
tgt = builder(env1, target='foo1.x', source='bar.y')[0]
|
|
src = tgt.sources[0]
|
|
assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner
|
|
assert tgt.builder.source_scanner is None, tgt.builder.source_scanner
|
|
assert tgt.get_source_scanner(bar_y) is None, tgt.get_source_scanner(bar_y)
|
|
assert not src.has_builder(), src.has_builder()
|
|
s = src.get_source_scanner(bar_y)
|
|
assert isinstance(s, SCons.Util.Null), repr(s)
|
|
|
|
# An Environment that has suffix-specified SCANNERS should
|
|
# provide a source scanner to the target.
|
|
class EnvTestScanner:
|
|
def key(self, env):
|
|
return '.y'
|
|
def instance(self, env):
|
|
return self
|
|
name = 'EnvTestScanner'
|
|
def __str__(self):
|
|
return self.name
|
|
def select(self, node):
|
|
return self
|
|
def path(self, env, dir=None):
|
|
return ()
|
|
def __call__(self, node, env, path):
|
|
return []
|
|
env3 = Environment(SCANNERS = [EnvTestScanner()])
|
|
env3.scanner = EnvTestScanner() # test env's version of SCANNERS
|
|
tgt = builder(env3, target='foo2.x', source='bar.y')[0]
|
|
src = tgt.sources[0]
|
|
assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner
|
|
assert not tgt.builder.source_scanner, tgt.builder.source_scanner
|
|
assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y)
|
|
assert str(tgt.get_source_scanner(bar_y)) == 'EnvTestScanner', tgt.get_source_scanner(bar_y)
|
|
assert not src.has_builder(), src.has_builder()
|
|
s = src.get_source_scanner(bar_y)
|
|
assert isinstance(s, SCons.Util.Null), repr(s)
|
|
|
|
# Can't simply specify the scanner as a builder argument; it's
|
|
# global to all invocations of this builder.
|
|
tgt = builder(env3, target='foo3.x', source='bar.y', source_scanner = scanner)[0]
|
|
src = tgt.sources[0]
|
|
assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner
|
|
assert not tgt.builder.source_scanner, tgt.builder.source_scanner
|
|
assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y)
|
|
assert str(tgt.get_source_scanner(bar_y)) == 'EnvTestScanner', tgt.get_source_scanner(bar_y)
|
|
assert not src.has_builder(), src.has_builder()
|
|
s = src.get_source_scanner(bar_y)
|
|
assert isinstance(s, SCons.Util.Null), s
|
|
|
|
# Now use a builder that actually has scanners and ensure that
|
|
# the target is set accordingly (using the specified scanner
|
|
# instead of the Environment's scanner)
|
|
builder = SCons.Builder.Builder(action='action',
|
|
source_scanner=scanner,
|
|
target_scanner=scanner)
|
|
tgt = builder(env3, target='foo4.x', source='bar.y')[0]
|
|
src = tgt.sources[0]
|
|
assert tgt.builder.target_scanner == scanner, tgt.builder.target_scanner
|
|
assert tgt.builder.source_scanner, tgt.builder.source_scanner
|
|
assert tgt.builder.source_scanner == scanner, tgt.builder.source_scanner
|
|
assert str(tgt.builder.source_scanner) == 'TestScanner', str(tgt.builder.source_scanner)
|
|
assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y)
|
|
assert tgt.get_source_scanner(bar_y) == scanner, tgt.get_source_scanner(bar_y)
|
|
assert str(tgt.get_source_scanner(bar_y)) == 'TestScanner', tgt.get_source_scanner(bar_y)
|
|
assert not src.has_builder(), src.has_builder()
|
|
s = src.get_source_scanner(bar_y)
|
|
assert isinstance(s, SCons.Util.Null), s
|
|
|
|
|
|
|
|
def test_Builder_API(self):
|
|
"""Test Builder interface.
|
|
|
|
Some of this is tested elsewhere in this file, but this is a
|
|
quick collection of common operations on builders with various
|
|
forms of component specifications."""
|
|
|
|
builder = SCons.Builder.Builder()
|
|
env = Environment(BUILDERS={'Bld':builder})
|
|
|
|
r = builder.get_name(env)
|
|
assert r == 'Bld', r
|
|
r = builder.get_prefix(env)
|
|
assert r == '', r
|
|
r = builder.get_suffix(env)
|
|
assert r == '', r
|
|
r = builder.get_src_suffix(env)
|
|
assert r == '', r
|
|
r = builder.src_suffixes(env)
|
|
assert r == [], r
|
|
|
|
# src_suffix can be a single string or a list of strings
|
|
# src_suffixes() caches its return value, so we use a new
|
|
# Builder each time we do any of these tests
|
|
|
|
bld = SCons.Builder.Builder()
|
|
env = Environment(BUILDERS={'Bld':bld})
|
|
|
|
bld.set_src_suffix('.foo')
|
|
r = bld.get_src_suffix(env)
|
|
assert r == '.foo', r
|
|
r = bld.src_suffixes(env)
|
|
assert r == ['.foo'], r
|
|
|
|
bld = SCons.Builder.Builder()
|
|
env = Environment(BUILDERS={'Bld':bld})
|
|
|
|
bld.set_src_suffix(['.foo', '.bar'])
|
|
r = bld.get_src_suffix(env)
|
|
assert r == '.foo', r
|
|
r = bld.src_suffixes(env)
|
|
assert r == ['.foo', '.bar'], r
|
|
|
|
bld = SCons.Builder.Builder()
|
|
env = Environment(BUILDERS={'Bld':bld})
|
|
|
|
bld.set_src_suffix(['.bar', '.foo'])
|
|
r = bld.get_src_suffix(env)
|
|
assert r == '.bar', r
|
|
r = sorted(bld.src_suffixes(env))
|
|
assert r == ['.bar', '.foo'], r
|
|
|
|
# adjust_suffix normalizes the suffix, adding a `.' if needed
|
|
|
|
r = builder.adjust_suffix('.foo')
|
|
assert r == '.foo', r
|
|
r = builder.adjust_suffix('_foo')
|
|
assert r == '_foo', r
|
|
r = builder.adjust_suffix('$foo')
|
|
assert r == '$foo', r
|
|
r = builder.adjust_suffix('foo')
|
|
assert r == '.foo', r
|
|
r = builder.adjust_suffix('f._$oo')
|
|
assert r == '.f._$oo', r
|
|
|
|
# prefix and suffix can be one of:
|
|
# 1. a string (adjusted and env variables substituted),
|
|
# 2. a function (passed (env,sources), returns suffix string)
|
|
# 3. a dict of src_suffix:suffix settings, key==None is
|
|
# default suffix (special case of #2, so adjust_suffix
|
|
# not applied)
|
|
|
|
builder = SCons.Builder.Builder(prefix='lib', suffix='foo')
|
|
|
|
env = Environment(BUILDERS={'Bld':builder})
|
|
r = builder.get_name(env)
|
|
assert r == 'Bld', r
|
|
r = builder.get_prefix(env)
|
|
assert r == 'lib', r
|
|
r = builder.get_suffix(env)
|
|
assert r == '.foo', r
|
|
|
|
mkpref = lambda env,sources: 'Lib'
|
|
mksuff = lambda env,sources: '.Foo'
|
|
builder = SCons.Builder.Builder(prefix=mkpref, suffix=mksuff)
|
|
|
|
env = Environment(BUILDERS={'Bld':builder})
|
|
r = builder.get_name(env)
|
|
assert r == 'Bld', r
|
|
r = builder.get_prefix(env)
|
|
assert r == 'Lib', r
|
|
r = builder.get_suffix(env)
|
|
assert r == '.Foo', r
|
|
|
|
builder = SCons.Builder.Builder(prefix='$PREF', suffix='$SUFF')
|
|
|
|
env = Environment(BUILDERS={'Bld':builder},PREF="LIB",SUFF=".FOO")
|
|
r = builder.get_name(env)
|
|
assert r == 'Bld', r
|
|
r = builder.get_prefix(env)
|
|
assert r == 'LIB', r
|
|
r = builder.get_suffix(env)
|
|
assert r == '.FOO', r
|
|
|
|
builder = SCons.Builder.Builder(prefix={None:'A_',
|
|
'.C':'E_'},
|
|
suffix={None:'.B',
|
|
'.C':'.D'})
|
|
|
|
env = Environment(BUILDERS={'Bld':builder})
|
|
r = builder.get_name(env)
|
|
assert r == 'Bld', r
|
|
r = builder.get_prefix(env)
|
|
assert r == 'A_', r
|
|
r = builder.get_suffix(env)
|
|
assert r == '.B', r
|
|
r = builder.get_prefix(env, [MyNode('X.C')])
|
|
assert r == 'E_', r
|
|
r = builder.get_suffix(env, [MyNode('X.C')])
|
|
assert r == '.D', r
|
|
|
|
builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={})
|
|
env = Environment(BUILDERS={'Bld':builder})
|
|
|
|
r = builder.get_name(env)
|
|
assert r == 'Bld', r
|
|
r = builder.get_prefix(env)
|
|
assert r == 'A_', r
|
|
r = builder.get_suffix(env)
|
|
assert r is None, r
|
|
r = builder.get_src_suffix(env)
|
|
assert r == '', r
|
|
r = builder.src_suffixes(env)
|
|
assert r == [], r
|
|
|
|
# Builder actions can be a string, a list, or a dictionary
|
|
# whose keys are the source suffix. The add_action()
|
|
# specifies a new source suffix/action binding.
|
|
|
|
builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={})
|
|
env = Environment(BUILDERS={'Bld':builder})
|
|
builder.add_action('.src_sfx1', 'FOO')
|
|
|
|
r = builder.get_name(env)
|
|
assert r == 'Bld', r
|
|
r = builder.get_prefix(env)
|
|
assert r == 'A_', r
|
|
r = builder.get_suffix(env)
|
|
assert r is None, r
|
|
r = builder.get_suffix(env, [MyNode('X.src_sfx1')])
|
|
assert r is None, r
|
|
r = builder.get_src_suffix(env)
|
|
assert r == '.src_sfx1', r
|
|
r = builder.src_suffixes(env)
|
|
assert r == ['.src_sfx1'], r
|
|
|
|
builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={})
|
|
env = Environment(BUILDERS={'Bld':builder})
|
|
builder.add_action('.src_sfx1', 'FOO')
|
|
builder.add_action('.src_sfx2', 'BAR')
|
|
|
|
r = builder.get_name(env)
|
|
assert r == 'Bld', r
|
|
r = builder.get_prefix(env)
|
|
assert r == 'A_', r
|
|
r = builder.get_suffix(env)
|
|
assert r is None, r
|
|
r = builder.get_src_suffix(env)
|
|
assert r == '.src_sfx1', r
|
|
r = sorted(builder.src_suffixes(env))
|
|
assert r == ['.src_sfx1', '.src_sfx2'], r
|
|
|
|
|
|
def test_Builder_Args(self):
|
|
"""Testing passing extra args to a builder."""
|
|
def buildFunc(target, source, env, s=self):
|
|
s.foo=env['foo']
|
|
s.bar=env['bar']
|
|
assert env['CC'] == 'mycc'
|
|
|
|
env=Environment(CC='cc')
|
|
|
|
builder = SCons.Builder.Builder(action=buildFunc)
|
|
tgt = builder(env, target='foo', source='bar', foo=1, bar=2, CC='mycc')[0]
|
|
tgt.build()
|
|
assert self.foo == 1, self.foo
|
|
assert self.bar == 2, self.bar
|
|
|
|
def test_emitter(self):
|
|
"""Test emitter functions."""
|
|
def emit(target, source, env):
|
|
foo = env.get('foo', 0)
|
|
bar = env.get('bar', 0)
|
|
for t in target:
|
|
assert isinstance(t, MyNode)
|
|
assert t.has_builder()
|
|
for s in source:
|
|
assert isinstance(s, MyNode)
|
|
if foo:
|
|
target.append("bar%d"%foo)
|
|
if bar:
|
|
source.append("baz")
|
|
return ( target, source )
|
|
|
|
env = Environment()
|
|
builder = SCons.Builder.Builder(action='foo',
|
|
emitter=emit,
|
|
target_factory=MyNode,
|
|
source_factory=MyNode)
|
|
tgt = builder(env, target='foo2', source='bar')[0]
|
|
assert str(tgt) == 'foo2', str(tgt)
|
|
assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
|
|
|
|
tgt = builder(env, target='foo3', source='bar', foo=1)
|
|
assert len(tgt) == 2, len(tgt)
|
|
assert 'foo3' in list(map(str, tgt)), list(map(str, tgt))
|
|
assert 'bar1' in list(map(str, tgt)), list(map(str, tgt))
|
|
|
|
tgt = builder(env, target='foo4', source='bar', bar=1)[0]
|
|
assert str(tgt) == 'foo4', str(tgt)
|
|
assert len(tgt.sources) == 2, len(tgt.sources)
|
|
assert 'baz' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
|
|
assert 'bar' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
|
|
|
|
env2=Environment(FOO=emit)
|
|
builder2=SCons.Builder.Builder(action='foo',
|
|
emitter="$FOO",
|
|
target_factory=MyNode,
|
|
source_factory=MyNode)
|
|
|
|
builder2a=SCons.Builder.Builder(action='foo',
|
|
emitter="$FOO",
|
|
target_factory=MyNode,
|
|
source_factory=MyNode)
|
|
|
|
assert builder2 == builder2a, repr(builder2.__dict__) + "\n" + repr(builder2a.__dict__)
|
|
|
|
tgt = builder2(env2, target='foo5', source='bar')[0]
|
|
assert str(tgt) == 'foo5', str(tgt)
|
|
assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
|
|
|
|
tgt = builder2(env2, target='foo6', source='bar', foo=2)
|
|
assert len(tgt) == 2, len(tgt)
|
|
assert 'foo6' in list(map(str, tgt)), list(map(str, tgt))
|
|
assert 'bar2' in list(map(str, tgt)), list(map(str, tgt))
|
|
|
|
tgt = builder2(env2, target='foo7', source='bar', bar=1)[0]
|
|
assert str(tgt) == 'foo7', str(tgt)
|
|
assert len(tgt.sources) == 2, len(tgt.sources)
|
|
assert 'baz' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
|
|
assert 'bar' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
|
|
|
|
def test_emitter_preserve_builder(self):
|
|
"""Test an emitter not overwriting a newly-set builder"""
|
|
env = Environment()
|
|
|
|
new_builder = SCons.Builder.Builder(action='new')
|
|
node = MyNode('foo8')
|
|
new_node = MyNode('foo8.new')
|
|
|
|
def emit(target, source, env, nb=new_builder, nn=new_node):
|
|
for t in target:
|
|
t.builder = nb
|
|
return [nn], source
|
|
|
|
builder=SCons.Builder.Builder(action='foo',
|
|
emitter=emit,
|
|
target_factory=MyNode,
|
|
source_factory=MyNode)
|
|
tgt = builder(env, target=node, source='bar')[0]
|
|
assert tgt is new_node, tgt
|
|
assert tgt.builder is builder, tgt.builder
|
|
assert node.builder is new_builder, node.builder
|
|
|
|
def test_emitter_suffix_map(self):
|
|
"""Test mapping file suffixes to emitter functions"""
|
|
env = Environment()
|
|
|
|
def emit4a(target, source, env):
|
|
source = list(map(str, source))
|
|
target = ['emit4a-' + x[:-3] for x in source]
|
|
return (target, source)
|
|
def emit4b(target, source, env):
|
|
source = list(map(str, source))
|
|
target = ['emit4b-' + x[:-3] for x in source]
|
|
return (target, source)
|
|
|
|
builder = SCons.Builder.Builder(action='foo',
|
|
emitter={'.4a':emit4a,
|
|
'.4b':emit4b},
|
|
target_factory=MyNode,
|
|
source_factory=MyNode)
|
|
tgt = builder(env, None, source='aaa.4a')[0]
|
|
assert str(tgt) == 'emit4a-aaa', str(tgt)
|
|
tgt = builder(env, None, source='bbb.4b')[0]
|
|
assert str(tgt) == 'emit4b-bbb', str(tgt)
|
|
tgt = builder(env, None, source='ccc.4c')[0]
|
|
assert str(tgt) == 'ccc', str(tgt)
|
|
|
|
def emit4c(target, source, env):
|
|
source = list(map(str, source))
|
|
target = ['emit4c-' + x[:-3] for x in source]
|
|
return (target, source)
|
|
|
|
builder.add_emitter('.4c', emit4c)
|
|
tgt = builder(env, None, source='ccc.4c')[0]
|
|
assert str(tgt) == 'emit4c-ccc', str(tgt)
|
|
|
|
def test_emitter_function_list(self):
|
|
"""Test lists of emitter functions"""
|
|
env = Environment()
|
|
|
|
def emit1a(target, source, env):
|
|
source = list(map(str, source))
|
|
target = target + ['emit1a-' + x[:-2] for x in source]
|
|
return (target, source)
|
|
def emit1b(target, source, env):
|
|
source = list(map(str, source))
|
|
target = target + ['emit1b-' + x[:-2] for x in source]
|
|
return (target, source)
|
|
builder1 = SCons.Builder.Builder(action='foo',
|
|
emitter=[emit1a, emit1b],
|
|
node_factory=MyNode)
|
|
|
|
tgts = builder1(env, target='target-1', source='aaa.1')
|
|
tgts = list(map(str, tgts))
|
|
assert tgts == ['target-1', 'emit1a-aaa', 'emit1b-aaa'], tgts
|
|
|
|
# Test a list of emitter functions through the environment.
|
|
def emit2a(target, source, env):
|
|
source = list(map(str, source))
|
|
target = target + ['emit2a-' + x[:-2] for x in source]
|
|
return (target, source)
|
|
def emit2b(target, source, env):
|
|
source = list(map(str, source))
|
|
target = target + ['emit2b-' + x[:-2] for x in source]
|
|
return (target, source)
|
|
builder2 = SCons.Builder.Builder(action='foo',
|
|
emitter='$EMITTERLIST',
|
|
node_factory=MyNode)
|
|
|
|
env = Environment(EMITTERLIST = [emit2a, emit2b])
|
|
|
|
tgts = builder2(env, target='target-2', source='aaa.2')
|
|
tgts = list(map(str, tgts))
|
|
assert tgts == ['target-2', 'emit2a-aaa', 'emit2b-aaa'], tgts
|
|
|
|
def test_emitter_TARGET_SOURCE(self):
|
|
"""Test use of $TARGET and $SOURCE in emitter results"""
|
|
|
|
env = SCons.Environment.Environment()
|
|
|
|
def emit(target, source, env):
|
|
return (target + ['${SOURCE}.s1', '${TARGET}.t1'],
|
|
source + ['${TARGET}.t2', '${SOURCE}.s2'])
|
|
|
|
builder = SCons.Builder.Builder(action='foo',
|
|
emitter = emit,
|
|
node_factory = MyNode)
|
|
|
|
targets = builder(env, target = 'TTT', source ='SSS')
|
|
sources = targets[0].sources
|
|
targets = list(map(str, targets))
|
|
sources = list(map(str, sources))
|
|
assert targets == ['TTT', 'SSS.s1', 'TTT.t1'], targets
|
|
assert sources == ['SSS', 'TTT.t2', 'SSS.s2'], targets
|
|
|
|
def test_no_target(self):
|
|
"""Test deducing the target from the source."""
|
|
|
|
env = Environment()
|
|
b = SCons.Builder.Builder(action='foo', suffix='.o')
|
|
|
|
tgt = b(env, None, 'aaa')[0]
|
|
assert str(tgt) == 'aaa.o', str(tgt)
|
|
assert len(tgt.sources) == 1, list(map(str, tgt.sources))
|
|
assert str(tgt.sources[0]) == 'aaa', list(map(str, tgt.sources))
|
|
|
|
tgt = b(env, None, 'bbb.c')[0]
|
|
assert str(tgt) == 'bbb.o', str(tgt)
|
|
assert len(tgt.sources) == 1, list(map(str, tgt.sources))
|
|
assert str(tgt.sources[0]) == 'bbb.c', list(map(str, tgt.sources))
|
|
|
|
tgt = b(env, None, 'ccc.x.c')[0]
|
|
assert str(tgt) == 'ccc.x.o', str(tgt)
|
|
assert len(tgt.sources) == 1, list(map(str, tgt.sources))
|
|
assert str(tgt.sources[0]) == 'ccc.x.c', list(map(str, tgt.sources))
|
|
|
|
tgt = b(env, None, ['d0.c', 'd1.c'])[0]
|
|
assert str(tgt) == 'd0.o', str(tgt)
|
|
assert len(tgt.sources) == 2, list(map(str, tgt.sources))
|
|
assert str(tgt.sources[0]) == 'd0.c', list(map(str, tgt.sources))
|
|
assert str(tgt.sources[1]) == 'd1.c', list(map(str, tgt.sources))
|
|
|
|
tgt = b(env, target = None, source='eee')[0]
|
|
assert str(tgt) == 'eee.o', str(tgt)
|
|
assert len(tgt.sources) == 1, list(map(str, tgt.sources))
|
|
assert str(tgt.sources[0]) == 'eee', list(map(str, tgt.sources))
|
|
|
|
tgt = b(env, target = None, source='fff.c')[0]
|
|
assert str(tgt) == 'fff.o', str(tgt)
|
|
assert len(tgt.sources) == 1, list(map(str, tgt.sources))
|
|
assert str(tgt.sources[0]) == 'fff.c', list(map(str, tgt.sources))
|
|
|
|
tgt = b(env, target = None, source='ggg.x.c')[0]
|
|
assert str(tgt) == 'ggg.x.o', str(tgt)
|
|
assert len(tgt.sources) == 1, list(map(str, tgt.sources))
|
|
assert str(tgt.sources[0]) == 'ggg.x.c', list(map(str, tgt.sources))
|
|
|
|
tgt = b(env, target = None, source=['h0.c', 'h1.c'])[0]
|
|
assert str(tgt) == 'h0.o', str(tgt)
|
|
assert len(tgt.sources) == 2, list(map(str, tgt.sources))
|
|
assert str(tgt.sources[0]) == 'h0.c', list(map(str, tgt.sources))
|
|
assert str(tgt.sources[1]) == 'h1.c', list(map(str, tgt.sources))
|
|
|
|
w = b(env, target='i0.w', source=['i0.x'])[0]
|
|
y = b(env, target='i1.y', source=['i1.z'])[0]
|
|
tgt = b(env, None, source=[w, y])[0]
|
|
assert str(tgt) == 'i0.o', str(tgt)
|
|
assert len(tgt.sources) == 2, list(map(str, tgt.sources))
|
|
assert str(tgt.sources[0]) == 'i0.w', list(map(str, tgt.sources))
|
|
assert str(tgt.sources[1]) == 'i1.y', list(map(str, tgt.sources))
|
|
|
|
def test_get_name(self):
|
|
"""Test getting name of builder.
|
|
|
|
Each type of builder should return its environment-specific
|
|
name when queried appropriately. """
|
|
|
|
b1 = SCons.Builder.Builder(action='foo', suffix='.o')
|
|
b2 = SCons.Builder.Builder(action='foo', suffix='.c')
|
|
b3 = SCons.Builder.Builder(action='bar', src_suffix = '.foo',
|
|
src_builder = b1)
|
|
b4 = SCons.Builder.Builder(action={})
|
|
b5 = SCons.Builder.Builder(action='foo', name='builder5')
|
|
b6 = SCons.Builder.Builder(action='foo')
|
|
assert isinstance(b4, SCons.Builder.CompositeBuilder)
|
|
assert isinstance(b4.action, SCons.Action.CommandGeneratorAction)
|
|
|
|
env = Environment(BUILDERS={'bldr1': b1,
|
|
'bldr2': b2,
|
|
'bldr3': b3,
|
|
'bldr4': b4})
|
|
env2 = Environment(BUILDERS={'B1': b1,
|
|
'B2': b2,
|
|
'B3': b3,
|
|
'B4': b4})
|
|
# With no name, get_name will return the class. Allow
|
|
# for caching...
|
|
b6_names = [
|
|
'SCons.Builder.BuilderBase',
|
|
"<class 'SCons.Builder.BuilderBase'>",
|
|
'SCons.Memoize.BuilderBase',
|
|
"<class 'SCons.Memoize.BuilderBase'>",
|
|
]
|
|
|
|
assert b1.get_name(env) == 'bldr1', b1.get_name(env)
|
|
assert b2.get_name(env) == 'bldr2', b2.get_name(env)
|
|
assert b3.get_name(env) == 'bldr3', b3.get_name(env)
|
|
assert b4.get_name(env) == 'bldr4', b4.get_name(env)
|
|
assert b5.get_name(env) == 'builder5', b5.get_name(env)
|
|
assert b6.get_name(env) in b6_names, b6.get_name(env)
|
|
|
|
assert b1.get_name(env2) == 'B1', b1.get_name(env2)
|
|
assert b2.get_name(env2) == 'B2', b2.get_name(env2)
|
|
assert b3.get_name(env2) == 'B3', b3.get_name(env2)
|
|
assert b4.get_name(env2) == 'B4', b4.get_name(env2)
|
|
assert b5.get_name(env2) == 'builder5', b5.get_name(env2)
|
|
assert b6.get_name(env2) in b6_names, b6.get_name(env2)
|
|
|
|
assert b5.get_name(None) == 'builder5', b5.get_name(None)
|
|
assert b6.get_name(None) in b6_names, b6.get_name(None)
|
|
|
|
# This test worked before adding batch builders, but we must now
|
|
# be able to disambiguate a CompositeAction into a more specific
|
|
# action based on file suffix at call time. Leave this commented
|
|
# out (for now) in case this reflects a real-world use case that
|
|
# we must accomodate and we want to resurrect this test.
|
|
#tgt = b4(env, target = 'moo', source='cow')
|
|
#assert tgt[0].builder.get_name(env) == 'bldr4'
|
|
|
|
class CompositeBuilderTestCase(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
def func_action(target, source, env):
|
|
return 0
|
|
|
|
builder = SCons.Builder.Builder(action={ '.foo' : func_action,
|
|
'.bar' : func_action})
|
|
|
|
self.func_action = func_action
|
|
self.builder = builder
|
|
|
|
def test___init__(self):
|
|
"""Test CompositeBuilder creation"""
|
|
env = Environment()
|
|
builder = SCons.Builder.Builder(action={})
|
|
|
|
tgt = builder(env, source=[])
|
|
assert tgt == [], tgt
|
|
|
|
assert isinstance(builder, SCons.Builder.CompositeBuilder)
|
|
assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
|
|
|
|
def test_target_action(self):
|
|
"""Test CompositeBuilder setting of target builder actions"""
|
|
env = Environment()
|
|
builder = self.builder
|
|
|
|
tgt = builder(env, target='test1', source='test1.foo')[0]
|
|
assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
|
|
assert tgt.builder.action is builder.action
|
|
|
|
tgt = builder(env, target='test2', source='test1.bar')[0]
|
|
assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
|
|
assert tgt.builder.action is builder.action
|
|
|
|
def test_multiple_suffix_error(self):
|
|
"""Test the CompositeBuilder multiple-source-suffix error"""
|
|
env = Environment()
|
|
builder = self.builder
|
|
|
|
flag = 0
|
|
try:
|
|
builder(env, target='test3', source=['test2.bar', 'test1.foo'])[0]
|
|
except SCons.Errors.UserError as e:
|
|
flag = 1
|
|
err = e
|
|
assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
|
|
expect = "While building `['test3']' from `test1.foo': Cannot build multiple sources with different extensions: .bar, .foo"
|
|
assert str(err) == expect, err
|
|
|
|
def test_source_ext_match(self):
|
|
"""Test the CompositeBuilder source_ext_match argument"""
|
|
env = Environment()
|
|
func_action = self.func_action
|
|
builder = SCons.Builder.Builder(action={ '.foo' : func_action,
|
|
'.bar' : func_action},
|
|
source_ext_match = None)
|
|
|
|
tgt = builder(env, target='test3', source=['test2.bar', 'test1.foo'])[0]
|
|
tgt.build()
|
|
|
|
def test_suffix_variable(self):
|
|
"""Test CompositeBuilder defining action suffixes through a variable"""
|
|
env = Environment(BAR_SUFFIX = '.BAR2', FOO_SUFFIX = '.FOO2')
|
|
func_action = self.func_action
|
|
builder = SCons.Builder.Builder(action={ '.foo' : func_action,
|
|
'.bar' : func_action,
|
|
'$BAR_SUFFIX' : func_action,
|
|
'$FOO_SUFFIX' : func_action })
|
|
|
|
tgt = builder(env, target='test4', source=['test4.BAR2'])[0]
|
|
assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
|
|
try:
|
|
tgt.build()
|
|
flag = 1
|
|
except SCons.Errors.UserError as e:
|
|
print(e)
|
|
flag = 0
|
|
assert flag, "It should be possible to define actions in composite builders using variables."
|
|
env['FOO_SUFFIX'] = '.BAR2'
|
|
builder.add_action('$NEW_SUFFIX', func_action)
|
|
flag = 0
|
|
try:
|
|
builder(env, target='test5', source=['test5.BAR2'])[0]
|
|
except SCons.Errors.UserError:
|
|
flag = 1
|
|
assert flag, "UserError should be thrown when we call a builder with ambigous suffixes."
|
|
|
|
def test_src_builder(self):
|
|
"""Test CompositeBuilder's use of a src_builder"""
|
|
env = Environment()
|
|
|
|
foo_bld = SCons.Builder.Builder(action = 'a-foo',
|
|
src_suffix = '.ina',
|
|
suffix = '.foo')
|
|
assert isinstance(foo_bld, SCons.Builder.BuilderBase)
|
|
builder = SCons.Builder.Builder(action = { '.foo' : 'foo',
|
|
'.bar' : 'bar' },
|
|
src_builder = foo_bld)
|
|
assert isinstance(builder, SCons.Builder.CompositeBuilder)
|
|
assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
|
|
|
|
tgt = builder(env, target='t1', source='t1a.ina t1b.ina')[0]
|
|
assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
|
|
|
|
tgt = builder(env, target='t2', source='t2a.foo t2b.ina')[0]
|
|
assert isinstance(tgt.builder, SCons.Builder.BuilderBase), tgt.builder.__dict__
|
|
|
|
bar_bld = SCons.Builder.Builder(action = 'a-bar',
|
|
src_suffix = '.inb',
|
|
suffix = '.bar')
|
|
assert isinstance(bar_bld, SCons.Builder.BuilderBase)
|
|
builder = SCons.Builder.Builder(action = { '.foo' : 'foo'},
|
|
src_builder = [foo_bld, bar_bld])
|
|
assert isinstance(builder, SCons.Builder.CompositeBuilder)
|
|
assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
|
|
|
|
builder.add_action('.bar', 'bar')
|
|
|
|
tgt = builder(env, target='t3-foo', source='t3a.foo t3b.ina')[0]
|
|
assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
|
|
|
|
tgt = builder(env, target='t3-bar', source='t3a.bar t3b.inb')[0]
|
|
assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
|
|
|
|
flag = 0
|
|
try:
|
|
builder(env, target='t5', source=['test5a.foo', 'test5b.inb'])[0]
|
|
except SCons.Errors.UserError as e:
|
|
flag = 1
|
|
err = e
|
|
assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
|
|
expect = "While building `['t5']' from `test5b.bar': Cannot build multiple sources with different extensions: .foo, .bar"
|
|
assert str(err) == expect, err
|
|
|
|
flag = 0
|
|
try:
|
|
builder(env, target='t6', source=['test6a.bar', 'test6b.ina'])[0]
|
|
except SCons.Errors.UserError as e:
|
|
flag = 1
|
|
err = e
|
|
assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
|
|
expect = "While building `['t6']' from `test6b.foo': Cannot build multiple sources with different extensions: .bar, .foo"
|
|
assert str(err) == expect, err
|
|
|
|
flag = 0
|
|
try:
|
|
builder(env, target='t4', source=['test4a.ina', 'test4b.inb'])[0]
|
|
except SCons.Errors.UserError as e:
|
|
flag = 1
|
|
err = e
|
|
assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
|
|
expect = "While building `['t4']' from `test4b.bar': Cannot build multiple sources with different extensions: .foo, .bar"
|
|
assert str(err) == expect, err
|
|
|
|
flag = 0
|
|
try:
|
|
builder(env, target='t7', source=[env.fs.File('test7')])[0]
|
|
except SCons.Errors.UserError as e:
|
|
flag = 1
|
|
err = e
|
|
assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
|
|
expect = "While building `['t7']': Cannot deduce file extension from source files: ['test7']"
|
|
assert str(err) == expect, err
|
|
|
|
flag = 0
|
|
try:
|
|
builder(env, target='t8', source=['test8.unknown'])[0]
|
|
except SCons.Errors.UserError as e:
|
|
flag = 1
|
|
err = e
|
|
assert flag, "UserError should be thrown when we call a builder target with an unknown suffix."
|
|
expect = "While building `['t8']' from `['test8.unknown']': Don't know how to build from a source file with suffix `.unknown'. Expected a suffix in this list: ['.foo', '.bar']."
|
|
assert str(err) == expect, err
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|
|
|
|
# Local Variables:
|
|
# tab-width:4
|
|
# indent-tabs-mode:nil
|
|
# End:
|
|
# vim: set expandtab tabstop=4 shiftwidth=4:
|