Skip to content
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

FileNotFoundException when running cljr-clean-ns #380

Closed
jeremyheiler opened this issue Jun 7, 2017 · 7 comments
Closed

FileNotFoundException when running cljr-clean-ns #380

jeremyheiler opened this issue Jun 7, 2017 · 7 comments

Comments

@jeremyheiler
Copy link

Expected behavior

I expect cljr-clean-ns to run without error.

Actual behavior

cljr--get-error-value: Error in nrepl-refactor: java.io.FileNotFoundException: story.clj (No such file or directory)
 at java.io.FileInputStream.open0 (FileInputStream.java:-2)
    java.io.FileInputStream.open (FileInputStream.java:195)
    java.io.FileInputStream.<init> (FileInputStream.java:138)
    java.io.FileInputStream.<init> (FileInputStream.java:93)
    java.io.FileReader.<init> (FileReader.java:58)
    sun.reflect.NativeConstructorAccessorImpl.newInstance0 (NativeConstructorAccessorImpl.java:-2)
    sun.reflect.NativeConstructorAccessorImpl.newInstance (NativeConstructorAccessorImpl.java:62)
    sun.reflect.DelegatingConstructorAccessorImpl.newInstance (DelegatingConstructorAccessorImpl.java:45)
    java.lang.reflect.Constructor.newInstance (Constructor.java:423)
    clojure.lang.Reflector.invokeConstructor (Reflector.java:180)
    refactor_nrepl.core$read_ns_form.invokeStatic (core.clj:105)
    refactor_nrepl.core$read_ns_form.invoke (core.clj:105)
    refactor_nrepl.core$clj_file_QMARK_.invokeStatic (core.clj:144)
    refactor_nrepl.core$clj_file_QMARK_.invoke (core.clj:139)
    clojure.core$some_fn$sp3__7168.invoke (core.clj:7163)
    refactor_nrepl.core$source_file_QMARK_.invokeStatic (core.clj:152)
    refactor_nrepl.core$source_file_QMARK_.invoke (core.clj:146)
    refactor_nrepl.ns.clean_ns$clean_ns.invokeStatic (clean_ns.clj:39)
    refactor_nrepl.ns.clean_ns$clean_ns.invoke (clean_ns.clj:38)
    refactor_nrepl.middleware$clean_ns_reply.invokeStatic (middleware.clj:101)
    refactor_nrepl.middleware$clean_ns_reply.invoke (middleware.clj:100)
    refactor_nrepl.middleware$wrap_refactor$fn__67377.invoke (middleware.clj:159)
    clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__47224.invoke (middleware.clj:22)
    cider.nrepl.middleware.enlighten$wrap_enlighten$fn__52858.invoke (enlighten.clj:86)
    clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__47224.invoke (middleware.clj:22)
    cider.nrepl.middleware.apropos$wrap_apropos$fn__50503.invoke (apropos.clj:91)
    clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__47224.invoke (middleware.clj:22)
    cider.nrepl.middleware.pprint$wrap_pprint_fn$fn__49605.invoke (pprint.clj:50)
    clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__47224.invoke (middleware.clj:22)
    clojure.tools.nrepl.middleware.session$session$fn__47559.invoke (session.clj:192)
    clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__47224.invoke (middleware.clj:22)
    clojure.tools.nrepl.server$handle_STAR_.invokeStatic (server.clj:19)
    clojure.tools.nrepl.server$handle_STAR_.invoke (server.clj:16)
    clojure.tools.nrepl.server$handle$fn__47632.invoke (server.clj:28)
    clojure.core$binding_conveyor_fn$fn__4676.invoke (core.clj:1938)
    clojure.lang.AFn.call (AFn.java:18)
    java.util.concurrent.FutureTask.run (FutureTask.java:266)
    java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1142)
    java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:617)
    java.lang.Thread.run (Thread.java:745)

Steps to reproduce the problem

  1. Run cider-jack-in for a project
  2. Open a namespace file in the project
  3. Run M-x cljr-clean-ns on that buffer

Environment & Version information

GNU Emacs 25.2.1 (x86_64-apple-darwin16.5.0, NS appkit-1504.82 Version 10.12.4 (Build 16E195)) of 2017-05-06

;; CIDER 0.14.0 (Berlin), nREPL 0.2.12
;; Clojure 1.8.0, Java 1.8.0_74

clj-refactor 2.3.1, refactor-nrepl 2.3.1

Leiningen 2.6.1 on Java 1.8.0_74 Java HotSpot(TM) 64-Bit Server VM

macOS 10.12
@benedekfazekas
Copy link
Member

i checked with the same version of cljr on a project of mine, worked fine. this only means that there is no general regression in this feature in 2.3.1.

your buffer needs to be backed by an actual file visible for the nrepl middleware part of the project. any chance it is not the case?

also can you please share further specifics of your project? any chance you can share a link to the project itself?

@jeremyheiler
Copy link
Author

Thanks for checking in on this.

I'm not able to share the project with you, sorry. It is a standard lein project, with a file in the src directory. The namespace matches the directory structure (and there is no -/_ confusion). I've attempted to load the file with both C-c C-k and C-c C-l, but cljr-clean-ns still does not work.

The message of the exception is suspicious to me:

java.io.FileNotFoundException: story.clj (No such file or directory)

Typically, FileNotFoundException will show the full path of the file it cannot find.

user> (java.io.FileInputStream. "/foo/foo")
FileNotFoundException /foo/foo (No such file or directory)  java.io.FileInputStream.open0 (FileInputStream.java:-2)

