Skip to content
This repository has been archived by the owner on Aug 30, 2022. It is now read-only.

Plugin with Leiningen 2 doesn't pull in dependencies correctly. #11

Closed
dkincaid opened this issue Jun 5, 2012 · 30 comments
Closed

Plugin with Leiningen 2 doesn't pull in dependencies correctly. #11

dkincaid opened this issue Jun 5, 2012 · 30 comments

Comments

@dkincaid
Copy link

dkincaid commented Jun 5, 2012

I'm having several issues using this plugin. The first is that for some reason it is adding modules into a project that shouldn't be there. For example, I have two separate projects in /src/project1 and /src/project2. Each has its own project.clj and .iml files. When I open project1 Leiningen adds project2 as a module inside of project1. Really weird. Both projects are listed in the Leiningen window.

The second problem is that IntelliJ can't find any of the dependencies. It seems that "lein deps" doesn't copy the dependency jars into /lib in version 2 any longer.

Very, very frustrated with this, so any advice would be much appreciated. I'd much rather use Leiningen than go back to Maven.

@derkork
Copy link
Owner

derkork commented Jun 6, 2012

Hi Dave,

In general, two projects shouldn't share the same source folder. The standard layout used by Leiningen (and Maven) is 1 folder per project with a src folder inside.Also IntelliJ itself works on the assumption that you have 1 project with n modules - so you can't have two projects open in the same window. I'd suggest the following folder layout:

your-project (the main project, actually just an aggregator):

  • project1 (a module below the project)
    • src
    • project.clj
  • project2 (another module below the project)
    • src
    • project.clj
  • project.clj

Have a look at the ring project: https://github.com/mmcgrana/ring they also have a project with several sub-projects.

If for whatever reason you cannot adhere to the default structure or if you still have the issue when using the default structure, it would help me a lot if you could provide a small sample project showing your issue. Also, can you tell me, which version of Leiningen you are using?

Hope this helps.

Kind regards,
Jan

@dkincaid
Copy link
Author

dkincaid commented Jun 6, 2012

The first issue (with multiple modules being created) was some sort of problem with the IntelliJ project files. I deleted the .iml file and the .idea/ directory and recreated the project and that problem is gone now.

The real problem is with the dependencies. I'm using Leiningen 2.0preview6. This version no longer copies the jar files into the lib/ directory, so IntelliJ can't find them.

As a workaround I used Leiningen 1 (lein1 deps) to copy the dependencies into lib/ and set that up as a library in my project.

@derkork
Copy link
Owner

derkork commented Jun 8, 2012

Ok, thanks for keeping me updated. As far as I can see Leiningen 2 uses the .m2 repo. I'll see if I can modify the plugin so it looks for dependencies there and links them into the libraries section. That will probably introduce a dependency to the maven plugin but since that comes bundled with IntelliJ it shouldn't be too much of an issue.

@64BitChris
Copy link
Contributor

Leiningen 2 uses Pomegranate at the core of its dependency management. Would it be possible to call the get-classpath function from pomegranate.clj and add those to the module?

I took a look at the IntelliJ plugin documentation, and it's not very clear when it comes to this use case. A maven project seems to update the module file (and write it out) when the pom changes. It would be nice to emulate this feature with the project.clj. Also, we'll have to keep in mind the new profiles when we're looking for :dependencies as profiles can change the classpath. All of this functionality exists for Maven, but I can't find much of the source in order to attempt to emulate it and I don't know the IntelliJ APIs well enough to take a good guess as to where I should start.

@derkork
Copy link
Owner

derkork commented Jul 23, 2012

I'm not sure if this "read the classpath and add it to the project" would actually work, I assume leiningen itself pulls some dependencies that are not necessarily parts of the project dependencies and one would have to filter these out with a more or less hacky heuristic. The cleanest way IMHO would be to call lein deps, which will download all the necessary deps into ~/.m2 and then collect the jars from there using the information from the project.clj file and add them to IDEAs module dependencies.

