boot-clj / boot

Build tooling for Clojure.
https://boot-clj.github.io/
Eclipse Public License 1.0
1.75k stars 181 forks source link

jar file generated by "jar" task is not compatible with Capsule #710

Closed echou closed 4 years ago

echou commented 6 years ago

Problem Description

I try to use Capsule to generate an executable jar with all dependencies included (alternative to uberjar). But the result jar file doesn't run and quit immediately.

Steps to reproduce

This is capule task definition in build.boot:


(merge-env! :dependencies '[[co.paralleluniverse/capsule "1.0.3"]])

(deftask capsule
         []
         (comp
           (tasks/javac)
           (tasks/aot)
           (tasks/pom)
           (tasks/jar)                                                ; generate project.jar
           (tasks/uber :as-jars true)                       ; copy dependencies jars
           (tasks/sift :include #{#"\.jar$"})            ; just keep all jar files
           (tasks/sift :add-jar {'co.paralleluniverse/capsule #"Capsule.class"})  ; copy capsule bootstrap class 
           (capsule-jar :file  "project-capsule.jar"
                                 :manifest {"Main-Class"          "Capsule"
                                                   "Application-ID"      "my-project"
                                                   "Application-Version" "0.1.0"
                                                   "Application-Class"   "my_project.core"})
           (tasks/sift :include #{#"-capsule\.jar$"})       ; just keep *-capsule.jar
           (tasks/target)))

Platform details

Platform (macOS, Linux, Windows): macOS Platform version: HIgh Sierra 10.13.2 JRE/JDK version (java -version): 1.8.0

Boot details

Boot version (2.7.1): 2.6.0 build.boot present? (yes/no): yes ~/.boot/profile present? (yes/no): no Task name? (if applicable): capsule

The cause

When Capsule.class starts, it uses JarInputStream.getManifest() to read MANIFEST.MF in the capsuled jar. JarInputStream assumes manifest is the first jar entry. However "jar" task (and finally boot.jar/update-jar!) writes MANIFEST.MF to the last entry, so getManifest() returns null.

After MANIFEST.MF is written first, the capsuled jar runs.

(defn update-jar!
  [jarfile old-fs new-fs attr main]
  (with-open [fs (fs/mkjarfs jarfile :create true)]
    (fs/write! (fs/->path fs) (create-manifest main attr) ["META-INF" "MANIFEST.MF"])
    (fs/patch! (fs/->path fs) old-fs new-fs)))
RadicalZephyr commented 4 years ago

hey @flyboarder any thoughts on merging this for 3.0.0 at least?

burn2delete commented 4 years ago

Absolutely!