Bug 1419892 - Link programs and libraries in the tup backend. draft
authorChris Manchester <cmanchester@mozilla.com>
Mon, 26 Mar 2018 15:29:51 -0700
changeset 772744 7d5805e4176f6a69c89d3f5ac9d22886cc48f355
parent 772574 7b9da7139d94951431a148dcaf8a388640c91b27
push id104041
push userbmo:cmanchester@mozilla.com
push dateMon, 26 Mar 2018 22:30:07 +0000
bugs1419892
milestone61.0a1
Bug 1419892 - Link programs and libraries in the tup backend. MozReview-Commit-ID: 26Yb0QdCn5H
python/mozbuild/mozbuild/backend/tup.py
--- a/python/mozbuild/mozbuild/backend/tup.py
+++ b/python/mozbuild/mozbuild/backend/tup.py
@@ -29,17 +29,21 @@ from ..frontend.data import (
     FinalTargetPreprocessedFiles,
     GeneratedFile,
     GeneratedSources,
     HostDefines,
     HostSources,
     JARManifest,
     ObjdirFiles,
     PerSourceFlag,
+    Program,
+    HostProgram,
+    SharedLibrary,
     Sources,
+    StaticLibrary,
     VariablePassthru,
 )
 from ..util import (
     FileAvoidWrite,
     expand_variables,
 )
 from ..frontend.context import (
     AbsolutePath,
@@ -57,21 +61,25 @@ class BackendTupfile(object):
         self.relobjdir = mozpath.relpath(objdir, topobjdir)
         self.environment = environment
         self.name = mozpath.join(objdir, 'Tupfile')
         self.rules_included = False
         self.shell_exported = False
         self.defines = []
         self.host_defines = []
         self.delayed_generated_files = []
+        self.delayed_installed_files = []
         self.per_source_flags = defaultdict(list)
         self.local_flags = defaultdict(list)
         self.sources = defaultdict(list)
         self.host_sources = defaultdict(list)
         self.variables = {}
+        self.static_lib = None
+        self.shared_lib = None
+        self.program = None
 
         self.fh = FileAvoidWrite(self.name, capture_diff=True)
         self.fh.write('# THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT.\n')
         self.fh.write('\n')
 
     def write(self, buf):
         self.fh.write(buf)
 
@@ -188,16 +196,28 @@ class TupOnly(CommonBackend, PartialBack
         # will be built before any rules that list this as an input.
         self._installed_idls = '$(MOZ_OBJ_ROOT)/<installed-idls>'
         self._installed_files = '$(MOZ_OBJ_ROOT)/<installed-files>'
         # The preprocessor including source-repo.h and buildid.h creates
         # dependencies that aren't specified by moz.build and cause errors
         # in Tup. Express these as a group dependency.
         self._early_generated_files = '$(MOZ_OBJ_ROOT)/<early-generated-files>'
 
+        # application.ini.h is a special case since we need to process
+        # the FINAL_TARGET_PP_FILES for application.ini before running
+        # the GENERATED_FILES script, and tup doesn't handle the rules
+        # out of order. Similarly, dependentlibs.list uses libxul as
+        # an input, so must be written after the rule for libxul.
+        self._delayed_files = (
+            'application.ini.h',
+            'dependentlibs.list',
+            'dependentlibs.list.gtest'
+        )
+
+
     def _get_backend_file(self, relobjdir):
         objdir = mozpath.normpath(mozpath.join(self.environment.topobjdir, relobjdir))
         if objdir not in self._backend_files:
             self._backend_files[objdir] = \
                     BackendTupfile(objdir, self.environment,
                                    self.environment.topsrcdir, self.environment.topobjdir)
         return self._backend_files[objdir]
 
@@ -207,16 +227,139 @@ class TupOnly(CommonBackend, PartialBack
     def _py_action(self, action):
         cmd = [
             '$(PYTHON)',
             '-m',
             'mozbuild.action.%s' % action,
         ]
         return cmd
 
+    def _lib_paths(self, objdir, libs):
+        return [mozpath.relpath(mozpath.join(l.objdir, l.import_name), objdir)
+                for l in libs]
+
+    def _gen_shared_library(self, backend_file):
+        if backend_file.shared_lib.name == 'libxul.so':
+            # This will fail to link currently due to missing rust symbols.
+            return
+
+        if backend_file.shared_lib.cxx_link:
+            mkshlib = (
+                [backend_file.environment.substs['CXX']] +
+                backend_file.local_flags['CXX_LDFLAGS']
+            )
+        else:
+            mkshlib = (
+                [backend_file.environment.substs['CC']] +
+                backend_file.local_flags['C_LDFLAGS']
+            )
+
+        mkshlib += (
+            backend_file.environment.substs['DSO_PIC_CFLAGS'] +
+            [backend_file.environment.substs['DSO_LDOPTS']] +
+            ['-Wl,-h,%s' % backend_file.shared_lib.soname] +
+            ['-o', backend_file.shared_lib.lib_name]
+        )
+
+        objs, _, shared_libs, os_libs, static_libs = self._expand_libs(backend_file.shared_lib)
+        static_libs = self._lib_paths(backend_file.objdir, static_libs)
+        shared_libs = self._lib_paths(backend_file.objdir, shared_libs)
+
+        list_file_name = '%s.list' % backend_file.shared_lib.name.replace('.', '_')
+        list_file = self._make_list_file(backend_file.objdir, objs, list_file_name)
+
+        inputs = objs + static_libs + shared_libs
+        if any(i.endswith('libxul.so') for i in inputs):
+            # Don't attempt to link anything that depends on libxul.
+            return
+
+        symbols_file = []
+        if backend_file.shared_lib.symbols_file:
+            inputs.append(backend_file.shared_lib.symbols_file)
+            # TODO: Assumes GNU LD
+            symbols_file = ['-Wl,--version-script,%s' % backend_file.shared_lib.symbols_file]
+
+        cmd = (
+            mkshlib +
+            [list_file] +
+            backend_file.local_flags['LDFLAGS'] +
+            static_libs +
+            shared_libs +
+            symbols_file +
+            [backend_file.environment.substs['OS_LIBS']] +
+            os_libs
+        )
+        backend_file.rule(
+            cmd=cmd,
+            inputs=inputs,
+            outputs=[backend_file.shared_lib.lib_name],
+            display='LINK %o'
+        )
+
+
+    def _gen_program(self, backend_file):
+        cc_or_cxx = 'CXX' if backend_file.program.cxx_link else 'CC'
+        objs, _, shared_libs, os_libs, static_libs = self._expand_libs(backend_file.program)
+        static_libs = self._lib_paths(backend_file.objdir, static_libs)
+        shared_libs = self._lib_paths(backend_file.objdir, shared_libs)
+
+        inputs = objs + static_libs + shared_libs
+        if any(i.endswith('libxul.so') for i in inputs):
+            # Don't attempt to link anything that depends on libxul.
+            return
+
+        list_file_name = '%s.list' % backend_file.program.name.replace('.', '_')
+        list_file = self._make_list_file(backend_file.objdir, objs, list_file_name)
+
+        outputs = [mozpath.relpath(backend_file.program.output_path.full_path,
+                                   backend_file.objdir)]
+        cmd = (
+            [backend_file.environment.substs[cc_or_cxx], '-o', '%o'] +
+            backend_file.local_flags['CXX_LDFLAGS'] +
+            [list_file] +
+            backend_file.local_flags['LDFLAGS'] +
+            static_libs +
+            [backend_file.environment.substs['MOZ_PROGRAM_LDFLAGS']] +
+            shared_libs +
+            [backend_file.environment.substs['OS_LIBS']] +
+            os_libs
+        )
+        backend_file.rule(
+            cmd=cmd,
+            inputs=inputs,
+            outputs=outputs,
+            display='LINK %o'
+        )
+
+
+    def _gen_static_library(self, backend_file):
+        ar = [
+            backend_file.environment.substs['AR'],
+            backend_file.environment.substs['AR_FLAGS'].replace('$@', '%o')
+        ]
+
+        objs, _, shared_libs, _, static_libs = self._expand_libs(backend_file.static_lib)
+        static_libs = self._lib_paths(backend_file.objdir, static_libs)
+        shared_libs = self._lib_paths(backend_file.objdir, shared_libs)
+
+        inputs = objs + static_libs
+
+        cmd = (
+            ar +
+            inputs
+        )
+
+        backend_file.rule(
+            cmd=cmd,
+            inputs=inputs,
+            outputs=[backend_file.static_lib.name],
+            display='AR %o'
+        )
+
+
     def consume_object(self, obj):
         """Write out build files necessary to build with tup."""
 
         if not isinstance(obj, ContextDerived):
             return False
 
         consumed = CommonBackend.consume_object(self, obj)
         if consumed:
@@ -229,21 +372,17 @@ class TupOnly(CommonBackend, PartialBack
 
             if self.environment.is_artifact_build:
                 skip_files = self._compile_env_gen_files
 
             for f in obj.outputs:
                 if any(mozpath.match(f, p) for p in skip_files):
                     return False
 
-            if 'application.ini.h' in obj.outputs:
-                # application.ini.h is a special case since we need to process
-                # the FINAL_TARGET_PP_FILES for application.ini before running
-                # the GENERATED_FILES script, and tup doesn't handle the rules
-                # out of order.
+            if any([f in obj.outputs for f in self._delayed_files]):
                 backend_file.delayed_generated_files.append(obj)
             else:
                 self._process_generated_file(backend_file, obj)
         elif (isinstance(obj, ChromeManifestEntry) and
               obj.install_target.startswith('dist/bin')):
             top_level = mozpath.join(obj.install_target, 'chrome.manifest')
             if obj.path != top_level:
                 entry = 'manifest %s' % mozpath.relpath(obj.path,
@@ -265,16 +404,24 @@ class TupOnly(CommonBackend, PartialBack
         elif isinstance(obj, ComputedFlags):
             self._process_computed_flags(obj, backend_file)
         elif isinstance(obj, (Sources, GeneratedSources)):
             backend_file.sources[obj.canonical_suffix].extend(obj.files)
         elif isinstance(obj, HostSources):
             backend_file.host_sources[obj.canonical_suffix].extend(obj.files)
         elif isinstance(obj, VariablePassthru):
             backend_file.variables = obj.variables
+        elif isinstance(obj, StaticLibrary):
+            backend_file.static_lib = obj
+        elif isinstance(obj, SharedLibrary):
+            backend_file.shared_lib = obj
+        elif isinstance(obj, HostProgram):
+            pass
+        elif isinstance(obj, Program):
+            backend_file.program = obj
 
         # The top-level Makefile.in still contains our driver target and some
         # things related to artifact builds, so as a special case ensure the
         # make backend generates a Makefile there.
         if obj.objdir == self.environment.topobjdir:
             return False
 
         return True
@@ -285,19 +432,28 @@ class TupOnly(CommonBackend, PartialBack
         # The approach here is similar to fastermake.py, but we
         # simply write out the resulting files here.
         for target, entries in self._manifest_entries.iteritems():
             with self._write_file(mozpath.join(self.environment.topobjdir,
                                                target)) as fh:
                 fh.write(''.join('%s\n' % e for e in sorted(entries)))
 
         for objdir, backend_file in sorted(self._backend_files.items()):
+            backend_file.gen_sources_rules([self._installed_files])
+            for condition, gen_method in ((backend_file.shared_lib, self._gen_shared_library),
+                                          (backend_file.static_lib and backend_file.static_lib.no_expand_lib,
+                                           self._gen_static_library),
+                                          (backend_file.program, self._gen_program)):
+                if condition:
+                    backend_file.export_shell()
+                    gen_method(backend_file)
             for obj in backend_file.delayed_generated_files:
                 self._process_generated_file(backend_file, obj)
-            backend_file.gen_sources_rules([self._installed_files])
+            for path, output in backend_file.delayed_installed_files:
+                backend_file.symlink_rule(path, output=output)
             with self._write_file(fh=backend_file):
                 pass
 
         with self._write_file(mozpath.join(self.environment.topobjdir, 'Tuprules.tup')) as fh:
             acdefines_flags = ' '.join(['-D%s=%s' % (name, shell_quote(value))
                 for (name, value) in sorted(self.environment.acdefines.iteritems())])
             # TODO: AB_CD only exists in Makefiles at the moment.
             acdefines_flags += ' -DAB_CD=en-US'
@@ -431,18 +587,22 @@ class TupOnly(CommonBackend, PartialBack
                     # We're not generating files in these directories yet, so
                     # don't attempt to install files generated from them.
                     if f.context.relobjdir not in ('layout/style/test',
                                                    'toolkit/library',
                                                    'js/src/shell'):
                         output = mozpath.join('$(MOZ_OBJ_ROOT)', target, path,
                                               f.target_basename)
                         gen_backend_file = self._get_backend_file(f.context.relobjdir)
-                        gen_backend_file.symlink_rule(f.full_path, output=output,
-                                                      output_group=self._installed_files)
+                        if f.target_basename in self._delayed_files:
+                            gen_backend_file.delayed_installed_files.append((f.full_path, output))
+                        else:
+                            gen_backend_file.symlink_rule(f.full_path, output=output,
+                                                          output_group=self._installed_files)
+
 
     def _process_final_target_pp_files(self, obj, backend_file):
         for i, (path, files) in enumerate(obj.files.walk()):
             for f in files:
                 self._preprocess(backend_file, f.full_path,
                                  destdir=mozpath.join(self.environment.topobjdir, obj.install_target, path),
                                  target=f.target_basename)