
                       -----------------------
                            P A T C H E S
                       _PATCH BUILDER'S GUIDE_
                       -----------------------
                                                
Each patch is named <version-number>p<n>, where n begins at 1 for each 
version and is incremented by 1 for each sucessive patch.  This guide 
currently only supports files that are part of a collection in one of the 
standard plt releases; for patches to spidey, mysterx, drjava, 
or any other separate distribution, this guide does not apply.

This protocol only supports replacing entire files; it also requires
that each sucessive patch include all previous patches, ie applying
patch 3 also applies patches 1 and 2.

                      STEPS TO BUILD A PATCH                       

1) collect a list of the files (and their collections) that have
changed, both in this patch and in any previous patch.

2) create a new collection directory called <version-number>p<n>.

3) Place each file that will be updated in this patch and each file
that was updated in previous patches in a subcollection of the
<version-number>p<n> collection, based on its location in the original
collection hierarchy. For example, if plt/collects/mzlib/pconverr.ss
is to be patched, put the new version of the file in
plt/collects/<version-number>p<n>/mzlib/pconverr.ss.
  
4) create the file <version-number>p<n>/info.ss and put the code in the 
lower "CODE" section in it.  Fill in the ellipses at the beginning of the 
code, according to the comments.

5) cd top of the plt tree, start up mzscheme and evaluate
   the following two expressions:
   
  (require-library "pack.ss" "setup")

  (pack "<version-number>p<n>.plt"
        "<version-number>p<n>"
                '("collects/<version-number>p<n>") 
                '(("<version-number>p<n>") ...))

  Be sure to replace <verstion-number>p<n> with the actual version number 
  and patch number. Also, the ellipses must be filled in with the names of 
  the collections that have had files replaced. For example, if the mzlib 
  collection and the drscheme collections have new files, the last 
  argument must be:
  
  '(("<version-number>p<n>") ("mzlib") ("drscheme"))
                
  This step will place a .plt file in the current directory. This file is
  the patch file.
  
6) Once you have created the patch file, install a fresh installation of 
   the latest version of drscheme (be sure that it is compiled) and test 
   the patch, by starting up Setup PLT with the patch (either by dragging 
   the patch file onto Setup PLT or starting Setup PLT with the patch 
   filename as a command line argument)

7) hand off to paul (info)
7a) web page (help desk must know the location)
7b) test
7c) public announcement


                                FACTS

1) Patches are specific to particular versions of MzScheme. A check is
performed, based on the results of evaluating:

  (version)

in mzscheme to determine the patches applicability.

2) Since each patch subsumes all previous patches, it is an error for
a previous patch to be applied, once a later one has been applied. If this 
happens, an error is signaled and the patch will not be applied.


                      SHORTCOMINGS AND RATIONALS

Each of these restrictions can be lifted with additional work and
smarts put into the patching engine. We decided that none of them were
necessary and that a simpler patching engine is a better patching
engine.

1) The patching engine only supports files that are in
collections. This simplifies the implementation of the patching
engine; to lift the restriction, we can add a special file in the
<version-number>p<n> collection that describes a mapping between other
files in the collection and the plt tree.

2) Each patch will be larger than the one before and (for many users)
partially redundant. We do this because we do not believe that the
patches will become prohibitively large.

3) When signature files are changed, the user must re-run Setup PLT with 
the --clean flag to re-compile .zo files (assuming that they had ever 
compiled them before). This should be fixed (in such a way that users who 
did not compile .zo files remain .zo-less).

               WHAT HAPPENS WHEN THE PATCH IS INSTALLED
                          
When Setup PLT starts to work on a .plt file, it first undoes the packing 
step (step 6 above), which creates a new collection called 
<version-number>p<n>. Then, it runs the usual Setup PLT stuff for each 
collection that was specified in the fourth argument to pack, in order.
Running the usual Setup PLT for the <version-number>p<n> collection means 
that the (lambda (plt-home) ...) in the above code is applied. This 
procedure does performs three actions:

  1) ensures that the patch is applicible to the version it is being 
  installed to. This means that both the mzscheme version number matches 
  and that no newer patches have been installed.
  
  2) replaces the files (possibly creating the directories they belong in)
  in the collection hierarchy with the files from the patch.
  
  3) updates a file with the latest patch number, which also changes 
  drscheme's version number.
  
Then, it runs the usual Setup PLT stuff for the other collections. 
Unfortunately, the .zo files are not deleted, so the user must re-run
Setup PLT with the --clean flag to recompile (this step is required if
a signature is changed)


                                  CODE
                                                                  
