Skip to content

gen_srcs: fix cljs goog deps and canonicalize output for gazelle#104

Open
miridius wants to merge 3 commits into
mainfrom
dave/gen-srcs-goog-and-dict-fixes
Open

gen_srcs: fix cljs goog deps and canonicalize output for gazelle#104
miridius wants to merge 3 commits into
mainfrom
dave/gen-srcs-goog-and-dict-fixes

Conversation

@miridius
Copy link
Copy Markdown
Contributor

@miridius miridius commented Jun 1, 2026

What

  • cljs goog.* requires now resolve to ClojureScript's label (@deps//:org_clojure_clojurescript). They were silently dropped, because goog.* ships inside the ClojureScript jar rather than as a scannable namespace.
  • Non-empty dicts (e.g. env = {…}) emit one entry per line, matching gazelle's renderer.
  • Rules are ordered lexically by basename, replacing the arbitrary (though already deterministic) group-by hash-map order.

Why

Found while dogfooding the new gazelle plugin against gen_srcs. For gazelle to replace gen_srcs as a drop-in, its output has to match gen_srcs byte-for-byte — so gen_srcs's output must be correct (the missing goog deps were a real bug), canonical (dict line-breaking), and reproducible by a non-Clojure tool. gen_srcs is already deterministic, but a lexical sort is trivial for the Go plugin to reproduce whereas Clojure's group-by hash order is not.

@miridius miridius force-pushed the dave/gen-srcs-goog-and-dict-fixes branch from 506a25e to 78e0697 Compare June 1, 2026 22:11
miridius and others added 3 commits June 2, 2026 01:24
goog.* namespaces are Closure Library, shipped transitively inside the
ClojureScript jar rather than as scannable cljs namespaces, so gen_srcs
silently dropped them from a file's deps. Route them to cljs.core's
label so the dependency is emitted.

Co-authored-by: Claude <noreply@anthropic.com>
buildifier keeps a single-entry dict inline, but gazelle's renderer puts
every non-empty dict one entry per line. buildifier accepts both, so it
never converges the two tools; emit non-empty dicts multi-line so gen_srcs
matches gazelle. Only {} stays inline.

Tested with property-based specs, which add test.check and a clj-kondo
lint-as for defspec.

Co-authored-by: Claude <noreply@anthropic.com>
gen-dir's rule order came from group-by hash-map iteration — deterministic
but arbitrary. A gazelle plugin reproducing gen_srcs output has to match it
byte-for-byte, and a lexical sort is trivial to reproduce whereas Clojure's
HAMT hash order is not. Aggregation rules stay emitted last.

Co-authored-by: Claude <noreply@anthropic.com>
@miridius miridius force-pushed the dave/gen-srcs-goog-and-dict-fixes branch from 78e0697 to 25fed23 Compare June 1, 2026 23:26
@miridius miridius marked this pull request as ready for review June 1, 2026 23:28
@miridius miridius requested a review from a team June 1, 2026 23:28
Comment on lines +231 to +233
(let [entries (->> x
(sort-by (comp emit-bazel* key))
(mapv (fn [[k v]] (str (emit-bazel* k) ": " (emit-bazel* v)))))]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are always small so probably doesn't matter, but you could mapv first and just sort after, and I think you'll get the same coll

Comment on lines +553 to +558
ns (cond
;; goog.* (Closure Library) ships transitively with ClojureScript;
;; resolve it to cljs.core's label.
(and (= :cljs platform) (goog-ns? ns)) 'cljs.core
(= :cljs platform) (or (cljs-auto-alias src-ns->label dep-ns->label ns) ns)
:else ns)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a bit tricky to follow the branches on first glance. could extract a fn and thread it here with e.g.

(cond-> ns (= :cljs platform) resolve-cljs-ns

Comment on lines 842 to 847
(->> paths
(group-by fs/basename)
;; group-by orders by hash; sort lexically
(sort-by key)
(mapcat (fn [[_base paths]]
(ns-rules args paths)))))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

paths is already sorted by str above, so we could avoid the group-by and so something like:

(->> paths
     (partition-by fs/basename)
     (mapcat (fn [paths] (ns-rules args paths))))

(finally
(fs/rm-rf (.toPath dir)))))))

(deftest ns-rules-cljs-goog-requires-route-to-clojurescript
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these tests are quite verbose and have lots of duplication. claude suggested:

(defn- deps-for
  "Write one source file into a fresh temp dir, run ns-rules, return its deps.
   Cleans up the temp dir."
  [filename content dep-ns->label]
  (let [dir (make-temp-dir)]
    (try
      (-> (gb/ns-rules (minimal-args dir dep-ns->label)
                       [(write-file dir filename content)])
          extract-deps)
      (finally (fs/rm-rf (.toPath dir))))))
(deftest ns-rules-cljs-goog-requires-route-to-clojurescript
  (testing "goog.* requires in a .cljs file resolve to cljs.core's label"
    (let [deps (deps-for "ui.cljs"
                         "(ns example.ui (:require [goog.string :as gstr] [goog.dom :as dom]))"
                         {:clj {} :cljs {'cljs.core "org_clojure_clojurescript"}})]
      (is (some #(= "@deps//:org_clojure_clojurescript" %) deps)
          "goog.* should resolve to ClojureScript's label, not be dropped"))))

; ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants