Bug 1062221 - Change how DIRS and TEST_DIRS are handled. r=gps

Up to now, DIRS and TEST_DIRS were dumb values. This change makes them
a list of ContextDerivedValues, and handles the fact that some types of
paths are relative to the current source directory and others to the
topsrcdir.

This also makes us one step closer to fixing bug 991983.
This commit is contained in:
Mike Hommey 2014-10-02 09:14:07 +09:00
parent 0412683b7d
commit 2ff412e6fe
7 changed files with 225 additions and 37 deletions

View File

@ -9,7 +9,12 @@ CONFIGURE_SUBST_FILES += ['installer/Makefile']
DIRS += [
'../locales',
'locales',
'stumbler',
]
if CONFIG['MOZ_ANDROID_MLS_STUMBLER']:
DIRS += ['stumbler']
DIRS += [
'base',
'chrome',
'components',
@ -21,9 +26,6 @@ DIRS += [
'extensions',
]
if not CONFIG['MOZ_ANDROID_MLS_STUMBLER']:
DIRS.remove('stumbler')
if not CONFIG['LIBXUL_SDK']:
DIRS += ['../../xulrunner/tools/redit']

View File

@ -787,19 +787,21 @@ class RecursiveMakeBackend(CommonBackend):
"""Process a data.DirectoryTraversal instance."""
fh = backend_file.fh
def relativize(dirs):
return [mozpath.normpath(mozpath.join(backend_file.relobjdir, d))
for d in dirs]
def relativize(base, dirs):
return (mozpath.relpath(d.translated, base) for d in dirs)
if obj.dirs:
fh.write('DIRS := %s\n' % ' '.join(obj.dirs))
self._traversal.add(backend_file.relobjdir, dirs=relativize(obj.dirs))
fh.write('DIRS := %s\n' % ' '.join(
relativize(backend_file.objdir, obj.dirs)))
self._traversal.add(backend_file.relobjdir,
dirs=relativize(self.environment.topobjdir, obj.dirs))
if obj.test_dirs:
fh.write('TEST_DIRS := %s\n' % ' '.join(obj.test_dirs))
fh.write('TEST_DIRS := %s\n' % ' '.join(
relativize(backend_file.objdir, obj.test_dirs)))
if self.environment.substs.get('ENABLE_TESTS', False):
self._traversal.add(backend_file.relobjdir,
tests=relativize(obj.test_dirs))
dirs=relativize(self.environment.topobjdir, obj.test_dirs))
# The directory needs to be registered whether subdirectories have been
# registered or not.

View File

