devops: support Debugging Builds with a devops:make procedure.
authorMatt Birkholz <matt@birchwood-abbey.net>
Mon, 19 Feb 2018 02:02:42 +0000 (19:02 -0700)
committerMatt Birkholz <matt@birchwood-abbey.net>
Mon, 19 Feb 2018 02:02:42 +0000 (19:02 -0700)
src/devops/build.texi
src/devops/devops.scm

index 655a92f4f4f39d2a80df2b9770a2242d30e4dca2..c2b2006407bee5a3466eea99ce5957b4ea0223c2 100644 (file)
@@ -27,6 +27,7 @@ they were created.  No dependency checking is currently in place.
 @menu
 * Project Repository::
 * Project Configuration::
+* Debugging Builds::
 * Release Process::
 * Build Process::
 * Lint Detection::
@@ -92,6 +93,71 @@ the host's operating system.  If it starts with @code{"Ubuntu"} and
 mentions a recent Ubuntu version, the host will build Debian packages.
 @end deffn
 
+@node Debugging Builds
+@section Debugging Builds
+
+A debugging build runs in the working directory of a developer's git
+repository (or shadow thereof) and provides special options to core
+and plugin @code{./configure} scripts (e.g.
+@code{"--enable-debugging"}).  The build begins by loading a
+@file{devops-config.scm} file into the @code{(devops)} package where
+it can adjust many aspects of the build.  The special options for the
+build are specified by applying @code{make-configuration} to a list of
+strings.
+
+@deffn Procedure make-configuration . options
+@var{Options} should be a list of strings to be passed as arguments to
+the @code{./configure} scripts of the core and plugins.
+@end deffn
+
+An example @file{devops-config.scm} file:
+
+@smallexample
+;; Shared configuration.
+(load (merge-pathnames "devops/config.scm"
+                       (current-load-pathname)))
+;; Build-specific configuration.
+(make-configuration "--enable-debugging")
+@end smallexample
+
+If the working directory contains @file{src}, @file{doc} and
+@file{tests} subdirectories it is assumed to contain a core system.
+In this case a @code{--prefix} option is added to the core build
+configuration so that the core is installed in the subdirectories
+@file{bin}, @file{lib} and @file{share}.  The test core is built and
+installed first, and is used as the host Scheme for plugin builds and
+installs.  There is no inter-plugin dependency analysis currently, so
+plugins are built and installed serially in the order they were
+created.
+
+To accommodate multiple debugging builds, a ``shadow'' of the working
+directory can be created.  A shadow directory contains symbolic links
+to the files in a source directory and shadows of its subdirectories,
+much like the shadow directories created by the X11 build utility
+@code{lndir}.  If the build starts in a subdirectory of the working
+directory, it will assume the build is intended to run in shadows of
+the working directories.  If it does not find a
+@file{devops-config.scm} file in this subdirectory, it will attempt to
+load one from the working directory.
+
+@deffn Procedure devops:make target
+@var{Target} is optional and defaults to @code{native} which specifies
+a core with the same Scheme architecture as the host.  It can also be
+the symbol @code{svm} which specifies a core cross-compiled to a
+Scheme virtual machine with the same word size as the host.  Plugins,
+unlike the core, are always native to their host Scheme compiler
+@verb{|${MIT_SCHEME_EXE=mit-scheme}|} (as well as their C compiler
+@verb{|${CC=gcc}|}, dynamic linker, etc.).
+
+This procedure is continuable.  When restarted it first checks any
+shadow directories for missing or incorrect links, then re-makes and
+re-installs the core (if any) and each plugin in order.  Note that
+this does not take into account syntactic dependencies between
+plugins.  If a plugin exports syntax and changes how that syntax
+expands, any plugins using the syntax will need to be cleaned (at
+least partially) and re-built.
+@end deffn
+
 @node Release Process
 @section Release Process
 
