Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 31 additions & 21 deletions MODULE.bazel
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
module(name = "rules_clojure",
version = "0.5")
module(
name = "rules_clojure",
version = "0.5",
)

bazel_dep(name = "rules_java", version = "8.16.1")
bazel_dep(name = "rules_jvm_external", version = "6.8")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

bazel_dep(name = "buildifier_prebuilt", version = "8.5.1.2", dev_dependency = True)

maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
maven.install(
name = "maven_deps",
artifacts = [
Expand All @@ -13,50 +17,56 @@ maven.install(
"org.clojure:core.specs.alpha:0.4.74",
"org.clojure:data.json:2.4.0",
"org.clojure:tools.deps:0.28.1578",
"org.clojure:tools.namespace:1.1.0"
"org.clojure:tools.namespace:1.1.0",
],
fail_if_repin_required = True,
repositories = [
"https://repo1.maven.org/maven2",
"https://repo.clojars.org/"
"https://repo.clojars.org/",
],
fail_if_repin_required = True
)

maven.amend_artifact(
name = "maven_deps",
coordinates = "org.clojure:clojure",
exclusions = ["org.clojure:spec.alpha",
"org.clojure:core.specs.alpha"]
exclusions = [
"org.clojure:spec.alpha",
"org.clojure:core.specs.alpha",
],
)

maven.amend_artifact(
name = "maven_deps",
coordinates = "org.clojure:spec.alpha",
exclusions = ["org.clojure:clojure",
"org.clojure:core.specs.alpha"]
exclusions = [
"org.clojure:clojure",
"org.clojure:core.specs.alpha",
],
)

maven.amend_artifact(
name = "maven_deps",
coordinates = "org.clojure:core.specs.alpha",
exclusions = ["org.clojure:clojure",
"org.clojure:spec.alpha"]
exclusions = [
"org.clojure:clojure",
"org.clojure:spec.alpha",
],
)

# used for testing
maven.install(
name = "clojure_old",
artifacts = [
"org.clojure:clojure:1.8.0",],
"org.clojure:clojure:1.8.0",
],
fail_if_repin_required = True,
repositories = [
"https://repo1.maven.org/maven2",
"https://repo.clojars.org/"
])

"https://repo.clojars.org/",
],
)
use_repo(maven, "maven_deps")
use_repo(maven, "clojure_old")

bazel_dep(name = "example-simple", version = "0.0.0", dev_dependency = True)
local_path_override(module_name = "example-simple",
path = "examples/simple")
local_path_override(
module_name = "example-simple",
path = "examples/simple",
)
5 changes: 4 additions & 1 deletion MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

183 changes: 137 additions & 46 deletions src/rules_clojure/gen_build.clj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,64 @@
val
(throw (ex-info "value does not conform" (s/explain-data spec val)))))

(def ^:private attr-priority
"Attribute sort priorities based on a subset of buildifier's NamePriority table.
Lower numbers sort first. Unlisted attributes default to 0."
{:name -99
:src -92
:srcs -90
:outs -88
:hdrs -87
:size -95
:timeout -94
:testonly -93
:exports 2
:runtime_deps 3
:deps 4
:implementation 5
:implements 6
:alwayslink 7})

(def ^:private sortable-list-attrs
"List attributes that buildifier sorts alphabetically.
Superset of attrs we generate, from buildifier's IsSortableListArg."
#{:data :default_visibility :deps :exports :hdrs :implementation_deps
:outs :private_deps :resources :runtime_deps :srcs :tags :tests :tools})

(defn- build-file-header
"Starlark docstring header for generated BUILD files."
[generator-description]
(str "\"\"\"\nDo not edit this file manually!\n"
"It is automatically generated by " generator-description ".\n\"\"\"\n\n"))

(defn- sort-kwargs-entries
"Sort kwargs entries by buildifier priority: name first, then by priority
bucket, then alphabetically within bucket."
[entries]
(sort-by (fn [[k _]] [(get attr-priority k 0) (name k)]) entries))

(defn- label-sort-key
"Sort key matching buildifier's byStringExpr: phase by prefix (: < // < @),
then by the label with colons replaced by dots (equivalent to buildifier's
segment-by-segment comparison since segments contain no dots)."
[s]
(let [phase (cond
(str/starts-with? s ":") 1
(str/starts-with? s "//") 2
(str/starts-with? s "@") 3
:else 0)]
[phase (str/replace s ":" ".")]))

