Bug 900526, part 3: Define FINAL_TARGET and friends in moz.build, r=gps
authorJoshua Cranmer <Pidgeot18@gmail.com>
Mon, 21 Oct 2013 13:08:35 -0500
changeset 166365 6916000f7b8ce028107d3c078148bfe2c19e008e
parent 166364 fb42a764f90bc91a628e472889e00b27b28b55ed
child 166366 5452527770cb322cb7f50e8abedccf2eb6c8a4a0
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs900526
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 900526, part 3: Define FINAL_TARGET and friends in moz.build, r=gps
config/config.mk
js/src/config/config.mk
python/mozbuild/mozbuild/backend/recursivemake.py
python/mozbuild/mozbuild/frontend/data.py
python/mozbuild/mozbuild/frontend/emitter.py
python/mozbuild/mozbuild/frontend/sandbox.py
python/mozbuild/mozbuild/frontend/sandbox_symbols.py
python/mozbuild/mozbuild/test/backend/common.py
python/mozbuild/mozbuild/test/backend/data/final_target/both/moz.build
python/mozbuild/mozbuild/test/backend/data/final_target/dist-subdir/moz.build
python/mozbuild/mozbuild/test/backend/data/final_target/final-target/moz.build
python/mozbuild/mozbuild/test/backend/data/final_target/moz.build
python/mozbuild/mozbuild/test/backend/data/final_target/xpi-name/moz.build
python/mozbuild/mozbuild/test/backend/test_recursivemake.py
--- a/config/config.mk
+++ b/config/config.mk
@@ -144,22 +144,26 @@ include $(topsrcdir)/config/makefiles/fu
 endif
 
 RM = rm -f
 
 # LIBXUL_DIST is not defined under js/src, thus we make it mean DIST there.
 LIBXUL_DIST ?= $(DIST)
 
 # FINAL_TARGET specifies the location into which we copy end-user-shipped
-# build products (typelibs, components, chrome).
+# build products (typelibs, components, chrome). It may already be specified by
+# a moz.build file.
 #
 # If XPI_NAME is set, the files will be shipped to $(DIST)/xpi-stage/$(XPI_NAME)
 # instead of $(DIST)/bin. In both cases, if DIST_SUBDIR is set, the files will be
 # shipped to a $(DIST_SUBDIR) subdirectory.
-FINAL_TARGET = $(if $(XPI_NAME),$(DIST)/xpi-stage/$(XPI_NAME),$(DIST)/bin)$(DIST_SUBDIR:%=/%)
+FINAL_TARGET ?= $(if $(XPI_NAME),$(DIST)/xpi-stage/$(XPI_NAME),$(DIST)/bin)$(DIST_SUBDIR:%=/%)
+# Override the stored value for the check to make sure that the variable is not
+# redefined in the Makefile.in value.
+FINAL_TARGET_FROZEN := '$(FINAL_TARGET)'
 
 ifdef XPI_NAME
 DEFINES += -DXPI_NAME=$(XPI_NAME)
 endif
 
 # The VERSION_NUMBER is suffixed onto the end of the DLLs we ship.
 VERSION_NUMBER		= 50
 
--- a/js/src/config/config.mk
+++ b/js/src/config/config.mk
@@ -144,22 +144,26 @@ include $(topsrcdir)/config/makefiles/fu
 endif
 
 RM = rm -f
 
 # LIBXUL_DIST is not defined under js/src, thus we make it mean DIST there.
 LIBXUL_DIST ?= $(DIST)
 
 # FINAL_TARGET specifies the location into which we copy end-user-shipped
-# build products (typelibs, components, chrome).
+# build products (typelibs, components, chrome). It may already be specified by
+# a moz.build file.
 #
 # If XPI_NAME is set, the files will be shipped to $(DIST)/xpi-stage/$(XPI_NAME)
 # instead of $(DIST)/bin. In both cases, if DIST_SUBDIR is set, the files will be
 # shipped to a $(DIST_SUBDIR) subdirectory.
