#! /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 < ;;; ;;; 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 . (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 (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") (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@" . ,(file-name "ar" deps)) ("@BASH@" . ,(file-name "bash" deps)) ("@CC@" . ,(or (file-name "cc" deps) "")) ("@DOT@" . ,(file-name "dot" deps)) ("@GIT@" . ,(or (file-name "git" deps) "")) ("@GUILE@" . ,(file-name "guile" deps)) ("@GUIX@" . ,(or (file-name "guix" deps) "")) ("@HELP2MAN@" . ,(file-name "help2man" deps)) ("@MAKEINFO@" . ,(file-name "makeinfo" deps)) ("@MES_FOR_BUILD@" . ,(or (file-name "mes" deps) (file-name "guile" deps))) ("@MES_SEED@" . ,(or mes-seed "")) ("@PERL@" . ,(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"))))))