Coder Social home page Coder Social logo

uberdeps's Introduction

Uberjar builder for deps.edn projects

Takes deps.edn and packs an uberjar out of it.

  • Fast: Does not unpack intermediate jars on disk.
  • Explicit: Prints dependency tree. Realize how much crap you’re packing.
  • Standalone: does not depend on current classpath, does not need live Clojure environment.
  • Embeddable and configurable: fine-tune your build by combining config options and calling specific steps from your code.

Rationale

It is important to be aware that build classpath is not the same as your runtime classpath. What you need to build your app is usually not what you need to run it. For example, build classpath can contain uberdeps, tools.deps.alpha, ClojureScript compiler etc. Your production application can happily run without all that! Build classpath does not need your app dependencies. When you just packing files into a jar you don’t not need something monumental like Datomic or nREPL loaded!

Other build systems sometimes do not make a strict distinction here. It’s not uncommon to e.g. see ClojureScript dependency in project.clj in main profile.

Uberdeps is different from other archivers in that it strictly separates the two. It works roughly as follows:

  1. JVM with a single uberdeps dependency is started (NOT on the app’s classpath).
  2. It reads your app’s deps.edn and figures out from it which jars, files and dirs it should package. Your app or your app’s dependencies are not loaded! Just their dependencies are analyzed, using tools.deps.alpha as a runtime library.
  3. Final archive is created, JVM exits.

Project setup

Ideally you would setup a separate deps.edn for packaging:

project
├ deps.edn
├ src
├ ...
└ uberdeps
  ├ deps.edn
  └ package.sh

with following content:

uberdeps/deps.edn:

{:deps {uberdeps/uberdeps {:mvn/version "1.3.0"}}}

uberdeps/package.sh:

#!/bin/bash -e
cd "$( dirname "${BASH_SOURCE[0]}" )"
clojure -M -m uberdeps.uberjar --deps-file ../deps.edn --target ../target/project.jar

To be clear:

  • /uberdeps/deps.edn is used only to start uberdeps. Files, paths, profiles from it won’t affect resulting archive in any way.
  • /deps.edn (referred as --deps-file ../deps.edn from /uberdeps/package.sh) is what’s analyzed during packaging. Its content determines what goes into the final archive.

In an ideal world, I’d prefer to have /uberdeps.edn next to /deps.edn in a top-level dir instead of /uberdeps/deps.edn. Unfortunately, clj / clojure bash scripts do not allow overriding deps.edn file path with anything else, that’s why extra uberdeps directory is needed.

Project setup — extra aliases

You CAN use aliases to control what goes into resulting archive, just as you would with normal deps.edn. Just remember to tell uberdeps about it with --aliases option:

deps.edn:

{ :paths ["src"]
  ...
  :aliases {
    :package {
      :extra-paths ["resources" "target/cljs/"]
    }
    :nrepl {
      :extra-deps {nrepl/nrepl {:mvn/version "0.6.0"}}
    }
  }
}

uberdeps/package.sh:

#!/bin/bash -e
cd "$( dirname "${BASH_SOURCE[0]}" )"
clojure -M -m uberdeps.uberjar --deps-file ../deps.edn --target ../target/project.jar --aliases package:nrepl:...

Project setup — quick and dirty

Sometimes it’s just too much setup to have an extra script and extra deps.edn file just to run simple archiver. In that case you can add uberdeps in your main deps.edn under an alias. This will mean your app’s classpath will load during packaging, which is extra work but should make no harm.

project
├ deps.edn
├ src
└ ...

deps.edn:

{ :paths ["src"]
  ...
  :aliases {
    :uberdeps {
      :replace-deps {uberdeps/uberdeps {:mvn/version "1.3.0"}}
      :replace-paths []
      :main-opts ["-m" "uberdeps.uberjar"]
    }
  }
}

and invoke it like this (requires clj >= 1.10.1.672):

clj -M:uberdeps

