Skip to content

Commit 1834f5d

Browse files
committed
Add Bazel path mapping support
1 parent ae097aa commit 1834f5d

File tree

6 files changed

+1506
-2134
lines changed

6 files changed

+1506
-2134
lines changed

WORKSPACE

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ maven_install(
5151
"org.clojure:data.json:2.4.0",
5252
"org.clojure:java.classpath:1.0.0",
5353
"org.clojure:tools.namespace:1.1.0",
54-
"org.clojure:tools.deps.alpha:0.14.1212"
54+
"org.clojure:tools.deps.alpha:0.14.1212",
55+
"org.clojure:tools.cli:1.2.245"
5556
],
5657
maven_install_json = "@//:frozen_deps_install.json",
5758
fail_if_repin_required = True,

frozen_deps_install.json

Lines changed: 1386 additions & 2013 deletions
Large diffs are not rendered by default.

rules.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ clojure_library = rule(
1010
"data": attr.label_list(default = [], allow_files = True),
1111
"resources": attr.label_list(default=[], allow_files=True),
1212
"aot": attr.string_list(default = [], doc = "namespaces to be compiled"),
13-
"resource_strip_prefix": attr.string(default = ""),
13+
"resource_strip_prefix": attr.string(),
1414
"compiledeps": attr.label_list(default = []),
1515
"javacopts": attr.string_list(default = [], allow_empty = True, doc = "Optional javac compiler options"),
1616
"jvm_flags": attr.string_list(default=[], doc = "Optional jvm_flags to pass to the worker binary"),

rules/jar.bzl

Lines changed: 24 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -10,68 +10,6 @@ def distinct(lst):
1010
d[i] = True
1111
return d.keys()
1212

13-
def paths(resources, resource_strip_prefix):
14-
"""Return a list of path tuples (target, source) where:
15-
target - is a path in the archive (with given prefix stripped off)
16-
source - is an absolute path of the resource file
17-
18-
Tuple ordering is aligned with zipper format ie zip_path=file
19-
20-
Args:
21-
resources: list of file objects
22-
resource_strip_prefix: string to strip from resource path
23-
"""
24-
return [(_target_path(resource, resource_strip_prefix), resource.path) for resource in resources]
25-
26-
def _strip_prefix(path, prefix):
27-
return path[len(prefix):] if path.startswith(prefix) else path
28-
29-
def _target_path(resource, resource_strip_prefix):
30-
path = _target_path_by_strip_prefix(resource, resource_strip_prefix) if resource_strip_prefix else _target_path_by_default_prefixes(resource)
31-
return _strip_prefix(path, "/")
32-
33-
def _target_path_by_strip_prefix(resource, resource_strip_prefix):
34-
# Start from absolute resource path and then strip roots so we get to correct short path
35-
# resource.short_path sometimes give weird results ie '../' prefix
36-
path = resource.path
37-
if resource_strip_prefix != resource.owner.workspace_root:
38-
path = _strip_prefix(path, resource.owner.workspace_root + "/")
39-
path = _strip_prefix(path, resource.root.path + "/")
40-
41-
# proto_library translates strip_import_prefix to proto_source_root which includes root so we have to strip it
42-
prefix = _strip_prefix(resource_strip_prefix, resource.root.path + "/")
43-
if not path.startswith(prefix):
44-
fail("Resource file %s is not under the specified prefix %s to strip" % (path, prefix))
45-
return path[len(prefix):]
46-
47-
def _target_path_by_default_prefixes(resource):
48-
path = resource.path
49-
50-
# Here we are looking to find out the offset of this resource inside
51-
# any resources folder. We want to return the root to the resources folder
52-
# and then the sub path inside it
53-
dir_1, dir_2, rel_path = path.partition("resources")
54-
if rel_path:
55-
return rel_path
56-
57-
# The same as the above but just looking for java
58-
(dir_1, dir_2, rel_path) = path.partition("java")
59-
if rel_path:
60-
return rel_path
61-
62-
# Both short_path and path have quirks we wish to avoid, in short_path there are times where
63-
# it is prefixed by `../` instead of `external/`. And in .path it will instead return the entire
64-
# bazel-out/... path, which is also wanting to be avoided. So instead, we return the short-path if
65-
# path starts with bazel-out and the entire path if it does not.
66-
return resource.short_path if path.startswith("bazel-out") else path
67-
68-
def restore_prefix(src, stripped):
69-
"""opposite of _target_path. Given a source and stripped file, return the prefix """
70-
if src.path.endswith(stripped):
71-
return src.path[:len(src.path)-len(stripped)]
72-
else:
73-
fail("Resource file %s is not under the specified prefix %s to strip" % (src, stripped))
74-
7513
def argsfile_name(label):
7614
return str(label).replace("@","_").replace("/","_") + "_args"
7715

@@ -122,55 +60,51 @@ def clojure_jar_impl(ctx):
12260

12361
input_files = ctx.files.srcs + ctx.files.resources
12462

125-
if len(input_files):
126-
src_dir = restore_prefix(input_files[0], _target_path(input_files[0], ctx.attr.resource_strip_prefix))
127-
else:
128-
src_dir = None
129-
130-
compile_classpath = compile_info.transitive_runtime_jars.to_list() + ctx.files.compiledeps + [classes_dir]
131-
compile_classpath = [f.path for f in compile_classpath]
132-
compile_classpath = compile_classpath + [p for p in [src_dir] if p]
63+
compile_classpath = depset(
64+
ctx.files.compiledeps + [classes_dir],
65+
transitive = [compile_info.transitive_runtime_jars],
66+
)
13367

13468
native_libs = []
135-
for f in runfiles.files.to_list():
136-
## Bazel on mac sticks weird looking directories in runfiles, like _solib_darwin/_U_S_Snative_C_Ulibsodium___Unative_Slibsodium_Slib. filter them out
137-
if (f.path.endswith(".dylib") or f.path.endswith(".so")) and (f.path.rfind("solib_darwin") == -1):
138-
native_libs.append(f)
13969

14070
aot_nses = distinct(aot_nses)
14171

14272
javaopts_str = " ".join(ctx.attr.javacopts)
14373

144-
compile_args = {"classes-dir": classes_dir.path,
145-
"output-jar": output_jar.path,
146-
"src-dir": src_dir,
147-
"srcs": [_target_path(s, ctx.attr.resource_strip_prefix) for s in ctx.files.srcs],
148-
"resources": [_target_path(s, ctx.attr.resource_strip_prefix) for s in ctx.files.resources],
149-
"aot-nses": aot_nses,
150-
"classpath": compile_classpath}
74+
compile_args = ctx.actions.args()
75+
compile_args.use_param_file("@%s", use_always = True)
76+
77+
compile_args.add_all([classes_dir], before_each = "--classes-dir", expand_directories = False)
78+
compile_args.add_all([output_jar], before_each = "--output-jar", expand_directories = False)
15179

152-
args_file = ctx.actions.declare_file(argsfile_name(ctx.label))
153-
ctx.actions.write(
154-
output = args_file,
155-
content = json.encode(compile_args))
80+
if ctx.attr.resource_strip_prefix != "":
81+
compile_args.add("--resource-strip-prefix")
82+
compile_args.add(ctx.attr.resource_strip_prefix)
15683

157-
inputs = ctx.files.srcs + ctx.files.resources + compile_info.transitive_runtime_jars.to_list() + native_libs + [args_file] + worker_classpath_depset.to_list()
84+
compile_args.add_all(ctx.files.srcs, before_each="--src")
85+
compile_args.add_all(ctx.files.resources, before_each="--resource")
86+
compile_args.add_all(aot_nses, before_each="--aot-nses")
87+
compile_args.add("--classpath")
88+
compile_args.add_joined(compile_classpath, join_with=":")
15889

159-
worker_classpath_str = ":".join([d.path for d in worker_classpath_depset.to_list()])
90+
inputs = depset(
91+
ctx.files.srcs + ctx.files.resources + native_libs,
92+
transitive = [compile_info.transitive_runtime_jars, worker_classpath_depset],
93+
)
16094

16195
ctx.actions.run(
16296
executable= ctx.executable._clojureworker_binary,
16397
arguments=
16498
["--jvm_flags=" + f for f in ctx.attr.jvm_flags] +
165-
["-m", "rules-clojure.worker",
166-
"@%s" % args_file.path],
99+
["-m", "rules-clojure.worker"] + [compile_args],
167100
outputs = [output_jar, classes_dir],
168101
inputs = inputs,
169102
mnemonic = "ClojureCompile",
170103
progress_message = "Compiling %s" % ctx.label,
171104
execution_requirements={"supports-workers": "1",
172105
"supports-multiplex-workers": "1",
173-
"requires-worker-protocol": "json"})
106+
"requires-worker-protocol": "json",
107+
"supports-path-mapping": "1"})
174108

