1
# This Source Code Form is subject to the terms of the Mozilla Public
2
# License, v. 2.0. If a copy of the MPL was not distributed with this
3
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
# Combined with build/autoconf/config.status.m4, ConfigStatus is an almost
6
# drop-in replacement for autoconf 2.13's config.status, with features
7
# borrowed from autoconf > 2.5, and additional features.
9
from __future__ import with_statement
10
from optparse import OptionParser
11
import sys, re, os, posixpath, ntpath
12
from StringIO import StringIO
13
# Standalone js doesn't have virtualenv.
14
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'config'))
15
from Preprocessor import Preprocessor
17
# Basic logging facility
21
print >>sys.stderr, string
23
# We need relpath, but it is introduced in python 2.6
24
# http://docs.python.org/library/os.path.html
25
def my_relpath(path, start):
27
Return a relative version of a path
28
from /usr/lib/python2.6/posixpath.py
32
raise ValueError("no path specified")
34
start_list = os.path.abspath(start).split(os.path.sep)
35
path_list = os.path.abspath(path).split(os.path.sep)
37
# Work out how much of the filepath is shared by start and path.
38
i = len(os.path.commonprefix([start_list, path_list]))
40
rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
43
return os.path.join(*rel_list)
45
relpath = getattr(os.path, "relpath", my_relpath)
47
def ensureParentDir(file):
48
'''Ensures the directory parent to the given file exists'''
49
dir = os.path.dirname(file)
50
if dir and not os.path.exists(dir):
53
except OSError, error:
54
if error.errno != errno.EEXIST:
57
class FileAvoidWrite(StringIO):
58
'''file-like object that buffers its output and only writes it to disk
59
if the new contents are different from what the file may already contain.
61
def __init__(self, filename):
62
self.filename = filename
63
StringIO.__init__(self)
69
file = open(self.filename, 'rU')
74
if file.read() == buf:
75
log("%s is unchanged" % relpath(self.filename, os.curdir))
82
log("creating %s" % relpath(self.filename, os.curdir))
83
ensureParentDir(self.filename)
84
with open(self.filename, 'w') as file:
89
def __exit__(self, type, value, traceback):
93
'''Escape some characters with a backslash, and double dollar signs.
95
return re.sub('''([ \t`#$^&*(){}\\|;'"<>?\[\]])''', r'\\\1', str(s)).replace('$', '$$')
97
class ConfigEnvironment(object):
98
'''A ConfigEnvironment is defined by a source directory and a build
99
directory. It preprocesses files from the source directory and stores
100
the result in the object directory.
102
There are two types of files: config files and config headers,
103
each treated through a different member function.
105
Creating a ConfigEnvironment requires a few arguments:
106
- topsrcdir and topobjdir are, respectively, the top source and
107
the top object directory.
108
- defines is a list of (name, value) tuples. In autoconf, these are
109
set with AC_DEFINE and AC_DEFINE_UNQUOTED
110
- non_global_defines are a list of names appearing in defines above
111
that are not meant to be exported in ACDEFINES and ALLDEFINES (see
113
- substs is a list of (name, value) tuples. In autoconf, these are
116
ConfigEnvironment automatically defines two additional substs variables
117
from all the defines not appearing in non_global_defines:
118
- ACDEFINES contains the defines in the form -DNAME=VALUE, for use on
119
preprocessor command lines. The order in which defines were given
120
when creating the ConfigEnvironment is preserved.
121
- ALLDEFINES contains the defines in the form #define NAME VALUE, in
122
sorted order, for use in config files, for an automatic listing of
124
and another additional subst variable from all the other substs:
125
- ALLSUBSTS contains the substs in the form NAME = VALUE, in sorted
126
order, for use in autoconf.mk. It includes ACDEFINES, but doesn't
129
ConfigEnvironment expects a "top_srcdir" subst to be set with the top
130
source directory, in msys format on windows. It is used to derive a
131
"srcdir" subst when treating config files. It can either be an absolute
132
path or a path relative to the topobjdir.
135
def __init__(self, topobjdir = '.', topsrcdir = '.',
136
defines = [], non_global_defines = [], substs = []):
137
self.defines = dict(defines)
138
self.substs = dict(substs)
139
self.topsrcdir = topsrcdir
140
self.topobjdir = topobjdir
141
global_defines = [name for name, value in defines if not name in non_global_defines]
142
self.substs['ACDEFINES'] = ' '.join(["-D%s=%s" % (name, shell_escape(self.defines[name])) for name in global_defines])
143
self.substs['ALLSUBSTS'] = '\n'.join(sorted(["%s = %s" % (name, self.substs[name]) for name in self.substs]))
144
self.substs['ALLDEFINES'] = '\n'.join(sorted(["#define %s %s" % (name, self.defines[name]) for name in global_defines]))
146
def get_relative_srcdir(self, file):
147
'''Returns the relative source directory for the given file, always
148
using / as a path separator.
150
assert(isinstance(file, basestring))
151
dir = posixpath.dirname(relpath(file, self.topobjdir).replace(os.sep, '/'))
156
def get_top_srcdir(self, file):
157
'''Returns a normalized top_srcdir for the given file: if
158
substs['top_srcdir'] is a relative path, it is relative to the
159
topobjdir. Adjust it to be relative to the file path.'''
160
top_srcdir = self.substs['top_srcdir']
161
if posixpath.isabs(top_srcdir) or ntpath.isabs(top_srcdir):
163
return posixpath.normpath(posixpath.join(self.get_depth(file), top_srcdir))
165
def get_file_srcdir(self, file):
166
'''Returns the srcdir for the given file, where srcdir is in msys
167
format on windows, thus derived from top_srcdir.
169
dir = self.get_relative_srcdir(file)
170
top_srcdir = self.get_top_srcdir(file)
171
return posixpath.normpath(posixpath.join(top_srcdir, dir))
173
def get_depth(self, file):
174
'''Returns the DEPTH for the given file, that is, the path to the
175
object directory relative to the directory containing the given file.
176
Always uses / as a path separator.
178
return relpath(self.topobjdir, os.path.dirname(file)).replace(os.sep, '/')
180
def get_input(self, file):
181
'''Returns the input file path in the source tree that can be used
182
to create the given config file or header.
184
assert(isinstance(file, basestring))
185
return os.path.normpath(os.path.join(self.topsrcdir, "%s.in" % relpath(file, self.topobjdir)))
187
def create_config_file(self, path):
188
'''Creates the given config file. A config file is generated by
189
taking the corresponding source file and replacing occurences of
190
"@VAR@" by the value corresponding to "VAR" in the substs dict.
192
Additional substs are defined according to the file being treated:
193
"srcdir" for its the path to its source directory
194
"relativesrcdir" for its source directory relative to the top
195
"DEPTH" for the path to the top object directory
197
input = self.get_input(path)
199
pp.context.update(self.substs)
200
pp.context.update(top_srcdir = self.get_top_srcdir(path))
201
pp.context.update(srcdir = self.get_file_srcdir(path))
202
pp.context.update(relativesrcdir = self.get_relative_srcdir(path))
203
pp.context.update(DEPTH = self.get_depth(path))
204
pp.do_filter('attemptSubstitution')
206
with FileAvoidWrite(path) as pp.out:
209
def create_config_header(self, path):
210
'''Creates the given config header. A config header is generated by
211
taking the corresponding source file and replacing some #define/#undef
213
"#undef NAME" is turned into "#define NAME VALUE"
214
"#define NAME" is unchanged
215
"#define NAME ORIGINAL_VALUE" is turned into "#define NAME VALUE"
216
"#undef UNKNOWN_NAME" is turned into "/* #undef UNKNOWN_NAME */"
217
Whitespaces are preserved.
219
with open(self.get_input(path), 'rU') as input:
220
ensureParentDir(path)
221
output = FileAvoidWrite(path)
222
r = re.compile('^\s*#\s*(?P<cmd>[a-z]+)(?:\s+(?P<name>\S+)(?:\s+(?P<value>\S+))?)?', re.U)
227
name = m.group('name')
228
value = m.group('value')
230
if name in self.defines:
231
if cmd == 'define' and value:
232
l = l[:m.start('value')] + str(self.defines[name]) + l[m.end('value'):]
234
l = l[:m.start('cmd')] + 'define' + l[m.end('cmd'):m.end('name')] + ' ' + str(self.defines[name]) + l[m.end('name'):]
236
l = '/* ' + l[:m.end('name')] + ' */' + l[m.end('name'):]
241
def config_status(topobjdir = '.', topsrcdir = '.',
242
defines = [], non_global_defines = [], substs = [],
243
files = [], headers = []):
244
'''Main function, providing config.status functionality.
246
Contrary to config.status, it doesn't use CONFIG_FILES or CONFIG_HEADERS
247
variables, but like config.status from autoconf 2.6, single files may be
248
generated with the --file and --header options. Several such options can
249
be given to generate several files at the same time.
251
Without the -n option, this program acts as config.status and considers
252
the current directory as the top object directory, even when config.status
253
is in a different directory. It will, however, treat the directory
254
containing config.status as the top object directory with the -n option,
255
while files given to the --file and --header arguments are considered
256
relative to the current directory.
258
The --recheck option, like with the original config.status, runs configure
259
again, with the options given in the "ac_configure_args" subst.
261
The options to this function are passed when creating the
262
ConfigEnvironment, except for files and headers, which contain the list
263
of files and headers to be generated by default. These lists, as well as
264
the actual wrapper script around this function, are meant to be generated
265
by configure. See build/autoconf/config.status.m4.
267
Unlike config.status behaviour with CONFIG_FILES and CONFIG_HEADERS,
268
but like config.status behaviour with --file and --header, providing
269
files or headers on the command line inhibits the default generation of
270
files when given headers and headers when given files.
272
Unlike config.status, the FILE:TEMPLATE syntax is not supported for
273
files and headers. The template is always the filename suffixed with
274
'.in', in the corresponding directory under the top source directory.
277
if 'CONFIG_FILES' in os.environ:
278
raise Exception, 'Using the CONFIG_FILES environment variable is not supported. Use --file instead.'
279
if 'CONFIG_HEADERS' in os.environ:
280
raise Exception, 'Using the CONFIG_HEADERS environment variable is not supported. Use --header instead.'
282
parser = OptionParser()
283
parser.add_option('--recheck', dest='recheck', action='store_true',
284
help='update config.status by reconfiguring in the same conditions')
285
parser.add_option('--file', dest='files', metavar='FILE', action='append',
286
help='instantiate the configuration file FILE')
287
parser.add_option('--header', dest='headers', metavar='FILE', action='append',
288
help='instantiate the configuration header FILE')
289
parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
290
help='display verbose output')
291
parser.add_option('-n', dest='not_topobjdir', action='store_true',
292
help='do not consider current directory as top object directory')
293
(options, args) = parser.parse_args()
295
# Without -n, the current directory is meant to be the top object directory
296
if not options.not_topobjdir:
299
env = ConfigEnvironment(topobjdir = topobjdir, topsrcdir = topsrcdir,
300
defines = defines, non_global_defines = non_global_defines,
304
# Execute configure from the top object directory
305
if not os.path.isabs(topsrcdir):
306
topsrcdir = relpath(topsrcdir, topobjdir)
308
os.execlp('sh', 'sh', '-c', ' '.join([os.path.join(topsrcdir, 'configure'), env.substs['ac_configure_args'], '--no-create', '--no-recursion']))
311
files = options.files
314
headers = options.headers
315
if not options.files:
317
# Default to display messages when giving --file or --headers on the
319
if options.files or options.headers or options.verbose:
322
if not options.files and not options.headers:
323
print >>sys.stderr, "creating config files and headers..."
324
files = [os.path.join(topobjdir, f) for f in files]
325
headers = [os.path.join(topobjdir, f) for f in headers]
328
env.create_config_file(file)
329
for header in headers:
330
env.create_config_header(header)