2
votes

I have a project with following ns:

  • processor.bus <- general protocol for bus operations
  • processor.core <- main class to run
  • processor.pubsub <- concrete bus methods

in processor.pubsub I have following:

(ns processor.pubsub
  (:gen-class))

(defrecord PubsubBus [client])

; + other stuff related to this implementation

In processor.bus I have:

(ns tiptop.processor.bus
  (:gen-class)
  (:import [processor.pubsub PubsubBus]))

(defprotocol SendToBus
  (send-line! [self json]))

(extend-type PubsubBus
  SendToBus
  ....) 

The problem that Lein doesn't compile namespaces in right order. I get following error:

$ lein compile
Compiling user
Compiling processor.auth
Compiling processor.bus
java.lang.ClassNotFoundException: processor.pubsub.PubsubBus, compiling:(bus.clj:1:1)
Exception in thread "main" java.lang.ClassNotFoundException: processor.pubsub.PubsubBus, compiling:(bus.clj:1:1)

Notice it tries to compile my ns in alphabetical order (auth -> bus -> pubsub), instead if dependency order.

Of course I could precompile pubsub.clj before, like:

$ lein compile processor.pubsub
$ lein compile processor.bus
$ lein compile

But it doesn't seems quite right for me. What if I'll have more such dependent namespaces?

How I can tell Lein in which order it should compile my namespaces? Or maybe i'm missing something to configure in project.clj? I have :aot :all if it matters

1
Are you sure that it's just alphabetical order? Have you tried changing the names of your namespaces and recompiling? - Sam Estep

1 Answers

6
votes

Leiningen does not do anything to determine namespace dependencies - it simply compiles the namespaces that you tell it to. It is the Clojure compiler that handles namespace dependencies via the require (and obsolete use) built-ins.

In this case, you need to :require the namespace that defines the generated class before you can import it. Otherwise, you're relying on the imported class being present on the classpath as a side effect of some other operation (loading the namespace that defines it in the REPL, a previous lein compile command, etc). Adding an explicit :require in the namespace definition ensures the class is defined before it's imported:

(ns processor.bus
  (:gen-class)
  (:require [processor.pubsub])
  (:import [processor.pubsub PubsubBus]))

A couple of other notes:

  • I doubt :gen-class is doing what you think it's doing in these namespace declarations. It does not cause class files to get written for the protocol and records defined in these namespaces; that's what the :aot key is for in the Leiningen project.clj. The :gen-class flag as it's used here will cause classes named processor.bus and processor.pubsub to be generated.
  • It's unusual to see protocol implementation details for concrete record types specified via extend-type in the same namespace where the protocol is defined. The typical use case for extend-type is to extend your protocol to work with types that are beyond your control, like those built in to Clojure or defined in third-party libraries. When the protocol and record are defined in the same project, it's more common to see the protocol implementation defined inline as part of the defrecord body.