175109
return [
176110
default_info,

src/rules_clojure/BUILD

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ java_library(
2424
"@rules_clojure_maven_deps//:org_clojure_core_cache",
2525
"@rules_clojure_maven_deps//:org_clojure_data_json",
2626
"@rules_clojure_maven_deps//:org_clojure_java_classpath",
27-
"@rules_clojure_maven_deps//:org_clojure_tools_namespace"])
27+
"@rules_clojure_maven_deps//:org_clojure_tools_namespace",
28+
"@rules_clojure_maven_deps//:org_clojure_tools_cli"])
2829

2930
java_binary(
3031
name="bootstrap-bin",
@@ -62,6 +63,8 @@ java_import(name="libcompile",
6263
jars=["libcompile.jar"],
6364
data=[":bootstrap-compiler"])
6465

66+
67+
6568
java_binary(name="worker",
6669
main_class="clojure.main",
6770
jvm_flags=["-Dclojure.main.report=stderr",
@@ -90,6 +93,7 @@ clojure_library(
9093
"@rules_clojure_maven_deps//:org_clojure_data_json",
9194
"libfs"],
9295
aot=["clojure.java.classpath",
96+
"clojure.tools.namespace.parse",
9397
"clojure.tools.deps.alpha.extensions",
9498
"clojure.tools.deps.alpha.util.session",
9599
"clojure.tools.deps.alpha.util.io",

src/rules_clojure/worker.clj

Lines changed: 88 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
[clojure.data.json :as json]
44
[clojure.java.io :as io]
55
[clojure.spec.alpha :as s]
6+
[clojure.string :as str]
7+
[clojure.tools.cli :refer [parse-opts]]
68
[rules-clojure.fs :as fs]
79
[rules-clojure.jar :as jar]
810
[rules-clojure.util :as util]
911
[rules-clojure.persistent-classloader :as pcl])
1012
(:import java.nio.charset.StandardCharsets
11-
java.util.concurrent.TimeUnit))
13+
java.util.concurrent.TimeUnit
14+
[java.util.logging ConsoleHandler FileHandler Logger Level SimpleFormatter]
15+
java.lang.ProcessHandle))
1216