Profiles are a different topic I think as they would probably require some larger refactoring on the plugin. In fact you need a way to enable/disable plugins on the UI and then the plugin needs to take all the rules into account to build the resulting dependency tree. I'm actually not too happy that leiningen introduced profiles as it turns more and more into maven with all it's complexity.

I have a few hours to spare this week, so let's see if I can come up with a solution for the dependencies first and tackle the profile thing afterwards.

@64BitChris
Copy link
Contributor

Sounds great, thanks Jan!!

@derkork
Copy link
Owner

derkork commented Jul 24, 2012

This all just got a whole level more complicated. The magic word is "transitive dependencies". With Leiningen 1 that is not a problem, just import the whole lib folder and that's it. With Leiningen 2, I have to do the dependency resolution in the plugin. And it should better yield the same results as Leiningen itself. So I guess I have to use pomegranate for this. Sadly, my Clojure is not that good, so that is going to take a while...

@64BitChris
Copy link
Contributor

What functions do you need? If they're pure functions, I could help write the interactions with pomegranate. If you know the inputs/outputs, I can toy with pomegrantate to make an interface that you can leverage.

@derkork
Copy link
Owner

derkork commented Jul 26, 2012

Well let me write it in Java-notation, before I make a fool of myself :):

/**
   Fetches all direct and transitive dependencies of the given artifact
 */
List<Artifact> getDependencies(Artifact a)

where Artifact is something like:

class Artifact {
    public String groupId;
    public String artifactId;
    public String version;
    public String type;
    public String classifier;
    public String scope;
}

So maybe you could add a new class to the leiningen-interop.jar where I can access such a function from Java and which does the pomegranate magic. Once I got the artifacts, I can easily grab them from the local m2 repo and add them to the module dependencies with correct scope. Meanwhile I get to brush up on my clojure :D

@dkincaid
Copy link
Author

I edited the title of this issue to better reflect what the issue is

@technomancy
Copy link

For Leiningen 2 we've spun out the functionality you need for this into the separate leiningen-core library that you should be able to call directly in-process rather than running the deps task:

https://clojars.org/leiningen-core

http://leiningen.org/reference.html

That's what the Leiningen support in CounterClockwise uses. You should be able to pull in that library and set the classpath using leiningen.core.classpath/get-classpath after reading the project file with leiningen.core.project/read. If you have any trouble with this please drop us a line on the Leiningen mailing list.

@derkork
Copy link
Owner

derkork commented Jul 27, 2012

Sounds cool I'll check it out tonight :) Thank you!

@lrenn
Copy link

lrenn commented Aug 2, 2012

In the meantime you can just run lein pom, then setup the maven facet. It'll setup the classpath just fine.

If you just want a full string of the classpath, you can also run the lein classpath target which will output it as a string. It includes everything leiningen would use.

@derkork
Copy link
Owner

derkork commented Aug 8, 2012

@technomancy no luck. Everytime I add something like

