diff options
61 files changed, 3106 insertions, 1288 deletions
diff --git a/cmake/Platform/Emscripten.cmake b/cmake/Platform/Emscripten.cmake index aafd38a8..9bfa829d 100644 --- a/cmake/Platform/Emscripten.cmake +++ b/cmake/Platform/Emscripten.cmake @@ -1,48 +1,124 @@ # This file is a 'toolchain description file' for CMake. -# It teaches CMake about the Emscripten compiler, so that CMake can generate Unix Makefiles +# It teaches CMake about the Emscripten compiler, so that CMake can generate makefiles # from CMakeLists.txt that invoke emcc. # To use this toolchain file with CMake, invoke CMake with the following command line parameters -# cmake -DEMSCRIPTEN=1 -# -DCMAKE_TOOLCHAIN_FILE=<EmscriptenRoot>/cmake/Platform/Emscripten.cmake -# -DCMAKE_MODULE_PATH=<EmscriptenRoot>/cmake +# cmake -DCMAKE_TOOLCHAIN_FILE=<EmscriptenRoot>/cmake/Platform/Emscripten.cmake # -DCMAKE_BUILD_TYPE=<Debug|RelWithDebInfo|Release|MinSizeRel> -# -G "Unix Makefiles" +# -G "Unix Makefiles" (Linux and OSX) +# -G "MinGW Makefiles" (Windows) # <path/to/CMakeLists.txt> # Note, pass in here ONLY the path to the file, not the filename 'CMakeLists.txt' itself. # After that, build the generated Makefile with the command 'make'. On Windows, you may download and use 'mingw32-make' instead. -# the name of the target operating system -set(CMAKE_SYSTEM_NAME Emscripten) +# The following variable describes the target OS we are building to. +# Ideally, this could be 'Emscripten', but as Emscripten mimics the Linux platform, setting this to Linux will allow more of existing software to build. +# Be sure to run Emscripten test_openjpeg if planning to change this. +set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_CROSSCOMPILING TRUE) + +# Do a no-op access on the CMAKE_TOOLCHAIN_FILE variable so that CMake will not issue a warning on it being unused. +if (CMAKE_TOOLCHAIN_FILE) +endif() + +# Locate where the Emscripten compiler resides in relative to this toolchain file. if ("${EMSCRIPTEN_ROOT_PATH}" STREQUAL "") - set(CMAKE_FIND_ROOT_PATH "$ENV{EMSCRIPTEN}") + get_filename_component(GUESS_EMSCRIPTEN_ROOT_PATH "${CMAKE_CURRENT_LIST_DIR}/../../" ABSOLUTE) + if (EXISTS "${GUESS_EMSCRIPTEN_ROOT_PATH}/emranlib") + set(EMSCRIPTEN_ROOT_PATH "${GUESS_EMSCRIPTEN_ROOT_PATH}") + endif() +endif() + +# If not found by above search, locate using the EMSCRIPTEN environment variable. +if ("${EMSCRIPTEN_ROOT_PATH}" STREQUAL "") + set(EMSCRIPTEN_ROOT_PATH "$ENV{EMSCRIPTEN}") +endif() + +# Abort if not found. +if ("${EMSCRIPTEN_ROOT_PATH}" STREQUAL "") + message(FATAL_ERROR "Could not locate the Emscripten compiler toolchain directory! Either set the EMSCRIPTEN environment variable, or pass -DEMSCRIPTEN_ROOT_PATH=xxx to CMake to explicitly specify the location of the compiler!") +endif() + +# Normalize, convert Windows backslashes to forward slashes or CMake will crash. +get_filename_component(EMSCRIPTEN_ROOT_PATH "${EMSCRIPTEN_ROOT_PATH}" ABSOLUTE) + +if ("${CMAKE_MODULE_PATH}" STREQUAL "") + set(CMAKE_MODULE_PATH "${EMSCRIPTEN_ROOT_PATH}/cmake") +endif() + +set(CMAKE_FIND_ROOT_PATH "${EMSCRIPTEN_ROOT_PATH}/cmake") + +if (CMAKE_HOST_WIN32) + set(EMCC_SUFFIX ".bat") else() - set(CMAKE_FIND_ROOT_PATH "${EMSCRIPTEN_ROOT_PATH}") + set(EMCC_SUFFIX "") endif() # Specify the compilers to use for C and C++ if ("${CMAKE_C_COMPILER}" STREQUAL "") - set(CMAKE_C_COMPILER "emcc.bat") - set(CMAKE_CXX_COMPILER "em++.bat") - set(CMAKE_AR "emar.bat") - set(CMAKE_RANLIB "emranlib.bat") + set(CMAKE_C_COMPILER "${EMSCRIPTEN_ROOT_PATH}/emcc${EMCC_SUFFIX}") +endif() +if ("${CMAKE_CXX_COMPILER}" STREQUAL "") + set(CMAKE_CXX_COMPILER "${EMSCRIPTEN_ROOT_PATH}/em++${EMCC_SUFFIX}") +endif() + +if ("${CMAKE_AR}" STREQUAL "") + set(CMAKE_AR "${EMSCRIPTEN_ROOT_PATH}/emar${EMCC_SUFFIX}") +endif() + +if ("${CMAKE_RANLIB}" STREQUAL "") + set(CMAKE_RANLIB "${EMSCRIPTEN_ROOT_PATH}/emranlib${EMCC_SUFFIX}") endif() +# Don't do compiler autodetection, since we are cross-compiling. +include(CMakeForceCompiler) +CMAKE_FORCE_C_COMPILER("${CMAKE_C_COMPILER}" Clang) +CMAKE_FORCE_CXX_COMPILER("${CMAKE_CXX_COMPILER}" Clang) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) +# We would prefer to specify a standard set of Clang+Emscripten-friendly common convention for suffix files, especially for CMake executable files, +# but if these are adjusted, ${CMAKE_ROOT}/Modules/CheckIncludeFile.cmake will fail, since it depends on being able to compile output files with predefined names. +#SET(CMAKE_LINK_LIBRARY_SUFFIX "") +#SET(CMAKE_STATIC_LIBRARY_PREFIX "") +#SET(CMAKE_STATIC_LIBRARY_SUFFIX ".bc") +#SET(CMAKE_SHARED_LIBRARY_PREFIX "") +#SET(CMAKE_SHARED_LIBRARY_SUFFIX ".bc") +#IF (NOT CMAKE_EXECUTABLE_SUFFIX) +# SET(CMAKE_EXECUTABLE_SUFFIX ".js") +#endif() +#SET(CMAKE_FIND_LIBRARY_PREFIXES "") +#SET(CMAKE_FIND_LIBRARY_SUFFIXES ".bc") + +SET(CMAKE_C_USE_RESPONSE_FILE_FOR_OBJECTS 1) +SET(CMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS 1) +SET(CMAKE_C_USE_RESPONSE_FILE_FOR_INCLUDES 1) +SET(CMAKE_CXX_USE_RESPONSE_FILE_FOR_INCLUDES 1) + +set(CMAKE_C_RESPONSE_FILE_LINK_FLAG "@") +set(CMAKE_CXX_RESPONSE_FILE_LINK_FLAG "@") + # Specify the program to use when building static libraries. Force Emscripten-related command line options to clang. -set(CMAKE_CXX_ARCHIVE_CREATE "${CMAKE_CXX_COMPILER} -o <TARGET> -emit-llvm <LINK_FLAGS> <OBJECTS>") -set(CMAKE_C_ARCHIVE_CREATE "${CMAKE_C_COMPILER} -o <TARGET> -emit-llvm <LINK_FLAGS> <OBJECTS>") +set(CMAKE_CXX_ARCHIVE_CREATE "${CMAKE_CXX_COMPILER} ${CMAKE_START_TEMP_FILE} -o <TARGET> -emit-llvm <LINK_FLAGS> <OBJECTS>${CMAKE_END_TEMP_FILE}") +set(CMAKE_C_ARCHIVE_CREATE "${CMAKE_C_COMPILER} ${CMAKE_START_TEMP_FILE} -o <TARGET> -emit-llvm <LINK_FLAGS> <OBJECTS>${CMAKE_END_TEMP_FILE}") # Set a global EMSCRIPTEN variable that can be used in client CMakeLists.txt to detect when building using Emscripten. # There seems to be some kind of bug with CMake, so you might need to define this manually on the command line with "-DEMSCRIPTEN=1". set(EMSCRIPTEN 1) +# We are cross-compiling, so unset the common CMake variables that represent the target platform. Leave UNIX define enabled, since Emscripten +# mimics a Linux environment. +SET(WIN32) +SET(APPLE) + +set(CMAKE_C_SIZEOF_DATA_PTR 4) +set(CMAKE_CXX_SIZEOF_DATA_PTR 4) + set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG" CACHE STRING "Emscripten-overridden CMAKE_C_FLAGS_RELEASE") set(CMAKE_C_FLAGS_MINSIZEREL "-DNDEBUG" CACHE STRING "Emscripten-overridden CMAKE_C_FLAGS_MINSIZEREL") set(CMAKE_C_FLAGS_RELWITHDEBINFO "" CACHE STRING "Emscripten-overridden CMAKE_C_FLAGS_RELWITHDEBINFO") diff --git a/cmake/Platform/Emscripten_unix.cmake b/cmake/Platform/Emscripten_unix.cmake deleted file mode 100644 index 92a21fd1..00000000 --- a/cmake/Platform/Emscripten_unix.cmake +++ /dev/null @@ -1,24 +0,0 @@ -# On Unix platforms, we must specify the absolute path to emcc for cmake, having emcc in PATH will cause cmake to fail finding it. -# The user must set the EMSCRIPTEN variable to point to the Emscripten root folder. - -# Try locating Emscripten root directory based on the location of this toolchain file. -get_filename_component(GUESS_EMSCRIPTEN_ROOT_PATH "${CMAKE_CURRENT_LIST_FILE}/../../.." ABSOLUTE) -if (EXISTS "${GUESS_EMSCRIPTEN_ROOT_PATH}/emcc") - set(EMSCRIPTEN_ROOT_PATH "${GUESS_EMSCRIPTEN_ROOT_PATH}") -endif() - -# If not found, try if the environment variable Emscripten was set. -if ("${EMSCRIPTEN_ROOT_PATH}" STREQUAL "") - if ("$ENV{EMSCRIPTEN}" STREQUAL "") - message(ERROR "Could not locate emcc and the environment variable EMSCRIPTEN has not been set! Please point it to Emscripten root directory!") - else() - set(EMSCRIPTEN_ROOT_PATH "$ENV{EMSCRIPTEN}") - endif() -endif() - -set(CMAKE_C_COMPILER "${EMSCRIPTEN_ROOT_PATH}/emcc") -set(CMAKE_CXX_COMPILER "${EMSCRIPTEN_ROOT_PATH}/em++") -set(CMAKE_AR "${EMSCRIPTEN_ROOT_PATH}/emar") -set(CMAKE_RANLIB "${EMSCRIPTEN_ROOT_PATH}/emranlib") - -include(${EMSCRIPTEN_ROOT_PATH}/cmake/Platform/Emscripten.cmake) @@ -471,6 +471,9 @@ Options that are modified or new in %s include: to hide these warnings and acknowledge that the explicit use of absolute paths is intentional. + --proxy-to-worker Generates both html and js files. The main + program is in js, and the html proxies to/from it. + The target file, if specified (-o <target>), defines what will be generated: @@ -663,6 +666,12 @@ if '-M' in sys.argv or '-MM' in sys.argv: logging.debug('just dependencies: ' + ' '.join(cmd)) exit(subprocess.call(cmd)) +if '-E' in sys.argv: + # Just run the preprocessor + cmd = [CC] + sys.argv[1:] + logging.debug('just preprocssor ' + ' '.join(cmd)) + exit(subprocess.call(cmd)) + # Check if a target is specified target = None for i in range(len(sys.argv)-1): @@ -734,6 +743,7 @@ try: save_bc = False memory_init_file = False use_preload_cache = False + proxy_to_worker = False if use_cxx: default_cxx_std = '-std=c++03' # Enforce a consistent C++ standard when compiling .cpp files, if user does not specify one on the cmdline. @@ -897,6 +907,9 @@ try: memory_init_file = int(newargs[i+1]) newargs[i] = '' newargs[i+1] = '' + elif newargs[i] == '--proxy-to-worker': + proxy_to_worker = True + newargs[i] = '' elif newargs[i].startswith(('-I', '-L')): path_name = newargs[i][2:] if not absolute_warning_shown and os.path.isabs(path_name): @@ -1070,9 +1083,6 @@ try: shared.Settings.CORRECT_OVERFLOWS = 1 assert not shared.Settings.PGO, 'cannot run PGO in ASM_JS mode' - if shared.Settings.ASSERTIONS and shared.Settings.ALIASING_FUNCTION_POINTERS: - logging.warning('ALIASING_FUNCTION_POINTERS is on, function pointer comparisons may be invalid across types') - if shared.Settings.CORRECT_SIGNS >= 2 or shared.Settings.CORRECT_OVERFLOWS >= 2 or shared.Settings.CORRECT_ROUNDINGS >= 2: debug_level = 4 # must keep debug info to do line-by-line operations @@ -1107,13 +1117,27 @@ try: shared.Settings.LINKABLE = 1 # TODO: add FORCE_DCE option for the brave people that do want to dce here and in side modules debug_level = max(debug_level, 2) - if shared.Settings.DLOPEN_SUPPORT: - shared.Settings.LINKABLE = 1 + if shared.Settings.ASSERTIONS and shared.Settings.ALIASING_FUNCTION_POINTERS: + logging.warning('ALIASING_FUNCTION_POINTERS is on, function pointer comparisons may be invalid across types') if shared.Settings.STB_IMAGE and final_suffix in JS_CONTAINING_SUFFIXES: input_files.append(shared.path_from_root('third_party', 'stb_image.c')) shared.Settings.EXPORTED_FUNCTIONS += ['_stbi_load', '_stbi_load_from_memory', '_stbi_image_free'] + if type(shared.Settings.EXPORTED_FUNCTIONS) in (list, tuple): + # always need malloc and free to be kept alive and exported, for internal use and other modules + for required_export in ['_malloc', '_free']: + if required_export not in shared.Settings.EXPORTED_FUNCTIONS: + shared.Settings.EXPORTED_FUNCTIONS.append(required_export) + else: + logging.debug('using response file for EXPORTED_FUNCTIONS, make sure it includes _malloc and _free') + + if shared.Settings.ASM_JS and shared.Settings.DLOPEN_SUPPORT: + assert shared.Settings.DISABLE_EXCEPTION_CATCHING, 'no exceptions support with dlopen in asm yet' + + if proxy_to_worker: + shared.Settings.PROXY_TO_WORKER = 1 + ## Compile source code to bitcode logging.debug('compiling to bitcode') @@ -1695,7 +1719,11 @@ try: logging.debug('generating HTML') shell = open(shell_path).read() html = open(target, 'w') - if not Compression.on: + if proxy_to_worker: + html.write(shell.replace('{{{ SCRIPT_CODE }}}', open(shared.path_from_root('src', 'proxyClient.js')).read().replace('{{{ filename }}}', target_basename))) + js_target = unsuffixed(target) + '.js' + shutil.copyfile(final, js_target) + elif not Compression.on: if debug_level >= 4: match = re.match('.*?<script[^>]*>{{{ SCRIPT_CODE }}}</script>', shell, re.DOTALL) @@ -1769,13 +1797,12 @@ try: html.close() else: if split_js_file: - from tools.split import split_javascript_file - split_javascript_file(final, unsuffixed(target), split_js_file) + from tools.split import split_javascript_file + split_javascript_file(final, unsuffixed(target), split_js_file) else: - if debug_level >= 4: generate_source_map(target) - - # copy final JS to output - shutil.move(final, target) + if debug_level >= 4: generate_source_map(target) + # copy final JS to output + shutil.move(final, target) if DEBUG: logging.debug('total time: %.2f seconds' % (time.time() - start_time)) diff --git a/emlibtool b/emlibtool deleted file mode 100755 index 1eb18edc..00000000 --- a/emlibtool +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python2 - -''' -This is a helper script. See emcc. -''' - -import os, sys -from tools import shared - -raise Exception('TODO: emlibtool') - diff --git a/emlibtool.bat b/emlibtool.bat deleted file mode 100644 index 4ea705be..00000000 --- a/emlibtool.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -python "%~dp0\emlibtool" %*
\ No newline at end of file diff --git a/emscripten.py b/emscripten.py index c5e235d8..fcf109bf 100755 --- a/emscripten.py +++ b/emscripten.py @@ -287,6 +287,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, exported_implemented_functions = set() for func_js, curr_forwarded_data in outputs: curr_forwarded_json = json.loads(curr_forwarded_data) + forwarded_json['Types']['hasInlineJS'] = forwarded_json['Types']['hasInlineJS'] or curr_forwarded_json['Types']['hasInlineJS'] forwarded_json['Types']['preciseI64MathUsed'] = forwarded_json['Types']['preciseI64MathUsed'] or curr_forwarded_json['Types']['preciseI64MathUsed'] for key, value in curr_forwarded_json['Functions']['blockAddresses'].iteritems(): forwarded_json['Functions']['blockAddresses'][key] = value @@ -444,6 +445,11 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, forwarded_json['Functions']['libraryFunctions'].get('llvm_ctlz_i32'): basic_vars += ['cttz_i8', 'ctlz_i8'] + if settings.get('DLOPEN_SUPPORT'): + for sig in last_forwarded_json['Functions']['tables'].iterkeys(): + basic_vars.append('F_BASE_%s' % sig) + asm_setup += ' var F_BASE_%s = %s;\n' % (sig, 'FUNCTION_TABLE_OFFSET' if settings.get('SIDE_MODULE') else '0') + '\n' + asm_runtime_funcs = ['stackAlloc', 'stackSave', 'stackRestore', 'setThrew'] + ['setTempRet%d' % i for i in range(10)] # function tables def asm_coerce(value, sig): @@ -475,8 +481,12 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, ''' % (sig, i, args, arg_coercions, jsret)) from tools import shared + shared.Settings.copy(settings) asm_setup += '\n' + shared.JS.make_invoke(sig) + '\n' basic_funcs.append('invoke_%s' % sig) + if settings.get('DLOPEN_SUPPORT'): + asm_setup += '\n' + shared.JS.make_extcall(sig) + '\n' + basic_funcs.append('extCall_%s' % sig) # calculate exports exported_implemented_functions = list(exported_implemented_functions) @@ -534,7 +544,7 @@ function asmPrintFloat(x, y) { } // EMSCRIPTEN_START_ASM var asm = (function(global, env, buffer) { - 'use asm'; + %s var HEAP8 = new global.Int8Array(buffer); var HEAP16 = new global.Int16Array(buffer); var HEAP32 = new global.Int32Array(buffer); @@ -543,7 +553,7 @@ var asm = (function(global, env, buffer) { var HEAPU32 = new global.Uint32Array(buffer); var HEAPF32 = new global.Float32Array(buffer); var HEAPF64 = new global.Float64Array(buffer); -''' % (asm_setup,) + '\n' + asm_global_vars + ''' +''' % (asm_setup, "'use asm';" if not forwarded_json['Types']['hasInlineJS'] and not settings['SIDE_MODULE'] else "'almost asm';") + '\n' + asm_global_vars + ''' var __THREW__ = 0; var threwValue = 0; var setjmpId = 0; @@ -606,23 +616,36 @@ function setTempRet%d(value) { // EMSCRIPTEN_END_ASM (%s, %s, buffer); %s; +''' % (pre_tables + '\n'.join(function_tables_impls) + '\n' + function_tables_defs.replace('\n', '\n '), exports, the_global, sending, receiving)] + + if not settings.get('SIDE_MODULE'): + funcs_js.append(''' Runtime.stackAlloc = function(size) { return asm['stackAlloc'](size) }; Runtime.stackSave = function() { return asm['stackSave']() }; Runtime.stackRestore = function(top) { asm['stackRestore'](top) }; -''' % (pre_tables + '\n'.join(function_tables_impls) + '\n' + function_tables_defs.replace('\n', '\n '), exports, the_global, sending, receiving)] +''') # Set function table masks - def function_table_maskize(js): - masks = {} - default = None - for sig, table in last_forwarded_json['Functions']['tables'].iteritems(): - masks[sig] = str(table.count(',')) - default = sig + masks = {} + max_mask = 0 + for sig, table in last_forwarded_json['Functions']['tables'].iteritems(): + mask = table.count(',') + masks[sig] = str(mask) + max_mask = max(mask, max_mask) + def function_table_maskize(js, masks): def fix(m): sig = m.groups(0)[0] return masks[sig] return re.sub(r'{{{ FTM_([\w\d_$]+) }}}', lambda m: fix(m), js) # masks[m.groups(0)[0]] - funcs_js = map(function_table_maskize, funcs_js) + funcs_js = map(lambda js: function_table_maskize(js, masks), funcs_js) + + if settings.get('DLOPEN_SUPPORT'): + funcs_js.append(''' + asm.maxFunctionIndex = %(max_mask)d; + DLFCN.registerFunctions(asm, %(max_mask)d+1, %(sigs)s, Module); + Module.SYMBOL_TABLE = SYMBOL_TABLE; +''' % { 'max_mask': max_mask, 'sigs': str(map(str, last_forwarded_json['Functions']['tables'].keys())) }) + else: function_tables_defs = '\n'.join([table for table in last_forwarded_json['Functions']['tables'].itervalues()]) outfile.write(function_tables_defs) @@ -637,13 +660,18 @@ Runtime.stackRestore = function(top) { asm['stackRestore'](top) }; symbol_table = {} for k, v in forwarded_json['Variables']['indexedGlobals'].iteritems(): if forwarded_json['Variables']['globals'][k]['named']: - symbol_table[k] = v + forwarded_json['Runtime']['GLOBAL_BASE'] + symbol_table[k] = str(v + forwarded_json['Runtime']['GLOBAL_BASE']) for raw in last_forwarded_json['Functions']['tables'].itervalues(): if raw == '': continue table = map(string.strip, raw[raw.find('[')+1:raw.find(']')].split(",")) - symbol_table.update(map(lambda x: (x[1], x[0]), - filter(lambda x: x[1] != '0', enumerate(table)))) - outfile.write("var SYMBOL_TABLE = %s;" % json.dumps(symbol_table)) + for i in range(len(table)): + value = table[i] + if value != '0': + if settings.get('SIDE_MODULE'): + symbol_table[value] = 'FUNCTION_TABLE_OFFSET+' + str(i) + else: + symbol_table[value] = str(i) + outfile.write("var SYMBOL_TABLE = %s;" % json.dumps(symbol_table).replace('"', '')) for funcs_js_item in funcs_js: # do this loop carefully to save memory funcs_js_item = indexize(funcs_js_item) @@ -663,69 +691,6 @@ def main(args, compiler_engine, cache, jcache, relooper, temp_files, DEBUG, DEBU name, value = setting.strip().split('=', 1) settings[name] = json.loads(value) - # Add header defines to settings - defines = {} - include_root = path_from_root('system', 'include') - headers = args.headers[0].split(',') if len(args.headers) > 0 else [] - seen_headers = set() - while len(headers) > 0: - header = headers.pop(0) - if not os.path.isabs(header): - header = os.path.join(include_root, header) - seen_headers.add(header) - for line in open(header, 'r'): - line = line.replace('\t', ' ') - m = re.match('^ *# *define +(?P<name>[-\w_.]+) +\(?(?P<value>[-\w_.|]+)\)?.*', line) - if not m: - # Catch enum defines of a very limited sort - m = re.match('^ +(?P<name>[A-Z_\d]+) += +(?P<value>\d+).*', line) - if m: - if m.group('name') != m.group('value'): - defines[m.group('name')] = m.group('value') - #else: - # print 'Warning: %s #defined to itself' % m.group('name') # XXX this can happen if we are set to be equal to an enum (with the same name) - m = re.match('^ *# *include *["<](?P<name>[\w_.-/]+)[">].*', line) - if m: - # Find this file - found = False - for w in [w for w in os.walk(include_root)]: - for f in w[2]: |