1317
(s/def ::classes-dir string?) ;; path to the *compile-files* dir
1418
(s/def ::output-jar string?) ;; path where the output jar should be written
15-
(s/def ::srcs (s/coll-of string?)) ;; seq of paths to src files to put on classpath while compiling. Relative to src-dir
16-
(s/def ::src-dir (s/nilable string?)) ;; path to root of source tree, relative to execroot
19+
(s/def ::srcs (s/coll-of string?)) ;; seq of paths to src files to put on classpath while compiling.
1720
(s/def ::resources (s/coll-of string?)) ;; seq of paths to include in the jar
1821
(s/def ::aot-nses (s/coll-of string?)) ;; seq of namespaces to AOT
1922
(s/def ::classpath (s/coll-of string?)) ;; seq of jars to put on compile classpath
@@ -23,8 +26,7 @@
2326
::classpath
2427
::aot-nses]
2528
:opt-un [::resources
26-
::srcs
27-
::src-dir]))
29+
::srcs]))
2830

2931
(s/def ::arguments (s/cat :c ::compile-req))
3032

@@ -46,6 +48,24 @@
4648
(defn all-classpath-jars [classpath]
4749
(set classpath))
4850

51+
;; bazel requires us to write to stdout, and doesn't reliably report
52+
;; stderr, so log to a temp file to guarantee we find everything.
53+
54+
(defn pid []
55+
(-> (ProcessHandle/current) .pid))
56+
57+
(defn configure-logging! []
58+
(let [handler (FileHandler. (format "/tmp/rules-clojure-worker-%s.log" (pid)))
59+
formatter (SimpleFormatter.)
60+
logger (Logger/getLogger (str *ns*))]
61+
(.setFormatter handler formatter)
62+
(.addHandler logger handler)
63+
(.addHandler logger (ConsoleHandler.))
64+
(.setLevel logger Level/INFO)))
65+
66+
(defn log [& args]
67+
(Logger/.log (Logger/getLogger (str *ns*)) Level/INFO (apply str args)))
68+
4969
(defn process-request
5070
[{:keys [classloader-strategy
5171
input-map] :as req}]
@@ -103,10 +123,9 @@
103123
real-out *out*]
104124
(let [exit (binding [*out* out-printer]
105125
(try
106-
(let [compile-req (json/read-str (first arguments) :key-fn keyword)]
107-
(process-request (assoc compile-req
108-
:classloader-strategy classloader-strategy
109-
:input-map (input-map inputs))))
126+
(process-request (assoc work-req
127+
:classloader-strategy classloader-strategy
128+
:input-map (input-map inputs)))
110129
0
111130
(catch Throwable t
112131
(println t) ;; print to bazel str
@@ -117,42 +136,83 @@
117136
:output (str baos)}
118137
(when requestId
119138
{:requestId requestId}))]
139+
(util/print-err "persistent done:" resp)
120140
(.write real-out (json/write-str resp))
121141
(.write real-out "\n")
122142
(.flush real-out))))
123143

