blob: 723a2ad1067a2ccecffabcdb90d6f9044785e810 [file] [log] [blame]
# 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/.
from __future__ import absolute_import, unicode_literals
import itertools
import json
import os
import re
import mozpack.path as mozpath
import mozwebidlcodegen
from .base import BuildBackend
from ..frontend.data import (
ConfigFileSubstitution,
ExampleWebIDLInterface,
HeaderFileSubstitution,
IPDLFile,
GeneratedEventWebIDLFile,
GeneratedWebIDLFile,
PreprocessedTestWebIDLFile,
PreprocessedWebIDLFile,
TestManifest,
TestWebIDLFile,
UnifiedSources,
XPIDLFile,
WebIDLFile,
)
from collections import defaultdict
from ..util import (
group_unified_files,
)
class XPIDLManager(object):
"""Helps manage XPCOM IDLs in the context of the build system."""
def __init__(self, config):
self.config = config
self.topsrcdir = config.topsrcdir
self.topobjdir = config.topobjdir
self.idls = {}
self.modules = {}
self.interface_manifests = {}
self.chrome_manifests = set()
def register_idl(self, idl, allow_existing=False):
"""Registers an IDL file with this instance.
The IDL file will be built, installed, etc.
"""
basename = mozpath.basename(idl.source_path)
root = mozpath.splitext(basename)[0]
xpt = '%s.xpt' % idl.module
manifest = mozpath.join(idl.install_target, 'components', 'interfaces.manifest')
chrome_manifest = mozpath.join(idl.install_target, 'chrome.manifest')
entry = {
'source': idl.source_path,
'module': idl.module,
'basename': basename,
'root': root,
'manifest': manifest,
}
if not allow_existing and entry['basename'] in self.idls:
raise Exception('IDL already registered: %s' % entry['basename'])
self.idls[entry['basename']] = entry
t = self.modules.setdefault(entry['module'], (idl.install_target, set()))
t[1].add(entry['root'])
if idl.add_to_manifest:
self.interface_manifests.setdefault(manifest, set()).add(xpt)
self.chrome_manifests.add(chrome_manifest)
class WebIDLCollection(object):
"""Collects WebIDL info referenced during the build."""
def __init__(self):
self.sources = set()
self.generated_sources = set()
self.generated_events_sources = set()
self.preprocessed_sources = set()
self.test_sources = set()
self.preprocessed_test_sources = set()
self.example_interfaces = set()
def all_regular_sources(self):
return self.sources | self.generated_sources | \
self.generated_events_sources | self.preprocessed_sources
def all_regular_basenames(self):
return [os.path.basename(source) for source in self.all_regular_sources()]
def all_regular_stems(self):
return [os.path.splitext(b)[0] for b in self.all_regular_basenames()]
def all_regular_bindinggen_stems(self):
for stem in self.all_regular_stems():
yield '%sBinding' % stem
for source in self.generated_events_sources:
yield os.path.splitext(os.path.basename(source))[0]
def all_regular_cpp_basenames(self):
for stem in self.all_regular_bindinggen_stems():
yield '%s.cpp' % stem
def all_test_sources(self):
return self.test_sources | self.preprocessed_test_sources
def all_test_basenames(self):
return [os.path.basename(source) for source in self.all_test_sources()]
def all_test_stems(self):
return [os.path.splitext(b)[0] for b in self.all_test_basenames()]
def all_test_cpp_basenames(self):
return ['%sBinding.cpp' % s for s in self.all_test_stems()]
def all_static_sources(self):
return self.sources | self.generated_events_sources | \
self.test_sources
def all_non_static_sources(self):
return self.generated_sources | self.all_preprocessed_sources()
def all_non_static_basenames(self):
return [os.path.basename(s) for s in self.all_non_static_sources()]
def all_preprocessed_sources(self):
return self.preprocessed_sources | self.preprocessed_test_sources
def all_sources(self):
return set(self.all_regular_sources()) | set(self.all_test_sources())
def all_basenames(self):
return [os.path.basename(source) for source in self.all_sources()]
def all_stems(self):
return [os.path.splitext(b)[0] for b in self.all_basenames()]
def generated_events_basenames(self):
return [os.path.basename(s) for s in self.generated_events_sources]
def generated_events_stems(self):
return [os.path.splitext(b)[0] for b in self.generated_events_basenames()]
class TestManager(object):
"""Helps hold state related to tests."""
def __init__(self, config):
self.config = config
self.topsrcdir = mozpath.normpath(config.topsrcdir)
self.tests_by_path = defaultdict(list)
def add(self, t, flavor=None, topsrcdir=None):
t = dict(t)
t['flavor'] = flavor
if topsrcdir is None:
topsrcdir = self.topsrcdir
else:
topsrcdir = mozpath.normpath(topsrcdir)
path = mozpath.normpath(t['path'])
assert mozpath.basedir(path, [topsrcdir])
key = path[len(topsrcdir)+1:]
t['file_relpath'] = key
t['dir_relpath'] = mozpath.dirname(key)
self.tests_by_path[key].append(t)
class CommonBackend(BuildBackend):
"""Holds logic common to all build backends."""
def _init(self):
self._idl_manager = XPIDLManager(self.environment)
self._test_manager = TestManager(self.environment)
self._webidls = WebIDLCollection()
self._configs = set()
self._ipdl_sources = set()
def consume_object(self, obj):
self._configs.add(obj.config)
if isinstance(obj, TestManifest):
for test in obj.tests:
self._test_manager.add(test, flavor=obj.flavor,
topsrcdir=obj.topsrcdir)
elif isinstance(obj, XPIDLFile):
self._idl_manager.register_idl(obj)
elif isinstance(obj, ConfigFileSubstitution):
# Do not handle ConfigFileSubstitution for Makefiles. Leave that
# to other
if mozpath.basename(obj.output_path) == 'Makefile':
return False
with self._get_preprocessor(obj) as pp:
pp.do_include(obj.input_path)
self.backend_input_files.add(obj.input_path)
elif isinstance(obj, HeaderFileSubstitution):
self._create_config_header(obj)
self.backend_input_files.add(obj.input_path)
# We should consider aggregating WebIDL types in emitter.py.
elif isinstance(obj, WebIDLFile):
self._webidls.sources.add(mozpath.join(obj.srcdir, obj.basename))
elif isinstance(obj, GeneratedEventWebIDLFile):
self._webidls.generated_events_sources.add(mozpath.join(
obj.srcdir, obj.basename))
elif isinstance(obj, TestWebIDLFile):
self._webidls.test_sources.add(mozpath.join(obj.srcdir,
obj.basename))
elif isinstance(obj, PreprocessedTestWebIDLFile):
self._webidls.preprocessed_test_sources.add(mozpath.join(
obj.srcdir, obj.basename))
elif isinstance(obj, GeneratedWebIDLFile):
self._webidls.generated_sources.add(mozpath.join(obj.srcdir,
obj.basename))
elif isinstance(obj, PreprocessedWebIDLFile):
self._webidls.preprocessed_sources.add(mozpath.join(
obj.srcdir, obj.basename))
elif isinstance(obj, ExampleWebIDLInterface):
self._webidls.example_interfaces.add(obj.name)
elif isinstance(obj, IPDLFile):
self._ipdl_sources.add(mozpath.join(obj.srcdir, obj.basename))
elif isinstance(obj, UnifiedSources):
if obj.have_unified_mapping:
self._write_unified_files(obj.unified_source_mapping, obj.objdir)
if hasattr(self, '_process_unified_sources'):
self._process_unified_sources(obj)
else:
return False
return True
def consume_finished(self):
if len(self._idl_manager.idls):
self._handle_idl_manager(self._idl_manager)
self._handle_webidl_collection(self._webidls)
sorted_ipdl_sources = list(sorted(self._ipdl_sources))
def files_from(ipdl):
base = mozpath.basename(ipdl)
root, ext = mozpath.splitext(base)
# Both .ipdl and .ipdlh become .cpp files
files = ['%s.cpp' % root]
if ext == '.ipdl':
# .ipdl also becomes Child/Parent.cpp files
files.extend(['%sChild.cpp' % root,
'%sParent.cpp' % root])
return files
ipdl_dir = mozpath.join(self.environment.topobjdir, 'ipc', 'ipdl')
ipdl_cppsrcs = list(itertools.chain(*[files_from(p) for p in sorted_ipdl_sources]))
unified_source_mapping = list(group_unified_files(ipdl_cppsrcs,
unified_prefix='UnifiedProtocols',
unified_suffix='cpp',
files_per_unified_file=16))
self._write_unified_files(unified_source_mapping, ipdl_dir, poison_windows_h=False)
self._handle_ipdl_sources(ipdl_dir, sorted_ipdl_sources, unified_source_mapping)
for config in self._configs:
self.backend_input_files.add(config.source)
# Write out a machine-readable file describing every test.
path = mozpath.join(self.environment.topobjdir, 'all-tests.json')
with self._write_file(path) as fh:
s = json.dumps(self._test_manager.tests_by_path)
fh.write(s)
def _handle_webidl_collection(self, webidls):
if not webidls.all_stems():
return
bindings_dir = mozpath.join(self.environment.topobjdir, 'dom', 'bindings')
all_inputs = set(webidls.all_static_sources())
for s in webidls.all_non_static_basenames():
all_inputs.add(mozpath.join(bindings_dir, s))
generated_events_stems = webidls.generated_events_stems()
exported_stems = webidls.all_regular_stems()
# The WebIDL manager reads configuration from a JSON file. So, we
# need to write this file early.
o = dict(
webidls=sorted(all_inputs),
generated_events_stems=sorted(generated_events_stems),
exported_stems=sorted(exported_stems),
example_interfaces=sorted(webidls.example_interfaces),
)
file_lists = mozpath.join(bindings_dir, 'file-lists.json')
with self._write_file(file_lists) as fh:
json.dump(o, fh, sort_keys=True, indent=2)
manager = mozwebidlcodegen.create_build_system_manager(
self.environment.topsrcdir,
self.environment.topobjdir,
mozpath.join(self.environment.topobjdir, 'dist')
)
# Bindings are compiled in unified mode to speed up compilation and
# to reduce linker memory size. Note that test bindings are separated
# from regular ones so tests bindings aren't shipped.
unified_source_mapping = list(group_unified_files(webidls.all_regular_cpp_basenames(),
unified_prefix='UnifiedBindings',
unified_suffix='cpp',
files_per_unified_file=32))
self._write_unified_files(unified_source_mapping, bindings_dir,
poison_windows_h=True)
self._handle_webidl_build(bindings_dir, unified_source_mapping,
webidls,
manager.expected_build_output_files(),
manager.GLOBAL_DEFINE_FILES)
def _write_unified_file(self, unified_file, source_filenames,
output_directory, poison_windows_h=False):
with self._write_file(mozpath.join(output_directory, unified_file)) as f:
f.write('#define MOZ_UNIFIED_BUILD\n')
includeTemplate = '#include "%(cppfile)s"'
if poison_windows_h:
includeTemplate += (
'\n'
'#ifdef _WINDOWS_\n'
'#error "%(cppfile)s included windows.h"\n'
"#endif")
includeTemplate += (
'\n'
'#ifdef PL_ARENA_CONST_ALIGN_MASK\n'
'#error "%(cppfile)s uses PL_ARENA_CONST_ALIGN_MASK, '
'so it cannot be built in unified mode."\n'
'#undef PL_ARENA_CONST_ALIGN_MASK\n'
'#endif\n'
'#ifdef INITGUID\n'
'#error "%(cppfile)s defines INITGUID, '
'so it cannot be built in unified mode."\n'
'#undef INITGUID\n'
'#endif')
f.write('\n'.join(includeTemplate % { "cppfile": s } for
s in source_filenames))
def _write_unified_files(self, unified_source_mapping, output_directory,
poison_windows_h=False):
for unified_file, source_filenames in unified_source_mapping:
self._write_unified_file(unified_file, source_filenames,
output_directory, poison_windows_h)
def _create_config_header(self, obj):
'''Creates the given config header. A config header is generated by
taking the corresponding source file and replacing some #define/#undef
occurences:
"#undef NAME" is turned into "#define NAME VALUE"
"#define NAME" is unchanged
"#define NAME ORIGINAL_VALUE" is turned into "#define NAME VALUE"
"#undef UNKNOWN_NAME" is turned into "/* #undef UNKNOWN_NAME */"
Whitespaces are preserved.
'''
with self._write_file(obj.output_path) as fh, \
open(obj.input_path, 'rU') as input:
r = re.compile('^\s*#\s*(?P<cmd>[a-z]+)(?:\s+(?P<name>\S+)(?:\s+(?P<value>\S+))?)?', re.U)
for l in input:
m = r.match(l)
if m:
cmd = m.group('cmd')
name = m.group('name')
value = m.group('value')
if name:
if name in obj.config.defines:
if cmd == 'define' and value:
l = l[:m.start('value')] \
+ str(obj.config.defines[name]) \
+ l[m.end('value'):]
elif cmd == 'undef':
l = l[:m.start('cmd')] \
+ 'define' \
+ l[m.end('cmd'):m.end('name')] \
+ ' ' \
+ str(obj.config.defines[name]) \
+ l[m.end('name'):]
elif cmd == 'undef':
l = '/* ' + l[:m.end('name')] + ' */' + l[m.end('name'):]
fh.write(l)