123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- # 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).
- input = sys.stdin.read()
- if input:
- data = {a: b for [a, b] in eval(input)}
- environ = {a: b for a, b in data['env']}
- # These environment variables as passed from old-configure may contain
- # posix-style paths, which will not be meaningful to the js
- # subconfigure, which runs as a native python process, so use their
- # values from the environment. In the case of autoconf implemented
- # subconfigures, Msys will re-convert them properly.
- for var in ('HOME', 'TERM', 'PATH', 'TMPDIR', 'TMP',
- 'TEMP', 'INCLUDE'):
- if var in environ and var in os.environ:
- environ[var] = os.environ[var]
- 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:]))
|