Permalink
Browse files

Bug 774032 part 6 - Replace autoconf handling of config files and hea…

…ders with our own. r=ted
  • Loading branch information...
1 parent 72ee105 commit 21df4884f04fb2130a0424eedd81b9035b729055 @glandium glandium committed Aug 4, 2012
View
@@ -1,8 +1,9 @@
dnl
dnl Local autoconf macros used with mozilla
dnl The contents of this file are under the Public Domain.
-dnl
+dnl
+builtin(include, build/autoconf/config.status.m4)dnl
builtin(include, build/autoconf/toolchain.m4)dnl
builtin(include, build/autoconf/ccache.m4)dnl
builtin(include, build/autoconf/nspr.m4)dnl
View
@@ -25,6 +25,7 @@ fi
# Common makefiles used by everyone
add_makefiles "
+mozilla-config.h
Makefile
build/Makefile
build/pgo/Makefile
View
@@ -0,0 +1,313 @@
+# 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/.
+
+# Combined with build/autoconf/config.status.m4, ConfigStatus is an almost
+# drop-in replacement for autoconf 2.13's config.status, with features
+# borrowed from autoconf > 2.5, and additional features.
+
+from __future__ import with_statement
+from optparse import OptionParser
+import sys, re, os, posixpath
+from StringIO import StringIO
+# Standalone js doesn't have virtualenv.
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'config'))
+from Preprocessor import Preprocessor
+
+# Basic logging facility
+verbose = False
+def log(string):
+ if verbose:
+ print >>sys.stderr, string
+
+# We need relpath, but it is introduced in python 2.6
+# http://docs.python.org/library/os.path.html
+def my_relpath(path, start):
+ """
+ Return a relative version of a path
+ from /usr/lib/python2.6/posixpath.py
+ """
+
+ if not path:
+ raise ValueError("no path specified")
+
+ start_list = os.path.abspath(start).split(os.path.sep)
+ path_list = os.path.abspath(path).split(os.path.sep)
+
+ # Work out how much of the filepath is shared by start and path.
+ i = len(os.path.commonprefix([start_list, path_list]))
+
+ rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
+ if not rel_list:
+ return os.curdir
+ return os.path.join(*rel_list)
+
+relpath = getattr(os.path, "relpath", my_relpath)
+
+def ensureParentDir(file):
+ '''Ensures the directory parent to the given file exists'''
+ dir = os.path.dirname(file)
+ if dir and not os.path.exists(dir):
+ try:
+ os.makedirs(dir)
+ except OSError, error:
+ if error.errno != errno.EEXIST:
+ raise
+
+class FileAvoidWrite(StringIO):
+ '''file-like object that buffers its output and only writes it to disk
+ if the new contents are different from what the file may already contain.
+ '''
+ def __init__(self, filename):
+ self.filename = filename
+ StringIO.__init__(self)
+
+ def close(self):
+ buf = self.getvalue()
+ StringIO.close(self)
+ try:
+ file = open(self.filename, 'rU')
+ except IOError:
+ pass
+ else:
+ try:
+ if file.read() == buf:
+ log("%s is unchanged" % relpath(self.filename, os.curdir))
+ return
+ except IOError:
+ pass
+ finally:
+ file.close()
+
+ log("creating %s" % relpath(self.filename, os.curdir))
+ ensureParentDir(self.filename)
+ with open(self.filename, 'w') as file:
+ file.write(buf)
+
+ def __enter__(self):
+ return self
+ def __exit__(self, type, value, traceback):
+ self.close()
+
+def shell_escape(s):
+ '''Escape some characters with a backslash, and double dollar signs.
+ '''
+ return re.sub('''([ \t`#$^&*(){}\\|;'"<>?\[\]])''', r'\\\1', str(s)).replace('$', '$$')
+
+class ConfigEnvironment(object):
+ '''A ConfigEnvironment is defined by a source directory and a build
+ directory. It preprocesses files from the source directory and stores
+ the result in the object directory.
+
+ There are two types of files: config files and config headers,
+ each treated through a different member function.
+
+ Creating a ConfigEnvironment requires a few arguments:
+ - topsrcdir and topobjdir are, respectively, the top source and
+ the top object directory.
+ - defines is a list of (name, value) tuples. In autoconf, these are
+ set with AC_DEFINE and AC_DEFINE_UNQUOTED
+ - non_global_defines are a list of names appearing in defines above
+ that are not meant to be exported in ACDEFINES and ALLDEFINES (see
+ below)
+ - substs is a list of (name, value) tuples. In autoconf, these are
+ set with AC_SUBST.
+
+ ConfigEnvironment automatically defines two additional substs variables
+ from all the defines not appearing in non_global_defines:
+ - ACDEFINES contains the defines in the form -DNAME=VALUE, for use on
+ preprocessor command lines. The order in which defines were given
+ when creating the ConfigEnvironment is preserved.
+ - ALLDEFINES contains the defines in the form #define NAME VALUE, in
+ sorted order, for use in config files, for an automatic listing of
+ defines.
+
+ ConfigEnvironment expects a "top_srcdir" subst to be set with the top
+ source directory, in msys format on windows. It is used to derive a
+ "srcdir" subst when treating config files.
+ '''
+
+ def __init__(self, topobjdir = '.', topsrcdir = '.',
+ defines = [], non_global_defines = [], substs = []):
+ self.defines = dict(defines)
+ self.substs = dict(substs)
+ self.topsrcdir = topsrcdir
+ self.topobjdir = topobjdir
+ global_defines = [name for name, value in defines if not name in non_global_defines]
+ self.substs['ACDEFINES'] = ' '.join(["-D%s=%s" % (name, shell_escape(self.defines[name])) for name in global_defines])
+ self.substs['ALLDEFINES'] = '\n'.join(sorted(["#define %s %s" % (name, self.defines[name]) for name in global_defines]))
+
+ def get_relative_srcdir(self, file):
+ '''Returns the relative source directory for the given file, always
+ using / as a path separator.
+ '''
+ assert(isinstance(file, basestring))
+ dir = posixpath.dirname(relpath(file, self.topobjdir).replace(os.sep, '/'))
+ if dir:
+ return dir
+ return '.'
+
+ def get_file_srcdir(self, file):
+ '''Returns the srcdir for the given file, where srcdir is in msys
+ format on windows, thus derived from top_srcdir.
+ '''
+ dir = self.get_relative_srcdir(file)
+ return posixpath.normpath(posixpath.join(self.substs['top_srcdir'], dir))
+
+ def get_depth(self, file):
+ '''Returns the DEPTH for the given file, that is, the path to the
+ object directory relative to the directory containing the given file.
+ Always uses / as a path separator.
+ '''
+ return relpath(self.topobjdir, os.path.dirname(file)).replace(os.sep, '/')
+
+ def get_input(self, file):
+ '''Returns the input file path in the source tree that can be used
+ to create the given config file or header.
+ '''
+ assert(isinstance(file, basestring))
+ return os.path.normpath(os.path.join(self.topsrcdir, "%s.in" % relpath(file, self.topobjdir)))
+
+ def create_config_file(self, path):
+ '''Creates the given config file. A config file is generated by
+ taking the corresponding source file and replacing occurences of
+ "@VAR@" by the value corresponding to "VAR" in the substs dict.
+
+ Additional substs are defined according to the file being treated:
+ "srcdir" for its the path to its source directory
+ "relativesrcdir" for its source directory relative to the top
+ "DEPTH" for the path to the top object directory
+ '''
+ input = self.get_input(path)
+ pp = Preprocessor()
+ pp.context.update(self.substs)
+ pp.context.update(srcdir = self.get_file_srcdir(path))
+ pp.context.update(relativesrcdir = self.get_relative_srcdir(path))
+ pp.context.update(DEPTH = self.get_depth(path))
+ pp.do_filter('attemptSubstitution')
+ pp.setMarker(None)
+ with FileAvoidWrite(path) as pp.out:
+ pp.do_include(input)
+
+ def create_config_header(self, path):
+ '''Creates the given config header. A config header is generated by
+ taking the corresponding source file and replacing some #define/#undef
+ occurences:
+ "#undef NAME" is turned into "#define NAME VALUE"
+ "#define NAME" is unchanged
+ "#define NAME ORIGINAL_VALUE" is turned into "#define NAME VALUE"
+ "#undef UNKNOWN_NAME" is turned into "/* #undef UNKNOWN_NAME */"
+ Whitespaces are preserved.
+ '''
+ with open(self.get_input(path), 'rU') as input:
+ ensureParentDir(path)
+ output = FileAvoidWrite(path)
+ r = re.compile('^\s*#\s*(?P<cmd>[a-z]+)(?:\s+(?P<name>\S+)(?:\s+(?P<value>\S+))?)?', re.U)
+ for l in input:
+ m = r.match(l)
+ if m:
+ cmd = m.group('cmd')
+ name = m.group('name')
+ value = m.group('value')
+ if name:
+ if name in self.defines:
+ if cmd == 'define' and value:
+ l = l[:m.start('value')] + str(self.defines[name]) + l[m.end('value'):]
+ elif cmd == 'undef':
+ l = l[:m.start('cmd')] + 'define' + l[m.end('cmd'):m.end('name')] + ' ' + str(self.defines[name]) + l[m.end('name'):]
+ elif cmd == 'undef':
+ l = '/* ' + l[:m.end('name')] + ' */' + l[m.end('name'):]
+
+ output.write(l)
+ output.close()
+
+def config_status(topobjdir = '.', topsrcdir = '.',
+ defines = [], non_global_defines = [], substs = [],
+ files = [], headers = []):
+ '''Main function, providing config.status functionality.
+
+ Contrary to config.status, it doesn't use CONFIG_FILES or CONFIG_HEADERS
+ variables, but like config.status from autoconf 2.6, single files may be
+ generated with the --file and --header options. Several such options can
+ be given to generate several files at the same time.
+
+ Without the -n option, this program acts as config.status and considers
+ the current directory as the top object directory, even when config.status
+ is in a different directory. It will, however, treat the directory
+ containing config.status as the top object directory with the -n option,
+ while files given to the --file and --header arguments are considered
+ relative to the current directory.
+
+ The --recheck option, like with the original config.status, runs configure
+ again, with the options given in the "ac_configure_args" subst.
+
+ The options to this function are passed when creating the
+ ConfigEnvironment, except for files and headers, which contain the list
+ of files and headers to be generated by default. These lists, as well as
+ the actual wrapper script around this function, are meant to be generated
+ by configure. See build/autoconf/config.status.m4.
+
+ Unlike config.status behaviour with CONFIG_FILES and CONFIG_HEADERS,
+ but like config.status behaviour with --file and --header, providing
+ files or headers on the command line inhibits the default generation of
+ files when given headers and headers when given files.
+
+ Unlike config.status, the FILE:TEMPLATE syntax is not supported for
+ files and headers. The template is always the filename suffixed with
+ '.in', in the corresponding directory under the top source directory.
+ '''
+
+ if 'CONFIG_FILES' in os.environ:
+ raise Exception, 'Using the CONFIG_FILES environment variable is not supported. Use --file instead.'
+ if 'CONFIG_HEADERS' in os.environ:
+ raise Exception, 'Using the CONFIG_HEADERS environment variable is not supported. Use --header instead.'
+
+ parser = OptionParser()
+ parser.add_option('--recheck', dest='recheck', action='store_true',
+ help='update config.status by reconfiguring in the same conditions')
+ parser.add_option('--file', dest='files', metavar='FILE', action='append',
+ help='instantiate the configuration file FILE')
+ parser.add_option('--header', dest='headers', metavar='FILE', action='append',
+ help='instantiate the configuration header FILE')
+ parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
+ help='display verbose output')
+ parser.add_option('-n', dest='not_topobjdir', action='store_true',
+ help='do not consider current directory as top object directory')
+ (options, args) = parser.parse_args()
+
+ # Without -n, the current directory is meant to be the top object directory
+ if not options.not_topobjdir:
+ topobjdir = '.'
+
+ env = ConfigEnvironment(topobjdir = topobjdir, topsrcdir = topsrcdir,
+ defines = defines, non_global_defines = non_global_defines,
+ substs = substs)
+
+ if options.recheck:
+ # Execute configure from the top object directory
+ if not os.path.isabs(topsrcdir):
+ topsrcdir = relpath(topsrcdir, topobjdir)
+ os.chdir(topobjdir)
+ os.execlp('sh', 'sh', '-c', ' '.join([os.path.join(topsrcdir, 'configure'), env.substs['ac_configure_args'], '--no-create', '--no-recursion']))
+
+ if options.files:
+ files = options.files
+ headers = []
+ if options.headers:
+ headers = options.headers
+ if not options.files:
+ files = []
+ # Default to display messages when giving --file or --headers on the
+ # command line.
+ if options.files or options.headers or options.verbose:
+ global verbose
+ verbose = True
+ if not options.files and not options.headers:
+ print >>sys.stderr, "creating config files and headers..."
+ files = [os.path.join(topobjdir, f) for f in files]
+ headers = [os.path.join(topobjdir, f) for f in headers]
+
+ for file in files:
+ env.create_config_file(file)
+ for header in headers:
+ env.create_config_header(header)
View
@@ -263,4 +263,8 @@ libs:: $(topsrcdir)/tools/rb/fix-linux-stack.pl
endif
GARBAGE += $(srcdir)/automationutils.pyc
+
+# Test for ConfigStatus.py
+check::
+ $(PYTHON) $(srcdir)/tests/unit-ConfigStatus.py
endif # ENABLE_TESTS
Oops, something went wrong.

0 comments on commit 21df488

Please sign in to comment.