index cd397bc3692e262116002f620b901d0da1c639b2..b8737ad7299abae627d3190915e763f051538256 100644 (file)
@@ -30,6 +30,7 @@ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
     (cond ((equal? arg1 "status") (apply devops:status (cdr cmdl)))
          ((equal? arg1 "release") (apply devops:release (cdr cmdl)))
          ((equal? arg1 "build") (apply devops:build (cdr cmdl)))
+         ((equal? arg1 "make") (apply devops:make (cdr cmdl)))
          (else (error "Unknown developer operation:" cmdl)))))
 
 (define (devops:status #!optional name)
@@ -735,6 +736,250 @@ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
        (deregister-timer-event record)
        value))))
 \f
+;;;; Make
+
+(define (devops:make target)
+  (if (not (member target '("native" "svm" "C" "C-old" "C2native" "C2svm")))
+      (error "unknown build target:" target))
+  (load-make-config)
+  (%exit
+   (call-with-current-continuation     ;throw here to unwind all
+    (lambda (abort-job)
+      (bind-condition-handler (list condition-type:error)
+         (named-lambda (job-error-handler condition)
+           (fresh-line)
+           (log "# "(emacs-friendly-timestamp)"\n")
+           (write-condition-report condition (current-output-port))
+           (newline)
+           (flush-output)
+           (abort-job 2))
+       (lambda ()
+         (with-^G-interrupt-handler
+          (named-lambda (job-^G-interrupt-handler)
+            (abort-job 3))
+          (lambda ()
+            (devops:make* target)
+            0))))))))
+
+(define (devops:make* target)
+  (let* ((prefix (or (get-environment-variable "PWD")
+                    (error "PWD not set")))
+        (host-exe (or (get-environment-variable "MIT_SCHEME_EXE")
+                      "mit-scheme"))
+        (target-exe 'unset))
+    (if (file-directory? ".git")
+       (make-install-doc prefix)
+       (if (file-directory? "../.git")
+           (begin
+             (lndir "../doc")
+             (make-install-doc prefix)
+             (for-each lndir '("../src" "../tests")))
+           (error "not a git working directory:"
+                  (working-directory-pathname))))
+
+    ;;(set-environment-variable! "LD_LIBRARY_PATH" "/usr/local/lib")
+    (set-subprocess-environment-variable! "FAST" "please")
+    (delete-subprocess-environment-variable! "DISPLAY")
+
+    (log "# Host: "host-exe"\n")
+    (log "# Target: "target"\n")
+    (log "# Config: "make-config"\n")
+
+    (cond
+     ((equal? target "native")
+      (if (not (file-exists? "src/configure"))
+         (trun "cd src/; ./Setup.sh"))
+      (if (not (file-exists? "src/Makefile"))
+         (trun "cd src/; ./configure --prefix="prefix" "make-config))
+      (trun "cd src/; make tags")
+      (trun "cd src/; make all")
+      (trun "cd src/; make check")
+      (trun "cd src/; umask 022; make install")
+      (set! target-exe (project-name)))
+
+     ((equal? target "svm")
+      (if (not (file-exists? "src/configure"))
+         (trun "cd src/; ./Setup.sh"))
+      (if (not (file-exists? "src/Makefile"))
+         (trun "cd src/; ./configure" " --prefix="prefix
+               " --enable-cross-compiling --enable-native-code=svm "
+               make-config))
+      (trun "cd src/; make tags")
+      (trun "cd src/; make all")
+      (trun "cd src/; make check")
+      (trun "cd src/; umask 022; make install")
+      (set! target-exe (string (project-name)"-svm1")))
+
+     ((equal? target "C")
+      (if (not (file-exists? "src/configure"))
+         (trun "cd src/; ./Setup.sh"))
+      (if (not (file-exists? "src/Makefile"))
+         (trun "cd src/; ./configure --prefix="prefix
+               " --enable-native-code=C "make-config))
+      (trun "cd src/; make tags")
+      (trun "cd src/; make all-native")
+      (trun "cd src/; make check")
+      (trun "cd src/; umask 022; make install")
+      (set! target-exe (string (project-name)"-c")))
+
+     ((equal? target "C-old")
+      (trun "cd src/; make tags")
+      (trun "cd src/; ./etc/make-liarc-dist.sh --prefix="prefix" "make-config)
+      (trun "cd src/; ./etc/make-liarc.sh --prefix="prefix" "make-config)
+      (trun "cd src/; make check")
+      (trun "cd src/; umask 022; make install")
+      (set! target-exe (string (project-name)"-c")))
+
+     ((equal? target "C2native")
+      (trun "cd src/; make tags")
+      (trun "cd src/; ./etc/make-native.sh --prefix="prefix" "make-config)
+      (trun "cd src/; make check")
+      (trun "cd src/; umask 022; make install")
+      (set! target-exe (project-name)))
+
+     ((equal? target "C2svm")
+      (trun "cd src/; make tags")
+      (trun "cd src/; ./etc/make-native.sh --prefix="prefix" "make-config)
+      (trun "cd src/; make check")
+      (trun "cd src/; umask 022; make install")
+      (set! target-exe (string (project-name)"-svm1")))
+
+     (else
+      (error "Unexpected target:" target)))
+
+    (let ((plugin-errors
+          (with-subprocess-environment-variable
+           "MIT_SCHEME_EXE" (string prefix"/bin/"target-exe)
+           (lambda ()
+             (reduce + 0
+                     (map (lambda (plugin) (make-install-plugin plugin prefix))
+                          plugin-list))))))
+      (if (not (zero? plugin-errors))
+         (error "plugins failed to build:" plugin-errors)))))
+
+(define (load-make-config)
+  (cond ((file-exists? "devops-config.scm")
+        (load "devops-config.scm"))
+       ((file-exists? "../devops-config.scm")
+        (load "../devops-config.scm"))
+       ((let* ((git-root (find-git-root))
+               (file (string git-root"/devops-config.scm")))
+          (and (file-exists? file)
+               (begin (load file)
+                      #t))))
+       (else
+        (error "no devops test configuration found"))))
+
+(define make-config "")
+(define (make-configuration . args)
+  (if (not (null? args))
+      (begin
+       (if (not (list-of-type? args string?))
+           (error:wrong-type-argument args "list of strings"
+                                      'make-configuration))
+       (set! make-config (decorated-string-append "" " " "" args))))
+  make-config)
+
+(define (lndir dest)
+  (let ((dirname (dirname dest)))
+    (let loop ((name (filename dest))
+              (dstdir dirname)
+              (srcdir "")
+              (reldir (drop-slash dirname)))
+      (let ((dst (string dstdir name))
+           (src (string srcdir name)))
+       (let ((dst-atts (file-attributes-direct dst))
+             (src-atts (file-attributes-direct src)))
+         (if (eq? #t (file-attributes/type dst-atts))
+             (if (not (or (eq? #f src-atts)
+                          (eq? #t (file-attributes/type src-atts))))
+                 (warn "not a directory:" src)
+                 (let ((dstdir* (string dst"/"))
+                       (srcdir* (string src "/"))
+                       (reldir* (string "../"reldir"/"name)))
+                   (if (eq? #f src-atts)
+                       (make-directory src))
+                   (for-each
+                     (lambda (name)
+                       (cond ((string=? "." name))
+                             ((string=? ".." name))
+                             ((string-suffix? "~" name))
+                             (else (loop name dstdir* srcdir* reldir*))))
+                     (directory-file-names dst))))
+             (let ((reldirname (string reldir"/"name)))
+               (if (eq? #f src-atts)
+                   (soft-link-file reldirname src)
+                   (let ((src-type (file-attributes/type src-atts)))
+                     (cond ((not (string? src-type))
+                            (warn "not a symlink:" src))
+                           ((not (string=? src-type reldirname))
+                            (warn "bogus symlink:" src reldirname))))))))))))
+
+(define (make-install-doc prefix)
+  (if (not (file-exists? "doc/configure"))
+      (trun "cd doc/; autoconf"))
+  (if (not (file-exists? "doc/Makefile"))
+      (trun "cd doc/; ./configure --prefix="prefix))
+  (trun "cd doc/; make all")
+  (trun "cd doc/; umask 022; make install"))
+
+(define (make-install-plugin plugin prefix)
+  (let ((result (ignore-errors
+                (lambda () (make-install-plugin* plugin prefix)))))
+    (if (condition? result)
+       (begin
+         (log "# "(plugin-name plugin)" failed to build:"
+              " "(condition/report-string result)"\n")
+         1)
+       0)))
+
+(define (make-install-plugin* plugin prefix)
+  (let ((dir (plugin-directory plugin)))
+    (if (not (file-directory? dir))
+       (error "plugin directory not found:" dir))
+    (log "# "dir":\n")
+    (if (not (file-exists? (string dir"/configure")))
+       (trun "cd "dir"/; ./autogen.sh"))
+    (if (not (file-exists? (string dir"/Makefile")))
+       (trun "cd "dir"/; ./configure --prefix="prefix))
+    (trun "cd "dir"/; make tags")
+    (trun "cd "dir"/; make all")
+    (trun "cd "dir"/; make check")
+    (trun "cd "dir"/; umask 022; make install")
+    (if (find (lambda (line) (string-prefix? "info_TEXINFOS" line))
+             (file-lines dir"/Makefile.am"))
+       (begin
+         (trun "cd "dir"/; umask 022; make install-html")
+         (trun "cd "dir"/; umask 022; make install-pdf")))))
+
+(define (trun . strings)
+  ;;(log-timestamp)
+  (log "# "(emacs-friendly-timestamp)"\n")
+  (apply run strings))
+
+(define (emacs-friendly-timestamp)
+  (let ((dt (local-decoded-time)))
+    (define (pad num) (string-pad-left (number->string num) 2 #\0))
+    (let ((year (number->string (decoded-time/year dt)))
+         (month (pad (decoded-time/month dt)))
+         (day (pad (decoded-time/day dt)))
+         (hour (pad (decoded-time/hour dt)))
+         (minute (pad (decoded-time/minute dt)))
+         (second (pad (decoded-time/second dt))))
+      (string year"-"month"-"day" "hour"."minute"."second))))
+
+(define (with-^G-interrupt-handler inside-handler thunk)
+  (let ((env (->environment '(runtime interrupt-handler)))
+       (outside-handler))
+    (dynamic-wind
+     (lambda ()
+       (set! outside-handler (environment-lookup env 'hook/^G-interrupt))
+       (environment-assign! env 'hook/^G-interrupt inside-handler))
+     thunk
+     (lambda ()
+       (set! inside-handler (environment-lookup env 'hook/^G-interrupt))
+       (environment-assign! env 'hook/^G-interrupt outside-handler)))))
+\f
 ;;;; Configuration
 
 (define (project-name #!optional name)
@@ -921,4 +1166,45 @@ Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
              (begin
                (write-string line out)
                (newline out)
-               (loop))))))))
\ No newline at end of file
+               (loop))))))))
+
+(define (find-git-root)
+  (let ((pwd (drop-slash (or (get-environment-variable "PWD")
+                            (error "PWD not set")))))
+    (let loop ((parent (drop-slash (dirname pwd))))
+      (if (string-null? parent)
+         (error "no git root found:" pwd)
+         (if (file-directory? (string parent"/.git"))
+             parent
+             (loop (drop-slash (dirname pwd))))))))
+
+(define (drop-slash string)
+  (let ((len-1 (fix:-1+ (string-length string))))
+    (if (and (fix:>= len-1 0)
+            (char=? #\/ (string-ref string len-1)))
+       (string-slice string 0 len-1)
+       string)))
+
+(define (filename filename)
+  (let ((i (string-find-previous-char filename #\/)))
+    (if (not i)
+       filename
+       (string-slice filename (fix:1+ i)))))
+
+(define (dirname filename)
+  (let ((i (string-find-previous-char filename #\/)))
+    (if (not i)
+       ""
+       (string-slice filename 0 (fix:1+ i)))))
+
+(define (basename filename)
+  (let* ((start (let ((i (string-find-previous-char filename #\/)))
+                 (if (not i)
+                     0
+                     (fix:1+ i))))
+        (end (string-find-next-char filename #\. start)))
+    (if (not end)
+       (if (fix:zero? start)
+           filename
+           (string-slice filename start))
+       (string-slice filename start end))))
\ No newline at end of file