# # All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or # its licensors. # # For complete copyright and license terms please see the LICENSE at the root of this # distribution (the "License"). All use of this software is governed by the License, # or, if provided, by the license below or the license accompanying this file. Do not # remove or modify any license notices. This file is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # # System Imports import os import stat # waflib imports from waflib.TaskGen import feature, before_method, after_method from waflib.Configure import conf, Logs from waflib.Tools.ccroot import lib_patterns, SYSTEM_LIB_PATHS from waflib import Node, Utils, Errors from waflib.Build import BuildContext # lmbrwaflib imports from lmbrwaflib.utils import fast_copy2, should_overwrite_file def use_windows_dll_semantics(ctx): if ctx.is_windows_platform(): return True platform = ctx.env['PLATFORM'].lower() if platform.startswith('xenia'): return True return False def get_static_suffix(): return "_static" def get_shared_suffix(): return "_shared" def should_link_aws_native_sdk_statically(bld): platform, configuration = bld.get_platform_and_configuration() # Only target platforms are eligible for this check if bld.is_target_platform(platform): return False # Monolithic builds must link statically if bld.is_build_monolithic(platform, configuration): return False if bld.check_platform_explicit_boolean_attribute(platform, 'link_aws_sdk_statically'): return True return False def get_dynamic_lib_extension(bld): platform = bld.env['PLATFORM'] if bld.is_apple_platform(platform): return '.dylib' elif any(substring in platform for substring in ['linux', 'android']): return '.so' return '.dll' def get_platform_lib_prefix(bld): platform = bld.env['PLATFORM'] platform_details = bld.get_target_platform_detail(platform) lib_prefix = platform_details.attributes.get('lib_prefix','') return lib_prefix def aws_native_sdk_platforms(bld): return [ 'win', 'darwin', 'ios', 'appletv', 'linux', 'android_armv8_clang', 'provo', 'salem', 'xenia_vs2019', 'xenia_vs2017', 'project_generator'] def should_project_include_aws_native_sdk(bld): platform = bld.env['PLATFORM'] if 'project_generator' in platform: return True # Build return does_platform_support_aws_native_sdk(bld) def does_platform_support_aws_native_sdk(bld): platform = bld.env['PLATFORM'] if any(substring in platform for substring in aws_native_sdk_platforms(bld)): return True return False @conf def add_aws_native_sdk_platform_defines(self, define_list): if should_project_include_aws_native_sdk(self): define_list.append('PLATFORM_SUPPORTS_AWS_NATIVE_SDK') if use_windows_dll_semantics(self): define_list.append('USE_WINDOWS_DLL_SEMANTICS') @conf def make_static_library_task_name(self, libName): return libName + get_static_suffix() @conf def make_shared_library_task_name(self, libName): return libName + get_shared_suffix() @conf def make_shared_library_task_list(self, libraryList): return [self.make_shared_library_task_name(l) for l in libraryList] @conf def make_static_library_task_list(self, libraryList): return [self.make_static_library_task_name(l) for l in libraryList] @conf def make_aws_library_task_list(self, libraryList): # Special case for LyMetrics/LyIdentity since we don't ship the source code, instead we use the pre-built libs extra_lib = [] if 'LyMetricsProducer' in libraryList: if 'LyMetricsShared' not in libraryList: libraryList.append('LyMetricsShared') extra_lib.append('AWSNativeSDKInit') if 'LyMetricsShared' in libraryList and 'LyIdentity' not in libraryList: libraryList.append('LyIdentity') shouldLinkStatically = isinstance(self, BuildContext) and should_link_aws_native_sdk_statically(self) if shouldLinkStatically: return self.make_static_library_task_list(libraryList) + extra_lib else: return self.make_shared_library_task_list(libraryList) + extra_lib def convert_dual_task_name_to_lib_name(taskName): shared_index = taskName.find(get_shared_suffix()) if shared_index >= 0: return taskName[0:shared_index] else: return taskName[0:taskName.find(get_static_suffix())] @conf def read_dual_library_shared(self, name, paths=[], export_includes=[], export_defines=[]): """ Read a system shared library, enabling its use as a local library. Performs name-mangling on the task name to ensure uniqueness with a static version of the same library. Will trigger a rebuild if the file changes. """ return self(name=self.make_shared_library_task_name(name), features='fake_dual_lib', lib_paths=paths, lib_type='shlib', export_includes=export_includes, export_defines=export_defines) @conf def read_dual_library_static(self, name, paths=[], export_includes=[], export_defines=[]): """ Read a system static library, enabling a use as a local library. Performs name-mangling on the task name to ensure uniqueness with a shared version of the same library. Will trigger a rebuild if the file changes. """ return self(name=self.make_static_library_task_name(name), features='fake_dual_lib', lib_paths=paths, lib_type='stlib', export_includes=export_includes, export_defines=export_defines) @feature('fake_dual_lib') def process_dual_lib(self): """ Find the location of a foreign library. Used by :py:class:`lmbrwaflib.lumberyard_sdks.read_dual_library_shared` and :py:class:`lmbrwaflib.lumberyard_sdks.read_dual_library_static`. """ node = None library_name = convert_dual_task_name_to_lib_name(self.name) names = [x % library_name for x in lib_patterns[self.lib_type]] for x in self.lib_paths + [self.path] + SYSTEM_LIB_PATHS: if not isinstance(x, Node.Node): x = self.bld.root.find_node(x) or self.path.find_node(x) if not x: continue for y in names: node = x.find_node(y) if node: node.cache_sig = Utils.h_file(node.abspath()) break else: continue break else: raise Errors.WafError('could not find library %r' % library_name) self.link_task = self.create_task('fake_%s' % self.lib_type, [], [node]) if not getattr(self, 'target', None): self.target = library_name if not getattr(self, 'output_file_name', None): self.output_file_name = library_name @conf def install_internal_sdk_headers(bld, build_task_name, sdk_name, header_list): if isinstance(bld, BuildContext): try: task_gen = bld.get_tgen_by_name(bld.make_shared_library_task_name(build_task_name)) if task_gen != None: for header in header_list: task_gen.create_task('copy_outputs', task_gen.path.make_node(build_task_name + '/include/' + header), bld.engine_node.make_node('Tools/InternalSDKs/' + sdk_name + '/include/' + header)) except: return def _get_build_dir(self, forceReleaseDir=False): build_dir = 'x64' # default to x64, unless we have a better case platform = self.env['PLATFORM'].lower() # Return early for these platforms as they don't follow the same directory # structure or naming convention as window platforms if 'ios' in platform: build_dir = 'ios' return build_dir elif 'appletv' in platform: build_dir = 'appletv' return build_dir elif 'mac' in platform: build_dir = 'OSX' return build_dir elif 'linux' in platform: build_dir = 'linux' return build_dir elif 'android' in platform: build_dir = 'android' return build_dir elif 'provo' in platform: build_dir = 'provo' return build_dir elif 'xenia' in platform: build_dir = 'xenia' return build_dir build_dir += '/' config = self.env['CONFIGURATION'].lower() if forceReleaseDir is True: build_dir += 'Release' else: if 'debug' in config: build_dir += 'Debug' elif 'profile' in config: build_dir += 'Release' else: build_dir += 'Release' return build_dir @conf def BuildPlatformLibraryDirectory(bld, forceStaticLinking): config = bld.env['CONFIGURATION'] platform = bld.env['PLATFORM'] if 'debug' in config: configDir = 'Debug' else: configDir = 'Release' if forceStaticLinking: libDir = 'lib' else: libDir = 'bin' if 'ios' in platform: platformDir = 'ios' elif 'appletv' in platform: platformDir = 'appletv' elif 'darwin' in platform: platformDir = 'mac' elif 'linux' in platform: platformDir = 'linux/intel64' elif 'android' in platform: platformDir = 'android' elif 'provo' in platform: platformDir = 'provo' elif 'xenia' in platform: platformDir = 'xenia' else: platformDir = 'windows/intel64' return '{}/{}/{}'.format(libDir, platformDir, configDir) @feature('AWSNativeSDK') @after_method('apply_monolithic_pch_objs') def link_aws_sdk_core_after(self): # AWSNativeSDK has a requirement that the aws-cpp-sdk-core library be linked after other aws libraries # The use system make this difficult as it adds dependent libraries in a first-seen order, and this results # in the wrong link order for monolithic builds. # This function removes the library from the use system after includes are applied but before the libs are # calculated, and instead adds the library to the libs, which are added after the use libraries, fixing the link order platform = self.env['PLATFORM'] if 'linux' in platform and 'aws-cpp-sdk-core_static' in self.use: self.use.remove('aws-cpp-sdk-core_static') if 'aws-cpp-sdk-core' not in self.env['LIB']: self.env['LIB'] += ['aws-cpp-sdk-core'] @feature('ExternalLyIdentity') @before_method('apply_incpaths') def copy_external_ly_identity(self): pass @feature('ExternalLyMetrics') @before_method('apply_incpaths') def copy_external_ly_metrics(self): pass def get_python_home_lib_and_dll(ctx, env): """ Get the following (internal) python paths based on the host platform 1. Home path 2. Includes path 3. Lib path 4. Path to the shared object (dll) :param ctx: The build context to search within :param env: The build context platform environment to search within :return: see the description """ if not env['EMBEDDED_PYTHON_HOME'] or not env['EMBEDDED_PYTHON_INCLUDE_PATH']\ or not env['EMBEDDED_PYTHON_LIBPATH'] or not env['EMBEDDED_PYTHON_SHARED_OBJECT']\ or not env['EMBEDDED_PYTHON_HOME_RELATIVE_PATH']: raise Errors.WafError('Python dll not supported for platform {}'.format(ctx.get_waf_host_platform())) return env['EMBEDDED_PYTHON_HOME'], env['EMBEDDED_PYTHON_INCLUDE_PATH'],\ env['EMBEDDED_PYTHON_LIBPATH'], env['EMBEDDED_PYTHON_SHARED_OBJECT'],\ env['EMBEDDED_PYTHON_HOME_RELATIVE_PATH'] def copy_local_python_to_target(task, source_python_dll_path): """ Perform a quick simple copy of python.dll from a local python folder :param task: The current running task :param source_python_dll_path: The source python path """ current_platform = task.env['PLATFORM'].lower() current_configuration = task.env['CONFIGURATION'].lower() # Determine the target folder(s) output_sub_folder = getattr(task, 'output_sub_folder', None) # If we have a custom output folder, then make a list of nodes from it target_folders = getattr(task, 'output_folder', []) if len(target_folders) > 0: target_folders = [task.bld.path.make_node(node) if isinstance(node, str) else node for node in target_folders] else: target_folders = task.bld.get_output_folders(current_platform, current_configuration) python_name = os.path.basename(source_python_dll_path) # Copy to each output folder target node for target_node in target_folders: python_dll_target_path = os.path.join(target_node.abspath(), python_name) if output_sub_folder: python_dll_target_path = os.path.join(target_node.make_node(output_sub_folder).abspath(), python_name) if should_overwrite_file(source_python_dll_path, python_dll_target_path): try: # In case the file is readonly, we'll remove the existing file first if os.path.exists(python_dll_target_path): os.chmod(python_dll_target_path, stat.S_IWRITE) fast_copy2(source_python_dll_path, python_dll_target_path) except Exception as e: Logs.warn('[WARN] Unable to copy {} to destination {} ({}). ' 'Check the file permissions or any process that may be locking it.' .format(python_dll_target_path, python_dll_target_path, e.message)) @feature('EmbeddedPython') @before_method('apply_system_incpaths') def enable_embedded_python(self): # Only win_x64 builds support embedding Python. # # We assume 'project_generator' (the result of doing a lmbr_waf configure) is # using the host waf platform build so that the include directory is configured property # for that host platform. platform = self.env['PLATFORM'].lower() config = self.env['CONFIGURATION'].lower() if self.bld.is_windows_platform(platform) or self.bld.is_linux_platform(platform) or self.bld.is_mac_platform(platform) or platform == 'project_generator': env = self.bld.env # If the platform is project generator, the default environment will not have the EMBEDDED_PYTHON_* # env varaiable set. Therefore the first platform environment which contains the host_platform name is used instead if platform == 'project_generator': host_platform = self.bld.get_waf_host_platform() for platform_env in self.bld.all_envs: if host_platform in platform_env: env = self.bld.all_envs[platform_env] break # Set the USE_DEBUG_PYTHON environment variable to the location of # a debug build of python if you want to use one for debug builds. # # This does NOT work with the boost python helpers, which are used # by the Editor, since the boost headers always undef _DEBUG before # including the python headers. # # Any code that includes python headers directly should also undef # _DEBUG before including those headers except for when USE_DEBUG_PYTHON # is defined. if 'debug' in config and 'USE_DEBUG_PYTHON' in os.environ: python_home = os.environ['USE_DEBUG_PYTHON'] python_dll = '{}/python37_d.dll'.format(python_home) self.env['DEFINES'] += ['USE_DEBUG_PYTHON'] else: python_home, python_include_dir, python_libs_dir, python_dll, python_home_relative = get_python_home_lib_and_dll(self.bld, env) self.env['SYSTEM_INCLUDES'] += [python_include_dir] self.env['LIBPATH'] += [python_libs_dir] # Standardize on forward-slash through the entire path. We specifically don't use backslashes to avoid an interaction with compiler # response-file generation in msvcdeps.py and msvc_helper.py that "fixes up" all the compiler flags, in part by replacing backslashes # with double-backslashes. If we tried to replace backslashes with double-backslashes here to make it a valid C++ string, it would # get double-fixed-up in the case that a response file gets used (long directory path names). self.env['DEFINES'] += ['DEFAULT_LY_PYTHONHOME="{}"'.format(python_home.replace('\\', '/'))] self.env['DEFINES'] += ['DEFAULT_LY_PYTHONHOME_RELATIVE="{}"'.format(python_home_relative.replace('\\', '/'))] if 'LIBPATH_BOOSTPYTHON' in self.env: self.env['LIBPATH'] += self.env['LIBPATH_BOOSTPYTHON'] elif 'STLIBPATH_BOOSTPYTHON' in self.env: self.env['LIBPATH'] += self.env['STLIBPATH_BOOSTPYTHON'] elif self.env['PLATFORM'] != 'project_generator': Logs.warn( '[WARN] Required 3rd party boostpython not detected. This may cause a link error in project {}.'.format( self.name)) @feature('internal_telemetry') @before_method('apply_incpaths') def stub_internal_telemetry(self): pass def _apply_to_attribute(self, output_attr_name, bin_relative_output_folder): if self.bld.is_engine_local(): # Only consider copying to the tools folder if we are building from the engine root # Go up one level to apply the relative path to the eventual output folder output_path = os.path.join('..', bin_relative_output_folder) current_attr = getattr(self,output_attr_name,None) if current_attr: if isinstance(current_attr,list): current_attr.append(output_path) else: current_attr = [current_attr, output_path] else: current_attr = output_path setattr(self, output_attr_name, current_attr) @feature('copy_to_lmbrsetup') @before_method('set_link_outputs') def copy_to_tools_lmbr_setup(self): _apply_to_attribute(self, 'output_sub_folder_copy', self.bld.get_lmbr_setup_tools_output_folder())