(use 'leiningen.core.project)

to my clojure file I get a: java.lang.ClassNotFoundException: clojure.lang.ILookupHost

Same happens when I just do lein repl and do the (use .. ) there. I have not the slightest idea what I might be doing wrong.

@skuro
Copy link

skuro commented Aug 9, 2012

It seems a Clojure version mismatch problem. clojure.lang.ILookupHost existed in v1.2 of the language but it's not there since v1.3. Any chance you can post the relevant stack trace to try and see who's trying to reach that class?

@skuro
Copy link

skuro commented Aug 9, 2012

I just tried to run the plugin configuration, and it failed with the same exception upon opening a project. Updating the clojure version in the plugin project.clj to clojure 1.4.0 made it work.

EDIT: just FYI, ILookupHost is looked up on LeiningenProjectFile class construction

@derkork
Copy link
Owner

derkork commented Aug 10, 2012

Hmm, still no joy. I have updated the dependency to 1.4.0:

(defproject de.janthomae/leiningenplugin "1.1.0-SNAPSHOT"
  :description "IntelliJ plugin for controlling Leiningen"
  :dependencies [[org.clojure/clojure "1.4.0"]
                  [leiningen-core "2.0.0-SNAPSHOT"]]
  :plugins [[lein-pprint "1.1.1"]]
  :dev-dependencies [[junit/junit "4.8.2"]]
  :source-paths ["src"]
  :resource-paths ["lein-resources"]
  :compile-path "classes"
  :aot :all
;  :disable-implicit-clean true
  :omit-source true
  :target-dir "out"
  :jar-name "leiningen-interop.jar")

Still getting an exception:


Compiling de.janthomae.leiningenplugin.leiningen.LeiningenProjectFile
Exception in thread "main" java.lang.NoClassDefFoundError: clojure/lang/ILookupHost, compiling:(project.clj:1)
    at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3387)
    at clojure.lang.Compiler.compile1(Compiler.java:7035)
    at clojure.lang.Compiler.compile1(Compiler.java:7025)
    at clojure.lang.Compiler.compile(Compiler.java:7097)
    at clojure.lang.RT.compile(RT.java:387)
    at clojure.lang.RT.load(RT.java:427)
    at clojure.lang.RT.load(RT.java:400)
    at clojure.core$load$fn__4890.invoke(core.clj:5415)
    at clojure.core$load.doInvoke(core.clj:5414)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5227)
    at clojure.core$load_lib.doInvoke(core.clj:5264)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:603)
    at clojure.core$load_libs.doInvoke(core.clj:5298)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:605)
    at clojure.core$use.doInvoke(core.clj:5392)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3382)
    at clojure.lang.Compiler.compile1(Compiler.java:7035)
    at clojure.lang.Compiler.compile(Compiler.java:7097)
    at clojure.lang.RT.compile(RT.java:387)
    at clojure.lang.RT.load(RT.java:427)
    at clojure.lang.RT.load(RT.java:400)
    at clojure.core$load$fn__4890.invoke(core.clj:5415)
    at clojure.core$load.doInvoke(core.clj:5414)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5227)
    at clojure.core$compile$fn__4895.invoke(core.clj:5426)
    at clojure.core$compile.invoke(core.clj:5425)
    at user$eval7.invoke(NO_SOURCE_FILE:1)
    at clojure.lang.Compiler.eval(Compiler.java:6511)
    at clojure.lang.Compiler.eval(Compiler.java:6501)
    at clojure.lang.Compiler.eval(Compiler.java:6477)
    at clojure.core$eval.invoke(core.clj:2797)
    at clojure.main$eval_opt.invoke(main.clj:297)
    at clojure.main$initialize.invoke(main.clj:316)
    at clojure.main$null_opt.invoke(main.clj:349)
    at clojure.main$main.doInvoke(main.clj:427)
    at clojure.lang.RestFn.invoke(RestFn.java:421)
    at clojure.lang.Var.invoke(Var.java:419)
    at clojure.lang.AFn.applyToHelper(AFn.java:163)
    at clojure.lang.Var.applyTo(Var.java:532)
    at clojure.main.main(main.java:37)