(defn- emit-vector
"Emit a vector of pre-stringified items. Inline when count <= 1, otherwise
multi-line with 8-space element indent + 4-space closing bracket."
[items]
(if (<= (count items) 1)
(str "[" (str/join ", " items) "]")
(str "[\n "
(str/join ",\n " items)
",\n ]")))

(defn emit-bazel-dispatch [x]
(class x))

Expand All @@ -92,8 +150,18 @@
(defmethod emit-bazel* :default [x]
(assert false (print-str "don't know how to emit" (class x))))

(defn- shorten-label
"Buildifier rule: collapse `//path/to/pkg:pkg` to `//path/to/pkg` when the
target name equals the directory basename. Leaves all other strings
unchanged."
[s]
(let [m (re-matches #"(.*//.+)/([^/:]+):([^/:]+)" s)]
(if (and m (= (nth m 2) (nth m 3)))
(str (nth m 1) "/" (nth m 2))
s)))

(defmethod emit-bazel* String [x]
(pr-str x))
(pr-str (shorten-label x)))

(defmethod emit-bazel* Keyword [x]
(name x))
Expand All @@ -106,36 +174,59 @@
true "True"
false "False"))

(defn emit-bazel-kwargs [kwargs]
(defn emit-bazel-kwargs
"Return a seq of \"k = v\" entry strings, sorted by buildifier attribute priority.
Sortable list-typed values (per `sortable-list-attrs`) are sorted by `label-sort-key`.
Vector values render inline (<=1 element) or multi-line via `emit-vector`."
[kwargs]
{:pre [(map? kwargs)]
:post [(string? %)]}
:post [(sequential? %)]}
(->> (:x kwargs)
sort-kwargs-entries
(map (fn [[k v]]
(print-str (emit-bazel* k) "=" (emit-bazel* v))))
(interpose ",\n\t")
(apply str)))
(let [v-str (cond
(and (vector? v) (contains? sortable-list-attrs k))
(emit-vector (->> v (sort-by label-sort-key) (mapv emit-bazel*)))
(vector? v)
(emit-vector (mapv emit-bazel* v))
:else (emit-bazel* v))]
(str (emit-bazel* k) " = " v-str))))))

(defmethod emit-bazel* KeywordArgs [x]
(emit-bazel-kwargs x))

(defmethod emit-bazel* IPersistentList [[name & args]]
;; function call
(let [args (when (seq args)
(mapv emit-bazel* args))]
(str name "(" (apply str (interpose ", " args)) ")")))
(str/join ", " (emit-bazel-kwargs x)))

(defmethod emit-bazel* IPersistentList [[fn-name & args]]
;; Buildifier-canonical function call:
;; - positional-only OR single-kwarg-no-positional: single-line.
;; - mixed or >1 kwarg: multi-line with 4-space arg indent and trailing comma.
;; - load() args after the bzl-path are sorted alphabetically (buildifier rule).
(let [kwarg-args (filter kwargs? args)
positional (remove kwargs? args)
positional-strs (cond->> (mapv emit-bazel* positional)
(= 'load fn-name) (#(into [(first %)] (sort (rest %)))))
kwarg-entries (vec (mapcat emit-bazel-kwargs kwarg-args))
kwarg-count (count kwarg-entries)
positional-count (count positional-strs)]
(if (or (zero? kwarg-count)
(and (zero? positional-count) (= 1 kwarg-count)))
(str fn-name "(" (str/join ", " (concat positional-strs kwarg-entries)) ")")
(str fn-name "(\n "
(str/join ",\n " (concat positional-strs kwarg-entries))
",\n)"))))

(defmethod emit-bazel* IPersistentVector [x]
(str "[" (->> x
(map emit-bazel*)
(interpose ",")
(apply str)) "]"))
;; Standalone vector (non-kwarg context): inline. Kwarg-context formatting
;; with sorting and multi-line is handled in emit-bazel-kwargs.
(str "[" (->> x (map emit-bazel*) (str/join ", ")) "]"))

(defmethod emit-bazel* IPersistentMap [x]
(str "{" (->> x
(map (fn [[k v]]
(str (emit-bazel* k) " : " (emit-bazel* v))))
(interpose ",")
(apply str)) "}"))
;; Buildifier-canonical dict literal: `{"k": "v"}` (no space before colon).
(str "{"
(->> x
(map (fn [[k v]]
(str (emit-bazel* k) ": " (emit-bazel* v))))
(str/join ", "))
"}"))

