如何使用Scons让项目将构建输出放入同一目录?

15

背景

我正在尝试使用Scons创建一个基本的C++样例项目,它有两个子项目:

  • Prj1是一个依赖于Prj2的EXE
  • Prj2是一个导出一些函数的DLL

我遇到的问题是库在与其SConscript文件相同的目录中构建其.obj、.pdb、.lib、.dll等文件,而EXE在其SConscript所在的目录中构建其文件。应用程序成功构建了Prj2依赖项和自身。然而,您无法运行生成的EXE,因为它找不到需要的库,因为它在其他目录中。

问题

如何使具有依赖关系的多个项目将其二进制文件和调试信息输出到公共目录,以便可以执行和调试它们?

可能的解决方案

以下是我想到的:

  • 我尝试使用VariantDir(以前称为BuildDir),但似乎不起作用。也许我在这里弄错了什么。
  • 我可以通过Fo/Fd之类的方式明确地告诉编译器和链接器在哪里放置它们的文件(这是最好或唯一的解决方案吗?)
  • 在生成的二进制文件上执行复制命令(这似乎是一种麻烦且难以维护的方法)

更新

我更新了下面的文件结构和文件内容,以反映整个可行的解决方案。感谢grieve提供的见解。

命令

使用此配置,不幸的是,您必须通过cd到构建目录然后运行下面的命令来执行构建。我需要设置一个正常工作的别名来解决这个问题。


build> scons ../bin/project1.exe

文件结构

    /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: 
#   https://dev59.com/BOo6XIcBkEYKwwoYTzRK
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]

Prj1的SConscript文件


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 )

Prj2的SConscript文件


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 )

1
哇,如果所有的SO问题都能像这样出色地提出... +1 - Greg Hewgill
1
谢谢Greg!真是太疯狂了,我不到一个小时前才第一次进入你的网站并浏览了你的博客... - Zach Burlingame
2个回答

6

好的,第三次尝试就是魅力所在。我只是将其放在一个新答案中,以使其更清晰。我与我的本地scons专家交谈,他表示安装方法应该可以工作,但有一种更简单的方法。

只需定义您想要可执行文件(或dll)放置的完整路径即可。所以:

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

如果您不想在所有地方都这样做,您可以创建一个全局函数:

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

然后在您的SConscript中,可以使用以下代码:

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

请注意,您可以向环境添加自己的属性,这是其中的一种方式。
env["MY_OUTPUT_DIR"]

这里的内容是关于IT技术的,作者并没有事先准备好,所以可能会有一些小的语法错误等。显然,您可以将相同的技巧应用于共享库和静态库。

为了完全公开披露,我曾给我的本地scons专家提供了回答这个问题的机会,但他害怕会上瘾于这个网站,因此拒绝了。 :)


太棒了,我会尝试一下。非常感谢你和当地专家! - Zach Burlingame
好的,这非常接近了。您的语法错误在全局函数中,您在那里使用了“sources = srcs”。它应该是“source = srcs” - Program()参数“source”是单数形式,否则它是正确的!再次感谢grieve和本地专家。 - Zach Burlingame
很高兴能够帮忙。我还编辑了这篇文章,修复了语法错误。 - grieve

3
VariantDir是实现这一功能的方法。你的Sconstruct如何调用Sconscript文件?你是否阅读过文档的这一部分:http://www.scons.org/doc/1.1.0/HTML/scons-user/c3271.html(我假设你已经阅读了)。

我越想越觉得你想要使用DefaultInstall的组合。

在你的SConscripts中调用:

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

然后在你的Sconstruct中调用

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

那应该就可以了,我认为链接清楚地展示了它们是如何协同工作的。

谢谢你的关注。我这里没有足够的空间来回答,所以我把信息添加到了问题中! - Zach Burlingame
哦,是的,我已经阅读了相当多的SCons文档,包括那篇文章。我已经反复阅读了VariantDir文档,并一直在想它是否能够完成我想要的功能,但我束手无策。然后我在想,也许我完全误解了它的目的。 - Zach Burlingame
是的。我认为VariantDir只是一种将目标文件和其他内容与源代码分开的方法,但我需要更多地尝试才能记住它。如果您还没有看过它,http://www.scons.org/wiki/SconsRecipes也是一个很好的资源。 - grieve
太棒了,谢谢grieve。我明天会试一下,如果有进一步的问题或者确认它可以工作,我会回复你的。另外,我很喜欢SCons wiki。到目前为止,我已经使用了其中几个示例来使事情正常运行,比如单元测试。 - Zach Burlingame
我仍然遇到一些问题。我将会发布我的SConstruct和SConscript文件,这样你就可以看看它们。我知道对于这种类型的事情来说,调试/指导是很困难的。 - Zach Burlingame

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接