tags:

views:

1219

answers:

2

Background

I'm trying out Scons by setting up a basic C++ sample project that has two sub-projects:

  • Prj1 is an EXE that depends on Prj2
  • Prj2 is a DLL that exports some functions

The problem I'm running into is that the library builds its .obj, .pdb, .lib, .dll, etc. files in the same directory as it's SConscript file while the EXE builds its files in the same directory as its SConscript. The application successfully builds both the Prj2 dependency and itself. However, you cannot run the resulting EXE because it can't find the library it needs because it is in the other directory.

Question

How can I get multiple projects that have dependences to output their binaries and debug information into a common directory so that they can be executed and debugged?

Potential Solutions

This is what I have thought of so far:

  • I tried using VariantDir (previously called BuildDir) however this doesn't seem to work. Perhaps I'm messing something up here.
  • I could potentially tell the compiler and the linker explicitly (via Fo/Fd for example) where to drop their files (is this the best or only solution???)
  • Execute a copy command on the resulting binaries (this seems like a hack and quite a pain to manage/maintain)

Update

I updated the File Structure and file contents below to reflect the working solution in it's entirety. Thanks to grieve for his insight.

Command

With this configuration, you must unfortunately execute the build by cd'ing to the build directory and then running the command below. I need to get a properly working alias setup to get around this.


build> scons ../bin/project1.exe

File Structure

    /scons-sample
       /bin
          /release
          /debug
       /build
           SConstruct
           scons_helper.py
       /prj1
           SConscript
           /include
           /src
              main.cpp
       /prj2
          SConscript
          /include
             functions.h
          /src
             functions.cpp

SConstruct


import os.path

BIN_DIR = '../bin'
OBJ_DIR = './obj'

#--------------------------------------
#            CxxTest Options
#--------------------------------------
CXXTEST_DIR = '../extern/CxxTest/CxxTest-latest'
PERL = 'perl -w'
TESTS = '*.h'
TESTGEN = PERL + CXXTEST_DIR + '/cxxtestgen.pl'
CXXTESTGEN_FLAGS = '--runner=ParenPrinter \
                    --abort-on-fail \
                    --have-eh'

#--------------------------------------
#            Options
#--------------------------------------
SetOption( 'implicit_cache', 1 )

# command line options
opts = Options()
opts.AddOptions(
EnumOption(
            'debug',
         'Debug version (useful for developers only)',
         'no',
         allowed_values = ('yes', 'no'),
         map = { },
         ignorecase = 1
        )
)

#--------------------------------------
#           Environment
#--------------------------------------
env = Environment( 

    options = opts,

    #--------------------------------------
    #           Linker Options
    #--------------------------------------
    LIBPATH = [
                '../extern/wxWidgets/wxWidgets-latest/lib/vc_dll'
              ],

    LIBS =  [
               # 'wxmsw28d_core.lib',
               # 'wxbase28d.lib',
               # 'wxbase28d_odbc.lib',
               # 'wxbase28d_net.lib',
                'kernel32.lib',
                'user32.lib',
                'gdi32.lib',
                'winspool.lib',
                'comdlg32.lib',
                'advapi32.lib',
                'shell32.lib',
                'ole32.lib',
                'oleaut32.lib',
                'uuid.lib',
                'odbc32.lib',
                'odbccp32.lib'
            ],

    LINKFLAGS = '/nologo /subsystem:console /incremental:yes /debug /machine:I386',

    #--------------------------------------
    #           Compiler Options
    #--------------------------------------
    CPPPATH = [
                './include/', 
                '../extern/wxWidgets/wxWidgets-latest/include',
                '../extern/wxWidgets/wxWidgets-latest/vc_dll/mswd'
               ],

    CPPDEFINES = [ 
                    'WIN32',
                    '_DEBUG',
                    '_CONSOLE',
                    '_MBCS',
                    'WXUSINGDLL',
                    '__WXDEBUG__'
                 ],

    CCFLAGS = '/W4 /EHsc /RTC1 /MDd /nologo /Zi /TP /errorReport:prompt'
)

env.Decider( 'MD5-timestamp' )        # For speed, use timestamps for change, followed by MD5
Export( 'env', 'BIN_DIR' )          # Export this environment for use by the SConscript files

#--------------------------------------
#           Builders
#--------------------------------------
SConscript( '../prj1/SConscript' )
SConscript( '../prj2/SConscript' )
Default( 'prj1' )

scons_helper.py


import os.path

#--------------------------------------
#            Functions
#--------------------------------------

# Prepends the full path information to the output directory so that the build
# files are dropped into the directory specified by trgt rather than in the 
# same directory as the SConscript file.
# 
# Parameters:
#   env     - The environment to assign the Program value for
#   outdir  - The relative path to the location you want the Program binary to be placed
#   trgt    - The target application name (without extension)
#   srcs    - The list of source files
# Ref:
#   Credit grieve and his local SCons guru for this: 
#   http://stackoverflow.com/questions/279860/how-do-i-get-projects-to-place-their-build-output-into-the-same-directory-with
def PrefixProgram(env, outdir, trgt, srcs):
    env.Program(target = os.path.join(outdir, trgt), source = srcs)

# Similar to PrefixProgram above, except for SharedLibrary
def PrefixSharedLibrary(env, outdir, trgt, srcs):
    env.SharedLibrary(target = os.path.join(outdir, trgt), source = srcs)

def PrefixFilename(filename, extensions):
    return [(filename + ext) for ext in extensions]

