mirror of
git://git.code.sf.net/p/sbcl/sbcl
synced 2026-09-10 07:26:40 -04:00
318 lines
12 KiB
Common Lisp
318 lines
12 KiB
Common Lisp
;;;; Generate WiX XML Source, from which we eventually generate the .MSI
|
|
|
|
;;;; This software is part of the SBCL system. See the README file for
|
|
;;;; more information.
|
|
;;;;
|
|
;;;; This software is derived from the CMU CL system, which was
|
|
;;;; written at Carnegie Mellon University and released into the
|
|
;;;; public domain. The software is in the public domain and is
|
|
;;;; provided with absolutely no warranty. See the COPYING and CREDITS
|
|
;;;; files for more information.
|
|
|
|
;;;; Restore internal-features. Needed to package libsbcl files if
|
|
;;;; sb-linkable-runtime is enabled.
|
|
(eval-when (:compile-toplevel :load-toplevel :execute)
|
|
(setq *features* (union *features* sb-impl:+internal-features+)))
|
|
|
|
;;;; XML generation
|
|
|
|
(defvar *indent-level* 0)
|
|
|
|
(defvar *sbcl-source-root*
|
|
(truename
|
|
(merge-pathnames (make-pathname :directory (list :relative :up))
|
|
(make-pathname :name nil :type nil :defaults *load-truename*))))
|
|
|
|
(defun print-xml (sexp &optional (stream *standard-output*))
|
|
(destructuring-bind (tag &optional attributes &body children) sexp
|
|
(when attributes (assert (evenp (length attributes))))
|
|
(format stream "~V@T<~A~{ ~A='~A'~}~@[/~]>~%"
|
|
*indent-level* tag attributes (not children))
|
|
(let ((*indent-level* (+ *indent-level* 3)))
|
|
(dolist (child children)
|
|
(unless (listp child)
|
|
(error "Malformed child: ~S in ~S" child children))
|
|
(print-xml child stream)))
|
|
(when children
|
|
(format stream "~V@T</~A>~%" *indent-level* tag))))
|
|
|
|
(defun xml-1.0 (pathname sexp)
|
|
(with-open-file (xml pathname :direction :output :if-exists :supersede
|
|
:external-format :ascii)
|
|
(format xml "<?xml version='1.0'?>~%")
|
|
(print-xml sexp xml)))
|
|
|
|
(defun application-name ()
|
|
"Steel Bank Common Lisp")
|
|
|
|
(defun application-name/version+machine-type ()
|
|
(format nil "~A ~A (~A)"
|
|
(application-name) (lisp-implementation-version) (machine-type)))
|
|
|
|
(defun manufacturer-name ()
|
|
"http://www.sbcl.org")
|
|
|
|
(defun upgrade-code ()
|
|
"BFF1D4CA-0153-4AAC-BB21-06DC4B8EAD7D")
|
|
|
|
(defun version-digits (&optional (horrible-thing (lisp-implementation-version)))
|
|
"Turns something like 0.pre7.14.flaky4.13 (see version.lisp-expr)
|
|
into an acceptable form for WIX (up to four dot-separated numbers)."
|
|
(with-output-to-string (output)
|
|
(loop with position = 0
|
|
for separator = "" then "."
|
|
for next-digit = (position-if #'digit-char-p horrible-thing :start position)
|
|
repeat 4
|
|
while next-digit
|
|
do (multiple-value-bind (number end)
|
|
(parse-integer horrible-thing :start next-digit :junk-allowed t)
|
|
(format output "~A~D" separator number)
|
|
(setf position end)))))
|
|
|
|
;;;; GUID generation
|
|
;;;;
|
|
;;;; Apparently this willy-nilly regeneration of GUIDs is a bad thing, and
|
|
;;;; we should probably have a single GUID per release / Component, so
|
|
;;;; that no matter by whom the .MSI is built the GUIDs are the same.
|
|
;;;;
|
|
;;;; Something to twiddle on a rainy day, I think.
|
|
|
|
(load-shared-object "OLE32.DLL")
|
|
|
|
(define-alien-type uuid
|
|
(struct uuid
|
|
(data1 unsigned-int)
|
|
(data2 unsigned-short)
|
|
(data3 unsigned-short)
|
|
(data4 (array unsigned-char 8))))
|
|
|
|
(define-alien-routine ("CoCreateGuid" co-create-guid) int (guid (* uuid)))
|
|
|
|
(defun uuid-string (uuid)
|
|
(declare (type (alien (* uuid)) uuid))
|
|
(let ((data4 (slot uuid 'data4)))
|
|
(format nil "~8,'0X-~4,'0X-~4,'0X-~2,'0X~2,'0X-~{~2,'0X~}"
|
|
(slot uuid 'data1)
|
|
(slot uuid 'data2)
|
|
(slot uuid 'data3)
|
|
(deref data4 0)
|
|
(deref data4 1)
|
|
(loop for i from 2 upto 7 collect (deref data4 i)))))
|
|
|
|
(defun make-guid ()
|
|
(let (guid)
|
|
(unwind-protect
|
|
(progn
|
|
(setf guid (make-alien (struct uuid)))
|
|
(co-create-guid guid)
|
|
(uuid-string guid))
|
|
(free-alien guid))))
|
|
|
|
(defvar *id-char-substitutions* '((#\\ . #\_)
|
|
(#\/ . #\_)
|
|
(#\: . #\.)
|
|
(#\- . #\.)))
|
|
|
|
(defun id (string)
|
|
;; Mangle a string till it can be used as an Id. A-Z, a-z, 0-9, and
|
|
;; _ are ok, nothing else is.
|
|
(map 'string (lambda (c)
|
|
(or (cdr (assoc c *id-char-substitutions*))
|
|
c))
|
|
string))
|
|
|
|
(defun directory-id (name)
|
|
(id (format nil "Directory_~A" (enough-namestring name *sbcl-source-root*))))
|
|
|
|
(defun file-id (pathname)
|
|
(id (format nil "File_~A" (enough-namestring pathname *sbcl-source-root*))))
|
|
|
|
(defparameter *ignored-directories* '("CVS" ".svn"))
|
|
|
|
(defparameter *pathname-type-abbrevs*
|
|
'(("lisp" . "lsp")
|
|
("fasl" . "fas")
|
|
("SBCL" . "txt") ; README.SBCL -> README.txt
|
|
("texinfo" . "tfo")
|
|
("lisp-temp" . "lmp")
|
|
("html" . "htm")))
|
|
|
|
(defparameter *components* nil)
|
|
|
|
(defun component-id (pathname)
|
|
(let ((id (id (format nil "Contrib_~A" (enough-namestring pathname *sbcl-source-root*)))))
|
|
(push id *components*)
|
|
id))
|
|
|
|
(defun ref-all-components ()
|
|
(prog1
|
|
(mapcar (lambda (id)
|
|
`("ComponentRef" ("Id" ,id)))
|
|
*components*)
|
|
(setf *components* nil)))
|
|
|
|
(defun collect-1-component (root)
|
|
`("Directory" ("Name" ,(car (last (pathname-directory root)))
|
|
"Id" ,(directory-id root))
|
|
("Component" ("Id" ,(component-id root)
|
|
"Guid" ,(make-guid)
|
|
"DiskId" 1
|
|
#+64-bit "Win64" #+64-bit "yes")
|
|
,@(loop for file in (directory
|
|
(make-pathname :name :wild :type :wild :defaults root))
|
|
when (or (pathname-name file) (pathname-type file))
|
|
collect `("File" ("Name" ,(file-namestring file)
|
|
"Id" ,(file-id file)
|
|
"Source" ,(enough-namestring file)))))))
|
|
|
|
(defun directory-empty-p (dir)
|
|
(null (directory (make-pathname :name :wild :type :wild :defaults dir))))
|
|
|
|
(defun collect-components (root)
|
|
(append (unless (directory-empty-p root) (list (collect-1-component root)))
|
|
(loop for directory in
|
|
(directory
|
|
(merge-pathnames (make-pathname
|
|
:directory '(:relative :wild)
|
|
:name nil :type nil)
|
|
root))
|
|
unless (member (car (last (pathname-directory directory)))
|
|
*ignored-directories* :test #'equal)
|
|
append (collect-components directory))))
|
|
|
|
(defun collect-contrib-components ()
|
|
(collect-components "../obj/sbcl-home/contrib/"))
|
|
|
|
(defun split-string-on-spaces (list-string)
|
|
"Takes a string of space separated tokens and returns them as a list
|
|
of strings."
|
|
(let ((next-space (position #\Space list-string)))
|
|
(if next-space
|
|
(list* (subseq list-string 0 next-space)
|
|
(split-string-on-spaces (subseq list-string (1+ next-space))))
|
|
(list list-string))))
|
|
|
|
(defun read-libsbcl-value-from-sbcl.mk ()
|
|
"Parse sbcl.mk, looking for the LIBSBCL= line and return its value."
|
|
(with-open-file (s "../src/runtime/sbcl.mk")
|
|
(loop for line = (read-line s nil nil)
|
|
while line
|
|
for match-location = (search "LIBSBCL=" line)
|
|
when (and match-location (zerop match-location))
|
|
do (return (subseq line 8)))))
|
|
|
|
(defun collect-libsbcl-files ()
|
|
(let ((filenames (list* "sbcl.mk"
|
|
(split-string-on-spaces (read-libsbcl-value-from-sbcl.mk)))))
|
|
(loop for filename in filenames
|
|
collecting `("File" ("Name" ,filename
|
|
"Source" ,(concatenate 'string "../src/runtime/" filename))))))
|
|
|
|
(defun make-extension (type mime)
|
|
`("Extension" ("Id" ,type "ContentType" ,mime)
|
|
("Verb" ("Id" ,(format nil "load_~A" type)
|
|
"Argument" "--core \"[#sbcl.core]\" --load \"%1\""
|
|
"Command" "Load with SBCL"
|
|
"Target" "[#sbcl.exe]"))))
|
|
|
|
(defun write-wxs (pathname)
|
|
;; both :INVERT and :PRESERVE could be used here, but this seemed
|
|
;; better at the time
|
|
(xml-1.0
|
|
pathname
|
|
`("Wix" ("xmlns" "http://schemas.microsoft.com/wix/2006/wi")
|
|
("Product" ("Id" "*"
|
|
"Name" ,(application-name/version+machine-type)
|
|
"Version" ,(version-digits)
|
|
"Manufacturer" ,(manufacturer-name)
|
|
"UpgradeCode" ,(upgrade-code)
|
|
"Language" 1033)
|
|
("Package" ("Id" "*"
|
|
"Manufacturer" ,(manufacturer-name)
|
|
"InstallerVersion" 500
|
|
"Compressed" "yes"
|
|
#+(or arm64 x86-64) "Platform" #+x86-64 "x64" #+arm64 "arm64"
|
|
"InstallScope" "perMachine"))
|
|
("Media" ("Id" 1
|
|
"Cabinet" "sbcl.cab"
|
|
"EmbedCab" "yes"))
|
|
("Property" ("Id" "PREVIOUSVERSIONSINSTALLED"
|
|
"Secure" "yes"))
|
|
("Upgrade" ("Id" ,(upgrade-code))
|
|
("UpgradeVersion" ("Minimum" "1.0.0"
|
|
"Maximum" "99.0.0"
|
|
"Property" "PREVIOUSVERSIONSINSTALLED"
|
|
"IncludeMinimum" "yes"
|
|
"IncludeMaximum" "no")))
|
|
("InstallExecuteSequence" ()
|
|
("RemoveExistingProducts" ("After" "InstallInitialize")))
|
|
("Directory" ("Id" "TARGETDIR"
|
|
"Name" "SourceDir")
|
|
("Directory" ("Id" "ProgramMenuFolder")
|
|
("Directory" ("Id" "ProgramMenuDir"
|
|
"Name" ,(application-name/version+machine-type))
|
|
("Component" ("Id" "ProgramMenuDir"
|
|
"Guid" ,(make-guid))
|
|
("RemoveFolder" ("Id" "ProgramMenuDir"
|
|
"On" "uninstall"))
|
|
("RegistryValue" ("Root" "HKCU"
|
|
"Key" "Software\\[Manufacturer]\\[ProductName]"
|
|
"Type" "string"
|
|
"Value" ""
|
|
"KeyPath" "yes")))))
|
|
("Directory" ("Id" #+x86 "ProgramFilesFolder"
|
|
#+64-bit "ProgramFiles64Folder"
|
|
"Name" "PFiles")
|
|
("Directory" ("Id" "BaseFolder"
|
|
"Name" ,(application-name))
|
|
("Directory" ("Id" "INSTALLDIR")
|
|
("Component" ("Id" "SBCL_SetPATH"
|
|
"Guid" ,(make-guid)
|
|
"DiskId" 1
|
|
#+64-bit "Win64" #+64-bit "yes")
|
|
("CreateFolder")
|
|
("Environment" ("Id" "Env_PATH"
|
|
"System" "yes"
|
|
"Action" "set"
|
|
"Name" "PATH"
|
|
"Part" "last"
|
|
"Value" "[INSTALLDIR]")))
|
|
("Component" ("Id" "SBCL_Base"
|
|
"Guid" ,(make-guid)
|
|
"DiskId" 1
|
|
#+64-bit "Win64" #+64-bit "yes")
|
|
;; If we want to associate files with SBCL, this
|
|
;; is how it's done -- but doing this by default
|
|
;; and without asking the user for permission Is
|
|
;; Bad. Before this is enabled we need to figure out
|
|
;; how to make WiX ask for permission for this...
|
|
;; ,(make-extension "fasl" "application/x-lisp-fasl")
|
|
;; ,(make-extension "lisp" "text/x-lisp-source")
|
|
("File" ("Name" "sbcl.core"
|
|
"Source" "sbcl.core"))
|
|
#+sb-linkable-runtime
|
|
,@(collect-libsbcl-files)
|
|
("File" ("Name" "sbcl.exe"
|
|
"Source" "../src/runtime/sbcl.exe"
|
|
"KeyPath" "yes")
|
|
("Shortcut" ("Id" "sbcl.lnk"
|
|
"Advertise" "yes"
|
|
"Name" ,(application-name/version+machine-type)
|
|
"Directory" "ProgramMenuDir"
|
|
"Arguments" "--core \"[#sbcl.core]\""))))
|
|
,@(collect-contrib-components)))))
|
|
("Feature" ("Id" "Minimal"
|
|
"Title" "SBCL Executable"
|
|
"ConfigurableDirectory" "INSTALLDIR"
|
|
"Level" 1)
|
|
("ComponentRef" ("Id" "SBCL_Base"))
|
|
("ComponentRef" ("Id" "ProgramMenuDir"))
|
|
("Feature" ("Id" "Contrib" "Level" 1 "Title" "Contributed Modules")
|
|
,@(ref-all-components))
|
|
("Feature" ("Id" "SetPath" "Level" 1 "Title" "Set Environment Variable: PATH")
|
|
("ComponentRef" ("Id" "SBCL_SetPATH"))))
|
|
("WixVariable" ("Id" "WixUILicenseRtf"
|
|
"Value" "License.rtf"))
|
|
("Property" ("Id" "WIXUI_INSTALLDIR" "Value" "INSTALLDIR"))
|
|
("UIRef" ("Id" "WixUI_FeatureTree"))))))
|