Perhaps the full path isn't being sent to nrepl?

@expez
Copy link
Member

expez commented Jun 8, 2017

You can check what's being sent to the middleware by checking the *nrepl-messages* buffer. Hopefully that shows the right path.

There's nothing else special with your setup? Everything is local? E.g. no docker containers, tramp connections to a remote server etc?

I use clean-ns every day and it works fine, so I'm trying to determine what's different in your situation.

@jeremyheiler
Copy link
Author

Hmm... I don't have a *nrepl-messages* buffer. Everything is local.

I added some log messages to cljr--clean-ns and found this:

(defun cljr--clean-ns (&optional path no-prune?)
  "If PATH is passed use that instead of the path to the current buffer

If NO-PRUNE is passed, the default is overridden and unused stuff isn't \
removed."
  ;; Don't save prematurely when called from `cljr-project-clean'
  (unless (and *cljr--noninteractive*
               (not (buffer-modified-p)))
    (save-buffer))
  (message "path")
  (message path)
  (message "buffer-file-name")
  (message (buffer-file-name))
  (message "cljr proj dir")
  (message (cljr--project-dir))
  (message "cljr proj rel path")
  (message (cljr--project-relative-path (buffer-file-name)))
  (let ((path (or path (cljr--project-relative-path (buffer-file-name)))))
    (message "after path")
    (message path)
    (when-let (new-ns (cljr--call-middleware-sync
		       (cljr--create-msg "clean-ns"
					 "path" path
					 "libspec-whitelist" cljr-libspec-whitelist
					 "prune-ns-form" (if no-prune? "false"
							   "true"))
		       "ns"))
      (cljr--replace-ns new-ns))
    (unless *cljr--noninteractive*
      (cljr--post-command-message "Namespace form cleaned!"))))
path
buffer-file-name
/Users/jeremy/clubhouse/src/server/server/src/clubhouse/models/story.clj
cljr proj dir
/Users/jeremy/clubhouse/src/server/server/src/clubhouse/models/
cljr proj rel path
story.clj
after path
story.clj

It seems to think that the project directory is the directory of the Clojure file I'm trying to clean.

Ah hah!

Looking at cljr--project-dir, it tests to see if the directory is the project root if it finds (among other thigns) a project.clj. And yes, there is a project.clj in that directory, but it's not a lein project.clj.

Ugh, that is frustrating. I wonder if there's a better way to implement cljr--project-dir?

@expez
Copy link
Member

expez commented Jun 9, 2017

Ah, damn!

Yeah, that function is probably a bit too naive. I suppose the next step would be to just peek into the file and ensure a defproject form is in there.

Hmm... I don't have a *nrepl-messages* buffer.

Sorry about that, it's invisible by default, but you can toggle it with (setq nrepl-log-messages t), if you're ever in this situation again.

FWIW, the relative path stuff was added to fix #286, so clean-ns would work in a Vagrant setup. To get on with your day you can just send down the absolute path, instead of the project relative one, until this one is patched properly.

Thanks for taking the time to dig into this yourself! 👍

@plexus
Copy link
Contributor

plexus commented Apr 18, 2019

I'm also running into this, in a situation where I'm editing a file that's part of a different project, but that's also on the classpath (e.g. when a code base is split across multiple repos). I imagine this scenario will become ever more common, as tools.deps makes it so easy with :local/root.

The easiest way to fix this is to pass the absolute rather than the relative path, so to revert the "fix" for #286, which is essentially about making things work when running nREPL on a remote machine.

Neither of these approaches is totally correct, because they both make assumptions that aren't always true.

  • The original code made the assumption that Emacs and the JVM share the same filesystem
  • The fix for cljr-clean-ns with vagrant #286 makes the assumption that the project directory is the same directory as the current working directory of the JVM (possibly mounted elsewhere).

A better approach could be to determine from the Emacs side which namespace the user is editing, and then on the JVM side looking up the corresponding file on the classpath.

This also makes an assumption, namely that if you're editing a file that declares (ns foo.bar.baz), that that is also the file that would be loaded by (:require 'foo.bar.baz), which also won't always be true, but it seems a reasonable assumption.

To really make this foolproof you could try in order

  1. find the current namespace on the classpath
  2. if not found, or if it turns out to be in a jar rather than a regular file, then try the absolute path
  3. if still not found, try the relative path.

Looking at it this way, I think for starters doing 2 & 3 would already fix a lot cases. Happy to put together a PR for this.


The current behaviour assumes that the project root (the first parent directory to contain ("project.clj" "build.boot" "pom.xml" "deps.edn")), is also the current directory of the JVM process. I.e. that if you obtain a relative path with

(cljr--project-relative-path filename)

And then pass the result to the JVM and do

(io/file relative-path)

and that the result is again the original file.

plexus added a commit to plexus/refactor-nrepl that referenced this issue Apr 18, 2019
Allow the client to send both the absolute path and a relative path to a file,
we use the first one that we can find.

The absolute path is used when the client (Emacs) and the JVM are on the same
filesystem/same machine. The second is used when they are not on the same
filesystem (e.g. running nREPL on a remote machine or in a VM), but the JVM runs
in the (mounted) project directory, so that relative file lookups still work.

See clojure-emacs/clj-refactor.el#380 (comment)
plexus added a commit that referenced this issue Apr 24, 2019
@vemv
Copy link
Member

vemv commented Mar 15, 2022

I'm pretty certain I fixed this one in refactor-nrepl at some point

@vemv vemv closed this as completed Mar 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants