GNU Mes.
[mes.git] / configure
1 #! /bin/sh
2 # -*- scheme -*-
3 unset LANG LC_ALL
4 guile=$(command -v ${GUILE-guile})
5 guix=$(command -v ${GUIX-guix})
6 if [ -n "$guix" ] ; then
7     install="guix environment -l .guix.scm"
8 else
9     install="sudo apt-get install guile-2.2-dev"
10 fi
11 if [ -z "$guile" ]; then
12     cat <<EOF
13
14 Missing dependencies: ${GUILE-guile}, please install Guile 2.2 or later; run
15     $install
16 EOF
17 exit 1
18 fi
19 GUILE=$guile
20 export GUILE
21 exec ${guile} -L . --no-auto-compile -e '(configure)' -s "$0" ${1+"$@"}
22 !#
23
24 ;;; GNU Mes --- Maxwell Equations of Software
25 ;;; Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
26 ;;;
27 ;;; configure: This file is part of GNU Mes.
28 ;;;
29 ;;; GNU Mes is free software; you can redistribute it and/or modify it
30 ;;; under the terms of the GNU General Public License as published by
31 ;;; the Free Software Foundation; either version 3 of the License, or (at
32 ;;; your option) any later version.
33 ;;;
34 ;;; GNU Mes is distributed in the hope that it will be useful, but
35 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
36 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37 ;;; GNU General Public License for more details.
38 ;;;
39 ;;; You should have received a copy of the GNU General Public License
40 ;;; along with GNU Mes.  If not, see <http://www.gnu.org/licenses/>.
41
42 (define-module (configure)
43   #:use-module (srfi srfi-1)
44   #:use-module (srfi srfi-9)
45   #:use-module (srfi srfi-9 gnu)
46   #:use-module (srfi srfi-26)
47   #:use-module (ice-9 and-let-star)
48   #:use-module (ice-9 curried-definitions)
49   #:use-module (ice-9 getopt-long)
50   #:use-module (ice-9 match)
51   #:use-module (ice-9 optargs)
52   #:use-module (ice-9 popen)
53   #:use-module (ice-9 rdelim)
54   #:use-module (ice-9 regex)
55   #:export (main))
56
57 (define* (PATH-search-path name #:key (default name) warn?)
58   (or (search-path (string-split (getenv "PATH") #\:) name)
59       (and (and warn? (format (current-error-port) "warning: not found: ~a\n" name))
60            default)))
61
62 (define *shell* "sh")
63 (define PACKAGE "mes")
64 (define VERSION "0.16.1")
65
66 ;;; Utility
67 (define (logf port string . rest)
68   (apply format (cons* port string rest))
69   (force-output port)
70   #t)
71
72 (define (stderr string . rest)
73   (apply logf (cons* (current-error-port) string rest)))
74
75 (define (stdout string . rest)
76   (apply logf (cons* (current-output-port) string rest)))
77
78 (define %verbose? #f)
79
80 (define (verbose string . rest)
81   (if %verbose? (apply stderr (cons string rest))))
82
83 (define (gulp-pipe command)
84   (let* ((port (open-pipe* OPEN_READ *shell* "-c" command))
85          (output (read-string port))
86          (status (close-pipe port)))
87     (verbose "command[~a]: ~s => ~a\n" status command output)
88     (if (not (zero? status)) "" (string-trim-right output #\newline))))
89
90 (define* ((->string #:optional (infix "")) h . t)
91   (let ((o (if (pair? t) (cons h t) h)))
92     (match o
93       ((? char?) (make-string 1 o))
94       ((? number?) (number->string o))
95       ((? string?) o)
96       ((? symbol?) (symbol->string o))
97       ((h ... t) (string-join (map (->string) o) ((->string) infix)))
98       (_ ""))))
99
100 (define (tuple< a b)
101   (cond
102    ((and (null? a) (null? b)) #t)
103    ((null? a) (not (null? b)))
104    ((null? b) #f)
105    ((and (not (< (car a) (car b)))
106          (not (< (car b) (car a))))
107     (tuple< (cdr a) (cdr b)))
108    (else (< (car a) (car b)))))
109
110 (define (tuple<= a b)
111   (or (equal? a b) (tuple< a b)))
112
113 (define (conjoin . predicates)
114   (lambda (. arguments)
115     (every (cut apply <> arguments) predicates)))
116
117 (define (char->char from to char)
118   (if (eq? char from) to char))
119
120 (define (string-replace-char string from to)
121   (string-map (cut char->char from to <>) string))
122
123 ;;; Configure
124
125 (define-immutable-record-type <dependency>
126   (make-depedency name version-expected optional? version-option commands file-name)
127   dependency?
128   (name dependency-name)
129   (version-expected dependency-version-expected)
130   (version-option dependency-version-option)
131   (optional? dependency-optional?)
132   (commands dependency-commands)
133   (file-name dependency-file-name)
134   (version-found dependency-version-found))
135
136 (define* (make-dep name #:optional (version '(0))
137                    #:key optional? (version-option "--version") (commands (list name)) file-name)
138   (let* ((env-var (getenv (name->shell-name name)))
139          (commands (if env-var (cons env-var commands) commands)))
140    (make-depedency name version optional? version-option commands file-name)))
141
142 (define (find-dep name deps)
143   (find (compose (cut equal? <> name) dependency-name) deps))
144
145 (define (file-name name deps)
146   (and=> (find-dep name deps) dependency-file-name))
147
148 (define (variable-name dependency)
149   (and=>
150    (dependency-name dependency)
151    name->shell-name))
152
153 (define (name->shell-name name)
154   (string-upcase (string-replace-char name #\- #\_)))
155
156 (define (version->string version)
157   ((->string '.) version))
158
159 (define (string->version string)
160   (and-let* ((version (string-tokenize string
161                                        (char-set-adjoin char-set:digit #\.)))
162              ((pair? version))
163              (version (sort version (lambda (a b) (> (string-length a) (string-length b)))))
164              (version (car version))
165              (version (string-tokenize version
166                                        (char-set-complement (char-set #\.)))))
167             (map string->number version)))
168
169 (define (check-program-version dependency)
170   (let ((name (dependency-name dependency))
171         (expected (dependency-version-expected dependency))
172         (version-option (dependency-version-option dependency))
173         (commands (dependency-commands dependency)))
174     (let loop ((commands commands))
175       (if (null? commands) dependency
176           (let ((command (car commands)))
177             (stdout "checking for ~a~a... " command
178                     (if (null? expected) ""
179                         (format #f " [~a]" (version->string expected))))
180             (let* ((output (gulp-pipe (format #f "~a ~a 2>&1" command version-option)))
181                    (actual (string->version output))
182                    (pass? (and actual (tuple< expected actual)))
183                    (dependency (set-field dependency (dependency-version-found) actual)))
184               (stdout "~a ~a\n" (if pass? (if (pair? actual) "" " yes")
185                                     (if actual " no, found" "no")) (version->string actual))
186               (if pass? (let ((file-name (or (PATH-search-path command)
187                                              (dependency-file-name dependency))))
188                           (set-field dependency (dependency-file-name) file-name))
189                   (loop (cdr commands)))))))))
190
191 (define (check-file dependency)
192   (stdout "checking for ~a... " (dependency-name dependency))
193   (let ((file-name (and (file-exists? (dependency-file-name dependency))
194                         (dependency-file-name dependency))))
195     (stdout "~a\n" (or file-name ""))
196     (set-field dependency (dependency-file-name) file-name)))
197
198 (define* (check-header-c dependency  #:optional (check check-compile-header-c))
199   (let ((name (dependency-name dependency)))
200     (stderr "checking for ~a..." name)
201     (let ((result (check name)))
202       (stderr " ~a\n" (if result "yes" "no"))
203       (if result (set-field dependency (dependency-file-name) name)
204           dependency-file-name))))
205
206 (define (check-compile-header-c header)
207   (zero? (system (format #f "echo '#include ~s' | gcc -E - > /dev/null 2>&1" header))))
208
209 (define (parse-opts args)
210   (let* ((option-spec
211           '((build (value #t))
212             (host (value #t))
213             (help (single-char #\h))
214             (prefix (value #t))
215             (bindir (value #t))
216             (datadir (value #t))
217             (docdir (value #t))
218             (libdir (value #t))
219             (sysconfdir (value #t))
220             (verbose (single-char #\v))
221             (with-courage)
222             (infodir (value #t))
223             (mandir (value #t))
224             (disable-silent-rules)
225
226             (enable-fast-install)       ; Ignored for Guix
227             (includedir (value #t))     ; Ignored for Debian
228             (mandir (value #t))         ; Ignored for Debian
229             (localstatedir (value #t))  ; Ignored for Debian
230             (libdir (value #t))         ; Ignored for Debian
231             (libexecdir (value #t))     ; Ignored for Debian
232             (runstatedir (value #t))    ; Ignored for Debian
233             (disable-maintainer-mode)   ; Ignored for Debian
234             (disable-dependency-tracking) ; Ignored for Debian
235             )))
236
237     (getopt-long args option-spec)))
238
239 (define* (print-help #:optional (port (current-output-port)))
240   (format port "\
241 `configure' configures ~a ~a to adapt to many kinds of systems.
242
243 Usage: ./configure [OPTION]... [VAR=VALUE]
244
245 To assign environment variables (e.g., CC, CFLAGS...), specify them as
246 VAR=VALUE.  See below for descriptions of some of the useful variables.
247
248 Defaults for the options are specified in brackets.
249
250 Options:
251   -h, --help           display this help
252       --build=BUILD    configure for building on BUILD [guessed]
253       --disable-silent-rules
254                        verbose build output [BUILD_DEBUG=1]
255       --host=HOST      cross-compile to build programs to run on HOST [BUILD]
256   -v, --verbose        be verbose
257   --with-courage       assert being courageous to configure for unsupported platform
258
259 Installation directories:
260   --prefix=DIR         install in prefix DIR [~a]
261   --infodir=DIR        info documentation [PREFIX/share/info]
262   --mandir=DIR         man pages [PREFIX/share/man]
263
264 Ignored for Guix:
265   --enable-fast-install
266
267 Ignored for Debian:
268   --disable-dependency-tracking
269   --disable-maintainer-mode
270   --includedir=DIR
271   --libdir=DIR
272   --libexecdir=DIR
273   --localstatedir=DIR
274   --runstatedir=DIR
275
276 Some influential environment variables:
277   CC                C compiler command
278   CFLAGS            C compiler flags
279   CC32              x86 C compiler command
280   CC32_CFLAGS       x86 C compiler flags
281   GUILE             guile command
282   GUILE_TOOLS       guile-tools command
283   MES_CFLAGS        MesCC flags
284   MES_SEED          location of mes-seed
285   MESCC_TOOLS_SEED  location of mescc-tools-seed
286   TCC               tcc C compiler command
287   TINYCC_SEED       location of tinycc-seed
288 " PACKAGE VERSION (getenv "prefix")))
289
290 (define (main args)
291   (let* ((options (parse-opts args))
292          (build-type (option-ref options 'build %host-type))
293
294          (arch (car (string-split build-type #\-)))
295          (host-type (option-ref options 'host %host-type))(prefix "/usr/local")
296
297          (prefix "/usr/local")
298          (prefix (option-ref options 'prefix prefix))
299          (infodir (option-ref options 'infodir "${prefix}/share/info"))
300          (mandir (option-ref options 'infodir "${prefix}/share/man"))
301          (sysconfdir (option-ref options 'sysconfdir "${prefix}/etc"))
302
303          (bindir (option-ref options 'bindir "${prefix}/etc"))
304          (datadir (option-ref options 'datadir "${prefix}/share"))
305          (docdir (option-ref options 'docdir "${datadir}/doc/mes-${VERSION}"))
306          (libdir (option-ref options 'libdir "${prefix}/lib"))
307          (moduledir "${datadir}/mes/module")
308          (guile-effective-version (effective-version))
309          (guile-site-dir (if (equal? prefix ".") (canonicalize-path ".")
310                              (string-append "${prefix}/share/guile/site/" guile-effective-version)))
311          (guile-site-ccache-dir (if (equal? prefix ".") (canonicalize-path ".")
312                                     (string-append "${prefix}/lib/guile/" guile-effective-version "/site-ccache")))
313
314          (srcdir (dirname (car (command-line))))
315          (abs-top-srcdir (canonicalize-path srcdir))
316          (abs-top-builddir (canonicalize-path (getcwd)))
317          (top-builddir (if (equal? srcdir ".") "."
318                            abs-top-builddir))
319          (top-builddest (if (equal? srcdir ".") ""
320                         (string-append abs-top-builddir "/")))
321
322
323          (with-courage? (option-ref options 'with-courage #f))
324          (disable-silent-rules? (option-ref options 'disable-silent-rules #f))
325          (vars (filter (cut string-index <> #\=) (option-ref options '() '())))
326          (help? (option-ref options 'help #f)))
327     (define (srcdir-relative file-name)
328       (if (equal? srcdir ".") file-name
329           (string-append srcdir "/" file-name)))
330     (when help?
331       (print-help)
332       (exit 0))
333     (set! %verbose? (option-ref options 'verbose #f))
334     (when %verbose?
335       (stderr "configure args=~s\n" args))
336     (for-each (lambda (v) (apply setenv (string-split v #\=))) vars)
337     (let* ((mes-seed (or (getenv "MES_SEED")
338                          (srcdir-relative "../mes-seed")))
339            (tinycc-seed (or (getenv "TINYCC_SEED")
340                             (srcdir-relative "../tinycc-seed")))
341            (mescc-tools-seed (or (getenv "MESCC_TOOLS_SEED")
342                                  (srcdir-relative "../mescc-tools-seed")))
343            (deps (fold (lambda (program results)
344                          (cons (check-program-version program) results))
345                        '()
346                        (list (make-dep "guile" '(2 0) #:commands '("guile-2.2" "guile-2.0" "guile-2" "guile"))
347                              (make-dep "guix" '(0 13) #:optional? #t)
348                              (make-dep "bash" '(2 0) #:optional? #t)
349                              (make-dep "guile-tools" '(2 0))
350                              (make-dep "mes-seed" '(0 16 1) #:optional? #t
351                                        #:commands (list (string-append mes-seed "/refresh.sh"))
352                                        #:file-name mes-seed)
353                              (make-dep "tinycc-seed" '(0 16) #:optional? #t
354                                        #:commands (list (string-append tinycc-seed "/refresh.sh"))
355                                        #:file-name tinycc-seed)
356                              (make-dep "cc" '(2 95) #:commands '("gcc"))
357                              (make-dep "make" '(4))
358                              (make-dep "cc32" '(2 95)
359                                        #:optional? #t
360                                        #:commands '("i686-unknown-linux-gnu-gcc"))
361                              (make-dep "M1" '(0 3))
362                              (make-dep "blood-elf" '(0 1))
363                              (make-dep "hex2" '(0 3))
364                              (make-dep "tcc" '(0 9 26) #:optional? #t #:version-option "-v")
365                              (make-dep "makeinfo" '(5) #:optional? #t)
366                              (make-dep "dot" '(2) #:version-option "-V")
367                              (make-dep "help2man" '(1 47) #:optional? #t)
368                              (make-dep "perl" '(5) #:optional? #t)
369                              (make-dep "git" '(2) #:optional? #t))))
370            (deps (cons (check-program-version (make-dep "nyacc" '(0 80 41) #:commands (list (string-append (file-name "guile" deps) " -c '(use-modules (nyacc lalr)) (display *nyacc-version*)'")) #:file-name #t))
371                        deps))
372            (deps (if (file-name "cc" deps)
373                      (cons* (check-header-c (make-dep "stdio.h"))
374                             (check-header-c (make-dep "limits.h"))
375                             deps)
376                      deps))
377            (deps (cons (check-file (make-dep "mescc-tools-seed" '(0) #:optional? #t
378                                              #:file-name mescc-tools-seed))
379                        deps))
380            (missing (filter (conjoin (negate dependency-file-name)
381                                      (negate dependency-optional?)) deps)))
382
383       (define* (substitute file-name pairs
384                            #:key (target (if (string-suffix? ".in" file-name)
385                                              (string-drop-right file-name 3) file-name)))
386         (system* "mkdir" "-p" (dirname target))
387         (with-output-to-file target
388           (lambda _
389             (display
390              (fold (lambda (o result)
391                      (regexp-substitute/global #f (car o) result 'pre (cdr o) 'post))
392                    (with-input-from-file file-name read-string) pairs)))))
393
394       (when (and (not (member arch '("i686" "x86_64"))) (not with-courage?))
395         (stderr "platform not supported: ~a, try --with-courage\n" arch)
396         (exit 1))
397       (when (pair? missing)
398         (stderr "\nMissing dependencies: ~a\n" (string-join (map dependency-name missing)))
399         (exit 1))
400       (let ((git (find-dep "git" deps)))
401         (when (and git
402                    (not (file-exists? ".git")))
403           ;; Debian wants to run `make clean' from a tarball
404           (and (zero? (system* "git" "init"))
405                (zero? (system* "git" "add" "."))
406                (zero? (system* "git" "commit" "--allow-empty" "-m" "Import mes")))))
407       (with-output-to-file ".config.make"
408         (lambda _
409           (stdout "PACKAGE:=~a\n" PACKAGE)
410           (stdout "VERSION:=~a\n" VERSION)
411
412           (stdout "ARCH:=~a\n" arch)
413           (stdout "build:=~a\n" build-type)
414           (stdout "host:=~a\n" host-type)
415
416           (stdout "top_builddest:=~a\n" top-builddest)
417           (stdout "top_builddir:=~a\n" top-builddir)
418           (stdout "abs_top_builddir:=~a\n" abs-top-builddir)
419           (stdout "abs_top_srcdir:=~a\n" abs-top-srcdir)
420
421           (stdout "srcdir:=~a\n" srcdir)
422
423           (stdout "prefix:=~a\n" (gulp-pipe (string-append "echo " prefix)))
424           (stdout "datadir:=~a\n" datadir)
425           (stdout "docdir:=~a\n" docdir)
426
427           (stdout "bindir:=~a\n" bindir)
428           (stdout "guile_site_ccache_dir:=~a\n" guile-site-ccache-dir)
429           (stdout "guile_site_dir:=~a\n" guile-site-dir)
430           (stdout "infodir:=~a\n" infodir)
431           (stdout "libdir:=~a\n" libdir)
432           (stdout "mandir:=~a\n" mandir)
433           (stdout "moduledir:=~a\n" moduledir)
434           (stdout "sysconfdir:=~a\n" sysconfdir)
435
436           (for-each (lambda (o)
437                       (stdout "~a:=~a\n" (variable-name o) (or (dependency-file-name o) "")))
438                     deps)
439           (stdout "GUILE_EFFECTIVE_VERSION:=~a\n" (effective-version))
440
441           (when disable-silent-rules?
442             (stdout "BUILD_DEBUG:=1\n"))
443
444           (for-each (lambda (o)
445                       (stdout "~a:=~a\n" o (or (getenv o) "")))
446                     '(
447                       "CFLAGS"
448                       "CC32_CFLAGS"
449                       "HEX2FLAGS"
450                       "M1FLAGS"
451                       "CC32_CFLAGS"
452                       "MES_CFLAGS"
453                       ))))
454
455       (let ((pairs `(("@srcdir@" . ,abs-top-srcdir)
456                      ("@abs_top_srcdir@" . ,abs-top-srcdir)
457                      ("@abs_top_builddir@" . ,abs-top-builddir)
458                      ("@top_builddir@" . ,top-builddir)
459                      ("@top_builddest@" . ,top-builddest)
460                      ("@BASH@" . ,(file-name "bash" deps))
461                      ("@GUILE@" . ,(file-name "guile" deps))
462                      ("@guile_site_dir@" . ,guile-site-dir)
463                      ("@guile_site_ccache_dir@" . ,guile-site-ccache-dir)
464                      ("@VERSION@" . ,VERSION)
465                      ("mes/module/" . ,(string-append moduledir "/")))))
466         (for-each (lambda (o)
467                     (let* ((src (srcdir-relative o))
468                            (target (string-drop-right o 3))
469                            (target (if (not (string-prefix? "build-aux/" target)) target
470                                        (string-drop target (string-length "build-aux/")))))
471                       (substitute src pairs #:target target)))
472                   '(
473                     "build-aux/pre-inst-env.in"
474                     "mes/module/mes/boot-0.scm.in"
475                     "scripts/mescc.in"
476                     ))
477         (when (not (equal? srcdir "."))
478           (substitute (string-append srcdir "/build-aux/GNUmakefile.in")
479                       pairs
480                       #:target "GNUmakefile")
481           (system (string-append "cd mes/module/mes && ln -sf " abs-top-srcdir "/mes/module/mes/*.mes ."))))
482       (chmod "pre-inst-env" #o755)
483       (chmod "scripts/mescc" #o755)
484       (let ((make (and=> (file-name "make" deps) basename)))
485         (format (current-output-port)
486                 "\nRun:
487   ~a            to build mes
488   ~a help       for help on other targets\n"
489                 (or make "./build.sh")
490                 (or make "./build.sh"))))))