@ -25,13 +25,16 @@ from mozbuild.util import (
HierarchicalStringListWithFlagsFactory,
KeyedDefaultDict,
List,
memoize,
memoized_property,
ReadOnlyKeyedDefaultDict,
StrictOrderingOnAppendList,
StrictOrderingOnAppendListWithFlagsFactory,
TypedList,
)
import mozpack.path as mozpath
from types import StringTypes
from UserString import UserString
import itertools
@ -227,6 +230,76 @@ class FinalTargetValue(ContextDerivedValue, unicode):
return unicode.__new__(cls, value)
class SourcePath(ContextDerivedValue, UserString):
"""Stores and resolves a source path relative to a given context
This class is used as a backing type for some of the sandbox variables.
It expresses paths relative to a context. Paths starting with a '/'
are considered relative to the topsrcdir, and other paths relative
to the current source directory for the associated context.
"""
def __new__(cls, context, value=None):
if not isinstance(context, Context) and value is None:
return unicode(context)
return super(SourcePath, cls).__new__(cls)
def __init__(self, context, value=None):
self.context = context
self.value = value
@memoized_property
def data(self):
"""Serializes the path for UserString."""
if self.value.startswith('/'):
ret = None
# If the path starts with a '/' and is actually relative to an
# external source dir, use that as base instead of topsrcdir.
if self.context.config.external_source_dir:
ret = mozpath.join(self.context.config.external_source_dir,
self.value[1:])
if not ret or not os.path.exists(ret):
ret = mozpath.join(self.context.config.topsrcdir,
self.value[1:])
else:
ret = mozpath.join(self.context.srcdir, self.value)
return mozpath.normpath(ret)
def __unicode__(self):
# UserString doesn't implement a __unicode__ function at all, so add
# ours.
return self.data
@memoized_property
def translated(self):
"""Returns the corresponding path in the objdir.
Ideally, we wouldn't need this function, but the fact that both source
path under topsrcdir and the external source dir end up mixed in the
objdir (aka pseudo-rework), this is needed.
"""
if self.value.startswith('/'):
ret = mozpath.join(self.context.config.topobjdir, self.value[1:])
else:
ret = mozpath.join(self.context.objdir, self.value)
return mozpath.normpath(ret)
@memoize
def ContextDerivedTypedList(type, base_class=List):
"""Specialized TypedList for use with ContextDerivedValue types.
"""
assert issubclass(type, ContextDerivedValue)
class _TypedList(ContextDerivedValue, TypedList(type, base_class)):
def __init__(self, context, iterable=[]):
class _Type(type):
def __new__(cls, obj):
return type(context, obj)
self.TYPE = _Type
super(_TypedList, self).__init__(iterable)
return _TypedList
# This defines the set of mutable global variables.
#
# Each variable is a tuple of:
@ -346,7 +419,7 @@ VARIABLES = {
should load lazily. This only has an effect when building with MSVC.
""", None),
'DIRS': (List, list,
'DIRS': (ContextDerivedTypedList(SourcePath), list,
"""Child directories to descend into looking for build frontend files.
This works similarly to the ``DIRS`` variable in make files. Each str
@ -637,7 +710,7 @@ VARIABLES = {
``HOST_BIN_SUFFIX``, the name will remain unchanged.
""", None),
'TEST_DIRS': (List, list,
'TEST_DIRS': (ContextDerivedTypedList(SourcePath), list,
"""Like DIRS but only for directories that contain test-only code.
If tests are not enabled, this variable will be ignored.

View File

@ -945,7 +945,7 @@ class BuildReader(object):
if d in recurse_info:
raise SandboxValidationError(
'Directory (%s) registered multiple times in %s' % (
d, var), context)
mozpath.relpath(d, context.srcdir), var), context)
recurse_info[d] = {}
if 'templates' in sandbox.metadata:
@ -955,9 +955,8 @@ class BuildReader(object):
sandbox.recompute_exports()
recurse_info[d]['exports'] = dict(sandbox.metadata['exports'])
for relpath, child_metadata in recurse_info.items():
child_path = sandbox.normalize_path(mozpath.join(relpath,
'moz.build'), srcdir=curdir)
for path, child_metadata in recurse_info.items():
child_path = mozpath.join(path, 'moz.build')
# Ensure we don't break out of the topsrcdir. We don't do realpath
# because it isn't necessary. If there are symlinks in the srcdir,

View File

@ -98,7 +98,13 @@ class TestEmitterBasic(unittest.TestCase):
self.assertEqual(reldirs, ['', 'foo', 'foo/biz', 'bar'])
dirs = [o.dirs for o in objs]
self.assertEqual(dirs, [['foo', 'bar'], ['biz'], [], []])
self.assertEqual(dirs, [
[
mozpath.join(reader.config.topsrcdir, 'foo'),
mozpath.join(reader.config.topsrcdir, 'bar')
], [
mozpath.join(reader.config.topsrcdir, 'foo', 'biz')
], [], []])
def test_traversal_all_vars(self):
reader = self.reader('traversal-all-vars')
@ -115,8 +121,10 @@ class TestEmitterBasic(unittest.TestCase):
reldir = o.relativedir
if reldir == '':
self.assertEqual(o.dirs, ['regular'])
self.assertEqual(o.test_dirs, ['test'])
self.assertEqual(o.dirs, [
mozpath.join(reader.config.topsrcdir, 'regular')])
self.assertEqual(o.test_dirs, [
mozpath.join(reader.config.topsrcdir, 'test')])
def test_config_file_substitution(self):
reader = self.reader('config-file-substitution')

