[libc++] Properly handle errors happening during Lit configuration
Instead of silently swallowing errors that happen during Lit configuration (for example trying to obtain compiler macros but compiling fails), raise an exception with some amount of helpful information. This should avoid the possibility of silently configuring Lit in a bogus way, and also provides more helpful information when things fail. Note that this requires a bit more finesse around how we handle some failing configuration checks that we would previously return None for. Differential Revision: https://reviews.llvm.org/D114010
This commit is contained in:
		
							parent
							
								
									7dc9a03cfd
								
							
						
					
					
						commit
						f18f9ce366
					
				| 
						 | 
					@ -151,19 +151,19 @@ class TestProgramOutput(SetupConfigs):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.assertEqual(dsl.programOutput(self.config, source), "")
 | 
					        self.assertEqual(dsl.programOutput(self.config, source), "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_invalid_program_returns_None_1(self):
 | 
					    def test_program_that_fails_to_run_raises_runtime_error(self):
 | 
				
			||||||
        # The program compiles, but exits with an error
 | 
					        # The program compiles, but exits with an error
 | 
				
			||||||
        source = """
 | 
					        source = """
 | 
				
			||||||
        int main(int, char**) { return 1; }
 | 
					        int main(int, char**) { return 1; }
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.assertEqual(dsl.programOutput(self.config, source), None)
 | 
					        self.assertRaises(dsl.ConfigurationRuntimeError, lambda: dsl.programOutput(self.config, source))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_invalid_program_returns_None_2(self):
 | 
					    def test_program_that_fails_to_compile_raises_compilation_error(self):
 | 
				
			||||||
        # The program doesn't compile
 | 
					        # The program doesn't compile
 | 
				
			||||||
        source = """
 | 
					        source = """
 | 
				
			||||||
        int main(int, char**) { this doesnt compile }
 | 
					        int main(int, char**) { this doesnt compile }
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.assertEqual(dsl.programOutput(self.config, source), None)
 | 
					        self.assertRaises(dsl.ConfigurationCompilationError, lambda: dsl.programOutput(self.config, source))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_pass_arguments_to_program(self):
 | 
					    def test_pass_arguments_to_program(self):
 | 
				
			||||||
        source = """
 | 
					        source = """
 | 
				
			||||||
| 
						 | 
					@ -231,6 +231,11 @@ class TestHasLocale(SetupConfigs):
 | 
				
			||||||
    def test_nonexistent_locale(self):
 | 
					    def test_nonexistent_locale(self):
 | 
				
			||||||
        self.assertFalse(dsl.hasAnyLocale(self.config, ['for_sure_this_is_not_an_existing_locale']))
 | 
					        self.assertFalse(dsl.hasAnyLocale(self.config, ['for_sure_this_is_not_an_existing_locale']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_localization_program_doesnt_compile(self):
 | 
				
			||||||
 | 
					        compilerIndex = findIndex(self.config.substitutions, lambda x: x[0] == '%{cxx}')
 | 
				
			||||||
 | 
					        self.config.substitutions[compilerIndex] = ('%{cxx}', 'this-is-certainly-not-a-valid-compiler!!')
 | 
				
			||||||
 | 
					        self.assertRaises(dsl.ConfigurationCompilationError, lambda: dsl.hasAnyLocale(self.config, ['en_US.UTF-8']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestCompilerMacros(SetupConfigs):
 | 
					class TestCompilerMacros(SetupConfigs):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,14 @@ import lit.Test
 | 
				
			||||||
import lit.TestRunner
 | 
					import lit.TestRunner
 | 
				
			||||||
import lit.util
 | 
					import lit.util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConfigurationError(Exception):
 | 
				
			||||||
 | 
					  pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConfigurationCompilationError(ConfigurationError):
 | 
				
			||||||
 | 
					  pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConfigurationRuntimeError(ConfigurationError):
 | 
				
			||||||
 | 
					  pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _memoizeExpensiveOperation(extractCacheKey):
 | 
					def _memoizeExpensiveOperation(extractCacheKey):
 | 
				
			||||||
  """
 | 
					  """
 | 
				
			||||||
| 
						 | 
					@ -115,7 +123,7 @@ def sourceBuilds(config, source):
 | 
				
			||||||
  with _makeConfigTest(config) as test:
 | 
					  with _makeConfigTest(config) as test:
 | 
				
			||||||
    with open(test.getSourcePath(), 'w') as sourceFile:
 | 
					    with open(test.getSourcePath(), 'w') as sourceFile:
 | 
				
			||||||
      sourceFile.write(source)
 | 
					      sourceFile.write(source)
 | 
				
			||||||
    out, err, exitCode, timeoutInfo = _executeScriptInternal(test, ['%{build}'])
 | 
					    _, _, exitCode, _ = _executeScriptInternal(test, ['%{build}'])
 | 
				
			||||||
    return exitCode == 0
 | 
					    return exitCode == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@_memoizeExpensiveOperation(lambda c, p, args=None: (c.substitutions, c.environment, p, args))
 | 
					@_memoizeExpensiveOperation(lambda c, p, args=None: (c.substitutions, c.environment, p, args))
 | 
				
			||||||
| 
						 | 
					@ -124,22 +132,22 @@ def programOutput(config, program, args=None):
 | 
				
			||||||
  Compiles a program for the test target, run it on the test target and return
 | 
					  Compiles a program for the test target, run it on the test target and return
 | 
				
			||||||
  the output.
 | 
					  the output.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  If the program fails to compile or run, None is returned instead. Note that
 | 
					  Note that execution of the program is done through the %{exec} substitution,
 | 
				
			||||||
  execution of the program is done through the %{exec} substitution, which means
 | 
					  which means that the program may be run on a remote host depending on what
 | 
				
			||||||
  that the program may be run on a remote host depending on what %{exec} does.
 | 
					  %{exec} does.
 | 
				
			||||||
  """
 | 
					  """
 | 
				
			||||||
  if args is None:
 | 
					  if args is None:
 | 
				
			||||||
    args = []
 | 
					    args = []
 | 
				
			||||||
  with _makeConfigTest(config) as test:
 | 
					  with _makeConfigTest(config) as test:
 | 
				
			||||||
    with open(test.getSourcePath(), 'w') as source:
 | 
					    with open(test.getSourcePath(), 'w') as source:
 | 
				
			||||||
      source.write(program)
 | 
					      source.write(program)
 | 
				
			||||||
    _, _, exitCode, _ = _executeScriptInternal(test, ['%{build}'])
 | 
					    _, err, exitCode, _ = _executeScriptInternal(test, ['%{build}'])
 | 
				
			||||||
    if exitCode != 0:
 | 
					    if exitCode != 0:
 | 
				
			||||||
      return None
 | 
					      raise ConfigurationCompilationError("Failed to build program, stderr is:\n{}".format(err))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    out, err, exitCode, _ = _executeScriptInternal(test, ["%{{run}} {}".format(' '.join(args))])
 | 
					    out, err, exitCode, _ = _executeScriptInternal(test, ["%{{run}} {}".format(' '.join(args))])
 | 
				
			||||||
    if exitCode != 0:
 | 
					    if exitCode != 0:
 | 
				
			||||||
      return None
 | 
					      raise ConfigurationRuntimeError("Failed to run program, stderr is:\n{}".format(err))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    actualOut = re.search("# command output:\n(.+)\n$", out, flags=re.DOTALL)
 | 
					    actualOut = re.search("# command output:\n(.+)\n$", out, flags=re.DOTALL)
 | 
				
			||||||
    actualOut = actualOut.group(1) if actualOut else ""
 | 
					    actualOut = actualOut.group(1) if actualOut else ""
 | 
				
			||||||
| 
						 | 
					@ -172,21 +180,26 @@ def hasAnyLocale(config, locales):
 | 
				
			||||||
  depending on the %{exec} substitution.
 | 
					  depending on the %{exec} substitution.
 | 
				
			||||||
  """
 | 
					  """
 | 
				
			||||||
  program = """
 | 
					  program = """
 | 
				
			||||||
    #include <locale.h>
 | 
					    #include <stddef.h>
 | 
				
			||||||
    #include <stdio.h>
 | 
					    #if defined(_LIBCPP_HAS_NO_LOCALIZATION)
 | 
				
			||||||
    int main(int argc, char** argv) {
 | 
					      int main(int, char**) { return 1; }
 | 
				
			||||||
      // For debugging purposes print which locales are (not) supported.
 | 
					    #else
 | 
				
			||||||
      for (int i = 1; i < argc; i++) {
 | 
					      #include <locale.h>
 | 
				
			||||||
        if (::setlocale(LC_ALL, argv[i]) != NULL) {
 | 
					      int main(int argc, char** argv) {
 | 
				
			||||||
          printf("%s is supported.\\n", argv[i]);
 | 
					        for (int i = 1; i < argc; i++) {
 | 
				
			||||||
          return 0;
 | 
					          if (::setlocale(LC_ALL, argv[i]) != NULL) {
 | 
				
			||||||
 | 
					            return 0;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        printf("%s is not supported.\\n", argv[i]);
 | 
					        return 1;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return 1;
 | 
					    #endif
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  """
 | 
					  """
 | 
				
			||||||
  return programOutput(config, program, args=[pipes.quote(l) for l in locales]) is not None
 | 
					  try:
 | 
				
			||||||
 | 
					    programOutput(config, program, args=[pipes.quote(l) for l in locales])
 | 
				
			||||||
 | 
					  except ConfigurationRuntimeError:
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					  return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@_memoizeExpensiveOperation(lambda c, flags='': (c.substitutions, c.environment, flags))
 | 
					@_memoizeExpensiveOperation(lambda c, flags='': (c.substitutions, c.environment, flags))
 | 
				
			||||||
def compilerMacros(config, flags=''):
 | 
					def compilerMacros(config, flags=''):
 | 
				
			||||||
| 
						 | 
					@ -198,20 +211,17 @@ def compilerMacros(config, flags=''):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  If the optional `flags` argument (a string) is provided, these flags will
 | 
					  If the optional `flags` argument (a string) is provided, these flags will
 | 
				
			||||||
  be added to the compiler invocation when generating the macros.
 | 
					  be added to the compiler invocation when generating the macros.
 | 
				
			||||||
 | 
					 | 
				
			||||||
  If we fail to extract the compiler macros because of a compiler error, None
 | 
					 | 
				
			||||||
  is returned instead.
 | 
					 | 
				
			||||||
  """
 | 
					  """
 | 
				
			||||||
  with _makeConfigTest(config) as test:
 | 
					  with _makeConfigTest(config) as test:
 | 
				
			||||||
    with open(test.getSourcePath(), 'w') as sourceFile:
 | 
					    with open(test.getSourcePath(), 'w') as sourceFile:
 | 
				
			||||||
      # Make sure files like <__config> are included, since they can define
 | 
					      # Make sure files like <__config> are included, since they can define
 | 
				
			||||||
      # additional macros.
 | 
					      # additional macros.
 | 
				
			||||||
      sourceFile.write("#include <stddef.h>")
 | 
					      sourceFile.write("#include <stddef.h>")
 | 
				
			||||||
    unparsedOutput, err, exitCode, timeoutInfo = _executeScriptInternal(test, [
 | 
					    unparsedOutput, err, exitCode, _ = _executeScriptInternal(test, [
 | 
				
			||||||
      "%{{cxx}} %s -dM -E %{{flags}} %{{compile_flags}} {}".format(flags)
 | 
					      "%{{cxx}} %s -dM -E %{{flags}} %{{compile_flags}} {}".format(flags)
 | 
				
			||||||
    ])
 | 
					    ])
 | 
				
			||||||
    if exitCode != 0:
 | 
					    if exitCode != 0:
 | 
				
			||||||
      return None
 | 
					      raise ConfigurationCompilationError("Failed to retrieve compiler macros, stderr is:\n{}".format(err))
 | 
				
			||||||
    parsedMacros = dict()
 | 
					    parsedMacros = dict()
 | 
				
			||||||
    defines = (l.strip() for l in unparsedOutput.split('\n') if l.startswith('#define '))
 | 
					    defines = (l.strip() for l in unparsedOutput.split('\n') if l.startswith('#define '))
 | 
				
			||||||
    for line in defines:
 | 
					    for line in defines:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue