mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 784841 - Part 6: Move some functions from ConfigStatus to mozbuild; r=ted
--HG-- rename : build/tests/unit-ConfigStatus.py => python/mozbuild/mozbuild/test/backend/test_configenvironment.py
This commit is contained in:
parent
04f89eb353
commit
bddcff9d72
@ -6,15 +6,13 @@
|
||||
# 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, ntpath
|
||||
import errno
|
||||
from StringIO import StringIO
|
||||
from os.path import relpath
|
||||
import os
|
||||
import sys
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
from mozbuild.backend.configenvironment import ConfigEnvironment
|
||||
|
||||
# Standalone js doesn't have virtualenv.
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'config'))
|
||||
from Preprocessor import Preprocessor
|
||||
|
||||
# Basic logging facility
|
||||
@ -24,200 +22,6 @@ def log(string):
|
||||
print >>sys.stderr, string
|
||||
|
||||
|
||||
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.
|
||||
and another additional subst variable from all the other substs:
|
||||
- ALLSUBSTS contains the substs in the form NAME = VALUE, in sorted
|
||||
order, for use in autoconf.mk. It includes ACDEFINES, but doesn't
|
||||
include ALLDEFINES.
|
||||
|
||||
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. It can either be an absolute
|
||||
path or a path relative to the topobjdir.
|
||||
'''
|
||||
|
||||
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['ALLSUBSTS'] = '\n'.join(sorted(["%s = %s" % (name, self.substs[name]) for name in self.substs]))
|
||||
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_top_srcdir(self, file):
|
||||
'''Returns a normalized top_srcdir for the given file: if
|
||||
substs['top_srcdir'] is a relative path, it is relative to the
|
||||
topobjdir. Adjust it to be relative to the file path.'''
|
||||
top_srcdir = self.substs['top_srcdir']
|
||||
if posixpath.isabs(top_srcdir) or ntpath.isabs(top_srcdir):
|
||||
return top_srcdir
|
||||
return posixpath.normpath(posixpath.join(self.get_depth(file), top_srcdir))
|
||||
|
||||
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)
|
||||
top_srcdir = self.get_top_srcdir(file)
|
||||
return posixpath.normpath(posixpath.join(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(top_srcdir = self.get_top_srcdir(path))
|
||||
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 = []):
|
||||
@ -276,9 +80,8 @@ def config_status(topobjdir = '.', topsrcdir = '.',
|
||||
if not options.not_topobjdir:
|
||||
topobjdir = '.'
|
||||
|
||||
env = ConfigEnvironment(topobjdir = topobjdir, topsrcdir = topsrcdir,
|
||||
defines = defines, non_global_defines = non_global_defines,
|
||||
substs = substs)
|
||||
env = ConfigEnvironment(topsrcdir, topobjdir, defines=defines,
|
||||
non_global_defines=non_global_defines, substs=substs)
|
||||
|
||||
if options.recheck:
|
||||
# Execute configure from the top object directory
|
||||
|
@ -103,7 +103,6 @@ GDBINIT_DEST = $(FINAL_TARGET)
|
||||
INSTALL_TARGETS += GDBINIT
|
||||
|
||||
PYTHON_UNIT_TESTS := \
|
||||
tests/unit-ConfigStatus.py \
|
||||
tests/test.py \
|
||||
$(NULL)
|
||||
|
||||
|
@ -6,15 +6,13 @@
|
||||
# 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, ntpath
|
||||
import errno
|
||||
from StringIO import StringIO
|
||||
from os.path import relpath
|
||||
import os
|
||||
import sys
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
from mozbuild.backend.configenvironment import ConfigEnvironment
|
||||
|
||||
# Standalone js doesn't have virtualenv.
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'config'))
|
||||
from Preprocessor import Preprocessor
|
||||
|
||||
# Basic logging facility
|
||||
@ -24,200 +22,6 @@ def log(string):
|
||||
print >>sys.stderr, string
|
||||
|
||||
|
||||
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.
|
||||
and another additional subst variable from all the other substs:
|
||||
- ALLSUBSTS contains the substs in the form NAME = VALUE, in sorted
|
||||
order, for use in autoconf.mk. It includes ACDEFINES, but doesn't
|
||||
include ALLDEFINES.
|
||||
|
||||
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. It can either be an absolute
|
||||
path or a path relative to the topobjdir.
|
||||
'''
|
||||
|
||||
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['ALLSUBSTS'] = '\n'.join(sorted(["%s = %s" % (name, self.substs[name]) for name in self.substs]))
|
||||
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_top_srcdir(self, file):
|
||||
'''Returns a normalized top_srcdir for the given file: if
|
||||
substs['top_srcdir'] is a relative path, it is relative to the
|
||||
topobjdir. Adjust it to be relative to the file path.'''
|
||||
top_srcdir = self.substs['top_srcdir']
|
||||
if posixpath.isabs(top_srcdir) or ntpath.isabs(top_srcdir):
|
||||
return top_srcdir
|
||||
return posixpath.normpath(posixpath.join(self.get_depth(file), top_srcdir))
|
||||
|
||||
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)
|
||||
top_srcdir = self.get_top_srcdir(file)
|
||||
return posixpath.normpath(posixpath.join(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(top_srcdir = self.get_top_srcdir(path))
|
||||
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 = []):
|
||||
@ -276,9 +80,8 @@ def config_status(topobjdir = '.', topsrcdir = '.',
|
||||
if not options.not_topobjdir:
|
||||
topobjdir = '.'
|
||||
|
||||
env = ConfigEnvironment(topobjdir = topobjdir, topsrcdir = topsrcdir,
|
||||
defines = defines, non_global_defines = non_global_defines,
|
||||
substs = substs)
|
||||
env = ConfigEnvironment(topsrcdir, topobjdir, defines=defines,
|
||||
non_global_defines=non_global_defines, substs=substs)
|
||||
|
||||
if options.recheck:
|
||||
# Execute configure from the top object directory
|
||||
|
@ -11,6 +11,7 @@ include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
test_dirs := \
|
||||
mozbuild/mozbuild/test \
|
||||
mozbuild/mozbuild/test/backend \
|
||||
mozbuild/mozbuild/test/compilation \
|
||||
mozbuild/mozbuild/test/frontend \
|
||||
mozbuild/mozpack/test \
|
||||
|
0
python/mozbuild/mozbuild/backend/__init__.py
Normal file
0
python/mozbuild/mozbuild/backend/__init__.py
Normal file
179
python/mozbuild/mozbuild/backend/configenvironment.py
Normal file
179
python/mozbuild/mozbuild/backend/configenvironment.py
Normal file
@ -0,0 +1,179 @@
|
||||
# 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/.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import ntpath
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
|
||||
from os.path import relpath
|
||||
|
||||
from Preprocessor import Preprocessor
|
||||
|
||||
from ..util import (
|
||||
ensureParentDir,
|
||||
FileAvoidWrite,
|
||||
)
|
||||
|
||||
|
||||
RE_SHELL_ESCAPE = re.compile('''([ \t`#$^&*(){}\\|;'"<>?\[\]])''')
|
||||
|
||||
|
||||
def shell_escape(s):
|
||||
"""Escape some characters with a backslash, and double dollar signs."""
|
||||
return RE_SHELL_ESCAPE.sub(r'\\\1', str(s)).replace('$', '$$')
|
||||
|
||||
|
||||
class ConfigEnvironment(object):
|
||||
"""Perform actions associated with a configured but bare objdir.
|
||||
|
||||
The purpose of this class is to preprocess files from the source directory
|
||||
and output results 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.
|
||||
and another additional subst variable from all the other substs:
|
||||
- ALLSUBSTS contains the substs in the form NAME = VALUE, in sorted
|
||||
order, for use in autoconf.mk. It includes ACDEFINES, but doesn't
|
||||
include ALLDEFINES.
|
||||
|
||||
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. It can either be an absolute
|
||||
path or a path relative to the topobjdir.
|
||||
"""
|
||||
|
||||
def __init__(self, topsrcdir, topobjdir, 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['ALLSUBSTS'] = '\n'.join(sorted(['%s = %s' % (name,
|
||||
self.substs[name]) for name in self.substs]))
|
||||
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_top_srcdir(self, file):
|
||||
'''Returns a normalized top_srcdir for the given file: if
|
||||
substs['top_srcdir'] is a relative path, it is relative to the
|
||||
topobjdir. Adjust it to be relative to the file path.'''
|
||||
top_srcdir = self.substs['top_srcdir']
|
||||
if posixpath.isabs(top_srcdir) or ntpath.isabs(top_srcdir):
|
||||
return top_srcdir
|
||||
return posixpath.normpath(posixpath.join(self.get_depth(file), top_srcdir))
|
||||
|
||||
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)
|
||||
top_srcdir = self.get_top_srcdir(file)
|
||||
return posixpath.normpath(posixpath.join(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(top_srcdir = self.get_top_srcdir(path))
|
||||
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()
|
||||
|
0
python/mozbuild/mozbuild/test/backend/__init__.py
Normal file
0
python/mozbuild/mozbuild/test/backend/__init__.py
Normal file
@ -1,55 +1,23 @@
|
||||
from __future__ import with_statement
|
||||
# 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/.
|
||||
|
||||
import os, posixpath
|
||||
from StringIO import StringIO
|
||||
import unittest
|
||||
from mozunit import main, MockedOpen
|
||||
import ConfigStatus
|
||||
from ConfigStatus import FileAvoidWrite
|
||||
|
||||
import mozbuild.backend.configenvironment as ConfigStatus
|
||||
|
||||
class ConfigEnvironment(ConfigStatus.ConfigEnvironment):
|
||||
def __init__(self, **args):
|
||||
ConfigStatus.ConfigEnvironment.__init__(self, **args)
|
||||
def __init__(self, *args, **kwargs):
|
||||
ConfigStatus.ConfigEnvironment.__init__(self, *args, **kwargs)
|
||||
# Be helpful to unit tests
|
||||
if not 'top_srcdir' in self.substs:
|
||||
if os.path.isabs(self.topsrcdir):
|
||||
self.substs['top_srcdir'] = self.topsrcdir.replace(os.sep, '/')
|
||||
else:
|
||||
self.substs['top_srcdir'] = ConfigStatus.relpath(self.topsrcdir, self.topobjdir).replace(os.sep, '/')
|
||||
|
||||
class TestFileAvoidWrite(unittest.TestCase):
|
||||
def test_file_avoid_write(self):
|
||||
'''Test the FileAvoidWrite class
|
||||
'''
|
||||
with MockedOpen({'file': 'content'}):
|
||||
# Overwriting an existing file replaces its content
|
||||
with FileAvoidWrite('file') as file:
|
||||
file.write('bazqux')
|
||||
self.assertEqual(open('file', 'r').read(), 'bazqux')
|
||||
|
||||
# Creating a new file (obviously) stores its content
|
||||
with FileAvoidWrite('file2') as file:
|
||||
file.write('content')
|
||||
self.assertEqual(open('file2').read(), 'content')
|
||||
|
||||
class MyMockedOpen(MockedOpen):
|
||||
'''MockedOpen extension to raise an exception if something
|
||||
attempts to write in an opened file.
|
||||
'''
|
||||
def __call__(self, name, mode):
|
||||
if 'w' in mode:
|
||||
raise Exception, 'Unexpected open with write mode'
|
||||
return MockedOpen.__call__(self, name, mode)
|
||||
|
||||
with MyMockedOpen({'file': 'content'}):
|
||||
# Validate that MyMockedOpen works as intended
|
||||
file = FileAvoidWrite('file')
|
||||
file.write('foobar')
|
||||
self.assertRaises(Exception, file.close)
|
||||
|
||||
# Check that no write actually happens when writing the
|
||||
# same content as what already is in the file
|
||||
with FileAvoidWrite('file') as file:
|
||||
file.write('content')
|
||||
self.substs['top_srcdir'] = os.path.relpath(self.topsrcdir, self.topobjdir).replace(os.sep, '/')
|
||||
|
||||
|
||||
class TestEnvironment(unittest.TestCase):
|
||||
@ -57,7 +25,7 @@ class TestEnvironment(unittest.TestCase):
|
||||
'''Test the automatically set values of ACDEFINES, ALLDEFINES
|
||||
and ALLSUBSTS.
|
||||
'''
|
||||
env = ConfigEnvironment(
|
||||
env = ConfigEnvironment('.', '.',
|
||||
defines = [ ('foo', 'bar'), ('baz', 'qux 42'),
|
||||
('abc', 'def'), ('extra', 'foobar') ],
|
||||
non_global_defines = ['extra', 'ignore'],
|
||||
@ -86,7 +54,7 @@ zzz = "abc def"''')
|
||||
@foo@
|
||||
@bar@
|
||||
'''}):
|
||||
env = ConfigEnvironment(substs = [ ('foo', 'bar baz') ])
|
||||
env = ConfigEnvironment('.', '.', substs = [ ('foo', 'bar baz') ])
|
||||
env.create_config_file('file')
|
||||
self.assertEqual(open('file', 'r').read(), '''#ifdef foo
|
||||
bar baz
|
||||
@ -113,7 +81,7 @@ bar baz
|
||||
# define foo 42
|
||||
#endif
|
||||
'''}):
|
||||
env = ConfigEnvironment(defines = [ ('foo', 'baz qux'), ('baz', 1) ])
|
||||
env = ConfigEnvironment('.', '.', defines = [ ('foo', 'baz qux'), ('baz', 1) ])
|
||||
env.create_config_header('file')
|
||||
self.assertEqual(open('file','r').read(), '''
|
||||
/* Comment */
|
@ -1,6 +1,6 @@
|
||||
# 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/.
|
||||
# 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/.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@ -8,9 +8,15 @@ import hashlib
|
||||
import unittest
|
||||
|
||||
from mozfile.mozfile import NamedTemporaryFile
|
||||
from mozunit import main
|
||||
from mozunit import (
|
||||
main,
|
||||
MockedOpen,
|
||||
)
|
||||
|
||||
from mozbuild.util import hash_file
|
||||
from mozbuild.util import (
|
||||
FileAvoidWrite,
|
||||
hash_file,
|
||||
)
|
||||
|
||||
|
||||
class TestHashing(unittest.TestCase):
|
||||
@ -44,5 +50,39 @@ class TestHashing(unittest.TestCase):
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
|
||||
class TestFileAvoidWrite(unittest.TestCase):
|
||||
def test_file_avoid_write(self):
|
||||
with MockedOpen({'file': 'content'}):
|
||||
# Overwriting an existing file replaces its content
|
||||
with FileAvoidWrite('file') as file:
|
||||
file.write('bazqux')
|
||||
self.assertEqual(open('file', 'r').read(), 'bazqux')
|
||||
|
||||
# Creating a new file (obviously) stores its content
|
||||
with FileAvoidWrite('file2') as file:
|
||||
file.write('content')
|
||||
self.assertEqual(open('file2').read(), 'content')
|
||||
|
||||
class MyMockedOpen(MockedOpen):
|
||||
'''MockedOpen extension to raise an exception if something
|
||||
attempts to write in an opened file.
|
||||
'''
|
||||
def __call__(self, name, mode):
|
||||
if 'w' in mode:
|
||||
raise Exception, 'Unexpected open with write mode'
|
||||
return MockedOpen.__call__(self, name, mode)
|
||||
|
||||
with MyMockedOpen({'file': 'content'}):
|
||||
# Validate that MyMockedOpen works as intended
|
||||
file = FileAvoidWrite('file')
|
||||
file.write('foobar')
|
||||
self.assertRaises(Exception, file.close)
|
||||
|
||||
# Check that no write actually happens when writing the
|
||||
# same content as what already is in the file
|
||||
with FileAvoidWrite('file') as file:
|
||||
file.write('content')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@ -8,7 +8,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
import errno
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
from StringIO import StringIO
|
||||
|
||||
|
||||
def hash_file(path):
|
||||
@ -84,3 +88,52 @@ class ReadOnlyDefaultDict(DefaultOnReadDict, ReadOnlyDict):
|
||||
def __init__(self, d, defaults=None, global_default=undefined):
|
||||
DefaultOnReadDict.__init__(self, d, defaults, global_default)
|
||||
|
||||
|
||||
def ensureParentDir(path):
|
||||
"""Ensures the directory parent to the given file exists."""
|
||||
d = os.path.dirname(path)
|
||||
if d and not os.path.exists(path):
|
||||
try:
|
||||
os.makedirs(d)
|
||||
except OSError, error:
|
||||
if error.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
|
||||
class FileAvoidWrite(StringIO):
|
||||
"""File-like object that buffers output and only writes if content changed.
|
||||
|
||||
We create an instance from an existing filename. New content is written to
|
||||
it. When we close the file object, if the content in the in-memory buffer
|
||||
differs from what is on disk, then we write out the new content. Otherwise,
|
||||
the original file is untouched.
|
||||
"""
|
||||
def __init__(self, filename):
|
||||
StringIO.__init__(self)
|
||||
self.filename = filename
|
||||
|
||||
def close(self):
|
||||
buf = self.getvalue()
|
||||
StringIO.close(self)
|
||||
try:
|
||||
existing = open(self.filename, 'rU')
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
if existing.read() == buf:
|
||||
return
|
||||
except IOError:
|
||||
pass
|
||||
finally:
|
||||
existing.close()
|
||||
|
||||
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()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user