Search code examples
buildscons

How do I build from different locations in my tree with multiple SConstruct files that use Repositories?


I've got the following directory structure:

root/
  SConstruct
  item0/
    SConstruct
    unit0/
      unit0.h
      private/
        unit0.c
      test/
        test_unit0.c
        SConstruct
        SConscript
    unit1/
      (repeat unit0 structure)
  item1/
    (repeat item0 structure)

I want to be able to run scons from root to build all things below it, and from root/item0 to build all things below it, etc.

The top-level SConstruct looks like:

SConscript('item0/SConstruct', duplicate=0)
SConscript('item1/SConstruct', duplicate=0)

The item-level SConstruct:

SConscript('unit0/SConstruct', duplicate=0)
SConscript('unit1/SConstruct', duplicate=0)

The unit-level SConstruct:

import os

proj_root = os.path.abspath('../../../../..')
proj_shared_path = os.path.join(proj_root,
    os.path.normpath('trunk/sys/shared'))

env = Environment()
SConscript(
    'SConscript',
    variant_dir='output',
    exports=['env', 'proj_shared_path'],
    duplicate=0)

The unit-level SConscript (important stuff only):

Import('*')
# Setup Repositories to access out-of-current-directory-tree files.
lib1_path = os.path.join(proj_shared_path, 'lib1')
env.Repository('#/../private', lib1_path)

# Set general compilation and link flags.
env.Append(
    CFLAGS = '-Wall -Wextra -std=c99 -pedantic',
    CPPPATH = ['#/../..', proj_shared_path])

# These are the files we don't need to run gcovr on.
non_cov_srcs = [
    'testit.c',
    'lib1.c'
    ]

# Create a 'coverage' environment to build files that we will run gcovr on.
cov = env.Clone()
cov.Append(CFLAGS = '--coverage -O0')
cov_srcs = cov.Object('unit0.c')
SideEffect('unit0.gcno', cov_srcs)

# Our unit test program combines all the sources we've listed so far.
test_exe = env.Program(
    'test_unit0',
    [cov_srcs, non_cov_srcs],
    LIBS='gcov', LINKFLAGS='--coverage')

Now, this all works fine when I run scons from root/item0/unit0/test. However, when I run from root or root/item0, I get various types of errors, like below (run from root/item0):

scons: done reading SConscript files.
scons: Building targets ...
scons: *** [unit0\test\output\unit0.obj] Source 'unit0\test\unit0.c' not found, needed by target 'unit0\test\output\unit0.obj'.
scons: building terminated because of errors.

Clearly unit0\test\unit0.c won't be found because it doesn't exist, but I'm not sure why scons thinks it has to be there since the Repository is defined as '#/../private' and there is no private shown in the error message.

edit I've figured out that it's probably looking for unit0\test\unit0.c because unit0\test is the "root" directory for SConscript. I'm also guessing that the #/../private Repository location is only correct when scons is run from unit0\test. But, when I try to specify the Repository in any other way (including an absolute path built with os.path.abspath (and verified to be correct)), scons still doesn't seem to want to look there for unit0.c. Why is that?


Solution

  • With further help from @bdbaddog in the #scons IRC channel, I was able to determine the following:

    1. What I was actually trying to achieve was ensuring the build artifacts for each source ended up in my variant_dir
    2. With SConstruct and SConscript in the same directory, Repository ended up achieving this when building from the unit0/test directory. But, I didn't know why.

    To properly achieve (1), Repository is not the right tool. Instead, SConscript should look like this:

    Import('*')
    
    # Set general compilation and link flags.
    env.Append(
        CFLAGS = '-Wall -Wextra -std=c99 -pedantic',
        CPPPATH = ['#/item0', '#/lib1'])
    
    # These are the files we don't need to run gcovr on.
    non_cov_objs = [
        Object(target='testit', source='testit.c'),
        Object(target='lib1', source='#/lib1/lib1.c'
        ]
    
    # Create a 'coverage' environment to build files that we will run gcovr on.
    cov = env.Clone()
    cov.Append(CFLAGS = '--coverage -O0')
    cov_objs = cov.Object(
        target='unit0', 
        source='#/item0/unit0/private/unit0.c')
    SideEffect('unit0.gcno', cov_srcs)
    
    # Our unit test program combines all the sources we've listed so far.
    test_exe = env.Program(
        'test_unit0',
        [cov_objs, non_cov_objs],
        LIBS='gcov', LINKFLAGS='--coverage')
    

    By specifying target and source for each Object, I can achieve what I want in terms of the object files (and other side-effects) being generated in the variant_dir.