Fix make-array transforms.
[sbcl.git] / tools-for-build / wxs.lisp
1 ;;;; Generate WiX XML Source, from which we eventually generate the .MSI
2
3 ;;;; This software is part of the SBCL system. See the README file for
4 ;;;; more information.
5 ;;;;
6 ;;;; This software is derived from the CMU CL system, which was
7 ;;;; written at Carnegie Mellon University and released into the
8 ;;;; public domain. The software is in the public domain and is
9 ;;;; provided with absolutely no warranty. See the COPYING and CREDITS
10 ;;;; files for more information.
11
12 ;;;; XML generation
13
14 (defvar *indent-level* 0)
15
16 (defvar *sbcl-source-root*
17   (truename
18    (merge-pathnames (make-pathname :directory (list :relative :up))
19                     (make-pathname :name nil :type nil :defaults *load-truename*))))
20
21 (defun print-xml (sexp &optional (stream *standard-output*))
22   (destructuring-bind (tag &optional attributes &body children) sexp
23     (when attributes (assert (evenp (length attributes))))
24     (format stream "~VT<~A~{ ~A='~A'~}~@[/~]>~%"
25             *indent-level* tag attributes (not children))
26       (let ((*indent-level* (+ *indent-level* 3)))
27         (dolist (child children)
28           (unless (listp child)
29             (error "Malformed child: ~S in ~S" child children))
30           (print-xml child stream)))
31       (when children
32         (format stream "~VT</~A>~%" *indent-level* tag))))
33
34 (defun xml-1.0 (pathname sexp)
35   (with-open-file (xml pathname :direction :output :if-exists :supersede
36                        :external-format :ascii)
37      (format xml "<?xml version='1.0'?>")
38      (print-xml sexp xml)))
39
40 (defun application-name ()
41   (format nil "Steel Bank Common Lisp ~A (~A)" (lisp-implementation-version) (machine-type)))
42
43 (defun version-digits (&optional (horrible-thing (lisp-implementation-version)))
44   "Turns something like 0.pre7.14.flaky4.13 (see version.lisp-expr)
45   into an acceptable form for WIX (up to four dot-separated numbers)."
46   (with-output-to-string (output)
47     (loop repeat 4
48           with position = 0
49           for separator = "" then "."
50           for next-digit = (position-if #'digit-char-p horrible-thing
51                                     :start position)
52           while next-digit
53           do (multiple-value-bind (number end)
54                  (parse-integer horrible-thing :start next-digit :junk-allowed t)
55                (format output "~A~D" separator number)
56                (setf position end)))))
57
58 ;;;; GUID generation
59 ;;;;
60 ;;;; Apparently this willy-nilly regeneration of GUIDs is a bad thing, and
61 ;;;; we should probably have a single GUID per release / Component, so
62 ;;;; that no matter by whom the .MSI is built the GUIDs are the same.
63 ;;;;
64 ;;;; Something to twiddle on a rainy day, I think.
65
66 (load-shared-object "OLE32.DLL")
67
68 (define-alien-type uuid
69     (struct uuid
70             (data1 unsigned-int)
71             (data2 unsigned-short)
72             (data3 unsigned-short)
73             (data4 (array unsigned-char 8))))
74
75 (define-alien-routine ("CoCreateGuid" co-create-guid) int (guid (* uuid)))
76
77 (defun uuid-string (uuid)
78   (declare (type (alien (* uuid)) uuid))
79   (let ((data4 (slot uuid 'data4)))
80     (format nil "~8,'0X-~4,'0X-~4,'0X-~2,'0X~2,'0X-~{~2,'0X~}"
81             (slot uuid 'data1)
82             (slot uuid 'data2)
83             (slot uuid 'data3)
84             (deref data4 0)
85             (deref data4 1)
86             (loop for i from 2 upto 7 collect (deref data4 i)))))
87
88 (defun make-guid ()
89   (let (guid)
90     (unwind-protect
91          (progn
92            (setf guid (make-alien (struct uuid)))
93            (co-create-guid guid)
94            (uuid-string guid))
95       (free-alien guid))))
96
97 (defun list-all-contribs ()
98   (loop for flag in (directory "../obj/asdf-cache/*/test-passed.test-report")
99         collect (car (last (pathname-directory flag)))))
100
101 (defvar *id-char-substitutions* '((#\\ . #\_)
102                                   (#\/ . #\_)
103                                   (#\: . #\.)
104                                   (#\- . #\.)))
105
106 (defun id (string)
107   ;; Mangle a string till it can be used as an Id. A-Z, a-z, 0-9, and
108   ;; _ are ok, nothing else is.
109   (map 'string (lambda (c)
110                  (or (cdr (assoc c *id-char-substitutions*))
111                      c))
112        string))
113
114 (defun directory-id (name)
115   (id (format nil "Directory_~A" (enough-namestring name *sbcl-source-root*))))
116
117 (defun file-id (pathname)
118   (id (format nil "File_~A" (enough-namestring pathname *sbcl-source-root*))))
119
120 (defparameter *ignored-directories* '("CVS" ".svn" "test-output"))
121
122 (defparameter *pathname-type-abbrevs*
123   '(("lisp" . "lsp")
124     ("fasl" . "fas")
125     ("SBCL" . "txt") ; README.SBCL -> README.txt
126     ("texinfo" . "tfo")
127     ("lisp-temp" . "lmp")
128     ("html" . "htm")))
129
130 (defparameter *components* nil)
131
132 (defun component-id (pathname)
133   (let ((id (id (format nil "Contrib_~A" (enough-namestring pathname *sbcl-source-root*)))))
134     (push id *components*)
135     id))
136
137 (defun ref-all-components ()
138   (prog1
139       (mapcar (lambda (id)
140                 `("ComponentRef" ("Id" ,id)))
141               *components*)
142     (setf *components* nil)))
143
144 (defun collect-1-component (root)
145   `("Directory" ("Name" ,(car (last (pathname-directory root)))
146                  "Id" ,(directory-id root))
147     ("Component" ("Id" ,(component-id root)
148                   "Guid" ,(make-guid)
149                   "DiskId" 1)
150      ,@(loop for file in (directory
151                           (make-pathname :name :wild :type :wild :defaults root))
152              when (or (pathname-name file) (pathname-type file))
153              collect `("File" ("Name" ,(file-namestring file)
154                                "Id" ,(file-id file)
155                                "Source" ,(enough-namestring file)))))))
156
157 (defun directory-empty-p (dir)
158   (null (directory (make-pathname :name :wild :type :wild :defaults dir))))
159
160 (defun collect-components (root)
161   (append (unless (directory-empty-p root) (list (collect-1-component root)))
162           (loop for directory in
163                 (directory
164                  (merge-pathnames (make-pathname
165                                    :directory '(:relative :wild)
166                                    :name nil :type nil)
167                                   root))
168                 unless (member (car (last (pathname-directory directory)))
169                                *ignored-directories* :test #'equal)
170                 append (collect-components directory))))
171
172 (defun collect-contrib-components ()
173   (loop for contrib in (directory "../obj/asdf-cache/*/test-passed.test-report")
174         append (collect-components (make-pathname :name nil
175                                                   :type nil
176                                                   :version nil
177                                                   :defaults contrib))))
178
179 (defun make-extension (type mime)
180   `("Extension" ("Id" ,type "ContentType" ,mime)
181     ("Verb" ("Id" ,(format nil "load_~A" type)
182              "Argument" "--core \"[#sbcl.core]\" --load \"%1\""
183              "Command" "Load with SBCL"
184              "Target" "[#sbcl.exe]"))))
185
186 (defun write-wxs (pathname)
187   ;; both :INVERT and :PRESERVE could be used here, but this seemed
188   ;; better at the time
189   (xml-1.0
190    pathname
191    `("Wix" ("xmlns" "http://schemas.microsoft.com/wix/2006/wi")
192      ("Product" ("Id" "*"
193                  "Name" ,(application-name)
194                  "Version" ,(version-digits)
195                  "Manufacturer" "http://www.sbcl.org"
196                  "UpgradeCode" "BFF1D4CA-0153-4AAC-BB21-06DC4B8EAD7D"
197                  "Language" 1033)
198       ("Package" ("Id" "*"
199                   "Manufacturer" "http://www.sbcl.org"
200                   "InstallerVersion" 200
201                   "Compressed" "yes"
202                   #+x86-64 "Platform" #+x86-64 "x64"
203                   "InstallScope" "perMachine"))
204       ("Media" ("Id" 1
205                 "Cabinet" "sbcl.cab"
206                 "EmbedCab" "yes"))
207       ("Property" ("Id" "PREVIOUSVERSIONSINSTALLED"
208                    "Secure" "yes"))
209       ("Upgrade" ("Id" "BFF1D4CA-0153-4AAC-BB21-06DC4B8EAD7D")
210        ("UpgradeVersion" ("Minimum" "1.0.0"
211                           "Maximum" "99.0.0"
212                           "Property" "PREVIOUSVERSIONSINSTALLED"
213                           "IncludeMinimum" "yes"
214                           "IncludeMaximum" "no")))
215       ("InstallExecuteSequence" ()
216        ("RemoveExistingProducts" ("After" "InstallInitialize")))
217       ("Directory" ("Id" "TARGETDIR"
218                     "Name" "SourceDir")
219        ("Directory" ("Id" "ProgramMenuFolder")
220         ("Component" ("Id" "SBCL_Shortcut"
221                       "Guid" ,(make-guid))
222          ("Shortcut" ("Id" "sbcl.lnk"
223                       "Name" ,(application-name)
224                       "Target" "[INSTALLDIR]sbcl.exe"
225                       "Arguments" "--core \"[#sbcl.core]\""))
226          ("RegistryValue" ("Root" "HKCU"
227                            "Key" ,(application-name)
228                            "Name" "installed"
229                            "Type" "integer"
230                            "Value" "1"
231                            "KeyPath" "yes"))))
232        ("Directory" ("Id" #-x86-64 "ProgramFilesFolder" #+x86-64 "ProgramFiles64Folder"
233                      "Name" "PFiles")
234         ("Directory" ("Id" "BaseFolder"
235                       "Name" "Steel Bank Common Lisp")
236          ("Directory" ("Id" "VersionFolder"
237                        "Name" ,(lisp-implementation-version))
238           ("Directory" ("Id" "INSTALLDIR")
239            ("Component" ("Id" "SBCL_SetHOME"
240                          "Guid" ,(make-guid)
241                          "DiskId" 1)
242             ("Environment" ("Id" "Env_SBCL_HOME"
243                             "System" "yes"
244                             "Action" "set"
245                             "Name" "SBCL_HOME"
246                             "Part" "all"
247                             "Value" "[INSTALLDIR]")))
248
249            ("Component" ("Id" "SBCL_SetPATH"
250                          "Guid" ,(make-guid)
251                          "DiskId" 1)
252             ("Environment" ("Id" "Env_PATH"
253                             "System" "yes"
254                             "Action" "set"
255                             "Name" "PATH"
256                             "Part" "last"
257                             "Value" "[INSTALLDIR]")))
258            ("Component" ("Id" "SBCL_Base"
259                          "Guid" ,(make-guid)
260                          "DiskId" 1)
261             ;; If we want to associate files with SBCL, this
262             ;; is how it's done -- but doing this by default
263             ;; and without asking the user for permission Is
264             ;; Bad. Before this is enabled we need to figure out
265             ;; how to make WiX ask for permission for this...
266             ;; ,(make-extension "fasl" "application/x-lisp-fasl")
267             ;; ,(make-extension "lisp" "text/x-lisp-source")
268             ("File" ("Name" "sbcl.exe"
269                      "Source" "../src/runtime/sbcl.exe"))
270             ("File" ("Name" "sbcl.core"
271                      "Source" "sbcl.core")))
272            ,@(collect-contrib-components))))))
273       ("Feature" ("Id" "Minimal"
274                   "Title" "SBCL Executable"
275                   "ConfigurableDirectory" "INSTALLDIR"
276                   "Level" 1)
277        ("ComponentRef" ("Id" "SBCL_Base"))
278        ("Feature" ("Id" "Contrib" "Level" 1 "Title" "Contributed Modules")
279                   ,@(ref-all-components))
280        ("Feature" ("Id" "Shortcut" "Level" 1 "Title" "Add Start Menu Shortcut")
281                   ("ComponentRef" ("Id" "SBCL_Shortcut")))
282        ("Feature" ("Id" "SetPath" "Level" 1 "Title" "Set Environment Variable: PATH")
283                   ("ComponentRef" ("Id" "SBCL_SetPATH")))
284        ;; SetHome is still enabled by default (level 1), because SBCL
285        ;; does not yet support running without SBCL_HOME:
286        ("Feature" ("Id" "SetHome" "Level" 1 "Title" "Set Environment Variable: SBCL_HOME")
287                   ("ComponentRef" ("Id" "SBCL_SetHOME"))))
288       ("WixVariable" ("Id" "WixUILicenseRtf"
289                       "Value" "License.rtf"))
290       ("Property" ("Id" "WIXUI_INSTALLDIR" "Value" "INSTALLDIR"))
291       ("UIRef" ("Id" "WixUI_FeatureTree"))))))