|  | @@ -38,84 +38,36 @@ import shutil
 | 
	
		
			
				|  |  |  import sys
 | 
	
		
			
				|  |  |  import tempfile
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +def _unix_commandfile_spawn(self, command):
 | 
	
		
			
				|  |  | +  """Wrapper around distutils.util.spawn that attempts to use command files.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def _unix_piecemeal_link(
 | 
	
		
			
				|  |  | -    self, target_desc, objects, output_filename, output_dir=None,
 | 
	
		
			
				|  |  | -    libraries=None, library_dirs=None, runtime_library_dirs=None,
 | 
	
		
			
				|  |  | -    export_symbols=None, debug=0, extra_preargs=None, extra_postargs=None,
 | 
	
		
			
				|  |  | -    build_temp=None, target_lang=None):
 | 
	
		
			
				|  |  | -  """`link` externalized method taken almost verbatim from UnixCCompiler.
 | 
	
		
			
				|  |  | +  Meant to replace the CCompiler method `spawn` on UnixCCompiler and its
 | 
	
		
			
				|  |  | +  derivatives (e.g. the MinGW32 compiler).
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  Modifies the link command for unix-like compilers by using a command file so
 | 
	
		
			
				|  |  | -  that long command line argument strings don't break the command shell's
 | 
	
		
			
				|  |  | -  ARG_MAX character limit.
 | 
	
		
			
				|  |  | +  Some commands like `gcc` (and friends like `clang`) support command files to
 | 
	
		
			
				|  |  | +  work around shell command length limits.
 | 
	
		
			
				|  |  |    """
 | 
	
		
			
				|  |  | -  objects, output_dir = self._fix_object_args(objects, output_dir)
 | 
	
		
			
				|  |  | -  libraries, library_dirs, runtime_library_dirs = self._fix_lib_args(
 | 
	
		
			
				|  |  | -      libraries, library_dirs, runtime_library_dirs)
 | 
	
		
			
				|  |  | -  # filter out standard library paths, which are not explicitely needed
 | 
	
		
			
				|  |  | -  # for linking
 | 
	
		
			
				|  |  | -  library_dirs = [dir for dir in library_dirs
 | 
	
		
			
				|  |  | -                  if not dir in ('/lib', '/lib64', '/usr/lib', '/usr/lib64')]
 | 
	
		
			
				|  |  | -  runtime_library_dirs = [dir for dir in runtime_library_dirs
 | 
	
		
			
				|  |  | -                          if not dir in ('/lib', '/lib64', '/usr/lib', '/usr/lib64')]
 | 
	
		
			
				|  |  | -  lib_opts = ccompiler.gen_lib_options(self, library_dirs, runtime_library_dirs,
 | 
	
		
			
				|  |  | -                             libraries)
 | 
	
		
			
				|  |  | -  if (not (isinstance(output_dir, str) or isinstance(output_dir, bytes))
 | 
	
		
			
				|  |  | -      and output_dir is not None):
 | 
	
		
			
				|  |  | -    raise TypeError("'output_dir' must be a string or None")
 | 
	
		
			
				|  |  | -  if output_dir is not None:
 | 
	
		
			
				|  |  | -    output_filename = os.path.join(output_dir, output_filename)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -  if self._need_link(objects, output_filename):
 | 
	
		
			
				|  |  | -    ld_args = (objects + self.objects +
 | 
	
		
			
				|  |  | -               lib_opts + ['-o', output_filename])
 | 
	
		
			
				|  |  | -    if debug:
 | 
	
		
			
				|  |  | -      ld_args[:0] = ['-g']
 | 
	
		
			
				|  |  | -    if extra_preargs:
 | 
	
		
			
				|  |  | -      ld_args[:0] = extra_preargs
 | 
	
		
			
				|  |  | -    if extra_postargs:
 | 
	
		
			
				|  |  | -      ld_args.extend(extra_postargs)
 | 
	
		
			
				|  |  | -    self.mkpath(os.path.dirname(output_filename))
 | 
	
		
			
				|  |  | -    try:
 | 
	
		
			
				|  |  | -      if target_desc == ccompiler.CCompiler.EXECUTABLE:
 | 
	
		
			
				|  |  | -        linker = self.linker_exe[:]
 | 
	
		
			
				|  |  | -      else:
 | 
	
		
			
				|  |  | -        linker = self.linker_so[:]
 | 
	
		
			
				|  |  | -      if target_lang == "c++" and self.compiler_cxx:
 | 
	
		
			
				|  |  | -        # skip over environment variable settings if /usr/bin/env
 | 
	
		
			
				|  |  | -        # is used to set up the linker's environment.
 | 
	
		
			
				|  |  | -        # This is needed on OSX. Note: this assumes that the
 | 
	
		
			
				|  |  | -        # normal and C++ compiler have the same environment
 | 
	
		
			
				|  |  | -        # settings.
 | 
	
		
			
				|  |  | -        i = 0
 | 
	
		
			
				|  |  | -        if os.path.basename(linker[0]) == "env":
 | 
	
		
			
				|  |  | -          i = 1
 | 
	
		
			
				|  |  | -          while '=' in linker[i]:
 | 
	
		
			
				|  |  | -            i = i + 1
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        linker[i] = self.compiler_cxx[i]
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -      if sys.platform == 'darwin':
 | 
	
		
			
				|  |  | -        import _osx_support
 | 
	
		
			
				|  |  | -        linker = _osx_support.compiler_fixup(linker, ld_args)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -      temporary_directory = tempfile.mkdtemp()
 | 
	
		
			
				|  |  | -      command_filename = os.path.abspath(
 | 
	
		
			
				|  |  | -          os.path.join(temporary_directory, 'command'))
 | 
	
		
			
				|  |  | -      with open(command_filename, 'w') as command_file:
 | 
	
		
			
				|  |  | -        escaped_ld_args = [arg.replace('\\', '\\\\') for arg in ld_args]
 | 
	
		
			
				|  |  | -        command_file.write(' '.join(escaped_ld_args))
 | 
	
		
			
				|  |  | -      self.spawn(linker + ['@{}'.format(command_filename)])
 | 
	
		
			
				|  |  | -    except errors.DistutilsExecError:
 | 
	
		
			
				|  |  | -      raise ccompiler.LinkError
 | 
	
		
			
				|  |  | +  command_base = os.path.basename(command[0].strip())
 | 
	
		
			
				|  |  | +  if command_base == 'ccache':
 | 
	
		
			
				|  |  | +    command_base = command[:2]
 | 
	
		
			
				|  |  | +    command_args = command[2:]
 | 
	
		
			
				|  |  | +  elif command_base.startswith('ccache') or command_base in ['gcc', 'clang', 'clang++', 'g++']:
 | 
	
		
			
				|  |  | +    command_base = command[:1]
 | 
	
		
			
				|  |  | +    command_args = command[1:]
 | 
	
		
			
				|  |  |    else:
 | 
	
		
			
				|  |  | -    log.debug("skipping %s (up-to-date)", output_filename)
 | 
	
		
			
				|  |  | +    return ccompiler.CCompiler.spawn(self, command)
 | 
	
		
			
				|  |  | +  temporary_directory = tempfile.mkdtemp()
 | 
	
		
			
				|  |  | +  command_filename = os.path.abspath(os.path.join(temporary_directory, 'command'))
 | 
	
		
			
				|  |  | +  with open(command_filename, 'w') as command_file:
 | 
	
		
			
				|  |  | +    escaped_args = [arg.replace('\\', '\\\\') for arg in command_args]
 | 
	
		
			
				|  |  | +    command_file.write(' '.join(escaped_args))
 | 
	
		
			
				|  |  | +  modified_command = command_base + ['@{}'.format(command_filename)]
 | 
	
		
			
				|  |  | +  result = ccompiler.CCompiler.spawn(self, modified_command)
 | 
	
		
			
				|  |  | +  shutil.rmtree(temporary_directory)
 | 
	
		
			
				|  |  | +  return result
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -# TODO(atash) try replacing this monkeypatch of the compiler harness' link
 | 
	
		
			
				|  |  | -# operation with a monkeypatch of the distutils `spawn` that applies
 | 
	
		
			
				|  |  | -# command-argument-file hacks where it can. Might be cleaner.
 | 
	
		
			
				|  |  |  def monkeypatch_unix_compiler():
 | 
	
		
			
				|  |  |    """Monkeypatching is dumb, but it's either that or we become maintainers of
 | 
	
		
			
				|  |  |       something much, much bigger."""
 | 
	
		
			
				|  |  | -  unixccompiler.UnixCCompiler.link = _unix_piecemeal_link
 | 
	
		
			
				|  |  | +  unixccompiler.UnixCCompiler.spawn = _unix_commandfile_spawn
 |