(s/fdef emit-bazel :args (s/cat :x ::bazel) :ret string?)
(defn emit-bazel
Expand Down Expand Up @@ -726,32 +817,30 @@
(mapcat (fn [[_base paths]]
(ns-rules args paths)))))

content (str "#autogenerated, do not edit\n"
(emit-bazel (list 'package (kwargs {:default_visibility ["//visibility:public"]})))
"\n"
content (str (build-file-header "the `//:gen_srcs` target from `rules_clojure`")
(emit-bazel (apply list 'load "@rules_clojure//:rules.bzl"
(cond-> ["clojure_library" "clojure_test"]
has-binary? (conj "clojure_binary"))))
"\n\n"
(emit-bazel (list 'package (kwargs {:default_visibility ["//visibility:public"]})))
"\n"
"\n"
(str/join "\n\n" rules)
"\n"
"\n"
(when (seq rules)
(str "\n" (str/join "\n\n" rules) "\n"))
(when (or (seq paths) (seq clj-subdirs))
(str
(emit-bazel (list 'clojure_library (kwargs {:name "__clj_lib"
:deps (vec
(concat
(dedupe (map (fn [p] (str ":" (fs/basename p))) paths))
(map (fn [p]
(str "//" (fs/path-relative-to deps-edn-dir p) ":__clj_lib")) clj-subdirs)))})))
"\n"
"\n"
(emit-bazel (list 'filegroup (kwargs (merge
{:name "__clj_files"
:srcs (mapv fs/filename paths)
:data (mapv (fn [p]
(str "//" (fs/path-relative-to deps-edn-dir p) ":__clj_files")) clj-subdirs)})))))))]
(str "\n"
(emit-bazel (list 'clojure_library (kwargs {:name "__clj_lib"
:deps (vec
(concat
(distinct (map (fn [p] (str ":" (fs/basename p))) paths))
(map (fn [p]
(str "//" (fs/path-relative-to deps-edn-dir p) ":__clj_lib")) clj-subdirs)))})))
"\n\n"
(emit-bazel (list 'filegroup (kwargs (merge
{:name "__clj_files"
:srcs (mapv fs/filename paths)
:data (mapv (fn [p]
(str "//" (fs/path-relative-to deps-edn-dir p) ":__clj_files")) clj-subdirs)}))))
"\n")))]
(let [build-file (-> dir (fs/->path "BUILD.bazel") fs/path->file)
changed? (not= content (when (.exists build-file) (slurp build-file :encoding "UTF-8")))]
(when changed?
Expand Down Expand Up @@ -837,9 +926,10 @@
"generates the BUILD file for @deps//: with a single target containing all deps.edn-resolved dependencies"
[{:keys [repository-dir deps-build-dir dep-ns->label jar->lib lib->jar lib->deps deps-repo-tag deps-bazel] :as args}]
(spit (-> (fs/->path deps-build-dir "BUILD.bazel") fs/path->file)
(str/join "\n\n" (concat
[(emit-bazel (list 'package (kwargs {:default_visibility ["//visibility:public"]})))
(emit-bazel (list 'load "@rules_clojure//:rules.bzl" "clojure_library"))]
(str (build-file-header "`rules_clojure`")
(str/join "\n\n" (concat
[(emit-bazel (list 'load "@rules_clojure//:rules.bzl" "clojure_library"))
(emit-bazel (list 'package (kwargs {:default_visibility ["//visibility:public"]})))]
(->> jar->lib
(sort-by (fn [[k v]] (library->label v)))
(mapcat (fn [[jarpath lib]]
Expand Down Expand Up @@ -894,6 +984,7 @@
{:name "__all"
:runtime_deps (->> jar->lib
(mapv (comp library->label val)))})))]))
"\n")

:encoding "UTF-8"))

Expand Down
2 changes: 2 additions & 0 deletions test/rules_clojure/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ clojure_test(name="persistent-classloader-test",

clojure_test(name="gen-build-test",
deps=[":test-deps"],
data=["@buildifier_prebuilt//:buildifier"],
env={"BUILDIFIER": "$(rlocationpath @buildifier_prebuilt//:buildifier)"},
test_ns = "rules-clojure.gen-build-test")

clojure_test(name="compile-test",
Expand Down
Loading