Friday, February 20, 2009

Ruby script to rebuild Eclipse .classpath

I'm in the process of rebuilding my Tapestry training workshop to use Eclipse instead of IDEA. One part of this is that I use the Maven Ant tasks to handle dependencies from an Ant build file. The end result are folders of JAR files:

lib/runtime
Runtime JARs
lib/runtime-src
Corresponding source JARs (where available)
lib/test
Test-only JARs
lib/test-src
Corresponding source JARs

With Tapestry + Hibernate, that's like 20 JAR files ... way too many to maintain by hand (especially across five similar projects). I mean, the Eclipse API for this is not designed for frequent changes ... I should be able to point Eclipse at a directory and say "take everything you find" (like I can in IDEA).

I tend to chase the Tapestry release, updating the Workshop project as each new Tapestry release is available. I can't be doing this by hand all the time!

What to do, what to do ... ah, a Ruby script! I've actually gotten very rusty with Ruby, so there's probably easier ways to do this, but the basics are there. I love that Ruby has lots of ways of quoting text that aren't intrusive:

#!/usr/bin/ruby
# Rebuild the .classpath file based on the contents of lib/runtime, etc.

# Probably easier using XML Generator but don't have the docs handy

def process_scope(f, scope)
 # Now find the actual JARs and add an entry for each one.
 
 dir = "lib/#{scope}"

 return unless File.exists?(dir)
 
 Dir.entries(dir).select { |name| name =~ /\.jar$/ }.sort.each do |name|
   f.write %{  <classpathentry kind="lib" path="#{dir}/#{name}"}
   
   srcname = dir + "-src/" + name.gsub(/\.jar$/, "-sources.jar") 

   if File.exist?(srcname)
      f.write %{ sourcepath="#{srcname}"}
   end
   
   f.write %{/>\n}
 end
end

File.open(".classpath", "w") do |f|
  f.write %{<?xml version="1.0" encoding="UTF-8"?>
<classpath>
  <classpathentry kind="src" path="src/main/java"/>
  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
}

 process_scope(f, "runtime")
 process_scope(f, "test")
 
 f.write %{
  <classpathentry kind="lib" path="src/main/resources"/>
  <classpathentry kind="output" path="target/classes"/>
</classpath>
}
    
end

I integrated this script into my Ant build file, so that it runs after I pull down dependencies.

Once I get to the new workshop session on testing, I may need to update this to take account of testing (including compiling src/test/java to target/test-classes, etc.). Still, 45 minutes of tweaking (which would have been 10 minutes if I wasn't so rusty) will save me lots of time in the future, dividends payable immediately.

9 comments:

  1. What's wrong with the mvn eclipse:eclipse target?

    ReplyDelete
  2. Hm. I wandered down this path because IDEA really didn't like it if the JARs weren't inside the project, but now I've switched to Eclipse. I suppose I could keep Maven around just to generate the .classpath file, but I generally don't trust it.

    Yep, I'll think about switching over to mvn eclipse:eclipse, it may save me some trouble after all. I just have to make sure I have a M2_REPO variable set up in my Eclipse.

    ReplyDelete
  3. Hey Howard. I know you "really don't like Maven"™ but I have to say, don't use anything to generate the .classpath. Generating .classpaths is horrid, in my experience, especially since it's not auto-synced. Now that 2.0.9 has locked down plugin versions so you can actually get repeatable builds, I have used m2eclipse (the eclipse plugin that supports maven) and I only keep metadata in a pom.xml file. And I no longer have to edit xml, since it has a solid editor that lets you get at the pom contents quite nicely.

    I have now avoided checking in .project/.classpath, or any other ide-specific metadata, because maven support in these IDEs now syncs it automagically. Most importantly, it even lets any maven artifact that can be resolved from an open eclipse project to be so resolved, so your edits in one don't require a "mvn install" to propagate to other projects you have open.

    Perfect? No. But substantially convenient, and I regularly import and play with 40+ subprojects using this infrastructure. These days my only real problem has been that the Jetty runner doesn't work on my 3.4.x eclipse, so I have to "run-as" the pom.xml file as a "mvn jetty:run" build which works surprisingly well.

    ReplyDelete
  4. I think I'm echoing Christian - I actually bounce the exact same Maven project around between all three IDEs now (NetBeans, IntelliJ and Eclipse). Eclipse is the main place I work but I prefer IntelliJs debugger, etc. I don't save any eclipse files to source control. Each IDE either imports or opens the POM file.

    Admittedly, Eclipse wants to do flukey things like send me back to JDK 1.4 so I have a standard block that I copy into my POM files that define things like this. I've not had to download or manage JARs now for the last 6 months since moving to Maven.

    And when an upgrade comes out (to Tapestry), I update the versions in my pom file and all new libraries are automatically downloaded, thrown into the repo and I'm building. It takes a while to work out IDE specific idiosyncrasies, but once you do - it is a great middle ground to move back and forth between IDEs - even if you're not fond of the Maven effort in general.

    ReplyDelete
  5. Ahh ... one other suggestion. I'm sure Groovy would do much of what you're doing in Ruby. Keeping it all in the Java domain might be easier for you - and forcing oneself to start looking into and getting comfortable with Groovy will likely bode well in the future.

    ReplyDelete
  6. There is an eclipse plugin that covers your problem
    http://libcontainer.sourceforge.net/

    ReplyDelete
  7. I use ruby for generating classpaths within a maven project, too. Probably from lack of knowing maven well, but hey [the shaded plugin also helps quite a bit, but when you want to run a test class from the command line...I'm not sure how to do that, so ruby to the rescue.]
    re: groovy
    jruby might work :)

    ReplyDelete
  8. If there's any custom code to be written then GAnt is so the way to go.

    ReplyDelete
  9. my take on the script http://pastie.org/422613

    ReplyDelete

Please note that this is not a support forum for Tapestry. Requests for help will be deleted. Please subscribe to the Tapestry user mailing list if you are in need of support, or contact me directly for professional (for pay) support.

Spammers: Don't bother. I delete your comments and it's a waste of time for both of us. 垃圾邮件发送者:不要打扰。我删除您的评论和它的时间对我们双方的浪费