35
35
; ; # Usage
36
36
; ;
37
37
; ; 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.
42
38
43
39
; ;; Code:
44
40
47
43
(require 'seq )
48
44
(require 'json )
49
45
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.
74
48
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 .
77
51
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.
81
54
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)))
89
59
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) .
92
62
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.
94
65
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)))
98
77
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 .
101
80
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.
106
82
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" )))
112
87
(unless cargo
113
88
(user-error " flycheck-rust cannot find `cargo' . Please \
114
89
make sure that cargo is installed and on your PATH. See \
115
90
http://www.flycheck.org/en/latest/user/troubleshooting.html for \
116
91
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)))))
130
147
131
148
;;;### autoload
132
149
(defun flycheck-rust-setup ()
@@ -139,37 +156,10 @@ Flycheck according to the Cargo project layout."
139
156
; ; with `global-flycheck-mode' it will render Emacs unusable (see
140
157
; ; https://github.com/flycheck/flycheck-rust/issues/40#issuecomment-253760883).
141
158
(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))))
173
163
174
164
(provide 'flycheck-rust )
175
165
0 commit comments