Bug 905879 - More robust tier tracking; r=glandium

This commit is contained in:
Gregory Szorc 2013-08-20 00:06:32 -07:00
parent 850a171242
commit acd9d1a011
12 changed files with 263 additions and 122 deletions

View File

@ -94,6 +94,9 @@ ifdef ENABLE_TESTS
include $(topsrcdir)/testing/testsuite-targets.mk
endif
# Hacky way for precompile tier to bypass default tier traversal mechanism.
TIER_precompile_CUSTOM := 1
include $(topsrcdir)/config/rules.mk
distclean::

View File

@ -24,10 +24,12 @@ $(call BUILDSTATUS,SUBTIER_FINISH precompile $(1))
endef
export::
$(call BUILDSTATUS,SUBTIERS IPDL WebIDL XPIDL XPIDLParser)
default::
$(call BUILDSTATUS,TIER_START precompile IPDL WebIDL XPIDL)
+$(MAKE) export
$(call BUILDSTATUS,TIER_FINISH precompile)
export:: ipdl webidl xpidl-parser xpidl
export:: ipdl webidl xpidl
ipdl:
$(call make_subtier_dir,IPDL,$(DEPTH)/ipc/ipdl,ipdl)
@ -35,9 +37,9 @@ ipdl:
webidl:
$(call make_subtier_dir,WebIDL,$(DEPTH)/dom/bindings,webidl)
xpidl-parser:
$(call make_subtier_dir,XPIDLParser,$(DEPTH)/xpcom/idl-parser,xpidl-parser)
xpidl: xpidl-parser
xpidl:
$(call BUILDSTATUS,SUBTIER_START precompile XPIDL)
+$(MAKE) -C $(DEPTH)/xpcom/idl-parser xpidl-parser
$(call py_action,process_install_manifest,$(DIST)/idl $(DEPTH)/_build_manifests/install/dist_idl)
$(call make_subtier_dir,XPIDL,$(DEPTH)/config/makefiles/xpidl,xpidl)
+$(MAKE) -C $(DEPTH)/config/makefiles/xpidl xpidl
$(call BUILDSTATUS,SUBTIER_FINISH precompile XPIDL)

View File

@ -14,7 +14,7 @@ PARALLEL_DIRS_export = $(addsuffix _export,$(PARALLEL_DIRS))
###############
$(addprefix export_tier_,$(TIERS)): export_tier_%:
@$(ECHO) "$@"
$(foreach dir,$(tier_$*_dirs),$(call TIER_DIR_SUBMAKE,export,$(dir)))
$(foreach dir,$(tier_$*_dirs),$(call TIER_DIR_SUBMAKE,$*,export,$(dir),export))
#################
## Common targets

View File

@ -14,7 +14,7 @@ PARALLEL_DIRS_libs = $(addsuffix _libs,$(PARALLEL_DIRS))
###############
$(addprefix libs_tier_,$(TIERS)): libs_tier_%:
@$(ECHO) "$@"
$(foreach dir,$(tier_$*_dirs),$(call TIER_DIR_SUBMAKE,libs,$(dir)))
$(foreach dir,$(tier_$*_dirs),$(call TIER_DIR_SUBMAKE,$*,libs,$(dir),libs))
#################
## Common targets

View File

@ -14,7 +14,7 @@ PARALLEL_DIRS_tools = $(addsuffix _tools,$(PARALLEL_DIRS))
###############
$(addprefix tools_tier_,$(TIERS)): tools_tier_%:
@$(ECHO) "$@"
$(foreach dir,$(tier_$*_dirs),$(call TIER_DIR_SUBMAKE,tools,$(dir)))
$(foreach dir,$(tier_$*_dirs),$(call TIER_DIR_SUBMAKE,$*,tools,$(dir),tools))
#################
## Common targets

View File

