windows-installer.scm 6.6 KB

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