Fork me on GitHub
#clojure
<
2020-12-17
>
Alys Brooks01:12:15

Is the raw data available to the latest State of Clojure anywhere? I'd like to get a sense of how many people target Java versions after 11, but the survey only has the overlapping data, so I can't tell if the 10 percentish on 12, 13, and 14 are mostly distinct or mostly overlap. (Alternatively, is there a survey or data source of what Java versions Clojure projects typically use other than the State of Clojure?)

Alys Brooks01:12:54

FYI to people who haven't seen this before, it's a useful survey: https://clojure.org/news/2020/02/20/state-of-clojure-2020

seancorfield01:12:53

The raw data for all years is linked at the bottom of that article. 2020 is https://www.surveymonkey.com/results/SM-CDBF7CYT7/

seancorfield01:12:36

The totals shown in the table beneath the graph should indicate whether it was multiple-response (overlapping) or single-response I guess?

Alys Brooks01:12:31

Oh sorry. I was referring to the raw response data, which was available for some of the older surveys, like 2014, not the full SurveyMonkey report.

seancorfield01:12:02

This isn't detailed enough?

seancorfield01:12:43

Looking at the numbers, they add up to more that the total respondents so it must be a multiple-response question.

Alys Brooks03:12:46

To be honest, it gives me enough of an idea in practice. I was just hoping to have a more definitive answer to "how many people use non-LTS versions"

seancorfield03:12:36

Yeah, I was a bit surprised nearly 60% are still using 8.

dominicm22:12:00

I have no impetus to upgrade.

dominicm22:12:10

Just feels like risk, java 8 is great.

seancorfield22:12:37

We were originally on Oracle's JDK 8 so we had a good incentive to switch to OpenJDK 8 (we went with AdoptOpenJDK's build), and once we had decided to stop running on an Oracle "official" build, it just made sense for us to start testing on newer JDKs and there were enough differences between 8 and 11 to be a worthwhile upgrade (memory management, GC reporting, etc). We dev/test against 14 and 15 but haven't felt the need to roll it out to QA/production at this point (although 15 has some nice GC enhancements).

kwladyka12:12:37

RROR StatusLogger Unrecognized format specifier [d]
ERROR StatusLogger Unrecognized conversion specifier [d] starting at position 16 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [thread]
ERROR StatusLogger Unrecognized conversion specifier [thread] starting at position 25 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [level]
ERROR StatusLogger Unrecognized conversion specifier [level] starting at position 35 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [logger]
ERROR StatusLogger Unrecognized conversion specifier [logger] starting at position 47 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [msg]
ERROR StatusLogger Unrecognized conversion specifier [msg] starting at position 54 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [n]
ERROR StatusLogger Unrecognized conversion specifier [n] starting at position 56 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [d]
ERROR StatusLogger Unrecognized conversion specifier [d] starting at position 16 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [thread]
ERROR StatusLogger Unrecognized conversion specifier [thread] starting at position 25 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [level]
ERROR StatusLogger Unrecognized conversion specifier [level] starting at position 35 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [logger]
ERROR StatusLogger Unrecognized conversion specifier [logger] starting at position 47 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [msg]
ERROR StatusLogger Unrecognized conversion specifier [msg] starting at position 54 in conversion pattern.
ERROR StatusLogger Unrecognized format specifier [n]
ERROR StatusLogger Unrecognized conversion specifier [n] starting at position 56 in conversion pattern.
Do you know clean solution for this? log4j2 works in REPL, but not after make uberjar. I read this is about Log4j2Plugins.dat from plugins. I use deps.edn. I would never understand why logging in Java is so hard 🙂

kwladyka12:12:08

@U04V70XH6 I guess you are the one who can know the answer. It touch topic around https://github.com/seancorfield/depstar

kwladyka13:12:45

hmm I updated to new ver of depstar and I have different errors

kwladyka13:12:04

1) option 1

:exec-args {             :aot true
                                 :main-class api.core}
Exception in thread "main" java.lang.IllegalArgumentException: No implementation of method: :get-logger of protocol: #'clojure.tools.logging.impl/LoggerFactory found for class: nil
	at clojure.core$_cache_protocol_fn.invokeStatic(core_deftype.clj:583)
	at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:575)
	at clojure.tools.logging.impl$fn__881$G__865__888.invoke(impl.clj:25)
	at api.core$_main.invokeStatic(core.clj:43)
	at api.core$_main.invoke(core.clj:42)
	at clojure.lang.AFn.applyToHelper(AFn.java:152)
	at clojure.lang.AFn.applyTo(AFn.java:144)
	at api.core.main(Unknown Source)
2) option 2
:main-opts ["-m" "hf.depstar.uberjar" "api.jar" "-C" "-m" "api.core"]
2020-12-17 13:17:06,472 main ERROR Unable to locate plugin type for JsonTemplateLayout
2020-12-17 13:17:06,473 main ERROR Unable to locate plugin type for JsonTemplateLayout
2020-12-17 13:17:06,497 main ERROR Unable to locate plugin for JsonTemplateLayout
2020-12-17 13:17:06,508 main ERROR Could not create plugin of type class org.apache.logging.log4j.core.appender.ConsoleAppender for element Console: java.lang.NullPointerException java.lang.NullPointerException
	at org.apache.logging.log4j.core.config.plugins.visitors.PluginElementVisitor.findNamedNode(PluginElementVisitor.java:104)
	at org.apache.logging.log4j.core.config.plugins.visitors.PluginElementVisitor.visit(PluginElementVisitor.java:88)
	at org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.injectFields(PluginBuilder.java:185)
	at org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.build(PluginBuilder.java:121)
	at org.apache.logging.log4j.core.config.AbstractConfiguration.createPluginObject(AbstractConfiguration.java:1002)
	at org.apache.logging.log4j.core.config.AbstractConfiguration.createConfiguration(AbstractConfiguration.java:942)
	at org.apache.logging.log4j.core.config.AbstractConfiguration.createConfiguration(AbstractConfiguration.java:934)
	at org.apache.logging.log4j.core.config.AbstractConfiguration.doConfigure(AbstractConfiguration.java:552)
	at org.apache.logging.log4j.core.config.AbstractConfiguration.initialize(AbstractConfiguration.java:241)
	at org.apache.logging.log4j.core.config.AbstractConfiguration.start(AbstractConfiguration.java:288)
	at org.apache.logging.log4j.core.LoggerContext.setConfiguration(LoggerContext.java:622)
	at org.apache.logging.log4j.core.LoggerContext.reconfigure(LoggerContext.java:695)
	at org.apache.logging.log4j.core.LoggerContext.reconfigure(LoggerContext.java:712)
	at org.apache.logging.log4j.core.LoggerContext.start(LoggerContext.java:267)
	at org.apache.logging.log4j.core.impl.Log4jContextFactory.getContext(Log4jContextFactory.java:245)
	at org.apache.logging.log4j.core.impl.Log4jContextFactory.getContext(Log4jContextFactory.java:47)
	at org.apache.logging.log4j.LogManager.getContext(LogManager.java:174)
	at clojure.tools.logging$eval136.invokeStatic(NO_SOURCE_FILE:0)
	at clojure.tools.logging$eval136.invoke(NO_SOURCE_FILE)
	at clojure.lang.Compiler.eval(Compiler.java:7177)
	at clojure.lang.Compiler.eval(Compiler.java:7132)
	at clojure.core$eval.invokeStatic(core.clj:3214)
	at clojure.core$eval.invoke(core.clj:3210)
	at clojure.tools.logging.impl$log4j2_factory.invokeStatic(impl.clj:183)
	at clojure.tools.logging.impl$log4j2_factory.invoke(impl.clj:178)
	at clojure.lang.Var.invoke(Var.java:380)
	at clojure.tools.logging$call_str.invokeStatic(logging.clj:316)
` `

kwladyka13:12:36

all the time work in REPL

kwladyka22:12:32

I ended with

2020-12-17 22:45:53,526 main ERROR Unable to locate plugin type for JsonTemplateLayout
2020-12-17 22:45:53,527 main ERROR Unable to locate plugin type for JsonTemplateLayout
2020-12-17 22:45:53,550 main ERROR Unable to locate plugin for JsonTemplateLayout
2020-12-17 22:45:53,560 main ERROR Could not create plugin of type class org.apache.logging.log4j.core.appender.ConsoleAppender for element Console: java.lang.NullPointerException java.lang.NullPointerException
	at org.apache.logging.log4j.core.config.plugins.visitors.PluginElementVisito
after depstar update

kwladyka22:12:19

But I see this in verbose mode /Users/kwladyka/.m2/repository/org/apache/logging/log4j/log4j-layout-template-json/2.14.0/log4j-layout-template-json-2.14.0.jar so it should be there

kwladyka22:12:13

and this Found META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat in multiple dependencies, selecting the largest maybe this is the reason

seancorfield22:12:16

@U0WL6FA77 That message is expected, when multiple Log4j2Plugins.dat files are provided in dependencies. Selecting the largest is usually the correct strategy but perhaps you have an unusual library on your classpath that is pulling in a larger version that does not include the default plugins cache. If your project is on GitHub, I can take a look. If not, I can't debug it so I can't help you.

seancorfield22:12:15

This is a design problem with log4j2 that people have complained about for years and the maintainers have agreed it's a design problem and said they would change how it works in 3.0 but we have no idea when that will be release.

seancorfield22:12:44

That's a Leiningen plugin. It cannot be used with deps.edn.

kwladyka22:12:23

yes… just thinking if not log4j2 and choose something else. I am trying whole week to make a simple JSON log output in custom format…

kwladyka22:12:31

for google cloud jsonPayload logging

seancorfield22:12:41

When I write depstar 2.0, I will be able to have external dependencies and I can address the log4j2 problem using similar code. But for now depstar cannot have external dependencies.

❤️ 3
kwladyka22:12:23

Is any way to choose Log4j2Plugins.dat source?

kwladyka22:12:49

ok, this already help with decision 🙂

seancorfield22:12:19

As I said, if your project is on GitHub, I can take a look at it and see what might improve the log4j2 file selection.

seancorfield22:12:46

As you can see, logging in Java is a giant mess.

kwladyka23:12:15

yes, I really hate logging in Java.

kwladyka23:12:37

Java is the worst part of Clojure 😉

seancorfield23:12:25

I assume you'll be switching to a different logging library now?

kwladyka23:12:21

I guess logback as blind choice 🙂

kwladyka23:12:21

I will see the difference with log4j2. I have never do so deep logging things.

kwladyka23:12:35

hmm maybe I can also try to copy Log4j2Plugins.dat to jar as an extra command somehow form the right place if this will even work. I use only 1 plugin at once, so in this specific case it should.

kwladyka23:12:56

oh logback is not supported by clojure.tools.logging, I have bad luck today

kwladyka23:12:42

I had hope to add :exclude ["META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat"] and add this file into resources but it doesn’t work with the same exception

seancorfield00:12:40

Because it would have to be at that exact same relative path in resources and your :exclude would still exclude it.

kwladyka00:12:18

yes… so I don’t have more ideas

kwladyka22:12:22

FYI I found java.util.logging is the best for me to generate custom JSON and in general doing custom things with logs. This is the easiest and simplest way which I found. Here is my solution. Maybe you will find it useful one day :)

kwladyka22:12:22

(ns api.logs.google-jsonPayload
  (:require [jsonista.core :as json]
            [clojure.stacktrace :as stacktrace])
  (:import (java.util.logging LogManager ConsoleHandler Level SimpleFormatter LogRecord)
           ( StringWriter PrintWriter)))

(def cloud-run? (some? (System/getenv "K_REVISION")))

(def line-separator (System/getProperty "line.separator"))

(def ANSI-colours {:reset "\u001B[0m"
                   :black "\u001B[30m"
                   :red "\u001B[31m"
                   :green "\u001B[32m"
                   :yellow "\u001B[33m"
                   :blue "\u001B[34m"
                   :purple "\u001B[35m"
                   :cyan "\u001B[36m"
                   :white "\u001B[37m"})

(defn getCause [^Throwable thrown]
  (when-let [cause ^Throwable (.getCause thrown)]
    {:class (.getClass cause)
     :message (.getMessage cause)
     :print-thrownable (with-out-str (clojure.stacktrace/print-throwable cause))
     :print-stack-trace (with-out-str (stacktrace/print-stack-trace cause))
     :print-cause-trace (with-out-str (clojure.stacktrace/print-cause-trace cause))}))

(defn getThrown [^LogRecord record]
  (when-let [thrown ^Throwable (.getThrown record)]
    (let [cause (getCause thrown)]
      (cond->
        {:class (.getClass thrown)
         :message (.getMessage thrown)
         :print-thrownable (with-out-str (clojure.stacktrace/print-throwable thrown))
         :print-stack-trace (with-out-str (stacktrace/print-stack-trace thrown))
         :print-cause-trace (with-out-str (clojure.stacktrace/print-cause-trace thrown))}
        cause (assoc :cause cause)))))

(defn record->jsonPayload [^LogRecord record]
  (let [thrown ^Throwable (.getThrown record)
        thrown-map (getThrown record)
        level (.getLevel record)
        ex-info? (= clojure.lang.ExceptionInfo (class thrown))]
    (cond->
      {:severity (.getName level)
       :logger-name (.getLoggerName record)
       :message (if thrown
                  (let [w (StringWriter.)]
                    (.printStackTrace thrown (PrintWriter. w))
                    (.toString w))
                  (.getMessage record))}
      ex-info? (assoc :ex-info (ex-data thrown))
      (= Level/SEVERE level) (assoc "@type" "")
      thrown (assoc :logger-message (.getMessage record))
      thrown-map (assoc :thrown thrown-map))))