Caused by: java.lang.NoClassDefFoundError: clojure/lang/ILookupHost
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
    at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    at cemerick.pomegranate.aether__init.load(Unknown Source)
    at cemerick.pomegranate.aether__init.<clinit>(Unknown Source)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:247)
    at clojure.lang.RT.loadClassForName(RT.java:2056)
    at clojure.lang.RT.load(RT.java:419)
    at clojure.lang.RT.load(RT.java:400)
    at clojure.core$load$fn__4890.invoke(core.clj:5415)
    at clojure.core$load.doInvoke(core.clj:5414)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5227)
    at clojure.core$load_lib.doInvoke(core.clj:5264)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:603)
    at clojure.core$load_libs.doInvoke(core.clj:5298)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:603)
    at clojure.core$require.doInvoke(core.clj:5381)
    at clojure.lang.RestFn.invoke(RestFn.java:421)
    at cemerick.pomegranate$loading__4414__auto__.invoke(pomegranate.clj:1)
    at cemerick.pomegranate__init.load(Unknown Source)
    at cemerick.pomegranate__init.<clinit>(Unknown Source)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:247)
    at clojure.lang.RT.loadClassForName(RT.java:2056)
    at clojure.lang.RT.load(RT.java:419)
    at clojure.lang.RT.load(RT.java:400)
    at clojure.core$load$fn__4890.invoke(core.clj:5415)
    at clojure.core$load.doInvoke(core.clj:5414)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5227)
    at clojure.core$load_lib.doInvoke(core.clj:5264)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:603)
    at clojure.core$load_libs.doInvoke(core.clj:5298)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:603)
    at clojure.core$require.doInvoke(core.clj:5381)
    at clojure.lang.RestFn.invoke(RestFn.java:619)
    at leiningen.core.project$loading__4784__auto__.invoke(project.clj:1)
    at clojure.lang.AFn.applyToHelper(AFn.java:159)
    at clojure.lang.AFn.applyTo(AFn.java:151)
    at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3382)
    ... 44 more
Caused by: java.lang.ClassNotFoundException: clojure.lang.ILookupHost
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    ... 99 more
Compilation failed.

@64BitChris
Copy link
Contributor

If you replace the clojure.jar (or clojure-1.2.0.jar) in the plugin directory with the clojure 1.4 jar this should take care of this problem.

@derkork
Copy link
Owner

derkork commented Aug 10, 2012

@64BitChris I'm running this from a shell not using the Intellij plugin (just to make sure it's not a bug in the IntelliJ-plugin). I have deleted the lib folder which had an old clojure.jar inside but still the same exception.

@derkork
Copy link
Owner

derkork commented Aug 14, 2012

Adding a :eval-in :leiningen to the project file seemed to do the trick. I have no idea why this is necessary but I can live with that as long as it works.

@64BitChris
Copy link
Contributor

I'm trying to update this plugin to work against Lein 2. However, there are some very fundamental differences in the architecture from Lein 1 and Lein 2 - at least in the approach to how the plugin interacts with leiningen. In the current version of the plugin, the interaction is made through calls to Leiningen and thus needs to know the lein home, lein bat file, and lein path. With Lein 2, we can programmatically configure a project using the leiningen-core library. I've tested this and validated that I can create a project this way.

I've created a :gen-class to front the behavior to the leiningen-core library.

The problem I'm having is that when I try to call my generated class from inside the plugin I get a FileNotFound exception saying that it can't load clojure/core or clojure/core_init from the classpath. The odd part about this is that the clojure.lang.RT.load function is on the stack as well as other clojure.lang.RT. RT and core are in the same jar, it's finding RT but not core and I can't figure out why.

When I access the clojure.core_init.class.getClassLoader() from the debugger, I get an exception (the FileNotFound exception) the first time, but if I evaluate it again, it returns the PluginClassLoader, which I think it should do at the beginning.

Does anyone have any ideas as to why the IDEA plugin system is having problems with finding the clojure core classes? I have clojure.jar in my plugin lib directory and it gets deployed to the sandbox-plugins directory.

If I can get past this problem, I won't be far from being able to upgrade this plugin to support leiningen 2.

Thanks!

@derkork
Copy link
Owner

derkork commented Nov 12, 2012

Well this is where I hit the wall as well. I have no clue why the classes cannot be found especially they are clearly within the classpath. I also tried some manual invocations using the debugger (e.g. stopping a line before the clojure interop part and doing a class.forname myself which yielded a result). Seems like clojure is pulling off some weird magic inside but I'm by no means a clojure expert, so maybe someone with experience in clojure classloading could help here.

@64BitChris
Copy link
Contributor

Okay, I finally figured it out.

If I switch the current thread's classloader to the Leiningen Plugin Classloader and THEN call the Clojure libraries, everything is okay.

Thread.currentThread().setContextClassLoader(LeiningenConstants.class.getClassLoader());

