godot/modules/mono/SCsub
Viktor Ferenczi c5bd0c37ce Running builder (content generator) functions in subprocesses on Windows
- Refactored all builder (make_*) functions into separate Python modules along to the build tree
- Introduced utility function to wrap all invocations on Windows, but does not change it elsewhere
- Introduced stub to use the builders module as a stand alone script and invoke a selected function

There is a problem with file handles related to writing generated content (*.gen.h and *.gen.cpp)
on Windows, which randomly causes a SHARING VIOLATION error to the compiler resulting in flaky
builds. Running all such content generators in a new subprocess instead of directly inside the
build script works around the issue.

Yes, I tried the multiprocessing module. It did not work due to conflict with SCons on cPickle.
Suggested workaround did not fully work either.

Using the run_in_subprocess wrapper on osx and x11 platforms as well for consistency. In case of
running a cross-compilation on Windows they would still be used, but likely it will not happen
in practice. What counts is that the build itself is running on which platform, not the target
platform.

Some generated files are written directly in an SConstruct or SCsub file, before the parallel build starts. They don't need to be written in a subprocess, apparently, so I left them as is.
2018-07-27 21:37:55 +02:00

246 lines
9 KiB
Python

#!/usr/bin/env python
Import('env')
Import('env_modules')
env_mono = env_modules.Clone()
# TODO move functions to their own modules
def make_cs_files_header(src, dst):
from compat import byte_to_str
with open(dst, 'w') as header:
header.write('/* This is an automatically generated file; DO NOT EDIT! OK THX */\n')
header.write('#ifndef _CS_FILES_DATA_H\n')
header.write('#define _CS_FILES_DATA_H\n\n')
header.write('#include "map.h"\n')
header.write('#include "ustring.h"\n')
inserted_files = ''
import os
for file in os.listdir(src):
if file.endswith('.cs'):
with open(os.path.join(src, file), 'rb') as f:
buf = f.read()
decomp_size = len(buf)
import zlib
buf = zlib.compress(buf)
name = os.path.splitext(file)[0]
header.write('\nstatic const int _cs_' + name + '_compressed_size = ' + str(len(buf)) + ';\n')
header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decomp_size) + ';\n')
header.write('static const unsigned char _cs_' + name + '_compressed[] = { ')
for i, buf_idx in enumerate(range(len(buf))):
if i > 0:
header.write(', ')
header.write(byte_to_str(buf[buf_idx]))
inserted_files += '\tr_files.insert("' + file + '", ' \
'CompressedFile(_cs_' + name + '_compressed_size, ' \
'_cs_' + name + '_uncompressed_size, ' \
'_cs_' + name + '_compressed));\n'
header.write(' };\n')
version_file = os.path.join(src, 'VERSION.txt')
with open(version_file, 'r') as content_file:
try:
glue_version = int(content_file.read()) # make sure the format is valid
header.write('\n#define CS_GLUE_VERSION UINT32_C(' + str(glue_version) + ')\n')
except ValueError:
raise ValueError('Invalid C# glue version in: ' + version_file)
header.write('\nstruct CompressedFile\n' '{\n'
'\tint compressed_size;\n' '\tint uncompressed_size;\n' '\tconst unsigned char* data;\n'
'\n\tCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n'
'\t{\n' '\t\tcompressed_size = p_comp_size;\n' '\t\tuncompressed_size = p_uncomp_size;\n'
'\t\tdata = p_data;\n' '\t}\n' '\n\tCompressedFile() {}\n' '};\n'
'\nvoid get_compressed_files(Map<String, CompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n'
)
header.write('#endif // _CS_FILES_DATA_H')
env_mono.add_source_files(env.modules_sources, '*.cpp')
env_mono.add_source_files(env.modules_sources, 'mono_gd/*.cpp')
env_mono.add_source_files(env.modules_sources, 'utils/*.cpp')
if env['tools']:
env_mono.add_source_files(env.modules_sources, 'editor/*.cpp')
# NOTE: It is safe to generate this file here, since this is still execute serially
make_cs_files_header('glue/cs_files', 'glue/cs_compressed.gen.h')
vars = Variables()
vars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True))
vars.Add(BoolVariable('xbuild_fallback', 'If MSBuild is not found, fallback to xbuild', False))
vars.Update(env_mono)
# Glue sources
if env_mono['mono_glue']:
env_mono.add_source_files(env.modules_sources, 'glue/*.cpp')
else:
env_mono.Append(CPPDEFINES=['MONO_GLUE_DISABLED'])
if ARGUMENTS.get('yolo_copy', False):
env_mono.Append(CPPDEFINES=['YOLO_COPY'])
# Configure TLS checks
import tls_configure
conf = Configure(env_mono)
tls_configure.configure(conf)
env_mono = conf.Finish()
# Build GodotSharpTools solution
import os
def find_msbuild_unix(filename):
import os.path
import sys
hint_dirs = ['/opt/novell/mono/bin']
if sys.platform == 'darwin':
hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin', '/usr/local/var/homebrew/linked/mono/bin'] + hint_dirs
for hint_dir in hint_dirs:
hint_path = os.path.join(hint_dir, filename)
if os.path.isfile(hint_path):
return hint_path
elif os.path.isfile(hint_path + '.exe'):
return hint_path + '.exe'
for hint_dir in os.environ['PATH'].split(os.pathsep):
hint_dir = hint_dir.strip('"')
hint_path = os.path.join(hint_dir, filename)
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK):
return hint_path + '.exe'
return None
def find_msbuild_windows():
import mono_reg_utils as monoreg
bits = env['bits']
if bits == '32':
if os.getenv('MONO32_PREFIX'):
mono_root = os.getenv('MONO32_PREFIX')
else:
mono_root = monoreg.find_mono_root_dir(bits)
else:
if os.getenv('MONO64_PREFIX'):
mono_root = os.getenv('MONO64_PREFIX')
else:
mono_root = monoreg.find_mono_root_dir(bits)
if not mono_root:
raise RuntimeError('Cannot find mono root directory')
framework_path = os.path.join(mono_root, 'lib', 'mono', '4.5')
mono_bin_dir = os.path.join(mono_root, 'bin')
msbuild_mono = os.path.join(mono_bin_dir, 'msbuild.bat')
if os.path.isfile(msbuild_mono):
# The (Csc/Vbc/Fsc)ToolExe environment variables are required when
# building with Mono's MSBuild. They must point to the batch files
# in Mono's bin directory to make sure they are executed with Mono.
mono_msbuild_env = {
'CscToolExe': os.path.join(mono_bin_dir, 'csc.bat'),
'VbcToolExe': os.path.join(mono_bin_dir, 'vbc.bat'),
'FscToolExe': os.path.join(mono_bin_dir, 'fsharpc.bat')
}
return (msbuild_mono, framework_path, mono_msbuild_env)
msbuild_tools_path = monoreg.find_msbuild_tools_path_reg()
if msbuild_tools_path:
return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), framework_path, {})
return None
def mono_build_solution(source, target, env):
import subprocess
import mono_reg_utils as monoreg
from shutil import copyfile
framework_path = ''
msbuild_env = os.environ.copy()
# Needed when running from Developer Command Prompt for VS
if 'PLATFORM' in msbuild_env:
del msbuild_env['PLATFORM']
if os.name == 'nt':
msbuild_info = find_msbuild_windows()
if msbuild_info is None:
raise RuntimeError('Cannot find MSBuild executable')
msbuild_path = msbuild_info[0]
framework_path = msbuild_info[1]
msbuild_env.update(msbuild_info[2])
else:
msbuild_path = find_msbuild_unix('msbuild')
if msbuild_path is None:
xbuild_fallback = env['xbuild_fallback']
if xbuild_fallback and os.name == 'nt':
print('Option \'xbuild_fallback\' not supported on Windows')
xbuild_fallback = False
if xbuild_fallback:
print('Cannot find MSBuild executable, trying with xbuild')
print('Warning: xbuild is deprecated')
msbuild_path = find_msbuild_unix('xbuild')
if msbuild_path is None:
raise RuntimeError('Cannot find xbuild executable')
else:
raise RuntimeError('Cannot find MSBuild executable')
print('MSBuild path: ' + msbuild_path)
build_config = 'Release'
msbuild_args = [
msbuild_path,
os.path.abspath(str(source[0])),
'/p:Configuration=' + build_config,
]
if framework_path:
msbuild_args += ['/p:FrameworkPathOverride=' + framework_path]
try:
subprocess.check_call(msbuild_args, env=msbuild_env)
except subprocess.CalledProcessError:
raise RuntimeError('GodotSharpTools build failed')
src_dir = os.path.abspath(os.path.join(str(source[0]), os.pardir, 'bin', build_config))
dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
if not os.path.isdir(dst_dir):
if os.path.exists(dst_dir):
raise RuntimeError('Target directory is a file')
os.makedirs(dst_dir)
asm_file = 'GodotSharpTools.dll'
copyfile(os.path.join(src_dir, asm_file), os.path.join(dst_dir, asm_file))
output_dir = Dir('#bin').abspath
assemblies_output_dir = Dir(env['mono_assemblies_output_dir']).abspath
mono_sln_builder = Builder(action=mono_build_solution)
env_mono.Append(BUILDERS={'MonoBuildSolution': mono_sln_builder})
env_mono.MonoBuildSolution(
os.path.join(assemblies_output_dir, 'GodotSharpTools.dll'),
'editor/GodotSharpTools/GodotSharpTools.sln'
)
if os.path.normpath(output_dir) != os.path.normpath(assemblies_output_dir):
rel_assemblies_output_dir = os.path.relpath(assemblies_output_dir, output_dir)
env_mono.Append(CPPDEFINES={'GD_MONO_EDITOR_ASSEMBLIES_DIR': rel_assemblies_output_dir})