View File

@ -10,9 +10,42 @@ from mozunit import main
from mozbuild.frontend.context import (
Context,
VARIABLES,
ContextDerivedValue,
ContextDerivedTypedList,
)
from mozbuild.util import (
StrictOrderingOnAppendList,
UnsortedError,
)
class Fuga(object):
def __init__(self, value):
self.value = value
class Piyo(ContextDerivedValue):
def __init__(self, context, value):
if not isinstance(value, unicode):
raise ValueError
self.context = context
self.value = value
def lower(self):
return self.value.lower()
def __str__(self):
return self.value
VARIABLES = {
'HOGE': (unicode, unicode, None, None),
'FUGA': (Fuga, unicode, None, None),
'PIYO': (Piyo, unicode, None, None),
'HOGERA': (ContextDerivedTypedList(Piyo, StrictOrderingOnAppendList),
list, None, None),
}
class TestContext(unittest.TestCase):
def test_key_rejection(self):
@ -39,35 +72,101 @@ class TestContext(unittest.TestCase):
self.assertTrue(e[3])
def test_allowed_set(self):
self.assertIn('DIRS', VARIABLES)
self.assertIn('HOGE', VARIABLES)
ns = Context(allowed_variables=VARIABLES)
ns['DIRS'] = ['foo']
self.assertEqual(ns['DIRS'], ['foo'])
ns['HOGE'] = 'foo'
self.assertEqual(ns['HOGE'], 'foo')
def test_value_checking(self):
ns = Context(allowed_variables=VARIABLES)
# Setting to a non-allowed type should not work.
with self.assertRaises(ValueError) as ve:
ns['DIRS'] = True
ns['HOGE'] = True
e = ve.exception.args
self.assertEqual(e[0], 'global_ns')
self.assertEqual(e[1], 'set_type')
self.assertEqual(e[2], 'DIRS')
self.assertTrue(e[3])
self.assertEqual(e[4], list)
self.assertEqual(e[2], 'HOGE')
self.assertEqual(e[3], True)
self.assertEqual(e[4], unicode)
def test_key_checking(self):
# Checking for existence of a key should not populate the key if it
# doesn't exist.
g = Context(allowed_variables=VARIABLES)
self.assertFalse('DIRS' in g)
self.assertFalse('DIRS' in g)
self.assertFalse('HOGE' in g)
self.assertFalse('HOGE' in g)
def test_coercion(self):
ns = Context(allowed_variables=VARIABLES)
# Setting to a type different from the allowed input type should not
# work.
with self.assertRaises(ValueError) as ve:
ns['FUGA'] = False
e = ve.exception.args
self.assertEqual(e[0], 'global_ns')
self.assertEqual(e[1], 'set_type')
self.assertEqual(e[2], 'FUGA')
self.assertEqual(e[3], False)
self.assertEqual(e[4], unicode)
ns['FUGA'] = 'fuga'
self.assertIsInstance(ns['FUGA'], Fuga)
self.assertEqual(ns['FUGA'].value, 'fuga')
ns['FUGA'] = Fuga('hoge')
self.assertIsInstance(ns['FUGA'], Fuga)
self.assertEqual(ns['FUGA'].value, 'hoge')
def test_context_derived_coercion(self):
ns = Context(allowed_variables=VARIABLES)
# Setting to a type different from the allowed input type should not
# work.
with self.assertRaises(ValueError) as ve:
ns['PIYO'] = False
e = ve.exception.args
self.assertEqual(e[0], 'global_ns')
self.assertEqual(e[1], 'set_type')
self.assertEqual(e[2], 'PIYO')
self.assertEqual(e[3], False)
self.assertEqual(e[4], unicode)
ns['PIYO'] = 'piyo'
self.assertIsInstance(ns['PIYO'], Piyo)
self.assertEqual(ns['PIYO'].value, 'piyo')
self.assertEqual(ns['PIYO'].context, ns)
ns['PIYO'] = Piyo(ns, 'fuga')
self.assertIsInstance(ns['PIYO'], Piyo)
self.assertEqual(ns['PIYO'].value, 'fuga')
self.assertEqual(ns['PIYO'].context, ns)
def test_context_derived_typed_list(self):
ns = Context(allowed_variables=VARIABLES)
# Setting to a type that's rejected by coercion should not work.
with self.assertRaises(ValueError):
ns['HOGERA'] = [False]
ns['HOGERA'] += ['a', 'b', 'c']
self.assertIsInstance(ns['HOGERA'],
ContextDerivedTypedList(Piyo, StrictOrderingOnAppendList))
for n in range(0, 3):
self.assertIsInstance(ns['HOGERA'][n], Piyo)
self.assertEqual(ns['HOGERA'][n].value, ['a', 'b', 'c'][n])
self.assertEqual(ns['HOGERA'][n].context, ns)
with self.assertRaises(UnsortedError):
ns['HOGERA'] += ['f', 'e', 'd']
if __name__ == '__main__':
main()

