gecko-dev/config/check_macroassembler_style.py
Gregory Szorc 2b1957d1e5 Bug 1361172 - Rewrite code for finding files in VCS checkout; r=glandium
We're getting an intermittent failure running `hg manifest` in CI. I
have no clue why.

What I do know is that we now have the mozversioncontrol Python package
for containing utility code for interacting with version control. It is
slightly more robust and I'm willing to support it more than I am
check_utils.py.

This commit adds a new API to our abstract repository class to obtain the
files in the working directory by querying version control.

Since I suspect cwd was coming into play in automation, I've also
added a utility function to mozversioncontrol to attempt to find
a version control checkout from the current working directory. It
simply traces ancestor paths looking for a .hg or .git directory.

Finally, I've ported all callers of the now-deleted API to the new
one. The old code had some "../.." paths in it, meaning it only
worked when cwd was just right. Since we resolve the absolute path
to the checkout and store it on the repo object, I've updated the
code so it should work no matter what cwd is as long as a repo can
be found. I'm not 100% confident I found all consumers assuming cwd.
But it's a start.

I'm not 100% confident this will fix the intermittent issues in CI. But
at least we should get a better error message and at least we'll be
running less hacky code.

MozReview-Commit-ID: AmCraHXcTEX

--HG--
extra : rebase_source : 815ae369776577ad374333920fd645d412a55148
2017-05-18 16:06:49 -07:00

284 lines
9.2 KiB
Python

