summaryrefslogtreecommitdiffstats
path: root/python/mozbuild/mozbuild/frontend/emitter.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozbuild/mozbuild/frontend/emitter.py')
-rw-r--r--python/mozbuild/mozbuild/frontend/emitter.py1892
1 files changed, 1892 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/frontend/emitter.py b/python/mozbuild/mozbuild/frontend/emitter.py
new file mode 100644
index 0000000000..8d62072421
--- /dev/null
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -0,0 +1,1892 @@
+# 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 logging
+import os
+import sys
+import time
+import traceback
+from collections import OrderedDict, defaultdict
+
+import mozinfo
+import mozpack.path as mozpath
+import six
+import toml
+from mach.mixin.logging import LoggingMixin
+from mozpack.chrome.manifest import Manifest
+
+from mozbuild.base import ExecutionSummary
+from mozbuild.util import OrderedDefaultDict, memoize
+
+from ..testing import REFTEST_FLAVORS, TEST_MANIFESTS, SupportFilesConverter
+from .context import Context, ObjDirPath, Path, SourcePath, SubContext
+from .data import (
+ BaseRustProgram,
+ ChromeManifestEntry,
+ ComputedFlags,
+ ConfigFileSubstitution,
+ Defines,
+ DirectoryTraversal,
+ Exports,
+ ExternalSharedLibrary,
+ ExternalStaticLibrary,
+ FinalTargetFiles,
+ FinalTargetPreprocessedFiles,
+ GeneratedFile,
+ HostDefines,
+ HostLibrary,
+ HostProgram,
+ HostRustLibrary,
+ HostRustProgram,
+ HostSharedLibrary,
+ HostSimpleProgram,
+ HostSources,
+ InstallationTarget,
+ IPDLCollection,
+ JARManifest,
+ Library,
+ Linkable,
+ LocalInclude,
+ LocalizedFiles,
+ LocalizedPreprocessedFiles,
+ ObjdirFiles,
+ ObjdirPreprocessedFiles,
+ PerSourceFlag,
+ Program,
+ RustLibrary,
+ RustProgram,
+ RustTests,
+ SandboxedWasmLibrary,
+ SharedLibrary,
+ SimpleProgram,
+ Sources,
+ StaticLibrary,
+ TestHarnessFiles,
+ TestManifest,
+ UnifiedSources,
+ VariablePassthru,
+ WasmDefines,
+ WasmSources,
+ WebIDLCollection,
+ XPCOMComponentManifests,
+ XPIDLModule,
+)
+from .reader import SandboxValidationError
+
+
+class TreeMetadataEmitter(LoggingMixin):
+ """Converts the executed mozbuild files into data structures.
+
+ This is a bridge between reader.py and data.py. It takes what was read by
+ reader.BuildReader and converts it into the classes defined in the data
+ module.
+ """
+
+ def __init__(self, config):
+ self.populate_logger()
+
+ self.config = config
+
+ mozinfo.find_and_update_from_json(config.topobjdir)
+
+ self.info = dict(mozinfo.info)
+
+ self._libs = OrderedDefaultDict(list)
+ self._binaries = OrderedDict()
+ self._compile_dirs = set()
+ self._host_compile_dirs = set()
+ self._wasm_compile_dirs = set()
+ self._asm_compile_dirs = set()
+ self._compile_flags = dict()
+ self._compile_as_flags = dict()
+ self._linkage = []
+ self._static_linking_shared = set()
+ self._crate_verified_local = set()
+ self._crate_directories = dict()
+ self._idls = defaultdict(set)
+
+ # Keep track of external paths (third party build systems), starting
+ # from what we run a subconfigure in. We'll eliminate some directories
+ # as we traverse them with moz.build (e.g. js/src).
+ subconfigures = os.path.join(self.config.topobjdir, "subconfigures")
+ paths = []
+ if os.path.exists(subconfigures):
+ paths = open(subconfigures).read().splitlines()
+ self._external_paths = set(mozpath.normsep(d) for d in paths)
+
+ self._emitter_time = 0.0
+ self._object_count = 0
+ self._test_files_converter = SupportFilesConverter()
+
+ def summary(self):
+ return ExecutionSummary(
+ "Processed into {object_count:d} build config descriptors in "
+ "{execution_time:.2f}s",
+ execution_time=self._emitter_time,
+ object_count=self._object_count,
+ )
+
+ def emit(self, output, emitfn=None):
+ """Convert the BuildReader output into data structures.
+
+ The return value from BuildReader.read_topsrcdir() (a generator) is
+ typically fed into this function.
+ """
+ contexts = {}
+ emitfn = emitfn or self.emit_from_context
+
+ def emit_objs(objs):
+ for o in objs:
+ self._object_count += 1
+ yield o
+
+ for out in output:
+ # Nothing in sub-contexts is currently of interest to us. Filter
+ # them all out.
+ if isinstance(out, SubContext):
+ continue
+
+ if isinstance(out, Context):
+ # Keep all contexts around, we will need them later.
+ contexts[os.path.normcase(out.objdir)] = out
+
+ start = time.monotonic()
+ # We need to expand the generator for the timings to work.
+ objs = list(emitfn(out))
+ self._emitter_time += time.monotonic() - start
+
+ for o in emit_objs(objs):
+ yield o
+
+ else:
+ raise Exception("Unhandled output type: %s" % type(out))
+
+ # Don't emit Linkable objects when COMPILE_ENVIRONMENT is not set
+ if self.config.substs.get("COMPILE_ENVIRONMENT"):
+ start = time.monotonic()
+ objs = list(self._emit_libs_derived(contexts))
+ self._emitter_time += time.monotonic() - start
+
+ for o in emit_objs(objs):
+ yield o
+
+ def _emit_libs_derived(self, contexts):
+
+ # First aggregate idl sources.
+ webidl_attrs = [
+ ("GENERATED_EVENTS_WEBIDL_FILES", lambda c: c.generated_events_sources),
+ ("GENERATED_WEBIDL_FILES", lambda c: c.generated_sources),
+ ("PREPROCESSED_TEST_WEBIDL_FILES", lambda c: c.preprocessed_test_sources),
+ ("PREPROCESSED_WEBIDL_FILES", lambda c: c.preprocessed_sources),
+ ("TEST_WEBIDL_FILES", lambda c: c.test_sources),
+ ("WEBIDL_FILES", lambda c: c.sources),
+ ("WEBIDL_EXAMPLE_INTERFACES", lambda c: c.example_interfaces),
+ ]
+ ipdl_attrs = [
+ ("IPDL_SOURCES", lambda c: c.sources),
+ ("PREPROCESSED_IPDL_SOURCES", lambda c: c.preprocessed_sources),
+ ]
+ xpcom_attrs = [("XPCOM_MANIFESTS", lambda c: c.manifests)]
+
+ idl_sources = {}
+ for root, cls, attrs in (
+ (self.config.substs.get("WEBIDL_ROOT"), WebIDLCollection, webidl_attrs),
+ (self.config.substs.get("IPDL_ROOT"), IPDLCollection, ipdl_attrs),
+ (
+ self.config.substs.get("XPCOM_ROOT"),
+ XPCOMComponentManifests,
+ xpcom_attrs,
+ ),
+ ):
+ if root:
+ collection = cls(contexts[os.path.normcase(root)])
+ for var, src_getter in attrs:
+ src_getter(collection).update(self._idls[var])
+
+ idl_sources[root] = collection.all_source_files()
+ if isinstance(collection, WebIDLCollection):
+ # Test webidl sources are added here as a somewhat special
+ # case.
+ idl_sources[mozpath.join(root, "test")] = [
+ s for s in collection.all_test_cpp_basenames()
+ ]
+
+ yield collection
+
+ # Next do FINAL_LIBRARY linkage.
+ for lib in (l for libs in self._libs.values() for l in libs):
+ if not isinstance(lib, (StaticLibrary, RustLibrary)) or not lib.link_into:
+ continue
+ if lib.link_into not in self._libs:
+ raise SandboxValidationError(
+ 'FINAL_LIBRARY ("%s") does not match any LIBRARY_NAME'
+ % lib.link_into,
+ contexts[os.path.normcase(lib.objdir)],
+ )
+ candidates = self._libs[lib.link_into]
+
+ # When there are multiple candidates, but all are in the same
+ # directory and have a different type, we want all of them to
+ # have the library linked. The typical usecase is when building
+ # both a static and a shared library in a directory, and having
+ # that as a FINAL_LIBRARY.
+ if (
+ len(set(type(l) for l in candidates)) == len(candidates)
+ and len(set(l.objdir for l in candidates)) == 1
+ ):
+ for c in candidates:
+ c.link_library(lib)
+ else:
+ raise SandboxValidationError(
+ 'FINAL_LIBRARY ("%s") matches a LIBRARY_NAME defined in '
+ "multiple places:\n %s"
+ % (lib.link_into, "\n ".join(l.objdir for l in candidates)),
+ contexts[os.path.normcase(lib.objdir)],
+ )
+
+ # ...and USE_LIBS linkage.
+ for context, obj, variable in self._linkage:
+ self._link_libraries(context, obj, variable, idl_sources)
+
+ def recurse_refs(lib):
+ for o in lib.refs:
+ yield o
+ if isinstance(o, StaticLibrary):
+ for q in recurse_refs(o):
+ yield q
+
+ # Check that all static libraries refering shared libraries in
+ # USE_LIBS are linked into a shared library or program.
+ for lib in self._static_linking_shared:
+ if all(isinstance(o, StaticLibrary) for o in recurse_refs(lib)):
+ shared_libs = sorted(
+ l.basename
+ for l in lib.linked_libraries
+ if isinstance(l, SharedLibrary)
+ )
+ raise SandboxValidationError(
+ 'The static "%s" library is not used in a shared library '
+ "or a program, but USE_LIBS contains the following shared "
+ "library names:\n %s\n\nMaybe you can remove the "
+ 'static "%s" library?'
+ % (lib.basename, "\n ".join(shared_libs), lib.basename),
+ contexts[os.path.normcase(lib.objdir)],
+ )
+
+ @memoize
+ def rust_libraries(obj):
+ libs = []
+ for o in obj.linked_libraries:
+ if isinstance(o, (HostRustLibrary, RustLibrary)):
+ libs.append(o)
+ elif isinstance(o, (HostLibrary, StaticLibrary, SandboxedWasmLibrary)):
+ libs.extend(rust_libraries(o))
+ return libs
+
+ def check_rust_libraries(obj):
+ rust_libs = set(rust_libraries(obj))
+ if len(rust_libs) <= 1:
+ return
+ if isinstance(obj, (Library, HostLibrary)):
+ what = '"%s" library' % obj.basename
+ else:
+ what = '"%s" program' % obj.name
+ raise SandboxValidationError(
+ "Cannot link the following Rust libraries into the %s:\n"
+ "%s\nOnly one is allowed."
+ % (
+ what,
+ "\n".join(
+ " - %s" % r.basename
+ for r in sorted(rust_libs, key=lambda r: r.basename)
+ ),
+ ),
+ contexts[os.path.normcase(obj.objdir)],
+ )
+
+ # Propagate LIBRARY_DEFINES to all child libraries recursively.
+ def propagate_defines(outerlib, defines):
+ outerlib.lib_defines.update(defines)
+ for lib in outerlib.linked_libraries:
+ # Propagate defines only along FINAL_LIBRARY paths, not USE_LIBS
+ # paths.
+ if (
+ isinstance(lib, StaticLibrary)
+ and lib.link_into == outerlib.basename
+ ):
+ propagate_defines(lib, defines)
+
+ for lib in (l for libs in self._libs.values() for l in libs):
+ if isinstance(lib, Library):
+ propagate_defines(lib, lib.lib_defines)
+ check_rust_libraries(lib)
+ yield lib
+
+ for lib in (l for libs in self._libs.values() for l in libs):
+ lib_defines = list(lib.lib_defines.get_defines())
+ if lib_defines:
+ objdir_flags = self._compile_flags[lib.objdir]
+ objdir_flags.resolve_flags("LIBRARY_DEFINES", lib_defines)
+
+ objdir_flags = self._compile_as_flags.get(lib.objdir)
+ if objdir_flags:
+ objdir_flags.resolve_flags("LIBRARY_DEFINES", lib_defines)
+
+ for flags_obj in self._compile_flags.values():
+ yield flags_obj
+
+ for flags_obj in self._compile_as_flags.values():
+ yield flags_obj
+
+ for obj in self._binaries.values():
+ if isinstance(obj, Linkable):
+ check_rust_libraries(obj)
+ yield obj
+
+ LIBRARY_NAME_VAR = {
+ "host": "HOST_LIBRARY_NAME",
+ "target": "LIBRARY_NAME",
+ "wasm": "SANDBOXED_WASM_LIBRARY_NAME",
+ }
+
+ ARCH_VAR = {"host": "HOST_OS_ARCH", "target": "OS_TARGET"}
+
+ STDCXXCOMPAT_NAME = {"host": "host_stdc++compat", "target": "stdc++compat"}
+
+ def _link_libraries(self, context, obj, variable, extra_sources):
+ """Add linkage declarations to a given object."""
+ assert isinstance(obj, Linkable)
+
+ if context.objdir in extra_sources:
+ # All "extra sources" are .cpp for the moment, and happen to come
+ # first in order.
+ obj.sources[".cpp"] = extra_sources[context.objdir] + obj.sources[".cpp"]
+
+ for path in context.get(variable, []):
+ self._link_library(context, obj, variable, path)
+
+ # Link system libraries from OS_LIBS/HOST_OS_LIBS.
+ for lib in context.get(variable.replace("USE", "OS"), []):
+ obj.link_system_library(lib)
+
+ # We have to wait for all the self._link_library calls above to have
+ # happened for obj.cxx_link to be final.
+ # FIXME: Theoretically, HostSharedLibrary shouldn't be here (bug
+ # 1474022).
+ if (
+ not isinstance(
+ obj, (StaticLibrary, HostLibrary, HostSharedLibrary, BaseRustProgram)
+ )
+ and obj.cxx_link
+ ):
+ if (
+ context.config.substs.get("MOZ_STDCXX_COMPAT")
+ and context.config.substs.get(self.ARCH_VAR.get(obj.KIND)) == "Linux"
+ ):
+ self._link_library(
+ context, obj, variable, self.STDCXXCOMPAT_NAME[obj.KIND]
+ )
+ if obj.KIND == "target":
+ for lib in context.config.substs.get("STLPORT_LIBS", []):
+ obj.link_system_library(lib)
+
+ def _link_library(self, context, obj, variable, path):
+ force_static = path.startswith("static:") and obj.KIND == "target"
+ if force_static:
+ path = path[7:]
+ name = mozpath.basename(path)
+ dir = mozpath.dirname(path)
+ candidates = [l for l in self._libs[name] if l.KIND == obj.KIND]
+ if dir:
+ if dir.startswith("/"):
+ dir = mozpath.normpath(mozpath.join(obj.topobjdir, dir[1:]))
+ else:
+ dir = mozpath.normpath(mozpath.join(obj.objdir, dir))
+ dir = mozpath.relpath(dir, obj.topobjdir)
+ candidates = [l for l in candidates if l.relobjdir == dir]
+ if not candidates:
+ # If the given directory is under one of the external
+ # (third party) paths, use a fake library reference to
+ # there.
+ for d in self._external_paths:
+ if dir.startswith("%s/" % d):
+ candidates = [
+ self._get_external_library(dir, name, force_static)
+ ]
+ break
+
+ if not candidates:
+ raise SandboxValidationError(
+ '%s contains "%s", but there is no "%s" %s in %s.'
+ % (variable, path, name, self.LIBRARY_NAME_VAR[obj.KIND], dir),
+ context,
+ )
+
+ if len(candidates) > 1:
+ # If there's more than one remaining candidate, it could be
+ # that there are instances for the same library, in static and
+ # shared form.
+ libs = {}
+ for l in candidates:
+ key = mozpath.join(l.relobjdir, l.basename)
+ if force_static:
+ if isinstance(l, StaticLibrary):
+ libs[key] = l
+ else:
+ if key in libs and isinstance(l, SharedLibrary):
+ libs[key] = l
+ if key not in libs:
+ libs[key] = l
+ candidates = list(libs.values())
+ if force_static and not candidates:
+ if dir:
+ raise SandboxValidationError(
+ '%s contains "static:%s", but there is no static '
+ '"%s" %s in %s.'
+ % (variable, path, name, self.LIBRARY_NAME_VAR[obj.KIND], dir),
+ context,
+ )
+ raise SandboxValidationError(
+ '%s contains "static:%s", but there is no static "%s" '
+ "%s in the tree"
+ % (variable, name, name, self.LIBRARY_NAME_VAR[obj.KIND]),
+ context,
+ )
+
+ if not candidates:
+ raise SandboxValidationError(
+ '%s contains "%s", which does not match any %s in the tree.'
+ % (variable, path, self.LIBRARY_NAME_VAR[obj.KIND]),
+ context,
+ )
+
+ elif len(candidates) > 1:
+ paths = (mozpath.join(l.relsrcdir, "moz.build") for l in candidates)
+ raise SandboxValidationError(
+ '%s contains "%s", which matches a %s defined in multiple '
+ "places:\n %s"
+ % (
+ variable,
+ path,
+ self.LIBRARY_NAME_VAR[obj.KIND],
+ "\n ".join(paths),
+ ),
+ context,
+ )
+
+ elif force_static and not isinstance(candidates[0], StaticLibrary):
+ raise SandboxValidationError(
+ '%s contains "static:%s", but there is only a shared "%s" '
+ "in %s. You may want to add FORCE_STATIC_LIB=True in "
+ '%s/moz.build, or remove "static:".'
+ % (
+ variable,
+ path,
+ name,
+ candidates[0].relobjdir,
+ candidates[0].relobjdir,
+ ),
+ context,
+ )
+
+ elif isinstance(obj, StaticLibrary) and isinstance(
+ candidates[0], SharedLibrary
+ ):
+ self._static_linking_shared.add(obj)
+ obj.link_library(candidates[0])
+
+ @memoize
+ def _get_external_library(self, dir, name, force_static):
+ # Create ExternalStaticLibrary or ExternalSharedLibrary object with a
+ # context more or less truthful about where the external library is.
+ context = Context(config=self.config)
+ context.add_source(mozpath.join(self.config.topsrcdir, dir, "dummy"))
+ if force_static:
+ return ExternalStaticLibrary(context, name)
+ else:
+ return ExternalSharedLibrary(context, name)
+
+ def _parse_cargo_file(self, context):
+ """Parse the Cargo.toml file in context and return a Python object
+ representation of it. Raise a SandboxValidationError if the Cargo.toml
+ file does not exist. Return a tuple of (config, cargo_file)."""
+ cargo_file = mozpath.join(context.srcdir, "Cargo.toml")
+ if not os.path.exists(cargo_file):
+ raise SandboxValidationError(
+ "No Cargo.toml file found in %s" % cargo_file, context
+ )
+ with open(cargo_file, "r") as f:
+ return toml.load(f), cargo_file
+
+ def _verify_deps(
+ self, context, crate_dir, crate_name, dependencies, description="Dependency"
+ ):
+ """Verify that a crate's dependencies all specify local paths."""
+ for dep_crate_name, values in six.iteritems(dependencies):
+ # A simple version number.
+ if isinstance(values, (six.binary_type, six.text_type)):
+ raise SandboxValidationError(
+ "%s %s of crate %s does not list a path"
+ % (description, dep_crate_name, crate_name),
+ context,
+ )
+
+ dep_path = values.get("path", None)
+ if not dep_path:
+ raise SandboxValidationError(
+ "%s %s of crate %s does not list a path"
+ % (description, dep_crate_name, crate_name),
+ context,
+ )
+
+ # Try to catch the case where somebody listed a
+ # local path for development.
+ if os.path.isabs(dep_path):
+ raise SandboxValidationError(
+ "%s %s of crate %s has a non-relative path"
+ % (description, dep_crate_name, crate_name),
+ context,
+ )
+
+ if not os.path.exists(
+ mozpath.join(context.config.topsrcdir, crate_dir, dep_path)
+ ):
+ raise SandboxValidationError(
+ "%s %s of crate %s refers to a non-existent path"
+ % (description, dep_crate_name, crate_name),
+ context,
+ )
+
+ def _rust_library(
+ self, context, libname, static_args, is_gkrust=False, cls=RustLibrary
+ ):
+ # We need to note any Rust library for linking purposes.
+ config, cargo_file = self._parse_cargo_file(context)
+ crate_name = config["package"]["name"]
+
+ if crate_name != libname:
+ raise SandboxValidationError(
+ "library %s does not match Cargo.toml-defined package %s"
+ % (libname, crate_name),
+ context,
+ )
+
+ # Check that the [lib.crate-type] field is correct
+ lib_section = config.get("lib", None)
+ if not lib_section:
+ raise SandboxValidationError(
+ "Cargo.toml for %s has no [lib] section" % libname, context
+ )
+
+ crate_type = lib_section.get("crate-type", None)
+ if not crate_type:
+ raise SandboxValidationError(
+ "Can't determine a crate-type for %s from Cargo.toml" % libname, context
+ )
+
+ crate_type = crate_type[0]
+ if crate_type != "staticlib":
+ raise SandboxValidationError(
+ "crate-type %s is not permitted for %s" % (crate_type, libname), context
+ )
+
+ dependencies = set(six.iterkeys(config.get("dependencies", {})))
+
+ features = context.get(cls.FEATURES_VAR, [])
+ unique_features = set(features)
+ if len(features) != len(unique_features):
+ raise SandboxValidationError(
+ "features for %s should not contain duplicates: %s"
+ % (libname, features),
+ context,
+ )
+
+ return cls(
+ context,
+ libname,
+ cargo_file,
+ crate_type,
+ dependencies,
+ features,
+ is_gkrust,
+ **static_args,
+ )
+
+ def _handle_linkables(self, context, passthru, generated_files):
+ linkables = []
+ host_linkables = []
+ wasm_linkables = []
+
+ def add_program(prog, var):
+ if var.startswith("HOST_"):
+ host_linkables.append(prog)
+ else:
+ linkables.append(prog)
+
+ def check_unique_binary(program, kind):
+ if program in self._binaries:
+ raise SandboxValidationError(
+ 'Cannot use "%s" as %s name, '
+ "because it is already used in %s"
+ % (program, kind, self._binaries[program].relsrcdir),
+ context,
+ )
+
+ for kind, cls in [("PROGRAM", Program), ("HOST_PROGRAM", HostProgram)]:
+ program = context.get(kind)
+ if program:
+ check_unique_binary(program, kind)
+ self._binaries[program] = cls(context, program)
+ self._linkage.append(
+ (
+ context,
+ self._binaries[program],
+ kind.replace("PROGRAM", "USE_LIBS"),
+ )
+ )
+ add_program(self._binaries[program], kind)
+
+ all_rust_programs = []
+ for kind, cls in [
+ ("RUST_PROGRAMS", RustProgram),
+ ("HOST_RUST_PROGRAMS", HostRustProgram),
+ ]:
+ programs = context[kind]
+ if not programs:
+ continue
+
+ all_rust_programs.append((programs, kind, cls))
+
+ # Verify Rust program definitions.
+ if all_rust_programs:
+ config, cargo_file = self._parse_cargo_file(context)
+ bin_section = config.get("bin", None)
+ if not bin_section:
+ raise SandboxValidationError(
+ "Cargo.toml in %s has no [bin] section" % context.srcdir, context
+ )
+
+ defined_binaries = {b["name"] for b in bin_section}
+
+ for programs, kind, cls in all_rust_programs:
+ for program in programs:
+ if program not in defined_binaries:
+ raise SandboxValidationError(
+ "Cannot find Cargo.toml definition for %s" % program,
+ context,
+ )
+
+ check_unique_binary(program, kind)
+ self._binaries[program] = cls(context, program, cargo_file)
+ add_program(self._binaries[program], kind)
+
+ for kind, cls in [
+ ("SIMPLE_PROGRAMS", SimpleProgram),
+ ("CPP_UNIT_TESTS", SimpleProgram),
+ ("HOST_SIMPLE_PROGRAMS", HostSimpleProgram),
+ ]:
+ for program in context[kind]:
+ if program in self._binaries:
+ raise SandboxValidationError(
+ 'Cannot use "%s" in %s, '
+ "because it is already used in %s"
+ % (program, kind, self._binaries[program].relsrcdir),
+ context,
+ )
+ self._binaries[program] = cls(
+ context, program, is_unit_test=kind == "CPP_UNIT_TESTS"
+ )
+ self._linkage.append(
+ (
+ context,
+ self._binaries[program],
+ "HOST_USE_LIBS"
+ if kind == "HOST_SIMPLE_PROGRAMS"
+ else "USE_LIBS",
+ )
+ )
+ add_program(self._binaries[program], kind)
+
+ host_libname = context.get("HOST_LIBRARY_NAME")
+ libname = context.get("LIBRARY_NAME")
+
+ if host_libname:
+ if host_libname == libname:
+ raise SandboxValidationError(
+ "LIBRARY_NAME and HOST_LIBRARY_NAME must have a different value",
+ context,
+ )
+
+ is_rust_library = context.get("IS_RUST_LIBRARY")
+ if is_rust_library:
+ lib = self._rust_library(context, host_libname, {}, cls=HostRustLibrary)
+ elif context.get("FORCE_SHARED_LIB"):
+ lib = HostSharedLibrary(context, host_libname)
+ else:
+ lib = HostLibrary(context, host_libname)
+ self._libs[host_libname].append(lib)
+ self._linkage.append((context, lib, "HOST_USE_LIBS"))
+ host_linkables.append(lib)
+
+ final_lib = context.get("FINAL_LIBRARY")
+ if not libname and final_lib:
+ # If no LIBRARY_NAME is given, create one.
+ libname = context.relsrcdir.replace("/", "_")
+
+ static_lib = context.get("FORCE_STATIC_LIB")
+ shared_lib = context.get("FORCE_SHARED_LIB")
+
+ static_name = context.get("STATIC_LIBRARY_NAME")
+ shared_name = context.get("SHARED_LIBRARY_NAME")
+
+ is_framework = context.get("IS_FRAMEWORK")
+
+ soname = context.get("SONAME")
+
+ lib_defines = context.get("LIBRARY_DEFINES")
+
+ wasm_lib = context.get("SANDBOXED_WASM_LIBRARY_NAME")
+
+ shared_args = {}
+ static_args = {}
+
+ if final_lib:
+ if static_lib:
+ raise SandboxValidationError(
+ "FINAL_LIBRARY implies FORCE_STATIC_LIB. "
+ "Please remove the latter.",
+ context,
+ )
+ if shared_lib:
+ raise SandboxValidationError(
+ "FINAL_LIBRARY conflicts with FORCE_SHARED_LIB. "
+ "Please remove one.",
+ context,
+ )
+ if is_framework:
+ raise SandboxValidationError(
+ "FINAL_LIBRARY conflicts with IS_FRAMEWORK. " "Please remove one.",
+ context,
+ )
+ static_args["link_into"] = final_lib
+ static_lib = True
+
+ if libname:
+ if is_framework:
+ if soname:
+ raise SandboxValidationError(
+ "IS_FRAMEWORK conflicts with SONAME. " "Please remove one.",
+ context,
+ )
+ shared_lib = True
+ shared_args["variant"] = SharedLibrary.FRAMEWORK
+
+ if not static_lib and not shared_lib:
+ static_lib = True
+
+ if static_name:
+ if not static_lib:
+ raise SandboxValidationError(
+ "STATIC_LIBRARY_NAME requires FORCE_STATIC_LIB", context
+ )
+ static_args["real_name"] = static_name
+
+ if shared_name:
+ if not shared_lib:
+ raise SandboxValidationError(
+ "SHARED_LIBRARY_NAME requires FORCE_SHARED_LIB", context
+ )
+ shared_args["real_name"] = shared_name
+
+ if soname:
+ if not shared_lib:
+ raise SandboxValidationError(
+ "SONAME requires FORCE_SHARED_LIB", context
+ )
+ shared_args["soname"] = soname
+
+ if context.get("NO_EXPAND_LIBS"):
+ if not static_lib:
+ raise SandboxValidationError(
+ "NO_EXPAND_LIBS can only be set for static libraries.", context
+ )
+ static_args["no_expand_lib"] = True
+
+ if shared_lib and static_lib:
+ if not static_name and not shared_name:
+ raise SandboxValidationError(
+ "Both FORCE_STATIC_LIB and FORCE_SHARED_LIB are True, "
+ "but neither STATIC_LIBRARY_NAME or "
+ "SHARED_LIBRARY_NAME is set. At least one is required.",
+ context,
+ )
+ if static_name and not shared_name and static_name == libname:
+ raise SandboxValidationError(
+ "Both FORCE_STATIC_LIB and FORCE_SHARED_LIB are True, "
+ "but STATIC_LIBRARY_NAME is the same as LIBRARY_NAME, "
+ "and SHARED_LIBRARY_NAME is unset. Please either "
+ "change STATIC_LIBRARY_NAME or LIBRARY_NAME, or set "
+ "SHARED_LIBRARY_NAME.",
+ context,
+ )
+ if shared_name and not static_name and shared_name == libname:
+ raise SandboxValidationError(
+ "Both FORCE_STATIC_LIB and FORCE_SHARED_LIB are True, "
+ "but SHARED_LIBRARY_NAME is the same as LIBRARY_NAME, "
+ "and STATIC_LIBRARY_NAME is unset. Please either "
+ "change SHARED_LIBRARY_NAME or LIBRARY_NAME, or set "
+ "STATIC_LIBRARY_NAME.",
+ context,
+ )
+ if shared_name and static_name and shared_name == static_name:
+ raise SandboxValidationError(
+ "Both FORCE_STATIC_LIB and FORCE_SHARED_LIB are True, "
+ "but SHARED_LIBRARY_NAME is the same as "
+ "STATIC_LIBRARY_NAME. Please change one of them.",
+ context,
+ )
+
+ symbols_file = context.get("SYMBOLS_FILE")
+ if symbols_file:
+ if not shared_lib:
+ raise SandboxValidationError(
+ "SYMBOLS_FILE can only be used with a SHARED_LIBRARY.", context
+ )
+ if context.get("DEFFILE"):
+ raise SandboxValidationError(
+ "SYMBOLS_FILE cannot be used along DEFFILE.", context
+ )
+ if isinstance(symbols_file, SourcePath):
+ if not os.path.exists(symbols_file.full_path):
+ raise SandboxValidationError(
+ "Path specified in SYMBOLS_FILE does not exist: %s "
+ "(resolved to %s)" % (symbols_file, symbols_file.full_path),
+ context,
+ )
+ shared_args["symbols_file"] = True
+ else:
+ if symbols_file.target_basename not in generated_files:
+ raise SandboxValidationError(
+ (
+ "Objdir file specified in SYMBOLS_FILE not in "
+ + "GENERATED_FILES: %s"
+ )
+ % (symbols_file,),
+ context,
+ )
+ shared_args["symbols_file"] = symbols_file.target_basename
+
+ if shared_lib:
+ lib = SharedLibrary(context, libname, **shared_args)
+ self._libs[libname].append(lib)
+ self._linkage.append((context, lib, "USE_LIBS"))
+ linkables.append(lib)
+ if not lib.installed:
+ generated_files.add(lib.lib_name)
+ if symbols_file and isinstance(symbols_file, SourcePath):
+ script = mozpath.join(
+ mozpath.dirname(mozpath.dirname(__file__)),
+ "action",
+ "generate_symbols_file.py",
+ )
+ defines = ()
+ if lib.defines:
+ defines = lib.defines.get_defines()
+ yield GeneratedFile(
+ context,
+ script,
+ "generate_symbols_file",
+ lib.symbols_file,
+ [symbols_file],
+ defines,
+ required_during_compile=[lib.symbols_file],
+ )
+ if static_lib:
+ is_rust_library = context.get("IS_RUST_LIBRARY")
+ if is_rust_library:
+ lib = self._rust_library(
+ context,
+ libname,
+ static_args,
+ is_gkrust=bool(context.get("IS_GKRUST")),
+ )
+ else:
+ lib = StaticLibrary(context, libname, **static_args)
+ self._libs[libname].append(lib)
+ self._linkage.append((context, lib, "USE_LIBS"))
+ linkables.append(lib)
+
+ if lib_defines:
+ if not libname:
+ raise SandboxValidationError(
+ "LIBRARY_DEFINES needs a " "LIBRARY_NAME to take effect",
+ context,
+ )
+ lib.lib_defines.update(lib_defines)
+
+ if wasm_lib:
+ if wasm_lib == libname:
+ raise SandboxValidationError(
+ "SANDBOXED_WASM_LIBRARY_NAME and LIBRARY_NAME must have a "
+ "different value.",
+ context,
+ )
+ if wasm_lib == host_libname:
+ raise SandboxValidationError(
+ "SANDBOXED_WASM_LIBRARY_NAME and HOST_LIBRARY_NAME must "
+ "have a different value.",
+ context,
+ )
+ if wasm_lib == shared_name:
+ raise SandboxValidationError(
+ "SANDBOXED_WASM_LIBRARY_NAME and SHARED_NAME must have a "
+ "different value.",
+ context,
+ )
+ if wasm_lib == static_name:
+ raise SandboxValidationError(
+ "SANDBOXED_WASM_LIBRARY_NAME and STATIC_NAME must have a "
+ "different value.",
+ context,
+ )
+ lib = SandboxedWasmLibrary(context, wasm_lib)
+ self._libs[libname].append(lib)
+ wasm_linkables.append(lib)
+ self._wasm_compile_dirs.add(context.objdir)
+
+ seen = {}
+ for symbol in ("SOURCES", "UNIFIED_SOURCES"):
+ for src in context.get(symbol, []):
+ basename = os.path.splitext(os.path.basename(src))[0]
+ if basename in seen:
+ other_src, where = seen[basename]
+ extra = ""
+ if "UNIFIED_SOURCES" in (symbol, where):
+ extra = " in non-unified builds"
+ raise SandboxValidationError(
+ f"{src} from {symbol} would have the same object name "
+ f"as {other_src} from {where}{extra}.",
+ context,
+ )
+ seen[basename] = (src, symbol)
+
+ # Only emit sources if we have linkables defined in the same context.
+ # Note the linkables are not emitted in this function, but much later,
+ # after aggregation (because of e.g. USE_LIBS processing).
+ if not (linkables or host_linkables or wasm_linkables):
+ return
+
+ # TODO: objdirs with only host things in them shouldn't need target
+ # flags, but there's at least one Makefile.in (in
+ # build/unix/elfhack) that relies on the value of LDFLAGS being
+ # passed to one-off rules.
+ self._compile_dirs.add(context.objdir)
+
+ if host_linkables or any(
+ isinstance(l, (RustLibrary, RustProgram)) for l in linkables
+ ):
+ self._host_compile_dirs.add(context.objdir)
+
+ sources = defaultdict(list)
+ gen_sources = defaultdict(list)
+ all_flags = {}
+ for symbol in ("SOURCES", "HOST_SOURCES", "UNIFIED_SOURCES", "WASM_SOURCES"):
+ srcs = sources[symbol]
+ gen_srcs = gen_sources[symbol]
+ context_srcs = context.get(symbol, [])
+ seen_sources = set()
+ for f in context_srcs:
+ if f in seen_sources:
+ raise SandboxValidationError(
+ "Source file should only "
+ "be added to %s once: %s" % (symbol, f),
+ context,
+ )
+ seen_sources.add(f)
+ full_path = f.full_path
+ if isinstance(f, SourcePath):
+ srcs.append(full_path)
+ else:
+ assert isinstance(f, Path)
+ gen_srcs.append(full_path)
+ if symbol == "SOURCES":
+ context_flags = context_srcs[f]
+ if context_flags:
+ all_flags[full_path] = context_flags
+
+ if isinstance(f, SourcePath) and not os.path.exists(full_path):
+ raise SandboxValidationError(
+ "File listed in %s does not "
+ "exist: '%s'" % (symbol, full_path),
+ context,
+ )
+
+ # Process the .cpp files generated by IPDL as generated sources within
+ # the context which declared the IPDL_SOURCES attribute.
+ ipdl_root = self.config.substs.get("IPDL_ROOT")
+ for symbol in ("IPDL_SOURCES", "PREPROCESSED_IPDL_SOURCES"):
+ context_srcs = context.get(symbol, [])
+ for f in context_srcs:
+ root, ext = mozpath.splitext(mozpath.basename(f))
+
+ suffix_map = {
+ ".ipdlh": [".cpp"],
+ ".ipdl": [".cpp", "Child.cpp", "Parent.cpp"],
+ }
+ if ext not in suffix_map:
+ raise SandboxValidationError(
+ "Unexpected extension for IPDL source %s" % ext
+ )
+
+ gen_sources["UNIFIED_SOURCES"].extend(
+ mozpath.join(ipdl_root, root + suffix) for suffix in suffix_map[ext]
+ )
+
+ no_pgo = context.get("NO_PGO")
+ no_pgo_sources = [f for f, flags in six.iteritems(all_flags) if flags.no_pgo]
+ if no_pgo:
+ if no_pgo_sources:
+ raise SandboxValidationError(
+ "NO_PGO and SOURCES[...].no_pgo " "cannot be set at the same time",
+ context,
+ )
+ passthru.variables["NO_PROFILE_GUIDED_OPTIMIZE"] = no_pgo
+ if no_pgo_sources:
+ passthru.variables["NO_PROFILE_GUIDED_OPTIMIZE"] = no_pgo_sources
+
+ # A map from "canonical suffixes" for a particular source file
+ # language to the range of suffixes associated with that language.
+ #
+ # We deliberately don't list the canonical suffix in the suffix list
+ # in the definition; we'll add it in programmatically after defining
+ # things.
+ suffix_map = {
+ ".s": set([".asm"]),
+ ".c": set(),
+ ".m": set(),
+ ".mm": set(),
+ ".cpp": set([".cc", ".cxx"]),
+ ".S": set(),
+ }
+
+ # The inverse of the above, mapping suffixes to their canonical suffix.
+ canonicalized_suffix_map = {}
+ for suffix, alternatives in six.iteritems(suffix_map):
+ alternatives.add(suffix)
+ for a in alternatives:
+ canonicalized_suffix_map[a] = suffix
+
+ # A map from moz.build variables to the canonical suffixes of file
+ # kinds that can be listed therein.
+ all_suffixes = list(suffix_map.keys())
+ varmap = dict(
+ SOURCES=(Sources, all_suffixes),
+ HOST_SOURCES=(HostSources, [".c", ".mm", ".cpp"]),
+ UNIFIED_SOURCES=(UnifiedSources, [".c", ".mm", ".m", ".cpp"]),
+ )
+ # Only include a WasmSources context if there are any WASM_SOURCES.
+ # (This is going to matter later because we inject an extra .c file to
+ # compile with the wasm compiler if, and only if, there are any WASM
+ # sources.)
+ if sources["WASM_SOURCES"] or gen_sources["WASM_SOURCES"]:
+ varmap["WASM_SOURCES"] = (WasmSources, [".c", ".cpp"])
+ # Track whether there are any C++ source files.
+ # Technically this won't do the right thing for SIMPLE_PROGRAMS in
+ # a directory with mixed C and C++ source, but it's not that important.
+ cxx_sources = defaultdict(bool)
+
+ # Source files to track for linkables associated with this context.
+ ctxt_sources = defaultdict(lambda: defaultdict(list))
+
+ for variable, (klass, suffixes) in varmap.items():
+ # Group static and generated files by their canonical suffixes, and
+ # ensure we haven't been given filetypes that we don't recognize.
+ by_canonical_suffix = defaultdict(lambda: {"static": [], "generated": []})
+ for srcs, key in (
+ (sources[variable], "static"),
+ (gen_sources[variable], "generated"),
+ ):
+ for f in srcs:
+ canonical_suffix = canonicalized_suffix_map.get(
+ mozpath.splitext(f)[1]
+ )
+ if canonical_suffix not in suffixes:
+ raise SandboxValidationError(
+ "%s has an unknown file type." % f, context
+ )
+ by_canonical_suffix[canonical_suffix][key].append(f)
+
+ # Yield an object for each canonical suffix, grouping generated and
+ # static sources together to allow them to be unified together.
+ for canonical_suffix in sorted(by_canonical_suffix.keys()):
+ if canonical_suffix in (".cpp", ".mm"):
+ cxx_sources[variable] = True
+ elif canonical_suffix in (".s", ".S"):
+ self._asm_compile_dirs.add(context.objdir)
+ src_group = by_canonical_suffix[canonical_suffix]
+ obj = klass(
+ context,
+ src_group["static"],
+ src_group["generated"],
+ canonical_suffix,
+ )
+ srcs = list(obj.files)
+ if isinstance(obj, UnifiedSources) and obj.have_unified_mapping:
+ srcs = sorted(dict(obj.unified_source_mapping).keys())
+ ctxt_sources[variable][canonical_suffix] += srcs
+ yield obj
+
+ if ctxt_sources:
+ for linkable in linkables:
+ for target_var in ("SOURCES", "UNIFIED_SOURCES"):
+ for suffix, srcs in ctxt_sources[target_var].items():
+ linkable.sources[suffix] += srcs
+ for host_linkable in host_linkables:
+ for suffix, srcs in ctxt_sources["HOST_SOURCES"].items():
+ host_linkable.sources[suffix] += srcs
+ for wasm_linkable in wasm_linkables:
+ for suffix, srcs in ctxt_sources["WASM_SOURCES"].items():
+ wasm_linkable.sources[suffix] += srcs
+
+ for f, flags in sorted(six.iteritems(all_flags)):
+ if flags.flags:
+ ext = mozpath.splitext(f)[1]
+ yield PerSourceFlag(context, f, flags.flags)
+
+ # If there are any C++ sources, set all the linkables defined here
+ # to require the C++ linker.
+ for vars, linkable_items in (
+ (("SOURCES", "UNIFIED_SOURCES"), linkables),
+ (("HOST_SOURCES",), host_linkables),
+ ):
+ for var in vars:
+ if cxx_sources[var]:
+ for l in linkable_items:
+ l.cxx_link = True
+ break
+
+ def emit_from_context(self, context):
+ """Convert a Context to tree metadata objects.
+
+ This is a generator of mozbuild.frontend.data.ContextDerived instances.
+ """
+
+ # We only want to emit an InstallationTarget if one of the consulted
+ # variables is defined. Later on, we look up FINAL_TARGET, which has
+ # the side-effect of populating it. So, we need to do this lookup
+ # early.
+ if any(k in context for k in ("FINAL_TARGET", "XPI_NAME", "DIST_SUBDIR")):
+ yield InstallationTarget(context)
+
+ # We always emit a directory traversal descriptor. This is needed by
+ # the recursive make backend.
+ for o in self._emit_directory_traversal_from_context(context):
+ yield o
+
+ for obj in self._process_xpidl(context):
+ yield obj
+
+ computed_flags = ComputedFlags(context, context["COMPILE_FLAGS"])
+ computed_link_flags = ComputedFlags(context, context["LINK_FLAGS"])
+ computed_host_flags = ComputedFlags(context, context["HOST_COMPILE_FLAGS"])
+ computed_as_flags = ComputedFlags(context, context["ASM_FLAGS"])
+ computed_wasm_flags = ComputedFlags(context, context["WASM_FLAGS"])
+
+ # Proxy some variables as-is until we have richer classes to represent
+ # them. We should aim to keep this set small because it violates the
+ # desired abstraction of the build definition away from makefiles.
+ passthru = VariablePassthru(context)
+ varlist = [
+ "EXTRA_DSO_LDOPTS",
+ "RCFILE",
+ "RCINCLUDE",
+ "WIN32_EXE_LDFLAGS",
+ "USE_EXTENSION_MANIFEST",
+ ]
+ for v in varlist:
+ if v in context and context[v]:
+ passthru.variables[v] = context[v]
+
+ if (
+ context.config.substs.get("OS_TARGET") == "WINNT"
+ and context["DELAYLOAD_DLLS"]
+ ):
+ if context.config.substs.get("CC_TYPE") != "clang":
+ context["LDFLAGS"].extend(
+ [("-DELAYLOAD:%s" % dll) for dll in context["DELAYLOAD_DLLS"]]
+ )
+ else:
+ context["LDFLAGS"].extend(
+ [
+ ("-Wl,-Xlink=-DELAYLOAD:%s" % dll)
+ for dll in context["DELAYLOAD_DLLS"]
+ ]
+ )
+ context["OS_LIBS"].append("delayimp")
+
+ for v in ["CMFLAGS", "CMMFLAGS"]:
+ if v in context and context[v]:
+ passthru.variables["MOZBUILD_" + v] = context[v]
+
+ for v in ["CXXFLAGS", "CFLAGS"]:
+ if v in context and context[v]:
+ computed_flags.resolve_flags("MOZBUILD_%s" % v, context[v])
+
+ for v in ["WASM_CFLAGS", "WASM_CXXFLAGS"]:
+ if v in context and context[v]:
+ computed_wasm_flags.resolve_flags("MOZBUILD_%s" % v, context[v])
+
+ for v in ["HOST_CXXFLAGS", "HOST_CFLAGS"]:
+ if v in context and context[v]:
+ computed_host_flags.resolve_flags("MOZBUILD_%s" % v, context[v])
+
+ if "LDFLAGS" in context and context["LDFLAGS"]:
+ computed_link_flags.resolve_flags("MOZBUILD", context["LDFLAGS"])
+
+ deffile = context.get("DEFFILE")
+ if deffile and context.config.substs.get("OS_TARGET") == "WINNT":
+ if isinstance(deffile, SourcePath):
+ if not os.path.exists(deffile.full_path):
+ raise SandboxValidationError(
+ "Path specified in DEFFILE does not exist: %s "
+ "(resolved to %s)" % (deffile, deffile.full_path),
+ context,
+ )
+ path = mozpath.relpath(deffile.full_path, context.objdir)
+ else:
+ path = deffile.target_basename
+
+ if context.config.substs.get("GNU_CC"):
+ computed_link_flags.resolve_flags("DEFFILE", [path])
+ else:
+ computed_link_flags.resolve_flags("DEFFILE", ["-DEF:" + path])
+
+ dist_install = context["DIST_INSTALL"]
+ if dist_install is True:
+ passthru.variables["DIST_INSTALL"] = True
+ elif dist_install is False:
+ passthru.variables["NO_DIST_INSTALL"] = True
+
+ # Ideally, this should be done in templates, but this is difficult at
+ # the moment because USE_STATIC_LIBS can be set after a template
+ # returns. Eventually, with context-based templates, it will be
+ # possible.
+ if context.config.substs.get(
+ "OS_ARCH"
+ ) == "WINNT" and not context.config.substs.get("GNU_CC"):
+ use_static_lib = context.get(
+ "USE_STATIC_LIBS"
+ ) and not context.config.substs.get("MOZ_ASAN")
+ rtl_flag = "-MT" if use_static_lib else "-MD"
+ if context.config.substs.get("MOZ_DEBUG") and not context.config.substs.get(
+ "MOZ_NO_DEBUG_RTL"
+ ):
+ rtl_flag += "d"
+ computed_flags.resolve_flags("RTL", [rtl_flag])
+ if not context.config.substs.get("CROSS_COMPILE"):
+ computed_host_flags.resolve_flags("RTL", [rtl_flag])
+
+ generated_files = set()
+ localized_generated_files = set()
+ for obj in self._process_generated_files(context):
+ for f in obj.outputs:
+ generated_files.add(f)
+ if obj.localized:
+ localized_generated_files.add(f)
+ yield obj
+
+ for path in context["CONFIGURE_SUBST_FILES"]:
+ sub = self._create_substitution(ConfigFileSubstitution, context, path)
+ generated_files.add(str(sub.relpath))
+ yield sub
+
+ for defines_var, cls, backend_flags in (
+ ("DEFINES", Defines, (computed_flags, computed_as_flags)),
+ ("HOST_DEFINES", HostDefines, (computed_host_flags,)),
+ ("WASM_DEFINES", WasmDefines, (computed_wasm_flags,)),
+ ):
+ defines = context.get(defines_var)
+ if defines:
+ defines_obj = cls(context, defines)
+ if isinstance(defines_obj, Defines):
+ # DEFINES have consumers outside the compile command line,
+ # HOST_DEFINES do not.
+ yield defines_obj
+ else:
+ # If we don't have explicitly set defines we need to make sure
+ # initialized values if present end up in computed flags.
+ defines_obj = cls(context, context[defines_var])
+
+ defines_from_obj = list(defines_obj.get_defines())
+ if defines_from_obj:
+ for flags in backend_flags:
+ flags.resolve_flags(defines_var, defines_from_obj)
+
+ idl_vars = (
+ "GENERATED_EVENTS_WEBIDL_FILES",
+ "GENERATED_WEBIDL_FILES",
+ "PREPROCESSED_TEST_WEBIDL_FILES",
+ "PREPROCESSED_WEBIDL_FILES",
+ "TEST_WEBIDL_FILES",
+ "WEBIDL_FILES",
+ "IPDL_SOURCES",
+ "PREPROCESSED_IPDL_SOURCES",
+ "XPCOM_MANIFESTS",
+ )
+ for context_var in idl_vars:
+ for name in context.get(context_var, []):
+ self._idls[context_var].add(mozpath.join(context.srcdir, name))
+ # WEBIDL_EXAMPLE_INTERFACES do not correspond to files.
+ for name in context.get("WEBIDL_EXAMPLE_INTERFACES", []):
+ self._idls["WEBIDL_EXAMPLE_INTERFACES"].add(name)
+
+ local_includes = []
+ for local_include in context.get("LOCAL_INCLUDES", []):
+ full_path = local_include.full_path
+ if not isinstance(local_include, ObjDirPath):
+ if not os.path.exists(full_path):
+ raise SandboxValidationError(
+ "Path specified in LOCAL_INCLUDES does not exist: %s (resolved to %s)"
+ % (local_include, full_path),
+ context,
+ )
+ if not os.path.isdir(full_path):
+ raise SandboxValidationError(
+ "Path specified in LOCAL_INCLUDES "
+ "is a filename, but a directory is required: %s "
+ "(resolved to %s)" % (local_include, full_path),
+ context,
+ )
+ if (
+ full_path == context.config.topsrcdir
+ or full_path == context.config.topobjdir
+ ):
+ raise SandboxValidationError(
+ "Path specified in LOCAL_INCLUDES "
+ "(%s) resolves to the topsrcdir or topobjdir (%s), which is "
+ "not allowed" % (local_include, full_path),
+ context,
+ )
+ include_obj = LocalInclude(context, local_include)
+ local_includes.append(include_obj.path.full_path)
+ yield include_obj
+
+ computed_flags.resolve_flags(
+ "LOCAL_INCLUDES", ["-I%s" % p for p in local_includes]
+ )
+ computed_as_flags.resolve_flags(
+ "LOCAL_INCLUDES", ["-I%s" % p for p in local_includes]
+ )
+ computed_host_flags.resolve_flags(
+ "LOCAL_INCLUDES", ["-I%s" % p for p in local_includes]
+ )
+ computed_wasm_flags.resolve_flags(
+ "LOCAL_INCLUDES", ["-I%s" % p for p in local_includes]
+ )
+
+ for obj in self._handle_linkables(context, passthru, generated_files):
+ yield obj
+
+ generated_files.update(
+ [
+ "%s%s" % (k, self.config.substs.get("BIN_SUFFIX", ""))
+ for k in self._binaries.keys()
+ ]
+ )
+
+ components = []
+ for var, cls in (
+ ("EXPORTS", Exports),
+ ("FINAL_TARGET_FILES", FinalTargetFiles),
+ ("FINAL_TARGET_PP_FILES", FinalTargetPreprocessedFiles),
+ ("LOCALIZED_FILES", LocalizedFiles),
+ ("LOCALIZED_PP_FILES", LocalizedPreprocessedFiles),
+ ("OBJDIR_FILES", ObjdirFiles),
+ ("OBJDIR_PP_FILES", ObjdirPreprocessedFiles),
+ ("TEST_HARNESS_FILES", TestHarnessFiles),
+ ):
+ all_files = context.get(var)
+ if not all_files:
+ continue
+ if dist_install is False and var != "TEST_HARNESS_FILES":
+ raise SandboxValidationError(
+ "%s cannot be used with DIST_INSTALL = False" % var, context
+ )
+ has_prefs = False
+ has_resources = False
+ for base, files in all_files.walk():
+ if var == "TEST_HARNESS_FILES" and not base:
+ raise SandboxValidationError(
+ "Cannot install files to the root of TEST_HARNESS_FILES",
+ context,
+ )
+ if base == "components":
+ components.extend(files)
+ if base == "defaults/pref":
+ has_prefs = True
+ if mozpath.split(base)[0] == "res":
+ has_resources = True
+ for f in files:
+ if (
+ var
+ in (
+ "FINAL_TARGET_PP_FILES",
+ "OBJDIR_PP_FILES",
+ "LOCALIZED_PP_FILES",
+ )
+ and not isinstance(f, SourcePath)
+ ):
+ raise SandboxValidationError(
+ ("Only source directory paths allowed in " + "%s: %s")
+ % (var, f),
+ context,
+ )
+ if var.startswith("LOCALIZED_"):
+ if isinstance(f, SourcePath):
+ if f.startswith("en-US/"):
+ pass
+ elif "locales/en-US/" in f:
+ pass
+ else:
+ raise SandboxValidationError(
+ "%s paths must start with `en-US/` or "
+ "contain `locales/en-US/`: %s" % (var, f),
+ context,
+ )
+
+ if not isinstance(f, ObjDirPath):
+ path = f.full_path
+ if "*" not in path and not os.path.exists(path):
+ raise SandboxValidationError(
+ "File listed in %s does not exist: %s" % (var, path),
+ context,
+ )
+ else:
+ # TODO: Bug 1254682 - The '/' check is to allow
+ # installing files generated from other directories,
+ # which is done occasionally for tests. However, it
+ # means we don't fail early if the file isn't actually
+ # created by the other moz.build file.
+ if f.target_basename not in generated_files and "/" not in f:
+ raise SandboxValidationError(
+ (
+ "Objdir file listed in %s not in "
+ + "GENERATED_FILES: %s"
+ )
+ % (var, f),
+ context,
+ )
+
+ if var.startswith("LOCALIZED_"):
+ # Further require that LOCALIZED_FILES are from
+ # LOCALIZED_GENERATED_FILES.
+ if f.target_basename not in localized_generated_files:
+ raise SandboxValidationError(
+ (
+ "Objdir file listed in %s not in "
+ + "LOCALIZED_GENERATED_FILES: %s"
+ )
+ % (var, f),
+ context,
+ )
+ else:
+ # Additionally, don't allow LOCALIZED_GENERATED_FILES to be used
+ # in anything *but* LOCALIZED_FILES.
+ if f.target_basename in localized_generated_files:
+ raise SandboxValidationError(
+ (
+ "Outputs of LOCALIZED_GENERATED_FILES cannot "
+ "be used in %s: %s"
+ )
+ % (var, f),
+ context,
+ )
+
+ # Addons (when XPI_NAME is defined) and Applications (when
+ # DIST_SUBDIR is defined) use a different preferences directory
+ # (default/preferences) from the one the GRE uses (defaults/pref).
+ # Hence, we move the files from the latter to the former in that
+ # case.
+ if has_prefs and (context.get("XPI_NAME") or context.get("DIST_SUBDIR")):
+ all_files.defaults.preferences += all_files.defaults.pref
+ del all_files.defaults._children["pref"]
+
+ if has_resources and (
+ context.get("DIST_SUBDIR") or context.get("XPI_NAME")
+ ):
+ raise SandboxValidationError(
+ "RESOURCES_FILES cannot be used with DIST_SUBDIR or " "XPI_NAME.",
+ context,
+ )
+
+ yield cls(context, all_files)
+
+ for c in components:
+ if c.endswith(".manifest"):
+ yield ChromeManifestEntry(
+ context,
+ "chrome.manifest",
+ Manifest("components", mozpath.basename(c)),
+ )
+
+ rust_tests = context.get("RUST_TESTS", [])
+ if rust_tests:
+ # TODO: more sophisticated checking of the declared name vs.
+ # contents of the Cargo.toml file.
+ features = context.get("RUST_TEST_FEATURES", [])
+
+ yield RustTests(context, rust_tests, features)
+
+ for obj in self._process_test_manifests(context):
+ yield obj
+
+ for obj in self._process_jar_manifests(context):
+ yield obj
+
+ computed_as_flags.resolve_flags("MOZBUILD", context.get("ASFLAGS"))
+
+ if context.get("USE_NASM") is True:
+ nasm = context.config.substs.get("NASM")
+ if not nasm:
+ raise SandboxValidationError("nasm is not available", context)
+ passthru.variables["AS"] = nasm
+ passthru.variables["AS_DASH_C_FLAG"] = ""
+ passthru.variables["ASOUTOPTION"] = "-o "
+ computed_as_flags.resolve_flags(
+ "OS", context.config.substs.get("NASM_ASFLAGS", [])
+ )
+
+ if context.get("USE_INTEGRATED_CLANGCL_AS") is True:
+ if context.config.substs.get("CC_TYPE") != "clang-cl":
+ raise SandboxValidationError("clang-cl is not available", context)
+ passthru.variables["AS"] = context.config.substs.get("CC")
+ passthru.variables["AS_DASH_C_FLAG"] = "-c"
+ passthru.variables["ASOUTOPTION"] = "-o "
+
+ if passthru.variables:
+ yield passthru
+
+ if context.objdir in self._compile_dirs:
+ self._compile_flags[context.objdir] = computed_flags
+ yield computed_link_flags
+
+ if context.objdir in self._asm_compile_dirs:
+ self._compile_as_flags[context.objdir] = computed_as_flags
+
+ if context.objdir in self._host_compile_dirs:
+ yield computed_host_flags
+
+ if context.objdir in self._wasm_compile_dirs:
+ yield computed_wasm_flags
+
+ def _create_substitution(self, cls, context, path):
+ sub = cls(context)
+ sub.input_path = "%s.in" % path.full_path
+ sub.output_path = path.translated
+ sub.relpath = path
+
+ return sub
+
+ def _process_xpidl(self, context):
+ # XPIDL source files get processed and turned into .h and .xpt files.
+ # If there are multiple XPIDL files in a directory, they get linked
+ # together into a final .xpt, which has the name defined by
+ # XPIDL_MODULE.
+ xpidl_module = context["XPIDL_MODULE"]
+
+ if not xpidl_module:
+ if context["XPIDL_SOURCES"]:
+ raise SandboxValidationError(
+ "XPIDL_MODULE must be defined if " "XPIDL_SOURCES is defined.",
+ context,
+ )
+ return
+
+ if not context["XPIDL_SOURCES"]:
+ raise SandboxValidationError(
+ "XPIDL_MODULE cannot be defined " "unless there are XPIDL_SOURCES",
+ context,
+ )
+
+ if context["DIST_INSTALL"] is False:
+ self.log(
+ logging.WARN,
+ "mozbuild_warning",
+ dict(path=context.main_path),
+ "{path}: DIST_INSTALL = False has no effect on XPIDL_SOURCES.",
+ )
+
+ for idl in context["XPIDL_SOURCES"]:
+ if not os.path.exists(idl.full_path):
+ raise SandboxValidationError(
+ "File %s from XPIDL_SOURCES " "does not exist" % idl.full_path,
+ context,
+ )
+
+ yield XPIDLModule(context, xpidl_module, context["XPIDL_SOURCES"])
+
+ def _process_generated_files(self, context):
+ for path in context["CONFIGURE_DEFINE_FILES"]:
+ script = mozpath.join(
+ mozpath.dirname(mozpath.dirname(__file__)),
+ "action",
+ "process_define_files.py",
+ )
+ yield GeneratedFile(
+ context,
+ script,
+ "process_define_file",
+ six.text_type(path),
+ [Path(context, path + ".in")],
+ )
+
+ generated_files = context.get("GENERATED_FILES") or []
+ localized_generated_files = context.get("LOCALIZED_GENERATED_FILES") or []
+ if not (generated_files or localized_generated_files):
+ return
+
+ for (localized, gen) in (
+ (False, generated_files),
+ (True, localized_generated_files),
+ ):
+ for f in gen:
+ flags = gen[f]
+ outputs = f
+ inputs = []
+ if flags.script:
+ method = "main"
+ script = SourcePath(context, flags.script).full_path
+
+ # Deal with cases like "C:\\path\\to\\script.py:function".
+ if ".py:" in script:
+ script, method = script.rsplit(".py:", 1)
+ script += ".py"
+
+ if not os.path.exists(script):
+ raise SandboxValidationError(
+ "Script for generating %s does not exist: %s" % (f, script),
+ context,
+ )
+ if os.path.splitext(script)[1] != ".py":
+ raise SandboxValidationError(
+ "Script for generating %s does not end in .py: %s"
+ % (f, script),
+ context,
+ )
+ else:
+ script = None
+ method = None
+
+ for i in flags.inputs:
+ p = Path(context, i)
+ if isinstance(p, SourcePath) and not os.path.exists(p.full_path):
+ raise SandboxValidationError(
+ "Input for generating %s does not exist: %s"
+ % (f, p.full_path),
+ context,
+ )
+ inputs.append(p)
+
+ yield GeneratedFile(
+ context,
+ script,
+ method,
+ outputs,
+ inputs,
+ flags.flags,
+ localized=localized,
+ force=flags.force,
+ )
+
+ def _process_test_manifests(self, context):
+ for prefix, info in TEST_MANIFESTS.items():
+ for path, manifest in context.get("%s_MANIFESTS" % prefix, []):
+ for obj in self._process_test_manifest(context, info, path, manifest):
+ yield obj
+
+ for flavor in REFTEST_FLAVORS:
+ for path, manifest in context.get("%s_MANIFESTS" % flavor.upper(), []):
+ for obj in self._process_reftest_manifest(
+ context, flavor, path, manifest
+ ):
+ yield obj
+
+ def _process_test_manifest(self, context, info, manifest_path, mpmanifest):
+ flavor, install_root, install_subdir, package_tests = info
+
+ path = manifest_path.full_path
+ manifest_dir = mozpath.dirname(path)
+ manifest_reldir = mozpath.dirname(
+ mozpath.relpath(path, context.config.topsrcdir)
+ )
+ manifest_sources = [
+ mozpath.relpath(pth, context.config.topsrcdir)
+ for pth in mpmanifest.source_files
+ ]
+ install_prefix = mozpath.join(install_root, install_subdir)
+
+ try:
+ if not mpmanifest.tests:
+ raise SandboxValidationError("Empty test manifest: %s" % path, context)
+
+ defaults = mpmanifest.manifest_defaults[os.path.normpath(path)]
+ obj = TestManifest(
+ context,
+ path,
+ mpmanifest,
+ flavor=flavor,
+ install_prefix=install_prefix,
+ relpath=mozpath.join(manifest_reldir, mozpath.basename(path)),
+ sources=manifest_sources,
+ dupe_manifest="dupe-manifest" in defaults,
+ )
+
+ filtered = mpmanifest.tests
+
+ missing = [t["name"] for t in filtered if not os.path.exists(t["path"])]
+ if missing:
+ raise SandboxValidationError(
+ "Test manifest (%s) lists "
+ "test that does not exist: %s" % (path, ", ".join(missing)),
+ context,
+ )
+
+ out_dir = mozpath.join(install_prefix, manifest_reldir)
+
+ def process_support_files(test):
+ install_info = self._test_files_converter.convert_support_files(
+ test, install_root, manifest_dir, out_dir
+ )
+
+ obj.pattern_installs.extend(install_info.pattern_installs)
+ for source, dest in install_info.installs:
+ obj.installs[source] = (dest, False)
+ obj.external_installs |= install_info.external_installs
+ for install_path in install_info.deferred_installs:
+ if all(
+ [
+ "*" not in install_path,
+ not os.path.isfile(
+ mozpath.join(context.config.topsrcdir, install_path[2:])
+ ),
+ install_path not in install_info.external_installs,
+ ]
+ ):
+ raise SandboxValidationError(
+ "Error processing test "
+ "manifest %s: entry in support-files not present "
+ "in the srcdir: %s" % (path, install_path),
+ context,
+ )
+
+ obj.deferred_installs |= install_info.deferred_installs
+
+ for test in filtered:
+ obj.tests.append(test)
+
+ # Some test files are compiled and should not be copied into the
+ # test package. They function as identifiers rather than files.
+ if package_tests:
+ manifest_relpath = mozpath.relpath(
+ test["path"], mozpath.dirname(test["manifest"])
+ )
+ obj.installs[mozpath.normpath(test["path"])] = (
+ (mozpath.join(out_dir, manifest_relpath)),
+ True,
+ )
+
+ process_support_files(test)
+
+ for path, m_defaults in mpmanifest.manifest_defaults.items():
+ process_support_files(m_defaults)
+
+ # We also copy manifests into the output directory,
+ # including manifests from [include:foo] directives.
+ for mpath in mpmanifest.manifests():
+ mpath = mozpath.normpath(mpath)
+ out_path = mozpath.join(out_dir, mozpath.basename(mpath))
+ obj.installs[mpath] = (out_path, False)
+
+ # Some manifests reference files that are auto generated as
+ # part of the build or shouldn't be installed for some
+ # reason. Here, we prune those files from the install set.
+ # FUTURE we should be able to detect autogenerated files from
+ # other build metadata. Once we do that, we can get rid of this.
+ for f in defaults.get("generated-files", "").split():
+ # We re-raise otherwise the stack trace isn't informative.
+ try:
+ del obj.installs[mozpath.join(manifest_dir, f)]
+ except KeyError:
+ raise SandboxValidationError(
+ "Error processing test "
+ "manifest %s: entry in generated-files not present "
+ "elsewhere in manifest: %s" % (path, f),
+ context,
+ )
+
+ yield obj
+ except (AssertionError, Exception):
+ raise SandboxValidationError(
+ "Error processing test "
+ "manifest file %s: %s"
+ % (path, "\n".join(traceback.format_exception(*sys.exc_info()))),
+ context,
+ )
+
+ def _process_reftest_manifest(self, context, flavor, manifest_path, manifest):
+ manifest_full_path = manifest_path.full_path
+ manifest_reldir = mozpath.dirname(
+ mozpath.relpath(manifest_full_path, context.config.topsrcdir)
+ )
+
+ # reftest manifests don't come from manifest parser. But they are
+ # similar enough that we can use the same emitted objects. Note
+ # that we don't perform any installs for reftests.
+ obj = TestManifest(
+ context,
+ manifest_full_path,
+ manifest,
+ flavor=flavor,
+ install_prefix="%s/" % flavor,
+ relpath=mozpath.join(manifest_reldir, mozpath.basename(manifest_path)),
+ )
+ obj.tests = list(sorted(manifest.tests, key=lambda t: t["path"]))
+
+ yield obj
+
+ def _process_jar_manifests(self, context):
+ jar_manifests = context.get("JAR_MANIFESTS", [])
+ if len(jar_manifests) > 1:
+ raise SandboxValidationError(
+ "While JAR_MANIFESTS is a list, "
+ "it is currently limited to one value.",
+ context,
+ )
+
+ for path in jar_manifests:
+ yield JARManifest(context, path)
+
+ # Temporary test to look for jar.mn files that creep in without using
+ # the new declaration. Before, we didn't require jar.mn files to
+ # declared anywhere (they were discovered). This will detect people
+ # relying on the old behavior.
+ if os.path.exists(os.path.join(context.srcdir, "jar.mn")):
+ if "jar.mn" not in jar_manifests:
+ raise SandboxValidationError(
+ "A jar.mn exists but it "
+ "is not referenced in the moz.build file. "
+ "Please define JAR_MANIFESTS.",
+ context,
+ )
+
+ def _emit_directory_traversal_from_context(self, context):
+ o = DirectoryTraversal(context)
+ o.dirs = context.get("DIRS", [])
+
+ # Some paths have a subconfigure, yet also have a moz.build. Those
+ # shouldn't end up in self._external_paths.
+ if o.objdir:
+ self._external_paths -= {o.relobjdir}
+
+ yield o