rust-lang / rust-mode

Emacs configuration for Rust
Apache License 2.0
1.09k stars 176 forks source link

Support multiple binary targets in `rust-run` #471

Open thblt opened 1 year ago

thblt commented 1 year ago

rust-run fails if cargo run needs a --bin argument, which is the case if:

  1. The workspace has multiple binary targets.
  2. No target is listed as the package's default-run

It is probably reasonably easy to support this. A possible interface update to rust-run could simply prompt for a target name iff both conditions above hold (multiple binary target, no default) OR it's called with an argument. The chosen target's name could be saved somewhere so further invocations of rust-run can just reuse it (unless called with an arg)

This is a quick and dirty way to get all binary targets from the current workspace, and the default target:

(let* ((json (with-temp-buffer
               (call-process "cargo" nil (current-buffer) nil
                             "metadata" "--format-version" "1")
               (goto-char (point-min))
               (json-parse-buffer)))
       ;; !! this is a bit dirty. !!
       (members (seq-map (lambda (wm)
                           (car (split-string wm " ")))
                         (gethash "workspace_members" json)))
       (targets
        (-flatten ; from dash.el, but not strictly required.
         (seq-map (lambda (v)
                    (seq-keep
                     (lambda (target)
                       (when (seq-contains-p
                              (gethash "kind" target) "bin" 'string=)
                         (gethash "name" target))) v))
                  (seq-map (lambda (package) (gethash "targets" package))
                           (seq-filter
                            (lambda (pkg) (seq-contains-p members (gethash "name" pkg)))
                            (gethash "packages" json))))))
       (default-run (gethash "default_run"
                             (car
                              (seq-filter
                               (lambda (item) (string= "raoc2021" (gethash "name" item)))
                               (gethash "packages" json))))))
  (message "Workspace members: %s" members)
  (message "Binary targets: %s" targets)
  (message "Default run target is: %s" default-run))

What do you think?

brotzeit commented 1 year ago

I want to avoid complaints from other users. But maybe we can add this as wrapper around cargo-run ? I've seen this kind of request before so other users would certainly also benefit.

thblt commented 1 year ago

Do you mean rust-run? There's no cargo-run defined in my Emacs.

brotzeit commented 1 year ago

Yeah, I mean rust-run.

thblt commented 1 year ago

This is how I've implemented this in my Emacs:

(defvar-local thblt/rust-run-target nil
  "The current binary target, set by `thblt/rust-run'.")

(defun thblt/rust-run (target)
  "Like `rust-run', but prompt for a target if necessary.

The last run target is stored in `thblt/rust-run-target'.  To
force target selection, use a prefix argument."
  ;; Notice this could be generalized to all targets, eg for `rust-build'.
  (interactive
   (list
    (let*
        ((json
          (with-temp-buffer
            (call-process "cargo" nil (current-buffer) nil
                          "metadata" "--format-version" "1")
            (goto-char (point-min))
            (json-parse-buffer :null-object nil)))
         ;; !! this is a bit dirty. !!
         (members
          (seq-map (lambda (wm)
                     (car (split-string wm " ")))
                   (gethash "workspace_members" json)))
         (targets
          (-flatten ; from dash.el, but not strictly required.
           (seq-map (lambda (v)
                      (seq-keep (lambda (target)
                                  (when
                                      (seq-contains-p
                                       ;; Filter out non-binary targets.
                                       (gethash "kind" target) "bin" 'string=)
                                    (gethash "name" target))) v))
                    (seq-map (lambda (package)
                               (gethash "targets" package))
                             (seq-filter
                              (lambda (pkg)
                                (seq-contains-p members
                                                (gethash "name" pkg)))
                              (gethash "packages" json))))))
         (default-run
          (gethash "default_run"
                   (car
                    (seq-filter
                     (lambda (item)
                       ;;(string= "raoc2021" (gethash "name" item))
                       t )
                     (gethash "packages" json))))))
      (or default-run
          (and (not current-prefix-arg)
               (member thblt/rust-run-target targets)
               thblt/rust-run-target)
          (completing-read "Target: " targets)))))
  ;; Save buffers
  (when-let (project-current (project-current))
    (mapc (lambda (buf) (with-current-buffer buf (when (buffer-file-name) (save-buffer))))
          (project-buffers project-current)))
  ;; Compile
  (rust--compile "%s run %s --bin %s" rust-cargo-bin rust-cargo-default-arguments target)
  ;; @TODO Should we still save if this is the default target?
  (when (called-interactively-p 'any) (setq thblt/rust-run-target target)))

This is a draft, of course.

Its main advantage is that it has no visible effect in all cases where rust-run “just works”, ie projects with a single binary target or an explicit default target. The prompt only appears if called with a prefix arg or if the call to cargo run would have failed.

thblt commented 1 year ago

I may have misunderstood which possible complaints you were referring to. My point above is that the change is a noop in all cases where rust-run worked as expected, and does the right thing (IMHO) in other cases.