In that case execution will happen like this:

  1. JVM will start with :uberdeps alias which will REPLACE all your normal dependencies on your app’s classpath with uberdeps dependency.
  2. uberdeps.uberjar namespace will be invoked as main namespace.
  3. Uberdeps process will read deps.edn AGAIN, this time figuring out what should go into archive. Note again, it doesn’t matter what’s on classpath of Uberdeps process. What matters is what it reads from deps.edn itself. Archive will not inherit any profiles enabled during execution, or any classpath resources, meaning, for example, that uberdeps won’t package its own classes to archive.
  4. Final archive is created, JVM exits.

No-config setup

You can invoke Uberdeps from command line at any time without any prior setup.

Add to your bash aliases:

clj -Sdeps '{:aliases {:uberjar {:replace-deps {uberdeps/uberdeps {:mvn/version "1.3.0"}} :replace-paths []}}}' -M:uberjar -m uberdeps.uberjar

Or add to your ~/.clojure/deps.edn:

:aliases {
  :uberjar {:replace-deps {uberdeps/uberdeps {:mvn/version "1.3.0"}}
            :replace-paths []
            :main-opts ["-m" "uberdeps.uberjar"]}
}

Both of these method will replace whatever is in your deps.edn with uberdeps, so at runtime it is an exact equivalent of “Quick and dirty” setup.

Using the generated uberjar

If your project has a -main function, you can run it from within the generated uberjar:

java -cp target/<your-project>.jar clojure.main -m <your-namespace-with-main>

Creating an executable jar

Given your project has a -main function like below:

(ns app.core
  (:gen-class))

(defn -main [& args]
  (println "Hello world"))

You can create an executable jar with these steps:

# 1. Ensure dir exists
mkdir classes

# 2. Add `classes` dir to the classpath in `deps.edn`:
{:paths [... "classes"]}

Make sure you have Clojure as a dependency—uberdeps won’t automatically add it for you:

{:deps {org.clojure/clojure {:mvn/version "1.10.0"}, ...}}

# 3. Aot compile
clj -M -e "(compile 'app.core)"

# 4. Uberjar with --main-class option
clojure -M:uberjar --main-class app.core

This will create a manifest in the jar under META-INF/MANIFEST.MF, which then allows you to run your jar directly:

java -jar target/<your-project>.jar

For more information on AOT compiling in tools.deps, have a look at the official guide.

Command-line options

Supported command-line options are:

--deps-file <file>                Which deps.edn file to use to build classpath. Defaults to 'deps.edn'
--aliases <alias:alias:...>       Colon-separated list of alias names to include from deps file. Defaults to nothing
--target <file>                   Jar file to ouput to. Defaults to 'target/<directory-name>.jar'
--exclude <regexp>                Exclude files that match one or more of the Regular Expression given. Can be used multiple times
--main-class <ns>                 Main class, if it exists (e.g. app.core)
--multi-release                   Add Multi-Release: true to the manifest. Off by default.
--level (debug|info|warn|error)   Verbose level. Defaults to debug

Programmatic API