(let ([version-string ...]    ;;; FILL IN version number as a scheme string
      [patch-number ...])     ;;; FILL IN patch number as a scheme number
  (lambda (request failure)
    (let ([patch-name (string-append 
                       version-string
                       "p"
                       (number->string patch-number))])
      (case request
        [(name) patch-name]
        [(install-collection)
         (lambda (plt-home)
           (let/ec skip-it
             (let* ([source-directory (collection-path patch-name)]
                    
                    [build-collection-directory
                     (lambda (top-collection . sub-collections)
                       (let loop ([dir (collection-path 
                                        top-collection)]
                                  [leftovers 
                                   sub-collections])
                         (unless (directory-exists? dir)
                           (printf "Creating directory ~a~n" dir)
                           (make-directory dir))
                         (cond
                           [(empty? leftovers) (void)]
                           [else (loop (build-path dir (car leftovers))
                                       (cdr leftovers))])))])
               
               (when (let ([already-installed-file 
                            (build-path
                             source-directory "patch-applied")]
                           [info-file
                            (build-path source-directory "info.ss")])
                       (and (file-exists? already-installed-file)
                            (> (file-or-directory-modify-seconds
                                already-installed-file)
                               (file-or-directory-modify-seconds
                                info-file))))
                 (printf "Patch already installed.~n")
                 (skip-it (void)))
               
               (unless (string=? (version) version-string)
                 (error 
                  (string->symbol patch-name)
                  "This patch may only be applied to version ~a of DrScheme.~
                  This is version ~a"
                  version-string
                  (version)))
               
               (build-collection-directory "drscheme" "tools" "patches")
               
               (let* ([patch-directory
                       (collection-path "drscheme" "tools" "patches")]
                      [latest-filename
                       (build-path patch-directory "latest")]
                      [latest-installed-patch-number
                       (if (file-exists? latest-filename)
                           (call-with-input-file latest-filename read)
                           0)])
                 
                 
                 ;; ensure patch number is greater than previous ones
                 (unless (> patch-number latest-installed-patch-number)
                   (printf
                    "Patch ~a has already been installed; cannot install ~
                                        patch ~a~n" 
                    latest-installed-patch-number
                    patch-number)
                                        (skip-it (void)))
                 
                 ;; for each directory in the patch collection,
                 ;;     copy the files over from those directories 
                 ;;     into the respective collections,
                 ;;     deleting the destinations (when they exist) first.
                 
                 (let loop ([depth 0]
                            [name-list null]
                            [src-directory source-directory]
                            [dst-directory 'collection-top])
                   
                   (let* ([spacer-string
                           (list->string
                            (build-list depth (lambda (arg) #\space)))]
                          [my-printf 
                           (lambda args
                             (printf "~a" spacer-string)
                             (apply printf args))])
                     
                     ; ensure that the directory exists
                     (unless (null? name-list)
                       (apply build-collection-directory name-list))
                     
                     (my-printf "In directory ~a:~n" dst-directory)
                     
                     (let* ([files-n-directories (directory-list src-directory)]
                            [path-adder
                             (lambda (name directory)
                               (if (eq? directory 'collection-top)
                                   (collection-path name)
                                   (build-path directory name)))]
                            [path-adding-filter 
                             (lambda (pred)
                               (filter
                                (lambda (name)
                                  (pred (path-adder name src-directory)))
                                files-n-directories))]
                            [path-adder-map
                             (lambda (name-list directory)
                               (map (lambda (name) 
                                      (path-adder name directory))
                                    name-list))]
                            
                            [files (if (null? name-list)
                                       null
                                       (path-adding-filter file-exists?))]
                            [src-file-paths 
                             (path-adder-map files src-directory)]
                            [dst-file-paths
                             (path-adder-map files dst-directory)]
                            
                            [directories
                             (path-adding-filter directory-exists?)]
                            [src-dir-paths
                             (path-adder-map
                              directories src-directory)]
                            [dst-dir-paths
                             (path-adder-map
                              directories dst-directory)])
                       
                       (my-printf "Deleting files.~n")
                       (for-each 
                        (lambda (dst-file-path)
                          (when (file-exists? dst-file-path)
                            (delete-file dst-file-path)))
                        dst-file-paths)
                       
                       (for-each
                        (lambda (src-file-path dst-file-path)
                          (my-printf "Copying: ~a~n" src-file-path)
                          (copy-file src-file-path dst-file-path))
                        src-file-paths
                        dst-file-paths)
                       
                       ; recur on directories
                       (for-each 
                        (lambda (dir-name src-dir-path dst-dir-path)
                          (loop (+ depth 1)
                                (append name-list (list dir-name))
                                src-dir-path
                                dst-dir-path))
                        directories
                        src-dir-paths
                        dst-dir-paths))))
                 
                 ;; signal completion of patch application
                 (call-with-output-file
                     (build-path source-directory "patch-applied")
                   (lambda (p) (write 'complete p))
                   'text 'truncate)
                 
                 ;; update the patch installation number
                 (call-with-output-file latest-filename 
                   (lambda (p) (write patch-number p)) 'text 'truncate)
                 
                 (printf "Patch successfully installed~n")))))]
        [else (failure)]))))
