/jrun

Launch Java code from the CLI, installation-free. ☕

Primary LanguageShellThe UnlicenseUnlicense

jrun: painless Java component execution

Summary

Maven is amazing. It manages dependencies so that Java projects become reusable "building blocks" in a much more robust way than many other languages offer. And the Maven Central repository contains a tremendous wealth of code, ripe for reuse in your own projects.

But shockingly, Maven provides no easy way to actually launch code from the beautifully managed dependencies stored so lovingly into ~/.m2/repository.

This project fills that gap: jrun launches Java code. You do not need to download or install any JARs; you just specify an "endpoint" consisting of a Maven artifact identifier, plus a main class if needed/desired, and jrun uses Maven to obtain and run it.

Installation

Just clone this repo, and symlink jrun into your favorite bin directory.

The script uses some common utilities (e.g., cat) as well as mvn and java for the heavy lifting. If you are missing anything, the script will tell you.

Usage

Usage: jrun [-v] [-u] [-U] [-m] <jvm-args> <endpoint> <main-args>

  -v          : verbose mode flag
  -u          : update/regenerate cached environment
  -U          : force update from remote Maven repositories (implies -u)
  -m          : use endpoints for dependency management (see "Details" below)
  <jvm-args>  : any list of arguments to the JVM
  <endpoint>  : the artifact(s) + main class to execute
  <main-args> : any list of arguments to the main class

The endpoint should have one of the following formats:

- groupId:artifactId
- groupId:artifactId:version
- groupId:artifactId:mainClass
- groupId:artifactId:version:mainClass
- groupId:artifactId:version:classifier:mainClass

If version is omitted, then RELEASE is used.
If mainClass is omitted, it is auto-detected.
You can also write part of a class beginning with an @ sign,
and it will be auto-completed.

Multiple artifacts can be concatenated with pluses,
and all of them will be included on the classpath.
However, you should not specify multiple main classes.

Examples

Program Command
Jython REPL jrun org.python:jython-standalone
JRuby eval echo "puts 'Hello Ruby'" | jrun org.jruby:jruby-complete:@jruby.Main
Groovy REPL jrun org.codehaus.groovy:groovy-groovysh:@shell.Main+commons-cli:commons-cli:1.3.1
SciJava REPL with JRuby jrun org.scijava:scijava-common:@ScriptREPL+org.scijava:scripting-jruby
SciJava REPL with Jython jrun org.scijava:scijava-common:@ScriptREPL+org.scijava:scripting-jython
SciJava REPL with Groovy jrun org.scijava:scijava-common:@ScriptREPL+org.scijava:scripting-groovy
SciJava REPL with Clojure jrun org.scijava:scijava-common:@ScriptREPL+org.scijava:scripting-clojure
SciJava REPL with JavaScript jrun org.scijava:scijava-common:@ScriptREPL+org.scijava:scripting-javascript

Note the usage of the + syntax as needed to append elements to the classpath.

FAQ

  • Is it fast? Endpoints are synthesized in a local cache under ~/.jrun. So invoking the same endpoint a second time is really quick.
  • What does "no installation" mean? Classpath elements are hard-linked into ~/.jrun from ~/.m2/repository rather than copied, so the ~/.jrun folder has a tiny footprint even if you execute lots of different endpoints.
  • What if an endpoint has a new version? Pass the -U flag to jrun to rebuild the endpoint. Note that unlike mvn, though, jrun does not check for updates otherwise.
  • Does it work on Windows? It works with Cygwin, Microsoft's Windows Subsystem for Linux, or MinGW (the latter tested via the Git for Windows project).

Configuration

You can configure the behavior of jrun using the $HOME/.jrunrc file.

Repositories

You can define additional remote Maven repositories, from which artifacts will be retrieved. E.g.:

[repositories]
imagej.public = https://maven.imagej.net/content/groups/public

If you need more control over where artifacts come from—for example, if you want to use your own remote Maven repository as a mirror of Maven Central—you can do it using Maven's usual ~/.m2/settings.xml; see Using Mirrors for Repositories.

Shortcuts

You can define shortcuts for launching commonly used programs:

[shortcuts]
repl = imagej:org.scijava.script.ScriptREPL
imagej = net.imagej:imagej
fiji = sc.fiji:fiji:LATEST
scifio = io.scif:scifio-cli

Shortcuts are substituted verbatim from the beginning of the endpoint, single-pass in the order they are defined. So e.g. now you can run:

jrun repl

Note that with the repl shortcut above, the main class (org.scijava.script.ScriptREPL) comes from a different artifact than the toplevel artifact (net.imagej:imagej). This is intentional, so that all of ImageJ, including all of the various SciJava scripting-<foo> plugins, is included in the classpath of the REPL.

Settings

There are a few configurable settings:

[settings]
m2Repo = /path/to/.m2Repo (default ~/.m2/repository)
cacheDir = /path/to/.jrun (default ~/.jrun)
links = soft (options: hard, soft, none; default hard)

Details

Dependency management

Maven has a feature whereby a project can override the versions of transitive (a.k.a. inherited) dependencies, via a <dependencyManagement> configuration. The problem is: a library may then believe it depends on components at particular versions as defined by its <dependencyManagement>, but downstream projects which depend on that library will resolve to different versions. See this SO thread and this gist for full details.

To work around this issue, you can pass -m to jrun, which causes it to add all endpoints to the synthesized POM's <dependencyManagement> section using import scope. By doing this, the versions of transitive dependencies used in the synthesized project should more precisely match those of each endpoint itself—although in the case of multiple endpoints concatenated via the + operator with conflicting dependency management, the earlier endpoints will win because they will be declared earlier in the POM. See also issue #9 in the jrun issue tracker.

Alternatives

There is JPM4J, but it did not work too well for me:

  • It wants to maintain its own local repository of JARs outside of Maven—why? Everyone should use Maven repositories, a thoroughly established standard.

  • For each artifact, you have to choose a single main class as its sole command which gets linked into a shell command that runs it.

  • It does not seem well-synced with Maven Central, and/or does not seem to deal with dependencies in the expected way; e.g.:

    $ jpm install -l -f -m org.scijava.script.ScriptREPL org.scijava:scijava-common
    Errors
      0. Target specifies Class-Path in JAR but the indicated file .../repo/scijava-expression-parser-3.0.0.jar is not found
      1. Target specifies Class-Path in JAR but the indicated file .../repo/gentyref-1.1.0.jar is not found
      2. Target specifies Class-Path in JAR but the indicated file .../repo/eventbus-1.4.jar is not found
    
  • The source is, oddly, part of bnd rather than in the jpm4j organization anywhere, which is not a good sign, modularity-wise.

  • Since April 2017, the web site is offline.

There is also Mop, a more general tool with similar features, but it has not been developed since 2010, and its website is also offline.