2

I would like to be a able to get Eclipse to ignore one Gradle project, and instead use a pre-built version of it.

Background

I have a project "parser" written in Scala, and a dozen others written in Java. The weakest link in my tool-set is Scala IDE. I use this plugin to edit & compile Scala code, but unfortunately it breaks the Java (JDT) tooling quite badly in mixed-language projects*.

  • Specifically: Call-hierarchy is missing results, searches crash and so on. Also Scala IDE appears to have lost funding and the issues sound fairly fundamental, so I'm not holding my breath for these issues to be fixed.

With Maven (m2e) I had a workaround I was quite happy with:

  1. Build as a .jar put into my local .m2 repository:

    cd parser; mvn install
    
  2. In Eclipse, close the "parser" project

"Like magic", m2e simply picked up the most recent 'installed' .jar and used it in place of the closed project.

An awesome answer would be how to get Gradle to do that!

However all I wish for is any solution that meets these...

Requirements

  1. That I can open Project parser when necessary (which is seldom), to edit and build changes via the Gradle command-line. I will close it when done.
  2. Other projects use the built .jar from my local .m2 repo. (It's fine if they always do so.)
  3. The change must not affect others who don't use Eclipse
  4. (ideally) the change can be used by other Eclipse users

Approaches

A similar question had this good answer by @lance-java with a number of general suggestions. I think I can rule out these ideas:

Something along the lines of lance-java's idea #4 sounds viable. Paraphrasing...

  • "use the eclipse plugin [in conjunction with] Buildship, e.g. using the whenMerged hook to tweak the generated .classpath [of all the Java projects]."

    UPDATE: [18 Apr]: I had hit a brick wall in this approach. Buildship was not putting the built .jar onto the runtime classpath. (UPDATE 2: Now resolved - see my answer.)

Questions

The main question: How can I structure a solution to this, that will actually work & avoid any major pitfalls?

Note that the project itself has a few dependencies, specifically:

dependencies {
  compile 'org.scala-lang:scala-library:2.12.4'
  compileOnly  'com.google.code.findbugs:jsr305:1.3.9'
  antlr 'org.antlr:antlr4:4.5.3'
}

So a sub-question may be: How to pull these in into the other projects without duplicating the definition? (If that doesn't work automatically.)

Luke Usherwood
  • 3,082
  • 1
  • 28
  • 35
  • Just to be clear, you would never user `parser` as a project dependency by only as a regular one, picked on a local .m2 repo ? – ToYonos Apr 17 '18 at 11:08
  • That's correct, I want to delete the reference to project `parser` (which Gradle adds by default) and replace it with something like `parser-1.0.0-SNAPSHOT.jar`. – Luke Usherwood Apr 17 '18 at 15:45
  • UPDATE: I just found a [big clue](https://discuss.gradle.org/t/a-jar-added-to-the-eclipse-classpath-is-missing-at-runtime/26612/2?u=lukeu) that trying to include .jars from the .m2 directory might be problematic. Maybe I'll have to avoid `maven-publish` and/or copy the jar elsewhere. – Luke Usherwood Apr 17 '18 at 15:45
  • I will try a proper answer tomorrow but I thing it's not that complicated (if I'm understanding correctly) – ToYonos Apr 17 '18 at 20:35

2 Answers2

1

So the solution was a bit involved. After adding 'maven-publish' to create the library, I then implemented the following to force Eclipse to use the prebuilt library:

subprojects {
    // Additional configuration to manipulate the Eclipse classpaths
    configurations {
        parserSubstitution
    }
    dependencies {
        parserSubstitution module("com.example:parser:${project.version}")
    }

    apply plugin: 'eclipse'
    eclipse {
        classpath {
        plusConfigurations += [ configurations.pseLangSubstitution ]
        file {
            whenMerged { cp ->

                // Get Gradle to add the depedency upon
                // parser-xxx.jar via 'plusConfigurations' above.
                // Then this here if we have a dependency on Project(':parser')
                //  - If so, remove it (completing the project -> jar substitution).
                //  - If not, remove the .jar dependency: it wasn't needed.
                def usesParser = entries.removeAll {
                    it instanceof ProjectDependency && it.path.startsWith('/parser')
                }
                def parserJar =
                    cp.entries.find { it instanceof Library && it.path.contains('parser-') }
                if (usesParser) {
                    // This trick stops Buildship deleting it from the runtime classpath
                    parserJar ?. entryAttributes ?. remove("gradle_used_by_scope")
                } else {
                    cp.entries.remove { parserJar }
                }
            }
        }
    }

So there are 2 parts to this:

  1. Using 'plusConfigurations' felt a bit round-about. I ended up doing this because I could not see how to construct class Library classpath entries directly. However it could well be that this is required to implement the 'transient dependencies' correctly anyway. (See the end of the question.)
  2. The trick to stop Buildship removing the .jar from the runtime classpath (thus deviating from a Gradle command-line launch) was provided to me by a Gradle developer in this discussion.

Usage

The solution works just as I hoped. Every time some code in this library is modified, I execute the following task of mine on the command line (which also does some other code & resource generation steps, in addition to building the parser jar):

./gradlew generateEclipse

Then in Eclipse I press keyboard shortcuts for "Gradle -> Refresh Gradle Projects", Build.

And harmony is restored. :-)

  • Navigating to the (prebuilt) source of parser works.
  • If I need to edit the source, I can open the parser project and edit it. Scala-IDE still does a good job for this.
  • When I'm done I execute the command, close the project and my Java tools are happy.
Luke Usherwood
  • 3,082
  • 1
  • 28
  • 35
0

In parser project

You shoud use the maven-publish plugin with the publishToMavenLocal task

apply plugin: 'maven-publish'

group = 'your.company'
version = '1.0.0'

publishing {    
    publications {
        mavenJava(MavenPublication) {
            from components.java

            pom.withXml {
                def root = asNode()
                root.appendNode('name', 'Your parser project name')
                root.appendNode('description', 'Your parser project description')
            }
        }
    }
}

Everytime you make a modification, just change the version number if necessary and go with gradle publishToMavenLocal

In other java project using parser

Just use parser as a regular dependency :

repositories {
    mavenLocal()
    ...
}

compile 'your.company:parser:1.0.0'

If my understanding of your situation is good, it should do the trick.

ToYonos
  • 16,469
  • 2
  • 54
  • 70
  • Thanks for this! I read this to be a gradle-wide change, which would affect everyone (including those using IntelliJ or vi & doing command-line builds). Hence requirement 3 (and optional 4) - as I'm ideally looking for a fix that only patches up the IDE problems for Eclipse users. So if I understand this correctly, it has the downside that it would require other team-members to do this extra step also, right? – Luke Usherwood Apr 18 '18 at 10:09
  • Also note my 2nd previous comment yesterday: I found a [big clue](https://discuss.gradle.org/t/a-jar-added-to-the-eclipse-classpath-is-missing-at-runtime/26612/2?u=lukeu) about the "brick wall" I was hitting. I think Buildship is stripping out any jars inside my .m2 repo from the runtime classpath (presumably to avoid nasty interactions between Buildship and m2e (eclipse-gradle and eclipse-maven) when they are both installed. So with that in mind, my current thinking is: maybe just add a basic copy-task of the built .jar to "publish" it, and add an `eclipse { }` block to reference it? – Luke Usherwood Apr 18 '18 at 10:13
  • *The change must not affect others who don't use Eclipse* But the source code is versioned isn't it ? So it should work out of the box whatever the ide – ToYonos Apr 18 '18 at 10:13
  • Yes, it's in git. The normal workflow for others would typically just be `gradlew build`. Would this command still work if they edited the `parser` module themselves, or `git pull` somebody's else's changes that modified it? – Luke Usherwood Apr 18 '18 at 10:17
  • With the above solution, every user must make sure a fresh jar of `parser` is available within the local .m2 repo, so after a pull or a local modification of the project, a `gradle publishToMavenLocal` is necessary – ToYonos Apr 18 '18 at 10:21
  • The Buildship behaviour can be an issue though. Your "copy/paste" idea can work but it's becoming quite specific. – ToYonos Apr 18 '18 at 10:24
  • I am a Buildship user, i just created a dummy project, installed it in my local .m2 repo, used it another project and it both compiles and runs from Eclipse. You should give it a shot (I am not using maven at all though) – ToYonos Apr 18 '18 at 10:50
  • I've awarded the bounty as a partial answer, the other part was solved with the help of a Buildship developer (under the 'brick wall' link). I'll try and put together a complete answer here soon (although I'm going to be travelling all the coming week.) – Luke Usherwood Apr 22 '18 at 09:23