;;; -*- Mode:LISP; Package:(RELEASE GLOBAL); Readtable:CL; Base:10 -*-
;;;
;;; Utility for making source release
;;;
;;; -dg 12/4/86

(defconst *default-lambda-ucode-types-to-release*
	  '("LMC" "LMC-LOCS" "LMC-DCL" "LMC-SYM" "LMC-TBL"))

(defconst *default-explorer-ucode-types-to-release*
	  '("EMC" "EMC-LOCS" "EMC-DCL" "EMC-SYM" "EMC-TBL"))

(defconst *default-lambda-system-filters*
	  '(cadr-micro-assembler
	     cadr-debugger
	     cadr
	     lambda-ucode
	     magtape
	     ))

(defconst *default-lambda-pathname-filters*
	  `("sys:cold;* * *"			; sensitive
	    "sys:lambda-diag;* * *"		; sensitive
	    "sys:fonts;equivalence * *"		; random MIT
            "sys:fonts;color* * *"		; old CADR
	    "sys:micro-compiler;* * *"		; option
	    "sys:sys2;gc * *"			; mystique
	    "sys:sys;config* * *"		; GJC
	    "sys:ulambda;* * *"			; sensitive
	    "sys:lmi-site;* * *"		; unnecessary
	    "sys:file2;* * *"			; RMS filesystem
	    "sys:gateway;SYSTEM-BOOKREPORTS * *"	;LMI sensitive
	    ))

(defconst *default-lambda-additional-pathnames*
	  '("SYS: DEMO; TVBGAR QFASL >"
	    "SYS: DEMO; WORMCH QFASL >"
	    "SYS: GATEWAY; ZINFONT QFASL >"
	    "SYS: IO; CRDTBL LISP >" ;; is actually a source, processed by readtable compiler.
	    "SYS: IO; RDTBL LISP >"
	    "SYS: ZMAIL; LEX733 LISP >" ;; is actually a source, processed by readtable compiler.
	    "SYS: CUSTOMER-SITE; SITE LISP >"
	    "SYS: CUSTOMER-SITE; SITE QFASL >"
	    "SYS: CUSTOMER-SITE; HOSTS TEXT >"
	    "SYS: CUSTOMER-SITE; HSTTBL LISP >"
	    "SYS: CUSTOMER-SITE; HSTTBL QFASL >"
	    "SYS: CUSTOMER-SITE; LMLOCS LISP >"
	    "SYS: CUSTOMER-SITE; LMLOCS QFASL >"
	    "SYS: CUSTOMER-SITE; SYS TRANSLATIONS >"
	    "SYS: RELEASE; BETA-I-S3P29 PDATA >"
	    ))
	  
(defconst *default-lambda-additional-functions*
	  `(
	    mark-font-files
	    mark-gateway-data-files
	    mark-directory-copyright-files
	    check-for-copyrights
	    mark-examples
	     ))

(defconst *default-report-pathname* "dj:release.reports;")

(defun get-system-patch-filters (&rest systems)
  (let (collection
	(logical-translator (fs:parse-pathname "sys:")))
    (dolist (system (or systems si:*systems-list*) collection)
      (let ((object (si:find-system-named system t t)))
	(when (and object (si:system-patchable-p object))
	  (push (send logical-translator :back-translated-pathname
		      (si:patch-system-pathname (si:system-name object) :patch-file '* '* :wild))
		collection))))))

(defun filter-file (pathname &optional filter-list)
  (block filter-file
    (dolist (filter filter-list)
      (when (send filter :pathname-match pathname)
	(return-from filter-file t)))))

(defsubst mark-released (pathname)
  (putprop pathname t :source-file-released))

(defsubst clear-released (pathname)
  (putprop pathname nil :source-file-released))

(defsubst mark-restrained (pathname)
  (putprop pathname t :source-file-restrained))

(defsubst clear-restrained (pathname)
  (putprop pathname nil :source-file-restrained))

(defsubst mark-stray (pathname)
  (putprop pathname t :source-file-stray))

(defsubst clear-stray (pathname)
  (putprop pathname nil :source-file-stray))

(defun clear-source-pathname-flags (&optional (hash-table fs:*pathname-hash-table*))
  (maphash #'(lambda (ignore thing)
	       (clear-released thing)
	       (clear-restrained thing))
	   hash-table))

