Fork me on GitHub
#tools-build
<
2021-09-27
>
AustinP22:09:31

Hello! I'm working in a monorepo and trying to uberjar our projects using tools.build . I've gotten the uberjar compiling and built, but running it with java -jar , I keep hitting a java.lang.ClassNotFoundException. Could not find or load main class monorepo.base.core . I'm defining my main namespace in build.clj and passing it to :main in the uber fn, the pom.xml thats generated seems to be correct, and all the classes and files are present and correctly structured in target/classes as needed for it to compile. Unpacking the jar after its built I've confirmed the -main namespace is present, but still it cant be found despite my best efforts. Is there something obvious I'm missing here? My only guess is creating a source pom.xml that defines a main, to pass into write-pom , as I dont see Main-Class in the generated pom. But the attribute is obviously being set somewhere with the uber fn when it builds. Here is my build.clj, pretty much straight from the tools.build guide.

(ns build
    (:require [clojure.tools.build.api :as b]))

(def lib 'monorepo/project)
(def main 'monorepo.base.namespace_with_mainfn)
(def version (format "1.2.%s" (b/git-count-revs nil)))
(def class-dir "target/classes")
(def basis (b/create-basis {:project "deps.edn"}))
(def uber-file (format "target/%s-%s-standalone.jar" (name lib) version))


(defn clean [_]
      (b/delete {:path "target"}))

(defn prep [_]
      (b/write-pom {:class-dir class-dir
                    :lib lib
                    :version version
                    :basis basis
                    :src-dirs ["src"]})
      (b/copy-dir {:src-dirs ["../../bases/base" "."]
                   :target-dir class-dir}))

(defn uber [_]
      (b/compile-clj {:basis basis
                      :src-dirs ["src" "resources"]
                      :class-dir class-dir})
      (b/uber {:class-dir class-dir
               :main main
               :uber-file uber-file
               :basis basis}))

Alex Miller (Clojure team)22:09:38

the class not found is monorepo.base.core - your main namespace is monorepo.base.namespace_with_mainfn so those do not seem directly related

Alex Miller (Clojure team)22:09:00

just going from the error, do you see monorepo.base.core in your uberjar?

Alex Miller (Clojure team)22:09:02

re "I dont see `Main-Class` in the generated pom" - do you mean in the META-INF/MANIFEST.MF ?

Alex Miller (Clojure team)22:09:58

the pom is not used by Java to do anything, so is irrelevant here

AustinP22:09:41

Apologies I mistyped, the error message is indeed with the defined monorepo.base.namespace_with_mainfn . I do see monorepo.base.namespace_with_mainfn in my uberjar after its built

Alex Miller (Clojure team)22:09:28

that is, the uberjar should have monorepo/base/namespace_with_mainfn.class in it

AustinP22:09:29

yes, its inside the jar as a .class file

Alex Miller (Clojure team)22:09:13

and inside META-INF/MANIFEST.MF ?

AustinP22:09:41

I dont have a MANIFEST.MF under my META-INF. contents is: META-INF/maven/monorepo/project/pom.xml and pom.properties

Alex Miller (Clojure team)22:09:28

well something doesn't match up here. uber should always write a META-INF/MANIFEST.MF into the uber jar, and I don't know how it would be finding that class name otherwise

Alex Miller (Clojure team)22:09:55

you won't see this in the target/classes dir, only in the uberjar

Alex Miller (Clojure team)22:09:43

jar tf target/project-V-standalone.jar META-INF/MANIFEST.MF should verify it exists

AustinP22:09:33

oh sorry, yes inside the uberjar I have MANIFEST.MF and Main-Class is defined there correctly as monorepo.base.namespace_with_mainfn

Alex Miller (Clojure team)22:09:01

well, then I don't understand the error. what's the exact command you're running?

AustinP22:09:20

just java -jar target/project-standalone.jar

AustinP22:09:49

after running clj -T:build clean/prep/uber

Alex Miller (Clojure team)22:09:27

can you dump the full output?

AustinP22:09:08

Its just this short one

Error: Could not find or load main class monorepo.base.namespace_with_mainfn
Caused by: java.lang.ClassNotFoundException: monorepo.base.namespace_with_mainfn

Alex Miller (Clojure team)22:09:44

and you see jar tf target/project-standalone.jar monorepo/base/namespace_with_mainfn.class ?

AustinP23:09:11

waiting for it to uber again to see if this was the issue! It is there, but its under /classes/monorepo/base/namespace_with_mainfn.class , added classes in front of my main def in build.clj 🤞

AustinP23:09:16

Thinking I incorrectly assumed :class-dir was ignoring/handling that. will update when it finishes

Alex Miller (Clojure team)23:09:46

oh, well that's definitely not the right place, but that doesn't sound like the right fix

Alex Miller (Clojure team)23:09:29

I don't understand how you're getting that from the script above

Alex Miller (Clojure team)23:09:20

you should maybe clean target if you haven't been doing that, too

AustinP23:09:10

ahhh, yes finally finished

Error: Could not find or load main class classes.monorepo.project.namespace_with_mainfn
Caused by: java.lang.NoClassDefFoundError: monorepo/project/namespace_with_mainfn (wrong name: classes/monorepo/project/namespace_with_mainfn)

AustinP23:09:33

have been cleaning target each time. full clean/prep/uber

Alex Miller (Clojure team)23:09:38

the wrong path is being included in the uber jar, but I don't understand how you're getting that with the script above.

hiredman23:09:21

(def main 'monorepo.base.namespace_with_mainfn) is just wrong

hiredman23:09:43

main is just the namespace with the main defn

hiredman23:09:58

no path stuff, no extra bits at the front

Alex Miller (Clojure team)23:09:46

hard for me to tell whether that is correct or not without knowing what the namespaces are

AustinP23:09:02

that is just the namespace, as its shown at the top of the file with my -main function.

Alex Miller (Clojure team)23:09:15

yeah, so I don't think that is the wrong part

Alex Miller (Clojure team)23:09:05

the wrong part is that you are getting a classes/ prefix dir included but I can't tell whether this class is coming from some other part of the monorepo via a local dep or from this part as you have not provided enough information

Alex Miller (Clojure team)23:09:59

What's in ../../bases/base? What's in .? What's in target/classes after the copy? after the compile?

Alex Miller (Clojure team)23:09:16

the write-pom is not needed and can be removed

Alex Miller (Clojure team)23:09:54

what's in your deps.edn? are you using local/root deps?

AustinP23:09:01

This is a polylith monorepo, so the top level folder structure is

▾ workspace
  ▸ bases
  ▸ components
  ▸ development
  ▸ projects
Im in projects/thisProject using the deps.edn there that defines thisProject's base, components, etc. So in the copy-dir I go up 2 directories to the workspace, and back into bases/thisProjectsBase to get all the actual src code. "." is thisProject's folder that has the deps.edn and some other config files needed for compile. After the copy-dir , target/classes contains the folders from /bases/thisProjectsBase which are /src /cljs /resources , along with /node_modules /classes deps.edn shadow-cljs.edn etc that are in thisProject's folder

AustinP23:09:53

after compile it looks the same. and the uberjar is built as expected under thisProject/target

hiredman23:09:29

why does classes contain a classes dir?

hiredman23:09:45

ah, word wrapped, node_modules/classes

hiredman23:09:02

that does seem suspicious, it doesn't seem like node_modules should have a classes subdir, but I don't know enough about node and can't see how that would end up effecting the uberjar

AustinP23:09:04

no they actually are two separate ones. target/classes is where everything in the copy-dir is going, and shouldnt affect much. thisProject has a /classes folder that contains all the dependencies from deps.edn, so when I copy-dir "." , which is my thisProject dir, it copies over /classes as well into target/classes

hiredman23:09:32

what are the paths in your deps.edn? I bet your basis, is pointing to stuff up the directory tree, in conflict with the result of the copying everything into .

hiredman23:09:48

your deps.edn presumably has like a local dep on ../bases/subproject

hiredman23:09:26

(in which case I am not sure what the copy is for)