@ -429,8 +429,9 @@ UPDATE_TITLE = printf "\033]0;%s in %s\007" $(1) $(shell $(BUILD_TOOLS)/print-de
endif
ifdef MACH
BUILDSTATUS=@echo BUILDSTATUS $1
BUILDSTATUS=@echo "BUILDSTATUS $1"
endif
# Static directories are largely independent of our build system. But, they
# could share the same build mechanism (like moz.build files). We need to
# prevent leaking of our backend state to these independent build systems. This
@ -443,9 +444,9 @@ define SUBMAKE # $(call SUBMAKE,target,directory,static)
endef # The extra line is important here! don't delete it
define TIER_DIR_SUBMAKE
$(call BUILDSTATUS,TIERDIR_START $(2))
$(call SUBMAKE,$(1),$(2),$(3))
$(call BUILDSTATUS,TIERDIR_FINISH $(2))
$(call BUILDSTATUS,TIERDIR_START $(1) $(2) $(3))
$(call SUBMAKE,$(4),$(3),$(5))
$(call BUILDSTATUS,TIERDIR_FINISH $(1) $(2) $(3))
endef # Ths empty line is important.
@ -712,32 +713,38 @@ QUIET := -q
endif
# This function is called and evaluated to produce the rule to build the
# specified tier. Each tier begins by building the "static" directories.
# The BUILDSTATUS echo commands are used to faciliate easier parsing
# of build output. Build drivers are encouraged to filter these lines
# from the user.
# specified tier.
#
# Tiers are traditionally composed of directories that are invoked either
# once (so-called "static" directories) or 3 times with the export, libs, and
# tools sub-tiers.
#
# If the TIER_$(tier)_CUSTOM variable is defined, then these traditional
# tier rules are ignored and each directory in the tier is executed via a
# sub-make invocation (make -C).
define CREATE_TIER_RULE
tier_$(1)::
$(call BUILDSTATUS,TIER_START $(1))
$(call BUILDSTATUS,SUBTIERS $(if $(tier_$(1)_staticdirs),static )$(if $(tier_$(1)_dirs),export libs tools))
$(call BUILDSTATUS,STATICDIRS $$($$@_staticdirs))
$(call BUILDSTATUS,DIRS $$($$@_dirs))
ifdef TIER_$(1)_CUSTOM
$$(foreach dir,$$($$@_dirs),$$(call SUBMAKE,,$$(dir)))
else
$(call BUILDSTATUS,TIER_START $(1) $(if $(tier_$(1)_staticdirs),static )$(if $(tier_$(1)_dirs),export libs tools))
ifneq (,$(tier_$(1)_staticdirs))
$(call BUILDSTATUS,SUBTIER_START $(1) static)
$$(foreach dir,$$($$@_staticdirs),$$(call TIER_DIR_SUBMAKE,,$$(dir),1))
$(call BUILDSTATUS,SUBTIER_START $(1) static $$($$@_staticdirs))
$$(foreach dir,$$($$@_staticdirs),$$(call TIER_DIR_SUBMAKE,$(1),static,$$(dir),,1))
$(call BUILDSTATUS,SUBTIER_FINISH $(1) static)
endif
ifneq (,$(tier_$(1)_dirs))
$(call BUILDSTATUS,SUBTIER_START $(1) export)
$(call BUILDSTATUS,SUBTIER_START $(1) export $$($$@_dirs))
$$(MAKE) export_$$@
$(call BUILDSTATUS,SUBTIER_FINISH $(1) export)
$(call BUILDSTATUS,SUBTIER_START $(1) libs)
$(call BUILDSTATUS,SUBTIER_START $(1) libs $$($$@_dirs))
$$(MAKE) libs_$$@
$(call BUILDSTATUS,SUBTIER_FINISH $(1) libs)
$(call BUILDSTATUS,SUBTIER_START $(1) tools)
$(call BUILDSTATUS,SUBTIER_START $(1) tools $$($$@_dirs))
$$(MAKE) tools_$$@
$(call BUILDSTATUS,SUBTIER_FINISH $(1) tools)
$(call BUILDSTATUS TIER_FINISH $(1))
endif
$(call BUILDSTATUS,TIER_FINISH $(1))
endif
endef

View File

@ -14,7 +14,7 @@ PARALLEL_DIRS_export = $(addsuffix _export,$(PARALLEL_DIRS))
###############
$(addprefix export_tier_,$(TIERS)): export_tier_%:
@$(ECHO) "$@"
$(foreach dir,$(tier_$*_dirs),$(call TIER_DIR_SUBMAKE,export,$(dir)))
$(foreach dir,$(tier_$*_dirs),$(call TIER_DIR_SUBMAKE,$*,export,$(dir),export))
#################
## Common targets

View File

@ -14,7 +14,7 @@ PARALLEL_DIRS_libs = $(addsuffix _libs,$(PARALLEL_DIRS))
###############
$(addprefix libs_tier_,$(TIERS)): libs_tier_%:
@$(ECHO) "$@"
$(foreach dir,$(tier_$*_dirs),$(call TIER_DIR_SUBMAKE,libs,$(dir)))
$(foreach dir,$(tier_$*_dirs),$(call TIER_DIR_SUBMAKE,$*,libs,$(dir),libs))
#################
## Common targets

View File

@ -14,7 +14,7 @@ PARALLEL_DIRS_tools = $(addsuffix _tools,$(PARALLEL_DIRS))
###############
$(addprefix tools_tier_,$(TIERS)): tools_tier_%:
@$(ECHO) "$@"
$(foreach dir,$(tier_$*_dirs),$(call TIER_DIR_SUBMAKE,tools,$(dir)))
$(foreach dir,$(tier_$*_dirs),$(call TIER_DIR_SUBMAKE,$*,tools,$(dir),tools))
#################
## Common targets

View File

@ -429,8 +429,9 @@ UPDATE_TITLE = printf "\033]0;%s in %s\007" $(1) $(shell $(BUILD_TOOLS)/print-de
endif
ifdef MACH
BUILDSTATUS=@echo BUILDSTATUS $1
BUILDSTATUS=@echo "BUILDSTATUS $1"
endif
# Static directories are largely independent of our build system. But, they
# could share the same build mechanism (like moz.build files). We need to
# prevent leaking of our backend state to these independent build systems. This
@ -443,9 +444,9 @@ define SUBMAKE # $(call SUBMAKE,target,directory,static)
endef # The extra line is important here! don't delete it
define TIER_DIR_SUBMAKE
$(call BUILDSTATUS,TIERDIR_START $(2))
$(call SUBMAKE,$(1),$(2),$(3))
$(call BUILDSTATUS,TIERDIR_FINISH $(2))
$(call BUILDSTATUS,TIERDIR_START $(1) $(2) $(3))
$(call SUBMAKE,$(4),$(3),$(5))
$(call BUILDSTATUS,TIERDIR_FINISH $(1) $(2) $(3))
endef # Ths empty line is important.
@ -712,32 +713,38 @@ QUIET := -q
endif
# This function is called and evaluated to produce the rule to build the
# specified tier. Each tier begins by building the "static" directories.
# The BUILDSTATUS echo commands are used to faciliate easier parsing
# of build output. Build drivers are encouraged to filter these lines
# from the user.
# specified tier.
#
# Tiers are traditionally composed of directories that are invoked either
# once (so-called "static" directories) or 3 times with the export, libs, and
# tools sub-tiers.
#
# If the TIER_$(tier)_CUSTOM variable is defined, then these traditional
# tier rules are ignored and each directory in the tier is executed via a
# sub-make invocation (make -C).
define CREATE_TIER_RULE
tier_$(1)::
$(call BUILDSTATUS,TIER_START $(1))
$(call BUILDSTATUS,SUBTIERS $(if $(tier_$(1)_staticdirs),static )$(if $(tier_$(1)_dirs),export libs tools))
$(call BUILDSTATUS,STATICDIRS $$($$@_staticdirs))
$(call BUILDSTATUS,DIRS $$($$@_dirs))
ifdef TIER_$(1)_CUSTOM
$$(foreach dir,$$($$@_dirs),$$(call SUBMAKE,,$$(dir)))
else
$(call BUILDSTATUS,TIER_START $(1) $(if $(tier_$(1)_staticdirs),static )$(if $(tier_$(1)_dirs),export libs tools))
ifneq (,$(tier_$(1)_staticdirs))
$(call BUILDSTATUS,SUBTIER_START $(1) static)
$$(foreach dir,$$($$@_staticdirs),$$(call TIER_DIR_SUBMAKE,,$$(dir),1))
$(call BUILDSTATUS,SUBTIER_START $(1) static $$($$@_staticdirs))
$$(foreach dir,$$($$@_staticdirs),$$(call TIER_DIR_SUBMAKE,$(1),static,$$(dir),,1))
$(call BUILDSTATUS,SUBTIER_FINISH $(1) static)
endif
ifneq (,$(tier_$(1)_dirs))
$(call BUILDSTATUS,SUBTIER_START $(1) export)
$(call BUILDSTATUS,SUBTIER_START $(1) export $$($$@_dirs))
$$(MAKE) export_$$@
$(call BUILDSTATUS,SUBTIER_FINISH $(1) export)
$(call BUILDSTATUS,SUBTIER_START $(1) libs)
$(call BUILDSTATUS,SUBTIER_START $(1) libs $$($$@_dirs))
$$(MAKE) libs_$$@
$(call BUILDSTATUS,SUBTIER_FINISH $(1) libs)
$(call BUILDSTATUS,SUBTIER_START $(1) tools)
$(call BUILDSTATUS,SUBTIER_START $(1) tools $$($$@_dirs))
$$(MAKE) tools_$$@
$(call BUILDSTATUS,SUBTIER_FINISH $(1) tools)
$(call BUILDSTATUS TIER_FINISH $(1))
endif
$(call BUILDSTATUS,TIER_FINISH $(1))
endif
endef

View File

@ -9,7 +9,10 @@ import os
import sys
import time
from collections import namedtuple
from collections import (
namedtuple,
OrderedDict,
)
# keep in sync with psutil os support, see psutil/__init__.py
if sys.platform.startswith("freebsd") or sys.platform.startswith("darwin") or sys.platform.startswith("win32") or sys.platform.startswith("linux"):
@ -20,6 +23,8 @@ if sys.platform.startswith("freebsd") or sys.platform.startswith("darwin") or sy
else:
psutil = None
from ..base import MozbuildObject
from ..compilation.warnings import (
WarningsCollector,
WarningsDatabase,
@ -30,27 +35,150 @@ BuildOutputResult = namedtuple('BuildOutputResult',
('warning', 'state_changed', 'for_display'))
class BuildMonitor(object):
class TierStatus(object):
"""Represents the state and progress of tier traversal.
The build system is organized into linear phases called tiers. Each tier
executes in the order it was defined, 1 at a time.
Tiers can have subtiers. Subtiers can execute in any order. Some subtiers
execute sequentially. Others are concurrent.
Subtiers can have directories. Directories can execute in any order, just
like subtiers.
"""
def __init__(self):
self.tiers = OrderedDict()
self.active_tier = None
self.active_subtiers = set()
def set_tiers(self, tiers):
"""Record the set of known tiers."""
for tier in tiers:
self.tiers[tier] = dict(
begin_time=None,
finish_time=None,
duration=None,
subtiers=OrderedDict(),
)
def begin_tier(self, tier, subtiers):
"""Record that execution of a tier has begun."""
t = self.tiers[tier]
# We should ideally use a monotonic clock here. Unfortunately, we won't
# have one until Python 3.
t['begin_time'] = time.time()
for subtier in subtiers:
t['subtiers'][subtier] = dict(
begin_time=None,
finish_time=None,
duration=None,
concurrent=False,
dirs=OrderedDict(),
dirs_complete=0,
)
self.active_tier = tier
self.active_subtiers = set()
self.active_dirs = {}
def finish_tier(self, tier):
"""Record that execution of a tier has finished."""
t = self.tiers[tier]
t['finish_time'] = time.time()
t['duration'] = t['finish_time'] - t['begin_time']
def begin_subtier(self, tier, subtier, dirs):
"""Record that execution of a subtier has begun."""
st = self.tiers[tier]['subtiers'][subtier]
st['begin_time'] = time.time()
for d in dirs:
st['dirs'][d] = dict(
begin_time=None,
finish_time=None,
duration=None,
concurrent=False,
)
if self.active_subtiers:
st['concurrent'] = True
self.active_subtiers.add(subtier)
def finish_subtier(self, tier, subtier):
"""Record that execution of a subtier has finished."""
st = self.tiers[tier]['subtiers'][subtier]
st['finish_time'] = time.time()
self.active_subtiers.remove(subtier)
if self.active_subtiers:
st['concurrent'] = True
# A subtier may not have directories.
try:
del self.active_dirs[subtier]
except KeyError:
pass
st['duration'] = st['finish_time'] - st['begin_time']
def begin_dir(self, tier, subtier, d):
"""Record that execution of a directory has begun."""
entry = self.tiers[tier]['subtiers'][subtier]['dirs'][d]
entry['begin_time'] = time.time()
self.active_dirs.setdefault(subtier, set()).add(d)
if len(self.active_dirs[subtier]) > 1:
entry['concurrent'] = True
def finish_dir(self, tier, subtier, d):
"""Record that execution of a directory has finished."""
st = self.tiers[tier]['subtiers'][subtier]
st['dirs_complete'] += 1
entry = st['dirs'][d]
entry['finish_time'] = time.time()
self.active_dirs[subtier].remove(d)
entry['duration'] = entry['finish_time'] - entry['begin_time']
if self.active_dirs[subtier]:
entry['concurrent'] = True
def tier_status(self):
for tier, state in self.tiers.items():
active = self.active_tier == tier
finished = state['finish_time'] is not None
yield tier, active, finished
def current_subtier_status(self):
for subtier, state in self.tiers[self.active_tier]['subtiers'].items():
active = subtier in self.active_subtiers
finished = state['finish_time'] is not None
yield subtier, active, finished
def current_dirs_status(self):
for subtier, dirs in self.active_dirs.items():
st = self.tiers[self.active_tier]['subtiers'][subtier]
yield subtier, st['dirs'].keys(), dirs, st['dirs_complete']
class BuildMonitor(MozbuildObject):
"""Monitors the output of the build."""
def __init__(self, topobjdir, warnings_path):
def init(self, warnings_path):
"""Create a new monitor.
warnings_path is a path of a warnings database to use.
"""
self._warnings_path = warnings_path
self.tiers = []
self.subtiers = []
self.current_tier = None
self.current_subtier = None
self.current_tier_dirs = []
self.current_tier_static_dirs = []
self.current_subtier_dirs = []
self.current_subtier_started = set()
self.current_subtier_finished = set()
self.current_tier_dir = None
self.current_tier_dir_index = 0
self.tiers = TierStatus()
self.warnings_database = WarningsDatabase()
if os.path.exists(warnings_path):
@ -60,7 +188,7 @@ class BuildMonitor(object):
os.remove(warnings_path)
self._warnings_collector = WarningsCollector(
database=self.warnings_database, objdir=topobjdir)
database=self.warnings_database, objdir=self.topobjdir)
def start(self):
"""Record the start of the build."""
@ -93,51 +221,28 @@ class BuildMonitor(object):
update_needed = True
if action == 'TIERS':
self.tiers = args
update_needed = False
elif action == 'SUBTIERS':
self.subtiers = args
update_needed = False
elif action == 'STATICDIRS':
self.current_tier_static_dirs = args
update_needed = False
elif action == 'DIRS':
self.current_tier_dirs = args
self.tiers.set_tiers(args)
update_needed = False
elif action == 'TIER_START':
assert len(args) == 1
self.current_tier = args[0]
self.current_subtier = None
self.current_tier_dirs = []
self.current_subtier_started = set()
self.current_subtier_finished = set()
self.current_tier_dir = None
tier = args[0]
subtiers = args[1:]
self.tiers.begin_tier(tier, subtiers)
elif action == 'TIER_FINISH':
assert len(args) == 1
assert args[0] == self.current_tier
tier, = args
self.tiers.finish_tier(tier)
elif action == 'SUBTIER_START':
assert len(args) == 2
tier, subtier = args
assert tier == self.current_tier
self.current_subtier = subtier
if subtier == 'static':
self.current_subtier_dirs = self.current_tier_static_dirs
else:
self.current_subtier_dirs = self.current_tier_dirs
self.current_tier_dir_index = 0
self.current_subtier_started.add(subtier)
tier, subtier = args[0:2]
dirs = args[2:]
self.tiers.begin_subtier(tier, subtier, dirs)
elif action == 'SUBTIER_FINISH':
assert len(args) == 2
tier, subtier = args
assert tier == self.current_tier
self.current_subtier_finished.add(subtier)
self.tiers.finish_subtier(tier, subtier)
elif action == 'TIERDIR_START':
assert len(args) == 1
self.current_tier_dir = args[0]
self.current_tier_dir_index += 1
tier, subtier, d = args
self.tiers.begin_dir(tier, subtier, d)
elif action == 'TIERDIR_FINISH':
assert len(args) == 1
assert self.current_tier_dir == args[0]
tier, subtier, d = args
self.tiers.finish_dir(tier, subtier, d)
else:
raise Exception('Unknown build status: %s' % action)

View File

@ -113,7 +113,9 @@ class BuildProgressFooter(object):
def draw(self):
"""Draws this footer in the terminal."""
if not self._monitor.tiers:
tiers = self._monitor.tiers
if not tiers.tiers:
return
# The drawn terminal looks something like:
@ -124,34 +126,48 @@ class BuildProgressFooter(object):
# big comment below.
parts = [('bold', 'TIER'), ':', ' ']
current_encountered = False
for tier in self._monitor.tiers:
if tier == self._monitor.current_tier:
for tier, active, finished in tiers.tier_status():
if active:
parts.extend([('underline_yellow', tier), ' '])
current_encountered = True
elif not current_encountered:
elif finished:
parts.extend([('green', tier), ' '])
else:
parts.extend([tier, ' '])
parts.extend([('bold', 'SUBTIER'), ':', ' '])
for subtier in self._monitor.subtiers:
if subtier in self._monitor.current_subtier_finished:
parts.extend([('green', subtier), ' '])
elif subtier in self._monitor.current_subtier_started:
for subtier, active, finished in tiers.current_subtier_status():
if active:
parts.extend([('underline_yellow', subtier), ' '])
elif finished:
parts.extend([('green', subtier), ' '])
else:
parts.extend([subtier, ' '])
if self._monitor.current_subtier_dirs and self._monitor.current_tier_dir:
if tiers.active_dirs:
parts.extend([('bold', 'DIRECTORIES'), ': '])
have_dirs = False
for subtier, all_dirs, active_dirs, complete in tiers.current_dirs_status():
if len(all_dirs) < 2:
continue
have_dirs = True
parts.extend([
('bold', 'DIRECTORIES'), ': ',
'%02d' % self._monitor.current_tier_dir_index,
'%02d' % (complete + 1),
'/',
'%02d' % len(self._monitor.current_subtier_dirs),
'%02d' % len(all_dirs),
' ',
'(', ('magenta', self._monitor.current_tier_dir), ')',
'(',
])
for d in active_dirs:
parts.extend([
('magenta', d), ' ,'
])
parts[-1] = ')'
if not have_dirs:
parts = parts[0:-2]
# We don't want to write more characters than the current width of the
# terminal otherwise wrapping may result in weird behavior. We can't
@ -274,7 +290,8 @@ class Build(MachCommandBase):
from mozbuild.util import resolve_target_to_make
warnings_path = self._get_state_filename('warnings.json')
monitor = BuildMonitor(self.topobjdir, warnings_path)
monitor = self._spawn(BuildMonitor)
monitor.init(warnings_path)
with BuildOutputManager(self.log_manager, monitor) as output:
monitor.start()