3

Where I work we have a shell script that allow us to execute arbitrary Java classes with all the necessary libraries and settings, something like:

#!/bin/sh
$JAVA_HOME/bin/java -cp LONG_LIST_OF_JARS -Xmx6g -XX:MaxPermSize=128m "$@"

And used like so:

javacorp.sh com.mycorp.SomeJob

This is obviously better than needing to specify all the java arguments explicitly every time, but I dislike that it's only manually connected to the jars configured in our Eclipse project for compiling the codebase. I'm working on a personal project and looking to similarly being able to execute arbitrary classes from the command line, and trying to identify how to best provide a consistent java environment.

Currently I'm using Eclipse to run my applications, but I'd like to be able to run them from the command line directly, or on machines that don't have Eclipse installed. In particular, I'd also like to be able to limit the scope of classes/jars that can be executed. For example, javacorp.sh lets us run anything in our src/ directory, and only javacorpunit.sh includes classes in the tests/unit/ directory in the classpath.

  • Is there a clean way to use Ant, Maven, or some other build tool to execute a configured java command at the command line, with minimal boilerplate?
  • Is there any way to hook into the .classpath file Eclipse creates? This doesn't wholly solve my problem (e.g. consistent memory settings) but it'd be nice to use data that already exists.

Edit:

Another way of phrasing my question would be "What's the cleanest way to replicate Eclipse's easy 'Run the current file's main method' button on the command line?"

