def test_find_legacy_env(self): """Ensure legacy mozconfig path definitions result in error.""" os.environ["MOZ_MYCONFIG"] = "/foo" with self.assertRaises(MozconfigFindException) as e: find_mozconfig(self.get_temp_dir()) self.assertTrue(str(e.exception).startswith("The MOZ_MYCONFIG"))
def test_find_abs_path_not_exist(self): """Ensure a missing absolute path is detected.""" os.environ["MOZCONFIG"] = "/foo/bar/does/not/exist" with self.assertRaises(MozconfigFindException) as e: find_mozconfig(self.get_temp_dir()) self.assertIn("path that does not exist", str(e.exception)) self.assertTrue(str(e.exception).endswith("/foo/bar/does/not/exist"))
def test_find_path_not_file(self): """Ensure non-file paths are detected.""" os.environ["MOZCONFIG"] = gettempdir() with self.assertRaises(MozconfigFindException) as e: find_mozconfig(self.get_temp_dir()) self.assertIn("refers to a non-file", str(e.exception)) self.assertTrue(str(e.exception).endswith(gettempdir()))
def test_find_deprecated_path_srcdir(self): """Ensure we error when deprecated path locations are present.""" for p in DEPRECATED_TOPSRCDIR_PATHS: d = self.get_temp_dir() with open(os.path.join(d, p), "w"): pass with self.assertRaises(MozconfigFindException) as e: find_mozconfig(d) self.assertIn("This implicit location is no longer", str(e.exception)) self.assertIn(d, str(e.exception))
def test_find_multiple_defaults(self): """Ensure we error when multiple default files are present.""" self.assertGreater(len(DEFAULT_TOPSRCDIR_PATHS), 1) d = self.get_temp_dir() for p in DEFAULT_TOPSRCDIR_PATHS: with open(os.path.join(d, p), "w"): pass with self.assertRaises(MozconfigFindException) as e: find_mozconfig(d) self.assertIn("Multiple default mozconfig files present", str(e.exception))
def _output_mozconfig(self, application, mozconfig_builder): # Like 'generate_browser_mozconfig' or 'generate_mobile_android_mozconfig'. additional_mozconfig = getattr( self.instance, "generate_%s_mozconfig" % application )() if additional_mozconfig: mozconfig_builder.append(additional_mozconfig) raw_mozconfig = mozconfig_builder.generate() if raw_mozconfig: mozconfig_path = find_mozconfig(self.mach_context.topdir) if not mozconfig_path: # No mozconfig file exists yet mozconfig_path = os.path.join(self.mach_context.topdir, "mozconfig") with open(mozconfig_path, "w") as mozconfig_file: mozconfig_file.write(raw_mozconfig) print( 'Your requested configuration has been written to "%s".' % mozconfig_path ) else: suggestion = MOZCONFIG_SUGGESTION_TEMPLATE % ( mozconfig_path, raw_mozconfig, ) print(suggestion)
def test_find_deprecated_home_paths(self): """Ensure we error when deprecated home directory paths are present.""" for p in DEPRECATED_HOME_PATHS: home = self.get_temp_dir() os.environ["HOME"] = home path = os.path.join(home, p) with open(path, "w"): pass with self.assertRaises(MozconfigFindException) as e: find_mozconfig(self.get_temp_dir()) self.assertIn("This implicit location is no longer", str(e.exception)) self.assertIn(path, str(e.exception))
def test_find_default_files(self): """Ensure default paths are used when present.""" for p in DEFAULT_TOPSRCDIR_PATHS: d = self.get_temp_dir() path = os.path.join(d, p) with open(path, "w"): pass self.assertEqual(find_mozconfig(d), path)
def test_find_no_relative_configs(self): """Ensure a missing relative-path MOZCONFIG is detected.""" relative_mozconfig = ".mconfig" os.environ["MOZCONFIG"] = relative_mozconfig srcdir = self.get_temp_dir() curdir = self.get_temp_dir() dirs = [srcdir, curdir] orig_dir = os.getcwd() try: os.chdir(curdir) with self.assertRaises(MozconfigFindException) as e: find_mozconfig(srcdir) finally: os.chdir(orig_dir) self.assertIn("does not exist in any of", str(e.exception)) for d in dirs: self.assertIn(d, str(e.exception))
def test_find_multiple_configs(self): """Ensure multiple relative-path MOZCONFIGs result in error.""" relative_mozconfig = ".mconfig" os.environ["MOZCONFIG"] = relative_mozconfig srcdir = self.get_temp_dir() curdir = self.get_temp_dir() dirs = [srcdir, curdir] for d in dirs: path = os.path.join(d, relative_mozconfig) with open(path, "w") as f: f.write(path) orig_dir = os.getcwd() try: os.chdir(curdir) with self.assertRaises(MozconfigFindException) as e: find_mozconfig(srcdir) finally: os.chdir(orig_dir) self.assertIn("exists in more than one of", str(e.exception)) for d in dirs: self.assertIn(d, str(e.exception))
def _output_mozconfig(self, application): # Like 'generate_browser_mozconfig' or 'generate_mobile_android_mozconfig'. mozconfig = getattr(self.instance, 'generate_%s_mozconfig' % application)() if mozconfig: mozconfig_path = find_mozconfig(self.mach_context.topdir) if not mozconfig_path: # No mozconfig file exists yet mozconfig_path = os.path.join(self.mach_context.topdir, 'mozconfig') with open(mozconfig_path, 'w') as mozconfig_file: mozconfig_file.write(mozconfig) print('Your requested configuration has been written to "%s".' % mozconfig_path) else: suggestion = MOZCONFIG_SUGGESTION_TEMPLATE % (mozconfig_path, mozconfig) print(suggestion)
def test_find_relative_mozconfig(self): """Ensure a relative MOZCONFIG can be found in the srcdir.""" relative_mozconfig = '.mconfig' os.environ['MOZCONFIG'] = relative_mozconfig srcdir = self.get_temp_dir() curdir = self.get_temp_dir() path = os.path.join(srcdir, relative_mozconfig) with open(path, 'w'): pass orig_dir = os.getcwd() try: os.chdir(curdir) self.assertEqual(os.path.normpath(find_mozconfig(srcdir)), os.path.normpath(path)) finally: os.chdir(orig_dir)
def test_find_multiple_but_identical_configs(self): """Ensure multiple relative-path MOZCONFIGs pointing at the same file are OK.""" relative_mozconfig = '../src/.mconfig' os.environ['MOZCONFIG'] = relative_mozconfig topdir = self.get_temp_dir() srcdir = os.path.join(topdir, 'src') os.mkdir(srcdir) curdir = os.path.join(topdir, 'obj') os.mkdir(curdir) path = os.path.join(srcdir, relative_mozconfig) with open(path, 'w'): pass orig_dir = os.getcwd() try: os.chdir(curdir) self.assertEqual(os.path.realpath(find_mozconfig(srcdir)), os.path.realpath(path)) finally: os.chdir(orig_dir)
def read_mozconfig(self, path=None): """Read the contents of a mozconfig into a data structure. This takes the path to a mozconfig to load. If the given path is AUTODETECT, will try to find a mozconfig from the environment using find_mozconfig(). mozconfig files are shell scripts. So, we can't just parse them. Instead, we run the shell script in a wrapper which allows us to record state from execution. Thus, the output from a mozconfig is a friendly static data structure. """ if path is self.AUTODETECT: path = find_mozconfig(self.topsrcdir) result = { "path": path, "topobjdir": None, "configure_args": None, "make_flags": None, "make_extra": None, "env": None, "vars": None, } if path is None: if "MOZ_OBJDIR" in os.environ: result["topobjdir"] = os.environ["MOZ_OBJDIR"] return result path = mozpath.normsep(path) result["configure_args"] = [] result["make_extra"] = [] result["make_flags"] = [] # Since mozconfig_loader is a shell script, running it "normally" # actually leads to two shell executions on Windows. Avoid this by # directly calling sh mozconfig_loader. shell = "sh" if "MOZILLABUILD" in os.environ: shell = os.environ["MOZILLABUILD"] + "/msys/bin/sh" if sys.platform == "win32": shell = shell + ".exe" command = [ shell, mozpath.normsep(self._loader_script), mozpath.normsep(self.topsrcdir), path, sys.executable, mozpath.join(mozpath.dirname(self._loader_script), "action", "dump_env.py"), ] try: env = dict(os.environ) env["PYTHONIOENCODING"] = "utf-8" # We need to capture stderr because that's where the shell sends # errors if execution fails. output = six.ensure_text( subprocess.check_output( command, stderr=subprocess.STDOUT, cwd=self.topsrcdir, env=env, universal_newlines=True, )) except subprocess.CalledProcessError as e: lines = e.output.splitlines() # Output before actual execution shouldn't be relevant. try: index = lines.index("------END_BEFORE_SOURCE") lines = lines[index + 1:] except ValueError: pass raise MozconfigLoadException(path, MOZCONFIG_BAD_EXIT_CODE, lines) try: parsed = self._parse_loader_output(output) except AssertionError: # _parse_loader_output uses assertions to verify the # well-formedness of the shell output; when these fail, it # generally means there was a problem with the output, but we # include the assertion traceback just to be sure. print("Assertion failed in _parse_loader_output:") traceback.print_exc() raise MozconfigLoadException(path, MOZCONFIG_BAD_OUTPUT, output.splitlines()) def diff_vars(vars_before, vars_after): set1 = set(vars_before.keys()) - self.IGNORE_SHELL_VARIABLES set2 = set(vars_after.keys()) - self.IGNORE_SHELL_VARIABLES added = set2 - set1 removed = set1 - set2 maybe_modified = set1 & set2 changed = { "added": {}, "removed": {}, "modified": {}, "unmodified": {} } for key in added: changed["added"][key] = vars_after[key] for key in removed: changed["removed"][key] = vars_before[key] for key in maybe_modified: if vars_before[key] != vars_after[key]: changed["modified"][key] = (vars_before[key], vars_after[key]) elif key in self.ENVIRONMENT_VARIABLES: # In order for irrelevant environment variable changes not # to incur in re-running configure, only a set of # environment variables are stored when they are # unmodified. Otherwise, changes such as using a different # terminal window, or even rebooting, would trigger # reconfigures. changed["unmodified"][key] = vars_after[key] return changed result["env"] = diff_vars(parsed["env_before"], parsed["env_after"]) # Environment variables also appear as shell variables, but that's # uninteresting duplication of information. Filter them out. def filt(x, y): return {k: v for k, v in x.items() if k not in y} result["vars"] = diff_vars( filt(parsed["vars_before"], parsed["env_before"]), filt(parsed["vars_after"], parsed["env_after"]), ) result["configure_args"] = [self._expand(o) for o in parsed["ac"]] if "MOZ_OBJDIR" in parsed["env_before"]: result["topobjdir"] = parsed["env_before"]["MOZ_OBJDIR"] mk = [self._expand(o) for o in parsed["mk"]] for o in mk: match = self.RE_MAKE_VARIABLE.match(o) if match is None: result["make_extra"].append(o) continue name, value = match.group("var"), match.group("value") if name == "MOZ_MAKE_FLAGS": result["make_flags"] = value.split() continue if name == "MOZ_OBJDIR": result["topobjdir"] = value if parsed["env_before"].get("MOZ_PROFILE_GENERATE") == "1": # If MOZ_OBJDIR is specified in the mozconfig, we need to # make sure that the '/instrumented' directory gets appended # for the first build to avoid an objdir mismatch when # running 'mach package' on Windows. result["topobjdir"] = mozpath.join(result["topobjdir"], "instrumented") continue result["make_extra"].append(o) return result