5

I worked through a simple example using Project Jigsaw in Java 11.0.1, built using the oracle JDK 11 on Ubuntu 18.04.

Following that example, I have created a simple project which compiles to a module, packages the module into a jar, and then uses jlink to create a standalone distribution. Everything works -- the end result is a smallish folder with a stripped down JRE and my module.

The project is made of only three files and some folders:

.:
build.sh  src

./src:
com  module-info.java

./src/com:
greetings

./src/com/greetings:
Main.java

src/com/greetings/Main.java

package com.greetings;

public class Main {
   public static void main(String[] args) {
      System.out.println("Greetings!");
   }
}

src/module-info.java

module com.greetings { }

build.sh

#/bin/bash

#clean up from last build
rm -rf greetingsapp mlib mods

#compile
javac -d mods/com.greetings src/module-info.java src/com/greetings/Main.java

#Make module jar
mkdir mlib
jar --create --file=mlib/com.greetings.jar --main-class=com.greetings.Main -C mods/com.greetings .

#build distribution
jlink --module-path /usr/lib/jvm/java-11-oracle/jmods/:mlib --add-modules com.greetings --output greetingsapp --strip-debug --no-header-files --no-man-pages --launcher greetings=com.greetings

#run
greetingsapp/bin/greetings

All of that works. Now here's the problem:

The next thing I want to do is use an external library, so I added a few lines to Main.java:

Main.java - Updated

package com.greetings;

import org.apache.commons.cli.CommandLine; //new line

public class Main {

   CommandLine line; //new line

   public static void main(String[] args) {
      System.out.println("Greetings!");
   }
}

I then placed commons-cli-1.4.jar in a new directory named lib.

Which created this file structure:

.:
build.sh  lib  src

./lib:
commons-cli-1.4.jar

./src:
com  module-info.java

./src/com:
greetings

./src/com/greetings:
Main.java

I modified the compile line to include the commons jar in the classpath:

javac -cp lib/commons-cli-1.4.jar:. \
-d mods/com.greetings \
src/module-info.java src/com/greetings/Main.java

However, when I try to compile it, I get this error:

src/com/greetings/Main.java:10: error: package org.apache.commons.cli is not visible
import org.apache.commons.cli.CommandLine;
                     ^
   (package org.apache.commons.cli is declared in the unnamed module, but module org.apache.commons.cli does not read it)
1 error

How do I modify my project so I can compile against commons-cli-1.4.jar?


Edit, at the suggestion of the user nullpointer, I tried changing the -cp flag to just a -p flag, so the external jar is added to the module path instead. Unfortunately, that also doesn't work. Here are the various javac command I tried that also do not work:

javac -p lib -d mods/com.greetings \
src/module-info.java src/com/greetings/Main.java

javac --module-path=lib -d mods/com.greetings \
src/module-info.java src/com/greetings/Main.java

javac -p lib/commons-cli-1.4.jar -d mods/com.greetings \
src/module-info.java src/com/greetings/Main.java
Naman
  • 27,789
  • 26
  • 218
  • 353
Grumblesaurus
  • 3,021
  • 3
  • 31
  • 61
  • You should put that jar on the modulepath instead and that should solve the current compilation failure. You can do so using `--module-path or -p ` – Naman Nov 11 '18 at 05:24
  • Thank you, but it didn't work. I tried '-p lib' , '-p lib/commons-cli-1.4.jar' and '--module-path ./lib'. They all gave the same error. – Grumblesaurus Nov 11 '18 at 05:29
  • 1
    It should ideally not remain in the unnamed module if you've specified the jar to be on the modulepath and removed it from the classpath. Could you update the question with your new javac command line. – Naman Nov 11 '18 at 05:32
  • Just tried that, did not work. -- error: module not found: org.apache.commons.cli -- pointing at the line you added. – Grumblesaurus Nov 11 '18 at 05:38
  • That does mean, its still not there on the module path in my understanding. Maybe something in the command line getting missed from the vision of a normal eye. – Naman Nov 11 '18 at 05:41
  • 1
    *make sure your module declaration now requires the module* ... Giving it a try, noticed that the module name for the `commons-cli:commons-cli:1.4` would need `requires commons.cli;`, can you check with this? – Naman Nov 11 '18 at 05:46
  • 1
    Are you doing this for learning purposes or to get something done? For work to actually use, adopt Maven or Gradle? – chrylis -cautiouslyoptimistic- Nov 11 '18 at 05:56
  • 2
    Chrylis: learning for now. Next step is to convert a large existing project and I'm getting myself familiar with the building blocks right now. Every time I run into an issue there, I distill it down into this test environment, understand it, and then go apply it to the large project. That project currently uses Ant, but I'm probably transitioning to Gradle. – Grumblesaurus Nov 11 '18 at 06:12
  • 2
    @JoshuaD The final step of using `jlink` with automatic modules is what I am afraid is something I haven't seen people working a way until now. Apart from that, the rest of it should just be working fine. I gave your setup a try and could work with it and hence [made an answer](https://stackoverflow.com/a/53246194/1746118). – Naman Nov 11 '18 at 06:37

1 Answers1

2

You've placed the jar on the classpath because of which it results into an unnamed module..

The unnamed module exports all of its packages. ...

It does not, however, mean that code in a named module can access types in the unnamed module. ...

This restriction is intentional, since allowing named modules to depend upon the arbitrary content of the class path would make reliable configuration impossible.

Instead try placing the same jar on the modulepath from where it can be inferred as an automatic module.


You would also need to ensure that the module declaration of your module is updated accordingly to define a dependence on the newly added module to access its exported packages.

module com.greetings { 
    requires commons.cli;
}

Edit: Trying out the complete build.sh in your case would still fail, but at the linking step, because of the presence of an automatic module.

Naman
  • 27,789
  • 26
  • 218
  • 353
  • 1. How did you determine the name commons.cli? I have 14 external jars in my actual project. 2. Is there a solution to the jlink / external jar problem you highlighted, or is that unsolvable? – Grumblesaurus Nov 11 '18 at 06:52
  • 1. Using the [`jar --file=<> --describe-module` command](https://stackoverflow.com/a/46715853/1746118). 2. I believe that's intentionally restricted to ensure only explicit modules(named modules) or simple terms modular projects are used while creating standalone runtime environments using jlink. | Also, note 14 jars using command line is not a good choice then, you shall use a framework like Maven/Gradle as suggested by [chrylis in comments as well.](https://stackoverflow.com/questions/53246066/compile-module-that-depends-on-an-external-jar/53246194?noredirect=1#comment93377419_53246066) – Naman Nov 11 '18 at 06:58
  • So jlink is not a good option for a project which depends on 14 external jars that haven't been updated to be modules? Is there another method for creating a stripped down JRE? – Grumblesaurus Nov 11 '18 at 07:20
  • 1
    I was able to find examples on how to convert my jar dependencies here: https://github.com/codetojoy/easter_eggs_for_java_9/blob/master/egg_34_stack_overflow_47727869/run.sh#L51 It's a bit cumbersome, but it's good enough for me. – Grumblesaurus Nov 11 '18 at 08:39