mes/configure

575 lines
24 KiB
Plaintext
Raw Normal View History

#! /bin/sh
# -*- scheme -*-
unset LANG LC_ALL
guile=$(command -v ${GUILE-guile})
guix=$(command -v ${GUIX-guix})
if [ -n "$guix" ] ; then
install="guix environment -l .guix.scm"
else
install="sudo apt-get install guile-2.2-dev"
fi
if [ -z "$guile" ]; then
cat <<EOF
Missing dependencies: ${GUILE-guile}, please install Guile 2.2 or later; run
$install
EOF
exit 1
fi
GUILE=$guile
export GUILE
exec ${guile} -L . --no-auto-compile -e '(configure)' -s "$0" ${1+"$@"}
!#
;;; GNU Mes --- Maxwell Equations of Software
;;; Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
;;;
;;; configure: This file is part of GNU Mes.
;;;
;;; GNU Mes is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Mes is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Mes. If not, see <http://www.gnu.org/licenses/>.
(define-module (configure)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-9 gnu)
#:use-module (srfi srfi-26)
#:use-module (ice-9 and-let-star)
#:use-module (ice-9 curried-definitions)
#:use-module (ice-9 getopt-long)
#:use-module (ice-9 match)
#:use-module (ice-9 optargs)
#:use-module (ice-9 popen)
#:use-module (ice-9 rdelim)
#:use-module (ice-9 regex)
#:export (main))
(define* (PATH-search-path name #:key (default name) warn?)
(or (search-path (string-split (getenv "PATH") #\:) name)
(and (and warn? (format (current-error-port) "warning: not found: ~a\n" name))
default)))
(define *shell* "sh")
(define PACKAGE "mes")
(define VERSION "0.18")
;;; Utility
(define (logf port string . rest)
(apply format (cons* port string rest))
(force-output port)
#t)
(define (stderr string . rest)
(apply logf (cons* (current-error-port) string rest)))
(define (stdout string . rest)
(apply logf (cons* (current-output-port) string rest)))
(define %verbose? #f)
(define (verbose string . rest)
(if %verbose? (apply stderr (cons string rest))))
(define (gulp-pipe command)
(let* ((port (open-pipe* OPEN_READ *shell* "-c" command))
(output (read-string port))
(status (close-pipe port)))
(verbose "command[~a]: ~s => ~a\n" status command output)
(if (not (zero? status)) "" (string-trim-right output #\newline))))
(define* ((->string #:optional (infix "")) h . t)
(let ((o (if (pair? t) (cons h t) h)))
(match o
((? char?) (make-string 1 o))
((? number?) (number->string o))
((? string?) o)
((? symbol?) (symbol->string o))
((h ... t) (string-join (map (->string) o) ((->string) infix)))
(_ ""))))
(define (tuple< a b)
(cond
((and (null? a) (null? b)) #t)
((null? a) (not (null? b)))
((null? b) #f)
((and (not (< (car a) (car b)))
(not (< (car b) (car a))))
(tuple< (cdr a) (cdr b)))
(else (< (car a) (car b)))))
(define (tuple<= a b)
(or (equal? a b) (tuple< a b)))
(define (conjoin . predicates)
(lambda (. arguments)
(every (cut apply <> arguments) predicates)))
(define (char->char from to char)
(if (eq? char from) to char))
(define (string-replace-char string from to)
(string-map (cut char->char from to <>) string))
;;; Configure
(define-immutable-record-type <dependency>
(make-depedency name version-expected optional? version-option commands file-name data)
dependency?
(name dependency-name)
(version-expected dependency-version-expected)
(version-option dependency-version-option)
(optional? dependency-optional?)
(commands dependency-commands)
(file-name dependency-file-name)
(version-found dependency-version-found)
(data dependency-data))
(define* (make-dep name #:optional (version '(0))
#:key optional? (version-option "--version") (commands (list name)) file-name data)
(let* ((env-var (getenv (name->shell-name name)))
(commands (if env-var (cons env-var commands) commands)))
(make-depedency name version optional? version-option commands file-name data)))
(define (find-dep name deps)
(find (compose (cut equal? <> name) dependency-name) deps))
(define (file-name name deps)
(and=> (find-dep name deps) dependency-file-name))
(define (variable-name dependency)
(and=>
(dependency-name dependency)
name->shell-name))
(define (name->shell-name name)
(string-upcase (string-replace-char name #\- #\_)))
(define (version->string version)
((->string '.) version))
(define (string->version string)
(and-let* ((version (string-tokenize string
(char-set-adjoin char-set:digit #\.)))
((pair? version))
(version (sort version (lambda (a b) (> (string-length a) (string-length b)))))
(version (car version))
(version (string-tokenize version
(char-set-complement (char-set #\.)))))
(map string->number version)))
(define (check-program-version dependency)
(let ((name (dependency-name dependency))
(expected (dependency-version-expected dependency))
(version-option (dependency-version-option dependency))
(commands (dependency-commands dependency)))
(let loop ((commands commands))
(if (null? commands) dependency
(let ((command (car commands)))
(stdout "checking for ~a~a... " name
(if (null? expected) ""
(format #f " [~a]" (version->string expected))))
(let* ((output (gulp-pipe (format #f "~a ~a 2>&1" command version-option)))
(actual (string->version output))
(pass? (and actual (tuple< expected actual)))
(dependency (set-field dependency (dependency-version-found) actual)))
(stdout "~a ~a\n" (if pass? (if (pair? actual) "" " yes")
(if actual " no, found" "no")) (version->string actual))
(if pass? (let ((file-name (or (PATH-search-path command)
(dependency-file-name dependency))))
(set-field dependency (dependency-file-name) file-name))
(loop (cdr commands)))))))))
(define (check-file dependency)
(stdout "checking for ~a... " (dependency-name dependency))
(let ((file-name (and (file-exists? (dependency-file-name dependency))
(dependency-file-name dependency))))
(stdout "~a\n" (or file-name ""))
(set-field dependency (dependency-file-name) file-name)))
(define* (check-header-c cc dependency #:optional (check check-preprocess-header-c))
(let ((name (dependency-name dependency)))
(stderr "checking for ~a..." name)
(let ((result (check cc name)))
(stderr " ~a\n" (if result "yes" "no"))
(if result (set-field dependency (dependency-file-name) name)
dependency))))
(define* (check-compile-c cc dependency #:optional (check check-compile-string-c))
(let ((name (dependency-name dependency)))
(stderr "checking for ~a..." name)
(let ((result (check cc (dependency-data dependency))))
(stderr " ~a\n" (if result "yes" "no"))
(if result (set-field dependency (dependency-file-name) name)
dependency))))
(define* (check-link-c cc dependency #:optional (check check-link-string-c))
(let ((name (dependency-name dependency)))
(stderr "checking for ~a..." name)
(let ((result (check cc (dependency-data dependency))))
(stderr " ~a\n" (if result "yes" "no"))
(if result (set-field dependency (dependency-file-name) name)
dependency))))
(define (check-preprocess-header-c cc header)
(zero? (system (format #f "echo '#include \"~a\"' | ~a -E - > /dev/null 2>&1" header cc))))
(define (check-compile-string-c cc string)
(zero? (system (format #f "echo '~a' | ~a --std=gnu99 -c -x c -o .config.o - > /dev/null 2>&1" string cc))))
(define (check-link-string-c cc string)
(zero? (system (format #f "echo '~a' | ~a -v --std=gnu99 -x c -o .config-a.out - > /dev/null 2>&1" string cc))))
(define (parse-opts args)
(let* ((option-spec
'((build (value #t))
(host (value #t))
(help (single-char #\h))
(mes)
(prefix (value #t))
(program-prefix (value #t))
(bindir (value #t))
(datadir (value #t))
(docdir (value #t))
(libdir (value #t))
(srcdir (value #t))
(sysconfdir (value #t))
(verbose (single-char #\v))
(with-cheating)
(with-courage)
(infodir (value #t))
(mandir (value #t))
(disable-silent-rules)
(enable-silent-rules)
(enable-fast-install) ; Ignored for Guix
(includedir (value #t)) ; Ignored for Debian
(mandir (value #t)) ; Ignored for Debian
(localstatedir (value #t)) ; Ignored for Debian
(libdir (value #t)) ; Ignored for Debian
(libexecdir (value #t)) ; Ignored for Debian
(runstatedir (value #t)) ; Ignored for Debian
(disable-maintainer-mode) ; Ignored for Debian
(disable-dependency-tracking) ; Ignored for Debian
)))
(getopt-long args option-spec)))
(define* (print-help #:optional (port (current-output-port)))
(format port "\
`configure' configures ~a ~a to adapt to many kinds of systems.
Usage: ./configure [OPTION]... [VAR=VALUE]
To assign environment variables (e.g., CC, CFLAGS...), specify them as
VAR=VALUE. See below for descriptions of some of the useful variables.
Defaults for the options are specified in brackets.
Options:
-h, --help display this help
--build=BUILD configure for building on BUILD [guessed]
--disable-silent-rules
verbose build output [V=1]
--host=HOST cross-compile to build programs to run on HOST [BUILD]
--mes use Mes C Library
-v, --verbose be verbose
--with-courage assert being courageous to configure for unsupported platform
--with-cheating cheat using Guile instead of Mes
Installation directories:
--prefix=DIR install in prefix DIR [~a]
--infodir=DIR info documentation [PREFIX/share/info]
--mandir=DIR man pages [PREFIX/share/man]
Program names:
--program-prefix=PREFIX prepend PREFIX to installed program names
--program-suffix=SUFFIX append SUFFIX to installed program names
Ignored for Guix:
--enable-fast-install
Ignored for Debian:
--disable-dependency-tracking
--disable-maintainer-mode
--includedir=DIR
--libdir=DIR
--libexecdir=DIR
--localstatedir=DIR
--runstatedir=DIR
Some influential environment variables:
CC C compiler command
CFLAGS C compiler flags
GUILE guile command
GUILD guild command
MES_FOR_BUILD build system MES [can be mes or guile]
MES_SEED location of mes-seed
TINYCC_PREFIX location of tinycc [for tests/test2]
" PACKAGE VERSION (getenv "prefix")))
(define (main args)
(let* ((options (parse-opts args))
(build-type (option-ref options 'build %host-type))
(host-type (option-ref options 'host %host-type))(prefix "/usr/local")
(prefix "/usr/local")
(prefix (option-ref options 'prefix prefix))
(program-prefix (option-ref options 'program-prefix ""))
(program-suffix (option-ref options 'program-suffix ""))
(infodir (option-ref options 'infodir "${prefix}/share/info"))
(mandir (option-ref options 'infodir "${prefix}/share/man"))
(sysconfdir (option-ref options 'sysconfdir "${prefix}/etc"))
(bindir (option-ref options 'bindir "${prefix}/bin"))
(datadir (option-ref options 'datadir "${prefix}/share"))
(docdir (option-ref options 'docdir "${datadir}/doc/mes-${VERSION}"))
(libdir (option-ref options 'libdir "${prefix}/lib"))
(moduledir "${datadir}/mes/module")
(moduledir/ (gulp-pipe (string-append "echo " prefix "/share/mes/module/")))
(guile-effective-version (effective-version))
(guile-site-dir (if (equal? prefix ".") (canonicalize-path ".")
(string-append "${prefix}/share/guile/site/" guile-effective-version)))
(guile-site-ccache-dir (if (equal? prefix ".") (canonicalize-path ".")
(string-append "${prefix}/lib/guile/" guile-effective-version "/site-ccache")))
(srcdir (dirname (car (command-line))))
(srcdest (if (equal? srcdir ".") ""
(string-append srcdir "/")))
(abs-top-srcdir (canonicalize-path srcdir))
(abs-top-builddir (canonicalize-path (getcwd)))
(top-builddir (if (equal? srcdir ".") "."
abs-top-builddir))
(with-cheating? (option-ref options 'with-cheating #f))
(with-courage? (option-ref options 'with-courage #f))
(disable-silent-rules? (option-ref options 'disable-silent-rules #f))
(enable-silent-rules? (option-ref options 'enable-silent-rules #f))
(vars (filter (cut string-index <> #\=) (option-ref options '() '())))
(help? (option-ref options 'help #f))
(mes? (option-ref options 'mes #f)))
(when help?
(print-help)
(exit 0))
(set! %verbose? (option-ref options 'verbose #f))
(when %verbose?
(stderr "configure args=~s\n" args))
(for-each (lambda (v) (apply setenv (string-split v #\=))) vars)
(let* ((mes-seed (or (getenv "MES_SEED")
(string-append srcdest "../mes-seed")))
(mes-seed (and mes-seed
(file-exists? (string-append mes-seed "/x86-mes/mes.S"))
mes-seed))
(tinycc-prefix (or (getenv "TINYCC_PREFIX")
(string-append srcdest "../tinycc-prefix")))
(gcc (or (getenv "CC") "gcc"))
(tcc (or (getenv "TCC") "tcc"))
(mescc (or (getenv "MESCC") "mescc"))
(deps (fold (lambda (program results)
(cons (check-program-version program) results))
'()
(list (make-dep "hex2" '(0 3))
(make-dep "M1" '(0 3))
(make-dep "blood-elf" '(0 1))
(make-dep "guile" '(2 0) #:commands '("guile-2.2" "guile-2.0" "guile-2" "guile") #:optional? #t)
(make-dep "mes" '(0 18) #:optional? #t)
(make-dep "guix" '(0 13) #:optional? #t)
(make-dep "ar" '(2 10) #:optional? #t)
(make-dep "bash" '(2 0) #:optional? #t)
(make-dep "guild" '(2 0) #:commands '("guild" "guile-tools"))
(make-dep "cc" '(2 95) #:commands (list gcc tcc mescc) #:optional? #t)
(make-dep "make" '(4) #:optional? #t)
(make-dep "makeinfo" '(5) #:optional? #t)
(make-dep "dot" '(2) #:version-option "-V" #:optional? #t)
(make-dep "help2man" '(1 47) #:optional? #t)
(make-dep "perl" '(5) #:optional? #t)
(make-dep "git" '(2) #:optional? #t))))
(deps (cons (check-program-version (make-dep "nyacc" '(0 86 0) #:commands (list (string-append (file-name "guile" deps) " -c '(use-modules (nyacc lalr)) (display *nyacc-version*)'")) #:file-name #t))
deps))
(cc (file-name "cc" deps))
(deps (if cc
(cons* (check-header-c cc (make-dep "limits.h"))
(check-header-c cc (make-dep "stdio.h" #:optional? #t))
deps)
deps))
(deps (cons (check-file (make-dep "tinycc-prefix" '(0) #:optional? #t
#:file-name tinycc-prefix))
deps))
(missing (filter (conjoin (negate dependency-file-name)
(negate dependency-optional?)) deps))
(deps (if cc
(cons (check-compile-c cc (make-dep "cc is GNU C" #:data "#if !defined (__GNUC__)
#error no gnuc
#endif
"))
deps)
deps))
(gcc? (file-name "cc is GNU C" deps))
(deps (if cc
(cons (check-compile-c cc (make-dep "cc is Mes C" #:data "#if !defined (__MESC__)
#error no mesc
#endif
"))
deps)
deps))
(mesc? (file-name "cc is Mes C" deps))
(deps (if cc
(cons (check-compile-c cc (make-dep "cc is Tiny CC" #:data "#if !defined (__TINYCC__)
#error no tinycc
#endif
"))
deps)
deps))
(tcc? (file-name "cc is Tiny CC" deps))
(deps (if cc
(cons (check-link-c cc (make-dep "if cc can create executables" #:data "int main () {return 0;}"))
deps)
deps))
(mes? (or mes? (not (file-name "if cc can create executables" deps))))
(build-type (or (and cc (gulp-pipe* cc "-dumpmachine")) build-type))
(arch (car (string-split build-type #\-)))
(arch (if (member arch '("i386" "i486" "i586" "i686")) "x86"
arch))
(mes-arch arch)
(mes-arch (if mes? (string-append mes-arch "-mes") mes-arch))
(mes-arch (if gcc? (string-append mes-arch "-gcc") mes-arch))
(mes-arch (if tcc? (string-append mes-arch "-gcc") mes-arch))
(posix? (and (not mesc?) (not mes?))))
(define* (substitute file-name pairs
#:key (target (if (string-suffix? ".in" file-name)
(string-drop-right file-name 3) file-name)))
(system* "mkdir" "-p" (dirname target))
(with-output-to-file target
(lambda _
(display
(fold (lambda (o result)
(regexp-substitute/global #f (car o) result 'pre (cdr o) 'post))
(with-input-from-file file-name read-string) pairs)))))
(when (and (not (member arch '("x86" "x86_64"))) (not with-courage?))
(stderr "platform not supported: ~a, try --with-courage\n" arch)
(exit 1))
(when (pair? missing)
(stderr "\nMissing dependencies: ~a\n" (string-join (map dependency-name missing)))
(exit 1))
(let ((git (find-dep "git" deps)))
(when (and git
(not (file-exists? ".git")))
;; Debian wants to run `make clean' from a tarball
(and (zero? (system* "git" "init"))
(zero? (system* "git" "add" "."))
(zero? (system* "git" "commit" "--allow-empty" "-m" "Import mes")))))
(let ((pairs `(("@PACKAGE@" . ,PACKAGE)
("@VERSION@" . ,VERSION)
("@arch@" . ,arch)
("@build@" . ,build-type)
("@host@" . ,host-type)
("@gcc_p@" . ,(if gcc? "1" ""))
("@mes_arch@" . ,mes-arch)
("@mes_p@" . ,(if mes? "1" ""))
("@mesc_p@" . ,(if mesc? "1" ""))
("@posix_p@" . ,(if posix? "1" ""))
("@tcc_p@" . ,(if tcc? "1" ""))
("@abs_top_srcdir@" . ,abs-top-srcdir)
("@abs_top_builddir@" . ,abs-top-builddir)
("@top_builddir@" . ,top-builddir)
("@srcdest@" . ,srcdest)
("@srcdir@" . ,srcdir)
("@prefix@" . ,prefix)
("@program_prefix@" . ,program-prefix)
("@bindir@" . ,bindir)
("@datadir@" . ,datadir)
("@docdir@" . ,docdir)
("@guile_site_ccache_dir@" . ,guile-site-ccache-dir)
("@guile_site_dir@" . ,guile-site-dir)
("@infodir@" . ,infodir)
("@libdir@" . ,libdir)
("@mandir@" . ,mandir)
("@moduledir@" . ,moduledir)
("@sysconfdir@" . ,sysconfdir)
("@GUILE_EFFECTIVE_VERSION@" . ,(effective-version))
("@V@" . ,(if disable-silent-rules? 1 0))
("@AR@" . ,(or (file-name "ar" deps) ""))
("@BASH@" . ,(or (file-name "bash" deps) ""))
("@CC@" . ,(or (file-name "cc" deps) ""))
("@DOT@" . ,(Fileor -name "dot" deps))
("@GIT@" . ,(or (file-name "git" deps) ""))
("@GUILE@" . ,(file-name "guile" deps))
("@GUIX@" . ,(or (file-name "guix" deps) ""))
("@HELP2MAN@" . ,(or (file-name "help2man" deps) ""))
("@MAKEINFO@" . ,(or (file-name "makeinfo" deps) ""))
("@MES_FOR_BUILD@" . ,(or (file-name "mes" deps)
(file-name "guile" deps)))
("@MES_SEED@" . ,(or mes-seed ""))
("@PERL@" . ,(or (file-name "perl" deps) ""))
("@CFLAGS@" . ,(or (getenv "CFLAGS") ""))
("@HEX2FLAGS@" . ,(or (getenv "HEX2FLAGS") ""))
("@M1FLAGS@" . ,(or (getenv "M1FLAGS") ""))
("mes/module/" . ,(string-append moduledir/))
,@(map
(lambda (o)
(cons (string-append "@" (variable-name o) "@") (or (format #f "~a" (dependency-file-name o)) "")))
deps))))
(when (and (not cc)
(not mes-seed))
(format (current-error-port) "must supply C compiler or MES_SEED/x86-mes/mes.S\n")
(exit 2))
(for-each (lambda (o)
(let* ((src (string-append srcdest o))
(target (string-drop-right o 3))
(target (if (not (string-prefix? "build-aux/" target)) target
(string-drop target (string-length "build-aux/")))))
(substitute src pairs #:target target)))
'(
"build-aux/GNUmakefile.in"
"build-aux/config.status.in"
"build-aux/build.sh.in"
"build-aux/check.sh.in"
"build-aux/install.sh.in"
"build-aux/pre-inst-env.in"
"build-aux/uninstall.sh.in"
"mes/module/mes/boot-0.scm.in"
"scripts/mescc.in"
))
(chmod "pre-inst-env" #o755)
(chmod "scripts/mescc" #o755)
(chmod "build.sh" #o755)
(chmod "check.sh" #o755)
(chmod "install.sh" #o755)
(chmod "uninstall.sh" #o755)
(substitute (string-append srcdest "build-aux/config.make.in") pairs #:target ".config.make"))
(let ((make (and=> (file-name "make" deps) basename)))
(format (current-output-port)
"
GNU Mes is configured for ~a
Run:
~a to build mes
~a help for help on other targets\n"
mes-arch
(or make "./build.sh")
(or make "./build.sh"))))))