1 # Copyright 2001-2019 Crytek GmbH / Crytek Group. All rights reserved.
3 from waflib import Configure, Logs, Utils, Node, TaskGen, Options, ConfigSet
4 from waflib.Build import BuildContext, CleanContext, Context
5 from waflib.Tools import c_aliases, c
6 from waflib.Task import Task,RUN_ME
7 from waflib.Configure import conf, ConfigurationContext
8 from waflib.TaskGen import after_method, before_method, feature, extension
9 from waflib.Errors import BuildError, WafError
10 from waflib.TaskGen import taskgen_method
25 # Load globals from branch spec file if we must
26 from waf_branch_spec import PLATFORMS
27 from waf_branch_spec import CONFIGURATIONS
29 g_bootstrap_was_run = False
31 ###############################################################################
32 Configure.autoconfig = True
34 ###############################################################################
35 CRY_WAF_TOOL_DIR='Code/Tools/waf-1.7.13/crywaflib'
37 ###############################################################################
38 # List of subfolders to parse
44 ###############################################################################
45 ## Configure Options for WAF
47 opt.load('cry_utils' ,tooldir=CRY_WAF_TOOL_DIR)
49 opt.load('project_settings', tooldir=CRY_WAF_TOOL_DIR)
50 opt.load('branch_spec', tooldir=CRY_WAF_TOOL_DIR)
51 opt.load('gui_tasks' , tooldir=CRY_WAF_TOOL_DIR)
53 ###########################################
54 # Load support for Uber Files
55 opt.load('generate_uber_files' ,tooldir=CRY_WAF_TOOL_DIR)
57 ###########################################
58 # Load Project Generators based on host (use the custom cry versions)
59 host = Utils.unversioned_sys_platform()
61 opt.load('msvs', tooldir=CRY_WAF_TOOL_DIR)
62 opt.load('msvs_override_handling', tooldir=CRY_WAF_TOOL_DIR)
63 opt.load('mscv_helper' ,tooldir=CRY_WAF_TOOL_DIR)
66 opt.load('xcode' ,tooldir=CRY_WAF_TOOL_DIR)
68 opt.load('eclipse' ,tooldir=CRY_WAF_TOOL_DIR)
70 # Load tools to improve dependency checking (by using compiler features)
72 opt.load('mscv_helper', tooldir=CRY_WAF_TOOL_DIR)
73 opt.load('msvcdeps', tooldir=CRY_WAF_TOOL_DIR)
74 opt.load('gccdeps', tooldir=CRY_WAF_TOOL_DIR)
77 # Load internal module extensions
78 module_extension_toolsdir = CRY_WAF_TOOL_DIR + '/module_extensions'
79 for file_name in [each for each in os.listdir(module_extension_toolsdir) if each.endswith('.py')]:
80 opt.load(file_name[:-3], tooldir=module_extension_toolsdir)
82 # Load custom user module extensions
83 custom_module_extensions = Context.launch_dir + '/_WAF_/custom_module_extensions'
84 if os.path.exists(custom_module_extensions):
85 for file_name in [each for each in os.listdir(custom_module_extensions) if each.endswith('.py')]:
86 opt.load(file_name[:-3], tooldir=custom_module_extensions)
88 ###########################################
89 # Add custom cryengine options
91 opt.add_option('-p', '--project-spec', dest='project_spec', action='store', default='', help='Spec to use when building the project')
93 # Add special command line option to prevent recursive execution of WAF
94 opt.add_option('--internal-dont-check-recursive-execution', dest='internal_dont_check_recursive_execution', action='store', default='False', help='!INTERNAL ONLY! DONT USE')
96 # Add options primarily used by the Visual Studio WAF Addin
97 waf_addin_group = opt.add_option_group('Visual Studio WAF Addin Options')
98 waf_addin_group.add_option('-a', '--ask-for-user-input', dest='ask_for_user_input', action='store', default='True', help='Disables all user promps in WAF')
99 waf_addin_group.add_option( '--file-filter', dest='file_filter', action='store', default="", help='Only compile files matching this filter')
100 waf_addin_group.add_option( '--show-includes', dest='show_includes', action='store', default='False', help='Show all files included (requires a file_filter)')
101 waf_addin_group.add_option( '--show-preprocessed-file', dest='show_preprocessed_file', action='store', default='False', help='Generate only Preprocessor Output (requires a file_filter)')
102 waf_addin_group.add_option( '--show-disassembly', dest='show_disassembly', action='store', default='False', help='Generate only Assembler Output (requires a file_filter)')
104 # DEPRECATED OPTIONS, only used to keep backwards compatibility
105 waf_addin_group.add_option( '--output-file', dest='output_file', action='store', default="", help='*DEPRECATED* Specify Output File for Disassemly or Preprocess option (requires a file_filter)')
106 waf_addin_group.add_option( '--use-overwrite-file', dest='use_overwrite_file', action='store', default="False", help='*DEPRECATED* Use special BinTemp/cry_waf.configuration_overwrites to specify per target configurations')
107 waf_addin_group.add_option( '--console-mode', dest='console_mode', action='store', default="False", help='No Gui. Display console only')
108 waf_addin_group.add_option( '--generate-product', dest='generate_product', action='store', default="False", help='Generate CryEngine product version (internal use)')
110 # Lastly, load data driven settings
111 opt.load('default_settings', tooldir=CRY_WAF_TOOL_DIR)
112 opt.load('cryengine_modules', tooldir=CRY_WAF_TOOL_DIR)
114 ###############################################################################
115 ## Configure 'configure' step
117 ###########################################
118 # Load base configuration function
119 conf.load('c_config')
121 sys_platform = Utils.unversioned_sys_platform()
122 if sys_platform == 'linux':
124 elif sys_platform== 'win32':
126 elif sys_platform== 'darwin':
129 #Download SDKs for first time git users
130 if not conf.is_bootstrap_available():
131 if not os.path.isdir(os.path.join(conf.path.abspath(), 'Code/SDKs')):
132 download_sdk_exe = os.path.join(conf.path.abspath(), 'download_sdks.exe')
133 if host == 'win_x64':
134 if os.path.isfile(download_sdk_exe):
135 subprocess.call(download_sdk_exe)
137 conf.fatal('[ERROR] Missing 3rd party SDK folder "<root>/Code/SDKs". \nAuto download failed: "<root>/download_sdks.exe" could not be located.\nPlease follow the ReadMe instructions on GitHub to download the SDKs manually.' )
140 subprocess.call(["python3","download_sdks.py"])
142 conf.fatal('[ERROR] Missing 3rd party SDK folder "<root>/Code/SDKs". \nAuto download failed: "<root>/download_sdks.py" could not be located or Python3 is not available.\nPlease follow the ReadMe instructions on GitHub to download the SDKs manually.' )
144 ###########################################
145 # Check for changes in user settings file
146 conf.check_user_settings_files()
148 ###########################################
149 # Load common windows settings if needed
150 conf.load('winres', tooldir=CRY_WAF_TOOL_DIR)
152 # Load common platform scripts
153 conf.load('compile_settings_cryengine', tooldir=CRY_WAF_TOOL_DIR)
155 conf.load('compile_settings_msvc', tooldir=CRY_WAF_TOOL_DIR)
156 conf.load('compile_settings_gcc', tooldir=CRY_WAF_TOOL_DIR)
157 conf.load('compile_settings_clang', tooldir=CRY_WAF_TOOL_DIR)
159 conf.load('compile_settings_windows', tooldir=CRY_WAF_TOOL_DIR)
160 conf.load('compile_settings_linux', tooldir=CRY_WAF_TOOL_DIR)
161 conf.load('compile_settings_linux_x64', tooldir=CRY_WAF_TOOL_DIR)
162 conf.load('compile_settings_darwin', tooldir=CRY_WAF_TOOL_DIR)
164 optional_platforms = [
169 for platform in optional_platforms:
170 file_name = 'compile_settings_' + platform
171 if os.path.isfile(os.path.join(conf.path.abspath(), CRY_WAF_TOOL_DIR, file_name + '.py')):
172 conf.load(file_name, tooldir=CRY_WAF_TOOL_DIR)
175 conf.get_supported_platforms().remove(platform)
178 Logs.warn('[WARNING] - Compiler settings not found. (%s) ' % file_name )
180 # Load CppCheck since it is a 'global' platform
181 conf.load('cppcheck', tooldir=CRY_WAF_TOOL_DIR)
183 # Load QT last as we need to hook the runnable status at the lowest level
184 conf.load('qt' ,tooldir=CRY_WAF_TOOL_DIR)
186 ###########################################
187 # Load support for c# and swig
188 conf.load('swig', tooldir=CRY_WAF_TOOL_DIR)
189 conf.load('protoc', tooldir=CRY_WAF_TOOL_DIR)
190 conf.load('cs', tooldir=CRY_WAF_TOOL_DIR)
192 #Get user defined active specs
193 specs_platforms = set()
194 if conf.options.specs_to_include_in_project_generation:
195 spec_list = conf.options.specs_to_include_in_project_generation.replace(' ', '').split(',')
196 else: # Special case for Buildbot which sets no spec for specs_to_include_in_project_generation.
197 spec_list = conf.loaded_specs()
199 # Get the platform list of all active specs
200 tmp_specs_platforms = set()
201 for spec in spec_list:
202 for platform in conf.get_supported_platforms():
203 tmp_specs_platforms.update(conf.spec_valid_platforms(spec, platform))
205 # Convert to complete platform names i.e. win -> win_x64 and win_x86
206 for valid_platform in tmp_specs_platforms:
207 for value in conf.get_platform_permutation_map(valid_platform).values():
208 specs_platforms.update(value)
210 ###########################################
211 # Load settings for all platforms
213 installed_platforms = []
214 for platform in conf.get_supported_platforms():
216 # Check if platform is required for active specs
217 if platform not in specs_platforms:
220 Logs.info('[INFO] Configure "%s - [%s]"' % (platform, ', '.join(conf.get_supported_configurations())))
222 # Load Platform Configuration (except cppcheck as it is not a *real* compiler)
223 if platform != 'cppcheck':
224 file_name = 'compile_rules_' + host + '_' + platform
225 if os.path.isfile(os.path.join(conf.path.abspath(), CRY_WAF_TOOL_DIR, file_name + '.py')):
226 conf.load('compile_rules_' + host + '_' + platform, tooldir=CRY_WAF_TOOL_DIR)
228 # Use the specialized function to load platform specifics
229 function_name = 'check_%s_%s_installed' % ( host, platform )
230 if not hasattr(conf, function_name):
231 conf.fatal('[ERROR] Required function \'%s\' not found' % function_name )
233 # Try to load the function
234 if not getattr(conf, function_name)():
235 Logs.warn('[WARNING] - Unable to locate platform "%s" on system. Disabling platform support for: %s !!!' % (platform, platform))
238 Logs.warn('[WARNING] - Unable to locate compiler rules "%s". Disabling platform support for: %s !!!. ' % (file_name, platform))
242 installed_platforms.append(platform)
244 for configuration in conf.get_supported_configurations():
245 conf.setenv(platform + '_' + configuration) # change env to configuration env
246 conf.init_compiler_settings()
248 # Use the specialized function to load platform specifics
249 function_name = 'load_%s_%s_%s_settings' % ( configuration, host, platform )
250 if not hasattr(conf, function_name):
251 conf.fatal('[ERROR] Required function \'%s\' not found' % function_name )
253 # Try to load the function
254 getattr(conf, function_name)()
257 conf.configure_protoc()
260 conf.configure_swig()
261 conf.configure_mono()
262 conf.env['build_relevant_platforms'] = installed_platforms
263 conf.setenv('') # change env back to global env
265 conf.set_supported_platforms(installed_platforms)
266 conf.env['build_relevant_platforms'] = installed_platforms # need to store in global cache as well for project_generators
268 ###########################################
269 # Load support for c and cxx compiler
273 conf.load('protoc',tooldir=CRY_WAF_TOOL_DIR)
275 ###########################################
276 # Load Platform specific helpers based on host
277 host = Utils.unversioned_sys_platform()
281 ###########################################
282 # Create VS-Projects automatically during configure when running on windows
283 if host == 'win32' and conf.is_option_true('generate_vs_projects_automatically'):
284 # Workflow improvement: for all builds generate projects after the build
285 # except when using the default build target 'utilities' then do it before
286 if 'build' in Options.commands:
287 build_cmd_idx = Options.commands.index('build')
288 Options.commands.insert(build_cmd_idx, 'msvs')
290 Options.commands.append('msvs')
292 ###########################################
293 # Load remaining tools for correct auto configure
295 conf.load('recode', tooldir=CRY_WAF_TOOL_DIR)
296 conf.load('static_code_analyzer', tooldir=CRY_WAF_TOOL_DIR)
298 ###########################################
299 # Recurse into subfolders for auto conf when any wscript changes
300 conf.recurse(dirs=SUBFOLDERS, mandatory=False)
302 # Load Game Specific parts
303 for project in conf.game_projects():
304 conf.game_project = project
305 conf.recurse( conf.game_code_folder(project))
307 ###########################################
308 # Always update uber files during configuration
309 # But don't add the same command twice
310 if len(Options.commands) == 0:
311 Options.commands.insert(0, 'generate_uber_files')
312 elif not Options.commands[0] == 'generate_uber_files':
313 Options.commands.insert(0, 'generate_uber_files')
315 # Remove timestamp files to force builds of generate_uber_files and project gen even if
316 # some command after configure failes
318 generate_uber_files_timestamp = conf.get_bintemp_folder_node().make_node('generate_uber_files.timestamp')
319 os.stat(generate_uber_files_timestamp.abspath())
323 generate_uber_files_timestamp.delete()
326 project_gen_timestamp = conf.get_bintemp_folder_node().make_node('project_gen.timestamp')
327 os.stat(project_gen_timestamp.abspath())
331 project_gen_timestamp.delete()
333 def post_command_exec(bld):
335 if bld.cmd == 'msvs':
336 project_gen_timestamp = bld.get_bintemp_folder_node().make_node('project_gen.timestamp')
337 project_gen_timestamp.write('')
338 # [post uberfile gen]
339 elif bld.cmd == 'generate_uber_files':
340 generate_uber_files_timestamp = bld.get_bintemp_folder_node().make_node('generate_uber_files.timestamp')
341 generate_uber_files_timestamp.write('')
343 elif bld.cmd.startswith('build'):
344 for message in bld.post_build_msg_info:
347 for message in bld.post_build_msg_warning:
350 for message in bld.post_build_msg_error:
353 stored_file_filter = ''
354 stored_output_file = ''
356 ###############################################################################
360 ###########################################
361 # Setup command coordinator
362 bld.load('cmd_coordinator', tooldir=CRY_WAF_TOOL_DIR)
363 bld.setup_command_coordinator()
365 # We might run multiple instances of WAF at the same time
366 # 1) IB as master ... The WAF IB instance (master) will handle task creation and will intercept each call to a .exe file making it the "Build Master" ... the original WAF instance should just exit here
367 # 2) IB as service ... The original WAF instance(master) will handle the task creation and "may" forward the .exe calls to the IB instance (slave)
368 if not bld.instance_is_build_master():
371 # Keep backward compatibility
372 if bld.options.project_spec == 'everything':
373 bld.options.project_spec = 'trybuilder'
375 bld.options.project_spec = bld.options.project_spec.strip() # remove spaces
377 # Create a post build message container
378 bld.post_build_msg_info = []
379 bld.post_build_msg_warning = []
380 bld.post_build_msg_error = []
382 # Add groups to ensure all task generators who create static lib tasks are executd first
383 bld.add_group('generate_static_lib')
384 bld.add_group('regular_group')
385 # Set back to our regular compile group
386 bld.set_group('regular_group')
389 ###########################################
390 # Load common windows settings if needed
391 bld.load('winres', tooldir=CRY_WAF_TOOL_DIR)
393 # Check if a valid spec is defined
394 if bld.cmd != 'generate_uber_files' and bld.cmd != 'msvs' and bld.cmd != 'eclipse':
395 # project spec is a mandatory parameter
396 if bld.options.project_spec == '':
397 bld.fatal('[ERROR] No Project Spec defined. Use "-p <spec_name>" command line option to specify project spec. Valid specs are "%s". ' % bld.loaded_specs())
399 # Ensure that the selected spec is supported on this platform
400 if not bld.options.project_spec in bld.loaded_specs():
401 bld.fatal('[ERROR] Invalid Project Spec (%s) defined, valid specs are %s' % (bld.options.project_spec, bld.loaded_specs()))
403 # Check for valid single file compilation flag pairs
404 if bld.is_option_true('show_preprocessed_file') and bld.options.file_filter == "":
405 bld.fatal('--show-preprocessed-file can only be used in conjunction with --file-filter')
406 elif bld.is_option_true('show_disassembly') and bld.options.file_filter == "":
407 bld.fatal('--show-disassembly can only be used in conjunction with --file-filter')
409 ###########################################
410 # Check timestamps for special commands
411 if not 'generate_uber_files' in Options.commands and bld.cmd != 'generate_uber_files':
412 generate_uber_files_timestamp = bld.get_bintemp_folder_node().make_node('generate_uber_files.timestamp')
414 os.stat(generate_uber_files_timestamp.abspath())
416 # No generate_uber file timestamp, restart command chain, prefixed with gen uber files cmd
417 Options.commands = ['generate_uber_files'] + [bld.cmd] + Options.commands
420 if not 'msvs' in Options.commands and bld.cmd != 'msvs':
421 project_gen_timestamp = bld.get_bintemp_folder_node().make_node('project_gen.timestamp')
423 os.stat(project_gen_timestamp.abspath())
425 # No project_gen timestamp, append project_gen to commands
426 if bld.is_option_true('generate_vs_projects_automatically') and Utils.unversioned_sys_platform() == 'win32':
427 Options.commands = Options.commands + ['msvs']
429 ###########################################
430 # Check for valid variant if we are not generating projects
431 if bld.cmd == 'xcode' or bld.cmd == 'msvs' or bld.cmd == 'eclipse' or bld.cmd == 'generate_uber_files':
432 bld.env['PLATFORM'] = 'project_generator'
433 bld.env['CONFIGURATION'] = 'project_generator'
436 bld.fatal('please use a valid build configuration, try "waf --help"')
438 (platform, configuration) = bld.get_platform_and_configuration()
439 bld.env['PLATFORM'] = platform
440 bld.env['CONFIGURATION'] = configuration
442 if not platform in bld.get_supported_platforms():
443 bld.fatal( '[ERROR] - Target platform "%s" not supported. [on host platform: %s]' % (platform, Utils.unversioned_sys_platform()) )
445 # When building, only support platform that we are currently building for to reduce internal workload
446 bld.set_supported_platforms([bld.env['PLATFORM']])
448 # check if configuration is valid in the current spec scope
449 bVariantValid = False
450 for conf in bld.spec_valid_configurations():
451 if conf == configuration:
453 if not bVariantValid:
454 bld.fatal('[ERROR] Building Spec "%s" for "%s|%s" is not supported. Valid Configurations are: "%s".' % (bld.options.project_spec, platform, configuration, ', '.join(bld.spec_valid_configurations())))
456 # check if platform is valid in the current spec scope
457 bVariantValid = False
458 for p0 in bld.spec_valid_platforms():
459 for p1 in bld.get_platform_list(bld.env['PLATFORM']):
462 if not bVariantValid:
463 bld.fatal('[ERROR] Building Spec "%s" for "%s|%s" is not supported.. Valid Platforms are: "%s".' % (bld.options.project_spec, platform, configuration, ', '.join(bld.spec_valid_platforms())))
465 # Ensure that, if specified, target is supported in this spec
466 if bld.options.targets:
467 for target in bld.options.targets.split(','):
468 if not target in bld.spec_modules():
469 bld.fatal('[ERROR] Module "%s" is not configurated to be build in spec "%s" in "%s|%s"' % (target, bld.options.project_spec, platform, configuration))
471 ###########################################
472 bld.add_post_fun(post_command_exec)
474 ###########################################
475 # Setup Output Path for Project Geneators
476 bld.solution_name = bld.get_solution_name()
477 bld.projects_dir = bld.get_project_output_folder()
479 ###########################################
480 # Load configuration overwrites
481 bld.env['CONFIG_OVERWRITES'] = bld.get_solution_overrides()
483 ###########################################
484 # Disable optimizations if requested
485 # Must be done after computing overrides
486 if bld.is_option_true('force_deoptimized_builds'):
487 bld.env['CFLAGS'] += bld.env['COMPILER_FLAGS_DisableOptimization']
488 bld.env['CXXFLAGS'] += bld.env['COMPILER_FLAGS_DisableOptimization']
490 ###########################################
491 # Load Support for Recode if required
492 host = Utils.unversioned_sys_platform()
494 bld.load('recode', tooldir=CRY_WAF_TOOL_DIR)
495 if str(bld.options.support_recode) == str(True):
496 bld.enable_recode = bld.check_global_recode_enabled()
498 ###########################################
499 # Add support for static code analyzer
500 bld.load('static_code_analyzer', tooldir=CRY_WAF_TOOL_DIR)
502 # Clear <output folder>/Image/Loose on durango as a workaround for buggy deploy
503 if bld.env['PLATFORM'] == 'durango':
504 for node in bld.get_output_folders(bld.env['PLATFORM'], bld.env['CONFIGURATION']):
505 image_lost_dir = node.find_dir('Image/Loose')
507 files = image_lost_dir.ant_glob('**/*')
512 # Load Core Engine Parts (Engine, Tools, Core Shaders etc)
513 bld.game_project = None
514 bld.recurse(SUBFOLDERS, mandatory=False)
516 # Load Game Specific parts
517 for project in bld.game_projects():
518 bld.game_project = project
519 bld.recurse( bld.game_code_folder(project))
521 ###############################################################################
523 def target_clean(self):
525 tmp_targets = self.options.targets[:]
527 # Sort of recursive algorithm, find all outputs of targets
528 # Repeat if new targets were added due to use directives
529 while len(tmp_targets) > 0:
532 for tgen in self.get_all_task_gen():
534 if not tgen.target in tmp_targets:
540 if n.is_child_of(self.bldnode):
543 if hasattr(tgen, 'use'):
544 new_targets.append(tgen.use)
546 tmp_targets = new_targets[:]
548 # Final File list to delete
549 to_delete = list(set(to_delete))
550 for file in to_delete:
554 ###############################################################################
555 # Copy pasted from MSVS..
556 def convert_vs_configuration_to_waf_configuration(configuration):
557 if 'Debug' in configuration:
559 if 'Profile' in configuration:
561 if 'Release' in configuration:
563 if 'Performance' in configuration:
568 ###############################################################################
571 if not bld.is_option_true('ask_for_user_input'):
574 # Function to handle creating of new libraries
575 def install_waf_addin():
576 enviroment = os.environ.copy()
577 if not 'APPDATA' in enviroment:
578 print 'APPDATA enviroment variable not found, WAF cannot figure out where to install the addin'
581 install_path = enviroment['APPDATA'] + '\\Microsoft\\MSEnvShared\\Addins'
582 source_path = bld.path.abspath() + '\\Code\\Tools\\waf_addin\\Bin_Release'
584 Logs.info('WAF Addin will be installed into:\n%s' % install_path)
585 bld.start_msg('Create Install directory')
587 if not os.path.exists(install_path):
588 os.makedirs(install_path)
590 bld.end_msg('failed (%s)' % sys.exc_info()[1], color='RED')
594 for file in ['WAF_Addin.AddIn', 'WAF_Addin.dll', 'WAF_Addin.xml']:
595 bld.start_msg('Copy %s to install directory' % file)
596 # Make output writeable
598 os.chmod(install_path + '\\' + file, 493) # 0755
603 shutil.copy2(source_path + '\\' + file, install_path + '\\' + file)
605 bld.end_msg('failed (%s)' % sys.exc_info()[1], color='RED')
608 Options.commands.insert(0, 'utilities')
610 def reconfigure_waf():
611 Options.commands.insert(0, 'configure')
613 def bootstrap_update():
614 bld.ExecuteBootstrap()
615 Options.commands.insert(0, 'utilities')
617 def regenerate_vs_solution():
618 Options.commands.insert(0, 'utilities')
619 Options.commands.insert(0, 'msvs')
621 def regenerate_uber_files():
622 Options.commands.insert(0, 'utilities')
623 Options.commands.insert(0, 'generate_uber_files')
625 def regenerate_config_file():
627 # Remove current config file and remove some state
628 config_file = bld.get_user_settings_node()
632 bld.load_user_settings()
634 # Afterwards return to utilies dialog
635 Options.commands.insert(0, 'utilities')
637 def apply_new_crycommon_layout_to_custom_folder():
638 folder = raw_input('Please enter absolute folder path: ')
639 if os.path.isdir(folder):
640 bld.convert_files_in_folder_to_cry_common([bld.root.make_node(folder)])
642 Logs.error("Folder could not be found.", folder)
643 Options.commands.insert(0, 'utilities')
646 Options.commands.insert(0, 'utilities')
647 Options.commands.insert(0, 'show_option_dialog')
649 # Function to handle exit requests
653 if not bld.is_option_true('console_mode'):
654 conversion_file = ['Apply new CryCommon layout', [
655 ('Custom folder and subfolders', apply_new_crycommon_layout_to_custom_folder)
662 menu_regenerate = ['Generate', [
663 ("Visual Studio Solution", regenerate_vs_solution),
664 ("Uber Files", regenerate_uber_files)
667 menu_tools = ['Tools', [
668 ("Options", waf_options),
669 ("Configure", reconfigure_waf)
672 if bld.is_bootstrap_available():
673 menu_tools[1] += [("Run Bootstrap", bootstrap_update)]
675 #menu_tools[1] += [("Install WAF Addin", install_waf_addin)] # disabled as unmaintained
676 fn_result = bld.gui_get_waf_menu_choice([menu_tools, menu_regenerate, conversion_file, menu_file])
680 fn_result() # Execute result
682 Options.commands.insert(0, 'utilities')
687 #(install_waf_addin,Install WAF Addin), # disabled as unmaintained
688 (regenerate_vs_solution, 'Regenerate Visual Studio Solution'),
689 (regenerate_uber_files, 'Regenerate Uber Files'),
690 (regenerate_config_file, 'Regenerate Config File')
693 if bld.is_bootstrap_available():
694 choices += [(bootstrap_update, 'Run Bootstrap Update')]
696 print(' === Crytek WAF Build System === ')
697 print(' Please use waf --help to see all valid build targets and build options')
699 for idx, option in enumerate(choices):
700 print('%s: %s' % (idx, option[1]))
702 user_input = raw_input('Please select an option: ')
704 # Sanity check for valid user inputs
706 if int(user_input) < int(0) or int(user_input) > len(choices):
709 print('Invalid Input, please choose a value between 1 and %s', len(choices))
710 Options.commands.insert(0, 'utilities')
713 # Input is fine, exectue selection
714 if int(user_input) == 1:
718 choices[int(user_input)][0]()
720 ##############################################################################
721 class execute_utilities(BuildContext):
722 ''' Util class to execute waf utilities dialog '''
726 ###############################################################################
727 # Create Build Context Commands for multiple platforms/configurations
728 for platform in PLATFORMS[Utils.unversioned_sys_platform()]:
729 for configuration in CONFIGURATIONS:
730 # Create new class to execute build with variant
731 name = CleanContext.__name__.replace('Context','').lower()
732 class tmp_clean(CleanContext):
733 cmd = name + '_' + platform + '_' + configuration
734 variant = platform + '_' + configuration
736 def __init__(self, **kw):
737 super(CleanContext, self).__init__(**kw)
738 self.top_dir = kw.get('top_dir', Context.top_dir)
741 if Configure.autoconfig:
742 env = ConfigSet.ConfigSet()
746 env.load(os.path.join(Context.lock_dir, Options.lockfile))
748 Logs.warn('Configuring the project')
751 if env.run_dir != Context.run_dir:
755 for f in env['files']:
757 h = hash((h, Utils.readf(f, 'rb')))
758 except (IOError, EOFError):
759 pass # ignore missing files (will cause a rerun cause of the changed hash)
760 do_config = h != env.hash
763 Options.commands.insert(0, self.cmd)
764 Options.commands.insert(0, 'configure')
767 # Execute custom clear command
769 if not self.all_envs:
771 self.recurse([self.run_dir])
773 if self.options.targets:
782 # Create new class to execute clean with variant
783 name = BuildContext.__name__.replace('Context','').lower()
784 class tmp_build(BuildContext):
785 cmd = name + '_' + platform + '_' + configuration
786 variant = platform + '_' + configuration
788 ###############################################################################
789 # Install Default variant
790 for y in (BuildContext, CleanContext):
792 variant = 'utilities'
795 ###############################################################################
796 # Create Commands for backwards compatibility
797 if Utils.unversioned_sys_platform() == 'win32':
798 for configuration in CONFIGURATIONS:
799 for context in (BuildContext, CleanContext):
800 name = context.__name__.replace('Context','').lower()
802 cmd = name + '_win32_' + configuration
803 variant = 'win_x86_' + configuration
804 for configuration in CONFIGURATIONS:
805 for context in (BuildContext, CleanContext):
806 name = context.__name__.replace('Context','').lower()
808 cmd = name + '_win64_' + configuration
809 variant = 'win_x64_' + configuration
813 def copy_files(self, source_file, output_folders = None):
815 if not output_folders:
816 output_folders = self.bld.get_output_folders(self.bld.env['PLATFORM'], self.bld.env['CONFIGURATION'])
818 for node in output_folders:
820 if hasattr(self, 'output_sub_folder'):
821 _output_sub_folder = str(self.output_sub_folder)
822 if os.path.isabs(_output_sub_folder):
823 output_node = self.bld.root.make_node(_output_sub_folder)
825 output_node = output_node.make_node(_output_sub_folder)
826 output_node = output_node.make_node( os.path.basename(source_file.abspath()) )
828 path = os.path.dirname( output_node.abspath() )
829 if not os.path.exists( path ):
832 self.create_task('copy_outputs', source_file, output_node)
834 ###############################################################################
835 # Class to handle copying of the final outputs into the Bin folder
836 class copy_outputs(Task):
840 src = self.inputs[0].abspath()
841 tgt = self.outputs[0].abspath()
843 # Create output folder
844 if not os.path.exists( os.path.dirname( tgt ) ):
846 os.makedirs( os.path.dirname( tgt ) )
848 pass # Some other thread must have created the folder in the meantime
850 def _copy_file(src, tgt, recursion_count = 0):
851 # Make output writeable
853 os.chmod(tgt, 493) # 0755
858 shutil.copy2(src, tgt)
859 except (IOError, os.error) as why:
862 if recursion_count < 2:
865 return _copy_file(src, tgt, recursion_count)
867 self.err_msg = "[Error] %s\n[Error] Could not perform copy %s -> %s" % (str(why), src, tgt)
871 if recursion_count < 2:
874 return _copy_file(src, tgt, recursion_count)
875 self.err_msg = "[Error] Could not perform copy %s -> %s" % (src, tgt)
881 return _copy_file(src, tgt)
883 def runnable_status(self):
884 if super(copy_outputs, self).runnable_status() == -1:
887 src = self.inputs[0].abspath()
888 tgt = self.outputs[0].abspath()
890 # If there any target file is missing, we have to copy
892 stat_tgt = os.stat(tgt)
896 # Now compare both file stats
898 stat_src = os.stat(src)
902 # same size and identical timestamps -> make no copy
903 if stat_src.st_mtime >= stat_tgt.st_mtime + 2 or stat_src.st_size != stat_tgt.st_size:
906 # Everything fine, we can skip this task
910 ###############################################################################
911 # Function to generate the copy tasks for build outputs
912 @after_method('set_pdb_flags')
913 @after_method('apply_incpaths')
914 @after_method('apply_map_file')
916 def add_install_copy(self, output_folders = None):
917 if self.bld.cmd == "msvs" or 'android' in self.bld.env['PLATFORM']:
920 if not getattr(self, 'link_task', None):
923 if self._type == 'stlib': # Do not copy static libs
926 for src in self.link_task.outputs:
927 self.copy_files(src, output_folders)
930 ###############################################################################
931 # Function to generate the EXE_VERSION_INFO defines
932 @after_method('apply_incpaths')
934 def apply_version_info(self):
935 version = str( self.bld.get_product_version() )
937 version_parts = version.split('.')
938 if len(version_parts) < 3:
939 Logs.warn('Invalid build version (%s), falling back to (1.0.0.1)' % version )
940 version_parts = ['1', '0', '0', '1']
942 version_parts[0] = version_parts[0].strip()
943 version_parts[1] = version_parts[1].strip()
944 version_parts[2] = version_parts[2].strip()
945 version_parts[3] = version_parts[3].strip()
947 for t in getattr(self, 'compiled_tasks', []):
948 t.env.append_value('DEFINES', 'EXE_VERSION_INFO_0=' + version_parts[0])
949 t.env.append_value('DEFINES', 'EXE_VERSION_INFO_1=' + version_parts[1])
950 t.env.append_value('DEFINES', 'EXE_VERSION_INFO_2=' + version_parts[2])
951 t.env.append_value('DEFINES', 'EXE_VERSION_INFO_3=' + version_parts[3])
953 ###############################################################################
954 def wrap_execute(execute_method):
956 Decorator used to set the commands that can be configured automatically
959 # Make sure to create all needed temp folders
960 bin_temp = self.get_bintemp_folder_node()
962 tmp_files_folder = bin_temp.make_node('TempFiles')
963 tmp_files_folder.mkdir()
965 # Before executing any build or configure commands, check for config file
966 # and for bootstrap updates
967 self.load_user_settings()
968 check_for_bootstrap(self)
970 return execute_method(self)
974 BuildContext.execute = wrap_execute(BuildContext.execute)
975 ConfigurationContext.execute = wrap_execute(ConfigurationContext.execute)
977 ###############################################################################
978 def check_for_bootstrap(bld):
979 global g_bootstrap_was_run
980 if g_bootstrap_was_run:
982 g_bootstrap_was_run = True
984 if not (bld.is_bootstrap_available() and bld.is_option_true('auto_run_bootstrap')):
985 return # Skip auto bootstrapping
987 bootstrap_dat = bld.path.make_node('bootstrap.dat')
988 bootstrap_timestamp = bld.get_bintemp_folder_node().make_node('bootstrap.timestamp')
990 # Check for bootstrap.dat
992 stat_bootstrap_dat = os.stat(bootstrap_dat.abspath())
994 bld.fatal('bootstrap.dat file not found')
996 # Check for bootstrap.timestamp, run bootstrap is it doesn't exist
998 stat_bootstrap_timestamp = os.stat(bootstrap_timestamp.abspath())
999 if stat_bootstrap_dat.st_mtime < stat_bootstrap_timestamp.st_mtime + 2:
1000 return # No need to bootstrap
1004 bld.ExecuteBootstrap()
1006 # Create TimeStamp File
1007 bootstrap_timestamp.write('')
1009 ###############################################################################
1011 def get_output_folders(self, platform, configuration, target_spec = None, game_project = None):
1013 Specifies the final binary output folder depending on the current setup
1014 <root>/<out_folder_xxx><output_folder_post_fix><output_extension_xxx>/<output_sub_folder>
1018 # Add <out_folder_xxx> part
1019 if platform == 'win_x86':
1020 path += self.options.out_folder_win32
1021 elif platform == 'win_x64':
1022 path += self.options.out_folder_win64
1023 elif platform == 'durango':
1024 path += self.options.out_folder_durango
1025 elif platform == 'orbis':
1026 path += self.options.out_folder_orbis
1027 elif platform == 'linux_x86_gcc':
1028 path += self.options.out_folder_linux32_gcc
1029 elif platform == 'linux_x86_clang':
1030 path += self.options.out_folder_linux32_clang
1031 elif platform == 'linux_x64_gcc':
1032 path += self.options.out_folder_linux64_gcc
1033 elif platform == 'linux_x64_clang':
1034 path += self.options.out_folder_linux64_clang
1035 elif platform == 'darwin_x86':
1036 path += self.options.out_folder_darwin32
1037 elif platform == 'darwin_x64':
1038 path += self.options.out_folder_darwin64
1039 elif platform == 'android_arm':
1040 path += self.options.out_folder_android
1041 elif platform == 'android_arm64':
1042 path += self.options.out_folder_android64
1044 path += 'bin/platform_unknown'
1046 # Add <output_folder_post_fix> part
1047 if not target_spec: # Fall back to command line specified spec if none was given
1048 target_spec = self.options.project_spec
1051 post_fix = self.spec_output_folder_post_fix(target_spec, platform, configuration)
1055 # Add <output_extension_xxx> part
1056 if not configuration and self.env['CONFIGURATION']:
1057 _configuration = self.env['CONFIGURATION']
1059 _configuration = configuration
1061 if _configuration == 'debug':
1062 path += self.options.output_extension_debug
1063 elif _configuration == 'profile':
1064 path += self.options.output_extension_profile
1065 elif _configuration == 'performance':
1066 path += self.options.output_extension_performance
1067 elif _configuration == 'release':
1068 path += self.options.output_extension_release
1070 path += "_config_unknown"
1072 # Add <output_sub_folder> part
1073 if self.env['output_sub_folder']:
1074 path += os.sep + os.sep.join(self.env['output_sub_folder'])
1076 # Correct handling for absolute paths
1077 if os.path.isabs(path):
1078 output_folder_nodes = [ self.root.make_node(path) ]
1079 else: # For relative path, prefix binary output folder with game project folder
1080 output_folder_nodes = []
1081 if target_spec == None:
1082 target_spec = self.options.project_spec # Fall back to global spec
1083 output_folder_nodes += [ self.path.make_node(path)]
1084 # Code below is to support multiple output folder
1085 #for project in self.active_projects(target_spec):
1086 # project_folder = self.project_output_folder(project)
1087 # output_folder_nodes += [ self.path.make_node(project_folder).make_node(path) ]
1088 # output_folder_nodes += [ self.path.make_node(path) ]
1090 return output_folder_nodes
1093 ###############################################################################
1095 def is_bootstrap_available(bld):
1096 bootstrap_path = bld.path.abspath() + '/Tools/branch_bootstrap/bootstrap.exe'
1097 return os.path.isfile(bootstrap_path)
1099 ###############################################################################
1101 def ExecuteBootstrap(bld):
1103 if not is_bootstrap_available(bld):
1106 host = Utils.unversioned_sys_platform()
1109 executable = [bld.path.abspath() + '/Tools/branch_bootstrap/bootstrap.exe']
1110 elif host == 'darwin':
1111 executable = ['python3', bld.path.abspath() + '/Tools/branch_bootstrap/bootstrap.py']
1112 elif host == 'linux':
1113 executable = ['python3', bld.path.abspath() + '/Tools/branch_bootstrap/bootstrap.py']
1114 if executable is None:
1115 bld.fatal('unknown host, could not determine bootstrap executable')
1117 bootstrap_dat = bld.path.abspath() + '/bootstrap.dat'
1119 bintmp = bld.get_bintemp_folder_node()
1120 bootstrap_digest = bintmp.make_node('.bootstrap.digest.pickle')
1122 Logs.info('Beginning Branch Bootstrap Operation (this might take a while)')
1124 ret = subprocess.call(
1126 '-d' + bootstrap_dat,
1127 '-m' + bootstrap_digest.abspath(),
1128 '--addrelevance=buildmachine'])
1130 bld.msg('branch bootstrap', 'done')
1132 bld.msg('branch bootstrap', 'failed', color='RED')
1135 from waflib.Configure import conf
1138 def read_file_list(bld, file):
1139 file_node = bld.path.make_node(file)
1141 return bld.parse_json_file(file_node)
1144 def get_platform_and_configuration(bld):
1145 # Assume that the configuration begins after the last '_'
1146 platform = "_".join( bld.variant.split("_")[:-1] )
1147 configuration = bld.variant.split("_")[-1]
1149 # Keep Backward compatibility with old names
1150 if platform == '_win32':
1151 platform = 'win_x86'
1152 if platform == '_win64':
1153 platform = 'win_x64'
1155 return (platform, configuration)
1157 @feature('link_to_output_folder')
1158 @after_method('process_source')
1159 def link_to_output_folder(self):
1161 Task Generator for tasks which generate symbolic links from the source to the dest folder
1163 return # Disabled for now
1165 if self.bld.env['PLATFORM'] == 'project_generator':
1166 return # Dont create links during project generation
1168 if sys.getwindowsversion()[0] < 6:
1169 self.bld.fatal('not supported')
1171 # Compute base relative path (from <Root> to taskgen wscript
1172 relative_base_path = self.path.path_from(self.bld.path)
1174 # TODO: Need to handle absolute path here correctly
1175 spec_name = self.bld.options.project_spec
1176 for project in self.bld.active_projects(spec_name):
1177 project_folder = self.bld.project_output_folder(project)
1178 for file in self.source:
1179 # Build output folder
1180 relativ_file_path = file.path_from(self.path)
1182 output_node = self.bld.path.make_node(project_folder)
1183 output_node = output_node.make_node(relative_base_path)
1184 output_node = output_node.make_node(relativ_file_path)
1186 path = os.path.dirname( output_node.abspath() )
1187 if not os.path.exists( path ):
1190 self.create_task('create_symbol_link', file, output_node)
1193 ###############################################################################
1194 # Class to handle copying of the final outputs into the Bin folder
1195 class create_symbol_link(Task):
1199 src = self.inputs[0].abspath()
1200 tgt = self.outputs[0].abspath()
1202 # Make output writeable
1204 os.chmod(tgt, 493) # 0755
1209 kdll = ctypes.windll.LoadLibrary("kernel32.dll")
1210 res = kdll.CreateSymbolicLinkA(tgt, src, 0)
1212 self.generator.bld.fatal("File Link Error (%s -> %s( (%s)" % (src, tgt, sys.exc_info()[0]))
1216 def runnable_status(self):
1217 if super(create_symbol_link, self).runnable_status() == -1:
1220 src = self.inputs[0].abspath()
1221 tgt = self.outputs[0].abspath()
1223 # If there any target file is missing, we have to copy
1225 stat_tgt = os.stat(tgt)
1229 # Now compare both file stats
1231 stat_src = os.stat(src)
1235 # same size and identical timestamps -> make no copy
1236 if stat_src.st_mtime >= stat_tgt.st_mtime + 2:
1239 # Everything fine, we can skip this task
1243 @feature('cxx', 'c', 'cprogram', 'cxxprogram', 'cshlib', 'cxxshlib', 'cstlib', 'cxxstlib')
1244 @after_method('apply_link')
1245 def add_compiler_dependency(self):
1246 """ Helper function to ensure each compile task depends on the compiler """
1247 if self.env['PLATFORM'] == 'project_generator':
1250 # Create nodes for compiler and linker
1251 c_compiler_node = self.bld.root.make_node( self.env['CC'] )
1252 cxx_compiler_node = self.bld.root.make_node( self.env['CXX'] )
1253 linker_node = self.bld.root.make_node( self.env['LINK'] )
1255 # Let all compile tasks depend on the compiler
1256 for t in getattr(self, 'compiled_tasks', []):
1257 if os.path.isabs( self.env['CC'] ):
1258 t.dep_nodes.append(c_compiler_node)
1259 if os.path.isabs( self.env['CXX'] ):
1260 t.dep_nodes.append(cxx_compiler_node)
1262 # Let all link tasks depend on the linker
1263 if getattr(self, 'link_task', None):
1264 if os.path.isabs( self.env['LINK'] ):
1265 self.link_task.dep_nodes.append(linker_node)
1267 ###############################################################################
1268 def show_option_dialog(ctx):
1269 ctx.gui_modify_user_options()
1271 ###############################################################################
1272 class execute_waf_options_dialog(BuildContext):
1273 ''' Util class to execute waf options dialog '''
1274 cmd = 'show_option_dialog'
1275 fun = 'show_option_dialog'
1279 # "feature 'java' does not exist - bind at least one method to it"
1280 # the java feature is added if waf detects a single 'java' file.. which is used in android
1283 def dummy_func(ctx):