-
Notifications
You must be signed in to change notification settings - Fork 3
/
blaze-backend.lisp
329 lines (297 loc) · 13.6 KB
/
blaze-backend.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
#+xcvb
(module
(:author ("Francois-Rene Rideau")
:maintainer "Francois-Rene Rideau"
;; :run-depends-on ("string-escape")
:depends-on ("profiling" "specials" "virtual-pathnames"
"static-traversal" "computations"
"external-commands" "target-lisp-commands" "commands")))
(in-package :xcvb)
;; Backend for the Google BUILD.
(defclass blaze-traversal (static-traversal)
())
(defvar *BUILD-root* nil
"Path of the root to (writeable) BUILD packages.")
(defvar *READONLY-BUILD-root* nil
"Path of the root to read-only BUILD packages.")
(defvar *BUILD-package* nil
"current BUILD package")
(defvar *BUILD-package-directory* nil
"current BUILD package directory")
(define-option-spec +blaze-option-spec+
'((("build-root" #\r) :type string :optional t :documentation "root of BUILD packges") ;; default: google3 ancestor of current directory
(("readonly-root" #\R) :type string :optional t :documentation "google3 root (default: from current dir)") ;; default: ${BUILD-root}/../READONLY/google3/
(("package" #\P) :type string :optional t :documentation "BUILD package"))) ;; default: package for current dir under BUILD-root
(defun resolve-blaze-source-registry (source-registry &key root readonly-root)
(cond
((emptyp source-registry)
(let ((bsr (getenv "BLAZE_SOURCE_REGISTRY")))
(if (emptyp bsr)
(error "No --source-registry or BLAZE_SOURCE_REGISTRY specified")
(resolve-blaze-source-registry
bsr :root root :readonly-root readonly-root))))
((find (char source-registry 0) "\"(")
;; (warn "Using a Lisp source-registry at your own risk")
source-registry)
(t
(with-output-to-string (s)
(loop :for (entry . morep) :on (split-string source-registry :separator ";")
:for recursep = (string-suffix-p entry "//") :do
(labels ((x (p)
(if p
(format s "~A~@[/~*~]~@[:~]"
(ensure-directory-pathname p) recursep morep)
(error "Couldn't find BUILD directory for source-registry entry ~S" entry))))
(if (find #\: entry)
(x (label->pathname
entry :root root :readonly-root readonly-root))
(x (BUILD-package->pathname
entry :root root :readonly-root readonly-root)))))))))
(defun handle-blaze-options (&rest keys
&key build-root readonly-root package
source-registry lisp-binary-path
&allow-other-keys)
(let* ((current-dir
(unless (and build-root package)
(truename *default-pathname-defaults*)))
(build-dir
(or package
(find-BUILD-directory current-dir)
(error "Can't find BUILD directory")))
(build-root
(or build-root
(find-BUILD-root (or build-dir current-dir))
(error "Can't find BUILD root directory")))
(package-name
(or package
(enough-namestring build-dir build-root)))
(package-dir
(if package
(subpathname build-root package)
current-dir))
(readonly-root
(if readonly-root
(truename readonly-root)
(probe-file* (subpathname build-root "../READONLY/google3/"))))
(source-registry (resolve-blaze-source-registry
source-registry
:root build-root :readonly-root readonly-root))
(lisp-binary-path (label->pathname lisp-binary-path
:root build-root :readonly-root readonly-root)))
(when (>= (or *xcvb-verbosity* 5) 10)
(DBG :hbo current-dir package-dir source-registry lisp-binary-path build-dir build-root package-name readonly-root))
(setf *BUILD-root* build-root
*BUILD-package* package-name
*BUILD-package-directory* package-dir
*READONLY-BUILD-root* readonly-root
*default-pathname-defaults* package-dir)
(apply 'handle-global-options
:source-registry source-registry
:lisp-binary-path lisp-binary-path
keys)))
(defparameter *BUILD-root-p* (make-hash-table :test 'equal))
(define-memo-function (BUILD-root-p :table *BUILD-root-p*) (dir)
;; This is way too specific to Google
(equal (with-open-file (s (subpathname dir "__init__.py")
:direction :input :if-does-not-exist nil)
(and s (read-line s nil nil)))
"# DO NOT EDIT THIS FILE EXCEPT AS //depot/google3/tools/google3__init__.py"))
(defun find-BUILD-root (&optional (dir *default-pathname-defaults*))
(find-proper-ancestor dir 'BUILD-root-p))
(defparameter *BUILD-directory-p* (make-hash-table :test 'equal))
(define-memo-function (BUILD-directory-p :table *BUILD-directory-p*) (dir)
(and (probe-file (subpathname dir "BUILD")) t))
(defun find-BUILD-directory (&optional (dir *default-pathname-defaults*))
(find-proper-ancestor dir 'BUILD-directory-p))
(defun pathname->label (pathname &optional
(package-name *BUILD-package*))
(let* ((truename (truename pathname))
(build-dir (find-BUILD-directory truename))
(build-root (and build-dir (find-BUILD-root build-dir))))
(when build-root
(let ((filename (enough-namestring truename build-dir))
(package (enough-namestring build-dir build-root)))
(if (equal package package-name)
filename
(strcat package ":" filename))))))
(defun split-label (label &optional (package-name *BUILD-package*))
(destructuring-bind (&optional x y)
(split-string label :max 2 :separator ":")
(cond
(y (values x y))
((and x package-name) (values package-name x))
(t nil))))
(defun BUILD-package->pathname
(package-name
&key (root *BUILD-root*) (readonly-root *READONLY-BUILD-root*))
(flet ((sub (x)
(probe-file* (subpathname x package-name :type :directory))))
(or (sub root) (sub readonly-root))))
(defun label->pathname (label &key
(package-name *BUILD-package*)
(package-directory *BUILD-package-directory*)
(root *BUILD-root*)
(readonly-root *READONLY-BUILD-root*))
(multiple-value-bind (label-package-name label-filename)
(split-label label package-name)
(let ((package-dir
(if (and package-directory
(equal package-name label-package-name))
package-directory
(BUILD-package->pathname
label-package-name
:root root :readonly-root readonly-root))))
(subpathname package-dir label-filename))))
(defun computations-to-blaze-BUILD (env)
(with-output-to-string (s)
(dolist (computation *computations*)
(write-computation-to-blaze-BUILD env s computation))))
(defun write-blaze-BUILD (fullname &key output-path)
"Write a blaze BUILD file to output-path"
(multiple-value-bind (target-dependency build directory) (handle-target fullname)
(declare (ignore build directory))
(let* ((env (make-instance 'blaze-traversal))
(default-output-path (subpathname *default-pathname-defaults* "BUILD"))
(actual-output-path
(if output-path
(merge-pathnames* output-path default-output-path)
default-output-path))
(blaze-BUILD-path (ensure-absolute-pathname actual-output-path))
(blaze-BUILD-dir (pathname-directory-pathname blaze-BUILD-path))
(*default-pathname-defaults* blaze-BUILD-dir)
(*print-pretty* nil); otherwise SBCL will slow us down a lot.
(lisp-env-var (lisp-environment-variable-name :prefix nil))
(*lisp-executable-pathname* ;; magic escape!
;; NIY, blaze escape rather than Makefile escape
(list :blaze-BUILD "${" lisp-env-var "}")))
(log-format 9 "output-path: ~S" output-path)
(log-format 9 "default-output-path: ~S" default-output-path)
(log-format 9 "actual-output-path: ~S" actual-output-path)
(log-format 6 "blaze-BUILD-path: ~S" blaze-BUILD-path)
(log-format 9 "*default-pathname-defaults*: ~S" *default-pathname-defaults*)
(log-format 7 "workspace: ~S" *workspace*)
(log-format 7 "cache: ~S" *cache*)
(log-format 7 "object-cache: ~S" *object-cache*)
;; Pass 1: Traverse the graph of dependencies
(log-format 8 "T=~A building dependency graph" (get-universal-time))
(graph-for env target-dependency)
;; Pass 2: Build a Blaze-BUILD out of the *computations*
(log-format 8 "T=~A computing blaze-BUILD body" (get-universal-time))
(log-format 8 "All *computations*=~%~S" (reverse *computations*))
(let ((body (computations-to-blaze-BUILD env)))
(log-format 8 "T=~A creating blaze-BUILD" (get-universal-time))
(ensure-directories-exist blaze-BUILD-path)
(with-open-file (out blaze-BUILD-path
:direction :output
:if-exists :supersede)
(log-format 8 "T=~A printing blaze-BUILD" (get-universal-time))
(write-blaze-BUILD-prelude
:stream out :lisp-env-var lisp-env-var)
(princ body out)
(write-blaze-BUILD-conclusion out)))
(log-format 8 "T=~A done" (get-universal-time))
;; Return data for use by the non-enforcing Blaze-BUILD backend.
(values blaze-BUILD-path blaze-BUILD-dir))))
(defun write-blaze-BUILD-prelude (&key stream lisp-env-var)
(let ((vars (list lisp-env-var))
(implementation-pathname
(or *target-lisp-executable-pathname*
(lisp-implementation-name (get-lisp-implementation)))))
(write-generated-file-warning stream implementation-pathname)
vars))
;; TODO: clean
;; * a clean-xcvb target that removes the object directory
(defun write-blaze-BUILD-conclusion (&optional stream)
(format stream "~%"))
(defmethod vp-namestring :around ((env blaze-traversal) vp)
(declare (ignorable env vp))
;; something needs to be done here to convert between pathnames and text? or somewhere else?
(let ((namestring (call-next-method)))
namestring))
#|
(defmethod grain-pathname-text ((env blaze-traversal) (grain file-grain))
(let ((pathname (call-next-method)))
(values (escape-sh-token-for-blaze-BUILD (enough-namestring pathname)) pathname)))
|#
(defmethod grain-pathname-text :around ((env blaze-traversal) grain)
(declare (ignorable env grain))
(or (call-next-method) ""))
(defun blaze-BUILD-commands-for-computation (env computation-command)
(mapcar 'shell-tokens-to-Makefile ;; BOGUS
(external-commands-for-computation env computation-command)))
#|
;;; TODO: find labels!
(defmethod effective-namestring ((env blaze-traversal) fullname)
(fullname-enough-namestring env fullname))
(defmethod pseudo-effective-namestring ((env blaze-traversal) fullname)
(pseudo-fullname-enough-namestring env fullname))
|#
(defun write-computation-to-blaze-BUILD (env stream computation)
(with-accessors ((command computation-command)
(inputs computation-inputs)
(outputs computation-outputs)) computation
(let* ((target (grain-pathname-text env (first outputs)))
(tools ())) ;;TODO include the compiler and its support files!!!
;; TODO: C compiling extensions require the C compiler, too,
;; and a proper setup(!)
(format stream "~&genrule(~% ~
name = ~S,~% ~
cmd = '~{~A~^ ; ~}'~% ~
srcs = [ ~{~S~^, ~} ],~% ~
outs = [ ~{~S~^, ~} ],~% ~
tools = [ ~{~S~^, ~} ],~%)~%~%"
target
(mapcar/ #'blaze-BUILD-commands-for-computation env command)
(mapcar/ #'grain-pathname-text env inputs)
(mapcar/ #'grain-pathname-text env outputs)
(mapcar/ #'grain-pathname-text env tools)))))
(defmethod grain-pathname-text ((env blaze-traversal) (grain phony-grain))
(declare (ignore env))
(let ((n #|(normalize-name-for-blaze-BUILD|# (princ-to-string (fullname grain))));)
n))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Make-Blaze-BUILD ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-command make-blaze-BUILD
(("make-blaze-BUILD" "mbb")
(&rest keys &key)
`(,@+build-option-spec+
,@+setup-option-spec+
,@+base-image-option-spec+
,@+source-registry-option-spec+
,@+xcvb-program-option-spec+
,@+lisp-implementation-option-spec+
,@+blaze-option-spec+
,@+workspace-option-spec+
,@+cfasl-option-spec+
,@+verbosity-option-spec+
,@+profiling-option-spec+)
"Create some blaze BUILD"
"Create blaze BUILD rules to build a project." ignore)
(apply 'blaze-build :blaze-BUILD-only t keys))
(defun invoke-blaze (&key target directory (on-error 'error) env)
(let* ((blaze-command
`(,@(when env `("env" ,@env))
blaze build
,@(when target (ensure-list target)))))
(log-format 6 "Building with ~S" blaze-command)
(run
blaze-command
:directory directory
:output :interactive ;; for side-effects only
:on-error on-error)))
(define-command blaze-build
(("blaze-build" "blaze" "bb")
(&rest keys &key blaze-BUILD-only (exit t))
`(,@+make-blaze-BUILD-option-spec+)
"Use blaze to build your project"
"Create blaze BUILD rules to build a package, use them."
(build))
(apply 'handle-blaze-options keys)
(with-maybe-profiling ()
(multiple-value-bind (blaze-BUILD-path blaze-BUILD-dir)
(write-blaze-BUILD build)
(if blaze-BUILD-only
(values blaze-BUILD-path blaze-BUILD-dir)
(let ((code (invoke-blaze :directory blaze-BUILD-dir :target "fasl")))
(if exit
(exit code)
(values code blaze-BUILD-dir)))))))