!I integrate from //ce/main...
[CRYENGINE.git] / wscript
blobb07cacf29acd638b3d138605dbd2c9355e37ba33
1 # Copyright 2001-2016 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
12 import os
13 import shutil
14 import ConfigParser  
15 import traceback
16 import time
17 import subprocess
18 import sys
20 try:
21         import _winreg
22 except:
23         pass
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'
36         
37 ############################################################################### 
38 # List of subfolders to parse   
39 SUBFOLDERS =    [
40         'Code',
41         'Engine',
42         ]
44 ###############################################################################
45 ## Configure Options for WAF    
46 def options(opt):
47         opt.load('cry_utils' ,tooldir=CRY_WAF_TOOL_DIR) 
48         
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)
52         
53         ###########################################
54         # Load support for Uber Files
55         opt.load('generate_uber_files' ,tooldir=CRY_WAF_TOOL_DIR)       
56                 
57         ###########################################
58         # Load Project Generators based on host (use the custom cry versions)
59         host = Utils.unversioned_sys_platform()
60         
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)
64         
65         if host == 'darwin':
66                 opt.load('xcode' ,tooldir=CRY_WAF_TOOL_DIR)
67         if host == 'linux':
68                 opt.load('eclipse' ,tooldir=CRY_WAF_TOOL_DIR)
69                 
70         # Load tools to improve dependency checking (by using compiler features)
71         if host == 'win32':
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)
75         
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)
81                 
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)
87                         
88         ###########################################
89         # Add custom cryengine options  
90                         
91         opt.add_option('-p', '--project-spec', dest='project_spec', action='store', default='', help='Spec to use when building the project')
92         
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)') 
103         
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)')       
109         
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)
113         
114 ###############################################################################
115 ## Configure 'configure' step
116 def configure(conf):
117         ###########################################
118         # Load base configuration function              
119         conf.load('c_config')   
120         
121         sys_platform = Utils.unversioned_sys_platform()
122         if sys_platform == 'linux':
123                 host = 'linux_x64'
124         elif sys_platform== 'win32':
125                 host = 'win_x64'
126         elif sys_platform== 'darwin':
127                 host = 'darwin_x64'             
128         
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)                               
136                                 else:
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.' )
138                         else:
139                                 try:
140                                         subprocess.call(["python3","download_sdks.py"])
141                                 except:
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()
147         
148         ###########################################
149         # Load common windows settings if needed
150         conf.load('winres', tooldir=CRY_WAF_TOOL_DIR)
151         
152         # Load common platform scripts  
153         conf.load('compile_settings_cryengine', tooldir=CRY_WAF_TOOL_DIR)       
154         
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)
158         
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_x86', tooldir=CRY_WAF_TOOL_DIR)
162         conf.load('compile_settings_linux_x64', tooldir=CRY_WAF_TOOL_DIR)
163         conf.load('compile_settings_darwin', tooldir=CRY_WAF_TOOL_DIR)
164         
165         optional_platforms = [
166         'durango',
167         'orbis'         
168         ]       
169         
170         for platform in optional_platforms:     
171                 file_name = 'compile_settings_' + platform
172                 if os.path.isfile(os.path.join(conf.path.abspath(), CRY_WAF_TOOL_DIR, file_name + '.py')):
173                         conf.load(file_name, tooldir=CRY_WAF_TOOL_DIR)
174                 else:
175                         try:
176                                 conf.get_supported_platforms().remove(platform)
177                         except:
178                                 pass
179                         Logs.warn('[WARNING] - Compiler settings not found. (%s) ' % file_name )
181         # Load CppCheck since it is a 'global' platform
182         conf.load('cppcheck', tooldir=CRY_WAF_TOOL_DIR)
183         
184         # Load QT last as we need to hook the runnable status at the lowest level
185         conf.load('qt' ,tooldir=CRY_WAF_TOOL_DIR)
187         ###########################################
188         # Load support for c# and swig
189         conf.load('swig', tooldir=CRY_WAF_TOOL_DIR)
190         conf.load('protoc', tooldir=CRY_WAF_TOOL_DIR)
191         conf.load('cs', tooldir=CRY_WAF_TOOL_DIR)
192                 
193         #Get user defined active specs
194         specs_platforms = set() 
195         if  conf.options.specs_to_include_in_project_generation: 
196                 spec_list = conf.options.specs_to_include_in_project_generation.replace(' ', '').split(',')             
197         else:  # Special case for Buildbot which sets no spec for specs_to_include_in_project_generation.  
198                 spec_list = conf.loaded_specs() 
199                                 
200         # Get the platform list of all active specs
201         tmp_specs_platforms = set()
202         for spec in spec_list:
203                 for platform in conf.get_supported_platforms():
204                         tmp_specs_platforms.update(conf.spec_valid_platforms(spec, platform))
205                                 
206         # Convert to complete platform names i.e. win -> win_x64 and win_x86
207         for valid_platform in tmp_specs_platforms:
208                 for value in conf.get_platform_permutation_map(valid_platform).values():
209                         specs_platforms.update(value)
210                                 
211         ###########################################
212         # Load settings for all platforms
213         platforms_done = ''
214         installed_platforms = []
215         for platform in conf.get_supported_platforms():
216         
217                 # Check if platform is required for active specs
218                 if platform not in specs_platforms: 
219                         continue
221                 Logs.info('[INFO] Configure "%s - [%s]"' % (platform, ', '.join(conf.get_supported_configurations())))
222                 
223                 # Load Platform Configuration (except cppcheck as it is not a *real* compiler)
224                 if platform != 'cppcheck':
225                         file_name = 'compile_rules_' + host + '_' + platform
226                         if os.path.isfile(os.path.join(conf.path.abspath(), CRY_WAF_TOOL_DIR, file_name + '.py')):
227                                 conf.load('compile_rules_' + host + '_' + platform, tooldir=CRY_WAF_TOOL_DIR)
228                                 
229                                 # Use the specialized function to load platform specifics
230                                 function_name = 'check_%s_%s_installed' % ( host, platform )
231                                 if not hasattr(conf, function_name):
232                                         conf.fatal('[ERROR] Required function \'%s\' not found' % function_name )
234                                 # Try to load the function                      
235                                 if not getattr(conf, function_name)():
236                                         Logs.warn('[WARNING] - Unable to locate platform "%s" on system. Disabling platform support for: %s !!!' % (platform, platform))
237                                         continue
238                         else:
239                                 Logs.warn('[WARNING] - Unable to locate compiler rules  "%s". Disabling platform support for: %s !!!. ' % (file_name, platform))
240                                 continue
241                 
242                 # platform installed
243                 installed_platforms.append(platform)
244                 
245                 for configuration in conf.get_supported_configurations():
246                         conf.setenv(platform + '_' + configuration) # change env to configuration env
247                         conf.init_compiler_settings()
248                         
249                         # Use the specialized function to load platform specifics
250                         function_name = 'load_%s_%s_%s_settings' % ( configuration, host, platform )
251                         if not hasattr(conf, function_name):
252                                 conf.fatal('[ERROR] Required function \'%s\' not found' % function_name )
254                         # Try to load the function                      
255                         getattr(conf, function_name)()
256                         
257                         conf.configure_qt()                     
258                         conf.configure_protoc()
260                         # Load swig and mono
261                         conf.configure_swig()
262                         conf.configure_mono()
263                         conf.env['build_relevant_platforms'] = installed_platforms
264                         conf.setenv('') # change env back to global env
266         conf.set_supported_platforms(installed_platforms)
267         conf.env['build_relevant_platforms'] = installed_platforms # need to store in global cache as well for  project_generators
269         ###########################################     
270         # Load support for c and cxx compiler
271         conf.load('c')
272         conf.load('cxx')
273                 
274         conf.load('protoc',tooldir=CRY_WAF_TOOL_DIR)
275         
276         ###########################################
277         # Load Platform specific helpers based on host
278         host = Utils.unversioned_sys_platform() 
279         if host == 'darwin':
280                 conf.load('c_osx')
281         
282         ###########################################
283         # Create VS-Projects automatically during configure when running on windows
284         if host == 'win32' and conf.is_option_true('generate_vs_projects_automatically'):
285                 # Workflow improvement: for all builds generate projects after the build
286                 # except when using the default build target 'utilities' then do it before
287                 if 'build' in Options.commands:
288                         build_cmd_idx = Options.commands.index('build')         
289                         Options.commands.insert(build_cmd_idx, 'msvs') 
290                 else:
291                         Options.commands.append('msvs')
292          
293         ###########################################
294         # Load remaining tools for correct auto configure
295         if host == 'win32':
296                 conf.load('recode', tooldir=CRY_WAF_TOOL_DIR)   
297                 conf.load('static_code_analyzer', tooldir=CRY_WAF_TOOL_DIR)
298         
299         ###########################################
300         # Recurse into subfolders for auto conf when any wscript changes
301         conf.recurse(dirs=SUBFOLDERS, mandatory=False)
302                 
303         # Load Game Specific parts      
304         for project in conf.game_projects():
305                 conf.game_project = project
306                 conf.recurse( conf.game_code_folder(project))
308         ###########################################
309         # Always update uber files during configuration
310         # But don't add the same command twice
311         if len(Options.commands) == 0:
312                 Options.commands.insert(0, 'generate_uber_files')
313         elif not Options.commands[0] == 'generate_uber_files':
314                 Options.commands.insert(0, 'generate_uber_files')
316         # Remove timestamp files to force builds of generate_uber_files and project gen even if
317         # some command after configure failes           
318         try:
319                 generate_uber_files_timestamp = conf.get_bintemp_folder_node().make_node('generate_uber_files.timestamp')       
320                 os.stat(generate_uber_files_timestamp.abspath())
321         except OSError: 
322                 pass
323         else:
324                 generate_uber_files_timestamp.delete()
325         
326         try:
327                 project_gen_timestamp = conf.get_bintemp_folder_node().make_node('project_gen.timestamp')
328                 os.stat(project_gen_timestamp.abspath())
329         except OSError: 
330                 pass
331         else:
332                 project_gen_timestamp.delete()
334 def post_command_exec(bld):     
335         # [post project gen]
336         if bld.cmd == 'msvs':
337                 project_gen_timestamp = bld.get_bintemp_folder_node().make_node('project_gen.timestamp')
338                 project_gen_timestamp.write('')
339         # [post uberfile gen]
340         elif bld.cmd == 'generate_uber_files':
341                 generate_uber_files_timestamp = bld.get_bintemp_folder_node().make_node('generate_uber_files.timestamp')
342                 generate_uber_files_timestamp.write('')
343         # [post build]
344         elif bld.cmd.startswith('build'):
345                 for message in bld.post_build_msg_info:
346                         Logs.info(message)
347                         
348                 for message in bld.post_build_msg_warning:
349                         Logs.warn(message)
350                         
351                 for message in bld.post_build_msg_error:
352                         Logs.error(message)
353                         
354 stored_file_filter = ''
355 stored_output_file = ''
357 ###############################################################################
358 ## Run 'build' step     
359 def build(bld):
361         # Keep backward compatibility
362         if bld.options.project_spec == 'everything':    
363                 bld.options.project_spec = 'trybuilder'
364                 
365         bld.options.project_spec = bld.options.project_spec.strip() # remove spaces 
366         
367         # Create a post build message container
368         bld.post_build_msg_info = []
369         bld.post_build_msg_warning = []
370         bld.post_build_msg_error = []   
371         
372         # Add groups to ensure all task generators who create static lib tasks are executd first
373         bld.add_group('generate_static_lib')
374         bld.add_group('regular_group')
375         # Set back to our regular compile group
376         bld.set_group('regular_group')
377         
378         
379         ###########################################
380         # Load common windows settings if needed
381         bld.load('winres', tooldir=CRY_WAF_TOOL_DIR)
382                 
383         # Check if a valid spec is defined      
384         if bld.cmd != 'generate_uber_files' and bld.cmd != 'msvs' and bld.cmd != 'eclipse':
385                 # project spec is a mandatory parameter
386                 if bld.options.project_spec == '':
387                         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())
388                 
389                 # Ensure that the selected spec is supported on this platform
390                 if not bld.options.project_spec in bld.loaded_specs():
391                         bld.fatal('[ERROR] Invalid Project Spec (%s) defined, valid specs are %s' % (bld.options.project_spec, bld.loaded_specs()))
392         
393                 # Check for valid single file compilation flag pairs
394                 if bld.is_option_true('show_preprocessed_file') and bld.options.file_filter == "":  
395                         bld.fatal('--show-preprocessed-file can only be used in conjunction with --file-filter')
396                 elif bld.is_option_true('show_disassembly') and bld.options.file_filter == "":  
397                         bld.fatal('--show-disassembly can only be used in conjunction with --file-filter')    
398                                         
399         ###########################################
400         # Check timestamps for special commands         
401         if not 'generate_uber_files' in Options.commands and bld.cmd != 'generate_uber_files':
402                 generate_uber_files_timestamp = bld.get_bintemp_folder_node().make_node('generate_uber_files.timestamp')
403                 try:
404                         os.stat(generate_uber_files_timestamp.abspath())
405                 except OSError: 
406                         # No generate_uber file timestamp, restart command chain, prefixed with gen uber files cmd
407                         Options.commands = ['generate_uber_files'] + [bld.cmd] + Options.commands       
408                         return
409         
410         if not 'msvs' in Options.commands and bld.cmd != 'msvs':
411                 project_gen_timestamp = bld.get_bintemp_folder_node().make_node('project_gen.timestamp')
412                 try:
413                         os.stat(project_gen_timestamp.abspath())
414                 except OSError: 
415                         # No project_gen timestamp, append project_gen to commands
416                         if bld.is_option_true('generate_vs_projects_automatically') and  Utils.unversioned_sys_platform() == 'win32':
417                                 if not bld.is_option_true('internal_dont_check_recursive_execution'):
418                                         Options.commands = Options.commands + ['msvs']
419                                         
420         ###########################################
421         # Check for valid variant if we are not generating projects     
422         if bld.cmd == 'xcode' or bld.cmd == 'msvs' or bld.cmd == 'eclipse' or bld.cmd == 'generate_uber_files':
423                 bld.env['PLATFORM'] = 'project_generator'
424                 bld.env['CONFIGURATION'] = 'project_generator'
425         else:
426                 if not bld.variant:
427                         bld.fatal('please use a valid build configuration, try "waf --help"')                   
428                 
429                 (platform, configuration) = bld.get_platform_and_configuration()                        
430                 bld.env['PLATFORM']                     = platform
431                 bld.env['CONFIGURATION']        = configuration
432                 
433                 if  not platform in bld.get_supported_platforms():
434                         bld.fatal( '[ERROR] - Target platform "%s" not supported. [on host platform: %s]' % (platform, Utils.unversioned_sys_platform()) )
435                 
436                 # When building, only support platform that we are currently building for to reduce internal workload
437                 bld.set_supported_platforms([bld.env['PLATFORM']])
438                 
439                 # check if configuration is valid in the current spec scope
440                 bVariantValid = False
441                 for conf in bld.spec_valid_configurations():
442                         if conf == configuration:
443                                 bVariantValid = True
444                 if not bVariantValid:
445                         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())))
446                 
447                 # check if platform is valid in the current spec scope
448                 bVariantValid = False
449                 for p0 in bld.spec_valid_platforms():
450                         for p1 in bld.get_platform_list(bld.env['PLATFORM']):
451                                 if p0 == p1:
452                                         bVariantValid = True
453                 if not bVariantValid:
454                         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())))
456                 # Ensure that, if specified, target is supported in this spec
457                 if bld.options.targets:
458                         for target in bld.options.targets.split(','):
459                                 if not target in  bld.spec_modules():
460                                         bld.fatal('[ERROR] Module "%s" is not configurated to be build in spec "%s" in "%s|%s"' % (target, bld.options.project_spec, platform, configuration))
461                                         
462         ###########################################
463         # Setup command coordinator
464         bld.load('cmd_coordinator', tooldir=CRY_WAF_TOOL_DIR)
465         bld.setup_command_coordinator()
466          
467         # We might run multiple instances of WAF at the same time
468         # 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
469         # 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)
470         if not bld.instance_is_build_master():
471                 return
473         ###########################################
474         bld.add_post_fun(post_command_exec)
475         
476         ###########################################
477         # Setup Output Path for Project Geneators
478         bld.solution_name = bld.get_solution_name()
479         bld.projects_dir = bld.get_project_output_folder()      
480                                         
481         ###########################################
482         # Load configuration overwrites
483         bld.env['CONFIG_OVERWRITES'] = bld.get_solution_overrides()
485         ###########################################
486         # Disable optimizations if requested
487         # Must be done after computing overrides
488         if bld.is_option_true('force_deoptimized_builds'):
489                 bld.env['CFLAGS'] += bld.env['COMPILER_FLAGS_DisableOptimization']
490                 bld.env['CXXFLAGS'] += bld.env['COMPILER_FLAGS_DisableOptimization']
491                 
492         ###########################################
493         # Load Support for Recode if required
494         host = Utils.unversioned_sys_platform() 
495         if host == 'win32':
496                 bld.load('recode', tooldir=CRY_WAF_TOOL_DIR)                    
497                 if str(bld.options.support_recode) == str(True):
498                         bld.enable_recode = bld.check_global_recode_enabled()
499                         
500         ###########################################
501         # Add support for static code analyzer
502         bld.load('static_code_analyzer', tooldir=CRY_WAF_TOOL_DIR)              
503                 
504         # Clear <output folder>/Image/Loose on durango as a workaround for buggy deploy
505         if bld.env['PLATFORM'] == 'durango':
506                 for node in bld.get_output_folders(bld.env['PLATFORM'], bld.env['CONFIGURATION']):
507                         image_lost_dir = node.find_dir('Image/Loose')
508                         if image_lost_dir:
509                                 files = image_lost_dir.ant_glob('**/*')
510                                 for f in files:
511                                         f.chmod(493) # 0755
512                                         f.delete()
514         # Load Core Engine Parts (Engine, Tools, Core Shaders etc)
515         bld.game_project = None
516         bld.recurse(SUBFOLDERS, mandatory=False)        
517         
518         # Load Game Specific parts      
519         for project in bld.game_projects():
520                 bld.game_project = project
521                 bld.recurse( bld.game_code_folder(project))
522                 
523 ###############################################################################
524 @conf
525 def target_clean(self):
526         
527         tmp_targets = self.options.targets[:]
528         to_delete = []
529         # Sort of recursive algorithm, find all outputs of targets
530         # Repeat if new targets were added due to use directives
531         while len(tmp_targets) > 0:
532                 new_targets = []
533                 
534                 for tgen in self.get_all_task_gen():                            
535                         tgen.post()
536                         if not tgen.target in tmp_targets:
537                                 continue
538                 
539                         for t in tgen.tasks:
540                                 # Collect outputs
541                                 for n in t.outputs:
542                                         if n.is_child_of(self.bldnode):
543                                                 to_delete.append(n)
544                         # Check for use flag
545                         if hasattr(tgen, 'use'):                        
546                                 new_targets.append(tgen.use)
547                 # Copy new targets
548                 tmp_targets = new_targets[:]
549                 
550         # Final File list to delete
551         to_delete = list(set(to_delete))
552         for file in to_delete:
553                 file.delete()
554         
555                 
556 ###############################################################################
557 # Copy pasted from MSVS..
558 def convert_vs_configuration_to_waf_configuration(configuration):
559         if 'Debug' in configuration:
560                 return 'debug'
561         if 'Profile' in configuration:
562                 return 'profile'
563         if 'Release' in configuration:
564                 return 'release'
565         if 'Performance' in configuration:
566                 return 'performance'
567                         
568         return ''
569                 
570 ###############################################################################         
571 @conf
572 def utilities(bld):             
573         if not bld.is_option_true('ask_for_user_input'):        
574                 return
575                 
576         # Function to handle creating of new libraries
577         def install_waf_addin():
578                 enviroment = os.environ.copy()          
579                 if not 'APPDATA' in enviroment:
580                         print 'APPDATA enviroment variable not found, WAF cannot figure out where to install the addin'
581                         return
582                 
583                 install_path = enviroment['APPDATA'] + '\\Microsoft\\MSEnvShared\\Addins'
584                 source_path = bld.path.abspath() + '\\Code\\Tools\\waf_addin\\Bin_Release'
585                 
586                 Logs.info('WAF Addin will be installed into:\n%s' % install_path)               
587                 bld.start_msg('Create Install directory')
588                 try:
589                         if not os.path.exists(install_path):
590                                 os.makedirs(install_path)
591                 except:
592                         bld.end_msg('failed (%s)' % sys.exc_info()[1], color='RED')
593                 else:
594                         bld.end_msg('ok')
595                         
596                 for file in ['WAF_Addin.AddIn', 'WAF_Addin.dll', 'WAF_Addin.xml']:
597                         bld.start_msg('Copy %s to install directory' % file)
598                         # Make output writeable
599                         try:
600                                 os.chmod(install_path + '\\' + file, 493) # 0755
601                         except:
602                                 pass
603                                 
604                         try:
605                                 shutil.copy2(source_path + '\\' + file, install_path + '\\' + file)
606                         except:
607                                 bld.end_msg('failed (%s)' % sys.exc_info()[1], color='RED')
608                         else:
609                                 bld.end_msg('ok')
610                 Options.commands.insert(0, 'utilities')
611                 
612         def reconfigure_waf():
613                 Options.commands.insert(0, 'configure')
614                 
615         def bootstrap_update():
616                 bld.ExecuteBootstrap()
617                 Options.commands.insert(0, 'utilities')
618         
619         def regenerate_vs_solution():
620                 Options.commands.insert(0, 'utilities')
621                 Options.commands.insert(0, 'msvs')
622         
623         def regenerate_uber_files():
624                 Options.commands.insert(0, 'utilities')
625                 Options.commands.insert(0, 'generate_uber_files')
626                                 
627         def regenerate_config_file():
628                 print type(bld)
629                 # Remove current config file and remove some state
630                 config_file = bld.get_user_settings_node()
631                 config_file.delete()
633                 # Load user settings            
634                 bld.load_user_settings()
635                 
636                 # Afterwards return to utilies dialog
637                 Options.commands.insert(0, 'utilities')
638                 
639         def apply_new_crycommon_layout_to_custom_folder():
640                  folder = raw_input('Please enter absolute folder path: ')
641                  if os.path.isdir(folder):
642                         bld.convert_files_in_folder_to_cry_common([bld.root.make_node(folder)])
643                  else:
644                         Logs.error("Folder could not be found.", folder)                        
645                  Options.commands.insert(0, 'utilities')                
646                                                 
647         def waf_options():                              
648                 Options.commands.insert(0, 'utilities')
649                 Options.commands.insert(0, 'show_option_dialog')
650                 
651         # Function to handle exit requests
652         def exit():
653                 pass
654                                 
655         if not bld.is_option_true('console_mode'):
656                 conversion_file = ['Apply new CryCommon layout', [ 
657                         ('Custom folder and subfolders', apply_new_crycommon_layout_to_custom_folder) 
658                 ]]              
659                 
660                 menu_file = ['', [              
661                         ('Exit', exit) 
662                 ]]
663                 
664                 menu_regenerate = ['Generate', [ 
665                         ("Visual Studio Solution", regenerate_vs_solution),
666                         ("Uber Files", regenerate_uber_files)
667                  ]]
668                 
669                 menu_tools = ['Tools', [
670                         ("Options", waf_options),
671                         ("Configure", reconfigure_waf)
672                 ]]
673                 
674                 if bld.is_bootstrap_available():
675                         menu_tools[1] += [("Run Bootstrap", bootstrap_update)]
676                         
677                 #menu_tools[1] += [("Install WAF Addin", install_waf_addin)] # disabled as unmaintained         
678                 fn_result = bld.gui_get_waf_menu_choice([menu_tools, menu_regenerate,  conversion_file, menu_file])
679                 
680                 
681                 if fn_result:
682                         fn_result()      # Execute result
683                 else:
684                         Options.commands.insert(0, 'utilities')
685                         
686         else:                   
687                 choices = [ 
688                     (exit, 'Exit'),
689                                 #(install_waf_addin,Install WAF Addin), # disabled as unmaintained
690                                 (regenerate_vs_solution, 'Regenerate Visual Studio Solution'),                          
691                                 (regenerate_uber_files, 'Regenerate Uber Files'),
692                                 (regenerate_config_file, 'Regenerate Config File')
693                         ]
694                         
695                 if bld.is_bootstrap_available():
696                         choices += [(bootstrap_update, 'Run Bootstrap Update')]
697                         
698                 print(' === Crytek WAF Build System === ')
699                 print(' Please use waf --help to see all valid build targets and build options')
700                 
701                 for idx, option in enumerate(choices):
702                         print('%s: %s' % (idx, option[1]))
703                 
704                 user_input = raw_input('Please select an option: ')
705                 print('')
706                 # Sanity check for valid user inputs
707                 try:                                    
708                         if int(user_input) < int(0) or int(user_input) > len(choices):
709                                 raise                   
710                 except:
711                         print('Invalid Input, please choose a value between 1 and %s', len(choices))    
712                         Options.commands.insert(0, 'utilities')
713                         return
714                 
715                 # Input is fine, exectue selection
716                 if int(user_input) == 1:
717                         return
718                 
719                 print type(bld)
720                 choices[int(user_input)][0]()
722 ##############################################################################
723 class execute_utilities(BuildContext):
724         ''' Util class to execute waf utilities dialog  '''
725         cmd = 'utilities'
726         fun = 'utilities'
727         
728 ###############################################################################
729 # Create Build Context Commands for multiple platforms/configurations
730 for platform in PLATFORMS[Utils.unversioned_sys_platform()]:
731         for configuration in CONFIGURATIONS:
732                 # Create new class to execute build with variant
733                 name = CleanContext.__name__.replace('Context','').lower()
734                 class tmp_clean(CleanContext):
735                         cmd = name + '_' + platform + '_' + configuration
736                         variant = platform + '_' + configuration
737                         
738                         def __init__(self, **kw):
739                                 super(CleanContext, self).__init__(**kw)
740                                 self.top_dir = kw.get('top_dir', Context.top_dir)
741                                 
742                         def execute(self):
743                                 if Configure.autoconfig:
744                                         env = ConfigSet.ConfigSet()
745                                         
746                                         do_config = False
747                                         try:
748                                                 env.load(os.path.join(Context.lock_dir, Options.lockfile))
749                                         except Exception:
750                                                 Logs.warn('Configuring the project')
751                                                 do_config = True
752                                         else:
753                                                 if env.run_dir != Context.run_dir:
754                                                         do_config = True
755                                                 else:
756                                                         h = 0
757                                                         for f in env['files']:
758                                                                 try:                                            
759                                                                         h = hash((h, Utils.readf(f, 'rb')))
760                                                                 except (IOError, EOFError):
761                                                                         pass # ignore missing files (will cause a rerun cause of the changed hash)                                                                                                                      
762                                                         do_config = h != env.hash
764                                         if do_config:
765                                                 Options.commands.insert(0, self.cmd)
766                                                 Options.commands.insert(0, 'configure')
767                                                 return
769                                 # Execute custom clear command
770                                 self.restore()
771                                 if not self.all_envs:
772                                         self.load_envs()
773                                 self.recurse([self.run_dir])
774         
775                                 if self.options.targets:
776                                         self.target_clean()
777                                 else:
778                                         try:
779                                                 self.clean()
780                                         finally:
781                                                 self.store()
782                                 
783                         
784                 # Create new class to execute clean with variant
785                 name = BuildContext.__name__.replace('Context','').lower()
786                 class tmp_build(BuildContext):
787                         cmd = name + '_' + platform + '_' + configuration
788                         variant = platform + '_' + configuration                                
789         
790 ###############################################################################
791 # Install Default variant                               
792 for y in (BuildContext, CleanContext):
793         class tmp(y):
794                 variant = 'utilities'
795                 fun = 'utilities'
796                 
797 ###############################################################################
798 # Create Commands for backwards compatibility
799 if Utils.unversioned_sys_platform() == 'win32': 
800                 for configuration in CONFIGURATIONS:
801                         for context in (BuildContext, CleanContext):
802                                 name = context.__name__.replace('Context','').lower()
803                                 class tmp(context):
804                                         cmd = name + '_win32_' + configuration
805                                         variant = 'win_x86_' + configuration
806                 for configuration in CONFIGURATIONS:
807                         for context in (BuildContext, CleanContext):
808                                 name = context.__name__.replace('Context','').lower()
809                                 class tmp(context):
810                                         cmd = name + '_win64_' + configuration
811                                         variant = 'win_x64_' + configuration
814 @taskgen_method
815 def copy_files(self, source_file, output_folders = None):
817         if not output_folders:
818                 output_folders = self.bld.get_output_folders(self.bld.env['PLATFORM'], self.bld.env['CONFIGURATION'])
820         for node in output_folders:
821                 output_node = node
822                 if hasattr(self, 'output_sub_folder'):
823                         _output_sub_folder = str(self.output_sub_folder)
824                         if os.path.isabs(_output_sub_folder):
825                                 output_node = self.bld.root.make_node(_output_sub_folder)
826                         else:
827                                 output_node = output_node.make_node(_output_sub_folder)
828                 output_node = output_node.make_node( os.path.basename(source_file.abspath()) )
830                 path = os.path.dirname( output_node.abspath() )
831                 if not os.path.exists( path ):
832                         os.makedirs( path )
834                 self.create_task('copy_outputs', source_file, output_node) 
835         
836 ###############################################################################
837 # Class to handle copying of the final outputs into the Bin folder
838 class copy_outputs(Task):
839         color = 'YELLOW'
840         
841         def run(self):
842                 src = self.inputs[0].abspath()
843                 tgt = self.outputs[0].abspath()
844                 
845                 # Create output folder
846                 if not os.path.exists( os.path.dirname( tgt ) ):
847                         try:
848                                 os.makedirs( os.path.dirname( tgt ) )
849                         except:
850                                 pass # Some other thread must have created the folder in the meantime
851                         
852                 def _copy_file(src, tgt, recursion_count = 0):
853                         # Make output writeable
854                         try:
855                                 os.chmod(tgt, 493) # 0755
856                         except:
857                                 pass
858                                 
859                         try:
860                                 shutil.copy2(src, tgt)
861                         except (IOError, os.error) as why:
862                         
863                                 # Try again
864                                 if recursion_count < 2:
865                                         time.sleep(1)
866                                         recursion_count += 1
867                                         return _copy_file(src, tgt, recursion_count)
868                                         
869                                 self.err_msg = "[Error] %s\n[Error] Could not perform copy %s -> %s" % (str(why), src, tgt)
870                                 return -1
871                         except:
872                                 # Try again
873                                 if recursion_count < 2:
874                                         time.sleep(1)
875                                         recursion_count += 1
876                                         return _copy_file(src, tgt, recursion_count)                                    
877                                 self.err_msg = "[Error] Could not perform copy %s -> %s" % (src, tgt)
878                                 return -1
879                                 
880                         return 0
882                 # Copy file
883                 return _copy_file(src, tgt)
884                 
885         def runnable_status(self):
886                 if super(copy_outputs, self).runnable_status() == -1:
887                         return -1
888                 
889                 src = self.inputs[0].abspath()
890                 tgt = self.outputs[0].abspath()
892                 # If there any target file is missing, we have to copy
893                 try:
894                         stat_tgt = os.stat(tgt)
895                 except OSError: 
896                         return RUN_ME
897                 
898                 # Now compare both file stats
899                 try:
900                         stat_src = os.stat(src)                         
901                 except OSError:
902                         pass
903                 else:
904                         # same size and identical timestamps -> make no copy
905                         if stat_src.st_mtime >= stat_tgt.st_mtime + 2 or stat_src.st_size != stat_tgt.st_size:                  
906                                 return RUN_ME
907                                 
908                 # Everything fine, we can skip this task                
909                 return -2 # SKIP_ME
910                 
911         
912 ###############################################################################
913 # Function to generate the copy tasks for build outputs 
914 @after_method('set_pdb_flags')
915 @after_method('apply_incpaths')
916 @after_method('apply_map_file')
917 @feature('c', 'cxx')
918 def add_install_copy(self, output_folders = None):
919         if self.bld.cmd == "msvs" or 'android' in self.bld.env['PLATFORM']:
920                 return
922         if not getattr(self, 'link_task', None):
923                 return
925         if self._type == 'stlib': # Do not copy static libs
926                 return
927                 
928         for src in self.link_task.outputs:              
929                 self.copy_files(src, output_folders)
931         
932 ###############################################################################
933 # Function to generate the EXE_VERSION_INFO defines
934 @after_method('apply_incpaths')
935 @feature('c', 'cxx')
936 def apply_version_info(self):
937         version = str( self.bld.get_product_version() )
939         version_parts = version.split('.')
940         if len(version_parts) < 3:
941                 Logs.warn('Invalid build version (%s), falling back to (1.0.0.1)' % version )                                           
942                 version_parts = ['1', '0', '0', '1']
944         version_parts[0] = version_parts[0].strip()
945         version_parts[1] = version_parts[1].strip()
946         version_parts[2] = version_parts[2].strip()
947         version_parts[3] = version_parts[3].strip()
949         for t in getattr(self, 'compiled_tasks', []):
950                 t.env.append_value('DEFINES', 'EXE_VERSION_INFO_0=' + version_parts[0])
951                 t.env.append_value('DEFINES', 'EXE_VERSION_INFO_1=' + version_parts[1])
952                 t.env.append_value('DEFINES', 'EXE_VERSION_INFO_2=' + version_parts[2])
953                 t.env.append_value('DEFINES', 'EXE_VERSION_INFO_3=' + version_parts[3])
955 ############################################################################### 
956 def wrap_execute(execute_method):
957         """
958         Decorator used to set the commands that can be configured automatically
959         """
960         def execute(self):              
961                 # Make sure to create all needed temp folders
962                 bin_temp = self.get_bintemp_folder_node()
963                 bin_temp.mkdir()
964                 tmp_files_folder = bin_temp.make_node('TempFiles')
965                 tmp_files_folder.mkdir()
966                 
967                 # Before executing any build or configure commands, check for config file
968                 # and for bootstrap updates
969                 self.load_user_settings()
970                 check_for_bootstrap(self)
971         
972                 return execute_method(self)
973                         
974         return execute
975         
976 BuildContext.execute = wrap_execute(BuildContext.execute)
977 ConfigurationContext.execute = wrap_execute(ConfigurationContext.execute)
979 ############################################################################### 
980 def check_for_bootstrap(bld):
981         global g_bootstrap_was_run
982         if g_bootstrap_was_run:
983                 return
984         g_bootstrap_was_run = True      
985         
986         if not (bld.is_bootstrap_available() and bld.is_option_true('auto_run_bootstrap')):
987                 return # Skip auto bootstrapping
988                 
989         bootstrap_dat = bld.path.make_node('bootstrap.dat')
990         bootstrap_timestamp = bld.get_bintemp_folder_node().make_node('bootstrap.timestamp')
991         
992         # Check for bootstrap.dat
993         try:
994                 stat_bootstrap_dat = os.stat(bootstrap_dat.abspath())
995         except OSError: 
996                 bld.fatal('bootstrap.dat file not found')
997         
998         # Check for bootstrap.timestamp, run bootstrap is it doesn't exist
999         try:
1000                 stat_bootstrap_timestamp = os.stat(bootstrap_timestamp.abspath())
1001                 if stat_bootstrap_dat.st_mtime < stat_bootstrap_timestamp.st_mtime + 2:
1002                         return # No need to bootstrap
1003         except OSError:                         
1004                 pass
1005                 
1006         bld.ExecuteBootstrap()
1007         
1008         # Create TimeStamp File
1009         bootstrap_timestamp.write('')
1011 ############################################################################### 
1012 @conf
1013 def get_output_folders(self, platform, configuration, target_spec = None, game_project = None):
1014         """
1015         Specifies the final binary output folder depending on the current setup
1016         <root>/<out_folder_xxx><output_folder_post_fix><output_extension_xxx>/<output_sub_folder>
1017         """
1018         path = ""
1019                 
1020         # Add <out_folder_xxx> part
1021         if platform == 'win_x86':
1022                 path += self.options.out_folder_win32           
1023         elif platform == 'win_x64':
1024                 path += self.options.out_folder_win64           
1025         elif platform == 'durango':             
1026                 path += self.options.out_folder_durango
1027         elif platform == 'orbis':
1028                 path += self.options.out_folder_orbis   
1029         elif platform == 'linux_x86_gcc':
1030                 path += self.options.out_folder_linux32_gcc
1031         elif platform == 'linux_x86_clang':
1032                 path += self.options.out_folder_linux32_clang   
1033         elif platform == 'linux_x64_gcc':
1034                 path += self.options.out_folder_linux64_gcc
1035         elif platform == 'linux_x64_clang':
1036                 path += self.options.out_folder_linux64_clang
1037         elif platform == 'darwin_x86':
1038                 path += self.options.out_folder_darwin32
1039         elif platform == 'darwin_x64':
1040                 path += self.options.out_folder_darwin64
1041         elif platform == 'android_arm':
1042                 path += self.options.out_folder_android
1043         elif platform == 'android_arm64':
1044                 path += self.options.out_folder_android64
1045         else:
1046                 path += 'bin/platform_unknown'
1047                                 
1048         # Add <output_folder_post_fix> part
1049         if not target_spec: # Fall back to command line specified spec if none was given
1050                 target_spec = self.options.project_spec
1051                 
1052         if target_spec:
1053                 post_fix = self.spec_output_folder_post_fix(target_spec, platform, configuration)
1054                 if post_fix:
1055                         path += post_fix
1056                         
1057         # Add <output_extension_xxx> part
1058         if not configuration and self.env['CONFIGURATION']:
1059                 _configuration = self.env['CONFIGURATION']
1060         else:
1061                 _configuration = configuration
1063         if _configuration == 'debug':        
1064                 path += self.options.output_extension_debug             
1065         elif _configuration == 'profile':
1066                 path += self.options.output_extension_profile           
1067         elif _configuration == 'performance':
1068                 path += self.options.output_extension_performance
1069         elif _configuration == 'release':
1070                 path += self.options.output_extension_release
1071         else:
1072                 path += "_config_unknown"
1073         
1074         # Add <output_sub_folder> part
1075         if self.env['output_sub_folder']:               
1076                 path += os.sep + os.sep.join(self.env['output_sub_folder'])                     
1078         # Correct handling for absolute paths
1079         if os.path.isabs(path):
1080                 output_folder_nodes = [ self.root.make_node(path) ]
1081         else: # For relative path, prefix binary output folder with game project folder
1082                 output_folder_nodes = []
1083                 if target_spec == None:
1084                         target_spec = self.options.project_spec # Fall back to global spec
1085                 output_folder_nodes += [ self.path.make_node(path)]     
1086                 # Code below is to support multiple output folder
1087                 #for project in self.active_projects(target_spec):
1088                 #       project_folder = self.project_output_folder(project)
1089                 #       output_folder_nodes += [ self.path.make_node(project_folder).make_node(path) ]
1090                 #       output_folder_nodes += [ self.path.make_node(path)      ]
1092         return output_folder_nodes
1094         
1095 ###############################################################################
1096 @conf
1097 def is_bootstrap_available(bld):
1098         bootstrap_path = bld.path.abspath() + '/Tools/branch_bootstrap/bootstrap.exe'
1099         return os.path.isfile(bootstrap_path)
1100         
1101 ############################################################################### 
1102 @conf
1103 def ExecuteBootstrap(bld):
1105         if not is_bootstrap_available(bld):
1106                 return
1107                 
1108         host = Utils.unversioned_sys_platform()
1109         executable = None
1110         if host == 'win32':
1111                 executable = [bld.path.abspath() + '/Tools/branch_bootstrap/bootstrap.exe']
1112         elif host == 'darwin':
1113                 executable = ['python3', bld.path.abspath() + '/Tools/branch_bootstrap/bootstrap.py']
1114         elif host == 'linux':
1115                 executable = ['python3', bld.path.abspath() + '/Tools/branch_bootstrap/bootstrap.py']
1116         if executable is None:
1117                 bld.fatal('unknown host, could not determine bootstrap executable')
1118         
1119         bootstrap_dat   = bld.path.abspath() + '/bootstrap.dat'
1120         
1121         bintmp = bld.get_bintemp_folder_node()
1122         bootstrap_digest = bintmp.make_node('.bootstrap.digest.pickle')
1123                         
1124         Logs.info('Beginning Branch Bootstrap Operation (this might take a while)')
1125         
1126         ret = subprocess.call(
1127                 executable + [
1128                         '-d' + bootstrap_dat,
1129                         '-m' + bootstrap_digest.abspath(),
1130                                                 '--addrelevance=buildmachine'])
1131         if ret == 0:
1132                 bld.msg('branch bootstrap', 'done')
1133         else:
1134                 bld.msg('branch bootstrap', 'failed', color='RED')
1135                 sys.exit(1)     
1136                 
1137 from waflib.Configure import conf
1138         
1139 @conf
1140 def read_file_list(bld, file):
1141         file_node = bld.path.make_node(file)    
1143         return bld.parse_json_file(file_node)
1144         
1145 @conf
1146 def get_platform_and_configuration(bld):
1147         # Assume that the configuration begins after the last '_'
1148         platform =  "_".join( bld.variant.split("_")[:-1] )
1149         configuration = bld.variant.split("_")[-1]
1150         
1151         # Keep Backward compatibility with old names
1152         if platform == '_win32':
1153                 platform = 'win_x86'
1154         if platform == '_win64':
1155                 platform = 'win_x64'
1157         return (platform, configuration)
1159 @feature('link_to_output_folder')
1160 @after_method('process_source')
1161 def link_to_output_folder(self):
1162         """
1163         Task Generator for tasks which generate symbolic links from the source to the dest folder
1164         """
1165         return # Disabled for now
1166         
1167         if self.bld.env['PLATFORM'] == 'project_generator':
1168                 return # Dont create links during project generation
1170         if sys.getwindowsversion()[0] < 6:
1171                 self.bld.fatal('not supported')
1172                 
1173         # Compute base relative path (from <Root> to taskgen wscript
1174         relative_base_path = self.path.path_from(self.bld.path)
1175         
1176         # TODO: Need to handle absolute path here correctly
1177         spec_name = self.bld.options.project_spec                       
1178         for project in self.bld.active_projects(spec_name):
1179                 project_folder = self.bld.project_output_folder(project)
1180                 for file in self.source:
1181                         # Build output folder
1182                         relativ_file_path = file.path_from(self.path)
1183                         
1184                         output_node = self.bld.path.make_node(project_folder)
1185                         output_node = output_node.make_node(relative_base_path)
1186                         output_node = output_node.make_node(relativ_file_path)
1187                         
1188                         path = os.path.dirname( output_node.abspath() )
1189                         if not os.path.exists( path ):
1190                                 os.makedirs( path )
1191                         
1192                         self.create_task('create_symbol_link', file, output_node) 
1193         
1194 import ctypes
1195 ###############################################################################
1196 # Class to handle copying of the final outputs into the Bin folder
1197 class create_symbol_link(Task):
1198         color = 'YELLOW'
1199         
1200         def run(self):  
1201                 src = self.inputs[0].abspath()
1202                 tgt = self.outputs[0].abspath()
1203                         
1204                 # Make output writeable
1205                 try:
1206                         os.chmod(tgt, 493) # 0755
1207                 except:
1208                         pass
1209                         
1210                 try:
1211                         kdll = ctypes.windll.LoadLibrary("kernel32.dll")
1212                         res = kdll.CreateSymbolicLinkA(tgt, src, 0)
1213                 except:
1214                         self.generator.bld.fatal("File Link Error (%s -> %s( (%s)" % (src, tgt, sys.exc_info()[0]))
1215                 
1216                 return 0
1217                 
1218         def runnable_status(self):      
1219                 if super(create_symbol_link, self).runnable_status() == -1:
1220                         return -1
1221                 
1222                 src = self.inputs[0].abspath()
1223                 tgt = self.outputs[0].abspath()
1225                 # If there any target file is missing, we have to copy
1226                 try:
1227                         stat_tgt = os.stat(tgt)
1228                 except OSError:                 
1229                         return RUN_ME
1230                 
1231                 # Now compare both file stats
1232                 try:
1233                         stat_src = os.stat(src)                         
1234                 except OSError:
1235                         pass
1236                 else:
1237                         # same size and identical timestamps -> make no copy
1238                         if stat_src.st_mtime >= stat_tgt.st_mtime + 2:                  
1239                                 return RUN_ME
1240                                 
1241                 # Everything fine, we can skip this task                
1242                 return -2 # SKIP_ME     
1245 @feature('cxx', 'c', 'cprogram', 'cxxprogram', 'cshlib', 'cxxshlib', 'cstlib', 'cxxstlib')
1246 @after_method('apply_link')
1247 def add_compiler_dependency(self):
1248         """ Helper function to ensure each compile task depends on the compiler """
1249         if self.env['PLATFORM'] == 'project_generator':
1250                 return
1251                 
1252         # Create nodes for compiler and linker
1253         c_compiler_node = self.bld.root.make_node( self.env['CC'] )
1254         cxx_compiler_node = self.bld.root.make_node( self.env['CXX'] )
1255         linker_node = self.bld.root.make_node( self.env['LINK'] )
1257         # Let all compile tasks depend on the compiler
1258         for t in getattr(self, 'compiled_tasks', []):
1259                 if os.path.isabs( self.env['CC'] ):
1260                         t.dep_nodes.append(c_compiler_node)
1261                 if os.path.isabs( self.env['CXX'] ):
1262                         t.dep_nodes.append(cxx_compiler_node)
1263                 
1264         # Let all link tasks depend on the linker
1265         if getattr(self, 'link_task', None):
1266                 if os.path.isabs(  self.env['LINK'] ):
1267                         self.link_task.dep_nodes.append(linker_node)
1269 ###############################################################################
1270 def show_option_dialog(ctx):
1271         ctx.gui_modify_user_options()
1272                 
1273 ###############################################################################
1274 class execute_waf_options_dialog(BuildContext):
1275         ''' Util class to execute waf options dialog  '''
1276         cmd = 'show_option_dialog'
1277         fun = 'show_option_dialog'
1278         
1280 #avoid log message
1281 # "feature 'java' does not exist - bind at least one method to it"
1282 # the java feature is added if waf detects a single 'java' file.. which is used in android
1284 @feature('java')
1285 def dummy_func(ctx):
1286         pass