Where LeiningenConstants can be anything that is specific to the Leiningen Plugin Classpath.

I made this call before using the clojure libraries and everything works. The current thread in this context is the AWT-EventQueue thread, and the plugin's classloader doesn't have a parent set, so something in IntelliJ must be switching it back as everything else seems to work.

Anyway, this resolves the issue, but it makes me wonder if there's something in the plugin I have to do to tell the thread to switch to the plugin classloader and then switch back.

Jan, are you doing any active work that this helps you fix? I'm basically trying to add IDEA Module support - so I can go to the 'Add Module' dialog and then add a leiningen module. Since I'd have to introspect the dependencies, I decided to look at upgrading so that the leiningen plugin would use lein 2.0. I discussed this in #clojure and they suggested only supporting 2.0 instead of 1.7 and 2.0 (people could always use earlier versions for 1.7 support). I just want to make sure we don't do duplicate work, so if you don't have plans to do anything with this for a while, then I think we're all good :)

@derkork
Copy link
Owner

derkork commented Nov 13, 2012

I am trying to get the dependencies when working with Leiningen 2, so that you can just load up the leiningen project and have all dependencies satisfied and no red code in your project. From what I read you are trying the same. I'd suggest that you send me a pull request once you got the dependency resolution working, so I can build on this when importing the project into IntelliJ. You could then continue to work on the module support. Another priority is IDEA 12 support, which will probably require a branch as several APIs have been changed.

@64BitChris
Copy link
Contributor

Sounds good, I'll try to get the dependencies pulled in via leiningen-core with the current plugin and then submit the pull request to you. Probably won't get much focused time until this weekend.

Do we want to follow the La Clojure plugin's approach by having a branch for 11 and 12? We can create two branches now that point at the current head and then let them diverge from there.

Do you have a good link to a place that documents the IDEA Plugin APIs and the changes from 11 to 12? I'm using the Maven Plugin's source code to figure out what needs to be done and have to figure out what each component does.

Ultimately, I'd like to have the Leiningen Plugin be on par with the Maven plugin, in that they both do similar things, just with different underlying technologies. I think that level of support will make IDEA one of the top platforms for building Clojure apps. I can help with a lot of this as I have some extra time before the end of the year.

@derkork
Copy link
Owner

derkork commented Nov 16, 2012

No link except this one http://confluence.jetbrains.net/display/IDEADEV/PluginDevelopment . But it's rather dated. I got most of my knowledge from reverse engineering other stuff, mainly the Maven plugin.

Regarding the branch, we should definitely have one for IDEA 11 and one for IDEA 12. I'm not so fond of the idea of dropping support for Leiningen 1, but I have no data about what's in use right now. If we drop support for Leiningen 1 with IDEA 12 that also means that if you want to do a Leiningen 1 project, you have to go back to IDEA 11, because the older plugin versions that still support Leiningen 1 won't work on IDEA 12, as they have changed the API. Maybe we should go the way of the maven plugin here and support both versions (such as the Maven plugin supports Maven 2 & 3). That is unless you got a source that Leiningen 1 is no longer in use. If we decide to support both versions of Leiningen I'd rather wait with the branch until we got basic Leiningen 2 support up and running, so we don't have to do a ton of merges back and forth to backport Leiningen 2 functionality to IDEA 11.

I like your vision of making the plugin on par with the Maven plugin :)

@64BitChris
Copy link
Contributor

I just noticed that IDEA 12 has been released and wanted to post that I've got the integration with leiningen.core almost complete. It will pull in the transitive dependencies as well and list them just like the maven plugin (except displayed more leiningen-ish). I will be submitting a pull request in the next couple days, with the latest being before the end of the weekend.

@64BitChris
Copy link
Contributor

Okay, completed this and submitted pull request: #14

@64BitChris
Copy link
Contributor

Can we close this one now?

@derkork
Copy link
Owner

derkork commented Dec 20, 2012

Yup. Just uploaded a new version to the plugin repo which contains your fix. Again thank you :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants