racket/collects/compiler/distribute.rkt
Michael Filonenko 35a093469c windows: extflonum support
Enable extflonums in a MSVC build by relying on a MinGW-built DLL,
"longdouble.dll". The DLL is loaded on startup. If the DLL isn't
available, then `extflonum-available?' reports #f.

Instead of setting the floating-point mode globally to extended
precision, the mode is changed only just before (and restored right
after) extflonum arithmetic operations.
2013-03-18 07:42:32 -07:00

623 lines
25 KiB
Racket

(module distribute scheme/base
(require scheme/file
scheme/path
setup/dirs
mzlib/list
setup/variant
dynext/filename-version
"private/macfw.rkt"
"private/windlldir.rkt"
"private/collects-path.rkt")
(provide assemble-distribution)
(define (assemble-distribution dest-dir
orig-binaries
#:collects-path [collects-path #f] ; relative to dest-dir
#:copy-collects [copy-collects null])
(let* ([types (map get-binary-type orig-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 '(gracketcgc gracket3m))
#f
"bin")]))
orig-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 '(gracketcgc gracket3m))
(eq? 'macosx (system-type)))
(begin
(copy-app b dest)
(app-to-file dest))
(begin
(copy-file* b dest)
dest))))))
orig-binaries
sub-dirs
types)]
[single-mac-app? (and (eq? 'macosx (system-type))
(= 1 (length types))
(memq (car types) '(gracketcgc gracket3m)))])
;; Create directories for libs, collects, and extensions:
(let-values ([(lib-dir collects-dir relative-collects-dir exts-dir relative-exts-dir)
(if single-mac-app?
;; Special case: single Mac OS X GRacket 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"))
(build-path base 'up "Resources" "exts")
(build-path 'up "Resources" "exts")))
;; General case:
(let* ([specific-lib-dir
(build-path "lib"
"plt"
(if (null? binaries)
"generic"
(let-values ([(base name dir?)
(split-path (car binaries))])
(path-replace-suffix name #""))))]
[relative-collects-dir
(or collects-path
(build-path specific-lib-dir
"collects"))])
(values (build-path dest-dir "lib")
(build-path dest-dir relative-collects-dir)
relative-collects-dir
(build-path dest-dir specific-lib-dir "exts")
(build-path specific-lib-dir "exts"))))])
(make-directory* lib-dir)
(make-directory* collects-dir)
(make-directory* exts-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)
(let ([relative->binary-relative
(lambda (sub-dir type relative-dir)
(cond
[sub-dir
(build-path 'up relative-dir)]
[(and (eq? 'macosx (system-type))
(memq type '(gracketcgc gracket3m))
(not single-mac-app?))
(build-path 'up 'up 'up relative-dir)]
[else
relative-dir]))])
;; Patch binaries to find collects
(for-each (lambda (b type sub-dir)
(set-collects-path
b
(collects-path->bytes
(relative->binary-relative sub-dir type relative-collects-dir))))
binaries types sub-dirs)
(unless (null? binaries)
;; Copy over extensions and adjust embedded paths:
(copy-extensions-and-patch-binaries orig-binaries binaries types sub-dirs
exts-dir
relative-exts-dir
relative->binary-relative)
;; Copy over runtime files and adjust embedded paths:
(copy-runtime-files-and-patch-binaries orig-binaries binaries types sub-dirs
exts-dir
relative-exts-dir
relative->binary-relative)
;; 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
(if (equal? "win32\\x86_64" (path->string (system-library-subpath #f)))
"libiconv-2.dll"
"iconv.dll")
"longdouble.dll"))
(when (or (memq 'racketcgc types)
(memq 'gracketcgc types))
(map copy-dll
(list
(versionize "libracket~a.dll")
(versionize "libmzgc~a.dll"))))
(when (or (memq 'racket3m types)
(memq 'gracket3m types))
(map copy-dll
(list
(versionize "libracket3m~a.dll")))))]
[(macosx)
(when (or (memq 'racketcgc types)
(memq 'gracketcgc types))
(copy-framework "Racket" #f lib-dir))
(when (or (memq 'racket3m types)
(memq 'gracket3m types))
(copy-framework "Racket" #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 'racketcgc types)
(copy-bin "racket" 'cgc))
(when (memq 'racket3m types)
(copy-bin "racket" '3m))
(when (memq 'gracketcgc types)
(copy-bin "gracket" 'cgc))
(when (memq 'gracket3m types)
(copy-bin "gracket" '3m)))
(when (shared-libraries?)
(when (or (memq 'racketcgc types)
(memq 'gracketcgc types))
(copy-shared-lib "racket" lib-dir)
(copy-shared-lib "mzgc" lib-dir))
(when (or (memq 'racket3m types)
(memq 'gracket3m types))
(copy-shared-lib "racket3m" 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 "~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 "~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) '(gracketcgc gracket3m)))
;; Special case for single GRacket app:
(update-framework-path "@executable_path/../Frameworks/"
(car binaries)
#t)
;; General case:
(for-each (lambda (b type)
(update-framework-path (if (memq type '(racketcgc racket3m))
"@executable_path/../lib/"
"@executable_path/../../../lib/" )
b
(memq type '(gracketcgc gracket3m))))
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 decls
(read-one-int i) ; start of program
(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
#:exists 'update
(lambda ()
(let ([o (current-output-port)])
(file-position o (+ config-pos 12)) ; 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))))))))
(define (copy-and-patch-binaries copy? magic
extract-src construct-dest transform-entry
init-counter inc-counter
orig-binaries binaries types sub-dirs
exts-dir relative-exts-dir
relative->binary-relative)
(let loop ([orig-binaries orig-binaries]
[binaries binaries]
[types types]
[sub-dirs sub-dirs]
[counter init-counter])
(unless (null? binaries)
(let-values ([(exts start-pos end-pos)
(with-input-from-file (car binaries)
(lambda ()
(let* ([i (current-input-port)]
[m (regexp-match-positions magic i)])
(if m
;; Read table:
(begin
(file-position i (cdar m))
(let ([l (read i)])
(values (cadr l) (cdar m) (file-position i))))
;; No table:
(values null #f #f)))))])
(if (null? exts)
(loop (cdr orig-binaries) (cdr binaries) (cdr types) (cdr sub-dirs) counter)
(let-values ([(new-exts counter)
;; Copy over the extensions for this binary, generating a separate path
;; for each executable
(let loop ([exts exts][counter counter])
(cond
[(null? exts) (values null counter)]
[(and (pair? (car (car exts)))
(pair? (cdar (car exts)))
(eq? 'module (cadar (car exts))))
(let-values ([(rest-exts counter)
(loop (cdr exts) counter)])
(values (cons (car exts) rest-exts) counter))]
[else
(let* ([src (extract-src (car exts) (car orig-binaries))]
[dest (construct-dest src)]
[sub (format "e~a" counter)])
(when (and src copy?)
; Make dest and copy
(make-directory* (build-path exts-dir sub (or (path-only dest) 'same)))
(let ([f (build-path exts-dir sub dest)])
(when (or (file-exists? f)
(directory-exists? f)
(link-exists? f))
(delete-directory/files f))
(copy-directory/files src f)))
;; Generate the new extension entry for the table, and combine with
;; recur result for the rest:
(let-values ([(rest-exts counter)
(loop (cdr exts) (inc-counter counter))])
(values (if src
(cons (transform-entry
(path->bytes
(relative->binary-relative (car sub-dirs)
(car types)
(build-path relative-exts-dir sub dest)))
(car exts))
rest-exts)
(cons (car exts)
rest-exts))
counter)))]))])
(when copy?
;; Update the binary with the new paths
(let* ([str (string->bytes/utf-8 (format "~s" new-exts))]
[extra-space 7] ; = "(quote" plus ")"
[delta (- (- end-pos start-pos) (bytes-length str) extra-space)])
(when (negative? delta)
(error 'copy-and-patch-binaries
"not enough room in executable for revised ~s table"
magic))
(with-output-to-file (car binaries)
#:exists 'update
(lambda ()
(let ([o (current-output-port)])
(file-position o start-pos)
(write-bytes #"(quote" o)
(write-bytes str o)
;; Add space before final closing paren. This preserves space in case the
;; genereated binary is input for a future distribution build.
(write-bytes (make-bytes delta (char->integer #\space)) o)
(write-bytes #")" o))))))
(loop (cdr orig-binaries) (cdr binaries) (cdr types) (cdr sub-dirs) counter)))))))
(define (copy-extensions-and-patch-binaries orig-binaries binaries types sub-dirs
exts-dir relative-exts-dir
relative->binary-relative)
(copy-and-patch-binaries #t #rx#"eXtEnSiOn-modules[)]"
;; extract-src:
(lambda (ext orig-binary)
(path->complete-path
(bytes->path (car ext))
(let-values ([(base name dir?)
(split-path (path->complete-path orig-binary
(current-directory)))])
base)))
;; construct-dest:
(lambda (src)
(let-values ([(base name dir?) (split-path src)])
name))
;; transform-entry
(lambda (new-path ext)
(list new-path (cadr ext)))
0 add1 ; <- counter
orig-binaries binaries types sub-dirs
exts-dir relative-exts-dir
relative->binary-relative))
(define (copy-runtime-files-and-patch-binaries orig-binaries binaries types sub-dirs
exts-dir relative-exts-dir
relative->binary-relative)
(let ([paths null])
;; Pass 1: collect all the paths
(copy-and-patch-binaries #f #rx#"rUnTiMe-paths[)]"
;; extract-src:
(lambda (rt orig-binary)
(and (cadr rt)
(bytes? (cadr rt))
(bytes->path (cadr rt))))
;; construct-dest:
(lambda (src)
(when src
(set! paths (cons src paths)))
"dummy")
;; transform-entry
(lambda (new-path ext) ext)
"rt" values ; <- counter
orig-binaries binaries types sub-dirs
exts-dir relative-exts-dir
relative->binary-relative)
(unless (null? paths)
;; Determine the shared path prefix:
(let* ([root-table (make-hash)]
[root->path-element (lambda (root)
(hash-ref root-table
root
(lambda ()
(let ([v (format "r~a" (hash-count root-table))])
(hash-set! root-table root v)
v))))]
[explode (lambda (src)
(reverse
(let loop ([src src])
(let-values ([(base name dir?) (split-path src)])
(if base
(cons name (loop base))
(list (root->path-element name)))))))]
;; In reverse order, so we can pick off the paths
;; in the second pass:
[exploded (reverse (map explode paths))]
[max-len (apply max 0 (map length exploded))]
[common-len (let loop ([cnt 0])
(cond
[((add1 cnt) . = . max-len) cnt]
[(andmap (let ([i (list-ref (car exploded) cnt)])
(lambda (e)
(equal? (list-ref e cnt) i)))
exploded)
(loop (add1 cnt))]
[else cnt]))])
;; Pass 2: change all the paths
(copy-and-patch-binaries #t #rx#"rUnTiMe-paths[)]"
;; extract-src:
(lambda (rt orig-binary)
(and (cadr rt)
(bytes->path (cadr rt))))
;; construct-dest:
(lambda (src)
(and src
(begin0
(apply build-path (list-tail (car exploded) common-len))
(set! exploded (cdr exploded)))))
;; transform-entry
(lambda (new-path ext)
(cons (car ext) (list new-path)))
"rt" values ; <- counter
orig-binaries binaries types sub-dirs
exts-dir relative-exts-dir
relative->binary-relative)))))
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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?
'gracket3m
'gracketcgc)
(if 3m?
'racket3m
'racketcgc))))
(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)))