# Copyright (c) 2017 CEF Python, see the Authors file. # All rights reserved. Licensed under BSD 3-clause license. # Project website: https://github.com/cztomczak/cefpython """Called by build.py internally. Builds C++ projects using distutils/setuptools compilers. This tool is executed by build.py on Windows only currently. Output directories are in build/build_cefpython/. Usage: build_cpp_projects.py [--force] TODO: Linux/Mac support, see makefiles, add include dirs using compiler.add_include_dir, compiler/linker flags, refactor macros. """ # import setuptools so that distutils msvc compiler is patched # noinspection PyUnresolvedReferences import setuptools from distutils.ccompiler import new_compiler from common import * import shutil # Macros MACROS = [ "WIN32", "_WIN32", "_WINDOWS", ("WINVER", "0x0601"), ("_WIN32_WINNT", "0x0601"), # Windows 7+ "NDEBUG", "_NDEBUG", "_CRT_SECURE_NO_WARNINGS", ] cefpython_app_MACROS = MACROS + [ "BROWSER_PROCESS", ] subprocess_MACROS = MACROS + [ "RENDERER_PROCESS", "UNICODE", "_UNICODE", "NOMINMAX", "WIN32_LEAN_AND_MEAN", ("_HAS_EXCEPTIONS", "0"), ] # Compiler args COMPILER_ARGS = [ "/EHsc", ] subprocess_COMPILER_ARGS = [ "/MT", ] # Linker args subprocess_LINKER_ARGS = [ "/MANIFEST:NO", "/LARGEADDRESSAWARE", ] # Command line args FORCE_FLAG = False def main(): command_line_args() clean_build_directories_if_forced() build_cefpython_app_library() build_library(lib_name="client_handler", macros=MACROS, sources_dir=CLIENT_HANDLER_DIR, output_dir=BUILD_CLIENT_HANDLER) build_library(lib_name="cpp_utils", macros=MACROS, sources_dir=CPP_UTILS_DIR, output_dir=BUILD_CPP_UTILS) build_subprocess_executable() print("[build_cpp_projects.py] Done building C++ projects") def command_line_args(): global FORCE_FLAG if "--force" in sys.argv: FORCE_FLAG = True def clean_build_directories_if_forced(): build_dirs = [BUILD_CEFPYTHON_APP, BUILD_CLIENT_HANDLER, BUILD_CPP_UTILS, BUILD_SUBPROCESS] if FORCE_FLAG: print("[build_cpp_projects.py] Clean C++ projects build directories") for bdir in build_dirs: if os.path.isdir(bdir): shutil.rmtree(bdir) def get_compiler(static=False): # NOTES: # - VS2008 and VS2010 are both using distutils/msvc9compiler.py compiler = new_compiler() # Must initialize so that "compile_options" and others are available compiler.initialize() if static: compiler.compile_options.remove("/MD") # Overwrite function that adds /MANIFESTFILE, as for subprocess # manifest is disabled. Otherwise warning LNK4075 is generated. if hasattr(compiler, "manifest_setup_ldargs"): compiler.manifest_setup_ldargs = lambda *_: None return compiler def build_library(lib_name, macros, output_dir, sources=None, sources_dir=None): assert bool(sources_dir) ^ bool(sources) # xor print("[build_cpp_projects.py] Build library: {lib_name}" .format(lib_name=lib_name)) compiler = get_compiler() if not os.path.exists(output_dir): os.makedirs(output_dir) if sources_dir: assert not sources sources = get_sources(sources_dir) (changed, objects) = smart_compile(compiler, macros=macros, extra_args=COMPILER_ARGS, sources=sources, output_dir=output_dir) lib_path = os.path.join(output_dir, lib_name + LIB_EXT) if changed or not os.path.exists(lib_path): compiler.create_static_lib(objects, lib_name, output_dir=output_dir) print("[build_cpp_projects.py] Created library: {lib_name}" .format(lib_name=lib_name)) else: print("[build_cpp_projects.py] Library is up-to-date: {lib_name}" .format(lib_name=lib_name)) def build_cefpython_app_library(): sources = get_sources(SUBPROCESS_DIR, exclude_names=["main.cpp"]) main_message_loop_dir = os.path.join(SUBPROCESS_DIR, "main_message_loop") sources.extend(get_sources(main_message_loop_dir)) build_library(lib_name="cefpython_app", macros=cefpython_app_MACROS, sources=sources, output_dir=BUILD_CEFPYTHON_APP) def build_subprocess_executable(): print("[buil_cpp_projects.py] Build executable: subprocess") compiler = get_compiler(static=True) sources = get_sources(SUBPROCESS_DIR, exclude_names=["print_handler_gtk.cpp"]) (changed, objects) = smart_compile(compiler, macros=subprocess_MACROS, extra_args=subprocess_COMPILER_ARGS, sources=sources, output_dir=BUILD_SUBPROCESS) executable_path = os.path.join(BUILD_SUBPROCESS, "subprocess" + EXECUTABLE_EXT) if changed or not os.path.exists(executable_path): lib_dir = os.path.join(CEF_BINARIES_LIBRARIES, "lib") lib_dir_vs = os.path.join(lib_dir, get_msvs_for_python(vs_prefix=True)) compiler.link_executable(objects, output_progname="subprocess", output_dir=BUILD_SUBPROCESS, libraries=["libcef", "libcef_dll_wrapper_MT"], library_dirs=[lib_dir, lib_dir_vs], # TODO linker flags for Linux/Mac extra_preargs=None, extra_postargs=subprocess_LINKER_ARGS) else: print("[build_cpp_projects.py] Executable is up-to-date: subprocess") def get_sources(sources_dir, exclude_names=None): if not exclude_names: exclude_names = list() sources = glob.glob(os.path.join(sources_dir, "*.cpp")) if MAC: sources.extend(glob.glob(os.path.join(sources_dir, "*.mm"))) ret = list() for source_file in sources: filename = os.path.basename(source_file) if "_win.cpp" in filename and not WINDOWS: continue if "_linux.cpp" in filename and not LINUX: continue if "x11" in filename and not LINUX: continue if "gtk" in filename and not LINUX: continue if "_mac.cpp" in filename and not MAC: continue exclude = False for name in exclude_names: if name in filename: exclude = True break if not exclude: ret.append(source_file) return ret def smart_compile(compiler, macros, extra_args, sources, output_dir): """Smart compile will only recompile files that need recompiling.""" if not os.path.exists(output_dir): os.makedirs(output_dir) any_changed = False objects = list() for source_file in sources: header_file = source_file.replace(".cpp", ".h") header_file = header_file.replace(".mm", ".h") assert header_file.endswith(".h") if not os.path.isfile(header_file): header_file = None obj_file = os.path.join(output_dir, os.path.basename(source_file)) obj_file = obj_file.replace(".cpp", OBJ_EXT) obj_file = obj_file.replace(".mm", OBJ_EXT) assert obj_file.endswith(OBJ_EXT) if os.path.exists(obj_file): # Recompile source file if its time is newer than obj file, # Also check its header file time. Also check times of any # possible includes: cefpython_fixed.h, src/common/ files. obj_time = os.path.getmtime(obj_file) source_time = os.path.getmtime(source_file) header_time = os.path.getmtime(header_file) if header_file else 0 cefpython_h_fixed_time = os.path.getmtime( CEFPYTHON_API_HFILE_FIXED) common_files_time = get_directory_mtime(os.path.join(SRC_DIR, "common")) changed = ((source_time > obj_time) or (header_time > obj_time) or (cefpython_h_fixed_time > obj_time) or (common_files_time > obj_time)) else: changed = True if changed: any_changed = True else: objects.append(obj_file) if any_changed: # If any has changed must recompile all given sources (for a library # or executable). This is because we don't know which sources include # which header files so must recompile everything. objects = list() # Compile each source file separately so that when compiling # source files from different directories, object files are # all put in the same output_directory. Otherwise distutils # will create lots of subdirs in output_directory. macros = macros_as_tuples(macros) common_dir = os.path.join(SRC_DIR, "common") original_dir = os.getcwd() for source_file in sources: source_dir = os.path.dirname(source_file) os.chdir(source_dir) source_basename = os.path.basename(source_file) oneobj = compiler.compile([source_basename], output_dir=output_dir, macros=macros, # TODO include dirs for Linux/Mac include_dirs=[SRC_DIR, common_dir, get_python_include_path()], # TODO compiler flags for Linux/Mac extra_preargs=None, extra_postargs=extra_args) assert len(oneobj) == 1 objects.append(os.path.join(source_dir, oneobj[0])) os.chdir(original_dir) assert len(objects) return any_changed, objects def macros_as_tuples(macros): """Return all macros as tuples. Required by distutils.ccompiler.""" ret_macros = list() for macro in macros: if isinstance(macro, str): ret_macros.append((macro, "")) else: assert isinstance(macro, tuple) ret_macros.append(macro) return ret_macros def get_directory_mtime(directory): # For example check src/common/ directory for newest modification time assert os.path.isdir(directory) files = glob.glob(os.path.join(directory, "*")) ret_mtime = 0 for header_file in files: mtime = os.path.getmtime(header_file) if mtime > ret_mtime: ret_mtime = mtime assert ret_mtime return ret_mtime if __name__ == "__main__": main()