Skip to content

Update message handling for Rust 1.24. #235

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.cache
target/
Cargo.lock
12 changes: 8 additions & 4 deletions SyntaxCheckPlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ class RustSyntaxCheckThread(rust_thread.RustThread, rust_proc.ProcListener):
view = None
# Absolute path to the view that triggered the check.
triggered_file_name = None
# Directory of `triggered_file_name`.
# Directory where cargo will be run.
cwd = None
# Base path for relative paths in messages.
msg_rel_path = None
# This flag is used to terminate early. In situations where we can't
# auto-detect the appropriate Cargo target, we compile multiple targets.
# If we receive any messages for the current view, we might as well stop.
Expand Down Expand Up @@ -125,7 +127,8 @@ def get_rustc_messages(self):
# Clippy does not support cargo target filters, must be run for
# all targets.
cmd = settings.get_command(method, command_info, self.cwd,
force_json=True)
self.cwd, force_json=True)
self.msg_rel_path = cmd['msg_rel_path']
p = rust_proc.RustProc()
p.run(self.window, cmd['command'], self.cwd, self, env=cmd['env'])
p.wait()
Expand All @@ -135,9 +138,10 @@ def get_rustc_messages(self):
td = target_detect.TargetDetector(self.window)
targets = td.determine_targets(self.triggered_file_name)
for (target_src, target_args) in targets:
cmd = settings.get_command(method, command_info, self.cwd,
cmd = settings.get_command(method, command_info, self.cwd, self.cwd,
initial_settings={'target': ' '.join(target_args)},
force_json=True)
self.msg_rel_path = cmd['msg_rel_path']
if method == 'no-trans':
cmd['command'].extend(['--', '-Zno-trans', '-Zunstable-options'])
if (util.get_setting('rust_syntax_checking_include_tests', True) and
Expand Down Expand Up @@ -173,7 +177,7 @@ def on_error(self, proc, message):
print('Rust Error: %s' % message)

def on_json(self, proc, obj):
messages.add_rust_messages(self.window, self.cwd, obj,
messages.add_rust_messages(self.window, self.msg_rel_path, obj,
self.current_target_src, msg_cb=None)
if messages.has_message_for_path(self.window,
self.triggered_file_name):
Expand Down
6 changes: 4 additions & 2 deletions cargo_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,15 @@ def run(self):
cmd = self.settings.get_command(self.command_name,
self.command_info,
self.settings_path,
self.working_dir,
self.initial_settings)
if not cmd:
return
messages.clear_messages(self.window)
p = rust_proc.RustProc()
listener = opanel.OutputListener(self.window, self.working_dir,
self.command_name)
listener = opanel.OutputListener(self.window, cmd['msg_rel_path'],
self.command_name,
cmd['rustc_version'])
decode_json = util.get_setting('show_errors_inline', True) and \
self.command_info.get('allows_json', False)
try:
Expand Down
54 changes: 12 additions & 42 deletions rust/cargo_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import sublime
import sublime_plugin
from .cargo_settings import CargoSettings, CARGO_COMMANDS
from .util import index_with
from .util import index_with, get_cargo_metadata
from . import rust_proc, util

# Keep track of recent choices to set the default value.
Expand Down Expand Up @@ -54,7 +54,7 @@ class CargoConfigBase(sublime_plugin.WindowCommand):

# Dictionary of choices passed into the command, instead of using
# interactive UI.
input = None
cmd_input = None

# Sequence of questions to ask.
sequence = None
Expand Down Expand Up @@ -90,7 +90,7 @@ def run(self, **kwargs):
self.sequence_index = 0
# Copy, since WindowCommand reuses objects.
self._sequence = self.sequence[:]
self.input = kwargs
self.cmd_input = kwargs
self.settings = CargoSettings(self.window)
self.settings.load()
self.show_next_question()
Expand Down Expand Up @@ -123,8 +123,8 @@ def make_choice(value):
self._sequence[i:i] = next
self.show_next_question()

if q in self.input:
make_choice(self.input[q])
if q in self.cmd_input:
make_choice(self.cmd_input[q])
else:
try:
item_info = getattr(self, 'items_' + q)()
Expand Down Expand Up @@ -286,11 +286,11 @@ def items_which(self):
"""Choice to select at which level the setting should be saved at."""
# This is a bit of a hack so that when called programmatically you
# don't have to specify 'which'.
if 'which' not in self.input:
if 'variant' in self.input:
self.input['which'] = 'project_package_variant'
elif 'target' in self.input:
self.input['which'] = 'project_package_target'
if 'which' not in self.cmd_input:
if 'variant' in self.cmd_input:
self.cmd_input['which'] = 'project_package_variant'
elif 'target' in self.cmd_input:
self.cmd_input['which'] = 'project_package_target'

variant_extra = 'cargo build, cargo run, cargo test, etc.'
target_extra = '--bin, --example, --test, etc.'
Expand Down Expand Up @@ -615,9 +615,9 @@ def done(self):
// Enter environment variables here in JSON syntax.
// Close this view when done to commit the settings.
""")
if 'contents' in self.input:
if 'contents' in self.cmd_input:
# Used when parsing fails to attempt to edit again.
template = self.input['contents']
template = self.cmd_input['contents']
elif default:
template += sublime.encode_value(default, True)
else:
Expand Down Expand Up @@ -948,33 +948,3 @@ def _stock_build_system(self):
pkg_name = __name__.split('.')[0]
resource = 'Packages/%s/RustEnhanced.sublime-build' % pkg_name
return sublime.decode_value(sublime.load_resource(resource))


def get_cargo_metadata(window, cwd):
"""Load Cargo metadata.

:returns: None on failure, otherwise a dictionary from Cargo:
- packages: List of packages:
- name
- manifest_path: Path to Cargo.toml.
- targets: List of target dictionaries:
- name: Name of target.
- src_path: Path of top-level source file. May be a
relative path.
- kind: List of kinds. May contain multiple entries if
`crate-type` specifies multiple values in Cargo.toml.
Lots of different types of values:
- Libraries: 'lib', 'rlib', 'dylib', 'cdylib', 'staticlib',
'proc-macro'
- Executables: 'bin', 'test', 'example', 'bench'
- build.rs: 'custom-build'

:raises ProcessTermiantedError: Process was terminated by another thread.
"""
output = rust_proc.slurp_json(window,
'cargo metadata --no-deps'.split(),
cwd=cwd)
if output:
return output[0]
else:
return None
26 changes: 24 additions & 2 deletions rust/cargo_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,22 +350,29 @@ def get_merged(self, settings_path, variant, target, key,
return result

def get_command(self, cmd_name, cmd_info,
settings_path, initial_settings={}, force_json=False):
settings_path, working_dir,
initial_settings={}, force_json=False):
"""Generates the command arguments for running Cargo.

:param cmd_name: The name of the command, the key used to select a
"variant".
:param cmd_info: Dictionary from `CARGO_COMMANDS` with rules on how to
construct the command.
:param settings_path: The absolute path to the Cargo project root
directory.
directory or script.
:param working_dir: The directory where Cargo is to be run (typically
the project root).
:keyword initial_settings: Initial settings to inject which override
all other settings.
:keyword force_json: If True, will force JSON output.

:Returns: A dictionary with the keys:
- `command`: The command to run as a list of strings.
- `env`: Dictionary of environment variables (or None).
- `msg_rel_path`: The root path to use for relative paths in
messages.
- `rustc_version`: The version of rustc being used as a string,
such as '1.25.0-nightly'.

Returns None if the command cannot be constructed.
"""
Expand Down Expand Up @@ -453,7 +460,22 @@ def expand(s):
if not env:
env = None

# Determine the base path for paths in messages.
#
# Starting in Rust 1.24, all messages and symbols are relative to the
# workspace root instead of the package root.
metadata = util.get_cargo_metadata(self.window, working_dir, toolchain)
if metadata and 'workspace_root' in metadata:
# 'workspace_root' key added in 1.24.
msg_rel_path = metadata['workspace_root']
else:
msg_rel_path = working_dir

rustc_version = util.get_rustc_version(self.window, working_dir, toolchain=toolchain)

return {
'command': result,
'env': env,
'msg_rel_path': msg_rel_path,
'rustc_version': rustc_version,
}
12 changes: 6 additions & 6 deletions rust/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,11 +636,11 @@ def on_highlighted(idx):
window.show_quick_panel(panel_items, on_done, 0, 0, on_highlighted)


def add_rust_messages(window, cwd, info, target_path, msg_cb):
def add_rust_messages(window, base_path, info, target_path, msg_cb):
"""Add messages from Rust JSON to Sublime views.

- `window`: Sublime Window object.
- `cwd`: Directory where cargo/rustc was run.
- `base_path`: Base path used for resolving relative paths from Rust.
- `info`: Dictionary of messages from rustc or cargo.
- `target_path`: Absolute path to the top-level source file of the target
(lib.rs, main.rs, etc.). May be None if it is not known.
Expand Down Expand Up @@ -684,7 +684,7 @@ def add_rust_messages(window, cwd, info, target_path, msg_cb):
# List of message dictionaries, belonging to the main message.
additional_messages = []

_collect_rust_messages(window, cwd, info, target_path, msg_cb, {},
_collect_rust_messages(window, base_path, info, target_path, msg_cb, {},
main_message, additional_messages)

messages = _create_cross_links(main_message, additional_messages)
Expand Down Expand Up @@ -750,7 +750,7 @@ def escape_and_link(i_txt):
False, None, content, None)


def _collect_rust_messages(window, cwd, info, target_path,
def _collect_rust_messages(window, base_path, info, target_path,
msg_cb, parent_info,
main_message, additional_messages):
"""
Expand Down Expand Up @@ -831,7 +831,7 @@ def _collect_rust_messages(window, cwd, info, target_path,
return

def make_span_path(span):
return os.path.realpath(os.path.join(cwd, span['file_name']))
return os.path.realpath(os.path.join(base_path, span['file_name']))

def make_span_region(span):
# Sublime text is 0 based whilst the line/column info from
Expand Down Expand Up @@ -981,7 +981,7 @@ def find_span_r(span, expansion=None):

# Recurse into children (which typically hold notes).
for child in info['children']:
_collect_rust_messages(window, cwd, child, target_path,
_collect_rust_messages(window, base_path, child, target_path,
msg_cb, parent_info.copy(),
main_message, additional_messages)

Expand Down
14 changes: 9 additions & 5 deletions rust/opanel.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

import os
import re
from . import rust_proc, messages, util
from . import rust_proc, messages, util, semver

# Use the same panel name that Sublime's build system uses so that "Show Build
# Results" will open the same panel. I don't see any particular reason why
# this would be a problem. If it is, it's a simple matter of changing this.
PANEL_NAME = 'exec'


def create_output_panel(window, cwd):
def create_output_panel(window, base_dir):
output_view = window.create_output_panel(PANEL_NAME)
s = output_view.settings()
if util.get_setting('show_errors_inline', True):
Expand All @@ -25,7 +25,7 @@ def create_output_panel(window, cwd):
pattern = '(?|%s|%s)' % (build_pattern, test_pattern)
s.set('result_file_regex', pattern)
# Used for resolving relative paths.
s.set('result_base_dir', cwd)
s.set('result_base_dir', base_dir)
s.set('word_wrap', True) # XXX Or False?
s.set('line_numbers', False)
s.set('gutter', False)
Expand Down Expand Up @@ -58,10 +58,11 @@ class OutputListener(rust_proc.ProcListener):
# Sublime view used for output.
output_view = None

def __init__(self, window, base_path, command_name):
def __init__(self, window, base_path, command_name, rustc_version):
self.window = window
self.base_path = base_path
self.command_name = command_name
self.rustc_version = rustc_version

def on_begin(self, proc):
self.output_view = create_output_panel(self.window, self.base_path)
Expand All @@ -87,6 +88,9 @@ def on_data(self, proc, data):
lineno = int(m.group(2)) - 1
# Region columns appear to the left, so this is +1.
col = int(m.group(3))
# Rust 1.24 changed column numbering to be 1-based.
if semver.match(self.rustc_version, '>=1.24.0-beta'):
col -= 1
span = ((lineno, col), (lineno, col))
# +2 to skip ", "
build_region = sublime.Region(region_start + m.start() + 2,
Expand All @@ -105,7 +109,7 @@ def on_error(self, proc, message):

def on_json(self, proc, obj):
if 'message' in obj:
messages.add_rust_messages(self.window, proc.cwd, obj['message'],
messages.add_rust_messages(self.window, self.base_path, obj['message'],
None, self.msg_cb)

def msg_cb(self, message):
Expand Down
35 changes: 35 additions & 0 deletions rust/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,38 @@ def active_view_is_rust(window=None, view=None):
if not view.file_name():
return False
return 'source.rust' in view.scope_name(0)


def get_cargo_metadata(window, cwd, toolchain=None):
"""Load Cargo metadata.

:returns: None on failure, otherwise a dictionary from Cargo:
- packages: List of packages:
- name
- manifest_path: Path to Cargo.toml.
- targets: List of target dictionaries:
- name: Name of target.
- src_path: Path of top-level source file. May be a
relative path.
- kind: List of kinds. May contain multiple entries if
`crate-type` specifies multiple values in Cargo.toml.
Lots of different types of values:
- Libraries: 'lib', 'rlib', 'dylib', 'cdylib', 'staticlib',
'proc-macro'
- Executables: 'bin', 'test', 'example', 'bench'
- build.rs: 'custom-build'

:raises ProcessTermiantedError: Process was terminated by another thread.
"""
from . import rust_proc
cmd = ['cargo']
if toolchain:
cmd.append('+' + toolchain)
cmd.extend(['metadata', '--no-deps'])
output = rust_proc.slurp_json(window,
cmd,
cwd=cwd)
if output:
return output[0]
else:
return None
11 changes: 0 additions & 11 deletions tests/error-tests/Cargo.lock

This file was deleted.

4 changes: 0 additions & 4 deletions tests/error-tests/dcrate/Cargo.lock

This file was deleted.

3 changes: 2 additions & 1 deletion tests/error-tests/tests/arg-count-mismatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
// error-pattern: parameters were supplied

/*BEGIN*/fn f(x: isize) {
// ^^^^^^^^^^^^^^ERR(>=1.24.0-beta) defined here
}/*END*/
// ~ERR defined here
// ~ERR(<1.24.0-beta) defined here

// children without spans, spans with no labels
// Should display error (with link) and a note of expected type.
Expand Down
Loading