dimo414
  • 47,227
  • 18
  • 148
  • 244
  • Well, at least one improvement would be to remove LONG_LIST_OF_JARS and replace it with lib/*.jar. I also wouldn't use a single generic script like that, since you're generating it anyways, why not generate a proper script for every application (i.e. not specify the com.mycorp.MyApplication). – Kayaman Oct 18 '13 at 06:20
  • `lib/*.jar` is better, to be sure, but it's still tedious to type out, and not necessarily what I want (say there's a library that's only supposed to be used by certain tools). As for generating a proper script for every application, that seems like overkill for my use-case, they'd all be exactly identical except for the name of the class I want to run; additionally I'd like to minimize the cost of creating or running a new class, similar to how Eclipse lets you hit "Play" as soon as you've defined a main method, and runs it with all the configured jars/settings. – dimo414 Oct 18 '13 at 06:23
  • I fail to see a proper neeed for this. You want convenience scripts, although you could just hit "Run" in Eclipse. If you intend to run it on other environments, that's when you should create a proper script anyways. Seems like this script would be useful only when you don't want to fire up Eclipse (or are in a headless environment)? – Kayaman Oct 18 '13 at 06:26
  • Easy example, I want to pipe the output of a Java program I wrote in Eclipse which is dependent on several libraries and settings to a command. This is a need I (and my coworkers) have regularly, and I'm trying to determine if there's a "good" way to do it. – dimo414 Oct 18 '13 at 13:09
  • Then I'll still have to stick to the "write a proper script" solution. It won't take long and you only need to do it once. Maybe you'll end up writing almost identical scripts, but that's hardly an issue in the real world. – Kayaman Oct 18 '13 at 13:13
  • I definitely disagree with that, [DRY](http://en.wikipedia.org/wiki/Don%27t_Repeat_Yourself) is a core principle of a good design, and in particular creating dozens or potentially hundreds of duplicate scripts is a recipe for trouble. Implementing one wrapper script, as I describe above, is a better solution as it avoids the needless duplication you're suggesting, but is still manually hooked into the codebase. I'm looking for any options that might be more powerful/integrated. – dimo414 Oct 18 '13 at 16:43
  • I've noticed that people take the DRY principle to heart so much that they become totally oblivious to other glaring errors in their architecture. They're so afraid of repeating a single line, that they'll come up with convoluted "solutions" to "fix" it. Besides, you'll be generating the scripts anyways so it's still a non-issue. – Kayaman Oct 18 '13 at 17:05
  • I don't see a single benefit to what you're describing, how is running `MyApplication.sh --opt1 arg1` in any meaningful way better than `javacorp.sh com.mycorp.MyApplication --opt 1 arg1`? I see absolutely no point to replicating this behavior in dozens of files. I want to be able to execute arbitrary Java programs in a consistent environment. Why mandate that each class be wrapped up in a script before it can be run? In any case, if you really feel like what you're describing is the right approach, feel free to post it as an answer. – dimo414 Oct 18 '13 at 18:08
  • Well, a clear advantage is that you don't need to guess which is the main class name, so it's completely encapsulated. You also don't need to add libraries that aren't needed into the script. But I don't know what kind of environment you're working in, maybe my approach doesn't make sense in your case. I'm just having a hard time understanding why you absolutely insist on having a single generic script for several different Java applications. – Kayaman Oct 18 '13 at 18:13
  • Aside from tab-complete, it's no easier to guess a script name than a class name. It's convenient to be able to execute a class, for testing, or a small job, or whatever reason you'd like. It's necessary to do so with the appropriate libraries, otherwise the execution will fail. Therefore, I'm looking for the most robust way to ensure that any class I want to run can be run in the same environment it was built in. – dimo414 Oct 18 '13 at 18:21
  • It's pretty easy to guess a script name when it's the only script in the application. But your environment sounds weird. Like you've bundled several runnable classes into a single project. If that's the case, I guess your original javacorp.sh will be your solution. What kind of software development are you doing? – Kayaman Oct 18 '13 at 18:36
  • That really doesn't seem very strange to me, different classes serve different purposes, they should have different entry points. There are a few primary entry points, including a webserver and a backend API server, which are already managed by dedicated scripts, but that doesn't mean there aren't other jobs that need to be run or created regularly. Those scripts though would similarly benefit by calling into a consistent `java` execution script. – dimo414 Oct 18 '13 at 18:47
  • Ah. See, if my company were to work with this, the webserver and the backend API server and all other distinct entities would be separate Maven modules (or even projects), instead of all being clumped into a single project. But I suppose your codebase is quite old if you're using *make* as your build tool. – Kayaman Oct 18 '13 at 18:52
  • So in order to run a new `main()` method in your company's codebase you need to create a new Maven module? – dimo414 Oct 18 '13 at 18:57
  • I'm starting to sense that your programs are quite a bit smaller than what we work with... I'm talking about in the order of 10 KLOCs. The main methods have already been written, and I'd write one only if I needed to do a quick test in eclipse or something. How many main classes do you have? – Kayaman Oct 18 '13 at 19:02
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/39519/discussion-between-kayaman-and-dimo414) – Kayaman Oct 18 '13 at 19:04

3 Answers3

2

Have you considered generating the shell scripts from Ant? The <pathconvert> task can create a classpath from a <fileset>.

You can create an Ant target for each kind of shell script you want. make can then call these Ant targets to generate the shell scripts.

Chad Nouis
  • 6,861
  • 1
  • 27
  • 28
1

I think the solution to your problem is to create an executable jar. Something that can be run as follows:

java -jar myapp.jar

"look ma, no classpath" :-)

The secret is add the "Main-Class" and "Class-Path" entries into the jar's manifest file. This tells java what to run and which jars should be loaded onto the classpath:

<jar destfile="${jar.file}" basedir="${classes.dir}">
    <manifest>
        <attribute name="Main-Class" value="${jar.main.class}" />
        <attribute name="Class-Path" value="${jar.classpath}" />
    </manifest>
</jar>

To assist in creating the classpath, ANT has a really useful manifestclasspath task:

<manifestclasspath property="jar.classpath" jarfile="${jar.file}">
    <classpath>
        <fileset dir="${dist.dir}/lib" includes="*.jar"/>
    </classpath>
</manifestclasspath>

So using this example, at run-time java will expect the depedencies to reside in a "lib" subdirectory (the task will generate relative links).

Eclipse integration is tricky. A better approach for managing your classpath is to use a dependency manager like ivy, which has an Eclipse plugin. In this way both ANT and Eclipse use the same mechanism for controlling build dependencies.

Finally for a complete working example take a look at:

Hope this helps.

Community
  • 1
  • 1
Mark O'Connor
  • 76,015
  • 10
  • 139
  • 185
0

With Google's Bazel build system, you can easily define as many runnable Java programs as you want, with the java_binary target. You can define as many java_binary targets as you'd like, and they can all depend on the same set (or overlapping sets) of java_library targets so you don't need to manually curate the classpath of each binary.

Additionally, with a java_binary target you can also generate a *_deploy.jar file, which is a standalone executable Jar you can run anywhere.

dimo414
  • 47,227
  • 18
  • 148
  • 244