- boost-spirit dependency needed to be installed - Visual Studio wanted boost/filesystem.hpp included in more places - a deprecated enum was removed from new versions of boost, so we use its replacement enum now - vcpkg repository is now pinned to a commit where boost 1.84 libraries work smoothly, there should be no more surprises - vcpkg dependencies now cache, so successive CI runs are dramatically faster - for win-scons, we need to use 'call' in our batch script to source vcvarsall.bat - I updated the includepath and libpath we pass to win-scons to match reality, but it still can't find any vcpkg libraries. This will need further sleuthing
420 lines
14 KiB
Python
420 lines
14 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 and no optimization", 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')
|
|
env.VariantDir('#build/obj/test/deps', 'deps')
|
|
|
|
if env['debug']:
|
|
env.Append(CCFLAGS=['-g','-o0'])
|
|
|
|
# This command generates the header with git revision information
|
|
def gen_gitrev(env, target, source):
|
|
revid = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True);
|
|
fulltag = subprocess.check_output(["git", "tag", "--sort=v:refname"], text=True).split('\n')[-1]
|
|
tagrev = subprocess.check_output(["git", "rev-parse", fulltag], text=True) if fulltag else ""
|
|
with open(target[0].path, 'w') as gitrev_hpp:
|
|
print(file=gitrev_hpp)
|
|
print('#define GIT_REVISION "' + revid[0:7] + '"', file=gitrev_hpp)
|
|
print('#define GIT_TAG "' + fulltag + '"', file=gitrev_hpp)
|
|
print('#define GIT_TAG_REVISION "' + tagrev[0:7] + '"', file=gitrev_hpp)
|
|
print(file=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("""
|
|
{SDKROOT}/System/Library/Frameworks
|
|
{SDKROOT}/Library/Frameworks
|
|
{HOME}/Library/Frameworks
|
|
""".format(HOME = os.environ['HOME'], SDKROOT = os.environ['SDKROOT'])))
|
|
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(lambda s: s.decode('utf-8'), deps)
|
|
deps = list(map(str.strip, deps))
|
|
deps = list(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 range(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']:
|
|
vcpkg_prefix = (os.environ['HOME'] if 'HOME' in os.environ else 'C:') + f'/vcpkg/installed/x{env["bits"]}-windows'
|
|
env.Append(
|
|
LINKFLAGS=['/SUBSYSTEM:WINDOWS','/ENTRY:mainCRTStartup','/MACHINE:X86'],
|
|
CXXFLAGS=['/EHsc','/MD','/FIglobal.hpp'],
|
|
INCLUDEPATH=vcpkg_prefix + '/include',
|
|
LIBPATH=vcpkg_prefix + '/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++14","-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/fileio/gzstream/
|
|
#src/fileio/xml-parser/
|
|
"""))
|
|
|
|
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 range(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('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
|
|
tgui
|
|
"""))
|
|
|
|
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.*'))
|