# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import argparse import json import os import subprocess import sys import tempfile from collections import defaultdict, deque from concurrent.futures import ProcessPoolExecutor, as_completed from copy import deepcopy from pathlib import Path from shutil import which import mozpack.path as mozpath from mozbuild.bootstrap import bootstrap_toolchain from mozbuild.dirutils import mkdir from mozbuild.frontend.sandbox import alphabetical_sorted license_header = """# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. """ generated_header = """ ### This moz.build was AUTOMATICALLY GENERATED from a GN config, ### ### DO NOT edit it by hand. ### """ class MozbuildWriter: def __init__(self, fh): self._fh = fh self.indent = "" self._indent_increment = 4 # We need to correlate a small amount of state here to figure out # which library template to use ("Library()" or "SharedLibrary()") self._library_name = None self._shared_library = None def mb_serialize(self, v): if isinstance(v, list): if len(v) <= 1: return repr(v) # Pretty print a list raw = json.dumps(v, indent=self._indent_increment) # Add the indent of the current indentation level return raw.replace("\n", "\n" + self.indent) if isinstance(v, bool): return repr(v) return '"%s"' % v def finalize(self): if self._library_name: self.write("\n") if self._shared_library: self.write_ln( "SharedLibrary(%s)" % self.mb_serialize(self._library_name) ) else: self.write_ln("Library(%s)" % self.mb_serialize(self._library_name)) def write(self, content): self._fh.write(content) def write_ln(self, line): self.write(self.indent) self.write(line) self.write("\n") def write_attrs(self, context_attrs): for k in sorted(context_attrs.keys()): v = context_attrs[k] if isinstance(v, (list, set)): self.write_mozbuild_list(k, v) elif isinstance(v, dict): self.write_mozbuild_dict(k, v) else: self.write_mozbuild_value(k, v) def write_mozbuild_list(self, key, value): if value: self.write("\n") self.write(self.indent + key) self.write(" += [\n " + self.indent) self.write( (",\n " + self.indent).join( alphabetical_sorted(self.mb_serialize(v) for v in value) ) ) self.write("\n") self.write_ln("]") def write_mozbuild_value(self, key, value): if value: if key == "LIBRARY_NAME": self._library_name = value elif key == "FORCE_SHARED_LIB": self._shared_library = True else: self.write("\n") self.write_ln("%s = %s" % (key, self.mb_serialize(value))) self.write("\n") def write_mozbuild_dict(self, key, value): # Templates we need to use instead of certain values. replacements = ( ( ("COMPILE_FLAGS", '"WARNINGS_AS_ERRORS"', "[]"), "AllowCompilerWarnings()", ), ) if value: self.write("\n") if key == "GeneratedFile": self.write_ln("GeneratedFile(") self.indent += " " * self._indent_increment for o in value["outputs"]: self.write_ln("%s," % (self.mb_serialize(o))) for k, v in sorted(value.items()): if k == "outputs": continue self.write_ln("%s=%s," % (k, self.mb_serialize(v))) self.indent = self.indent[self._indent_increment :] self.write_ln(")") return for k in sorted(value.keys()): v = value[k] subst_vals = key, self.mb_serialize(k), self.mb_serialize(v) wrote_ln = False for flags, tmpl in replacements: if subst_vals == flags: self.write_ln(tmpl) wrote_ln = True if not wrote_ln: self.write_ln("%s[%s] = %s" % subst_vals) def write_condition(self, values): def mk_condition(k, v): if not v: return 'not CONFIG["%s"]' % k return 'CONFIG["%s"] == %s' % (k, self.mb_serialize(v)) self.write("\n") self.write("if ") self.write( " and ".join(mk_condition(k, values[k]) for k in sorted(values.keys())) ) self.write(":\n") self.indent += " " * self._indent_increment def terminate_condition(self): assert len(self.indent) >= self._indent_increment self.indent = self.indent[self._indent_increment :] def find_deps(all_targets, target): all_deps = set() queue = deque([target]) while queue: item = queue.popleft() all_deps.add(item) for dep in all_targets[item]["deps"]: if dep not in all_deps: queue.append(dep) return all_deps def filter_gn_config(path, gn_result, sandbox_vars, input_vars, gn_target): gen_path = path / "gen" # Translates the raw output of gn into just what we'll need to generate a # mozbuild configuration. gn_out = {"targets": {}, "sandbox_vars": sandbox_vars} cpus = { "arm64": "aarch64", "x64": "x86_64", "mipsel": "mips32", "mips64el": "mips64", "loong64": "loongarch64", } oses = { "android": "Android", "linux": "Linux", "mac": "Darwin", "openbsd": "OpenBSD", "win": "WINNT", } mozbuild_args = { "MOZ_DEBUG": "1" if input_vars.get("is_debug") else None, "OS_TARGET": oses[input_vars["target_os"]], "TARGET_CPU": cpus.get(input_vars["target_cpu"], input_vars["target_cpu"]), } if "use_x11" in input_vars: mozbuild_args["MOZ_X11"] = "1" if input_vars["use_x11"] else None gn_out["mozbuild_args"] = mozbuild_args all_deps = find_deps(gn_result["targets"], gn_target) for target_fullname in all_deps: raw_spec = gn_result["targets"][target_fullname] if raw_spec["type"] == "action": # Special handling for the action type to avoid putting empty # arrays of args, script and outputs on all other types in `spec`. spec = {} for spec_attr in ( "type", "args", "script", "outputs", ): spec[spec_attr] = raw_spec.get(spec_attr, []) if spec_attr == "outputs": # Rebase outputs from an absolute path in the temp dir to a # path relative to the target dir. spec[spec_attr] = [ mozpath.relpath(d, path) for d in spec[spec_attr] ] gn_out["targets"][target_fullname] = spec # TODO: 'executable' will need to be handled here at some point as well. if raw_spec["type"] not in ("static_library", "shared_library", "source_set"): continue spec = {} for spec_attr in ( "type", "sources", "defines", "include_dirs", "cflags", "cflags_c", "cflags_cc", "cflags_objc", "cflags_objcc", "deps", "libs", ): spec[spec_attr] = raw_spec.get(spec_attr, []) if spec_attr == "defines": spec[spec_attr] = [ d for d in spec[spec_attr] if "CR_XCODE_VERSION" not in d and "CR_SYSROOT_HASH" not in d and "_FORTIFY_SOURCE" not in d ] if spec_attr == "include_dirs": # Rebase outputs from an absolute path in the temp dir to a path # relative to the target dir. spec[spec_attr] = [ d if gen_path != Path(d) else "!//gen" for d in spec[spec_attr] ] gn_out["targets"][target_fullname] = spec return gn_out def process_gn_config( gn_config, topsrcdir, srcdir, non_unified_sources, sandbox_vars, mozilla_flags ): # Translates a json gn config into attributes that can be used to write out # moz.build files for this configuration. # Much of this code is based on similar functionality in `gyp_reader.py`. mozbuild_attrs = {"mozbuild_args": gn_config.get("mozbuild_args", None), "dirs": {}} targets = gn_config["targets"] project_relsrcdir = mozpath.relpath(srcdir, topsrcdir) non_unified_sources = set([mozpath.normpath(s) for s in non_unified_sources]) def target_info(fullname): path, name = target_fullname.split(":") # Stripping '//' gives us a path relative to the project root, # adding a suffix avoids name collisions with libraries already # in the tree (like "webrtc"). return path.lstrip("//"), name + "_gn" def resolve_path(path): # GN will have resolved all these paths relative to the root of the # project indicated by "//". if path.startswith("//"): path = path[2:] if not path.startswith("/"): path = "/%s/%s" % (project_relsrcdir, path) return path # Process all targets from the given gn project and its dependencies. for target_fullname, spec in targets.items(): target_path, target_name = target_info(target_fullname) context_attrs = {} # Remove leading 'lib' from the target_name if any, and use as # library name. name = target_name if spec["type"] in ("static_library", "shared_library", "source_set", "action"): if name.startswith("lib"): name = name[3:] context_attrs["LIBRARY_NAME"] = str(name) else: raise Exception( "The following GN target type is not currently " 'consumed by moz.build: "%s". It may need to be ' "added, or you may need to re-run the " "`GnConfigGen` step." % spec["type"] ) if spec["type"] == "shared_library": context_attrs["FORCE_SHARED_LIB"] = True if spec["type"] == "action" and "script" in spec: flags = [ resolve_path(spec["script"]), resolve_path(""), ] + spec.get("args", []) context_attrs["GeneratedFile"] = { "script": "/python/mozbuild/mozbuild/action/file_generate_wrapper.py", "entry_point": "action", "outputs": [resolve_path(f) for f in spec["outputs"]], "flags": flags, } sources = [] unified_sources = [] extensions = set() use_defines_in_asflags = False for f in spec.get("sources", []): f = f.lstrip("//") ext = mozpath.splitext(f)[-1] extensions.add(ext) src = "%s/%s" % (project_relsrcdir, f) if ext == ".h" or ext == ".inc": continue elif ext == ".def": context_attrs["SYMBOLS_FILE"] = src elif ext != ".S" and src not in non_unified_sources: unified_sources.append("/%s" % src) else: sources.append("/%s" % src) # The Mozilla build system doesn't use DEFINES for building # ASFILES. if ext == ".s": use_defines_in_asflags = True context_attrs["SOURCES"] = sources context_attrs["UNIFIED_SOURCES"] = unified_sources context_attrs["DEFINES"] = {} for define in spec.get("defines", []): if "=" in define: name, value = define.split("=", 1) context_attrs["DEFINES"][name] = value else: context_attrs["DEFINES"][define] = True context_attrs["LOCAL_INCLUDES"] = [] for include in spec.get("include_dirs", []): if include.startswith("!"): include = "!" + resolve_path(include[1:]) else: include = resolve_path(include) # moz.build expects all LOCAL_INCLUDES to exist, so ensure they do. resolved = mozpath.abspath(mozpath.join(topsrcdir, include[1:])) if not os.path.exists(resolved): # GN files may refer to include dirs that are outside of the # tree or we simply didn't vendor. Print a warning in this case. if not resolved.endswith("gn-output/gen"): print( "Included path: '%s' does not exist, dropping include from GN " "configuration." % resolved, file=sys.stderr, ) continue if include in context_attrs["LOCAL_INCLUDES"]: continue context_attrs["LOCAL_INCLUDES"] += [include] context_attrs["ASFLAGS"] = spec.get("asflags_mozilla", []) if use_defines_in_asflags and context_attrs["DEFINES"]: context_attrs["ASFLAGS"] += ["-D" + d for d in context_attrs["DEFINES"]] suffix_map = { ".c": ("CFLAGS", ["cflags", "cflags_c"]), ".cpp": ("CXXFLAGS", ["cflags", "cflags_cc"]), ".cc": ("CXXFLAGS", ["cflags", "cflags_cc"]), ".m": ("CMFLAGS", ["cflags", "cflags_objc"]), ".mm": ("CMMFLAGS", ["cflags", "cflags_objcc"]), } variables = (suffix_map[e] for e in extensions if e in suffix_map) for var, flag_keys in variables: flags = [ _f for _k in flag_keys for _f in spec.get(_k, []) if _f in mozilla_flags ] for f in flags: # the result may be a string or a list. if isinstance(f, str): context_attrs.setdefault(var, []).append(f) else: context_attrs.setdefault(var, []).extend(f) context_attrs["OS_LIBS"] = [] for lib in spec.get("libs", []): lib_name = os.path.splitext(lib)[0] if lib.endswith(".framework"): context_attrs["OS_LIBS"] += ["-framework " + lib_name] else: context_attrs["OS_LIBS"] += [lib_name] # Add some features to all contexts. Put here in case LOCAL_INCLUDES # order matters. context_attrs["LOCAL_INCLUDES"] += [ "!/ipc/ipdl/_ipdlheaders", "/ipc/chromium/src", "/tools/profiler/public", ] # These get set via VC project file settings for normal GYP builds. # TODO: Determine if these defines are needed for GN builds. if gn_config["mozbuild_args"]["OS_TARGET"] == "WINNT": context_attrs["DEFINES"]["UNICODE"] = True context_attrs["DEFINES"]["_UNICODE"] = True context_attrs["COMPILE_FLAGS"] = {"OS_INCLUDES": []} for key, value in sandbox_vars.items(): if context_attrs.get(key) and isinstance(context_attrs[key], list): # If we have a key from sandbox_vars that's also been # populated here we use the value from sandbox_vars as our # basis rather than overriding outright. context_attrs[key] = value + context_attrs[key] elif context_attrs.get(key) and isinstance(context_attrs[key], dict): context_attrs[key].update(value) else: context_attrs[key] = value target_relsrcdir = mozpath.join(project_relsrcdir, target_path, target_name) mozbuild_attrs["dirs"][target_relsrcdir] = context_attrs return mozbuild_attrs def find_common_attrs(config_attributes): # Returns the intersection of the given configs and prunes the inputs # to no longer contain these common attributes. common_attrs = deepcopy(config_attributes[0]) def make_intersection(reference, input_attrs): # Modifies `reference` so that after calling this function it only # contains parts it had in common with in `input_attrs`. for k, input_value in input_attrs.items(): # Anything in `input_attrs` must match what's already in # `reference`. common_value = reference.get(k) if common_value: if isinstance(input_value, list): reference[k] = [ i for i in common_value if input_value.count(i) == common_value.count(i) ] elif isinstance(input_value, dict): reference[k] = { key: value for key, value in common_value.items() if key in input_value and value == input_value[key] } elif input_value != common_value: del reference[k] elif k in reference: del reference[k] # Additionally, any keys in `reference` that aren't in `input_attrs` # must be deleted. for k in set(reference.keys()) - set(input_attrs.keys()): del reference[k] def make_difference(reference, input_attrs): # Modifies `input_attrs` so that after calling this function it contains # no parts it has in common with in `reference`. for k, input_value in list(input_attrs.items()): common_value = reference.get(k) if common_value: if isinstance(input_value, list): input_attrs[k] = [ i for i in input_value if common_value.count(i) != input_value.count(i) ] elif isinstance(input_value, dict): input_attrs[k] = { key: value for key, value in input_value.items() if key not in common_value } else: del input_attrs[k] for config_attr_set in config_attributes[1:]: make_intersection(common_attrs, config_attr_set) for config_attr_set in config_attributes: make_difference(common_attrs, config_attr_set) return common_attrs def write_mozbuild(topsrcdir, write_mozbuild_variables, relsrcdir, configs): target_srcdir = mozpath.join(topsrcdir, relsrcdir) mkdir(target_srcdir) target_mozbuild = mozpath.join(target_srcdir, "moz.build") with open(target_mozbuild, "w") as fh: mb = MozbuildWriter(fh) mb.write(license_header) mb.write("\n") mb.write(generated_header) try: if relsrcdir in write_mozbuild_variables["INCLUDE_TK_CFLAGS_DIRS"]: mb.write('if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":\n') mb.write(' CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]\n') except KeyError: pass try: if relsrcdir in write_mozbuild_variables["INCLUDE_SYSTEM_GBM_HANDLING"]: mb.write('CXXFLAGS += CONFIG["MOZ_GBM_CFLAGS"]\n') mb.write('if not CONFIG["MOZ_SYSTEM_GBM"]:\n') mb.write(' LOCAL_INCLUDES += [ "/third_party/gbm/gbm/" ]\n') except KeyError: pass try: if relsrcdir in write_mozbuild_variables["INCLUDE_SYSTEM_LIBDRM_HANDLING"]: mb.write('CXXFLAGS += CONFIG["MOZ_LIBDRM_CFLAGS"]\n') mb.write('if not CONFIG["MOZ_SYSTEM_LIBDRM"]:\n') mb.write(' LOCAL_INCLUDES += [ "/third_party/drm/drm/",\n') mb.write(' "/third_party/drm/drm/include/",\n') mb.write( ' "/third_party/drm/drm/include/libdrm" ]\n' ) except KeyError: pass try: if ( relsrcdir in write_mozbuild_variables["INCLUDE_SYSTEM_PIPEWIRE_HANDLING"] ): mb.write('CXXFLAGS += CONFIG["MOZ_PIPEWIRE_CFLAGS"]\n') mb.write('if not CONFIG["MOZ_SYSTEM_PIPEWIRE"]:\n') mb.write(' LOCAL_INCLUDES += [ "/third_party/pipewire/" ]\n') except KeyError: pass try: if relsrcdir in write_mozbuild_variables["INCLUDE_SYSTEM_LIBVPX_HANDLING"]: mb.write('if not CONFIG["MOZ_SYSTEM_LIBVPX"]:\n') mb.write(' LOCAL_INCLUDES += [ "/media/libvpx/libvpx/" ]\n') mb.write(' CXXFLAGS += CONFIG["MOZ_LIBVPX_CFLAGS"]\n') except KeyError: pass try: if relsrcdir in write_mozbuild_variables["INCLUDE_SYSTEM_DAV1D_HANDLING"]: mb.write('if CONFIG["MOZ_SYSTEM_AV1"]:\n') mb.write(' CXXFLAGS += CONFIG["MOZ_SYSTEM_DAV1D_CFLAGS"]\n') mb.write(' CXXFLAGS += CONFIG["MOZ_SYSTEM_LIBAOM_CFLAGS"]\n') except KeyError: pass all_args = [args for args, _ in configs] # Start with attributes that will be a part of the mozconfig # for every configuration, then factor by other potentially useful # combinations. # FIXME: this is a time-bomb. See bug 1775202. for attrs in ( (), ("MOZ_DEBUG",), ("OS_TARGET",), ("TARGET_CPU",), ("MOZ_DEBUG", "OS_TARGET"), ("OS_TARGET", "MOZ_X11"), ("OS_TARGET", "TARGET_CPU"), ("OS_TARGET", "TARGET_CPU", "MOZ_X11"), ("OS_TARGET", "TARGET_CPU", "MOZ_DEBUG"), ("OS_TARGET", "TARGET_CPU", "MOZ_DEBUG", "MOZ_X11"), ): conditions = set() for args in all_args: cond = tuple((k, args.get(k) or "") for k in attrs) conditions.add(cond) for cond in sorted(conditions): common_attrs = find_common_attrs( [ attrs for args, attrs in configs if all((args.get(k) or "") == v for k, v in cond) ] ) if any(common_attrs.values()): if cond: mb.write_condition(dict(cond)) mb.write_attrs(common_attrs) if cond: mb.terminate_condition() mb.finalize() return target_mozbuild def write_mozbuild_files( topsrcdir, srcdir, all_mozbuild_results, write_mozbuild_variables, ): # Translate {config -> {dirs -> build info}} into # {dirs -> [(config, build_info)]} configs_by_dir = defaultdict(list) for config_attrs in all_mozbuild_results: mozbuild_args = config_attrs["mozbuild_args"] dirs = config_attrs["dirs"] for d, build_data in dirs.items(): configs_by_dir[d].append((mozbuild_args, build_data)) mozbuilds = set() # threading this section did not produce noticeable speed gains for relsrcdir, configs in sorted(configs_by_dir.items()): mozbuilds.add( write_mozbuild(topsrcdir, write_mozbuild_variables, relsrcdir, configs) ) # write the project moz.build file dirs_mozbuild = mozpath.join(srcdir, "moz.build") mozbuilds.add(dirs_mozbuild) with open(dirs_mozbuild, "w") as fh: mb = MozbuildWriter(fh) mb.write(license_header) mb.write("\n") mb.write(generated_header) # Not every srcdir is present for every config, which needs to be # reflected in the generated root moz.build. dirs_by_config = { tuple(v["mozbuild_args"].items()): set(v["dirs"].keys()) for v in all_mozbuild_results } for attrs in ( (), ("OS_TARGET",), ("OS_TARGET", "TARGET_CPU"), ("OS_TARGET", "TARGET_CPU", "MOZ_X11"), ): conditions = set() for args in dirs_by_config.keys(): cond = tuple((k, dict(args).get(k) or "") for k in attrs) conditions.add(cond) for cond in sorted(conditions): common_dirs = None for args, dir_set in dirs_by_config.items(): if all((dict(args).get(k) or "") == v for k, v in cond): if common_dirs is None: common_dirs = deepcopy(dir_set) else: common_dirs &= dir_set for args, dir_set in dirs_by_config.items(): if all(dict(args).get(k) == v for k, v in cond): dir_set -= common_dirs if common_dirs: if cond: mb.write_condition(dict(cond)) mb.write_mozbuild_list("DIRS", ["/%s" % d for d in common_dirs]) if cond: mb.terminate_condition() # Remove possibly stale moz.builds for root, dirs, files in os.walk(srcdir): if "moz.build" in files: file = os.path.join(root, "moz.build") if file not in mozbuilds: os.unlink(file) def generate_gn_config( topsrcdir, build_root_dir, target_dir, gn_binary, input_variables, sandbox_variables, gn_target, moz_build_flag, non_unified_sources, mozilla_flags, ): def str_for_arg(v): if v in (True, False): return str(v).lower() return '"%s"' % v build_root_dir = topsrcdir / build_root_dir srcdir = build_root_dir / target_dir input_variables = input_variables.copy() input_variables.update( { f"{moz_build_flag}": True, "concurrent_links": 1, "action_pool_depth": 1, } ) if input_variables["target_os"] == "win": input_variables.update( { "visual_studio_path": "/", "visual_studio_version": 2015, "wdk_path": "/", } ) if input_variables["target_os"] == "mac": input_variables.update( { "mac_sdk_path": "/", "enable_wmax_tokens": False, } ) gn_args = "--args=%s" % " ".join( ["%s=%s" % (k, str_for_arg(v)) for k, v in input_variables.items()] ) with tempfile.TemporaryDirectory() as tempdir: # On Mac, `tempdir` starts with /var which is a symlink to /private/var. # We resolve the symlinks in `tempdir` here so later usage with # relpath() does not lead to unexpected results, should it be used # together with another path that has symlinks resolved. resolved_tempdir = Path(tempdir).resolve() gen_args = [ gn_binary, "gen", str(resolved_tempdir), gn_args, "--ide=json", "--root=./", # must find the google build directory in this directory f"--dotfile={target_dir}/.gn", ] print('Running "%s"' % " ".join(gen_args), file=sys.stderr) subprocess.check_call(gen_args, cwd=build_root_dir, stderr=subprocess.STDOUT) gn_config_file = resolved_tempdir / "project.json" with open(gn_config_file) as fh: raw_json = fh.read() raw_json = raw_json.replace(f"{target_dir}/", "") raw_json = raw_json.replace(f"{target_dir}:", ":") gn_config = json.loads(raw_json) gn_config = filter_gn_config( resolved_tempdir, gn_config, sandbox_variables, input_variables, gn_target, ) gn_config = process_gn_config( gn_config, topsrcdir, srcdir, non_unified_sources, gn_config["sandbox_vars"], mozilla_flags, ) return gn_config def main(): parser = argparse.ArgumentParser() parser.add_argument("config", help="configuration in json format") args = parser.parse_args() gn_binary = bootstrap_toolchain("gn/gn") or which("gn") if not gn_binary: raise Exception("The GN program must be present to generate GN configs.") with open(args.config) as fh: config = json.load(fh) topsrcdir = Path(__file__).parent.parent.resolve() vars_set = [] for is_debug in (True, False): for target_os in ("android", "linux", "mac", "openbsd", "win"): target_cpus = ["x64"] if target_os in ("android", "linux", "mac", "win", "openbsd"): target_cpus.append("arm64") if target_os in ("android", "linux"): target_cpus.append("arm") if target_os in ("android", "linux", "win"): target_cpus.append("x86") if target_os in ("linux", "openbsd"): target_cpus.append("riscv64") if target_os == "linux": target_cpus.extend(["loong64", "ppc64", "mipsel", "mips64el"]) for target_cpu in target_cpus: vars = { "host_cpu": "x64", "is_debug": is_debug, "target_cpu": target_cpu, "target_os": target_os, } if target_os == "linux": for use_x11 in (True, False): vars["use_x11"] = use_x11 vars_set.append(vars.copy()) else: if target_os == "openbsd": vars["use_x11"] = True vars_set.append(vars) gn_configs = [] NUM_WORKERS = 5 with ProcessPoolExecutor(max_workers=NUM_WORKERS) as executor: # Submit tasks to the executor futures = { executor.submit( generate_gn_config, topsrcdir, config["build_root_dir"], config["target_dir"], gn_binary, vars, config["gn_sandbox_variables"], config["gn_target"], config["moz_build_flag"], config["non_unified_sources"], config["mozilla_flags"], ): vars for vars in vars_set } # Process completed tasks as they finish for future in as_completed(futures): try: gn_configs.append(future.result()) except Exception as e: print(f"[Task] Task failed with exception: {e}") print("All generation tasks have been processed.") print("Writing moz.build files") write_mozbuild_files( topsrcdir, topsrcdir / config["build_root_dir"] / config["target_dir"], gn_configs, config["write_mozbuild_variables"], ) if __name__ == "__main__": main()