blob: 238073c99e563e70992bcb85fa97dcdb2f12e970 [file] [log] [blame]
#!/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 *
>>> # On Windows, the name of a NamedTemporaryFile cannot be used to open
>>> # the file for a second time if delete=True. Therefore, we have to
>>> # manually handle closing and deleting this file to allow us to open
>>> # the file by its name across all platforms.
>>> t = NamedTemporaryFile(delete=False)
>>> 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)
>>> t.close()
>>> os.remove(t.name)
"""
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 *
>>> # On Windows, the name of a NamedTemporaryFile cannot be used to open
>>> # the file for a second time if delete=True. Therefore, we have to
>>> # manually handle closing and deleting this file to allow us to open
>>> # the file by its name across all platforms.
>>> t = NamedTemporaryFile(delete=False)
>>> 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
>>> t.close()
>>> os.remove(t.name)
"""
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 *
>>> # On Windows, the name of a NamedTemporaryFile cannot be used to open
>>> # the file for a second time if delete=True. Therefore, we have to
>>> # manually handle closing and deleting this file to allow us to open
>>> # the file by its name across all platforms.
>>> target1 = NamedTemporaryFile(delete=False)
>>> 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()
>>> # On Windows, the name of a NamedTemporaryFile cannot be used to open
>>> # the file for a second time if delete=True. Therefore, we have to
>>> # manually handle closing and deleting this file to allow us to open
>>> # the file by its name across all platforms.
>>> target2 = NamedTemporaryFile(delete=False)
>>> 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
>>> # On Windows, the name of a NamedTemporaryFile cannot be used to open
>>> # the file for a second time if delete=True. Therefore, we have to
>>> # manually handle closing and deleting this file to allow us to open
>>> # the file by its name across all platforms.
>>> raw_output = NamedTemporaryFile(delete=False)
>>> 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([sys.executable,
... __file__, target1.name, target2.name, '--',
... sys.executable, '-c',
... 'import sys;sys.stdout.write(open(sys.argv[1]).read())',
... raw_output.name], universal_newlines=True)
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([sys.executable, __file__, 'foo.bar',
... '21', target1.name],
... universal_newlines=True), end='')
5
>>> print(subprocess.check_output([sys.executable, __file__, 'foo.bar',
... '8', target2.name],
... universal_newlines=True), end='')
3
Simulate errors on different line numbers
>>> # On Windows, the name of a NamedTemporaryFile cannot be used to open
>>> # the file for a second time if delete=True. Therefore, we have to
>>> # manually handle closing and deleting this file to allow us to open
>>> # the file by its name across all platforms.
>>> long_output = NamedTemporaryFile(delete=False)
>>> long_output.write('''
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 1)
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 1)
... //===--- Map.swift.gyb - Lazily map over a Sequence -----------*- swif
... //
... // 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 projec
... //
... //===-----------------------------------------------------------------
...
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 20)
...
... /// The `IteratorProtocol` used by `MapSequence` and `MapCollection`.
... /// Produces each element by passing the output of the `Base`
... /// `IteratorProtocol` through a transform function returning
... @_fixed_layout
... public struct LazyMapIterator<
... Base : IteratorProtocol, Element
... > : IteratorProtocol, Sequence {
... /// Advances to the next element and returns it, or `nil` if no
... /// exists.
... ///
... /// Once `nil` has been returned, all subsequent calls return `nil`.
... ///
... /// - Precondition: `next()` has not been applied to a copy.
... /// since the copy was made.
... @_inlineable
... public mutating func next() -> Element? {
... return _base.next().map(_transform)
... }
...
... @_inlineable
... public var base: Base { return _base }
...
... @_versioned
... internal var _base: Base
... @_versioned
... internal let _transform: (Base.Element) -> Element
...
... @_inlineable
... @_versioned
... internal init(_base: Base, _transform: @escaping (Base.Element)
... self._base = _base
... self._transform = _transform
... }
... }
...
... /// A `Sequence` whose elements consist of those in a `Base`
... /// `Sequence` passed through a transform function returning.
... /// These elements are computed lazily, each time they're read, by
... /// calling the transform function on a base element.
... @_fixed_layout
... public struct LazyMapSequence<Base : Sequence, Element>
... : LazySequenceProtocol {
...
... public typealias Elements = LazyMapSequence
...
... /// Returns an iterator over the elements of this sequence.
... ///
... /// - Complexity: O(1).
... @_inlineable
... public func makeIterator() -> LazyMapIterator<Base.Iterator, Element
... return LazyMapIterator(_base: _base.makeIterator(), _transform: _t
... }
...
... /// Returns a value less than or equal to the number of elements in
... /// `self`, **nondestructively**.
... ///
... /// - Complexity: O(*n*)
... @_inlineable
... public var underestimatedCount: Int {
... return _base.underestimatedCount
... }
...
... /// Creates an instance with elements `transform(x)` for each elemen
... /// `x` of base.
... @_inlineable
... @_versioned
... internal init(_base: Base, transform: @escaping (Base.Iterator.Eleme
... self._base = _base
... self._transform = transform
... }
...
... @_versioned
... internal var _base: Base
... @_versioned
... internal let _transform: (Base.Iterator.Element) -> Element
... }
...
... //===--- Collections -------------------------------------------------
...
... // FIXME(ABI)#45 (Conditional Conformance): `LazyMap*Collection` types
... // collapsed into one `LazyMapCollection` using conditional conformanc
... // Maybe even combined with `LazyMapSequence`.
... // rdar://problem/17144340
...
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 108)
...
... /// A `Collection` whose elements consist of those in a `Base`
... /// `Collection` passed through a transform function returning `Elemen
... /// These elements are computed lazily, each time they're read, by
... /// calling the transform function on a base element.
... @_fixed_layout
... public struct LazyMapCollection<
... Base : Collection, Element
... > : LazyCollectionProtocol,
... _CollectionWrapper
... {
... public typealias Base = Base_
... public typealias Index = Base.Index
... public typealias _Element = Base._Element
... public typealias SubSequence = Base.SubSequence
... typealias Indices = Base.Indices
...
... @_inlineable
... public subscript(position: Base.Index) -> Element {
... return _transform(_base[position])
... }
...
... /// Create an instance with elements `transform(x)` for each element
... /// `x` of base.
... @_inlineable
... @_versioned
... internal init(_base: Base, transform: @escaping (Base.Iterator.Eleme
... self._base = _base
... self._transform = transform
... }
...
... @_versioned
... internal var _base: Base
... @_versioned
... internal let _transform: (Base.Iterator.Element) -> Element
... }
...
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 108)
...
... /// A `Collection` whose elements consist of those in a `Base`
... /// `Collection` passed through a transform function returning `Elemen
... /// These elements are computed lazily, each time they're read, by
... /// calling the transform function on a base element.
... @_fixed_layout
... public struct LazyMapBidirectionalCollection<
... Base : BidirectionalCollection, Element
... > : LazyCollectionProtocol,
... _BidirectionalCollectionWrapper
... {
... public typealias Base = Base_
... public typealias Index = Base.Index
... public typealias _Element = Base._Element
... public typealias SubSequence = Base.SubSequence
... typealias Indices = Base.Indices
...
... @_inlineable
... public subscript(position: Base.Index) -> Element {
... return _transform(_base[position])
... }
...
... /// Create an instance with elements `transform(x)` for each element
... /// `x` of base.
... @_inlineable
... @_versioned
... internal init(_base: Base, transform: @escaping (Base.Iterator.Eleme
... self._base = _base
... self._transform = transform
... }
...
... @_versioned
... internal var _base: Base
... @_versioned
... internal let _transform: (Base.Iterator.Element) -> Element
... }
...
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 108)
...
... /// A `Collection` whose elements consist of those in a `Base`
... /// `Collection` passed through a transform function returning `Elemen
... /// These elements are computed lazily, each time they're read, by
... /// calling the transform function on a base element.
... @_fixed_layout
... public struct LazyMapRandomAccessCollection<
... Base : RandomAccessCollection, Element
... > : LazyCollectionProtocol,
... _RandomAccessCollectionWrapper
... {
... public typealias Base = Base_
... public typealias Index = Base.Index
... public typealias _Element = Base._Element
... public typealias SubSequence = Base.SubSequence
... typealias Indices = Base.Indices
...
... @_inlineable
... public subscript(position: Base.Index) -> Element {
... return _transform(_base[position])
... }
...
... /// Create an instance with elements `transform(x)` for each element
... /// `x` of base.
... @_inlineable
... @_versioned
... internal init(_base: Base, transform: @escaping (Base.Iterator.Eleme
... self._base = _base
... self._transform = transform
... }
...
... @_versioned
... internal var _base: Base
... @_versioned
... internal let _transform: (Base.Iterator.Element) -> Element
... }
...
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 146)
...
... //===--- Support for s.lazy ------------------------------------------
...
... extension LazySequenceProtocol {
... /// Returns a `LazyMapSequence` over this `Sequence`. The elements
... /// the result are computed lazily, each time they are read, by
... /// calling `transform` function on a base element.
... @_inlineable
... public func map<U>(
... _ transform: @escaping (Elements.Iterator.Element) -> U
... ) -> LazyMapSequence<Self.Elements, U> {
... return LazyMapSequence(_base: self.elements, transform: transform)
... }
... }
...
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 162)
...
... extension LazyCollectionProtocol
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 169)
... {
... /// Returns a `LazyMapCollection` over this `Collection`. The eleme
... /// the result are computed lazily, each time they are read, by
... /// calling `transform` function on a base element.
... @_inlineable
... public func map<U>(
... _ transform: @escaping (Elements.Iterator.Element) -> U
... ) -> LazyMapCollection<Self.Elements, U> {
... return LazyMapCollection(
... _base: self.elements,
... transform: transform)
... }
... }
...
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 162)
...
... extension LazyCollectionProtocol
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 165)
... where
... Self : BidirectionalCollection,
... Elements : BidirectionalCollection
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 169)
... {
... /// Returns a `LazyMapCollection` over this `Collection`. The eleme
... /// the result are computed lazily, each time they are read, by
... /// calling `transform` function on a base element.
... @_inlineable
... public func map<U>(
... _ transform: @escaping (Elements.Iterator.Element) -> U
... ) -> LazyMapBidirectionalCollection<Self.Elements, U> {
... return LazyMapBidirectionalCollection(
... _base: self.elements,
... transform: transform)
... }
... }
...
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 162)
...
... extension LazyCollectionProtocol
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 165)
... where
... Self : RandomAccessCollection,
... Elements : RandomAccessCollection
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 169)
... {
... /// Returns a `LazyMapCollection` over this `Collection`. The eleme
... /// the result are computed lazily, each time they are read, by
... /// calling `transform` function on a base element.
... @_inlineable
... public func map<U>(
... _ transform: @escaping (Elements.Iterator.Element) -> U
... ) -> LazyMapRandomAccessCollection<Self.Elements, U> {
... return LazyMapRandomAccessCollection(
... _base: self.elements,
... transform: transform)
... }
... }
...
... // ###sourceLocation(file: "/public/core/Map.swift.gyb", line: 184)
...
... @available(*, unavailable, renamed: "LazyMapIterator")
... public struct LazyMapGenerator<Base : IteratorProtocol, Element> {}
...
... extension LazyMapSequence {
... @available(*, unavailable, message: "use '.lazy.map' on the sequence
... public init(_ base: Base, transform: (Base.Iterator.Element) -> Elem
... Builtin.unreachable()
... }
... }
...
... extension LazyMapCollection {
... @available(*, unavailable, message: "use '.lazy.map' on the collecti
... public init(_ base: Base, transform: (Base.Iterator.Element) -> Elem
... Builtin.unreachable()
... }
... }
...
... // Local Variables:
... // eval: (read-only-mode 1)
... // End:
... ''')
>>> long_output.flush()
>>> long_output_result = subprocess.check_output(sys.executable + ' ' +
... __file__ + ' ' + long_output.name + ' -- ' + "echo '" +
... long_output.name + ":112:27: error:'",
... shell=True).rstrip()
>>> print(long_output_result)
/public/core/Map.swift.gyb:117:27: error:
>>> target1.close()
>>> os.remove(target1.name)
>>> target2.close()
>>> os.remove(target2.name)
>>> raw_output.close()
>>> os.remove(raw_output.name)
Lint this file.
>>> import python_lint
>>> python_lint.lint([os.path.realpath(__file__)], verbose=False)
0
"""
if len(sys.argv) <= 1:
import doctest
failure_count, _ = doctest.testmod()
sys.exit(failure_count)
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()