Files
oboe/SConstruct
Rémi Verschelde de48ef1b3c SCons: Do not enforce clang on posix, allowing use of GCC
The compiler can now be defined with `scons CC=clang CXX=clang++`
and/or with the `CC` and `CXX` environment variables.

The build will pick the default C and C++ compilers of the user's
system, so probably GCC on most, and Clang and some of them (e.g. on
Travis which is configured to use Clang).

Also cleaned up unnecessary definitions which are already part of the
base environment, and removed the unnecessary (default on Linux) and
Clang-only -stdlib=libstdc++ definition (still needed on macOS).
2018-04-07 16:01:34 +02:00

424 lines
13 KiB
Python

import os.path as path
import os
import subprocess
# Build options
opts = Variables(None, ARGUMENTS)
opts.Add(EnumVariable('OS', "Target platform", str(Platform()), ('darwin', 'win32', 'posix')))
opts.Add('toolset', "Toolset to pass to the SCons builder", 'default')
opts.Add(BoolVariable('debug', "Build with debug symbols", False))
opts.Add(EnumVariable('bits', "Build for 32-bit or 64-bit architectures", '32', ('32', '64')))
# Compiler configuration
opts.Add("CXX", "C++ compiler")
opts.Add("CC", "C compiler")
opts.Add("LINK", "Linker")
opts.Add("CCFLAGS", "Custom flags for both the C and C++ compilers")
opts.Add("CXXFLAGS", "Custom flags for the C++ compiler")
opts.Add("CFLAGS", "Custom flags for the C compiler")
opts.Add("LINKFLAGS", "Custom flags for the linker")
# Initialize environment with options and full user environment
env = Environment(variables=opts, ENV=os.environ)
Help(opts.GenerateHelpText(env))
platform = env['OS']
toolset = env['toolset']
arch = 'x86_64' if (env['bits'] == '64') else 'x86'
# Update env based on options
env.Replace(TARGET_ARCH=arch)
env.Replace(tools=[toolset])
# Check for platform support
if platform not in ("darwin", "win32", "posix"):
print "Sorry, your platform is not supported."
print "Platform is:", platform
print "Specify OS=<your-platform> if you believe this is incorrect."
print "(Supported platforms are: darwin, win32, posix)"
Exit(1)
print 'Building for:', platform
print 'Using toolchain:', toolset
print 'C++ compiler:', env['CXX']
env.VariantDir('#build/obj', 'src')
env.VariantDir('#build/obj/test', 'test')
if env['debug']:
env.Append(CCFLAGS=['-g'])
# This command generates the header with git revision information
def gen_gitrev(env, target, source):
revid = subprocess.check_output(["git", "rev-parse", "HEAD"]);
fulltag = subprocess.check_output(["git", "tag", "--sort=v:refname"]).split('\n')[-1]
tagrev = subprocess.check_output(["git", "rev-parse", fulltag]) if fulltag else ""
with open(target[0].path, 'w') as gitrev_hpp:
print >>gitrev_hpp
print >>gitrev_hpp, '#define GIT_REVISION "' + revid[0:7] + '"'
print >>gitrev_hpp, '#define GIT_TAG "' + fulltag + '"'
print >>gitrev_hpp, '#define GIT_TAG_REVISION "' + tagrev[0:7] + '"'
print >>gitrev_hpp
if path.exists(".git"):
git_refs = ['.git/HEAD']
with open('.git/HEAD') as git_head:
git_head = git_head.read().split()
if git_head[0] == 'ref:':
git_refs.append(path.join('.git', git_head[1]))
env.Command('src/tools/gitrev.hpp', git_refs, gen_gitrev)
else:
# Zipped source downloads from github do not include the repo (probably a good thing)
# TODO: This does not work on Windows
env.Command('src/tools/gitrev.hpp', '', r"""
echo -e "\n#define GIT_REVISION \"\"\n#define GIT_TAG \"\"\n#define GIT_TAG_REVISION \"\"\n" > #TARGET
""")
if platform == "darwin":
env.Append(CXXFLAGS=["-std=c++11","-stdlib=libc++","-include","global.hpp"], RPATH='../Frameworks')
env["CC"] = 'clang'
env["CXX"] = 'clang++'
env.Append(BUILDERS={
'Nib': Builder(
action = "ibtool --compile $TARGET $SOURCE",
suffix = ".nib",
src_suffix = ".xib"
)
}, LIBPATH=Split("""
/usr/lib
/usr/local/lib
"""), CPPPATH=Split("""
/usr/include
/usr/local/include
"""), FRAMEWORKPATH=Split("""
/System/Library/Frameworks
/Library/Frameworks
%s/Library/Frameworks
""" % os.environ['HOME']))
def build_app_package(env, source, build_dir, info):
source_name = source[0].name
pkg_path = path.join(build_dir, "%s.app/Contents/" % source_name)
nib = env.Nib(info['nib'].replace('rsrc', 'build/obj'), info['nib'])
icons = [path.join("#rsrc/icons/mac/", x + ".icns") for x in Split(info['icons'])]
env.Install(path.join(pkg_path, "MacOS"), source)
env.Install(path.join(pkg_path, "Resources"), icons + nib)
env.InstallAs(path.join(pkg_path, "Info.plist"), info['plist'])
env.Command(path.join(pkg_path, 'PkgInfo'), '', action="echo 'APPL%s' > $TARGET" % info['creator'])
def change_install_name(lib, source, target):
subprocess.call(["install_name_tool", "-change", source, target, lib])
system_prefixes = ('/System', '/usr/lib')
def is_user_lib(lib):
return all(not lib.startswith(x) for x in system_prefixes)
def get_deps_for(source):
deps = subprocess.check_output(['otool', '-L', source]).splitlines()[1:]
deps = map(str.strip, deps)
deps = filter(is_user_lib, deps)
deps = [x.split()[0] for x in deps]
return deps
def check_deps(source):
direct_deps = get_deps_for(source)
deps = set()
for i in xrange(len(direct_deps)):
dep = direct_deps[i]
if dep.startswith('@rpath/'):
direct_deps[i] = dep = dep.split('/', 1)[1]
if dep.startswith('../Frameworks/'):
direct_deps[i] = dep = dep.split('/', 2)[2]
else:
pass#change_install_name(dep, path.join('@rpath', dep), source)
if dep == source:
continue
deps.add(dep)
return deps
def split_path(lib):
while '.' not in path.basename(lib):
lib = path.dirname(lib)
return path.dirname(lib), path.basename(lib)
def bundle_libraries_for(target, source, env):
if not path.exists(target[0].path):
Execute(Mkdir(target))
deps = check_deps(source[0].path)
for dep in deps:
if 'framework' in dep:
paths = Split(env["FRAMEWORKPATH"])
else:
paths = env["LIBPATH"]
for search_path in paths:
check_path = path.join(search_path, dep)
if path.exists(check_path):
check_path = path.realpath(check_path)
src_dir, basefile = split_path(check_path)
src_path = path.join(src_dir, basefile)
dest_path = path.join(target[0].path, basefile)
if path.exists(dest_path):
break
Execute(Copy(dest_path, src_path))
bundle_libraries_for(target, [File(check_path)], env)
break
elif platform == "win32":
if 'msvc' in env['TOOLS']:
env.Append(
LINKFLAGS=['/SUBSYSTEM:WINDOWS','/ENTRY:mainCRTStartup','/MACHINE:X86'],
CXXFLAGS=['/EHsc','/MD','/FIglobal.hpp'],
LIBPATH=("C:\Program Files (x86)\Microsoft Visual Studio " + env['MSVC_VERSION'] + "\VC\lib"),
LIBS=Split("""
kernel32
user32
gdi32
winspool
comdlg32
advapi32
shell32
ole32
oleaut32
uuid
odbc32
odbccp32
""")
)
else:
env.Append(CXXFLAGS=["-include","global.hpp"])
def build_app_package(env, source, build_dir, info):
env.Install(build_dir, source)
elif platform == "posix":
env.Append(CXXFLAGS=["-std=c++11","-include","global.hpp"])
def build_app_package(env, source, build_dir, info):
env.Install(build_dir, source)
env.AddMethod(build_app_package, "Package")
# Allow user to specify additional library/include paths
env.Append(
LIBPATH=ARGUMENTS.get('LIBPATH', '').split(path.pathsep),
CPPPATH=ARGUMENTS.get('INCLUDEPATH', '').split(path.pathsep)
)
if platform == 'darwin':
env.Append(FRAMEWORKPATH=ARGUMENTS.get('FRAMEWORKPATH', '').split(path.pathsep))
# If any package managers are installed, add their dirs too.
if subprocess.call(['which', '-s', 'port']) == 0: # MacPorts
env.Append(
LIBPATH=['/opt/local/lib'],
CPPPATH=['/opt/local/include'],
FRAMEWORKPATH=['/opt/local/Library/Frameworks']
)
if subprocess.call(['which', '-s', 'fink']) == 0: # Fink
env.Append(
LIBPATH=['/sw/lib'],
CPPPATH=['/sw/include']
)
# pretty sketchy, but should point to your boost install
if subprocess.call(['which', '-s', 'brew']) == 0: # HomeBrew
brew_boost_version = '1.58.0'
env.Append(
LIBPATH=['/usr/local/Cellar/boost/'+brew_boost_version+'/lib'],
CPPPATH=['/usr/local/Cellar/boost/'+brew_boost_version+'/include'])
# Sometimes it's easier just to copy the dependencies into the repo dir
# We try to auto-detect this.
if path.exists('deps/lib'):
env.Append(LIBPATH=['deps/lib'])
if platform == 'darwin':
env.Append(FRAMEWORKPATH=['deps/lib'])
if path.exists('deps/include'):
env.Append(CPPPATH=['deps/include'])
# Include directories
env.Append(CPPPATH=Split("""
#src/
#src/classes/
#src/tools/
#src/fileio/
#src/fileio/gzstream/
#src/fileio/resmgr/
#src/fileio/xml-parser/
#src/gfx/
#src/dialogxml/dialogs/
#src/dialogxml/widgets/
#src/scenario/
#src/universe/
"""))
env['CONFIGUREDIR'] = '#build/conf'
env['CONFIGURELOG'] = '#build/conf/config.log'
if not env.GetOption('clean'):
conf = Configure(env)
if not conf.CheckCC() or not conf.CheckCXX():
print "There's a problem with your compiler!"
Exit(1)
if not conf.CheckLib('zlib' if (platform == "win32" and 'mingw' not in env["TOOLS"]) else 'z'):
print 'zlib must be installed!'
Exit(1)
def check_lib(lib, disp, suffixes=[], versions=[]):
if platform == "win32" and lib.startswith("boost"):
lib = "lib" + lib
if "mingw" in env["TOOLS"] and lib.startswith("sfml"):
lib = "lib" + lib
possible_names = [lib]
if platform == "win32":
if 'msvc' in env['TOOLS']:
vc_suffix = '-vc' + env['MSVC_VERSION'].replace('.','')
possible_names.append(lib + vc_suffix)
n = len(possible_names)
for i in xrange(n):
for suff in suffixes:
possible_names.append(possible_names[i] + suff)
for test in possible_names:
if conf.CheckLib(test, language='C++'):
bundled_libs.append(test)
return # Success!
for ver in versions:
if conf.CheckLib(test + ver, language='C++'):
bundled_libs.append(test + ver)
return # Success!
print disp, 'must be installed!'
print " If you're sure it's installed, try passing LIBPATH=..."
Exit(1)
def check_header(header, disp):
if not conf.CheckCXXHeader(header, '<>'):
print disp, 'must be installed!'
print " If you're sure it's installed, try passing INCLUDEPATH=..."
Exit(1)
boost_versions = ['-1_54', '-1_55', '-1_56', '-1_57', '-1_58'] # This is a bit of a hack. :(
bundled_libs = []
check_header('boost/lexical_cast.hpp', 'Boost.LexicalCast')
check_header('boost/optional.hpp', 'Boost.Optional')
check_header('boost/ptr_container/ptr_container.hpp', 'Boost.PointerContainer')
check_header('boost/any.hpp', 'Boost.Any')
check_header('boost/math_fwd.hpp', 'Boost.Math')
check_header('boost/spirit/include/classic.hpp', 'Boost.Spirit.Classic')
check_lib('boost_system', 'Boost.System', ['-mt'], boost_versions)
check_lib('boost_filesystem', 'Boost.Filesystem', ['-mt'], boost_versions)
check_lib('boost_thread', 'Boost.Thread', ['-mt'], boost_versions)
check_lib('sfml-system', 'SFML-system')
check_lib('sfml-window', 'SFML-window')
check_lib('sfml-audio', 'SFML-audio')
check_lib('sfml-graphics', 'SFML-graphics')
env = conf.Finish()
env.Append(CPPDEFINES=["TIXML_USE_TICPP"])
if platform == "win32":
# For the *resource.h headers
env.Append(CPPPATH=["#rsrc/menus"])
if platform == "darwin":
env.Append(LIBS=Split("""
objc
c++
"""))
env.Append(FRAMEWORKS=Split("""
OpenGL
Cocoa
"""))
elif platform == "win32":
env.Append(LIBS=Split("""
opengl32
"""))
elif platform == "posix":
env.Append(LIBS=Split("""
GL
X11
"""))
Export("env platform")
# The VariantDir directives near the top mean that the SConscript files are
# copied from src/ and test/ into build/obj/ and build/obj/test respectively.
# Thus, any edits to them should be made to the originals in src/ or test/.
# However, when referencing them we have to reference the copies.
# Gather common sources
tools = SConscript("build/obj/tools/SConscript")
dlog_util = SConscript("build/obj/dialogxml/SConscript")
common_sources = dlog_util + tools
install_dir = "#build/Blades of Exile"
party_classes = Glob("#src/universe/*.cpp")
Export("install_dir party_classes common_sources")
# Programs
SConscript([
"build/obj/game/SConscript",
"build/obj/pcedit/SConscript",
"build/obj/scenedit/SConscript",
"build/obj/test/SConscript"
])
# Data files
data_dir = path.join(install_dir, "data")
Export("data_dir")
SConscript(["rsrc/SConscript", "doc/SConscript"])
# Bundle required frameworks and libraries
if platform == "darwin":
targets = [
"Blades of Exile",
"BoE Character Editor",
"BoE Scenario Editor",
]
for targ in targets:
target_dir = path.join(install_dir, targ + '.app', 'Contents/Frameworks')
binary = path.join(install_dir, targ + '.app', 'Contents/MacOS', targ)
env.Command(Dir(target_dir), binary, [Delete(target_dir), bundle_libraries_for])
elif platform == "win32":
bundled_libs = Split("""
libsndfile-1
openal32
sfml-audio-2
sfml-graphics-2
sfml-system-2
sfml-window-2
zlib1
""")
target_dirs = ["#build/Blades of Exile", "#build/test"]
for lib in bundled_libs:
for lpath in env['LIBPATH']:
src_file = path.join(lpath, lib + ".dll")
if path.exists(src_file):
for targ in target_dirs:
env.Install(targ, src_file)
break
elif 'lib' in lpath:
src_file = path.join(lpath.replace('lib', 'bin'), lib + ".dll")
if path.exists(src_file):
for targ in target_dirs:
env.Install(targ, src_file)
break
# Extra: Microsoft redistributable libraries installer
if 'msvc' in env["TOOLS"]:
if path.exists("dep/VCRedistInstall.exe"):
env.Install("build/Blades of Exile/", "dep/VCRedistInstall.exe")
else:
print "WARNING: Cannot find installer for the MSVC redistributable libraries for your version of Visual Studio."
print "Please download it from Microsoft's website and place it at:"
print " dep/VCRedistInstall.exe"
# Create it so its lack doesn't cause makensis to break
# (Because the installer is an optional component.)
open("build/Blades of Exile/VCRedistInstall.exe", 'w').close()
if platform == "darwin":
env.VariantDir("#build/pkg", "pkg/mac")
SConscript("build/pkg/SConscript")
elif platform == "win32" and subprocess.call(['where', '/Q', 'makensis']) == 0:
env.VariantDir("#build/pkg", "pkg/win")
SConscript("build/pkg/SConscript")
# Cleanup
env.Clean('.', 'build')
env.Clean('.', Glob('.sconsign.*'))