mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 03:45:46 +00:00
Bug 1162191
- Add |mach artifact| for installing downloaded Fennec binaries. r=gps
DONTBUILD ON A CLOSED TREE: Android-only and the build changes are cosmetic. Very much a first cut, but I'd like to get some Fennec early adopters testing. This adds: * |mach artifact install| to fetch and install Fennec binaries; * |mach artifact last| to print details about what was last installed; * |mach artifact {print,clear}-caches|, for debugging. This code is exposed as a new mozbuild.artifacts Python but it's not particularly general. My intention was to get things out of the mach command more than produce a general artifact fetching API. We can leave that bike shed to Bug 1124378. I've been testing this with --disable-compile-environment and it works well locally, although there's no reason a knowledgeable developer couldn't use this in conjunction with a fully-built tree. (I don't know when such a situation would arise, but I know of no technical impediment.) --HG-- extra : commitid : 1T28aVfArqF extra : rebase_source : b8c11244de8be0a14d605853f30cd47312d0a4ba extra : histedit_source : 78a224501cd3cf0f86707c9c9549b61b4b248ba7
This commit is contained in:
parent
15c38ccf08
commit
0a6017a1af
@ -23,6 +23,7 @@ from mach.decorators import (
|
||||
CommandArgument,
|
||||
CommandProvider,
|
||||
Command,
|
||||
SubCommand,
|
||||
)
|
||||
|
||||
SUCCESS = '''
|
||||
@ -35,6 +36,7 @@ and in IntelliJ select File > Import project... and choose
|
||||
{topobjdir}/mobile/android/gradle
|
||||
'''
|
||||
|
||||
|
||||
@CommandProvider
|
||||
class MachCommands(MachCommandBase):
|
||||
@Command('gradle', category='devenv',
|
||||
@ -54,7 +56,6 @@ class MachCommands(MachCommandBase):
|
||||
ensure_exit_code=False, # Don't throw on non-zero exit code.
|
||||
cwd=mozpath.join(self.topobjdir, 'mobile', 'android', 'gradle'))
|
||||
|
||||
|
||||
@Command('gradle-install', category='devenv',
|
||||
description='Install gradle environment.',
|
||||
conditions=[conditions.is_android])
|
||||
@ -162,3 +163,104 @@ class MachCommands(MachCommandBase):
|
||||
print(SUCCESS.format(topobjdir=self.topobjdir))
|
||||
|
||||
return code
|
||||
|
||||
|
||||
@CommandProvider
|
||||
class PackageFrontend(MachCommandBase):
|
||||
"""Fetch and install binary artifacts from Mozilla automation."""
|
||||
|
||||
@Command('artifact', category='post-build',
|
||||
description='Use pre-built artifacts to build Fennec.',
|
||||
conditions=[
|
||||
conditions.is_android, # mobile/android only for now.
|
||||
conditions.is_hg, # mercurial only for now.
|
||||
])
|
||||
def artifact(self):
|
||||
'''Download, cache, and install pre-built binary artifacts to build Fennec.
|
||||
|
||||
Invoke |mach artifact| before each |mach package| to freshen your installed
|
||||
binary libraries. That is, package using
|
||||
|
||||
mach artifact install && mach package
|
||||
|
||||
to download, cache, and install binary artifacts from Mozilla automation,
|
||||
replacing whatever may be in your object directory. Use |mach artifact last|
|
||||
to see what binary artifacts were last used.
|
||||
|
||||
Never build libxul again!
|
||||
'''
|
||||
pass
|
||||
|
||||
def _make_artifacts(self, tree=None, job=None):
|
||||
self.log_manager.terminal_handler.setLevel(logging.INFO)
|
||||
|
||||
self._activate_virtualenv()
|
||||
self.virtualenv_manager.install_pip_package('pylru==1.0.9')
|
||||
self.virtualenv_manager.install_pip_package('taskcluster==0.0.16')
|
||||
|
||||
state_dir = self._mach_context.state_dir
|
||||
cache_dir = os.path.join(state_dir, 'package-frontend')
|
||||
|
||||
import which
|
||||
hg = which.which('hg')
|
||||
|
||||
# Absolutely must come after the virtualenv is populated!
|
||||
from mozbuild.artifacts import Artifacts
|
||||
artifacts = Artifacts(tree, job, log=self.log, cache_dir=cache_dir, hg=hg)
|
||||
return artifacts
|
||||
|
||||
@SubCommand('artifact', 'install',
|
||||
'Install a good pre-built artifact.')
|
||||
@CommandArgument('--tree', metavar='TREE', type=str,
|
||||
help='Firefox tree.',
|
||||
default='fx-team') # TODO: switch to central as this stabilizes.
|
||||
@CommandArgument('--job', metavar='JOB', choices=['android-api-11'],
|
||||
help='Build job.',
|
||||
default='android-api-11') # TODO: fish job from build configuration.
|
||||
@CommandArgument('source', metavar='SRC', nargs='?', type=str,
|
||||
help='Where to fetch and install artifacts from. Can be omitted, in '
|
||||
'which case the current hg repository is inspected; an hg revision; '
|
||||
'a remote URL; or a local file.',
|
||||
default=None)
|
||||
def artifact_install(self, source=None, tree=None, job=None):
|
||||
artifacts = self._make_artifacts(tree=tree, job=job)
|
||||
return artifacts.install_from(source, self.distdir)
|
||||
|
||||
@SubCommand('artifact', 'last',
|
||||
'Print the last pre-built artifact installed.')
|
||||
@CommandArgument('--tree', metavar='TREE', type=str,
|
||||
help='Firefox tree.',
|
||||
default='fx-team')
|
||||
@CommandArgument('--job', metavar='JOB', type=str,
|
||||
help='Build job.',
|
||||
default='android-api-11')
|
||||
def artifact_print_last(self, tree=None, job=None):
|
||||
artifacts = self._make_artifacts(tree=tree, job=job)
|
||||
artifacts.print_last()
|
||||
return 0
|
||||
|
||||
@SubCommand('artifact', 'print-cache',
|
||||
'Print local artifact cache for debugging.')
|
||||
@CommandArgument('--tree', metavar='TREE', type=str,
|
||||
help='Firefox tree.',
|
||||
default='fx-team')
|
||||
@CommandArgument('--job', metavar='JOB', type=str,
|
||||
help='Build job.',
|
||||
default='android-api-11')
|
||||
def artifact_print_cache(self, tree=None, job=None):
|
||||
artifacts = self._make_artifacts(tree=tree, job=job)
|
||||
artifacts.print_cache()
|
||||
return 0
|
||||
|
||||
@SubCommand('artifact', 'clear-cache',
|
||||
'Delete local artifacts and reset local artifact cache.')
|
||||
@CommandArgument('--tree', metavar='TREE', type=str,
|
||||
help='Firefox tree.',
|
||||
default='fx-team')
|
||||
@CommandArgument('--job', metavar='JOB', type=str,
|
||||
help='Build job.',
|
||||
default='android-api-11')
|
||||
def artifact_clear_cache(self, tree=None, job=None):
|
||||
artifacts = self._make_artifacts(tree=tree, job=job)
|
||||
artifacts.clear_cache()
|
||||
return 0
|
||||
|
398
python/mozbuild/mozbuild/artifacts.py
Normal file
398
python/mozbuild/mozbuild/artifacts.py
Normal file
@ -0,0 +1,398 @@
|
||||
# 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/.
|
||||
|
||||
'''
|
||||
Fetch build artifacts from a Firefox tree.
|
||||
|
||||
This provides an (at-the-moment special purpose) interface to download Android
|
||||
artifacts from Mozilla's Task Cluster.
|
||||
|
||||
This module performs the following steps:
|
||||
|
||||
* find a candidate hg parent revision using the local pushlog. The local
|
||||
pushlog is maintained by mozext locally and updated on every pull.
|
||||
|
||||
* map the candidate parent to candidate Task Cluster tasks and artifact
|
||||
locations. Pushlog entries might not correspond to tasks (yet), and those
|
||||
tasks might not produce the desired class of artifacts.
|
||||
|
||||
* fetch fresh Task Cluster artifacts and purge old artifacts, using a simple
|
||||
Least Recently Used cache.
|
||||
|
||||
The bulk of the complexity is in managing and persisting several caches. If
|
||||
we found a Python LRU cache that pickled cleanly, we could remove a lot of
|
||||
this code! Sadly, I found no such candidate implementations, so we pickle
|
||||
pylru caches manually.
|
||||
|
||||
None of the instances (or the underlying caches) are safe for concurrent use.
|
||||
A future need, perhaps.
|
||||
|
||||
This module requires certain modules be importable from the ambient Python
|
||||
environment. |mach artifact| ensures these modules are available, but other
|
||||
consumers will need to arrange this themselves.
|
||||
'''
|
||||
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import operator
|
||||
import os
|
||||
import pickle
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import urlparse
|
||||
import zipfile
|
||||
|
||||
import pylru
|
||||
import taskcluster
|
||||
|
||||
from mozbuild.util import (
|
||||
ensureParentDir,
|
||||
FileAvoidWrite,
|
||||
)
|
||||
|
||||
MAX_CACHED_PARENTS = 100 # Number of parent changesets to cache candidate pushheads for.
|
||||
NUM_PUSHHEADS_TO_QUERY_PER_PARENT = 50 # Number of candidate pushheads to cache per parent changeset.
|
||||
|
||||
MAX_CACHED_TASKS = 400 # Number of pushheads to cache Task Cluster task data for.
|
||||
|
||||
# Number of downloaded artifacts to cache. Each artifact can be very large,
|
||||
# so don't make this to large! TODO: make this a size (like 500 megs) rather than an artifact count.
|
||||
MAX_CACHED_ARTIFACTS = 6
|
||||
|
||||
# TODO: handle multiple artifacts with the same filename.
|
||||
# TODO: handle installing binaries from different types of artifacts (.tar.bz2, .dmg, etc).
|
||||
# Keep the keys of this map in sync with the |mach artifact| --job options.
|
||||
JOB_DETAILS = {
|
||||
# 'android-api-9': {'re': re.compile('public/build/fennec-(.*)\.android-arm\.apk')},
|
||||
'android-api-11': {'re': re.compile('public/build/geckolibs-(.*)\.aar')},
|
||||
# 'linux': {'re': re.compile('public/build/firefox-(.*)\.linux-i686\.tar\.bz2')},
|
||||
# 'linux64': {'re': re.compile('public/build/firefox-(.*)\.linux-x86_64\.tar\.bz2')},
|
||||
# 'macosx64': {'re': re.compile('public/build/firefox-(.*)\.mac\.dmg')},
|
||||
}
|
||||
|
||||
|
||||
def cachedmethod(cachefunc):
|
||||
'''Decorator to wrap a class or instance method with a memoizing callable that
|
||||
saves results in a (possibly shared) cache.
|
||||
'''
|
||||
def decorator(method):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
mapping = cachefunc(self)
|
||||
if mapping is None:
|
||||
return method(self, *args, **kwargs)
|
||||
key = (method.__name__, args, tuple(sorted(kwargs.items())))
|
||||
try:
|
||||
value = mapping[key]
|
||||
return value
|
||||
except KeyError:
|
||||
pass
|
||||
result = method(self, *args, **kwargs)
|
||||
mapping[key] = result
|
||||
return result
|
||||
return functools.update_wrapper(wrapper, method)
|
||||
return decorator
|
||||
|
||||
|
||||
class CacheManager(object):
|
||||
'''Maintain an LRU cache. Provide simple persistence, including support for
|
||||
loading and saving the state using a "with" block. Allow clearing the cache
|
||||
and printing the cache for debugging.
|
||||
|
||||
Provide simple logging.
|
||||
'''
|
||||
|
||||
def __init__(self, cache_dir, cache_name, cache_size, cache_callback=None, log=None):
|
||||
self._cache = pylru.lrucache(cache_size, callback=cache_callback)
|
||||
self._cache_filename = os.path.join(cache_dir, cache_name + '-cache.pickle')
|
||||
self._log = log
|
||||
|
||||
def log(self, *args, **kwargs):
|
||||
if self._log:
|
||||
self._log(*args, **kwargs)
|
||||
|
||||
def load_cache(self):
|
||||
try:
|
||||
items = pickle.load(open(self._cache_filename, 'rb'))
|
||||
for key, value in items:
|
||||
self._cache[key] = value
|
||||
except Exception as e:
|
||||
# Corrupt cache, perhaps? Sadly, pickle raises many different
|
||||
# exceptions, so it's not worth trying to be fine grained here.
|
||||
# We ignore any exception, so the cache is effectively dropped.
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{'filename': self._cache_filename, 'exception': repr(e)},
|
||||
'Ignoring exception unpickling cache file {filename}: {exception}')
|
||||
pass
|
||||
|
||||
def dump_cache(self):
|
||||
ensureParentDir(self._cache_filename)
|
||||
pickle.dump(list(reversed(list(self._cache.items()))), open(self._cache_filename, 'wb'), -1)
|
||||
|
||||
def clear_cache(self):
|
||||
with self:
|
||||
self._cache.clear()
|
||||
|
||||
def print_cache(self):
|
||||
with self:
|
||||
for item in self._cache.items():
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{'item': item},
|
||||
'{item}')
|
||||
|
||||
def print_last_item(self, args, sorted_kwargs, result):
|
||||
# By default, show nothing.
|
||||
pass
|
||||
|
||||
def print_last(self):
|
||||
# We use the persisted LRU caches to our advantage. The first item is
|
||||
# most recent.
|
||||
with self:
|
||||
item = next(self._cache.items(), None)
|
||||
if item is not None:
|
||||
(name, args, sorted_kwargs), result = item
|
||||
self.print_last_item(args, sorted_kwargs, result)
|
||||
else:
|
||||
self.log(logging.WARN, 'artifact',
|
||||
{},
|
||||
'No last cached item found.')
|
||||
|
||||
def __enter__(self):
|
||||
self.load_cache()
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.dump_cache()
|
||||
|
||||
|
||||
class PushHeadCache(CacheManager):
|
||||
'''Map parent hg revisions to candidate pushheads.'''
|
||||
|
||||
def __init__(self, hg, cache_dir, log=None):
|
||||
# It's not unusual to pull hundreds of changesets at once, and perhaps
|
||||
# |hg up| back and forth a few times.
|
||||
CacheManager.__init__(self, cache_dir, 'pushheads', MAX_CACHED_PARENTS, log=log)
|
||||
self._hg = hg
|
||||
|
||||
@cachedmethod(operator.attrgetter('_cache'))
|
||||
def pushheads(self, tree, parent):
|
||||
pushheads = subprocess.check_output([self._hg, 'log',
|
||||
'--template', '{node}\n',
|
||||
'-r', 'last(pushhead("{tree}") & ::"{parent}", {num})'.format(
|
||||
tree=tree, parent=parent, num=NUM_PUSHHEADS_TO_QUERY_PER_PARENT)])
|
||||
pushheads = pushheads.strip().split('\n')
|
||||
return pushheads
|
||||
|
||||
|
||||
class TaskCache(CacheManager):
|
||||
'''Map candidate pushheads to Task Cluster task IDs and artifact URLs.'''
|
||||
|
||||
def __init__(self, cache_dir, log=None):
|
||||
CacheManager.__init__(self, cache_dir, 'artifact_url', MAX_CACHED_TASKS, log=log)
|
||||
self._index = taskcluster.Index()
|
||||
self._queue = taskcluster.Queue()
|
||||
|
||||
@cachedmethod(operator.attrgetter('_cache'))
|
||||
def artifact_url(self, tree, job, rev):
|
||||
try:
|
||||
artifact_re = JOB_DETAILS[job]['re']
|
||||
except KeyError:
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{'job': job},
|
||||
'Unknown job {job}')
|
||||
raise KeyError("Unknown job")
|
||||
|
||||
# Bug 1175655: it appears that the Task Cluster index only takes
|
||||
# 12-char hex hashes.
|
||||
key = '{rev}.{tree}.{job}'.format(rev=rev[:12], tree=tree, job=job)
|
||||
try:
|
||||
namespace = 'buildbot.revisions.{key}'.format(key=key)
|
||||
task = self._index.findTask(namespace)
|
||||
except Exception:
|
||||
# Not all revisions correspond to pushes that produce the job we
|
||||
# care about; and even those that do may not have completed yet.
|
||||
raise ValueError('Task for {key} does not exist (yet)!'.format(key=key))
|
||||
taskId = task['taskId']
|
||||
|
||||
# TODO: Make this not Android-only by matching a regular expression.
|
||||
artifacts = self._queue.listLatestArtifacts(taskId)['artifacts']
|
||||
|
||||
def names():
|
||||
for artifact in artifacts:
|
||||
name = artifact['name']
|
||||
if artifact_re.match(name):
|
||||
yield name
|
||||
|
||||
# TODO: Handle multiple artifacts, taking the latest one.
|
||||
for name in names():
|
||||
# We can easily extract the task ID and the build ID from the URL.
|
||||
url = self._queue.buildUrl('getLatestArtifact', taskId, name)
|
||||
return url
|
||||
raise ValueError('Task for {key} existed, but no artifacts found!'.format(key=key))
|
||||
|
||||
def print_last_item(self, args, sorted_kwargs, result):
|
||||
tree, job, rev = args
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{'rev': rev},
|
||||
'Last installed binaries from hg parent revision {rev}')
|
||||
|
||||
|
||||
class ArtifactCache(CacheManager):
|
||||
'''Fetch Task Cluster artifact URLs and purge least recently used artifacts from disk.'''
|
||||
|
||||
def __init__(self, cache_dir, log=None):
|
||||
# TODO: instead of storing N artifact packages, store M megabytes.
|
||||
CacheManager.__init__(self, cache_dir, 'fetch', MAX_CACHED_ARTIFACTS, cache_callback=self.delete_file, log=log)
|
||||
self._cache_dir = cache_dir
|
||||
|
||||
def delete_file(self, key, value):
|
||||
try:
|
||||
os.remove(value)
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{'filename': value},
|
||||
'Purged artifact {filename}')
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
@cachedmethod(operator.attrgetter('_cache'))
|
||||
def fetch(self, url, force=False):
|
||||
args = ['wget', url]
|
||||
|
||||
if not force:
|
||||
args[1:1] = ['--timestamping']
|
||||
|
||||
proc = subprocess.Popen(args, cwd=self._cache_dir)
|
||||
status = None
|
||||
# Leave it to the subprocess to handle Ctrl+C. If it terminates as
|
||||
# a result of Ctrl+C, proc.wait() will return a status code, and,
|
||||
# we get out of the loop. If it doesn't, like e.g. gdb, we continue
|
||||
# waiting.
|
||||
while status is None:
|
||||
try:
|
||||
status = proc.wait()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
if status != 0:
|
||||
raise Exception('Process executed with non-0 exit code: %s' % args)
|
||||
|
||||
return os.path.abspath(os.path.join(self._cache_dir, os.path.basename(url)))
|
||||
|
||||
def print_last_item(self, args, sorted_kwargs, result):
|
||||
url, = args
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{'url': url},
|
||||
'Last installed binaries from url {url}')
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{'filename': result},
|
||||
'Last installed binaries from local file {filename}')
|
||||
|
||||
|
||||
class Artifacts(object):
|
||||
'''Maintain state to efficiently fetch build artifacts from a Firefox tree.'''
|
||||
|
||||
def __init__(self, tree, job, log=None, cache_dir='.', hg='hg'):
|
||||
self._tree = tree
|
||||
self._job = job
|
||||
self._log = log
|
||||
self._hg = hg
|
||||
self._cache_dir = cache_dir
|
||||
|
||||
self._pushhead_cache = PushHeadCache(self._hg, self._cache_dir, log=self._log)
|
||||
self._task_cache = TaskCache(self._cache_dir, log=self._log)
|
||||
self._artifact_cache = ArtifactCache(self._cache_dir, log=self._log)
|
||||
|
||||
def log(self, *args, **kwargs):
|
||||
if self._log:
|
||||
self._log(*args, **kwargs)
|
||||
|
||||
def install_from_file(self, filename, distdir):
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{'filename': filename},
|
||||
'Installing from {filename}')
|
||||
|
||||
# Copy all .so files to dist/bin, avoiding modification where possible.
|
||||
ensureParentDir(os.path.join(distdir, 'bin', '.dummy'))
|
||||
|
||||
with zipfile.ZipFile(filename) as zf:
|
||||
for info in zf.infolist():
|
||||
if not info.filename.endswith('.so'):
|
||||
continue
|
||||
n = os.path.join(distdir, 'bin', os.path.basename(info.filename))
|
||||
fh = FileAvoidWrite(n, mode='r')
|
||||
shutil.copyfileobj(zf.open(info), fh)
|
||||
fh.write(zf.open(info).read())
|
||||
file_existed, file_updated = fh.close()
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{'updating': 'Updating' if file_updated else 'Not updating', 'filename': n},
|
||||
'{updating} {filename}')
|
||||
return 0
|
||||
|
||||
def install_from_url(self, url, distdir):
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{'url': url},
|
||||
'Installing from {url}')
|
||||
with self._artifact_cache as artifact_cache: # The with block handles persistence.
|
||||
filename = artifact_cache.fetch(url)
|
||||
return self.install_from_file(filename, distdir)
|
||||
|
||||
def install_from_hg(self, revset, distdir):
|
||||
if not revset:
|
||||
revset = '.'
|
||||
if len(revset) != 40:
|
||||
revset = subprocess.check_output([self._hg, 'log', '--template', '{node}\n', '-r', revset]).strip()
|
||||
if len(revset.split('\n')) != 1:
|
||||
raise ValueError('hg revision specification must resolve to exactly one commit')
|
||||
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{'revset': revset},
|
||||
'Installing from {revset}')
|
||||
|
||||
url = None
|
||||
with self._task_cache as task_cache, self._pushhead_cache as pushhead_cache:
|
||||
# with blocks handle handle persistence.
|
||||
for pushhead in pushhead_cache.pushheads(self._tree, revset):
|
||||
try:
|
||||
url = task_cache.artifact_url(self._tree, self._job, pushhead)
|
||||
break
|
||||
except ValueError:
|
||||
pass
|
||||
if url:
|
||||
return self.install_from_url(url, distdir)
|
||||
return 1
|
||||
|
||||
def install_from(self, source, distdir):
|
||||
if source and os.path.isfile(source):
|
||||
return self.install_from_file(source, distdir)
|
||||
elif source and urlparse.urlparse(source).scheme:
|
||||
return self.install_from_url(source, distdir)
|
||||
else:
|
||||
return self.install_from_hg(source, distdir)
|
||||
|
||||
def print_last(self):
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{},
|
||||
'Printing last used artifact details.')
|
||||
self._pushhead_cache.print_last()
|
||||
self._task_cache.print_last()
|
||||
self._artifact_cache.print_last()
|
||||
|
||||
def clear_cache(self):
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{},
|
||||
'Deleting cached artifacts and caches.')
|
||||
self._pushhead_cache.clear_cache()
|
||||
self._task_cache.clear_cache()
|
||||
self._artifact_cache.clear_cache()
|
||||
|
||||
def print_cache(self):
|
||||
self.log(logging.INFO, 'artifact',
|
||||
{},
|
||||
'Printing cached artifacts and caches.')
|
||||
self._pushhead_cache.print_cache()
|
||||
self._task_cache.print_cache()
|
||||
self._artifact_cache.print_cache()
|
@ -764,6 +764,22 @@ class MachCommandConditions(object):
|
||||
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."""
|
||||
|
@ -119,11 +119,12 @@ class FileAvoidWrite(StringIO):
|
||||
enabled by default because it a) doesn't make sense for binary files b)
|
||||
could add unwanted overhead to calls.
|
||||
"""
|
||||
def __init__(self, filename, capture_diff=False):
|
||||
def __init__(self, filename, capture_diff=False, mode='rU'):
|
||||
StringIO.__init__(self)
|
||||
self.name = filename
|
||||
self._capture_diff = capture_diff
|
||||
self.diff = None
|
||||
self.mode = mode
|
||||
|
||||
def close(self):
|
||||
"""Stop accepting writes, compare file contents, and rewrite if needed.
|
||||
@ -142,7 +143,7 @@ class FileAvoidWrite(StringIO):
|
||||
old_content = None
|
||||
|
||||
try:
|
||||
existing = open(self.name, 'rU')
|
||||
existing = open(self.name, self.mode)
|
||||
existed = True
|
||||
except IOError:
|
||||
pass
|
||||
|
@ -413,6 +413,11 @@ ifdef MOZ_ENABLE_SZIP
|
||||
SZIP_LIBRARIES := $(ASSET_SO_LIBRARIES)
|
||||
endif
|
||||
|
||||
ifndef COMPILE_ENVIRONMENT
|
||||
# Any Fennec binary libraries we download are already szipped.
|
||||
ALREADY_SZIPPED=1
|
||||
endif
|
||||
|
||||
# Fennec's OMNIJAR_NAME can include a directory; for example, it might
|
||||
# be "assets/omni.ja". This path specifies where the omni.ja file
|
||||
# lives in the APK, but should not root the resources it contains
|
||||
@ -459,7 +464,7 @@ INNER_MAKE_PACKAGE = \
|
||||
( cd $(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH) && \
|
||||
unzip -o $(_ABS_DIST)/gecko.ap_ && \
|
||||
rm $(_ABS_DIST)/gecko.ap_ && \
|
||||
$(ZIP) $(if $(MOZ_ENABLE_SZIP),-0 )$(_ABS_DIST)/gecko.ap_ $(ASSET_SO_LIBRARIES) && \
|
||||
$(ZIP) $(if $(ALREADY_SZIPPED),-0 ,$(if $(MOZ_ENABLE_SZIP),-0 ))$(_ABS_DIST)/gecko.ap_ $(ASSET_SO_LIBRARIES) && \
|
||||
$(ZIP) -r9D $(_ABS_DIST)/gecko.ap_ $(DIST_FILES) -x $(NON_DIST_FILES) $(SZIP_LIBRARIES) && \
|
||||
$(if $(filter-out ./,$(OMNIJAR_DIR)), \
|
||||
mkdir -p $(OMNIJAR_DIR) && mv $(OMNIJAR_NAME) $(OMNIJAR_DIR) && ) \
|
||||
|
Loading…
Reference in New Issue
Block a user