Skip to content

Commit ae7c147

Browse files
authored
Merge pull request #43 from flycheck/refactor-rust-cargo
Use cargo to find the build target
2 parents f8ae845 + d5affd9 commit ae7c147

File tree

20 files changed

+234
-105
lines changed

20 files changed

+234
-105
lines changed

Cask

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@
22
(source melpa)
33

44
(package-file "flycheck-rust.el")
5+
6+
(development
7+
(depends-on "buttercup") ; BDD test framework for Emacs
8+
(depends-on "rust-mode")
9+
)

Makefile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright (c) 2016 fmdkdd
2+
3+
# This program is free software: you can redistribute it and/or modify it under
4+
# the terms of the GNU General Public License as published by the Free Software
5+
# Foundation, either version 3 of the License, or (at your option) any later
6+
# version.
7+
8+
# This program is distributed in the hope that it will be useful, but WITHOUT
9+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
11+
# details.
12+
13+
# You should have received a copy of the GNU General Public License along with
14+
# this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
CASK = cask
17+
18+
.PHONY: init
19+
init:
20+
$(CASK) install
21+
$(CASK) update
22+
23+
.PHONY: test
24+
test:
25+
$(CASK) exec buttercup -L .

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ Usage
3232

3333
Just use Flycheck as usual in your Rust/Cargo projects.
3434

35-
**Note:** You must run `cargo build` initially to install all dependencies. If
36-
you add new dependencies to `Cargo.toml` you need to run `cargo build` again.
37-
Otherwise you will see spurious errors about missing crates.
38-
3935
License
4036
-------
4137

flycheck-rust.el

Lines changed: 91 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@
3535
;; # Usage
3636
;;
3737
;; Just use Flycheck as usual in your Rust/Cargo projects.
38-
;;
39-
;; Note: You must run `cargo build` initially to install all dependencies. If
40-
;; you add new dependencies to `Cargo.toml` you need to run `cargo build`
41-
;; again. Otherwise you will see spurious errors about missing crates.
4238

4339
;;; Code:
4440