144+
;; [--classes-dir bazel-out/darwin_arm64-fastbuild/bin/external/deps/.ns_metosin_reitit_core_reitit_exception.classes --output-jar bazel-out/darwin_arm64-fastbuild/bin/external/deps/ns_metosin_reitit_core_reitit_exception.jar --resource-strip-prefix '' --aot-ns reitit.exception --classpath external/deps/repository/metosin/reitit-core/0.6.0/reitit-core-0.6.0.jar:external/deps/repository/meta-merge/meta-merge/1.0.0/meta-merge-1.0.0.jar:external/deps/repository/org/clojure/clojure/1.12.2/clojure-1.12.2.jar:external/deps/repository/org/clojure/core.specs.alpha/0.4.74/core.specs.alpha-0.4.74.jar:external/deps/repository/org/clojure/spec.alpha/0.5.238/spec.alpha-0.5.238.jar:bazel-out/darwin_arm64-fastbuild/bin/external/rules_clojure/src/rules_clojure/libcompile.jar]
145+
146+
(defn parse-classpath [classpath-str]
147+
(str/split classpath-str #":"))
148+
149+
(def cli-options
150+
;; An option with an argument
151+
[[nil "--classes-dir dir" "output directory where classfiles will be written"]
152+
[nil "--output-jar jar" "output jar name"]
153+
[nil "--resource-strip-prefix path" ]
154+
[nil "--aot-nses ns" "names of namespaces to AOT. May be repeated"
155+
:default []
156+
:update-fn conj
157+
:multi true]
158+
[nil "--classpath cp" "classpath to use while compiling, separated by :"
159+
:parse-fn parse-classpath]])
160+
161+
(defn parse-arguments [^String args]
162+
{:post [(do (log "worker parse-req" args "=>" %) true)]}
163+
(-> args
164+
(parse-opts cli-options)
165+
:options))
166+
124167
(defn process-persistent []
125168
(let [executor (java.util.concurrent.Executors/newWorkStealingPool)
126169
classloader-strategy (pcl/caching-threadsafe)]
127170
(loop []
128-
(if-let [line (read-line)]
129-
(let [work-req (json/read-str line :key-fn keyword)]
130-
(util/print-err "got req" work-req)
131-
(let [out *out*
132-
err *err*]
133-
(.submit executor ^Runnable (fn []
134-
(binding [*out* out
135-
*err* err]
136-
(process-persistent-1 (assoc work-req
137-
:classloader-strategy classloader-strategy)))))
138-
(recur)))
139-
(do
140-
(util/print-err "no request, exiting")
141-
(.shutdown executor)
142-
(util/print-err "awating task completion")
143-
(util/print-err "finished cleanly?" (.awaitTermination executor 60 TimeUnit/SECONDS))
144-
:exit)))))
171+
(log "blocking on read-line")
172+
(let [line (read-line)]
173+
(if (and line (seq line))
174+
(let [_ (log "persistent: line" line)
175+
work-req (json/read-str line :key-fn keyword)
176+
arguments (parse-arguments (:arguments work-req))
177+
prefix (:resource-strip-prefix arguments)
178+
_ (log "persistent: prefix:" prefix)
179+
arguments (if (seq prefix)
180+
(update arguments :classpath (fn [classpath] (distinct (conj classpath prefix))))
181+
arguments)
182+
work-req (-> work-req
183+
(dissoc :arguments)
184+
(merge arguments))]
185+
(log "persistent: req" work-req)
186+
(let [out *out*
187+
err *err*]
188+
(.submit executor ^Runnable (fn []
189+
(binding [*out* out
190+
*err* err]
191+
(process-persistent-1 (assoc work-req
192+
:classloader-strategy classloader-strategy)))))
193+
(recur)))
194+
(do
195+
(log "no request, exiting")
196+
(.shutdown executor)
197+
(log "awating task completion")
198+
(log "finished cleanly?" (.awaitTermination executor 60 TimeUnit/SECONDS))
199+
:exit))))))
145200

146201
(defn set-uncaught-exception-handler! []
147202
(Thread/setDefaultUncaughtExceptionHandler
148203
(reify Thread$UncaughtExceptionHandler
149204
(uncaughtException [_ _ ex]
150-
(util/print-err ex "uncaught exception")))))
205+
(log ex "uncaught exception")))))
151206

152207
(defn -main [& args]
153208
(set-uncaught-exception-handler!)
209+
(configure-logging!)
154210
(let [persistent? (some (fn [a] (= "--persistent_worker" a)) args)
155211
f (if persistent?
156212
(fn [_args] (process-persistent))
157213
process-ephemeral)]
158-
(f args)))
214+
(try
215+
(f args)
216+
(catch Exception e
217+
(log e)
218+
(throw e)))))

0 commit comments

Comments
 (0)