openlang

Keep loops and conditions out of build.gradle

Ran into a build.gradle where half the logic lived in closures that had their delegate reassigned at runtime. Took a while to work out what anything meant.

Groovy makes this easy. A closure resolves unqualified names against its owner first by default, and a DSL flips that by setting resolveStrategy to Closure.DELEGATE_FIRST. Fine for a documented configuration block. Miserable when a helper in another file swaps the delegate and a bare version starts meaning something else without a word.

Build scripts get read far more than they get written, mostly by someone who only wants to bump one dependency. So I keep to one rule. Declarative blocks in the script. Anything with a loop or a condition goes into a plugin class with a name and a test. No metaClass tricks in the build, ever.

The usual pushback is that it's more files. Yes. It's also the only way the next reader finds out what version refers to without stepping through the build in a debugger.

后续讨论 6

顺着彼此的依据读下去
  1. 固定链接

    Ruby has the same trap, and there the instance variables are what catch readers. Most Ruby DSLs run the block with instance_eval, so inside it self is the receiver, bare method calls go there, and instance variables belong to it too. So @config inside the block is not the @config of the code around it, and nothing on the page says so. Same temptation as the Groovy delegate, in a nicer coat.

    For anything a stranger will edit, I prefer blocks that yield the object, written as configure do |c| with c.version = ... on each line. Two more characters per line, and every name has a visible owner.

  2. 固定链接

    I only know Gradle from the outside, but the test part is the bit I'd push hardest. Build logic with a test reads like a spec of what the build does. Without one, the script is the only description, and it's written in the least readable dialect in the repo.

    Even one example per custom task helps. Given this input tree, these files come out. A newcomer learns more from that than from any comment above the task. And when someone changes the task, the example breaks instead of the release.

    Do the plugin tests in your setup run on every change, or only when someone remembers?

  3. 固定链接

    Different ecosystem, but tsconfig files rot the same way. A root config with several layers of extends and per-package overrides is as hard to read as any clever build script, and nobody can say which option wins without running tsc --showConfig. What I settled on there is one base file and one extends per package, nothing deeper.

  4. 固定链接

    Since TypeScript 5.0 extends takes an array, with later entries overriding earlier ones, so a package can list two flat bases without a chain. Which option won is still something I only trust tsc --showConfig for.

  5. 固定链接

    The part that stung in your build file, a helper in another file swapping the delegate, is what made me fond of how Lua loads config files. The environment is handed over once, at the place the file is loaded, and nowhere else.

    local cfg = {}
    local chunk = assert(load("version = '1.4.2'", "build.cfg", "t", cfg))
    chunk()
    print(cfg.version)  --> 1.4.2

    Every bare name in that chunk resolves against cfg, and since cfg has no print or require in it, the config can't call anything the loader didn't put there. It's the same trick as a delegate, really, except the swap sits in one line you can find with grep. I was a little surprised how much calmer the scripts I read became once that line was the only place to look.

同一个问题的其他角度

  1. Launching the consumer on a stopped event

    Got a question about a supervisor rule that treats a stopped process as a finished one, and a tiny repro makes the problem ob…

    回复 17最近活动 bashpython
  2. What does a --hash line actually pin?

    A requirements file with --hash on every line installs cleanly on my laptop and fails with a hash mismatch in a fresh Linux c…

    回复 8最近活动 python
  3. After the Kotlin DSL move, subprojects can no longer find implementation

    Moving an old multi-project build from the Groovy DSL to the Kotlin DSL. The root build.gradle had this block. The root I con…

    回复 2最近活动 kotlingroovy