windows-installer.scm 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. ; Copyright (c) 1993-2008 by Richard Kelsey and Jonathan Rees. See file COPYING.
  2. ; This produces the auto-generated part of the Windows installer
  3. ; source code.
  4. ; WINDOWS-FILE_NAME-SHORT-NAME implements the algorithm described on:
  5. ; http://support.microsoft.com/kb/142982/EN-US/
  6. ; (Except we use _ instead of ~.)
  7. ; We need this to generate an installer, among other things.
  8. ; The silliness of this crap makes Mike's head spin.
  9. ; ,open srfi-13 srfi-14
  10. (define *ms-dos-invalid-characters*
  11. (char-set-union char-set:whitespace
  12. ;; the spec also sez #\., which is ... silly
  13. (char-set #\" #\/ #\\ #\[ #\] #\: #\; #\= #\,)))
  14. ; OTHERS is a list of the file names generated previously
  15. ; This may return #f if everything is taken---Mike has no idea what
  16. ; Windows does in that case.
  17. (define (windows-file-name-short-name file-name others)
  18. (call-with-values
  19. (lambda ()
  20. ;; find the dot relevant for separating base from extension
  21. (let loop ((f (string-upcase
  22. (string-delete *ms-dos-invalid-characters* file-name))))
  23. (let ((last-dot-index (string-index-right f #\.)))
  24. (cond
  25. ((not last-dot-index) (values f ""))
  26. ((= (- (string-length f) 1)
  27. last-dot-index)
  28. (loop (substring f 0 (- (string-length f) 1))))
  29. (else
  30. (values (substring f 0 last-dot-index)
  31. (substring f (+ 1 last-dot-index) (string-length f))))))))
  32. (lambda (base extension)
  33. (let* ((extension (if (> (string-length extension) 3)
  34. (substring extension 0 3)
  35. extension))
  36. (attach-extension
  37. (if (string=? "" extension)
  38. values
  39. (lambda (base) (string-append base "." extension)))))
  40. ;; try the ~1, ~2, ... short versions
  41. (if (or (> (string-length base) 8)
  42. (string-index file-name char-set:whitespace))
  43. (let ((prefix (string-append (substring base 0
  44. (min 6 (string-length base)))
  45. "_")))
  46. (let loop ((digit 1))
  47. (if (> digit 9)
  48. #f
  49. (let ((attempt
  50. (attach-extension (string-append prefix
  51. (number->string digit)))))
  52. (if (not (member attempt others))
  53. attempt
  54. (loop (+ 1 digit)))))))
  55. (attach-extension base))))))
  56. (define *non-slashes* (char-set-complement (char-set #\/)))
  57. ; returns a pair of directory (itself a list) and base file name
  58. (define (split-file-name f)
  59. (let ((rev-components (reverse (string-tokenize f *non-slashes*))))
  60. (cons (reverse (cdr rev-components))
  61. (car rev-components))))
  62. (define (write-file-elements-include-file file-names uuids output-file-name)
  63. (call-with-output-file output-file-name
  64. (lambda (port)
  65. (display "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" port)
  66. (newline port)
  67. (display "<Include>" port)
  68. (newline port)
  69. (write-file-elements file-names uuids port)
  70. (display "</Include>" port)
  71. (newline port))))
  72. ; organize the directories into a tree
  73. ; returns a tree = list of (union string (cons subdir tree))
  74. (define (tree-ize-directory-alist alist)
  75. (call-with-values
  76. (lambda ()
  77. (partition (lambda (pair) (null? (car pair))) alist))
  78. (lambda (file-pairs directory-pairs)
  79. (let* ((prefixes (delete-duplicates (map caar directory-pairs)))
  80. (subdirectories
  81. (map (lambda (prefix)
  82. (let* ((with-prefix-pairs
  83. (filter (lambda (pair)
  84. (string=? prefix (caar pair)))
  85. directory-pairs))
  86. (omit-prefix
  87. (map (lambda (pair)
  88. (cons (cdar pair) (cdr pair)))
  89. with-prefix-pairs)))
  90. (cons prefix (tree-ize-directory-alist omit-prefix))))
  91. prefixes)))
  92. (append subdirectories (concatenate (map cdr file-pairs)))))))
  93. ; write the WiX file elements for a given list of file names
  94. (define (write-file-elements file-names uuids port)
  95. (let* ((split-names (map split-file-name file-names))
  96. (directories (delete-duplicates (map car split-names)))
  97. (alist
  98. (map (lambda (directory)
  99. (cons directory
  100. (filter-map (lambda (split-name)
  101. (if (equal? directory (car split-name))
  102. (cdr split-name)
  103. #f))
  104. split-names)))
  105. directories))
  106. (tree (tree-ize-directory-alist alist)))
  107. (write-directory-tree '() tree (make-uuid-source uuids) port)))
  108. (define (make-uuid-source uuids)
  109. (lambda ()
  110. (let ((uuid (car uuids)))
  111. (set! uuids (cdr uuids))
  112. uuid)))
  113. (define (write-directory-tree directory alist uuid-source port)
  114. (if (not (null? directory))
  115. (begin
  116. (display "<Directory Id=\"" port)
  117. (display (directory-id directory) port)
  118. (display "\" Name=\"" port)
  119. (display (windows-file-name-short-name (car (reverse directory))
  120. '()) ; not completely kosher
  121. port)
  122. (display "\" LongName=\"" port)
  123. (display (car (reverse directory)) port)
  124. (display "\" >" port)
  125. (newline port)))
  126. (call-with-values
  127. (lambda () (partition string? alist))
  128. (lambda (file-names directory-entries)
  129. (if (not (null? file-names))
  130. (begin
  131. (display "<Component Id=\"" port)
  132. (display (component-id directory) port)
  133. (display "\" Guid=\"" port)
  134. (display (uuid-source) port)
  135. (display "\">" port)
  136. (newline port)
  137. (let ((used-file-names (list '()))) ; poor man's cell
  138. (for-each (lambda (file-name)
  139. (write-file-element port directory file-name used-file-names))
  140. file-names))
  141. (display "</Component>" port)
  142. (newline port)))
  143. (for-each (lambda (entry)
  144. (write-directory-tree (append directory (list (car entry)))
  145. (cdr entry)
  146. uuid-source
  147. port))
  148. directory-entries)))
  149. (if (not (null? directory))
  150. (begin
  151. (display "</Directory>" port)
  152. (newline port))))
  153. (define (quote-component comp)
  154. (list->string
  155. (map (lambda (ch)
  156. (if (char=? ch #\-)
  157. #\.
  158. ch))
  159. (string->list comp))))
  160. ; insert separators between the components
  161. (define (components->string directory separator)
  162. (let ((id #f))
  163. (for-each (lambda (component)
  164. (if id
  165. (set! id (string-append id separator component))
  166. (set! id component)))
  167. directory)
  168. id))
  169. (define (components->quoted-string dir sep)
  170. (components->string (map quote-component dir) sep))
  171. (define (directory-id directory)
  172. (components->quoted-string directory "_"))
  173. (define (file-id directory base)
  174. (components->quoted-string (append directory (list base)) "_"))
  175. (define (file-src directory base)
  176. (components->string (append directory (list base)) "/"))
  177. (define (component-id directory)
  178. (components->quoted-string (append directory (list "component")) "_"))
  179. (define (write-file-element port directory base-name used-file-names)
  180. (display "<File Id=\"" port)
  181. (display (file-id directory base-name) port)
  182. (display "\" Name=\"" port)
  183. (let ((short-name (windows-file-name-short-name base-name (car used-file-names))))
  184. (set-car! used-file-names (cons short-name (car used-file-names)))
  185. (display short-name port))
  186. (display "\" LongName=\"" port)
  187. (display base-name port)
  188. (display "\" src=\"" port)
  189. (display (file-src directory base-name) port)
  190. (display "\" DiskId=\"1\" Vital=\"yes\" />" port)
  191. (newline port))