mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 07:42:04 +00:00
41706c08fe
subconfigure.py shortcuts js/src/configure to avoid a msys transition breaking the environment. But js/src/configure passes an extra --enable-project=js that subconfigure.py currently doesn't pass. And because in some cases, MOZ_BUILD_APP is set in the environment (notably, in OSX universal builds, through client.mk), we need to unset it before calling the subconfigure.
439 lines
15 KiB
Python
439 lines
15 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/.
|
|
|
|
# This script is used to capture the content of config.status-generated
|
|
# files and subsequently restore their timestamp if they haven't changed.
|
|
|
|
import argparse
|
|
import errno
|
|
import itertools
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import pickle
|
|
|
|
import mozpack.path as mozpath
|
|
|
|
|
|
class Pool(object):
|
|
def __new__(cls, size):
|
|
try:
|
|
import multiprocessing
|
|
size = min(size, multiprocessing.cpu_count())
|
|
return multiprocessing.Pool(size)
|
|
except:
|
|
return super(Pool, cls).__new__(cls)
|
|
|
|
def imap_unordered(self, fn, iterable):
|
|
return itertools.imap(fn, iterable)
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
def join(self):
|
|
pass
|
|
|
|
|
|
class File(object):
|
|
def __init__(self, path):
|
|
self._path = path
|
|
self._content = open(path, 'rb').read()
|
|
stat = os.stat(path)
|
|
self._times = (stat.st_atime, stat.st_mtime)
|
|
|
|
@property
|
|
def path(self):
|
|
return self._path
|
|
|
|
@property
|
|
def mtime(self):
|
|
return self._times[1]
|
|
|
|
@property
|
|
def modified(self):
|
|
'''Returns whether the file was modified since the instance was
|
|
created. Result is memoized.'''
|
|
if hasattr(self, '_modified'):
|
|
return self._modified
|
|
|
|
modified = True
|
|
if os.path.exists(self._path):
|
|
if open(self._path, 'rb').read() == self._content:
|
|
modified = False
|
|
self._modified = modified
|
|
return modified
|
|
|
|
def update_time(self):
|
|
'''If the file hasn't changed since the instance was created,
|
|
restore its old modification time.'''
|
|
if not self.modified:
|
|
os.utime(self._path, self._times)
|
|
|
|
|
|
# As defined in the various sub-configures in the tree
|
|
PRECIOUS_VARS = set([
|
|
'build_alias',
|
|
'host_alias',
|
|
'target_alias',
|
|
'CC',
|
|
'CFLAGS',
|
|
'LDFLAGS',
|
|
'LIBS',
|
|
'CPPFLAGS',
|
|
'CPP',
|
|
'CCC',
|
|
'CXXFLAGS',
|
|
'CXX',
|
|
'CCASFLAGS',
|
|
'CCAS',
|
|
])
|
|
|
|
|
|
CONFIGURE_DATA = 'configure.pkl'
|
|
|
|
|
|
# Autoconf, in some of the sub-configures used in the tree, likes to error
|
|
# out when "precious" variables change in value. The solution it gives to
|
|
# straighten things is to either run make distclean or remove config.cache.
|
|
# There's no reason not to do the latter automatically instead of failing,
|
|
# doing the cleanup (which, on buildbots means a full clobber), and
|
|
# restarting from scratch.
|
|
def maybe_clear_cache(data):
|
|
env = dict(data['env'])
|
|
for kind in ('target', 'host', 'build'):
|
|
arg = data[kind]
|
|
if arg is not None:
|
|
env['%s_alias' % kind] = arg
|
|
# configure can take variables assignments in its arguments, and that
|
|
# overrides whatever is in the environment.
|
|
for arg in data['args']:
|
|
if arg[:1] != '-' and '=' in arg:
|
|
key, value = arg.split('=', 1)
|
|
env[key] = value
|
|
|
|
comment = re.compile(r'^\s+#')
|
|
cache = {}
|
|
with open(data['cache-file']) as f:
|
|
for line in f:
|
|
if not comment.match(line) and '=' in line:
|
|
key, value = line.rstrip(os.linesep).split('=', 1)
|
|
# If the value is quoted, unquote it
|
|
if value[:1] == "'":
|
|
value = value[1:-1].replace("'\\''", "'")
|
|
cache[key] = value
|
|
for precious in PRECIOUS_VARS:
|
|
# If there is no entry at all for that precious variable, then
|
|
# its value is not precious for that particular configure.
|
|
if 'ac_cv_env_%s_set' % precious not in cache:
|
|
continue
|
|
is_set = cache.get('ac_cv_env_%s_set' % precious) == 'set'
|
|
value = cache.get('ac_cv_env_%s_value' % precious) if is_set else None
|
|
if value != env.get(precious):
|
|
print 'Removing %s because of %s value change from:' \
|
|
% (data['cache-file'], precious)
|
|
print ' %s' % (value if value is not None else 'undefined')
|
|
print 'to:'
|
|
print ' %s' % env.get(precious, 'undefined')
|
|
os.remove(data['cache-file'])
|
|
return True
|
|
return False
|
|
|
|
|
|
def split_template(s):
|
|
"""Given a "file:template" string, returns "file", "template". If the string
|
|
is of the form "file" (without a template), returns "file", "file.in"."""
|
|
if ':' in s:
|
|
return s.split(':', 1)
|
|
return s, '%s.in' % s
|
|
|
|
|
|
def get_config_files(data):
|
|
config_status = mozpath.join(data['objdir'], 'config.status')
|
|
if not os.path.exists(config_status):
|
|
return [], []
|
|
|
|
configure = mozpath.join(data['srcdir'], 'configure')
|
|
config_files = []
|
|
command_files = []
|
|
|
|
# Scan the config.status output for information about configuration files
|
|
# it generates.
|
|
config_status_output = subprocess.check_output(
|
|
[data['shell'], '-c', '%s --help' % config_status],
|
|
stderr=subprocess.STDOUT).splitlines()
|
|
state = None
|
|
for line in config_status_output:
|
|
if line.startswith('Configuration') and line.endswith(':'):
|
|
if line.endswith('commands:'):
|
|
state = 'commands'
|
|
else:
|
|
state = 'config'
|
|
elif not line.strip():
|
|
state = None
|
|
elif state:
|
|
for f, t in (split_template(couple) for couple in line.split()):
|
|
f = mozpath.join(data['objdir'], f)
|
|
t = mozpath.join(data['srcdir'], t)
|
|
if state == 'commands':
|
|
command_files.append(f)
|
|
else:
|
|
config_files.append((f, t))
|
|
|
|
return config_files, command_files
|
|
|
|
|
|
def prepare(srcdir, objdir, shell, args):
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--target', type=str)
|
|
parser.add_argument('--host', type=str)
|
|
parser.add_argument('--build', type=str)
|
|
parser.add_argument('--cache-file', type=str)
|
|
# The --srcdir argument is simply ignored. It's a useless autoconf feature
|
|
# that we don't support well anyways. This makes it stripped from `others`
|
|
# and allows to skip setting it when calling the subconfigure (configure
|
|
# will take it from the configure path anyways).
|
|
parser.add_argument('--srcdir', type=str)
|
|
|
|
data_file = os.path.join(objdir, CONFIGURE_DATA)
|
|
previous_args = None
|
|
if os.path.exists(data_file):
|
|
with open(data_file, 'rb') as f:
|
|
data = pickle.load(f)
|
|
previous_args = data['args']
|
|
|
|
# Msys likes to break environment variables and command line arguments,
|
|
# so read those from stdin, as they are passed from the configure script
|
|
# when necessary (on windows).
|
|
# However, for some reason, $PATH is not handled like other environment
|
|
# variables, and msys remangles it even when giving it is already a msys
|
|
# $PATH. Fortunately, the mangling/demangling is just find for $PATH, so
|
|
# we can just take the value from the environment. Msys will convert it
|
|
# back properly when calling subconfigure.
|
|
input = sys.stdin.read()
|
|
if input:
|
|
data = {a: b for [a, b] in eval(input)}
|
|
environ = {a: b for a, b in data['env']}
|
|
environ['PATH'] = os.environ['PATH']
|
|
args = data['args']
|
|
else:
|
|
environ = os.environ
|
|
|
|
args, others = parser.parse_known_args(args)
|
|
|
|
data = {
|
|
'target': args.target,
|
|
'host': args.host,
|
|
'build': args.build,
|
|
'args': others,
|
|
'shell': shell,
|
|
'srcdir': srcdir,
|
|
'env': environ,
|
|
}
|
|
|
|
if args.cache_file:
|
|
data['cache-file'] = mozpath.normpath(mozpath.join(os.getcwd(),
|
|
args.cache_file))
|
|
else:
|
|
data['cache-file'] = mozpath.join(objdir, 'config.cache')
|
|
|
|
if previous_args is not None:
|
|
data['previous-args'] = previous_args
|
|
|
|
try:
|
|
os.makedirs(objdir)
|
|
except OSError as e:
|
|
if e.errno != errno.EEXIST:
|
|
raise
|
|
|
|
with open(data_file, 'wb') as f:
|
|
pickle.dump(data, f)
|
|
|
|
|
|
def prefix_lines(text, prefix):
|
|
return ''.join('%s> %s' % (prefix, line) for line in text.splitlines(True))
|
|
|
|
|
|
def run(objdir):
|
|
ret = 0
|
|
output = ''
|
|
|
|
with open(os.path.join(objdir, CONFIGURE_DATA), 'rb') as f:
|
|
data = pickle.load(f)
|
|
|
|
data['objdir'] = objdir
|
|
|
|
cache_file = data['cache-file']
|
|
cleared_cache = True
|
|
if os.path.exists(cache_file):
|
|
cleared_cache = maybe_clear_cache(data)
|
|
|
|
config_files, command_files = get_config_files(data)
|
|
contents = []
|
|
for f, t in config_files:
|
|
contents.append(File(f))
|
|
|
|
# AC_CONFIG_COMMANDS actually only registers tags, not file names
|
|
# but most commands are tagged with the file name they create.
|
|
# However, a few don't, or are tagged with a directory name (and their
|
|
# command is just to create that directory)
|
|
for f in command_files:
|
|
if os.path.isfile(f):
|
|
contents.append(File(f))
|
|
|
|
# Only run configure if one of the following is true:
|
|
# - config.status doesn't exist
|
|
# - config.status is older than configure
|
|
# - the configure arguments changed
|
|
# - the environment changed in a way that requires a cache clear.
|
|
configure = mozpath.join(data['srcdir'], 'configure')
|
|
config_status_path = mozpath.join(objdir, 'config.status')
|
|
skip_configure = True
|
|
if not os.path.exists(config_status_path):
|
|
skip_configure = False
|
|
config_status = None
|
|
else:
|
|
config_status = File(config_status_path)
|
|
if config_status.mtime < os.path.getmtime(configure) or \
|
|
data.get('previous-args', data['args']) != data['args'] or \
|
|
cleared_cache:
|
|
skip_configure = False
|
|
|
|
relobjdir = os.path.relpath(objdir, os.getcwd())
|
|
|
|
if not skip_configure:
|
|
if mozpath.normsep(relobjdir) == 'js/src':
|
|
# Because configure is a shell script calling a python script
|
|
# calling a shell script, on Windows, with msys screwing the
|
|
# environment, we lose the benefits from our own efforts in this
|
|
# script to get past the msys problems. So manually call the python
|
|
# script instead, so that we don't do a native->msys transition
|
|
# here. Then the python configure will still have the right
|
|
# environment when calling the shell configure.
|
|
command = [
|
|
sys.executable,
|
|
os.path.join(os.path.dirname(__file__), '..', 'configure.py'),
|
|
'--enable-project=js',
|
|
]
|
|
data['env']['OLD_CONFIGURE'] = os.path.join(
|
|
os.path.dirname(configure), 'old-configure')
|
|
else:
|
|
command = [data['shell'], configure]
|
|
for kind in ('target', 'build', 'host'):
|
|
if data.get(kind) is not None:
|
|
command += ['--%s=%s' % (kind, data[kind])]
|
|
command += data['args']
|
|
command += ['--cache-file=%s' % cache_file]
|
|
|
|
# Pass --no-create to configure so that it doesn't run config.status.
|
|
# We're going to run it ourselves.
|
|
command += ['--no-create']
|
|
|
|
print prefix_lines('configuring', relobjdir)
|
|
print prefix_lines('running %s' % ' '.join(command[:-1]), relobjdir)
|
|
sys.stdout.flush()
|
|
try:
|
|
output += subprocess.check_output(command,
|
|
stderr=subprocess.STDOUT, cwd=objdir, env=data['env'])
|
|
except subprocess.CalledProcessError as e:
|
|
return relobjdir, e.returncode, e.output
|
|
|
|
# Leave config.status with a new timestamp if configure is newer than
|
|
# its original mtime.
|
|
if config_status and os.path.getmtime(configure) <= config_status.mtime:
|
|
config_status.update_time()
|
|
|
|
# Only run config.status if one of the following is true:
|
|
# - config.status changed or did not exist
|
|
# - one of the templates for config files is newer than the corresponding
|
|
# config file.
|
|
skip_config_status = True
|
|
if not config_status or config_status.modified:
|
|
# If config.status doesn't exist after configure (because it's not
|
|
# an autoconf configure), skip it.
|
|
if os.path.exists(config_status_path):
|
|
skip_config_status = False
|
|
else:
|
|
# config.status changed or was created, so we need to update the
|
|
# list of config and command files.
|
|
config_files, command_files = get_config_files(data)
|
|
for f, t in config_files:
|
|
if not os.path.exists(t) or \
|
|
os.path.getmtime(f) < os.path.getmtime(t):
|
|
skip_config_status = False
|
|
|
|
if not skip_config_status:
|
|
if skip_configure:
|
|
print prefix_lines('running config.status', relobjdir)
|
|
sys.stdout.flush()
|
|
try:
|
|
output += subprocess.check_output([data['shell'], '-c',
|
|
'./config.status'], stderr=subprocess.STDOUT, cwd=objdir,
|
|
env=data['env'])
|
|
except subprocess.CalledProcessError as e:
|
|
ret = e.returncode
|
|
output += e.output
|
|
|
|
for f in contents:
|
|
f.update_time()
|
|
|
|
return relobjdir, ret, output
|
|
|
|
|
|
def subconfigure(args):
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--list', type=str,
|
|
help='File containing a list of subconfigures to run')
|
|
parser.add_argument('--skip', type=str,
|
|
help='File containing a list of Subconfigures to skip')
|
|
parser.add_argument('subconfigures', type=str, nargs='*',
|
|
help='Subconfigures to run if no list file is given')
|
|
args, others = parser.parse_known_args(args)
|
|
subconfigures = args.subconfigures
|
|
if args.list:
|
|
subconfigures.extend(open(args.list, 'rb').read().splitlines())
|
|
if args.skip:
|
|
skips = set(open(args.skip, 'rb').read().splitlines())
|
|
subconfigures = [s for s in subconfigures if s not in skips]
|
|
|
|
if not subconfigures:
|
|
return 0
|
|
|
|
ret = 0
|
|
# One would think using a ThreadPool would be faster, considering
|
|
# everything happens in subprocesses anyways, but no, it's actually
|
|
# slower on Windows. (20s difference overall!)
|
|
pool = Pool(len(subconfigures))
|
|
for relobjdir, returncode, output in \
|
|
pool.imap_unordered(run, subconfigures):
|
|
print prefix_lines(output, relobjdir)
|
|
sys.stdout.flush()
|
|
ret = max(returncode, ret)
|
|
if ret:
|
|
break
|
|
pool.close()
|
|
pool.join()
|
|
return ret
|
|
|
|
|
|
def main(args):
|
|
if args[0] != '--prepare':
|
|
return subconfigure(args)
|
|
|
|
topsrcdir = os.path.abspath(args[1])
|
|
subdir = args[2]
|
|
# subdir can be of the form srcdir:objdir
|
|
if ':' in subdir:
|
|
srcdir, subdir = subdir.split(':', 1)
|
|
else:
|
|
srcdir = subdir
|
|
srcdir = os.path.join(topsrcdir, srcdir)
|
|
objdir = os.path.abspath(subdir)
|
|
|
|
return prepare(srcdir, objdir, args[3], args[4:])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv[1:]))
|