(require '[uberdeps.api :as uberdeps])

(let [exclusions [#"\.DS_Store" #".*\.cljs" #"cljsjs/.*"]
      deps       (clojure.edn/read-string (slurp "deps.edn"))]
  (binding [uberdeps/level      :warn]
    (uberdeps/package deps "target/uber.jar" {:aliases #{:uberjar}
                                              :exclusions exclusions})))

Merging

Sometimes assembling uberjar requires combining multiple files with the same name (coming from different libraries, for example) into a single file. Uberdeps does that automatically for these:

META-INF/plexus/components.xml uberdeps.api/components-merger
#"META-INF/services/.*"        uberdeps.api/services-merger   
#"data_readers.clj[cs]?"       uberdeps.api/clojure-maps-merger

You can provide your own merger by passing a merge function to uberdeps.api/package:

(def readme-merger
  {:collect
   (fn [acc content]
     (conj (or acc []) (str/upper-case content)))
   :combine
   (fn [acc]
     (str/join "\n" acc))})

Merger is a map with two keys: :collect and :combine. Collect accumulates values as they come. It takes an accumulator and a next file content, and must return the new accumulator:

((:collect readme-merger) acc content) -> acc'

File content is always a string. Accumulator can be any data structure you find useful for storing merged files. In readme-merger accumulator is a string, in clojure-maps-merger it is a clojure map, etc. On a first call to your merger accumulator will be nil.

Combine is called when all files with the same name have been processed and it is time to write the resulting single merged file to the jar. It will be called with your accumulator and must return a string with file content:

((:combine readme-merger) acc) -> content'

Custom mergers can be passed to uberdeps.api/package in :mergers option along with file path regexp:

(uberdeps.api/package
  deps
  "target/project.jar"
  {:mergers {#"(?i)README(\.md|\.txt)?" readme-merger}})

Passing custom mergers does not remove the default ones, but you can override them.

License

Copyright © 2019 Nikita Prokopov.

Licensed under MIT (see LICENSE).

uberdeps's People

Contributors

antonoellerer avatar deathtenk avatar dm3 avatar dupuchba avatar gavinkflam avatar gnarroway avatar heliosmaster avatar hoxu avatar imrekoszo avatar jdf-id-au avatar jorinvo avatar levitanong avatar lvh avatar miggl avatar mvproton avatar puredanger avatar timofeytt avatar tonsky avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

uberdeps's Issues

Remove duplicate dependencies

This

:deps
{io.pedestal/pedestal.service {:mvn/version "0.5.5"}
 cheshire {:mvn/version "5.8.1"}}

results in cheshire sources being included twice

Fix bash alias recommendation

Unfortunately, clj -Sdeps '{:deps {uberdeps/uberdeps {:mvn/version "1.0.2"}}}' -m uberdeps.uberjar does not

replace whatever is in your deps.edn with uberdeps

This can be verified by running clj -Sdeps '{:deps {uberdeps/uberdeps {:mvn/version "1.0.2"}}}' -Stree.

I'd recommend updating that part of the readme to clj -Sdeps '{:aliases {:uberjar {:replace-deps {uberdeps/uberdeps {:mvn/version "1.0.2"}}}}}' -M:uberjar -m uberdeps.uberjar. (assuming clojure tools >= 1.9.0.326 for -M support and >= 1.10.1.672 for replace-deps support)

Like above, deps of this can be verified by running clj -Sdeps '{:aliases {:uberjar {:replace-deps {uberdeps/uberdeps {:mvn/version "1.0.2"}}}}}' -A:uberjar -Stree

Ability to select duplicated file to use

I get message like that:

! Duplicate entry "logback.xml" from "src/**" already seen in "org.springframework.build/aws-maven 5.0.0.RELEASE"

and resulting uberjar contains wrong logback.xml :) I'm not sure what's the best way to proceed, maybe exclusions could (optionally?) match for project name.

Use :replace-deps to simplify recommended project setup in the README

With clojure cli version: 1.10.1.727
I can add an alias in my project deps.edn like so:

:uberjar  {:replace-deps {uberdeps/uberdeps {:mvn/version "1.0.0"}}
           :main-opts    ["-m" "uberdeps.uberjar"]}

and invoke it with:
clj -A:uberjar
and the classpath will only contain the uberdeps dependency.

This makes project setup way less effort. Consider recommending this approach in the README.
For older clojure cli versions clj -T:uberjar -M:uberjar is an alternative.

How to use generated jar file

As someone not so familiar with Clojure and jars, it would be very helpful to have a little information on how to use the resulting jar.
What I tried is running a Clojure module inside the jar but that doesn't seem to be the way it works:

java -jar target/myproject.jar -m clojure.main

I would really appreciate any hints here, thanks :)

Missing required argument for "-M ALIASES"

Hey,
First off, thank you very much for this useful project.
When testing it, I noticed that, when using the originally specified shell script to create an uberjar, I get the error notification Missing required argument for "-M ALIASES".
The clojure manpages say the following for me:

       -M:alias
              Concatenated main option aliases, ex: -M:test

When removing the -M flag from the script, everything runs as expected.
Do you know what my problem here might be?
Best regards
Anton Oellerer

NoSuchMethodError when building executable

Hey team, am falling into a bit of a noob issue, and would love some help digging deeper if you can spare the time.

I am trying to set up a jar, which i could deploy to something like google app engine. To do this, I am following the readme to set up an executable: https://github.com/tonsky/uberdeps#creating-an-executable-jar

Here's my code so far:
https://github.com/stopachka/clap/tree/uberdeps

When I run

clj -e "(compile 'clap.core)"
clj -A:uberjar --main-class clap.core
java -jar target/clap.core

I get:

Exception in thread "main" java.lang.NoSuchMethodError: 'java.lang.Object clojure.lang.Util.loadWithClass(java.lang.String, java.lang.Class)'
	at clap.core.<clinit>(Unknown Source)

If I try to run the classes directly:

java -cp `clj -Spath` clap.core  

The server boots up, which makes me think things are okay at this stage.

If I unzip the jar, I see something like this:

image

which also seems fine.

I think the issue is happening at the uberjar stage, but am not sure how to dig deeper from the error message above. Any thoughts much appreciated

Cannot find main class on windows

At work we have to use windows, and it seems there is an issue loading the main class in the jar generated by uberdeps. The same code works fine on Linux.

Setup:

  • minimal code with a gen-class that just does a println
  • :paths [“src” “classes”]
  • first aot with (compile ‘app.core)
  • then uberjar
  • then try java -cp out.jar clojure.main -m app.core

On windows:
Execution error FileNotFound...
Could not locate app/core__init, app/core.clj...

Unzipping the jar, all these files are there as expected. Furthermore, using the unzipped dir as the class path works as expected.

Using a reply with org.clojure/java.classpath against the jar, I can list the file names in the jar which also include the class (and source) files as expected.

Exactly the same code on a Linux box works fine.

Any ideas on which part of the toolchain is causing these issues?

For the record, uberjar created with lein work fine, and the contents do not look particularly different.

More info:
Clojure 1.10.1
OpenJDK 11.0.3

Using aliases with replace-deps and replace-paths doesn't seem to replace deps and paths

Hi there! I'm having some trouble using aliases in a single deps.edn file (rather than a separate deps.edn file), but perhaps I'm misunderstanding how this should work. Here's what I'm trying to do:

  • Default options in deps.edn file specify dependencies and paths as needed for the project
  • AOT compile Clojure sources
  • Bundle only the classes from AOT compilation

My deps.edn file:

{:deps {org.clojure/clojure {:mvn/version "1.10.0"}
        ...}
 :paths ["sources"]
 :aliases {:aot-compile {:extra-paths ["classes"]}
           :package {:replace-deps {} :replace-paths ["classes"]}
           :packager {:replace-deps {uberdeps/uberdeps {:mvn/version "1.1.1"}}
                      :replace-paths []}}}

Steps that I'm running:

clojure -Aaot-compile --eval "(compile 'main.core)"
clojure -M:packager --main uberdeps.uberjar --aliases package --target output.jar --main-class main.core

At this point, I expect output.jar to include no Clojure sources, but instead it includes all of the Clojure sources for the project, as well as for the dependencies.

Instead, if I use a separate package.deps.edn file:

{:deps {}
 :paths ["classes"]}

And run the following steps:

clojure -Aaot-compile --eval "(compile 'main.core)"
clojure -M:packager --main uberdeps.uberjar --deps-file package.deps.edn --target output.jar --main-class main.core

It appears to work as I expect. Is there any way for me to get this behavior using a single deps.edn file?

Uberjar returns null while executing .getResource on a resource directory but not on a resource file

I have a project with the structure
uberjardemo
|
---- resources
| |
| ---- directory
| |
| ---- file.txt
----- src
| |
| ------ main
| |
| ----- uberjardemo
| |
| ----- demo.clj
---- deps.edn

I am trying to find the protocol of the directory under the resources folder (this should be "file" when running directly and "jar" when running from an uberjar)

The code for demo.clj is

(ns uberjardemo.demo)

(defn -main
  "The entry-point for our app"
  [& args]
  (prn "protocol of file" (.getProtocol (.getResource (ClassLoader/getSystemClassLoader) "directory/file.txt"))) ;Protocol of file "jar"
  (prn "protocol of directory" (.getProtocol (.getResource (ClassLoader/getSystemClassLoader) "directory/"))) ;NullPointerException
  )

And the content of deps.edn is

{:paths   ["src/main" "resources" ]
 :deps    {org.clojure/clojure            {:mvn/version "1.10.0"}}
 :aliases {:uberjar  {:extra-deps {uberdeps {:mvn/version "0.1.4"}}
                      :main-opts  ["-m" "uberdeps.uberjar --target target/demo.jar"]}}}

While I run this code directly from clojure, it works. But when I build the jar using the command clojure -A:uberjar and execute it using java -cp target/demo.jar clojure.main -m uberjar.demo only the second prn statement throws a NullPointerException since (.getResource (ClassLoader/getSystemClassLoader) "directory/") returns null.

uberdeps run error in macs

  • os version: MacOS Monterey 12.5
  • hardware: Macbook pro 2019
  • cpu intel Core i5
  • openjdk version: 16.0.1
  • clojure cli version: 1.10.3.1069

Follow readme instruction:

mkdir classes
clojure -M -e "(compile 'com.howard.schedule-crawler.main)"
clojure -M:uberjar --main-class com.howard.schedule-crawler.main

get following error:

Syntax error (FileNotFoundException) compiling at (uberdeps/uberjar.clj:1:1).
Could not locate clojure/tools/deps/util/dir__init.class, clojure/tools/deps/util/dir.clj or clojure/tools/deps/util/dir.cljc on classpath.

deps.edn:

{:paths ["classes" "resources" "../../gold-web/main" "../../gold-web/resources"]
 :deps {org.clojure/clojure {:mvn/version "1.10.1"}
        org.clojure/tools.deps.alpha {:mvn/version "0.12.985"}
        uberdeps/uberdeps {:mvn/version "1.3.0"}
        org.apache.logging.log4j/log4j-slf4j-impl {:mvn/version "2.19.0"}
        jarohen/chime {:mvn/version "0.3.3"}
        org.clojure/tools.logging {:mvn/version "1.2.4"}
        ;; gold/restful-api {:local/root "./bases/restful-api"}
        gold/schedule-crawler {:local/root "../../bases/schedule-crawler"}
        gold/crawler {:local/root "../../components/crawler"}
        gold/database {:local/root "../../components/database"}}

 :jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/slf4j-factory"
            "-Dlog4j2.configurationFile=log4j2.properties"]
 :aliases {:uberjar {:replace-deps {uberdeps/uberdeps {:mvn/version "1.3.0"}}
                     :replace-paths []
                     :main-opts ["-m" "uberdeps.uberjar"]
                     :jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/slf4j-factory"
                                "-Dlog4j2.configurationFile=log4j2.properties"]}
           :opts {:jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/slf4j-factory"
                             "-Dlog4j2.configurationFile=log4j2.properties"]}
           :build {:main-opts ["-m" "com.howard.schedule-crawler.main"]}
           :test {:extra-paths []
                  :extra-deps  {}}}}