(defun clear-stray-pathname-flags  (&optional (hash-table fs:*pathname-hash-table*))
  (maphash #'(lambda (ignore pn)
	       (clear-stray pn))
	   hash-table))

(defun sorted-list-marked-pathnames (prop &optional (hash-table fs:*pathname-hash-table*))
  (let (return)
    (maphash #'(lambda (ignore pn)
		 (when (get pn prop)
		   (push pn return)))
	     hash-table)
    (sort return 'release-order-lessp)))

(defconst *gateway-root-directory-name* "gateway")

(defun release-order-lessp (pn1 pn2)
  (let ((dir1 (let ((temp (send pn1 :directory)))
		(if (consp temp) (car temp) temp)))
	(dir2 (let ((temp (send pn2 :directory)))
		(if (consp temp) (car temp) temp))))
    (cond ((string-equal *gateway-root-directory-name* dir1) nil)
	  ((string-equal *gateway-root-directory-name* dir2) t)
	  (t (string-lessp pn1 pn2)))))
	  
(defun list-stray-files (&optional (hash-table fs:*pathname-hash-table*))
  (sorted-list-marked-pathnames :source-file-stray hash-table))

(defun list-restrained-files (&optional (hash-table fs:*pathname-hash-table*))
  (sorted-list-marked-pathnames :source-file-restrained hash-table))

(defun list-release-files (&optional (hash-table fs:*pathname-hash-table*))
  (sorted-list-marked-pathnames :source-file-released hash-table))

(defun mark-source-files-from-environment (&key (systems si:*systems-list*)
					  ucode-types
					  pathname-filters
					  system-filters)
  (let (relevant-systems
	systems-to-restrain
	pathnames-to-restrain
	restrained-pathnames)
    ;;; setup system filters
    (dolist (system system-filters)
      (pushnew (si:find-system-named system t t) systems-to-restrain))
    ;;; setup pathname filters
    (dolist (desc pathname-filters)
      (pushnew (fs:parse-pathname desc) pathnames-to-restrain))
    ;;; determine relevant systems
    (dolist (system systems)
      (when (and (typep system 'si:system)
		 (not (memq system systems-to-restrain))
		 (symbol-package (si:system-symbolic-name system)))
	(push system relevant-systems)))
    ;;; Get files from systems
    (dolist (system relevant-systems)
      (do* ((files (si:system-source-files system) (cdr files))
	    (number-of-files (length files))
	    (file (car files) (car files))
	    (number-restrained 0)
	    (system-restrained-p (memq system systems-to-restrain)))
	   ((null file)
	    (format t "~&~10T~A~40T~D files.~53T(~D restrained | ~D released)"
		    (si:system-name system)
		    number-of-files
		    number-restrained
		    (- number-of-files number-restrained)))
	(if (not (or system-restrained-p
		     (block filter-by-pathname
		       (dolist (pf pathnames-to-restrain)
			 (when (send pf :pathname-match file)
			   (return-from filter-by-pathname t))))))
	    (mark-released file)
	  (mark-restrained file)
	  (incf number-restrained))))
    ;;; add micrcode files
    (dolist (type ucode-types)
      (mark-released (fs:parse-pathname
		       (format nil "SYS:UBIN;ULAMBDA ~A ~D" type
			       %microcode-version-number))))
    ;;; add patch directories
    (mark-patch-directories relevant-systems)
    restrained-pathnames))

(defun check-for-stray-files (filters)
  (format t "~&Clearing stray file flags from all pathnames ...")
  (clear-stray-pathname-flags)
  (format t "~&~10TChecking ~d fasloaded files for strays: "
	  (length si:fasloaded-file-truenames))
  (do* ((pos (cursorpos))
	(flist si:fasloaded-file-truenames (cdr flist))
	(count (length flist) (sub1 count))
	(stray-count 0)
	(pathname (fs:parse-pathname (car flist))
		  (fs:parse-pathname (car flist)))
	(translator (fs:parse-pathname "sys:"))
	(translated-pathname
	  (when flist
	    (send translator :back-translated-pathname
		  (fs:parse-pathname (car flist))))
	  (when flist
	    (send translator :back-translated-pathname
		  (fs:parse-pathname (car flist)))))
	(logical-pathname
	  (when translated-pathname
	    (send translated-pathname :new-pathname
		  :canonical-type :lisp :version :newest))
	  (when translated-pathname
	    (send translated-pathname :new-pathname
		  :canonical-type :lisp :version :newest)))
	non-logical-pathnames)
       ((null flist)
	(cursorpos (car pos) (cdr pos))
	(cursorpos 'l)
	(format t "~D stray files" stray-count)
	(when non-logical-pathnames
	  (dolist (pn non-logical-pathnames)
	    (format t "~&Warning: The pathname \"~A\" is not logical!" pn)))
	stray-count)
    (cursorpos (car pos) (cdr pos))
    (cursorpos 'l)
    (princ count)
    (if (null translated-pathname)
	(push pathname non-logical-pathnames)
      (unless (or (get logical-pathname :source-file-released)
		  (get logical-pathname :source-file-restrained)
		  (let ((np (send logical-pathname :new-canonical-type :qfasl)))
		    (or (get np :source-file-released)
			(get np :source-file-restrained)))
		  (filter-file logical-pathname filters))
	(mark-stray logical-pathname)
	(incf stray-count)))))

(defun mark-patch-directories (systems &aux (translator (fs:parse-pathname "sys:")))
  (dolist (slist si:patch-systems-list)
    (when (and (memq (si:find-system-named (car slist)) systems)
	       (not (mem 'equal slist si:frozen-patch-systems-list)))
      (mark-released (send translator :back-translated-pathname
			   (si:patch-system-pathname (car slist) :system-directory)))
      (mark-released (send translator :back-translated-pathname
			   (car (si:patch-directory-loaded-id slist)))))))

(defun assess-release-sources (systems
			       ucode-types
			       pathname-filters
			       system-filters
			       additional-pathnames
			       additional-functions)
  (format t "~&Clearing release status flags on all pathnames ...")
  (clear-source-pathname-flags)
  (format t "~&Checking loaded systems ...")
  (mark-source-files-from-environment
    :systems systems
    :ucode-types ucode-types
    :pathname-filters pathname-filters
    :system-filters system-filters)
  (format t "~&Adding additional pathnames...")
  (dolist (pn additional-pathnames)
    (let ((pathname (fs:parse-pathname pn)))
      (when (probef pathname)
	(mark-released pathname))))
  (format t "~&Adding additional functions...")
  (dolist (fn additional-functions)
    (dolist (file (funcall fn))
      (mark-released file)))
  (format t "~&Checking for stray files...")
  (check-for-stray-files (get-system-patch-filters))
  nil)

(defun assess-lambda-release-sources ()
  (assess-release-sources
    si:*systems-list*
    *default-lambda-ucode-types-to-release*
    *default-lambda-pathname-filters*
    *default-lambda-system-filters*
    *default-lambda-additional-pathnames*
    *default-lambda-additional-functions*))

(defun make-release-report (pathname-list &key (pathname *default-report-pathname*))
  (let* ((release-name (string-subst-char
			 #\- #\space
			 (prompt-and-read :string "~&Name for this release >> ")
			 nil))
	 (real-path (send (fs:parse-pathname pathname) :new-pathname
			  :name (string-append release-name "-source-release")
			  :canonical-type :text
			  :version :newest)))
    (with-open-file (stream real-path :direction :output :characters t)
      (format stream ";;; Release ~S Source File Report~@
                      ;;; Made by ~S~@
                      ;;; ~\\date\\~@
                      ;;; ~D files~3%"
	      release-name
	      si:user-id
	      (time:get-universal-time)
	      (length pathname-list))
      (dolist (pn pathname-list)
	(format stream "~A~%" pn)))))

(defun dump-source-list (pathname-list &key (pathname *default-report-pathname*)) 
  (let* ((release-name (string-subst-char
			 #\- #\space
			 (prompt-and-read :string "~&Name for this release >> ")
			 nil))
	 (real-path (send (fs:parse-pathname pathname) :new-pathname
			  :name (string-append release-name "-source-release")
			  :canonical-type :qfasl
			  :version :newest))
	 (*saved-file-list* pathname-list)) 
    (declare (special *saved-file-list*))
    (compiler:fasd-symbol-value real-path '*saved-file-list*)))

(defun restore-source-list (&optional (pathname *default-report-pathname*))
  (let* ((release-name (string-subst-char
			 #\- #\space
			 (prompt-and-read :string "~&Release name >> ")
			 nil))
	 (real-path (send (fs:parse-pathname pathname) :new-pathname
			  :name (string-append release-name "-source-release")
			  :canonical-type :qfasl
			  :version :newest))
	 *saved-file-list*)
    (declare (special *saved-file-list*))
    (load real-path)
    *saved-file-list*))

(defun check-for-copyrights (&optional (pathname-list (list-release-files)))
  (let (directory-pathnames
	no-file-list)
    (format t "~&Creating directory copyright file list...")
    (setq directory-pathnames (get-directory-copyright-files pathname-list))
    (format t " ~D files.~%Probing for files..."
	    (length directory-pathnames))
    (dolist (pn directory-pathnames no-file-list)
      (format t "~&~5T~A - " pn)
      (if (probef pn)
	  (format t "ok.~%")
	(push pn no-file-list)
	(format t "*** missing ***~%")))
    (format t "~D files missing.~%" (length no-file-list))
    (when no-file-list
      (copy-over-copyright-files
	no-file-list (set-difference directory-pathnames no-file-list) ))
    ;;; eventually this should check for COPYRIGHT lines and attribute lines
    ;;; in LISP files
    ))

(defun get-directory-copyright-files (pathname-list)
  (let (directory-cache
	collection)
    (dolist (pn pathname-list collection)
      (unless (member (send pn :directory) directory-cache :test 'equal)
	(push (send pn :directory) directory-cache)
	(let ((cpn (send pn :new-pathname
			 :device :unspecific
			 :name "COPYRIGHT"
			 :canonical-type :text
			 :version :newest)))
	  (pushnew cpn collection))))))

(defun mark-directory-copyright-files (&key
				       (hash-table fs:*pathname-hash-table*)
				       &aux
				       (count 0))
  (format t "~&~10TAssessing Copyright Files per directory  ... ")
  (maphash 'mark-directory-copyright-file-internal
	   hash-table (locf count) (list nil))
  (format t "~D copyright files released." count))

(defun mark-directory-copyright-file-internal (ignore pathname count-locative dir-cache)
  (unless (or (not (get pathname :source-file-released))
	      (member (send pathname :directory) dir-cache :test 'equal))
    (nconc dir-cache (ncons (send pathname :directory)))
    (let ((cpath (send pathname :new-pathname
		       :device :unspecfic
		       :name "COPYRIGHT"
		       :canonical-type :text
		       :version :newest)))
      (unless (get cpath :source-file-released)
	(incf (car count-locative))
	(mark-released cpath)))))

(defun copy-over-copyright-files (pathnames-not-present pathnames-present)
  (multiple-value-bind (copy-source overwrite-all)
      (do* ((l pathnames-present (cdr l))
	    (file (car l) (car l))
	    selected-pathname)
	   ((or (null file) selected-pathname)
	    (when selected-pathname
	      (values selected-pathname nil)))
;     (y-or-n-p "~&Overwrite all directories files with selected file? ")))) shouldn't do this really.
	(send *standard-output* :clear-screen)
	(format t "Examining copright file: ~A~2%" file)
	(viewf file)
	(when (y-or-n-p "~&~2%Use this file for directories missing a copyright file? ")
	  (setq selected-pathname file)))
    (if (not (or pathnames-not-present overwrite-all))
	(format t "~&No copyright files to be overwritten.")
      (format t "~&Using \"~A\" to overwrite ~:[NON-EXISTING~;ALL~] files..."
	      copy-source overwrite-all)
      (dolist (target (if overwrite-all
			  (delq copy-source (union pathnames-not-present pathnames-present))
			pathnames-not-present))
	(format t "~&Copying \"~A\" to \"~A\"" copy-source target)
	(fs:copy-file copy-source target)))))

;(tframe:define-command MAKE-LMI-SOURCE-RELEASE distribution
;  "Make a release source tape from the loaded environment."
;  :left (let* ((file-list (list-files-for-release))
;	       (length (length file-list))
;	       (start (time:get-universal-time)))
;	  (format t "~&~2%Dumping ~D files ... " length)
;	  (do* ((list file-list (cdr list))
;		(pathname (car list) (car list))
;		(to-go length (sub1 to-go)))
;	       ((null list))
;	    (tframe:with-status ("Writing source \"~A\"... [~D files to go]" pathname to-go)
;	      (send tape:*selected-format* :write-file
;		    tape:*selected-device*
;		    pathname
;		    :silent t)))
;	  (format t "done.  Took ~\\time-interval\\" (- (time:get-universal-time) start))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Special functions to mark additional files
;;;
;;;

(defun mark-gateway-data-files ()
  (format t "~&~10TAssessing Gateway files ... ")
  (si:find-system-named 'gateway)
  (let ((gateway-data-files (cdr (fs:directory-list  "gateway:data;* * >")))
	(byte-count 0)
	(file-count 0)
	(translator (fs:parse-pathname "sys:")))
    (dolist (file gateway-data-files)
      (if (not (zerop (or (get file :length-in-bytes)
			  (get file :length)
			  (get file :length-in-blocks)
			  (ferror nil "Bad file plist from FS:DIRECTORY-LIST: ~a" file))))
	  (progn (mark-released (send translator :back-translated-pathname (car file)))
		 (incf file-count)
		 (incf byte-count (* (or (get file :length-in-bytes)
					 (* (or (get file :length-in-blocks) 1) si:page-size 4))
				     (/ (get file :byte-size) 8))))
	(format t "~&File \"~A\" has zero length, looking for a good version ..."
		(car file))
	(do* ((list (butlast (cdr (fs:directory-list
				    (send (car file) :new-version :wild))))
		    (cdr list))
	      (file (car list) (car list))
	      found-one)
	     ((or (null file) found-one)
	      (unless found-one
		(when (and (not found-one)
			   (yes-or-no-p "~&Sorry no more files, should I abort?"))
		  (signal 'sys:abort :format-string "Abort from lossage!!!"))))
	  (when (not (zerop (or (get file :length-in-bytes)
				(get file :length)
				(get file :length-in-blocks)
				(ferror nil "Bad file plist from FS:DIRECTORY-LIST: ~a" file))))
	    (format t "found one.")
	    (incf file-count)
	    (setq found-one
		  (mark-released (send translator :back-translated-pathname
				       (car file))))))))
    (format t "~D files (~:D bytes)."
	    file-count byte-count)))

(defun mark-font-files ()
  (format t "~&~10TAssessing Fonts ... ")
  (let ((translator (fs:parse-pathname "sys:"))
	(files (cdr (fs:directory-list "sys:fonts;*.qfasl#>"))))
    (dolist (file files (format t "Releasing ~D font files." (length files)))
      (mark-released
	(send (send translator :back-translated-pathname (car file))
	      :new-version :newest)))))

(defun mark-examples ()
  (format t "~&~10TAssessing Examples ... ")
  (let ((translator (fs:parse-pathname "sys:"))
	(files (cdr (fs:directory-list "sys:examples;* * >"))))
    (dolist (file files (format t "Releaseing ~D examples files." (length files)))
      (mark-released
	(send (send translator :back-translated-pathname (car file))
	      :new-version :newest)))))

(defun print-non-logical-pathnames ()
  (mapatoms-all #'find-non-logical-source-pathname))

(defun find-non-logical-source-pathname (symbol &key
					 (function #'print-non-logical-source)
					 &aux sfn)
  (typecase (setq sfn (get symbol :source-file-name))
    (cons (find-non-logical-source-pathname (car sfn) :function function)
	  (find-non-logical-source-pathname (cdr sfn) :function function))
    (fs:logical-pathname)
    (pathname (funcall function symbol sfn))))
    
(defun print-non-logical-source (symbol pathname)
  (format t "~&~A:~A ~30T- ~A"
	  (symbol-package symbol)
	  symbol
	  pathname))