# Prefix the source files names with the source directory
def PrefixSources(srcdir, srcs):
    return  [os.path.join(srcdir, x) for x in srcs]

SConscript for Prj1


import os.path
import sys
sys.path.append( '../build' )
from scons_helper import *

Import( 'env', 'BIN_DIR' )        # Import the common environment

prj1_env = env.Clone()          # Clone it so we don't make changes to the global one

#--------------------------------------
#           Project Options
#--------------------------------------
PROG = 'project1'

#--------------------------------------
#            Header Files
#--------------------------------------
INC_DIR = [
            '../prj2/include'
          ]

HEADERS = [
            ''
          ]

#--------------------------------------
#            Source Files
#--------------------------------------
SRC_DIR = './src'
SOURCES = [
            'main.cpp'
          ]
# Prefix the source files names with the source directory
SOURCES = PrefixSources( SRC_DIR, SOURCES )

#--------------------------------------
#      Compiler and Linker Overrides
#--------------------------------------
prj1_env.Append(
    CPPPATH = INC_DIR,
    LIBS = 'project2',
    LIBPATH = BIN_DIR,

    # Microsoft Visual Studio Specific
    PDB = os.path.join( BIN_DIR, PROG + '.pdb' )
)

#--------------------------------------
#            Builders
#--------------------------------------
PrefixProgram( prj1_env, BIN_DIR, PROG, SOURCES )

SConscript for Prj2


import os.path   
import sys
sys.path.append( '../build' )
from scons_helper import *

Import( 'env', 'BIN_DIR' )        # Import the common environment

prj2_env = env.Clone()          # Clone it so we don't make changes to the global one

#--------------------------------------
#           Project Options
#--------------------------------------
PROG = 'project2'

#--------------------------------------
#            Header Files
#--------------------------------------
INC_DIR = [
             ''
          ]
HEADERS = [
            'functions.h'
          ]

#--------------------------------------
#            Source Files
#--------------------------------------
SRC_DIR = './src/'
SOURCES = [
            'functions.cpp'
          ]
# Prefix the source files names with the source directory
SOURCES = PrefixSources( SRC_DIR, SOURCES )

#--------------------------------------
#      Compiler and Linker Overrides
#--------------------------------------
# Update the environment with the project specific information
prj2_env.Append(
    CPPPATH = INC_DIR,

    # Microsoft Visual Studio Specific
    PDB = os.path.join( BIN_DIR, PROG + '.pdb' )
)

#--------------------------------------
#               Builders
#--------------------------------------
PrefixSharedLibrary( prj2_env, BIN_DIR, PROG, SOURCES )
+3  A: 

VariantDir is the way to do this. How does your Sconstruct call your Sconscript files? Also have you read this section of the documentation: http://www.scons.org/doc/1.1.0/HTML/scons-user/c3271.html (I assume you have).


The more I think about it the more I think you want to use a combination of Default and Install

In your SConscripts call

env.Install("../bin", <your target exe or dll>)

then in your Sconstruct call

env.Alias('install', "../bin")
Default('install')

That should do the trick, and I think the links make it clear how it all works together.

grieve
Thanks grieve. I didn't have room to answer it here, so I added the information to the question!
Burly
Oh and yes, I've read over quite a bit of the SCons documentation including that entry. I've read and re-read the VariantDir doc and have been thinking it has got to be able to do what I want, but I'm at a loss. Then I was thinking maybe I was completely misunderstanding its purpose.
Burly
Yes. I think VariantDir is just a way to keep the object files and what not separate from the source, but I am going to have to play around with it more to remember. http://www.scons.org/wiki/SconsRecipes is also a great resource if you haven't already seen it.
grieve
Awesome, thanks grieve. I'll try this out in the morning and respond if I have further issues or accept it works. Also, I've enjoyed the SCons wiki. I've used several receipes on it so far as examples to get things working, like unit tests.
Burly
I'm still running into some issues. I'm going to post my SConstruct and SConscript files so you can see them. I know debugging/instruction for this type of thing is difficult otherwise.
Burly
What issues are you seeing?
grieve
+3  A: 

Ok Third try is a charm. I am just placing this in a new answer to keep it cleaner. I talked with my local scons guru, and he stated that the install method should work, but there is a much easier way.

Simply define the full path where you want the executable (or dll) to go. So:

prj2_env.Program(target = os.path.join(BIN_DIR,PROG), source = SOURCES )

If you don't want to have to do this in all places you can make a global function:

def PrefixProgram(env, trgt, srcs):
    env.Program(target = os.path.join(env.["MY_OUTPUT_DIR"], trgt), source = srcs)

Then in your SConscript, something like:

import ('PrefixProgram')
# stuff ...
PrefixProgram(prj2_env, PROG, SOURCES)

Note that you can add your own attribute to the environment, which is where the

env["MY_OUTPUT_DIR"]

comes from. I wrote this off the cuff, so expect some minor syntax errors and what not. Obviously you can apply the same trick for shared and static libraries.

In the interest of full disclosure I offered my local scons guru the chance to answer this himself, but he was scared he would become addicted to the site and declined. :)

grieve
Awesome, I'll give this a try. Thank you and your local guru very much!
Burly
OK, so this was super close. The syntax error you have is in your global function where you have 'sources = srcs'. It should be 'source = srcs' - the Program() parameter 'source' is singular, otherwise it was correct! Thanks again grieve and local guru.
Burly
Glad I could help. I edited the post to fix the syntax error as well.
grieve