mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
339 lines
10 KiB
Python
Executable File
339 lines
10 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# line-directive.py - Transform line numbers in error messages -*- python -*-
|
|
#
|
|
# This source file is part of the Swift.org open source project
|
|
#
|
|
# Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
# Licensed under Apache License v2.0 with Runtime Library Exception
|
|
#
|
|
# See https://swift.org/LICENSE.txt for license information
|
|
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
#
|
|
# ----------------------------------------------------------------------------
|
|
#
|
|
# Converts line numbers in error messages according to "line directive"
|
|
# comments.
|
|
#
|
|
# ----------------------------------------------------------------------------
|
|
|
|
from __future__ import print_function
|
|
|
|
import bisect
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
line_pattern = re.compile(
|
|
r'^// ###sourceLocation\(file:\s*"([^"]+)",\s*line:\s*([0-9]+)\s*\)')
|
|
|
|
|
|
def _make_line_map(target_filename, stream=None):
|
|
"""
|
|
>>> from StringIO import StringIO
|
|
>>> _make_line_map('box',
|
|
... StringIO('''// ###sourceLocation(file: "foo.bar", line: 3)
|
|
... line 2
|
|
... line 3
|
|
... line 4
|
|
... // ###sourceLocation(file: "baz.txt", line: 20)
|
|
... line 6
|
|
... line 7
|
|
... '''))
|
|
[(0, 'box', 1), (1, 'foo.bar', 3), (5, 'baz.txt', 20)]
|
|
"""
|
|
result = [(0, target_filename, 1)]
|
|
input = stream or open(target_filename)
|
|
for i, l in enumerate(input.readlines()):
|
|
m = line_pattern.match(l)
|
|
if m:
|
|
result.append((i + 1, m.group(1), int(m.group(2))))
|
|
return result
|
|
|
|
|
|
_line_maps = {}
|
|
|
|
|
|
def fline_map(target_filename):
|
|
map = _line_maps.get(target_filename)
|
|
if map is None:
|
|
map = _make_line_map(target_filename)
|
|
_line_maps[target_filename] = map
|
|
return map
|
|
|
|
|
|
def map_line_to_source_file(target_filename, target_line_num):
|
|
"""
|
|
>>> from tempfile import *
|
|
>>> t = NamedTemporaryFile()
|
|
>>> t.write('''line 1
|
|
... line 2
|
|
... // ###sourceLocation(file: "foo.bar", line: 20)
|
|
... line 4
|
|
... line 5
|
|
... // ###sourceLocation(file: "baz.txt", line: 5)
|
|
... line 7
|
|
... line 8
|
|
... ''')
|
|
>>> t.flush()
|
|
>>> (t2, l) = map_line_to_source_file(t.name, 1)
|
|
>>> t2 == t.name, l
|
|
(True, 1)
|
|
>>> (t2, l) = map_line_to_source_file(t.name, 2)
|
|
>>> t2 == t.name, l
|
|
(True, 2)
|
|
>>> (t2, l) = map_line_to_source_file(t.name, 3)
|
|
>>> t2 == t.name, l
|
|
(True, 3)
|
|
>>> map_line_to_source_file(t.name, 4)
|
|
('foo.bar', 20)
|
|
>>> map_line_to_source_file(t.name, 5)
|
|
('foo.bar', 21)
|
|
>>> map_line_to_source_file(t.name, 6)
|
|
('foo.bar', 22)
|
|
>>> map_line_to_source_file(t.name, 7)
|
|
('baz.txt', 5)
|
|
>>> map_line_to_source_file(t.name, 8)
|
|
('baz.txt', 6)
|
|
>>> map_line_to_source_file(t.name, 42)
|
|
('baz.txt', 40)
|
|
"""
|
|
assert(target_line_num > 0)
|
|
map = fline_map(target_filename)
|
|
index = bisect.bisect_left(map, (target_line_num, '', 0))
|
|
base = map[index - 1]
|
|
return base[1], base[2] + (target_line_num - base[0] - 1)
|
|
|
|
|
|
def map_line_from_source_file(source_filename,
|
|
source_line_num,
|
|
target_filename):
|
|
"""
|
|
>>> from tempfile import *
|
|
>>> t = NamedTemporaryFile()
|
|
>>> t.write('''line 1
|
|
... line 2
|
|
... // ###sourceLocation(file: "foo.bar", line: 20)
|
|
... line 4
|
|
... line 5
|
|
... // ###sourceLocation(file: "baz.txt", line: 5)
|
|
... line 7
|
|
... line 8
|
|
... ''')
|
|
>>> t.flush()
|
|
>>> map_line_from_source_file(t.name, 1, t.name)
|
|
1
|
|
>>> map_line_from_source_file(t.name, 2, t.name)
|
|
2
|
|
>>> map_line_from_source_file(t.name, 3, t.name)
|
|
3
|
|
>>> try: map_line_from_source_file(t.name, 4, t.name)
|
|
... except RuntimeError: pass
|
|
>>> try: map_line_from_source_file('foo.bar', 19, t.name)
|
|
... except RuntimeError: pass
|
|
>>> map_line_from_source_file('foo.bar', 20, t.name)
|
|
4
|
|
>>> map_line_from_source_file('foo.bar', 21, t.name)
|
|
5
|
|
>>> map_line_from_source_file('foo.bar', 22, t.name)
|
|
6
|
|
>>> try: map_line_from_source_file('foo.bar', 23, t.name)
|
|
... except RuntimeError: pass
|
|
>>> map_line_from_source_file('baz.txt', 5, t.name)
|
|
7
|
|
>>> map_line_from_source_file('baz.txt', 6, t.name)
|
|
8
|
|
>>> map_line_from_source_file('baz.txt', 33, t.name)
|
|
35
|
|
>>> try: map_line_from_source_file(t.name, 33, t.name)
|
|
... except RuntimeError: pass
|
|
>>> try: map_line_from_source_file('foo.bar', 2, t.name)
|
|
... except RuntimeError: pass
|
|
"""
|
|
assert(source_line_num > 0)
|
|
map = fline_map(target_filename)
|
|
|
|
for i, (target_line_num, found_source_filename,
|
|
found_source_line_num) in enumerate(map):
|
|
if found_source_filename != source_filename:
|
|
continue
|
|
if found_source_line_num > source_line_num:
|
|
continue
|
|
result = target_line_num + (source_line_num - found_source_line_num)
|
|
if i + 1 == len(map) or map[i + 1][0] > result:
|
|
return result + 1
|
|
raise RuntimeError("line not found")
|
|
|
|
|
|
def read_response_file(file_path):
|
|
with open(file_path, 'r') as files:
|
|
return filter(None, files.read().split(';'))
|
|
|
|
|
|
def expand_response_files(files):
|
|
expanded_files = []
|
|
for file_path in files:
|
|
# Read a list of files from a response file.
|
|
if file_path[0] == '@':
|
|
expanded_files.extend(read_response_file(file_path[1:]))
|
|
else:
|
|
expanded_files.append(file_path)
|
|
|
|
return expanded_files
|
|
|
|
|
|
def run():
|
|
"""Simulate a couple of gyb-generated files
|
|
|
|
>>> from tempfile import *
|
|
>>> target1 = NamedTemporaryFile()
|
|
>>> target1.write('''line 1
|
|
... line 2
|
|
... // ###sourceLocation(file: "foo.bar", line: 20)
|
|
... line 4
|
|
... line 5
|
|
... // ###sourceLocation(file: "baz.txt", line: 5)
|
|
... line 7
|
|
... line 8
|
|
... ''')
|
|
>>> target1.flush()
|
|
>>> target2 = NamedTemporaryFile()
|
|
>>> target2.write('''// ###sourceLocation(file: "foo.bar", line: 7)
|
|
... line 2
|
|
... line 3
|
|
... // ###sourceLocation(file: "fox.box", line: 11)
|
|
... line 5
|
|
... line 6
|
|
... ''')
|
|
>>> target2.flush()
|
|
|
|
Simulate the raw output of compilation
|
|
|
|
>>> raw_output = NamedTemporaryFile()
|
|
>>> target1_name, target2_name = target1.name, target2.name
|
|
>>> raw_output.write('''A
|
|
... %(target1_name)s:2:111: error one
|
|
... B
|
|
... %(target1_name)s:4:222: error two
|
|
... C
|
|
... %(target1_name)s:8:333: error three
|
|
... D
|
|
... glitch in file %(target2_name)s:1 assert one
|
|
... E
|
|
... glitch in file %(target2_name)s, line 2 assert two
|
|
... glitch at %(target2_name)s, line 3 assert three
|
|
... glitch at %(target2_name)s:4 assert four
|
|
... glitch in [%(target2_name)s, line 5 assert five
|
|
... glitch in [%(target2_name)s:22 assert six
|
|
... ''' % locals())
|
|
>>> raw_output.flush()
|
|
|
|
Run this tool on the two targets, using a portable version of Unix 'cat' to
|
|
dump the output file.
|
|
|
|
>>> import subprocess
|
|
>>> output = subprocess.check_output([
|
|
... __file__, target1.name, target2.name, '--',
|
|
... sys.executable, '-c',
|
|
... 'import sys;sys.stdout.write(open(sys.argv[1]).read())',
|
|
... raw_output.name])
|
|
|
|
Replace temporary filenames and check it.
|
|
|
|
>>> print output.replace(target1.name,'TARGET1-NAME')
|
|
... .replace(target2.name,'TARGET2-NAME') + 'EOF'
|
|
A
|
|
TARGET1-NAME:2:111: error one
|
|
B
|
|
foo.bar:20:222: error two
|
|
C
|
|
baz.txt:6:333: error three
|
|
D
|
|
glitch in file TARGET2-NAME:1 assert one
|
|
E
|
|
glitch in file foo.bar, line 7 assert two
|
|
glitch at foo.bar, line 8 assert three
|
|
glitch at foo.bar:9 assert four
|
|
glitch in [fox.box, line 11 assert five
|
|
glitch in [fox.box:28 assert six
|
|
EOF
|
|
>>> print subprocess.check_output([__file__, 'foo.bar', '21',
|
|
... target1.name]),
|
|
5
|
|
>>> print subprocess.check_output([__file__, 'foo.bar', '8',
|
|
... target2.name]),
|
|
3
|
|
|
|
"""
|
|
if len(sys.argv) <= 1:
|
|
import doctest
|
|
doctest.testmod()
|
|
elif '--' not in sys.argv:
|
|
source_file = sys.argv[1]
|
|
source_line = int(sys.argv[2])
|
|
target_file = sys.argv[3]
|
|
print(map_line_from_source_file(source_file, source_line, target_file))
|
|
else:
|
|
dashes = sys.argv.index('--')
|
|
sources = expand_response_files(sys.argv[1:dashes])
|
|
|
|
# The first argument of command_args is the process to open.
|
|
# subprocess.Popen doesn't normalize arguments. This means that trying
|
|
# to open a non-normalized file (e.g. C:/swift/./bin/swiftc.exe) on
|
|
# Windows results in file/directory not found errors, as Popen
|
|
# delegates to the Win32 CreateProcess API. Unix systems handle
|
|
# non-normalized paths, so don't have this problem.
|
|
# Arguments passed to the process are normalized by the process.
|
|
command_args = expand_response_files(sys.argv[dashes + 1:])
|
|
command_args[0] = os.path.normpath(command_args[0])
|
|
|
|
command = subprocess.Popen(
|
|
command_args,
|
|
stderr=subprocess.STDOUT,
|
|
stdout=subprocess.PIPE,
|
|
universal_newlines=True
|
|
)
|
|
|
|
sources = '(?P<file>' + '|'.join(re.escape(s) for s in sources) + ')'
|
|
|
|
error_pattern = re.compile(
|
|
'^' + sources +
|
|
':(?P<line>[0-9]+):(?P<column>[0-9]+):;(?P<tail>.*?)\n?$')
|
|
|
|
assertion_pattern = re.compile(
|
|
'^(?P<head>.*( file | at |#[0-9]+: |[[]))' +
|
|
sources +
|
|
'(?P<middle>, line |:)(?P<line>[0-9]+)(?P<tail>.*?)\n?$')
|
|
|
|
while True:
|
|
input = command.stdout.readline()
|
|
if input == '':
|
|
break
|
|
output = input
|
|
|
|
def decode_match(p, l):
|
|
m = p.match(l)
|
|
if m is None:
|
|
return ()
|
|
file, line_num = map_line_to_source_file(
|
|
m.group('file'), int(m.group('line')))
|
|
return ((m, file, line_num),)
|
|
|
|
for (m, file, line_num) in decode_match(error_pattern, input):
|
|
output = '%s:%s:%s:%s\n' % (
|
|
file, line_num, int(m.group(3)), m.group(4))
|
|
break
|
|
else:
|
|
for (m, file, line_num) in decode_match(assertion_pattern,
|
|
input):
|
|
output = '%s%s%s%s%s\n' % (
|
|
m.group('head'), file, m.group('middle'), line_num,
|
|
m.group('tail'))
|
|
sys.stdout.write(output)
|
|
|
|
sys.exit(command.wait())
|
|
|
|
|
|
if __name__ == '__main__':
|
|
run()
|