(def root-logger (when cloud-run?
                   (let [root-logger (.getLogger (LogManager/getLogManager) "")
                         google-jsonPayload (proxy [SimpleFormatter] []
                                              (^String format [^LogRecord record]
                                                (let [color (if (= Level/SEVERE (.getLevel record))
                                                              (:red ANSI-colours)
                                                              (:white ANSI-colours))]
                                                  (str
                                                    #_color
                                                    (json/write-value-as-string (record->jsonPayload record)
                                                                                #_(json/object-mapper {:pretty true}))
                                                    #_(:reset ANSI-colours)
                                                    line-separator))))
                         console-handler (doto (ConsoleHandler.)
                                           ;(.setUseParentHandlers false)
                                           (.setFormatter google-jsonPayload))]

                     (doseq [handler (.getHandlers root-logger)]
                       (.removeHandler root-logger handler))

                     (doto root-logger
                       (.setLevel Level/INFO)
                       (.addHandler console-handler)))))
`

kwladyka22:12:05

thrown-map is an addition for me for now to debug logs 😛 😉 You can remove it, so then cut code by half.

seancorfield00:12:29

camelCase naming is not idiomatic Clojure 😛

seancorfield00:12:28

Take a look at Throwable->map (in clojure.core) -- it would simplify your code.

kwladyka00:12:40

yes, it is not final solution

kwladyka00:12:50

thank you, I will that

MatthewLisp16:12:40

Hello clojurians Anyone here using Clojure + gRPC ? Curious about Which libs do you use?

p-himik20:12:41

I haven't watched it myself but it sounds like it may be of use to you: https://www.youtube.com/watch?v=iyHvwkc6Wis

Yehonathan Sharvit16:12:33

Is there a way to build a Clojure library that automatically runs some code when it is in the list of dependencies of an application?

vemv21:12:05

I think this is possible via Java static initializers The trick that java classes are present regardless whether you :import them. So you could invoke clojure from a java class static initializer. If such class is in the classpath, mission accomplished I doubt it's a pleasant "API" though :)

alexmiller16:12:29

Not that I’m aware of

alexmiller16:12:42

Seems like that would be a security issue :)

👍 12
borkdude16:12:18

Usually this is done by executing code at the top-level. This would be executed on require.

borkdude16:12:03

(I just fixed a couple of problems with some lib that did this when trying to use it with GraalVM :))

Ben Sless16:12:08

Is that something like emacsclient for nrepl?

borkdude16:12:42

not really, it's just reply but then compiled to native

Yehonathan Sharvit16:12:40

Would you guys say it’s a bad practice for a lib to run some code at the top level?

borkdude16:12:47

It depends.

borkdude16:12:58

What is your use case?

Yehonathan Sharvit16:12:20

I am providing a logger lib on top of timbre that meant to be used only internally

Yehonathan Sharvit16:12:50

The logger lib exposes a (configure settings) function

dpsutton16:12:17

sounds like something someone should require and call some kind of init function

Yehonathan Sharvit16:12:24

Before configure is called, the behaviour of the lib is undefined

Yehonathan Sharvit16:12:41

The problem is that settings are usually read from a service

borkdude16:12:43

Being explicit about this like @dpsutton suggests is usually best

dpsutton16:12:01

read from a service means network call?

borkdude16:12:33

The place where you would do this is in your apps -main method for example

Yehonathan Sharvit16:12:35

Yes: service means network call And sometimes, an app needs to log before reading from a service>

dpsutton16:12:55

and your logger is explicitly undefined behavior before this network call succeeds?

Yehonathan Sharvit16:12:56

The current solution is that the app calls configure twice

borkdude16:12:05

you could make configure idempotent

borkdude16:12:22

or accumulative

Yehonathan Sharvit16:12:58

Even if it’s accumulative, calling configure twice is cumbersome

Yehonathan Sharvit16:12:45

@dpsutton the logger could work before the network call as it can accept an empty map as settings

dpsutton16:12:55

i kinda want to recap as i don't quite have a good grasp. You need to initialize the logger with a network call. The logger's behavior before being initialized is undefined. You have seen instances of needing to log before initialization

dpsutton16:12:58

is all of this true?

Yehonathan Sharvit16:12:53

Correct. To be 100% precise: I need to initialize a logger with a map that is received from a netwok call

borkdude16:12:42

Is this in CLJS? Why would you do a network call to init a logger?

dpsutton16:12:03

so what's your question? You could block awaiting the results of the network call. But fundamentally you need to handle failing network calls or slow calls. And does the app crash?

Yehonathan Sharvit16:12:45

@U04V15CAJ It’ CLJ. The logger settings come from the network

dpsutton16:12:56

if not, and you allow work to happen while its waiting, do you buffer all of these logs and then send them through once the network has (hopefully) succeeded? Do you have a fallback and log it to a file and then push that file through whatever configured logging settings eventually return?

Yehonathan Sharvit16:12:20

I’m fine with blocking

dpsutton16:12:27

then do that

Yehonathan Sharvit16:12:59

What about logs that are sent before the request to the network is sent?

dpsutton16:12:03

have (my-logger/init!) make the network call and block. i'd do this before anyone needs to log otherwise you get into weird territory

dpsutton16:12:08

i wouldn't allow that

dpsutton16:12:29

but if you want to, then you need to figure out a way to store them and then log them in the future with the correct logging settings

dpsutton16:12:29

but this could be a crazy hard problem. what do you do with those logs when the network request fails, or the program dies before initializing.

Yehonathan Sharvit16:12:57

The way it works is

(init-logger! {})
(log "App is up")
(let [settings (read-from-network)]
    (init-logger! (:log-settings settings))
    (log "after settings are read"))
      

borkdude16:12:36

maybe not bring the app up before you have the required info to log

borkdude16:12:45

that's probably what I would do

Yehonathan Sharvit16:12:56

It’s not a big issue that the logs that are sent between the two init-logger! are not using the same settings

dpsutton16:12:19

then what's wrong with what you've posted?

borkdude16:12:46

you could queue the pending logs until you have the right info, another option

Yehonathan Sharvit16:12:51

It’s just that it’s cumbersome for the apps to have to call init-logger! twice

dpsutton16:12:17

why not have the init logging do that instead?

Yehonathan Sharvit16:12:34

I was thinking of calling (init-logger) in the library code

dpsutton16:12:37

you call init and it starts immediately with an empty config and does a network call, updating the logging settings when that resolves

Yehonathan Sharvit16:12:06

The logger doesn’t know about the network call. It’s an app related stuff

Yehonathan Sharvit16:12:18

There are dozens of apps

Yehonathan Sharvit16:12:27

that use the same logger lib

dpsutton16:12:48

i don't think i know how to help then

Yehonathan Sharvit17:12:31

My question is: is it a bad practice to call (init-logger!) inside the logger lib. @U04V15CAJ you wrote earlier that https://clojurians.slack.com/archives/C03S1KBA2/p1608223187132500 On what side of the “it depends” my use case fall?

borkdude17:12:00

considering that it is your own library, I wouldn't worry about it

borkdude17:12:23

maybe document it in the README

Yehonathan Sharvit17:12:34

You mean: it’s ok or not ok?

dpsutton17:12:53

we're just people on the internet. but he's saying its ok

Yehonathan Sharvit17:12:02

Why the fact that this is my own library matters?

dpsutton17:12:28

because this would be crazy for something the general population used. but for an internal logging library its tied to your particular usecase

dpsutton17:12:49

this is 100% your code top to bottom. so do what you need to solve your goals

Yehonathan Sharvit17:12:51

You mean that I hold both sides: the lib and the usage of the lib

Yehonathan Sharvit17:12:06

Ok. Thank you guys

borkdude17:12:09

I'm in a meeting now, have to drop

Célio16:12:11

Where can I find examples of tools.cli using the :in-order option? I’m having a hard time figuring out how to use it correctly.

p-himik16:12:33

Seems like you just have to pass it into cli/parse-opts, so something like:

(cli/parse-opts args specs :in-order true)
What have you tried that didn't work?

Célio17:12:06

@U2FRKM4TW Yes, I tried it but the missing piece is how to structure the specs so that they are interpreted correctly.

Célio17:12:29

The docs at https://github.com/clojure/tools.cli are very vague about that.

p-himik17:12:18

I don't think it has anything to do with the specs. As per the docs: "`cli/parse-opts` accepts an :in-order option that directs it to stop processing arguments at the first unrecognized token." What exactly are you trying to achieve?

p-himik17:12:59

So e.g. if your specs are [["-p" "--port PORT"]] then cli/parse-opts will parse only -p and --port. Anything else will become arguments.

Célio18:12:58

@U2FRKM4TW I’m trying to write a CLI tool that accepts commands and subcommands as command line arguments, like eg: the git CLI

p-himik18:12:59

If my understanding is correct, then you have to create a global spec that does not include your subcommands and use it with :in-order true. Then, you create a spec for each subcommand and feed it to cli/parse-opts along with the arguments received from the cli/parse-opts called with the global spec.

Célio18:12:55

Oh I see, that makes sense.

Célio19:12:53

Thanks @U2FRKM4TW I get it now.

👍 3
Yehonathan Sharvit16:12:42

Just a moment @ccidral. Let us complete the discussion

dpsutton16:12:56

we shoudl use a thread. people don't have to wait to ask other questions

👍 6
Yehonathan Sharvit16:12:37

Sorry @ccidral Keep going

😁 3
borkdude16:12:01

Maybe you should use locking around your config... j/k ;)

benny18:12:27

what’s the most efficient way to take a vector of maps like this

[{:a 1 :b "foo"} {:a 1 :b "bar"} {:a 2 :b "baz"}]
and turn it into this?
[{:a 1 :items [{:b "foo"} {:b "bar"}]} {:a 2 :items [{:b "baz"}]}]

benny19:12:42

I would imagine it’s a combination of group-by and map but trying to avoid multiple traversals if possible

nwjsmith19:12:03

You could probably rig up a single-traversal version with reduce

12
hiredman19:12:28

it depends on if things are sorted or not

hiredman19:12:42

but it is some kind of join

hiredman19:12:47

a self join

caumond21:12:00

use the specter library?

bringe23:12:22

Hello. Is there a way to include java source paths using clojure cli? Something like :java-source-paths for lein? It tried adding the path to :paths in deps.edn, but that didn't seem to work.

alexmiller01:12:48

tools.build is coming...

😮 9
catjam 9
noisesmith23:12:46

no, and furthermore clojure cli doesn't do java compilation for you - there are extension tasks that can do it though

bringe23:12:11

Could you elaborate on "extension tasks?" I'm unfamiliar

noisesmith23:12:50

the only one I notice right away today that compiles java is mevyn, and it does that by delegating to mvn

bringe01:12:43

Oh cool, ty

bringe23:12:12

Oh I see, thanks for the info