View File

@ -38,7 +38,9 @@ test_data_path = mozpath.join(test_data_path, 'data')
class TestSandbox(unittest.TestCase):
def sandbox(self):
return Sandbox(Context(VARIABLES))
return Sandbox(Context({
'DIRS': (list, list, None, None),
}))
def test_exec_source_success(self):
sandbox = self.sandbox()
@ -217,7 +219,10 @@ class TestMozbuildSandbox(unittest.TestCase):
sandbox.exec_file('moz.build')
self.assertEqual(sandbox['DIRS'], ['foo', 'bar'])
self.assertEqual(sandbox['DIRS'], [
sandbox.normalize_path('foo'),
sandbox.normalize_path('bar'),
])
self.assertEqual(sandbox._context.main_path,
sandbox.normalize_path('moz.build'))
self.assertEqual(len(sandbox._context.all_paths), 2)
@ -263,11 +268,11 @@ class TestMozbuildSandbox(unittest.TestCase):
# child directory.
sandbox = self.sandbox(data_path='include-relative-from-child')
sandbox.exec_file('child/child.build')
self.assertEqual(sandbox['DIRS'], ['foo'])
self.assertEqual(sandbox['DIRS'], [sandbox.normalize_path('child/foo')])
sandbox = self.sandbox(data_path='include-relative-from-child')
sandbox.exec_file('child/child2.build')
self.assertEqual(sandbox['DIRS'], ['foo'])
self.assertEqual(sandbox['DIRS'], [sandbox.normalize_path('child/foo')])
def test_include_topsrcdir_relative(self):
# An absolute path for include() is relative to topsrcdir.
@ -275,7 +280,7 @@ class TestMozbuildSandbox(unittest.TestCase):
sandbox = self.sandbox(data_path='include-topsrcdir-relative')
sandbox.exec_file('moz.build')
self.assertEqual(sandbox['DIRS'], ['foo'])
self.assertEqual(sandbox['DIRS'], [sandbox.normalize_path('foo')])
def test_error(self):
sandbox = self.sandbox()
@ -346,7 +351,7 @@ SOURCES += ['hoge.cpp']
self.assertEqual(sandbox2._context, {
'SOURCES': ['qux.cpp', 'bar.cpp', 'foo.cpp', 'hoge.cpp'],
'DIRS': ['foo'],
'DIRS': [sandbox.normalize_path('foo')],
})
source = '''
@ -387,7 +392,7 @@ TemplateGlobalUPPERVariable()
sandbox2.exec_source(source, sandbox.normalize_path('foo.mozbuild'))
self.assertEqual(sandbox2._context, {
'SOURCES': [],
'DIRS': ['foo'],
'DIRS': [sandbox.normalize_path('foo')],
})
# However, the result of the template is mixed with the global