зеркало из https://github.com/mozilla/gecko-dev.git
223 строки
6.6 KiB
Python
223 строки
6.6 KiB
Python
#!/usr/bin/env 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/.
|
|
|
|
"""
|
|
Usage: extract_and_run_command.py [-j N] [command to run] -- [files and/or directories]
|
|
-j is the number of workers to start, defaulting to 1.
|
|
[command to run] must be a command that can accept one or many files
|
|
to process as arguments.
|
|
|
|
WARNING: This script does NOT respond to SIGINT. You must use SIGQUIT or SIGKILL to
|
|
terminate it early.
|
|
"""
|
|
|
|
### The canonical location for this file is
|
|
### https://hg.mozilla.org/build/tools/file/default/stage/extract_and_run_command.py
|
|
###
|
|
### Please update the copy in puppet to deploy new changes to
|
|
### stage.mozilla.org, see
|
|
# https://wiki.mozilla.org/ReleaseEngineering/How_To/Modify_scripts_on_stage
|
|
|
|
from __future__ import absolute_import
|
|
import logging
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
from os import path
|
|
from threading import Thread
|
|
|
|
try:
|
|
from Queue import Queue
|
|
except ImportError:
|
|
from queue import Queue
|
|
|
|
logging.basicConfig(stream=sys.stdout, level=logging.INFO, format="%(message)s")
|
|
log = logging.getLogger(__name__)
|
|
|
|
try:
|
|
# the future - https://github.com/mozilla/build-mar via a venv
|
|
from mardor.marfile import BZ2MarFile
|
|
except:
|
|
# the past - http://hg.mozilla.org/build/tools/file/default/buildfarm/utils/mar.py
|
|
sys.path.append(
|
|
path.join(path.dirname(path.realpath(__file__)), "../buildfarm/utils")
|
|
)
|
|
from mar import BZ2MarFile
|
|
|
|
SEVENZIP = "7za"
|
|
|
|
|
|
def extractMar(filename, tempdir):
|
|
m = BZ2MarFile(filename)
|
|
m.extractall(path=tempdir)
|
|
|
|
|
|
def extractExe(filename, tempdir):
|
|
try:
|
|
# We don't actually care about output, put we redirect to a tempfile
|
|
# to avoid deadlocking in wait() when stdout=PIPE
|
|
fd = tempfile.TemporaryFile()
|
|
proc = subprocess.Popen(
|
|
[SEVENZIP, "x", "-o%s" % tempdir, filename],
|
|
stdout=fd,
|
|
stderr=subprocess.STDOUT,
|
|
)
|
|
proc.wait()
|
|
except subprocess.CalledProcessError:
|
|
# Not all EXEs are 7-zip files, so we have to ignore extraction errors
|
|
pass
|
|
|
|
|
|
# The keys here are matched against the last 3 characters of filenames.
|
|
# The values are callables that accept two string arguments.
|
|
EXTRACTORS = {
|
|
".mar": extractMar,
|
|
".exe": extractExe,
|
|
}
|
|
|
|
|
|
def find_files(d):
|
|
"""yields all of the files in `d'"""
|
|
for root, _, files in os.walk(d):
|
|
for f in files:
|
|
yield path.abspath(path.join(root, f))
|
|
|
|
|
|
def rchmod(d, mode=0o755):
|
|
"""chmods everything in `d' to `mode', including `d' itself"""
|
|
os.chmod(d, mode)
|
|
for root, dirs, files in os.walk(d):
|
|
for item in dirs:
|
|
os.chmod(path.join(root, item), mode)
|
|
for item in files:
|
|
os.chmod(path.join(root, item), mode)
|
|
|
|
|
|
def maybe_extract(filename):
|
|
"""If an extractor is found for `filename', extracts it to a temporary
|
|
directory and chmods it. The consumer is responsible for removing
|
|
the extracted files, if desired."""
|
|
ext = path.splitext(filename)[1]
|
|
if ext not in EXTRACTORS:
|
|
return None
|
|
# Append the full filepath to the tempdir
|
|
tempdir_root = tempfile.mkdtemp()
|
|
tempdir = path.join(tempdir_root, filename.lstrip("/"))
|
|
os.makedirs(tempdir)
|
|
EXTRACTORS[ext](filename, tempdir)
|
|
rchmod(tempdir_root)
|
|
return tempdir_root
|
|
|
|
|
|
def process(item, command):
|
|
def format_time(t):
|
|
return time.strftime("%H:%M:%S", time.localtime(t))
|
|
|
|
# Buffer output to avoid interleaving of multiple workers'
|
|
logs = []
|
|
args = [item]
|
|
proc = None
|
|
start = time.time()
|
|
logs.append("START %s: %s" % (format_time(start), item))
|
|
# If the file was extracted, we need to process all of its files, too.
|
|
tempdir = maybe_extract(item)
|
|
if tempdir:
|
|
for f in find_files(tempdir):
|
|
args.append(f)
|
|
|
|
try:
|
|
fd = tempfile.TemporaryFile()
|
|
proc = subprocess.Popen(command + args, stdout=fd)
|
|
proc.wait()
|
|
if proc.returncode != 0:
|
|
raise Exception("returned %s" % proc.returncode)
|
|
finally:
|
|
if tempdir:
|
|
shutil.rmtree(tempdir)
|
|
fd.seek(0)
|
|
# rstrip() here to avoid an unnecessary newline, if it exists.
|
|
logs.append(fd.read().rstrip())
|
|
end = time.time()
|
|
elapsed = end - start
|
|
logs.append(
|
|
"END %s (%d seconds elapsed): %s\n" % (format_time(end), elapsed, item)
|
|
)
|
|
# Now that we've got all of our output, print it. It's important that
|
|
# the logging module is used for this, because "print" is not
|
|
# thread-safe.
|
|
log.info("\n".join(logs))
|
|
|
|
|
|
def worker(command, errors):
|
|
item = q.get()
|
|
while item != None:
|
|
try:
|
|
process(item, command)
|
|
except:
|
|
errors.put(item)
|
|
item = q.get()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# getopt is used in favour of optparse to enable "--" as a separator
|
|
# between the command and list of files. optparse doesn't allow that.
|
|
from getopt import getopt
|
|
|
|
options, args = getopt(sys.argv[1:], "j:h", ["help"])
|
|
|
|
concurrency = 1
|
|
for o, a in options:
|
|
if o == "-j":
|
|
concurrency = int(a)
|
|
elif o in ("-h", "--help"):
|
|
log.info(__doc__)
|
|
sys.exit(0)
|
|
|
|
if len(args) < 3 or "--" not in args:
|
|
log.error(__doc__)
|
|
sys.exit(1)
|
|
|
|
command = []
|
|
while args[0] != "--":
|
|
command.append(args.pop(0))
|
|
args.pop(0)
|
|
|
|
q = Queue()
|
|
errors = Queue()
|
|
threads = []
|
|
for i in range(concurrency):
|
|
t = Thread(target=worker, args=(command, errors))
|
|
t.start()
|
|
threads.append(t)
|
|
|
|
# find_files is a generator, so work will begin prior to it finding
|
|
# all of the files
|
|
for arg in args:
|
|
if path.isfile(arg):
|
|
q.put(arg)
|
|
else:
|
|
for f in find_files(arg):
|
|
q.put(f)
|
|
# Because the workers are started before we start populating the q
|
|
# they can't use .empty() to determine whether or not their done.
|
|
# We also can't use q.join() or j.task_done(), because we need to
|
|
# support Python 2.4. We know that find_files won't yield None,
|
|
# so we can detect doneness by having workers die when they get None
|
|
# as an item.
|
|
for i in range(concurrency):
|
|
q.put(None)
|
|
|
|
for t in threads:
|
|
t.join()
|
|
|
|
if not errors.empty():
|
|
log.error("Command failed for the following files:")
|
|
while not errors.empty():
|
|
log.error(" %s" % errors.get())
|
|
sys.exit(1)
|