@@ -47,86 +43,107 @@
4743
(require 'seq)
4844
(require 'json)
4945

50-
(defun flycheck-rust-executable-p (rel-name)
51-
"Whether REL-NAME denotes an executable.
52-
53-
REL-NAME is the file relative to the Cargo.toml file."
54-
(or (string= "src/main.rs" rel-name)
55-
(string-prefix-p "src/bin/" rel-name)))
56-
57-
(defun flycheck-rust-test-p (rel-name)
58-
"Whether REL-NAME denotes a test.
59-
60-
REL-NAME is the file relative to the Cargo.toml file."
61-
(string-prefix-p "tests/" rel-name))
62-
63-
(defun flycheck-rust-bench-p (rel-name)
64-
"Whether REL-NAME denotes a bench.
65-
66-
REL-NAME is the file relative to the Cargo.toml file."
67-
(string-prefix-p "benches/" rel-name))
68-
69-
(defun flycheck-rust-example-p (rel-name)
70-
"Whether REL-NAME denotes an example.
71-
72-
REL-NAME is the file relative to the Cargo.toml file."
73-
(string-prefix-p "examples/" rel-name))
46+
(defun flycheck-rust-find-manifest (file-name)
47+
"Get the Cargo.toml manifest for FILE-NAME.
7448
75-
(defun flycheck-rust-project-root ()
76-
"Get the project root for the current buffer.
49+
FILE-NAME is the path of a file in a cargo project given as a
50+
string.
7751
78-
Return the directory containing the Cargo file, or nil if there
79-
is none."
80-
(locate-dominating-file (buffer-file-name) "Cargo.toml"))
52+
See http://doc.crates.io/guide.html for an introduction to the
53+
Cargo.toml manifest.
8154
82-
(defun flycheck-rust-find-crate-root ()
83-
"Get the crate root (the nearest lib.rs or main.rs)
84-
relative to the current file."
85-
(-if-let (lib-crate-dir (locate-dominating-file (buffer-file-name) "lib.rs"))
86-
(expand-file-name "lib.rs" lib-crate-dir)
87-
(-when-let (exe-crate-dir (locate-dominating-file (buffer-file-name) "main.rs"))
88-
(expand-file-name "main.rs" exe-crate-dir))))
55+
Return the path to the Cargo.toml manifest file, or nil if the
56+
manifest could not be located."
57+
(-when-let (root-dir (locate-dominating-file file-name "Cargo.toml"))
58+
(expand-file-name "Cargo.toml" root-dir)))
8959

90-
(defun flycheck-rust-binary-crate-p (project-root)
91-
"Determine whether PROJECT-ROOT is a binary crate.
60+
(defun flycheck-rust-dirs-list (start end)
61+
"Return a list of directories from START (inclusive) to END (exclusive).
9262
93-
PROJECT-ROOT is the path to the root directory of the project.
63+
E.g., if START is '/a/b/c/d' and END is '/a', return the list
64+
'(/a/b/c/d /a/b/c /a/b) in this order.
9465
95-
Return non-nil if PROJECT-ROOT is a binary crate, nil otherwise."
96-
(let ((root-dir (file-name-directory project-root)))
97-
(file-exists-p (expand-file-name "src/main.rs" root-dir))))
66+
START and END are strings representing file paths. END should be
67+
above START in the file hierarchy; if not, the list stops at the
68+
root of the file hierarchy."
69+
(let ((dirlist)
70+
(dir (expand-file-name start))
71+
(end (expand-file-name end)))
72+
(while (not (or (equal dir (car dirlist)) ; avoid infinite loop
73+
(file-equal-p dir end)))
74+
(push dir dirlist)
75+
(setq dir (directory-file-name (file-name-directory dir))))
76+
(nreverse dirlist)))
9877

99-
(defun flycheck-rust-find-target (file-name)
100-
"Find and return the cargo target associated with the given file.
78+
(defun flycheck-rust-get-cargo-targets (manifest)
79+
"Return the list of available Cargo targets for the given project.
10180
102-
FILE-NAME is the name of the file that is matched against the
103-
`src_path' value in the list `targets' returned by `cargo
104-
read-manifest'. If there is no match, the first target is
105-
returned by default.
81+
MANIFEST is the path to the Cargo.toml file of the project.
10682
107-
Return a cons cell (TYPE . NAME), where TYPE is the target
108-
type (lib or bin), and NAME the target name (usually, the crate
109-
name)."
110-
(let ((json-array-type 'list)
111-
(cargo (funcall flycheck-executable-find "cargo")))
83+
Calls `cargo metadata --no-deps --manifest MANIFEST', parses and
84+
collects the targets for the current workspace, and returns them
85+
in a list, or nil if no targets could be found."
86+
(let ((cargo (funcall flycheck-executable-find "cargo")))
11287
(unless cargo
11388
(user-error "flycheck-rust cannot find `cargo'. Please \
11489
make sure that cargo is installed and on your PATH. See \
11590
http://www.flycheck.org/en/latest/user/troubleshooting.html for \
11691
more information on setting your PATH with Emacs."))
117-
(-let [(&alist 'targets targets)
118-
(with-temp-buffer
119-
(call-process cargo nil t nil "read-manifest")
120-
(goto-char (point-min))
121-
(json-read))]
122-
;; If there is a target that matches the file-name exactly, pick that
123-
;; one. Otherwise, just pick the first target.
124-
(-let [(&alist 'kind (kind) 'name name)
125-
(seq-find (lambda (target)
126-
(-let [(&alist 'src_path src_path) target]
127-
(string= file-name src_path)))
128-
targets (car targets))]
129-
(cons kind name)))))
92+
;; metadata contains a list of packages, and each package has a list of
93+
;; targets. We concatenate all targets, regardless of the package.
94+
(when-let ((packages (let-alist
95+
(with-temp-buffer
96+
(call-process cargo nil t nil
97+
"metadata" "--no-deps"
98+
"--manifest-path" manifest)
99+
(goto-char (point-min))
100+
(let ((json-array-type 'list))
101+
(json-read)))
102+
.packages)))
103+
(seq-mapcat (lambda (pkg)
104+
(let-alist pkg .targets))
105+
packages))))
106+
107+
(defun flycheck-rust-find-cargo-target (file-name)
108+
"Return the Cargo build target associated with FILE-NAME.
109+
110+
FILE-NAME is the path of the file that is matched against the
111+
`src_path' value in the list of `targets' returned by `cargo
112+
read-manifest'.
113+
114+
Return a cons cell (KIND . NAME) where KIND is the target
115+
kind (lib, bin, test, example or bench), and NAME the target
116+
name (usually, the crate name). If FILE-NAME exactly matches a
117+
target `src-path', this target is returned. Otherwise, return
118+
the closest matching target, or nil if no targets could be found.
119+
120+
See http://doc.crates.io/manifest.html#the-project-layout for a
121+
description of the conventional Cargo project layout."
122+
(-when-let* ((manifest (flycheck-rust-find-manifest file-name))
123+
(targets (flycheck-rust-get-cargo-targets manifest)))
124+
(let ((target
125+
(or
126+
;; If there is a target that matches the file-name exactly, pick
127+
;; that one
128+
(seq-find (lambda (target)
129+
(let-alist target (string= file-name .src_path)))
130+
targets)
131+
;; Otherwise find the closest matching target by walking up the tree
132+
;; from FILE-NAME and looking for targets in each directory. E.g.,
133+
;; the file 'tests/common/a.rs' will look for a target in
134+
;; 'tests/common', then in 'tests/', etc.
135+
(car (seq-find
136+
(lambda (pair)
137+
(-let [((&alist 'src_path target-path) . dir) pair]
138+
(file-equal-p dir (file-name-directory target-path))))
139+
;; build a list of (target . dir) candidates
140+
(-table-flat
141+
'cons targets
142+
(flycheck-rust-dirs-list file-name
143+
(file-name-directory manifest)))))
144+
;; If all else fails, just pick the first target
145+
(car targets))))
146+
(let-alist target (cons (car .kind) .name)))))
130147

131148
;;;###autoload
132149
(defun flycheck-rust-setup ()
@@ -139,37 +156,10 @@ Flycheck according to the Cargo project layout."
139156
;; with `global-flycheck-mode' it will render Emacs unusable (see
140157
;; https://github.com/flycheck/flycheck-rust/issues/40#issuecomment-253760883).
141158
(with-demoted-errors "Error in flycheck-rust-setup: %S"
142-
(when (buffer-file-name)
143-
(-when-let (root (flycheck-rust-project-root))
144-
(pcase-let ((rel-name (file-relative-name (buffer-file-name) root))
145-
(`(,target-type . ,target-name) (flycheck-rust-find-target
146-
(buffer-file-name))))
147-
;; These are valid crate roots as by Cargo's layout
148-
(if (or (flycheck-rust-executable-p rel-name)
149-
(flycheck-rust-test-p rel-name)
150-
(flycheck-rust-bench-p rel-name)
151-
(flycheck-rust-example-p rel-name)
152-
(string= "src/lib.rs" rel-name))
153-
(setq-local flycheck-rust-crate-root rel-name)
154-
;; For other files, the library is either the default library or the
155-
;; executable
156-
(setq-local flycheck-rust-crate-root (flycheck-rust-find-crate-root)))
157-
;; Check tests in libraries and integration tests
158-
(setq-local flycheck-rust-check-tests
159-
(not (flycheck-rust-executable-p rel-name)))
160-
;; Set the crate type
161-
(setq-local flycheck-rust-crate-type
162-
(if (string= target-type "bin")
163-
(progn
164-
;; If it's binary target, we need to pass the binary
165-
;; name
166-
(setq-local flycheck-rust-binary-name target-name)
167-
"bin")
168-
"lib"))
169-
;; Find build libraries
170-
(setq-local flycheck-rust-library-path
171-
(list (expand-file-name "target/debug" root)
172-
(expand-file-name "target/debug/deps" root))))))))
159+
(-when-let* ((file-name (buffer-file-name))
160+
((kind . name) (flycheck-rust-find-cargo-target file-name)))
161+
(setq-local flycheck-rust-crate-type kind)
162+
(setq-local flycheck-rust-binary-name name))))
173163

174164
(provide 'flycheck-rust)
175165

tests/test-crate/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[package]
2+
name = "test-crate"
3+
version = "0.1.0"

tests/test-crate/benches/a.rs

Whitespace-only changes.

tests/test-crate/benches/b.rs

Whitespace-only changes.

tests/test-crate/benches/support/mod.rs

Whitespace-only changes.

tests/test-crate/examples/a.rs

Whitespace-only changes.

tests/test-crate/examples/b.rs

Whitespace-only changes.

tests/test-crate/examples/support/mod.rs

Whitespace-only changes.

tests/test-crate/src/a.rs

Whitespace-only changes.

tests/test-crate/src/bin/a.rs

Whitespace-only changes.

tests/test-crate/src/bin/b.rs

Whitespace-only changes.

tests/test-crate/src/lib.rs

Whitespace-only changes.

tests/test-crate/src/main.rs

Whitespace-only changes.

tests/test-crate/tests/a.rs

Whitespace-only changes.

tests/test-crate/tests/b.rs

Whitespace-only changes.

tests/test-crate/tests/support/mod.rs

Whitespace-only changes.

tests/test-rust-setup.el

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
;;; test-rust-setup.el -*- lexical-binding: t; -*-
2+
3+
;; Copyright (C) 2016 fmdkdd
4+
5+
;; Author: fmdkdd
6+
7+
;; This file is not part of GNU Emacs.
8+
9+
;; This program is free software; you can redistribute it and/or modify
10+
;; it under the terms of the GNU General Public License as published by
11+
;; the Free Software Foundation, either version 3 of the License, or
12+
;; (at your option) any later version.
13+
14+
;; This program is distributed in the hope that it will be useful,
15+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
;; GNU General Public License for more details.
18+
19+
;; You should have received a copy of the GNU General Public License
20+
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
22+
;;; Commentary:
23+
24+
;; Integration tests for `flycheck-rust-setup'.
25+
26+
;;; Code:
27+
28+
(require 'flycheck-rust)
29+
30+
(buttercup-define-matcher :to-equal-one-of (elt &rest seq)
31+
(if (member elt seq)
32+
(cons t (format "Expected %S not to equal a member of %S" elt seq))
33+
(cons nil (format "Expected %S to equal a member of %S" elt seq))))
34+
35+
(defun crate-file (file-name)
36+
(expand-file-name file-name "tests/test-crate"))
37+
38+
(describe
39+
"`flycheck-rust-find-cargo-target' associates"
40+
41+
(it "'src/lib.rs' to the library target"
42+
(expect
43+
(car (flycheck-rust-find-cargo-target (crate-file "src/lib.rs")))
44+
:to-equal "lib"))
45+
46+
(it "'src/a.rs' to the library target"
47+
(expect
48+
(car (flycheck-rust-find-cargo-target (crate-file "src/a.rs")))
49+
:to-equal "lib"))
50+
51+
(it "'src/main.rs' to the main binary target"
52+
(expect
53+
(flycheck-rust-find-cargo-target (crate-file "src/main.rs"))
54+
:to-equal '("bin" . "test-crate")))
55+
56+
(it "'src/bin/a.rs' to the 'a' binary target"
57+
(expect
58+
(flycheck-rust-find-cargo-target (crate-file "src/bin/a.rs"))
59+
:to-equal '("bin" . "a")))
60+
61+
(it "'src/bin/b.rs' to the 'b' binary target"
62+
(expect
63+
(flycheck-rust-find-cargo-target (crate-file "src/bin/b.rs"))
64+
:to-equal '("bin" . "b")))
65+
66+
(it "'src/bin/support/mod.rs' to the 'b' binary target"
67+
(expect
68+
(flycheck-rust-find-cargo-target (crate-file "src/bin/support/mod.rs"))
69+
:to-equal '("bin" . "b")))
70+
71+
(it "'tests/a.rs' to the 'a' test target"
72+
(expect
73+
(flycheck-rust-find-cargo-target (crate-file "tests/a.rs"))
74+
:to-equal '("test" . "a")))
75+
76+
(it "'tests/support/mod.rs' to any test target"
77+
(expect
78+
(flycheck-rust-find-cargo-target (crate-file "tests/support/mod.rs"))
79+
:to-equal-one-of '("test". "a") '("test". "b")))
80+
81+
(it "'examples/a.rs' to the 'a' example target"
82+
(expect
83+
(flycheck-rust-find-cargo-target (crate-file "examples/a.rs"))
84+
:to-equal '("example" . "a")))
85+
86+
(it "'examples/b.rs' to the 'b' example target"
87+
(expect
88+
(flycheck-rust-find-cargo-target (crate-file "examples/b.rs"))
89+
:to-equal '("example" . "b")))
90+
91+
(it "'examples/support/mod.rs' to any example target"
92+
(expect
93+
(flycheck-rust-find-cargo-target (crate-file "examples/support/mod.rs"))
94+
:to-equal-one-of '("example" . "a") '("example" . "b")))
95+
96+
(it "'benches/a.rs' to the 'a' bench target"
97+
(expect
98+
(flycheck-rust-find-cargo-target (crate-file "benches/a.rs"))
99+
:to-equal '("bench" . "a")))
100+
101+
(it "'benches/b.rs' to the 'b' bench target"
102+
(expect
103+
(flycheck-rust-find-cargo-target (crate-file "benches/b.rs"))
104+
:to-equal '("bench" . "b")))
105+
106+
(it "'benches/support/mod.rs' to any bench target"
107+
(expect
108+
(flycheck-rust-find-cargo-target (crate-file "benches/support/mod.rs"))
109+
:to-equal-one-of '("bench" . "a") '("bench" . "b")))
110+
)

0 commit comments

Comments
 (0)