successful build jar using my ubuntu lab.

Handle non-existing parent directory

Hi, thanks for this simple tool!

When I first tried to run it I got an error:

[uberdeps] Packaging target/letsdo.events.jar...
Exception in thread "main" java.io.FileNotFoundException: target/myproject.jar (No such file or directory)

After creating the target directory manually, the error is resolved.

Could uberdeps create the parent directories automatically or show a useful error message to tell one that the problem is not that the .jar file does not exist, but that the directory does not exist? It would have saved me a few moments of wondering what's wrong :)

`:extra-paths` being ignored (regression in 1.1.2 ?)

Using the following simple deps.edn...

{:paths   ["src" "other"]
 :deps    {}
 :aliases {:package {:extra-paths ["resources"]}}}

Using 1.1.1, resources/** is included as expected:

% clj -Sdeps '{:aliases {:uberjar {:replace-deps {uberdeps/uberdeps {:mvn/version "1.1.1"}} :replace-paths []}}}' -M:uberjar -m uberdeps.uberjar --aliases package
[uberdeps] Packaging target/t.jar...
+ other/**
+ resources/**
+ src/**
+ org.clojure/clojure #:mvn{:version "1.10.3"}
.   org.clojure/core.specs.alpha #:mvn{:version "0.2.56"}
.   org.clojure/spec.alpha #:mvn{:version "0.2.194"}
[uberdeps] Packaged target/t.jar in 929 ms

but using 1.1.2, resources/** is not included which seems wrong:

% clj -Sdeps '{:aliases {:uberjar {:replace-deps {uberdeps/uberdeps {:mvn/version "1.1.2"}} :replace-paths []}}}' -M:uberjar -m uberdeps.uberjar --aliases package
[uberdeps] Packaging target/t.jar...
+ other/**
+ src/**
+ org.clojure/clojure #:mvn{:version "1.10.3"}
.   org.clojure/core.specs.alpha #:mvn{:version "0.2.56"}
.   org.clojure/spec.alpha #:mvn{:version "0.2.194"}
[uberdeps] Packaged target/t.jar in 654 ms```

If data_readers cljc has reader conditionals: "No dispatch macro for ?"

In particular, when including tick (or really, transitively, com.widdindustries/time-literals), its data_readers.cljc trips things up: https://github.com/henryw374/time-literals/blob/master/src/data_readers.cljc

Getting:

Execution error at uberdeps.api/fn (api.clj:108).
No dispatch macro for: ?

It seems to be with the edn/read-string, since it doesn't handle the reader conditionals: https://github.com/tonsky/uberdeps/blob/master/src/uberdeps/api.clj#L108

I tried to add the reader function to see if I can create a PR to fix it:

(clojure.edn/read-string
  {:readers {'? (fn [b] b)}}
  "#?(:clj 1 :cljs 2)")
; Execution error at services.slack/eval35759 (REPL:188).
; No dispatch macro for: ?

but it seems lower level, since if I change to #t, for example, I get:

(clojure.edn/read-string
  {:readers {'? (fn [b] b)}}
  "#t(:clj 1 :cljs 2)")
; Execution error at services.slack/eval35755 (REPL:182).
; No reader function for tag t

Not entirely sure what to suggest - I thought there was a way to say what environment we're reading in with edn/read-string, but I can't seem to find it.

Not everything in `:paths` is making it into the jar

I'm building a project using the "Quick & Dirty" uberdeps setup.
I have a few paths in addition to source in my deps.edn.

{:paths ["src" "resources" "migrations" "java-classes" "classes"] ...}

When I run the uberdeps command, it looks like it's including those directories:

$ clj -M:uberjar --main-class sqa.main --target target/sqa-app.jar
[uberdeps] Packaging target/sqa-app.jar...
+ classes/**
+ java-classes/**
+ migrations/**
+ resources/**
+ src/**

But in the jar itself, migrations/** and resources/** are absent.

$ jar -tf sqa-app.jar | grep ^migrations 
<tumbleweed>

I think I'm most likely doing something wrong, or have misunderstood how uberjars work, but I'm not sure what. Any advice much appreciated!

extra-paths ignored

Directories mentioned in alias :extra-paths are ignored.

   :paths ["src"]

    :uberdeps
    {
      :extra-paths ["resources"]
      :extra-deps {uberdeps {:mvn/version "0.1.4"}}
      :main-opts ["-m" "uberdeps.uberjar --target tmp/main.jar --level debug"]}

but that configuration works well

   :paths ["src" "resources]

    :uberdeps
    {
      :extra-deps {uberdeps {:mvn/version "0.1.4"}}
      :main-opts ["-m" "uberdeps.uberjar --target tmp/main.jar --level debug"]}

Sorting the paths affects precedence of duplicate entries

The fact that the paths get sorted before being added to the jar (https://github.com/tonsky/uberdeps/blob/1.1.3/src/uberdeps/api.clj#L244) doesn't seem to match my expectation that directories added via :extra-path should come earlier on the classpath such that duplicate entries (like config files) can be overridden (and guarantee to be the one ending up in the jar), for example, by specifying an alias when creating the uberjar.

It seems to work the way I expect if (sort ...) is replaced by (reverse ...) -- so that extra-path entries come first.
Alternatively, the paths could be assembled in that order in the first place, matching what gets returned by clj -Spath -A:myalias.

FileNotFoundException when running clojure -M:uberjar

I'm getting the same error.
When I run clojure -M:uberjar --target 'target/bot.jar' --main-class dmvbot.core I get

Execution error (FileNotFoundException) at java.io.FileInputStream/open0 (FileInputStream.java:-2).
--target (No such file or directory)

And the full stack trace is

 "Execution error (FileNotFoundException) at java.io.FileInputStream/open0 (FileInputStream.java:-2).\n--target (No such file or directory)\n",
 :clojure.main/triage
 {:clojure.error/class java.io.FileNotFoundException,
  :clojure.error/line -2,
  :clojure.error/cause "--target (No such file or directory)",
  :clojure.error/symbol java.io.FileInputStream/open0,
  :clojure.error/source "FileInputStream.java",
  :clojure.error/phase :execution},
 :clojure.main/trace
 {:via
  [{:type java.io.FileNotFoundException,
    :message "--target (No such file or directory)",
    :at [java.io.FileInputStream open0 "FileInputStream.java" -2]}],
  :trace
  [[java.io.FileInputStream open0 "FileInputStream.java" -2]
   [java.io.FileInputStream open "FileInputStream.java" 213]
   [java.io.FileInputStream <init> "FileInputStream.java" 155]
   [java.io.FileInputStream <init> "FileInputStream.java" 110]
   [clojure.lang.Compiler loadFile "Compiler.java" 7571]
   [clojure.main$load_script invokeStatic "main.clj" 475]
   [clojure.main$script_opt invokeStatic "main.clj" 535]
   [clojure.main$script_opt invoke "main.clj" 530]
   [clojure.main$main invokeStatic "main.clj" 664]
   [clojure.main$main doInvoke "main.clj" 616]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.main main "main.java" 40]],
  :cause "--target (No such file or directory)"}}

Alternatively, if I run clojure -M:uberjar --main-class dmvbot.core, I get

Execution error (FileNotFoundException) at java.io.FileInputStream/open0 (FileInputStream.java:-2).
--main-class (No such file or directory)

And the full stack trace is

{:clojure.main/message
 "Execution error (FileNotFoundException) at java.io.FileInputStream/open0 (FileInputStream.java:-2).\n--main-class (No such file or directory)\n",
 :clojure.main/triage
 {:clojure.error/class java.io.FileNotFoundException,
  :clojure.error/line -2,
  :clojure.error/cause "--main-class (No such file or directory)",
  :clojure.error/symbol java.io.FileInputStream/open0,
  :clojure.error/source "FileInputStream.java",
  :clojure.error/phase :execution},
 :clojure.main/trace
 {:via
  [{:type java.io.FileNotFoundException,
    :message "--main-class (No such file or directory)",
    :at [java.io.FileInputStream open0 "FileInputStream.java" -2]}],
  :trace
  [[java.io.FileInputStream open0 "FileInputStream.java" -2]
   [java.io.FileInputStream open "FileInputStream.java" 213]
   [java.io.FileInputStream <init> "FileInputStream.java" 155]
   [java.io.FileInputStream <init> "FileInputStream.java" 110]
   [clojure.lang.Compiler loadFile "Compiler.java" 7571]
   [clojure.main$load_script invokeStatic "main.clj" 475]
   [clojure.main$script_opt invokeStatic "main.clj" 535]
   [clojure.main$script_opt invoke "main.clj" 530]
   [clojure.main$main invokeStatic "main.clj" 664]
   [clojure.main$main doInvoke "main.clj" 616]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.main main "main.java" 40]],
  :cause "--main-class (No such file or directory)"}}

It looks like it is trying to open whatever comes after clojure -M:uberjar as a file.

Originally posted by @Sofianel5 in #25 (comment)

*print-namespace-maps* binding problem in REPL

I'm getting
Exception in thread "main" Syntax error compiling at (uberdeps/api.clj:19:1) caused by
java.lang.IllegalStateException: Can't change/establish root binding of: *print-namespace-maps* with set when trying to require uberdeps for use at the REPL within an established project (happens during REPL startup, and causes jvm to exit.). It doesn't happen when I require uberdeps for use at the REPL within a fresh, empty project.

I'm struggling to work out why this happens—do you have any suggestions?

Attempting to run uberjar throws FileNotFoundException

Hi Tonsky, thanks for this. I followed the instructions in the README to add uberdeps to my existing deps.edn-based Clojure project.

clj -A:uberdeps works as expected and outputs target/myproject.jar, but when I try to run it, java -cp target/myproject.jar clojure.main myproject.backend.core, throws:

Execution error (FileNotFoundException) at java.io.FileInputStream/open0 (FileInputStream.java:-2).
myproject.backend.core (No such file or directory)

Full report at:
/var/folders/rl/b7dvjss14wj6415t0v4f7z3m0000gn/T/clojure-4259307103758606041.edn

I doubt this is a bug in uberdeps, but there might be something incompatible in my project or I missed a step. I also tried clj -A:uberdeps --main-class myproject.backend.core and added "classes" to :paths.

Here is my source:

(ns myproject.backend.core
  (:gen-class)
  (:require [org.httpkit.server :as httpkit]
            [reitit.core :as r]
            [org.httpkit.client :as http]
            [taoensso.timbre :as log]))

(defn app [req]
  {:status  200
   :headers {"Content-Type" "text/html"}
   :body    "hello HTTP!"})

(defonce !server (atom nil))

(defn -main []
  (when-let [server @!server]
    (log/debug "Stopping server...")
    (server))

  (reset! !server
    (httpkit/run-server app {:port 8080})))

Bug/Feature Request: External dependencies that reference pom files

First off I'd like to thank you for this incredibly convenient tool! It's very handy. That said I did run into a problem with it.

So this tool packages things up nicely when all the dependencies it needs to get are in the form of jar files. However, sometimes you'll have cases where children dependencies will make references to dependencies of type pom, it makes an attempt at resolution, but given that the only thing in the directory is some file with the extension *.pom, and this will throw kind of a bizarre error that looks like the following:

Unknown entity at classpath: path/to/.m2/repository/org/eclipse/jetty/jetty-distribution/8.1.12.v20130726/jetty-distribution-8.1.12.v20130726.pom

To replicate this, I made a simple deps.edn file that looks something like this:

{:mvn/repos {"glue" {:url "https://aws-glue-etl-artifacts.s3.amazonaws.com/release/"}}
 :deps {com.amazonaws/AWSGlueETL {:mvn/version "1.0.0"}} }

I then ran the deps.edn structure through the packaging tool.

(require '[uberdeps.api :as uberdeps])

(def deps (read-string (slurp "./deps.edn")))

(uberdeps/package deps "target/uber.jar" {:aliases #{:uberjar}}) ; Exception Happens

And the exception listed above occurs. I did actually figure out the reason for this. Basically, if you follow that repository coordinate back to its source, you get a pom file that has a dependency of type pom. It looks like this:

    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-distribution</artifactId>
      <version>8.1.12.v20130726</version>
      <type>pom</type>
    </dependency>

Now thankfully, in the clojure.tools.deps.alpha library the metadata about the extension actually carries over, so this is something that you can account for. when you call clojure.tools.deps.alpha/resolve-deps, you actually get a structure that describes metadata about the child dependency.

{...
 org.eclipse.jetty/jetty-distribution
 {:mvn/version "8.1.12.v20130726",
  :extension "pom",
  :deps/manifest :mvn,
  :paths
  ["/path/to/.m2/repository/org/eclipse/jetty/jetty-distribution/8.1.12.v20130726/jetty-distribution-8.1.12.v20130726.pom"],
  :dependents [com.amazonaws/AWSGlueETL]}
...}

However in the actual packaging logic theres nothing accounting for when this metadata exists. Rather, it just assumes that we're only gonna be dealing with jars. So there's probably a few ways to hack around this, but ideally it would make sense for this tool to manage cases where you are required to deal with child dependencies that have the pom extension.

Again, thanks for the tool! it's great. Just wanted to give you the heads up on this issue I had with it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.