-FINAL_TARGET = $(if $(XPI_NAME),$(DIST)/xpi-stage/$(XPI_NAME),$(DIST)/bin)$(DIST_SUBDIR:%=/%)
+FINAL_TARGET ?= $(if $(XPI_NAME),$(DIST)/xpi-stage/$(XPI_NAME),$(DIST)/bin)$(DIST_SUBDIR:%=/%)
+# Override the stored value for the check to make sure that the variable is not
+# redefined in the Makefile.in value.
+FINAL_TARGET_FROZEN := '$(FINAL_TARGET)'
 
 ifdef XPI_NAME
 DEFINES += -DXPI_NAME=$(XPI_NAME)
 endif
 
 # The VERSION_NUMBER is suffixed onto the end of the DLLs we ship.
 VERSION_NUMBER		= 50
 
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -23,16 +23,17 @@ import mozpack.path as mozpath
 from .common import CommonBackend
 from ..frontend.data import (
     ConfigFileSubstitution,
     Defines,
     DirectoryTraversal,
     Exports,
     GeneratedEventWebIDLFile,
     GeneratedWebIDLFile,
+    InstallationTarget,
     IPDLFile,
     LocalInclude,
     PreprocessedTestWebIDLFile,
     PreprocessedWebIDLFile,
     Program,
     SandboxDerived,
     TestWebIDLFile,
     VariablePassthru,
@@ -404,16 +405,19 @@ class RecursiveMakeBackend(CommonBackend
             self._process_program(obj.program, backend_file)
 
         elif isinstance(obj, TestManifest):
             self._process_test_manifest(obj, backend_file)
 
         elif isinstance(obj, LocalInclude):
             self._process_local_include(obj.path, backend_file)
 
+        elif isinstance(obj, InstallationTarget):
+            self._process_installation_target(obj, backend_file)
+
         self._backend_files[obj.srcdir] = backend_file
 
     def _fill_root_mk(self):
         """
         Create two files, root.mk and root-deps.mk, the first containing
         convenience variables, and the other dependency definitions for a
         hopefully proper directory traversal.
         """
@@ -868,16 +872,32 @@ class RecursiveMakeBackend(CommonBackend
             if not os.path.exists(source):
                 raise Exception('File listed in EXPORTS does not exist: %s' % source)
 
         children = exports.get_children()
         for subdir in sorted(children):
             self._process_exports(obj, children[subdir], backend_file,
                 namespace=namespace + subdir)
 
+    def _process_installation_target(self, obj, backend_file):
+        # A few makefiles need to be able to override the following rules via
+        # make XPI_NAME=blah commands, so we default to the lazy evaluation as
+        # much as possible here to avoid breaking things.
+        if obj.xpiname:
+            backend_file.write('XPI_NAME = %s\n' % (obj.xpiname))
+        if obj.subdir:
+            backend_file.write('DIST_SUBDIR = %s\n' % (obj.subdir))
+        if obj.target and not obj.is_custom():
+            backend_file.write('FINAL_TARGET = $(DEPTH)/%s\n' % (obj.target))
+        else:
+            backend_file.write('FINAL_TARGET = $(if $(XPI_NAME),$(DIST)/xpi-stage/$(XPI_NAME),$(DIST)/bin)$(DIST_SUBDIR:%=/%)\n')
+
+        if not obj.enabled:
+            backend_file.write('NO_DIST_INSTALL := 1\n')
+
     def _handle_idl_manager(self, manager):
         build_files = self._install_manifests['xpidl']
 
         for p in ('Makefile', 'backend.mk', '.deps/.mkdir.done',
             'xpt/.mkdir.done'):
             build_files.add_optional_exists(p)
 
         for idl in manager.idls.values():
--- a/python/mozbuild/mozbuild/frontend/data.py
+++ b/python/mozbuild/mozbuild/frontend/data.py
@@ -15,16 +15,17 @@ contains the code for converting execute
 structures.
 """
 
 from __future__ import unicode_literals
 
 import os
 
 from collections import OrderedDict
+from .sandbox_symbols import compute_final_target
 
 
 class TreeMetadata(object):
     """Base class for all data being captured."""
 
 
 class ReaderSummary(TreeMetadata):
     """A summary of what the reader did."""
@@ -348,8 +349,34 @@ class LocalInclude(SandboxDerived):
     __slots__ = (
         'path',
     )
 
     def __init__(self, sandbox, path):
         SandboxDerived.__init__(self, sandbox)
 
         self.path = path
+
+class InstallationTarget(SandboxDerived):
+    """Describes the rules that affect where files get installed to."""
+
+    __slots__ = (
+        'xpiname',
+        'subdir',
+        'target',
+        'enabled'
+    )
+
+    def __init__(self, sandbox):
+        SandboxDerived.__init__(self, sandbox)
+
+        self.xpiname = sandbox.get('XPI_NAME', '')
+        self.subdir = sandbox.get('DIST_SUBDIR', '')
+        self.target = sandbox['FINAL_TARGET']
+        self.enabled = not sandbox.get('NO_DIST_INSTALL', False)
+
+    def is_custom(self):
+        """Returns whether or not the target is not derived from the default
+        given xpiname and subdir."""
+
+        return compute_final_target(dict(
+            XPI_NAME=self.xpiname,
+            DIST_SUBDIR=self.subdir)) == self.target
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -19,16 +19,17 @@ from mozpack.files import FileFinder
 
 from .data import (
     ConfigFileSubstitution,
     Defines,
     DirectoryTraversal,
     Exports,
     GeneratedEventWebIDLFile,
     GeneratedWebIDLFile,
+    InstallationTarget,
     IPDLFile,
     LocalInclude,
     PreprocessedTestWebIDLFile,
     PreprocessedWebIDLFile,
     Program,
     ReaderSummary,
     TestWebIDLFile,
     TestManifest,
@@ -203,16 +204,20 @@ class TreeMetadataEmitter(LoggingMixin):
             ('PREPROCESSED_WEBIDL_FILES', PreprocessedWebIDLFile),
             ('TEST_WEBIDL_FILES', TestWebIDLFile),
             ('WEBIDL_FILES', WebIDLFile),
         ]
         for sandbox_var, klass in simple_lists:
             for name in sandbox.get(sandbox_var, []):
                 yield klass(sandbox, name)
 
+        if sandbox.get('FINAL_TARGET') or sandbox.get('XPI_NAME') or \
+                sandbox.get('DIST_SUBDIR'):
+            yield InstallationTarget(sandbox)
+
         # While there are multiple test manifests, the behavior is very similar
         # across them. We enforce this by having common handling of all
         # manifests and outputting a single class type with the differences
         # described inside the instance.
         #
         # Keys are variable prefixes and values are tuples describing how these
         # manifests should be handled:
         #
--- a/python/mozbuild/mozbuild/frontend/sandbox.py
+++ b/python/mozbuild/mozbuild/frontend/sandbox.py
@@ -109,17 +109,24 @@ class GlobalNamespace(dict):
             pass
 
         # The variable isn't present yet. Fall back to VARIABLES.
         default = self._allowed_variables.get(name, None)
         if default is None:
             self.last_name_error = KeyError('global_ns', 'get_unknown', name)
             raise self.last_name_error
 
-        dict.__setitem__(self, name, copy.deepcopy(default[2]))
+        # If the default is specifically a lambda (or, rather, any function--but
+        # not a class that can be called), then it is actually a rule to
+        # generate the default that should be used.
+        default_rule = default[2]
+        if isinstance(default_rule, type(lambda: None)):
+            default_rule = default_rule(self)
+
+        dict.__setitem__(self, name, copy.deepcopy(default_rule))
         return dict.__getitem__(self, name)
 
     def __setitem__(self, name, value):
         if self._allow_all_writes:
             dict.__setitem__(self, name, value)
             return
 
         # We don't need to check for name.isupper() here because LocalNamespace
--- a/python/mozbuild/mozbuild/frontend/sandbox_symbols.py
+++ b/python/mozbuild/mozbuild/frontend/sandbox_symbols.py
@@ -19,16 +19,28 @@ from __future__ import unicode_literals
 
 from collections import OrderedDict
 from mozbuild.util import (
     HierarchicalStringList,
     StrictOrderingOnAppendList,
 )
 
 
+def compute_final_target(variables):
+    """Convert the default value for FINAL_TARGET"""
+    basedir = 'dist/'
+    if variables['XPI_NAME']:
+        basedir += 'xpi-stage/' + variables['XPI_NAME']
+    else:
+        basedir += 'bin'
+    if variables['DIST_SUBDIR']:
+        basedir += '/' + variables['DIST_SUBDIR']
+    return basedir
+ 
+ 
 # This defines the set of mutable global variables.
 #
 # Each variable is a tuple of:
 #
 #   (storage_type, input_types, default_value, docs, tier)
 #
 # Tier says for which specific tier the variable has an effect.
 # Valid tiers are:
@@ -493,16 +505,42 @@ VARIABLES = {
 
     'WEBRTC_SIGNALLING_TEST_MANIFESTS': (StrictOrderingOnAppendList, list, [],
         """List of manifest files defining WebRTC signalling tests.
         """, None),
 
     'XPCSHELL_TESTS_MANIFESTS': (StrictOrderingOnAppendList, list, [],
         """List of manifest files defining xpcshell tests.
         """, None),
+
+    # The following variables are used to control the target of installed files.
+    'XPI_NAME': (unicode, unicode, "",
+        """The name of an extension XPI to generate.
+
+        When this variable is present, the results of this directory will end up
+        being packaged into an extension instead of the main dist/bin results.
+        """, 'libs'),
+
+    'DIST_SUBDIR': (unicode, unicode, "",
+        """The name of an alternate directory to install files to.
+
+        When this variable is present, the results of this directory will end up
+        being placed in the $(DIST_SUBDIR) subdirectory of where it would
+        otherwise be placed.
+        """, 'libs'),
+
+    'FINAL_TARGET': (unicode, unicode, compute_final_target,
+        """The name of the directory to install targets to.
+
+        The directory is relative to the top of the object directory. The
+        default value is dependent on the values of XPI_NAME and DIST_SUBDIR. If
+        neither are present, the result is dist/bin. If XPI_NAME is present, the
+        result is dist/xpi-stage/$(XPI_NAME). If DIST_SUBDIR is present, then
+        the $(DIST_SUBDIR) directory of the otherwise default value is used.
+        """, 'libs'),
 }
 
 # The set of functions exposed to the sandbox.
 #
 # Each entry is a tuple of:
 #
 #  (method attribute, (argument types), docs)
 #
--- a/python/mozbuild/mozbuild/test/backend/common.py
+++ b/python/mozbuild/mozbuild/test/backend/common.py
@@ -83,16 +83,21 @@ CONFIGS = {
         'non_global_defines': [],
         'substs': [],
     },
     'local_includes': {
         'defines': [],
         'non_global_defines': [],
         'substs': [],
     },
+    'final_target': {
+        'defines': [],
+        'non_global_defines': [],
+        'substs': [],
+    },
 }
 
 
 class BackendTester(unittest.TestCase):
     def _get_environment(self, name):
         """Obtain a new instance of a ConfigEnvironment for a known profile.
 
         A new temporary object directory is created for the environment. The
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final_target/both/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+XPI_NAME = 'mycrazyxpi'
+DIST_SUBDIR = 'asubdir'
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final_target/dist-subdir/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIST_SUBDIR = 'asubdir'
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final_target/final-target/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+FINAL_TARGET = 'random-final-target'
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final_target/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DIRS += ['xpi-name', 'dist-subdir', 'both', 'final-target']
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/final_target/xpi-name/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+XPI_NAME = 'mycrazyxpi'
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -500,10 +500,43 @@ class TestRecursiveMakeBackend(BackendTe
         expected = [
             'LOCAL_INCLUDES += -I$(topsrcdir)/bar/baz',
             'LOCAL_INCLUDES += -I$(srcdir)/foo',
         ]
 
         found = [str for str in lines if str.startswith('LOCAL_INCLUDES')]
         self.assertEqual(found, expected)
 
+    def test_final_target(self):
+        """Test that FINAL_TARGET is written to backend.mk correctly."""
+        env = self._consume('final_target', RecursiveMakeBackend)
+
+        final_target_rule = "FINAL_TARGET = $(if $(XPI_NAME),$(DIST)/xpi-stage/$(XPI_NAME),$(DIST)/bin)$(DIST_SUBDIR:%=/%)"
+        print([x for x in os.walk(env.topobjdir)])
+        expected = dict()
+        expected[env.topobjdir] = []
+        expected[os.path.join(env.topobjdir, 'both')] = [
+            'XPI_NAME = mycrazyxpi',
+            'DIST_SUBDIR = asubdir',
+            final_target_rule
+        ]
+        expected[os.path.join(env.topobjdir, 'dist-subdir')] = [
+            'DIST_SUBDIR = asubdir',
+            final_target_rule
+        ]
+        expected[os.path.join(env.topobjdir, 'xpi-name')] = [
+            'XPI_NAME = mycrazyxpi',
+            final_target_rule
+        ]
+        expected[os.path.join(env.topobjdir, 'final-target')] = [
+            'FINAL_TARGET = $(DEPTH)/random-final-target'
+        ]
+        for key, expected_rules in expected.iteritems():
+            backend_path = os.path.join(key, 'backend.mk')
+            lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]]
+            found = [str for str in lines if
+                str.startswith('FINAL_TARGET') or str.startswith('XPI_NAME') or
+                str.startswith('DIST_SUBDIR')]
+            self.assertEqual(found, expected_rules)
+
+
 if __name__ == '__main__':
     main()