# vim: set ts=8 sts=4 et sw=4 tw=99:
# 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 checks that SpiderMonkey MacroAssembler methods are properly
# annotated.
#
# The MacroAssembler has one interface for all platforms, but it might have one
# definition per platform. The code of the MacroAssembler use a macro to
# annotate the method declarations, in order to delete the function if it is not
# present on the current platform, and also to locate the files in which the
# methods are defined.
#
# This script scans the MacroAssembler.h header, for method declarations.
# It also scans MacroAssembler-/arch/.cpp, MacroAssembler-/arch/-inl.h, and
# MacroAssembler-inl.h for method definitions. The result of both scans are
# uniformized, and compared, to determine if the MacroAssembler.h header as
# proper methods annotations.
#----------------------------------------------------------------------------
from __future__ import print_function
import difflib
import os
import re
import sys
from mozversioncontrol import get_repository_from_env
architecture_independent = set([ 'generic' ])
all_architecture_names = set([ 'x86', 'x64', 'arm', 'arm64', 'mips32', 'mips64' ])
all_shared_architecture_names = set([ 'x86_shared', 'mips_shared', 'arm', 'arm64' ])
reBeforeArg = "(?<=[(,\s])"
reArgType = "(?P<type>[\w\s:*&]+)"
reArgName = "(?P<name>\s\w+)"
reArgDefault = "(?P<default>(?:\s=[^,)]+)?)"
reAfterArg = "(?=[,)])"
reMatchArg = re.compile(reBeforeArg + reArgType + reArgName + reArgDefault + reAfterArg)
def get_normalized_signatures(signature, fileAnnot = None):
# Remove static
signature = signature.replace('static', '')
# Remove semicolon.
signature = signature.replace(';', ' ')
# Normalize spaces.
signature = re.sub(r'\s+', ' ', signature).strip()
# Match arguments, and keep only the type.
signature = reMatchArg.sub('\g<type>', signature)
# Remove class name
signature = signature.replace('MacroAssembler::', '')
# Extract list of architectures
archs = ['generic']
if fileAnnot:
archs = [fileAnnot['arch']]
if 'DEFINED_ON(' in signature:
archs = re.sub(r'.*DEFINED_ON\((?P<archs>[^()]*)\).*', '\g<archs>', signature).split(',')
archs = [a.strip() for a in archs]
signature = re.sub(r'\s+DEFINED_ON\([^()]*\)', '', signature)
elif 'PER_ARCH' in signature:
archs = all_architecture_names
signature = re.sub(r'\s+PER_ARCH', '', signature)
elif 'PER_SHARED_ARCH' in signature:
archs = all_shared_architecture_names
signature = re.sub(r'\s+PER_SHARED_ARCH', '', signature)
else:
# No signature annotation, the list of architectures remains unchanged.
pass
# Extract inline annotation
inline = False
if fileAnnot:
inline = fileAnnot['inline']
if 'inline ' in signature:
signature = re.sub(r'inline\s+', '', signature)
inline = True
inlinePrefx = ''
if inline:
inlinePrefx = 'inline '
signatures = [
{ 'arch': a, 'sig': inlinePrefx + signature }
for a in archs
]
return signatures
file_suffixes = set([
a.replace('_', '-') for a in
all_architecture_names.union(all_shared_architecture_names)
])
def get_file_annotation(filename):
origFilename = filename
filename = filename.split('/')[-1]
inline = False
if filename.endswith('.cpp'):
filename = filename[:-len('.cpp')]
elif filename.endswith('-inl.h'):
inline = True
filename = filename[:-len('-inl.h')]
else:
raise Exception('unknown file name', origFilename)
arch = 'generic'
for suffix in file_suffixes:
if filename == 'MacroAssembler-' + suffix:
arch = suffix
break
return {
'inline': inline,
'arch': arch.replace('-', '_')
}
def get_macroassembler_definitions(filename):
try:
fileAnnot = get_file_annotation(filename)
except:
return []
style_section = False
code_section = False
lines = ''
signatures = []
with open(filename) as f:
for line in f:
if '//{{{ check_macroassembler_style' in line:
style_section = True
elif '//}}} check_macroassembler_style' in line:
style_section = False
if not style_section:
continue
line = re.sub(r'//.*', '', line)
if line.startswith('{'):
if 'MacroAssembler::' in lines:
signatures.extend(get_normalized_signatures(lines, fileAnnot))
code_section = True
continue
if line.startswith('}'):
code_section = False
lines = ''
continue
if code_section:
continue
if len(line.strip()) == 0:
lines = ''
continue
lines = lines + line
# Continue until we have a complete declaration
if '{' not in lines:
continue
# Skip variable declarations
if ')' not in lines:
lines = ''
continue
return signatures
def get_macroassembler_declaration(filename):
style_section = False
lines = ''
signatures = []
with open(filename) as f:
for line in f:
if '//{{{ check_macroassembler_style' in line:
style_section = True
elif '//}}} check_macroassembler_style' in line:
style_section = False
if not style_section:
continue
line = re.sub(r'//.*', '', line)
if len(line.strip()) == 0:
lines = ''
continue
lines = lines + line
# Continue until we have a complete declaration
if ';' not in lines:
continue
# Skip variable declarations
if ')' not in lines:
lines = ''
continue
signatures.extend(get_normalized_signatures(lines))
lines = ''
return signatures
def append_signatures(d, sigs):
for s in sigs:
if s['sig'] not in d:
d[s['sig']] = []
d[s['sig']].append(s['arch']);
return d
def generate_file_content(signatures):
output = []
for s in sorted(signatures.keys()):
archs = set(sorted(signatures[s]))
if len(archs.symmetric_difference(architecture_independent)) == 0:
output.append(s + ';\n')
if s.startswith('inline'):
output.append(' is defined in MacroAssembler-inl.h\n')
else:
output.append(' is defined in MacroAssembler.cpp\n')
else:
if len(archs.symmetric_difference(all_architecture_names)) == 0:
output.append(s + ' PER_ARCH;\n')
elif len(archs.symmetric_difference(all_shared_architecture_names)) == 0:
output.append(s + ' PER_SHARED_ARCH;\n')
else:
output.append(s + ' DEFINED_ON(' + ', '.join(archs) + ');\n')
for a in archs:
a = a.replace('_', '-')
masm = '%s/MacroAssembler-%s' % (a, a)
if s.startswith('inline'):
output.append(' is defined in %s-inl.h\n' % masm)
else:
output.append(' is defined in %s.cpp\n' % masm)
return output
def check_style():
# We read from the header file the signature of each function.
decls = dict() # type: dict(signature => ['x86', 'x64'])
# We infer from each file the signature of each MacroAssembler function.
defs = dict() # type: dict(signature => ['x86', 'x64'])
repo = get_repository_from_env()
# Select the appropriate files.
for filename in repo.get_files_in_working_directory():
if not filename.startswith('js/src/jit/'):
continue
if 'MacroAssembler' not in filename:
continue
filename = os.path.join(repo.path, filename)
if filename.endswith('MacroAssembler.h'):
decls = append_signatures(decls, get_macroassembler_declaration(filename))
else:
defs = append_signatures(defs, get_macroassembler_definitions(filename))
# Compare declarations and definitions output.
difflines = difflib.unified_diff(generate_file_content(decls),
generate_file_content(defs),
fromfile='check_macroassembler_style.py declared syntax',
tofile='check_macroassembler_style.py found definitions')
ok = True
for diffline in difflines:
ok = False
print(diffline, end='')
return ok
def main():
ok = check_style()
if ok:
print('TEST-PASS | check_macroassembler_style.py | ok')
else:
print('TEST-UNEXPECTED-FAIL | check_macroassembler_style.py | actual output does not match expected output; diff is above')
sys.exit(0 if ok else 1)
if __name__ == '__main__':
main()