Fork me on GitHub
#duct
<
2018-11-23
>
flowthing07:11:38

I don't understand how to properly separate dev config from prod config in Duct 0.11. I'm trying to introduce Duct into an existing (minimal) project sort of piece by piece. Is there an example project somewhere I could look at? For example, I'd like to run :duct.compiler/sass only in dev.

flowthing07:11:26

I have read https://github.com/duct-framework/duct/blob/master/UPGRADING.md and I've managed to set up things so that when I do (duct/read-config "config.edn"), it includes the contents of my dev.edn under the duct.profile/dev key, but none of that is being executed.

flowthing09:11:29

Well, this is probably one problem, at least:

Syntax error (ClassCastException) compiling at (dev.clj:35:3).
class duct.logger.timbre.TimbreLogger cannot be cast to class clojure.lang.IFn (duct.logger.timbre.TimbreLogger is in unnamed module of loader clojure.lang.DynamicClassLoader @36f39f6a; clojure.lang.IFn is in unnamed module of loader 'app')
                 clojure.core/eval   core.clj: 3214
                               ...                 
      shadow.user$eval29750.invoke           :    1
shadow.user$eval29750.invokeStatic           :    1
                               ...                 
                     dev/eval29754    dev.clj:   35
                 integrant.repl/go   repl.clj:   54
               integrant.repl/prep   repl.clj:   16
                               ...                 
       clojure.core/alter-var-root   core.clj: 5505
       clojure.core/alter-var-root   core.clj: 5510
                               ...                 
            integrant.repl/prep/fn   repl.clj:   16
                  dev/eval29741/fn    dev.clj:   28
             duct.core/prep-config   core.clj:  193
            duct.core/build-config   core.clj:  182
            duct.core/fold-modules   core.clj:  145
               integrant.core/fold  core.cljc:  282
               clojure.core/reduce   core.clj: 6827
                               ...                 
            integrant.core/fold/fn  core.cljc:  282
         duct.core/fold-modules/fn   core.clj:  145

flowthing11:11:15

The same error is actually reproducible if you just add this into the config.edn of a clean Duct Leiningen template:

:duct.logger/timbre {:level     :info
                      :appenders {:duct.logger.timbre/println #ig/ref :duct.logger.timbre/println}}

 :duct.logger.timbre/println {}

flowthing11:11:08

@U0BKWMG5B Do you want an issue for this, too? I guess it belongs in duct-framework/logger.timbre?

flowthing11:11:19

I think the same error occurs whenever ig/init-key returns something that satisfy ifn?.

weavejester12:11:39

Have you moved all of your non-module keys into profiles? It sounds like you haven't done that step.

flowthing12:11:19

Well, it's unclear to me which keys are module keys and which are non-module keys.

weavejester12:11:49

Anything beginning with :duct.module or :duct.profile is a module key.

flowthing12:11:22

Okay. So :duct.logger/timbreshould go under :duct.profile/prod?

weavejester12:11:26

In 0.10 and below, modules were mixed with non-module keys. In 0.11, there separation is explicit.

weavejester12:11:31

Yes, that's right.

flowthing12:11:52

Or can it also go under :duct.profile/base?

weavejester12:11:56

Assuming you want to change the default :duct.module/logging settings.

weavejester12:11:25

It depends if you want the configuration in all profiles, in which case put it in base, or just in production, in which case put it in prod.

flowthing12:11:48

Okay. I think I gave that a try already, but I probably messed something up.

flowthing12:11:59

Let me give it a go.

flowthing13:11:31

Well, I have a config.edn like this:

{:duct.profile/base            {:duct.core/project-ns       

                                :duct.logger/timbre         {:set-root-config? true
                                                             :level            :info
                                                             :appenders        {:duct.logger.timbre/println #ig/ref :duct.logger.timbre/println}}

                                :duct.logger.timbre/println {}}

 :duct.profile/dev             #duct/include "dev"
 :duct.profile/local           #duct/include "local"
 :duct.profile/prod            {}

 :duct.module/logging          {}}

flowthing13:11:16

And then I do (repl/set-prep! #(duct/prep-config (read-config) [:duct.profile/dev])) and (go) and I get Assert failed: (map? config) from ig/prep.

flowthing13:11:34

But I guess there must still be something wrong with my config...

weavejester13:11:05

Does the assert have a stacktrace associated with it?

flowthing13:11:07

Assert failed: (map? config)
Syntax error compiling at (/foobar/dev/src/dev.clj:45:3).
  at clojure.lang.Compiler.load(Compiler.java:7647)
  at shadow.user$eval30043.invokeStatic(Unknown Source)
  at shadow.user$eval30043.invoke(Unknown Source)
  at clojure.lang.Compiler.eval(Compiler.java:7176)
  at clojure.lang.Compiler.eval(Compiler.java:7131)
  at clojure.core$eval.invokeStatic(core.clj:3214)
  at clojure.core$eval.invoke(core.clj:3210)
  at clojure.main$repl$read_eval_print__9068$fn__9071.invoke(main.clj:414)
  at clojure.main$repl$read_eval_print__9068.invoke(main.clj:414)
  at clojure.main$repl$fn__9077.invoke(main.clj:435)
  at clojure.main$repl.invokeStatic(main.clj:435)
  at clojure.main$repl.doInvoke(main.clj:345)
  at clojure.lang.RestFn.invoke(RestFn.java:1523)
  at nrepl.middleware.interruptible_eval$evaluate$fn__20103.invoke(interruptible_eval.clj:87)
  at clojure.lang.AFn.applyToHelper(AFn.java:152)
  at clojure.lang.AFn.applyTo(AFn.java:144)
  at clojure.core$apply.invokeStatic(core.clj:665)
  at clojure.core$with_bindings_STAR_.invokeStatic(core.clj:1973)
  at clojure.core$with_bindings_STAR_.doInvoke(core.clj:1973)
  at clojure.lang.RestFn.invoke(RestFn.java:425)
  at nrepl.middleware.interruptible_eval$evaluate.invokeStatic(interruptible_eval.clj:85)
  at nrepl.middleware.interruptible_eval$evaluate.invoke(interruptible_eval.clj:54)
  at nrepl.middleware.interruptible_eval$interruptible_eval$fn__20146$fn__20149.invoke(interruptible_eval.clj:218)
  at nrepl.middleware.interruptible_eval$run_next$fn__20141.invoke(interruptible_eval.clj:186)
  at clojure.lang.AFn.run(AFn.java:22)
  at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
  at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
  at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.AssertionError: Assert failed: (map? config)
  at integrant.core$prep.invokeStatic(core.cljc:412)
  at integrant.core$prep.invoke(core.cljc:412)
  at integrant.core$prep.invokeStatic(core.cljc:417)
  at integrant.core$prep.invoke(core.cljc:412)
  at duct.core$prep_config.invokeStatic(core.clj:195)
  at duct.core$prep_config.invoke(core.clj:184)
  at dev$eval30027$fn__30028.invoke(dev.clj:41)
  at integrant.repl$prep$fn__29833.invoke(repl.clj:16)
  at clojure.lang.AFn.applyToHelper(AFn.java:154)
  at clojure.lang.AFn.applyTo(AFn.java:144)
  at clojure.lang.Var.alterRoot(Var.java:308)
  at clojure.core$alter_var_root.invokeStatic(core.clj:5510)
  at clojure.core$alter_var_root.doInvoke(core.clj:5505)
  at clojure.lang.RestFn.invoke(RestFn.java:425)
  at integrant.repl$prep.invokeStatic(repl.clj:16)
  at integrant.repl$prep.invoke(repl.clj:14)
  at integrant.repl$go.invokeStatic(repl.clj:54)
  at integrant.repl$go.invoke(repl.clj:53)
  at dev$eval30047.invokeStatic(dev.clj:45)
  at dev$eval30047.invoke(dev.clj:45)
  at clojure.lang.Compiler.eval(Compiler.java:7176)
  at clojure.lang.Compiler.load(Compiler.java:7635)
  ... 27 more

weavejester13:11:45

What does your dev.edn file look like?

flowthing13:11:00

{:shadow.cljs/watch  {:id :app}

 :duct.compiler/sass {:source-paths  ["resources/sass"]
                      :include-paths ["node_modules"]
                      :output-path   "resources/public/css"}}

flowthing13:11:38

Hmm... does dev.edn need to have duct.profile/dev as the top-level key?

weavejester13:11:04

No, because it's included via #duct/include

weavejester13:11:18

The resource is literally inserted verbatim where the reader tag is.

flowthing13:11:50

It must be something odd in my project because if I copy-paste those same config files into a clean Leiningen Duct template, I don't get that error.

flowthing13:11:57

I'll keep digging. Many thanks for the help.

weavejester13:11:02

Okay, weird, I'm not seeing anything obviously wrong here. Would you mind running a couple of commands for me?

flowthing13:11:52

FWIW, this is not a Leiningen project I'm working on. It's a deps.edn project. Not sure whether it matters.

weavejester13:11:29

(-> (read-config) (doto ig/load-namespaces) (build-config [:duct.profile/dev]))

weavejester13:11:31

Oh, another thing to try is to shut down your REPL, clean your target directory, and restart your REPL.

weavejester13:11:43

Since multimethods can be missed by reloads.

flowthing13:11:46

That yields nil.

flowthing13:11:04

(Assuming build-config is duct.core/build-config.)

weavejester13:11:51

Does the same thing occur when you restart your repl?

flowthing13:11:47

Yes. I cleared everything I can think of.

weavejester13:11:24

Are the dependencies the same as the ones in the template?

weavejester13:11:45

:dependencies [[org.clojure/clojure "1.10.0-beta4"]
                 [duct/core "0.7.0-beta2"]
                 [duct/module.logging "0.4.0-beta1"]]

flowthing13:11:27

No! I had the wrong version (0.3.1) of duct/module.logging. picard-facepalm

flowthing13:11:41

That solved the assertion error. Many thanks!

weavejester13:11:56

No problem 🙂

weavejester13:11:33

Actually, let me open an issue for this to give this a clearer error message.

👍 1
flowthing13:11:17

As a bonus, I now understand much better how the whole thing works. The only thing I'm still not 100% clear on is the module/non-module distinction. For example, this page https://github.com/duct-framework/duct/wiki/Modules lists both logger.timbre and module.logging as modules, but logger.timbre actually isn't…?

weavejester13:11:57

Oh, that page wasn't added by me 🙂

weavejester13:11:20

I'll make a note to fix that page before release.

weavejester13:11:35

With regard to modules/non-modules, anything beginning with :duct.module or :duct.profile is a module. In 0.11, a module is a function that transforms a configuration, so config -> config.

flowthing13:11:27

That latter explanation was what I was looking for — thanks!

weavejester13:11:47

In 0.11, a configuration of modules is initiated into transformation functions, and then applied in dependency order to an empty map. This produces a new configuration, which is then initiated.

weavejester13:11:20

You can think of Duct's configuration as a "higher-order configuration", in the same way that we have higher-order functions.

weavejester13:11:46

Duct's config initiates into an Integrant config, which is then initiated into a system.

flowthing13:11:48

Right, so modules transform configurations and non-modules are things that contain stateful resources etc.

flowthing13:11:14

Also, everything works great now bananadance

weavejester13:11:42

The idea is that you can write a module that adds a whole bunch of configuration so you can abstract common patterns of config.

weavejester13:11:14

In the same way that higher-order functions allow abstraction of code patterns, like map, filter, reduce, etc.

flowthing13:11:26

Yes, that makes sense.

flowthing13:11:39

Might be useful to add at least one non-module key in the example here https://github.com/duct-framework/duct/blob/master/UPGRADING.md to make things clearer.

flowthing13:11:19

Where it says ;; ... more non-module keys, maybe have e.g. :duct.logger/timbre to make it clear you have to put it there.

flowthing13:11:53

Also, that page could maybe also make the module/non-module distinction explicit.

weavejester13:11:37

Those are good ideas.