racket/collects/compiler/distribute.ss
Matthew Flatt 91add0453f 369.4
svn: r5327
2007-01-12 07:09:56 +00:00

427 lines
13 KiB
Scheme

(module distribute mzscheme
(require (lib "kw.ss")
(lib "file.ss")
(lib "dirs.ss" "setup")
(lib "list.ss")
(lib "variant.ss" "setup")
(lib "filename-version.ss" "dynext")
"private/macfw.ss"
"private/windlldir.ss"
"private/collects-path.ss")
(provide assemble-distribution)
(define/kw (assemble-distribution dest-dir
binaries
#:key
[collects-path #f] ; relative to dest-dir
[copy-collects null])
(let* ([types (map get-binary-type binaries)]
[_ (unless (directory-exists? dest-dir)
(make-directory dest-dir))]
[sub-dirs (map (lambda (b type)
(case (system-type)
[(windows) #f]
[(unix) "bin"]
[(macosx) (if (memq type '(mredcgc mred3m))
#f
"bin")]))
binaries
types)]
;; Copy binaries into place:
[binaries
(map (lambda (b sub-dir type)
(let ([dest-dir (if sub-dir
(build-path dest-dir sub-dir)
dest-dir)])
(unless (directory-exists? dest-dir)
(make-directory dest-dir))
(let-values ([(base name dir?) (split-path b)])
(let ([dest (build-path dest-dir name)])
(if (and (memq type '(mredcgc mred3m))
(eq? 'macosx (system-type)))
(begin
(copy-app b dest)
(app-to-file dest))
(begin
(copy-file* b dest)
dest))))))
binaries
sub-dirs
types)]
[single-mac-app? (and (eq? 'macosx (system-type))
(= 1 (length types))
(memq (car types) '(mredcgc mred3m)))])
;; Create directories for libs and collects:
(let-values ([(lib-dir collects-dir relative-collects-dir)
(if single-mac-app?
;; Special case: single Mac OS X MrEd app:
(let-values ([(base name dir?)
(split-path (car binaries))])
(values
(simplify-path (build-path base 'up "Frameworks"))
(if collects-path
(build-path dest-dir collects-path)
(simplify-path (build-path base
'up
"Resources"
"collects")))
(if collects-path
(build-path 'up 'up 'up collects-path)
(build-path 'up "Resources" "collects"))))
;; General case:
(let ([relative-collects-dir
(or collects-path
(build-path "lib"
"plt"
(let-values ([(base name dir?)
(split-path (car binaries))])
(path-replace-suffix name #""))
"collects"))])
(values (build-path dest-dir "lib")
(build-path dest-dir relative-collects-dir)
relative-collects-dir)))])
(make-directory* lib-dir)
(make-directory* collects-dir)
;; Copy libs into place
(install-libs lib-dir types)
;; Copy collections into place
(for-each (lambda (dir)
(for-each (lambda (f)
(copy-directory/files*
(build-path dir f)
(build-path collects-dir f)))
(directory-list dir)))
copy-collects)
;; Patch binaries to find libs
(patch-binaries binaries types)
;; Patch binaries to find collects
(for-each (lambda (b type sub-dir)
(set-collects-path
b
(collects-path->bytes
(cond
[sub-dir
(build-path 'up relative-collects-dir)]
[(and (eq? 'macosx (system-type))
(memq type '(mred mredx))
(not single-mac-app?))
(build-path 'up 'up 'up relative-collects-dir)]
[else
relative-collects-dir]))))
binaries types sub-dirs))
;; Done!
(void)))
(define (install-libs lib-dir types)
(case (system-type)
[(windows)
(let ([copy-dll (lambda (name)
(copy-file* (search-dll (find-dll-dir) name)
(build-path lib-dir name)))]
[versionize (lambda (template)
(let ([f (search-dll (find-dll-dir)
(format template filename-version-part))])
(if (file-exists? f)
(format template filename-version-part)
(format template "xxxxxxx"))))])
(map copy-dll
(list
"iconv.dll"
"UnicoWS.dll"))
(when (or (memq 'mzschemecgc types)
(memq 'mredcgc types))
(map copy-dll
(list
(versionize "libmzsch~a.dll")
(versionize "libmzgc~a.dll"))))
(when (or (memq 'mzscheme3m types)
(memq 'mred3m types))
(map copy-dll
(list
(versionize "libmzsch3m~a.dll"))))
(when (memq 'mredcgc types)
(map copy-dll
(list
(versionize "libmred~a.dll"))))
(when (memq 'mred3m types)
(map copy-dll
(list
(versionize "libmred3m~a.dll")))))]
[(macosx)
(when (memq 'mzschemecgc types)
(copy-framework "MzScheme" #f lib-dir))
(when (memq 'mzscheme3m types)
(copy-framework "MzScheme" #t lib-dir))
(when (memq 'mredcgc types)
(copy-framework "MrEd" #f lib-dir))
(when (memq 'mred3m types)
(copy-framework "MrEd" #t lib-dir))]
[(unix)
(let ([lib-plt-dir (build-path lib-dir "plt")])
(unless (directory-exists? lib-plt-dir)
(make-directory lib-plt-dir))
(let ([copy-bin
(lambda (name variant)
(copy-file* (build-path (find-console-bin-dir)
(format "~a~a" name (variant-suffix variant #f)))
(build-path lib-plt-dir
(format "~a~a-~a" name variant (version)))))])
(when (memq 'mzschemecgc types)
(copy-bin "mzscheme" 'cgc))
(when (memq 'mzscheme3m types)
(copy-bin "mzscheme" '3m))
(when (memq 'mredcgc types)
(copy-bin "mred" 'cgc))
(when (memq 'mred3m types)
(copy-bin "mred" '3m)))
(when (shared-libraries?)
(when (or (memq 'mzschemecgc types)
(memq 'mredcgc types))
(copy-shared-lib "mzscheme" lib-dir)
(copy-shared-lib "mzgc" lib-dir))
(when (or (memq 'mzscheme3m types)
(memq 'mred3m types))
(copy-shared-lib "mzscheme3m" lib-dir))
(when (memq 'mredcgc types)
(copy-shared-lib "mred" lib-dir))
(when (memq 'mred3m types)
(copy-shared-lib "mred3m" lib-dir))))]))
(define (search-dll dll-dir dll)
(if dll-dir
(build-path dll-dir dll)
(let* ([exe-dir
(let ([exec (path->complete-path
(find-executable-path (find-system-path 'exec-file))
(find-system-path 'orig-dir))])
(let-values ([(base name dir?) (split-path exec)])
base))]
[paths (cons
exe-dir
(path-list-string->path-list
(or (getenv "PATH") "")
(list (find-system-path 'sys-dir))))])
(or (ormap (lambda (p)
(let ([p (build-path p dll)])
(and (file-exists? p)
p)))
paths)
;; Can't find it, so just use executable's dir:
(build-path exe-dir dll)))))
(define (copy-framework name 3m? lib-dir)
(let* ([fw-name (format "PLT_~a.framework" name)]
[sub-dir (build-path fw-name "Versions"
(if 3m?
(format "~a_3m" (version))
(version)))])
(make-directory* (build-path lib-dir sub-dir))
(let* ([fw-name (build-path sub-dir (format "PLT_~a" name))]
[dll-dir (find-framework fw-name)])
(copy-file* (build-path dll-dir fw-name)
(build-path lib-dir fw-name))
(let ([rsrc-src (build-path dll-dir sub-dir "Resources")])
(when (directory-exists? rsrc-src)
(copy-directory/files*
rsrc-src
(build-path lib-dir sub-dir "Resources")))))))
(define (find-framework fw-name)
(let ([dll-dir (find-dll-dir)])
(or dll-dir
(ormap (lambda (p)
(let ([f (build-path p fw-name)])
(and (file-exists? f)
p)))
'("/System/Library/Frameworks"
"/Library/Frameworks"
"~/Library/Frameworks"))
;; Can't find it, so just use relative path:
(build-path 'same))))
;; cache:
(define avail-lib-files #f)
(define (copy-shared-lib name lib-dir)
(unless avail-lib-files
(set! avail-lib-files (directory-list (find-dll-dir))))
(let* ([rx (byte-regexp (string->bytes/latin-1
(format "lib~a-~a.*[.](?:so|dylib)$" name (version))))]
[files (filter (lambda (f)
(regexp-match rx (path->bytes f)))
avail-lib-files)])
(when (null? files)
(error 'copy-shared-lib "cannot find shared library for ~a"
name))
(unless (null? (cdr files))
(error 'copy-shared-lib
"found multiple shared-library candidates for ~a: ~e"
name
files))
(copy-file* (build-path (find-dll-dir) (car files))
(build-path lib-dir (car files)))))
(define (patch-binaries binaries types)
(case (system-type)
[(windows)
(for-each (lambda (b)
(update-dll-dir b "lib"))
binaries)]
[(macosx)
(if (and (= 1 (length types))
(memq (car types) '(mredcgc mred3m)))
;; Special case for single MrEd app:
(update-framework-path "@executable_path/../Frameworks/"
(car binaries)
#t)
;; General case:
(for-each (lambda (b type)
(update-framework-path (if (memq type '(mzschemecgc mzscheme3m))
"@executable_path/../lib/"
"@executable_path/../../../lib/" )
b
(memq type '(mredcgc mred3m))))
binaries types))]
[(unix)
(for-each (lambda (b type)
(patch-stub-exe-paths b
(build-path
"../lib/plt"
(format "~a-~a" type (version)))
(and (shared-libraries?)
"../lib")))
binaries
types)]))
(define (patch-stub-exe-paths b exe shared-lib-dir)
;; Adjust paths to executable and DLL that is embedded in the executable
(let-values ([(config-pos start end prog-len dll-len rest)
(with-input-from-file b
(lambda ()
(let* ([i (current-input-port)]
[m (regexp-match-positions #rx#"cOnFiG:" i)])
(unless m
(error 'patch-stub-exe-paths
"cannot find config info"))
(read-byte i)
(read-one-int i) ; start of prog
(let ([start (read-one-int i)] ; start of data
[end (read-one-int i)]) ; end of data
(file-position i start)
(let ([prog-len (next-bytes-length i)]
[dll-len (next-bytes-length i)])
(values (+ (cdar m) 1) ; position after "cOnFiG:[" tag
start
end
prog-len
dll-len
(read-bytes (- (- end start) prog-len dll-len))))))))])
(let ([exe-bytes (path->bytes (to-path exe))]
[shared-lib-bytes (if shared-lib-dir
(path->bytes (to-path shared-lib-dir))
#"")])
(let ([delta (- (+ prog-len dll-len)
(add1 (bytes-length exe-bytes))
(add1 (bytes-length shared-lib-bytes)))])
(with-output-to-file b
(lambda ()
(let ([o (current-output-port)])
(file-position o (+ config-pos 8)) ; update the end of the program data
(write-one-int (- end delta) o)
(flush-output o)
(file-position o start)
(write-bytes exe-bytes o)
(write-bytes #"\0" o)
(write-bytes shared-lib-bytes o)
(write-bytes #"\0" o)
(write-bytes rest o)
(flush-output o)))
'update)))))
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Utilities
(define (shared-libraries?)
(eq? 'shared (system-type 'link)))
(define (to-path s)
(if (string? s)
(string->path s)
s))
(define (get-binary-type b)
;; Since this is called first, we also check that the executable
;; is a stub binary for Unix.
(with-input-from-file (app-to-file b)
(lambda ()
(let ([m (regexp-match #rx#"bINARy tYPe:(e?)(.)(.)(.)" (current-input-port))])
(if m
(begin
(when (eq? 'unix (system-type))
(unless (equal? (cadr m) #"e")
(error 'assemble-distribution
"file is an original PLT executable, not a stub binary: ~e"
b)))
(let ([3m? (equal? (list-ref m 4) #"3")])
(if (equal? (caddr m) #"r")
(if 3m?
'mred3m
'mredcgc)
(if 3m?
'mzscheme3m
'mzschemecgc))))
(error 'assemble-distribution
"file is not a PLT executable: ~e"
b))))))
(define (write-one-int n out)
(write-bytes (integer->integer-bytes n 4 #t #f) out))
(define (read-one-int in)
(integer-bytes->integer (read-bytes 4 in) #t #f))
(define (next-bytes-length in)
(let ([m (regexp-match-positions #rx#"\0" in)])
(cdar m)))
(define (copy-file* src dest)
(when (or (file-exists? dest)
(link-exists? dest))
(delete-file dest))
(copy-file src dest)
(let ([t (file-or-directory-modify-seconds src)])
(file-or-directory-modify-seconds dest t)))
(define (copy-directory/files* src dest)
(cond
[(directory-exists? src)
(unless (directory-exists? dest)
(make-directory dest))
(for-each (lambda (f)
(copy-directory/files* (build-path src f)
(build-path dest f)))
(directory-list src))]
[else
(copy-file* src dest)]))
(define (copy-app src dest)
(when (or (file-exists? dest)
(directory-exists? dest)
(link-exists? dest))
(delete-directory/files dest))
(copy-directory/files src dest))
(define (app-to-file b)
(if (and (eq? 'macosx (system-type))
(regexp-match #rx#"[.][aA][pP][pP]$"
(path->bytes (if (string? b)
(string->path b)
b))))
(let ([no-app
(let-values ([(base name dir?) (split-path b)])
(path-replace-suffix name #""))])
(build-path b "Contents" "MacOS" no-app))
b)))