mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-09-27 14:56:16 +00:00
874 lines
31 KiB
Python
874 lines
31 KiB
Python
# 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, print_function, unicode_literals
|
|
|
|
import json
|
|
import logging
|
|
import mozpack.path as mozpath
|
|
import multiprocessing
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import which
|
|
|
|
from mach.mixin.logging import LoggingMixin
|
|
from mach.mixin.process import ProcessExecutionMixin
|
|
|
|
from mozfile.mozfile import rmtree
|
|
|
|
from .backend.configenvironment import ConfigEnvironment
|
|
from .controller.clobber import Clobberer
|
|
from .mozconfig import (
|
|
MozconfigFindException,
|
|
MozconfigLoadException,
|
|
MozconfigLoader,
|
|
)
|
|
from .util import memoized_property
|
|
from .virtualenv import VirtualenvManager
|
|
|
|
|
|
_config_guess_output = []
|
|
|
|
|
|
def ancestors(path):
|
|
"""Emit the parent directories of a path."""
|
|
while path:
|
|
yield path
|
|
newpath = os.path.dirname(path)
|
|
if newpath == path:
|
|
break
|
|
path = newpath
|
|
|
|
def samepath(path1, path2):
|
|
if hasattr(os.path, 'samefile'):
|
|
return os.path.samefile(path1, path2)
|
|
return os.path.normcase(os.path.realpath(path1)) == \
|
|
os.path.normcase(os.path.realpath(path2))
|
|
|
|
class BadEnvironmentException(Exception):
|
|
"""Base class for errors raised when the build environment is not sane."""
|
|
|
|
|
|
class BuildEnvironmentNotFoundException(BadEnvironmentException):
|
|
"""Raised when we could not find a build environment."""
|
|
|
|
|
|
class ObjdirMismatchException(BadEnvironmentException):
|
|
"""Raised when the current dir is an objdir and doesn't match the mozconfig."""
|
|
def __init__(self, objdir1, objdir2):
|
|
self.objdir1 = objdir1
|
|
self.objdir2 = objdir2
|
|
|
|
def __str__(self):
|
|
return "Objdir mismatch: %s != %s" % (self.objdir1, self.objdir2)
|
|
|
|
|
|
class MozbuildObject(ProcessExecutionMixin):
|
|
"""Base class providing basic functionality useful to many modules.
|
|
|
|
Modules in this package typically require common functionality such as
|
|
accessing the current config, getting the location of the source directory,
|
|
running processes, etc. This classes provides that functionality. Other
|
|
modules can inherit from this class to obtain this functionality easily.
|
|
"""
|
|
def __init__(self, topsrcdir, settings, log_manager, topobjdir=None):
|
|
"""Create a new Mozbuild object instance.
|
|
|
|
Instances are bound to a source directory, a ConfigSettings instance,
|
|
and a LogManager instance. The topobjdir may be passed in as well. If
|
|
it isn't, it will be calculated from the active mozconfig.
|
|
"""
|
|
self.topsrcdir = topsrcdir
|
|
self.settings = settings
|
|
|
|
self.populate_logger()
|
|
self.log_manager = log_manager
|
|
|
|
self._make = None
|
|
self._topobjdir = topobjdir
|
|
self._mozconfig = None
|
|
self._config_guess_output = None
|
|
self._config_environment = None
|
|
self._virtualenv_manager = None
|
|
|
|
@classmethod
|
|
def from_environment(cls, cwd=None, detect_virtualenv_mozinfo=True):
|
|
"""Create a MozbuildObject by detecting the proper one from the env.
|
|
|
|
This examines environment state like the current working directory and
|
|
creates a MozbuildObject from the found source directory, mozconfig, etc.
|
|
|
|
The role of this function is to identify a topsrcdir, topobjdir, and
|
|
mozconfig file.
|
|
|
|
If the current working directory is inside a known objdir, we always
|
|
use the topsrcdir and mozconfig associated with that objdir. If no
|
|
mozconfig is associated with that objdir, we fall back to looking for
|
|
the mozconfig in the usual places.
|
|
|
|
If the current working directory is inside a known srcdir, we use that
|
|
topsrcdir and look for mozconfigs using the default mechanism, which
|
|
looks inside environment variables.
|
|
|
|
If the current Python interpreter is running from a virtualenv inside
|
|
an objdir, we use that as our objdir.
|
|
|
|
If we're not inside a srcdir or objdir, an exception is raised.
|
|
|
|
detect_virtualenv_mozinfo determines whether we should look for a
|
|
mozinfo.json file relative to the virtualenv directory. This was
|
|
added to facilitate testing. Callers likely shouldn't change the
|
|
default.
|
|
"""
|
|
|
|
cwd = cwd or os.getcwd()
|
|
topsrcdir = None
|
|
topobjdir = None
|
|
mozconfig = None
|
|
|
|
def load_mozinfo(path):
|
|
info = json.load(open(path, 'rt'))
|
|
topsrcdir = info.get('topsrcdir')
|
|
topobjdir = os.path.dirname(path)
|
|
mozconfig = info.get('mozconfig')
|
|
return topsrcdir, topobjdir, mozconfig
|
|
|
|
for dir_path in ancestors(cwd):
|
|
# If we find a mozinfo.json, we are in the objdir.
|
|
mozinfo_path = os.path.join(dir_path, 'mozinfo.json')
|
|
if os.path.isfile(mozinfo_path):
|
|
topsrcdir, topobjdir, mozconfig = load_mozinfo(mozinfo_path)
|
|
break
|
|
|
|
# We choose an arbitrary file as an indicator that this is a
|
|
# srcdir. We go with ourself because why not!
|
|
our_path = os.path.join(dir_path, 'python', 'mozbuild', 'mozbuild', 'base.py')
|
|
if os.path.isfile(our_path):
|
|
topsrcdir = dir_path
|
|
break
|
|
|
|
# See if we're running from a Python virtualenv that's inside an objdir.
|
|
mozinfo_path = os.path.join(os.path.dirname(sys.prefix), "mozinfo.json")
|
|
if detect_virtualenv_mozinfo and os.path.isfile(mozinfo_path):
|
|
topsrcdir, topobjdir, mozconfig = load_mozinfo(mozinfo_path)
|
|
|
|
# If we were successful, we're only guaranteed to find a topsrcdir. If
|
|
# we couldn't find that, there's nothing we can do.
|
|
if not topsrcdir:
|
|
raise BuildEnvironmentNotFoundException(
|
|
'Could not find Mozilla source tree or build environment.')
|
|
|
|
# Now we try to load the config for this environment. If mozconfig is
|
|
# None, read_mozconfig() will attempt to find one in the existing
|
|
# environment. If no mozconfig is present, the config will not have
|
|
# much defined.
|
|
loader = MozconfigLoader(topsrcdir)
|
|
current_project = os.environ.get('MOZ_CURRENT_PROJECT')
|
|
config = loader.read_mozconfig(mozconfig, moz_build_app=current_project)
|
|
|
|
config_topobjdir = MozbuildObject.resolve_mozconfig_topobjdir(
|
|
topsrcdir, config)
|
|
|
|
# If we're inside a objdir and the found mozconfig resolves to
|
|
# another objdir, we abort. The reasoning here is that if you are
|
|
# inside an objdir you probably want to perform actions on that objdir,
|
|
# not another one. This prevents accidental usage of the wrong objdir
|
|
# when the current objdir is ambiguous.
|
|
if topobjdir and config_topobjdir:
|
|
if current_project:
|
|
config_topobjdir = os.path.join(config_topobjdir, current_project)
|
|
|
|
_config_topobjdir = config_topobjdir
|
|
if not samepath(topobjdir, _config_topobjdir):
|
|
raise ObjdirMismatchException(topobjdir, _config_topobjdir)
|
|
|
|
topobjdir = topobjdir or config_topobjdir
|
|
if topobjdir:
|
|
topobjdir = os.path.normpath(topobjdir)
|
|
|
|
if topsrcdir == topobjdir:
|
|
raise BadEnvironmentException('The object directory appears '
|
|
'to be the same as your source directory (%s). This build '
|
|
'configuration is not supported.' % topsrcdir)
|
|
|
|
# If we can't resolve topobjdir, oh well. The constructor will figure
|
|
# it out via config.guess.
|
|
return cls(topsrcdir, None, None, topobjdir=topobjdir)
|
|
|
|
@staticmethod
|
|
def resolve_mozconfig_topobjdir(topsrcdir, mozconfig, default=None):
|
|
topobjdir = mozconfig['topobjdir'] or default
|
|
if not topobjdir:
|
|
return None
|
|
|
|
if '@CONFIG_GUESS@' in topobjdir:
|
|
topobjdir = topobjdir.replace('@CONFIG_GUESS@',
|
|
MozbuildObject.resolve_config_guess(mozconfig, topsrcdir))
|
|
|
|
if not os.path.isabs(topobjdir):
|
|
topobjdir = os.path.abspath(os.path.join(topsrcdir, topobjdir))
|
|
|
|
return os.path.normpath(topobjdir)
|
|
|
|
@property
|
|
def topobjdir(self):
|
|
if self._topobjdir is None:
|
|
self._topobjdir = MozbuildObject.resolve_mozconfig_topobjdir(
|
|
self.topsrcdir, self.mozconfig, default='obj-@CONFIG_GUESS@')
|
|
|
|
return self._topobjdir
|
|
|
|
@property
|
|
def virtualenv_manager(self):
|
|
if self._virtualenv_manager is None:
|
|
self._virtualenv_manager = VirtualenvManager(self.topsrcdir,
|
|
self.topobjdir, os.path.join(self.topobjdir, '_virtualenv'),
|
|
sys.stdout, os.path.join(self.topsrcdir, 'build',
|
|
'virtualenv_packages.txt'))
|
|
|
|
return self._virtualenv_manager
|
|
|
|
@property
|
|
def mozconfig(self):
|
|
"""Returns information about the current mozconfig file.
|
|
|
|
This a dict as returned by MozconfigLoader.read_mozconfig()
|
|
"""
|
|
if self._mozconfig is None:
|
|
loader = MozconfigLoader(self.topsrcdir)
|
|
self._mozconfig = loader.read_mozconfig(
|
|
moz_build_app=os.environ.get('MOZ_CURRENT_PROJECT'))
|
|
|
|
return self._mozconfig
|
|
|
|
@property
|
|
def config_environment(self):
|
|
"""Returns the ConfigEnvironment for the current build configuration.
|
|
|
|
This property is only available once configure has executed.
|
|
|
|
If configure's output is not available, this will raise.
|
|
"""
|
|
if self._config_environment:
|
|
return self._config_environment
|
|
|
|
config_status = os.path.join(self.topobjdir, 'config.status')
|
|
|
|
if not os.path.exists(config_status):
|
|
raise Exception('config.status not available. Run configure.')
|
|
|
|
self._config_environment = \
|
|
ConfigEnvironment.from_config_status(config_status)
|
|
|
|
return self._config_environment
|
|
|
|
@property
|
|
def defines(self):
|
|
return self.config_environment.defines
|
|
|
|
@property
|
|
def substs(self):
|
|
return self.config_environment.substs
|
|
|
|
@property
|
|
def distdir(self):
|
|
return os.path.join(self.topobjdir, 'dist')
|
|
|
|
@property
|
|
def bindir(self):
|
|
return os.path.join(self.topobjdir, 'dist', 'bin')
|
|
|
|
@property
|
|
def includedir(self):
|
|
return os.path.join(self.topobjdir, 'dist', 'include')
|
|
|
|
@property
|
|
def statedir(self):
|
|
return os.path.join(self.topobjdir, '.mozbuild')
|
|
|
|
@memoized_property
|
|
def extra_environment_variables(self):
|
|
'''Some extra environment variables are stored in .mozconfig.mk.
|
|
This functions extracts and returns them.'''
|
|
from mozbuild import shellutil
|
|
mozconfig_mk = os.path.join(self.topobjdir, '.mozconfig.mk')
|
|
env = {}
|
|
with open(mozconfig_mk) as fh:
|
|
for line in fh:
|
|
if line.startswith('export '):
|
|
exports = shellutil.split(line)[1:]
|
|
for e in exports:
|
|
if '=' in e:
|
|
key, value = e.split('=')
|
|
env[key] = value
|
|
return env
|
|
|
|
def is_clobber_needed(self):
|
|
if not os.path.exists(self.topobjdir):
|
|
return False
|
|
return Clobberer(self.topsrcdir, self.topobjdir).clobber_needed()
|
|
|
|
def have_winrm(self):
|
|
# `winrm -h` should print 'winrm version ...' and exit 1
|
|
try:
|
|
p = subprocess.Popen(['winrm.exe', '-h'],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT)
|
|
return p.wait() == 1 and p.stdout.read().startswith('winrm')
|
|
except:
|
|
return False
|
|
|
|
def remove_objdir(self):
|
|
"""Remove the entire object directory."""
|
|
|
|
if sys.platform.startswith('win') and self.have_winrm():
|
|
subprocess.check_call(['winrm', '-rf', self.topobjdir])
|
|
else:
|
|
# We use mozfile because it is faster than shutil.rmtree().
|
|
# mozfile doesn't like unicode arguments (bug 818783).
|
|
rmtree(self.topobjdir.encode('utf-8'))
|
|
|
|
def get_binary_path(self, what='app', validate_exists=True, where='default'):
|
|
"""Obtain the path to a compiled binary for this build configuration.
|
|
|
|
The what argument is the program or tool being sought after. See the
|
|
code implementation for supported values.
|
|
|
|
If validate_exists is True (the default), we will ensure the found path
|
|
exists before returning, raising an exception if it doesn't.
|
|
|
|
If where is 'staged-package', we will return the path to the binary in
|
|
the package staging directory.
|
|
|
|
If no arguments are specified, we will return the main binary for the
|
|
configured XUL application.
|
|
"""
|
|
|
|
if where not in ('default', 'staged-package'):
|
|
raise Exception("Don't know location %s" % where)
|
|
|
|
substs = self.substs
|
|
|
|
stem = self.distdir
|
|
if where == 'staged-package':
|
|
stem = os.path.join(stem, substs['MOZ_APP_NAME'])
|
|
|
|
if substs['OS_ARCH'] == 'Darwin':
|
|
if substs['MOZ_BUILD_APP'] == 'xulrunner':
|
|
stem = os.path.join(stem, 'XUL.framework');
|
|
else:
|
|
stem = os.path.join(stem, substs['MOZ_MACBUNDLE_NAME'], 'Contents',
|
|
'MacOS')
|
|
elif where == 'default':
|
|
stem = os.path.join(stem, 'bin')
|
|
|
|
leaf = None
|
|
|
|
leaf = (substs['MOZ_APP_NAME'] if what == 'app' else what) + substs['BIN_SUFFIX']
|
|
path = os.path.join(stem, leaf)
|
|
|
|
if validate_exists and not os.path.exists(path):
|
|
raise Exception('Binary expected at %s does not exist.' % path)
|
|
|
|
return path
|
|
|
|
@staticmethod
|
|
def resolve_config_guess(mozconfig, topsrcdir):
|
|
make_extra = mozconfig['make_extra'] or []
|
|
make_extra = dict(m.split('=', 1) for m in make_extra)
|
|
|
|
config_guess = make_extra.get('CONFIG_GUESS', None)
|
|
|
|
if config_guess:
|
|
return config_guess
|
|
|
|
# config.guess results should be constant for process lifetime. Cache
|
|
# it.
|
|
if _config_guess_output:
|
|
return _config_guess_output[0]
|
|
|
|
p = os.path.join(topsrcdir, 'build', 'autoconf', 'config.guess')
|
|
|
|
# This is a little kludgy. We need access to the normalize_command
|
|
# function. However, that's a method of a mach mixin, so we need a
|
|
# class instance. Ideally the function should be accessible as a
|
|
# standalone function.
|
|
o = MozbuildObject(topsrcdir, None, None, None)
|
|
args = o._normalize_command([p], True)
|
|
|
|
_config_guess_output.append(
|
|
subprocess.check_output(args, cwd=topsrcdir).strip())
|
|
return _config_guess_output[0]
|
|
|
|
def notify(self, msg):
|
|
"""Show a desktop notification with the supplied message
|
|
|
|
On Linux and Mac, this will show a desktop notification with the message,
|
|
but on Windows we can only flash the screen.
|
|
"""
|
|
moz_nospam = os.environ.get('MOZ_NOSPAM')
|
|
if moz_nospam:
|
|
return
|
|
|
|
try:
|
|
if sys.platform.startswith('darwin'):
|
|
try:
|
|
notifier = which.which('terminal-notifier')
|
|
except which.WhichError:
|
|
raise Exception('Install terminal-notifier to get '
|
|
'a notification when the build finishes.')
|
|
self.run_process([notifier, '-title',
|
|
'Mozilla Build System', '-group', 'mozbuild',
|
|
'-message', msg], ensure_exit_code=False)
|
|
elif sys.platform.startswith('linux'):
|
|
try:
|
|
import dbus
|
|
except ImportError:
|
|
raise Exception('Install the python dbus module to '
|
|
'get a notification when the build finishes.')
|
|
bus = dbus.SessionBus()
|
|
notify = bus.get_object('org.freedesktop.Notifications',
|
|
'/org/freedesktop/Notifications')
|
|
method = notify.get_dbus_method('Notify',
|
|
'org.freedesktop.Notifications')
|
|
method('Mozilla Build System', 0, '', msg, '', [], [], -1)
|
|
elif sys.platform.startswith('win'):
|
|
from ctypes import Structure, windll, POINTER, sizeof
|
|
from ctypes.wintypes import DWORD, HANDLE, WINFUNCTYPE, BOOL, UINT
|
|
class FLASHWINDOW(Structure):
|
|
_fields_ = [("cbSize", UINT),
|
|
("hwnd", HANDLE),
|
|
("dwFlags", DWORD),
|
|
("uCount", UINT),
|
|
("dwTimeout", DWORD)]
|
|
FlashWindowExProto = WINFUNCTYPE(BOOL, POINTER(FLASHWINDOW))
|
|
FlashWindowEx = FlashWindowExProto(("FlashWindowEx", windll.user32))
|
|
FLASHW_CAPTION = 0x01
|
|
FLASHW_TRAY = 0x02
|
|
FLASHW_TIMERNOFG = 0x0C
|
|
params = FLASHWINDOW(sizeof(FLASHWINDOW),
|
|
windll.kernel32.GetConsoleWindow(),
|
|
FLASHW_CAPTION | FLASHW_TRAY | FLASHW_TIMERNOFG, 3, 0)
|
|
FlashWindowEx(params)
|
|
except Exception as e:
|
|
self.log(logging.WARNING, 'notifier-failed', {'error':
|
|
e.message}, 'Notification center failed: {error}')
|
|
|
|
@property
|
|
def _config_guess(self):
|
|
if self._config_guess_output is None:
|
|
self._config_guess_output = MozbuildObject.resolve_config_guess(
|
|
self.mozconfig, self.topsrcdir)
|
|
|
|
return self._config_guess_output
|
|
|
|
def _ensure_objdir_exists(self):
|
|
if os.path.isdir(self.statedir):
|
|
return
|
|
|
|
os.makedirs(self.statedir)
|
|
|
|
def _ensure_state_subdir_exists(self, subdir):
|
|
path = os.path.join(self.statedir, subdir)
|
|
|
|
if os.path.isdir(path):
|
|
return
|
|
|
|
os.makedirs(path)
|
|
|
|
def _get_state_filename(self, filename, subdir=None):
|
|
path = self.statedir
|
|
|
|
if subdir:
|
|
path = os.path.join(path, subdir)
|
|
|
|
return os.path.join(path, filename)
|
|
|
|
def _wrap_path_argument(self, arg):
|
|
return PathArgument(arg, self.topsrcdir, self.topobjdir)
|
|
|
|
def _run_make(self, directory=None, filename=None, target=None, log=True,
|
|
srcdir=False, allow_parallel=True, line_handler=None,
|
|
append_env=None, explicit_env=None, ignore_errors=False,
|
|
ensure_exit_code=0, silent=True, print_directory=True,
|
|
pass_thru=False, num_jobs=0):
|
|
"""Invoke make.
|
|
|
|
directory -- Relative directory to look for Makefile in.
|
|
filename -- Explicit makefile to run.
|
|
target -- Makefile target(s) to make. Can be a string or iterable of
|
|
strings.
|
|
srcdir -- If True, invoke make from the source directory tree.
|
|
Otherwise, make will be invoked from the object directory.
|
|
silent -- If True (the default), run make in silent mode.
|
|
print_directory -- If True (the default), have make print directories
|
|
while doing traversal.
|
|
"""
|
|
self._ensure_objdir_exists()
|
|
|
|
args = self._make_path()
|
|
|
|
if directory:
|
|
args.extend(['-C', directory.replace(os.sep, '/')])
|
|
|
|
if filename:
|
|
args.extend(['-f', filename])
|
|
|
|
if num_jobs == 0 and self.mozconfig['make_flags']:
|
|
flags = iter(self.mozconfig['make_flags'])
|
|
for flag in flags:
|
|
if flag == '-j':
|
|
try:
|
|
flag = flags.next()
|
|
except StopIteration:
|
|
break
|
|
try:
|
|
num_jobs = int(flag)
|
|
except ValueError:
|
|
args.append(flag)
|
|
elif flag.startswith('-j'):
|
|
try:
|
|
num_jobs = int(flag[2:])
|
|
except (ValueError, IndexError):
|
|
break
|
|
else:
|
|
args.append(flag)
|
|
|
|
if allow_parallel:
|
|
if num_jobs > 0:
|
|
args.append('-j%d' % num_jobs)
|
|
else:
|
|
args.append('-j%d' % multiprocessing.cpu_count())
|
|
elif num_jobs > 0:
|
|
args.append('MOZ_PARALLEL_BUILD=%d' % num_jobs)
|
|
|
|
if ignore_errors:
|
|
args.append('-k')
|
|
|
|
if silent:
|
|
args.append('-s')
|
|
|
|
# Print entering/leaving directory messages. Some consumers look at
|
|
# these to measure progress.
|
|
if print_directory:
|
|
args.append('-w')
|
|
|
|
if isinstance(target, list):
|
|
args.extend(target)
|
|
elif target:
|
|
args.append(target)
|
|
|
|
fn = self._run_command_in_objdir
|
|
|
|
if srcdir:
|
|
fn = self._run_command_in_srcdir
|
|
|
|
append_env = dict(append_env or ())
|
|
append_env[b'MACH'] = '1'
|
|
|
|
params = {
|
|
'args': args,
|
|
'line_handler': line_handler,
|
|
'append_env': append_env,
|
|
'explicit_env': explicit_env,
|
|
'log_level': logging.INFO,
|
|
'require_unix_environment': False,
|
|
'ensure_exit_code': ensure_exit_code,
|
|
'pass_thru': pass_thru,
|
|
|
|
# Make manages its children, so mozprocess doesn't need to bother.
|
|
# Having mozprocess manage children can also have side-effects when
|
|
# building on Windows. See bug 796840.
|
|
'ignore_children': True,
|
|
}
|
|
|
|
if log:
|
|
params['log_name'] = 'make'
|
|
|
|
return fn(**params)
|
|
|
|
def _make_path(self):
|
|
baseconfig = os.path.join(self.topsrcdir, 'config', 'baseconfig.mk')
|
|
|
|
def is_xcode_lisense_error(output):
|
|
return self._is_osx() and 'Agreeing to the Xcode' in output
|
|
|
|
def validate_make(make):
|
|
if os.path.exists(baseconfig) and os.path.exists(make):
|
|
cmd = [make, '-f', baseconfig]
|
|
if self._is_windows():
|
|
cmd.append('HOST_OS_ARCH=WINNT')
|
|
try:
|
|
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
return False, is_xcode_lisense_error(e.output)
|
|
return True, False
|
|
return False, False
|
|
|
|
xcode_lisense_error = False
|
|
possible_makes = ['gmake', 'make', 'mozmake', 'gnumake']
|
|
|
|
if 'MAKE' in os.environ:
|
|
make = os.environ['MAKE']
|
|
possible_makes.insert(0, make)
|
|
|
|
for test in possible_makes:
|
|
if os.path.isabs(test):
|
|
make = test
|
|
else:
|
|
try:
|
|
make = which.which(test)
|
|
except which.WhichError:
|
|
continue
|
|
result, xcode_lisense_error_tmp = validate_make(make)
|
|
if result:
|
|
return [make]
|
|
if xcode_lisense_error_tmp:
|
|
xcode_lisense_error = True
|
|
|
|
if xcode_lisense_error:
|
|
raise Exception('Xcode requires accepting to the license agreement.\n'
|
|
'Please run Xcode and accept the license agreement.')
|
|
|
|
if self._is_windows():
|
|
raise Exception('Could not find a suitable make implementation.\n'
|
|
'Please use MozillaBuild 1.9 or newer')
|
|
else:
|
|
raise Exception('Could not find a suitable make implementation.')
|
|
|
|
def _run_command_in_srcdir(self, **args):
|
|
return self.run_process(cwd=self.topsrcdir, **args)
|
|
|
|
def _run_command_in_objdir(self, **args):
|
|
return self.run_process(cwd=self.topobjdir, **args)
|
|
|
|
def _is_windows(self):
|
|
return os.name in ('nt', 'ce')
|
|
|
|
def _is_osx(self):
|
|
return 'darwin' in str(sys.platform).lower()
|
|
|
|
def _spawn(self, cls):
|
|
"""Create a new MozbuildObject-derived class instance from ourselves.
|
|
|
|
This is used as a convenience method to create other
|
|
MozbuildObject-derived class instances. It can only be used on
|
|
classes that have the same constructor arguments as us.
|
|
"""
|
|
|
|
return cls(self.topsrcdir, self.settings, self.log_manager,
|
|
topobjdir=self.topobjdir)
|
|
|
|
def _activate_virtualenv(self):
|
|
self.virtualenv_manager.ensure()
|
|
self.virtualenv_manager.activate()
|
|
|
|
|
|
class MachCommandBase(MozbuildObject):
|
|
"""Base class for mach command providers that wish to be MozbuildObjects.
|
|
|
|
This provides a level of indirection so MozbuildObject can be refactored
|
|
without having to change everything that inherits from it.
|
|
"""
|
|
|
|
def __init__(self, context):
|
|
# Attempt to discover topobjdir through environment detection, as it is
|
|
# more reliable than mozconfig when cwd is inside an objdir.
|
|
topsrcdir = context.topdir
|
|
topobjdir = None
|
|
detect_virtualenv_mozinfo = True
|
|
if hasattr(context, 'detect_virtualenv_mozinfo'):
|
|
detect_virtualenv_mozinfo = getattr(context,
|
|
'detect_virtualenv_mozinfo')
|
|
try:
|
|
dummy = MozbuildObject.from_environment(cwd=context.cwd,
|
|
detect_virtualenv_mozinfo=detect_virtualenv_mozinfo)
|
|
topsrcdir = dummy.topsrcdir
|
|
topobjdir = dummy._topobjdir
|
|
except BuildEnvironmentNotFoundException:
|
|
pass
|
|
except ObjdirMismatchException as e:
|
|
print('Ambiguous object directory detected. We detected that '
|
|
'both %s and %s could be object directories. This is '
|
|
'typically caused by having a mozconfig pointing to a '
|
|
'different object directory from the current working '
|
|
'directory. To solve this problem, ensure you do not have a '
|
|
'default mozconfig in searched paths.' % (e.objdir1,
|
|
e.objdir2))
|
|
sys.exit(1)
|
|
|
|
except MozconfigLoadException as e:
|
|
print('Error loading mozconfig: ' + e.path)
|
|
print('')
|
|
print(e.message)
|
|
if e.output:
|
|
print('')
|
|
print('mozconfig output:')
|
|
print('')
|
|
for line in e.output:
|
|
print(line)
|
|
|
|
sys.exit(1)
|
|
|
|
MozbuildObject.__init__(self, topsrcdir, context.settings,
|
|
context.log_manager, topobjdir=topobjdir)
|
|
|
|
self._mach_context = context
|
|
|
|
# Incur mozconfig processing so we have unified error handling for
|
|
# errors. Otherwise, the exceptions could bubble back to mach's error
|
|
# handler.
|
|
try:
|
|
self.mozconfig
|
|
|
|
except MozconfigFindException as e:
|
|
print(e.message)
|
|
sys.exit(1)
|
|
|
|
except MozconfigLoadException as e:
|
|
print('Error loading mozconfig: ' + e.path)
|
|
print('')
|
|
print(e.message)
|
|
if e.output:
|
|
print('')
|
|
print('mozconfig output:')
|
|
print('')
|
|
for line in e.output:
|
|
print(line)
|
|
|
|
sys.exit(1)
|
|
|
|
# Always keep a log of the last command, but don't do that for mach
|
|
# invokations from scripts (especially not the ones done by the build
|
|
# system itself).
|
|
if (self.log_manager and self.log_manager.terminal and
|
|
not getattr(self, 'NO_AUTO_LOG', False)):
|
|
self._ensure_state_subdir_exists('.')
|
|
logfile = self._get_state_filename('last_log.json')
|
|
try:
|
|
fd = open(logfile, "wb")
|
|
self.log_manager.add_json_handler(fd)
|
|
except Exception as e:
|
|
self.log(logging.WARNING, 'mach', {'error': e},
|
|
'Log will not be kept for this command: {error}.')
|
|
|
|
|
|
class MachCommandConditions(object):
|
|
"""A series of commonly used condition functions which can be applied to
|
|
mach commands with providers deriving from MachCommandBase.
|
|
"""
|
|
@staticmethod
|
|
def is_firefox(cls):
|
|
"""Must have a Firefox build."""
|
|
if hasattr(cls, 'substs'):
|
|
return cls.substs.get('MOZ_BUILD_APP') == 'browser'
|
|
return False
|
|
|
|
@staticmethod
|
|
def is_mulet(cls):
|
|
"""Must have a Mulet build."""
|
|
if hasattr(cls, 'substs'):
|
|
return cls.substs.get('MOZ_BUILD_APP') == 'b2g/dev'
|
|
return False
|
|
|
|
@staticmethod
|
|
def is_b2g(cls):
|
|
"""Must have a B2G build."""
|
|
if hasattr(cls, 'substs'):
|
|
return cls.substs.get('MOZ_WIDGET_TOOLKIT') == 'gonk'
|
|
return False
|
|
|
|
@staticmethod
|
|
def is_b2g_desktop(cls):
|
|
"""Must have a B2G desktop build."""
|
|
if hasattr(cls, 'substs'):
|
|
return cls.substs.get('MOZ_BUILD_APP') == 'b2g' and \
|
|
cls.substs.get('MOZ_WIDGET_TOOLKIT') != 'gonk'
|
|
return False
|
|
|
|
@staticmethod
|
|
def is_emulator(cls):
|
|
"""Must have a B2G build with an emulator configured."""
|
|
try:
|
|
return MachCommandConditions.is_b2g(cls) and \
|
|
cls.device_name.startswith('emulator')
|
|
except AttributeError:
|
|
return False
|
|
|
|
@staticmethod
|
|
def is_android(cls):
|
|
"""Must have an Android build."""
|
|
if hasattr(cls, 'substs'):
|
|
return cls.substs.get('MOZ_WIDGET_TOOLKIT') == 'android'
|
|
return False
|
|
|
|
@staticmethod
|
|
def is_hg(cls):
|
|
"""Must have a mercurial source checkout."""
|
|
if hasattr(cls, 'substs'):
|
|
top_srcdir = cls.substs.get('top_srcdir')
|
|
return top_srcdir and os.path.isdir(os.path.join(top_srcdir, '.hg'))
|
|
return False
|
|
|
|
@staticmethod
|
|
def is_git(cls):
|
|
"""Must have a git source checkout."""
|
|
if hasattr(cls, 'substs'):
|
|
top_srcdir = cls.substs.get('top_srcdir')
|
|
return top_srcdir and os.path.isdir(os.path.join(top_srcdir, '.git'))
|
|
return False
|
|
|
|
|
|
class PathArgument(object):
|
|
"""Parse a filesystem path argument and transform it in various ways."""
|
|
|
|
def __init__(self, arg, topsrcdir, topobjdir, cwd=None):
|
|
self.arg = arg
|
|
self.topsrcdir = topsrcdir
|
|
self.topobjdir = topobjdir
|
|
self.cwd = os.getcwd() if cwd is None else cwd
|
|
|
|
def relpath(self):
|
|
"""Return a path relative to the topsrcdir or topobjdir.
|
|
|
|
If the argument is a path to a location in one of the base directories
|
|
(topsrcdir or topobjdir), then strip off the base directory part and
|
|
just return the path within the base directory."""
|
|
|
|
abspath = os.path.abspath(os.path.join(self.cwd, self.arg))
|
|
|
|
# If that path is within topsrcdir or topobjdir, return an equivalent
|
|
# path relative to that base directory.
|
|
for base_dir in [self.topobjdir, self.topsrcdir]:
|
|
if abspath.startswith(os.path.abspath(base_dir)):
|
|
return mozpath.relpath(abspath, base_dir)
|
|
|
|
return mozpath.normsep(self.arg)
|
|
|
|
def srcdir_path(self):
|
|
return mozpath.join(self.topsrcdir, self.relpath())
|
|
|
|
def objdir_path(self):
|
|
return mozpath.join(self.topobjdir, self.relpath())
|
|
|
|
|
|
class ExecutionSummary(dict):
|
|
"""Helper for execution summaries."""
|
|
|
|
def __init__(self, summary_format, **data):
|
|
self._summary_format = ''
|
|
assert 'execution_time' in data
|
|
self.extend(summary_format, **data)
|
|
|
|
def extend(self, summary_format, **data):
|
|
self._summary_format += summary_format
|
|
self.update(data)
|
|
|
|
def __str__(self):
|
|
return self._summary_format.format(**self)
|
|
|
|
def __getattr__(self, key):
|
|
return self[key]
|