;;; update-elc.el --- Bytecompile out-of-date dumped files, pre-dumping

;; Copyright (C) 1997, 2025 Free Software Foundation, Inc.
;; Copyright (C) 1996 Sun Microsystems, Inc.
;; Copyright (C) 2001, 2003, 2010 Ben Wing.

;; Author: Ben Wing <ben@xemacs.org>
;; Based On: Original by Steven L Baur <steve@xemacs.org>
;; Maintainer: XEmacs Development Team
;; Keywords: internal

;; This file is part of XEmacs.

;; XEmacs 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.

;; XEmacs 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 XEmacs.  If not, see <http://www.gnu.org/licenses/>.

;;; Synched up with: Not in FSF.

;;; Commentary:

;; Byte compile the .EL files necessary to dump out xemacs.
;; Also update the auto-autoloads.el files.

;; Use this file like this:

;; temacs -batch -l ../lisp/update-elc.el $lisp

;; where $lisp comes from the Makefile.  .elc files listed in $lisp will
;; cause the corresponding .el file to be compiled.  .el files listed in
;; $lisp will be ignored.

;; (the idea here is that you can bootstrap if your .ELC files
;; are missing or badly out-of-date)

;; See also update-elc-2.el

;;; Code:

;; Help debug problems.
(setq stack-trace-on-error t
      load-always-display-messages t)

(defvar processed nil)
(defvar update-elc-files-to-compile nil)
(defvar need-to-rebuild-autoloads nil)
(defvar need-to-rebuild-mule-autoloads nil)
(defvar need-to-rebuild-module-autoloads nil)
(defvar need-to-recompile-autoloads nil)
(defvar need-to-recompile-mule-autoloads nil)
(defvar need-to-recompile-module-autoloads nil)

(defvar exe-target nil)
(defvar dump-target nil)
(defvar dump-target-out-of-date-wrt-dump-files nil)
;(defvar dump-target-out-of-date-wrt-exe-target nil)

;(setq update-elc-files-to-compile
;      (delq nil
;	    (mapcar (function
;		     (lambda (x)
;		       (if (string-match "\.elc$" x)
;			   (let ((src (substring x 0 -1)))
;			     (if (file-newer-than-file-p src x)
;				 (progn
;				   (and (file-exists-p x)
;					(eq (file-writable-p x) nil)
;					(set-file-modes x (logior (file-modes x) 128)))
;				   src))))))
;		    ;; -batch gets filtered out.
;		    (nthcdr 3 command-line-args))))


(defvar build-directory (expand-file-name ".." invocation-directory))

(defvar source-lisp (file-name-directory (expand-file-name
 					  (car (nthcdr 2 command-line-args)))))

(defvar source-lisp-mule (expand-file-name "mule" source-lisp))
(defvar source-directory (expand-file-name ".." source-lisp))
(defvar source-modules (expand-file-name "../modules" (file-truename source-lisp)))
(defconst module-directory (expand-file-name "modules" build-directory))
(defvar aa-lisp (expand-file-name "auto-autoloads.el" source-lisp))
(defvar aac-lisp (expand-file-name "auto-autoloads.elc" source-lisp))
(defvar aa-lisp-mule (expand-file-name "auto-autoloads.el" source-lisp-mule))
(defvar aac-lisp-mule (expand-file-name "auto-autoloads.elc" source-lisp-mule))
(defvar aa-modules (expand-file-name "auto-autoloads.el" module-directory))
(defvar aac-modules (expand-file-name "auto-autoloads.elc" module-directory))

(setq load-path (list source-lisp))

(load "find-paths.el")
(load "packages.el")
(load "setup-paths.el")

;; Lisp files loaded in order to byte compile anything.  If any are out of
;; date, we need to load them as .el's, byte compile them, and reload as
;; .elc's.
(defvar lisp-files-needed-for-byte-compilation
  '("bytecomp"
    "cl-macs"
    "byte-optimize"))

;; Lisp files not in `lisp-files-needed-for-byte-compilation' that need
;; early byte compilation.  These are files loaded by update-elc.el in
;; order to do the compilation of all the rest of the files.
(defvar lisp-files-needing-early-byte-compilation
  '("easy-mmode"
    "autoload"
    "shadow"))

(defvar unbytecompiled-lisp-files
  '("paths.el"
    "dumped-lisp.el"
    "dumped-pkg-lisp.el"
    "raw-process.el")
  "Lisp files that should not be byte compiled.
Files in `additional-dump-dependencies' do not need to be listed here.")

(defvar additional-dump-dependencies
  '("loadup.el"
    "loadup-el.el"
    "update-elc.el")
  "Lisp files that are not dumped but which the dump depends on.
If any of these files are changed, we need to redump.")

(defvar lisp-files-ignored-when-checking-for-autoload-updating
  '("custom-load.el"
    "auto-autoloads.el"
    "finder-inf.el")
  "Lisp files that should not trigger auto-autoloads rebuilding.")

(defvar lisp-files-dependent-on-configuration
  '("mule/chinese")
  "Lisp files that need recompilation when XEmacs has been reconfigured.
These files have macros or `eval-when-compile' expressions that expand
differently depending on the presence of certain features, especially
`unicode-internal'.")

(defun update-elc-chop-extension (file)
  (subseq file 0 (string-match-p "\\.elc?$" file)))

;; we used to call packages-list-autoloads here, but it's false generality.
;; we need to handle each autoload file differently and there are only
;; two of them.

(let ((target-extension (and (eq system-type 'windows-nt) ".exe"))
      preloaded-file-list site-load-packages files-to-process)
  
  (load (expand-file-name "dumped-lisp.el" source-lisp))

  ;; Two situations here:
  ;; (1) temacs is dumped into xemacs, which is the case with pdump when
  ;;     --with-dump-in-exec, the default, and always the case with MS Windows
  ;;     native.
  ;; (3) xemacs is dumped or undumped, and it loads the dump from an external
  ;;     file.  Running `xemacs -nd' gets you the equivalent of `temacs'.
  ;;     Dumping creates a file `xemacs.dmp'.

  ;; #### need better ways of getting config params
  (if (member 'dump-in-exec (emacs-run-status))
      (setq exe-target (concatenate 'string "src/temacs" target-extension)
            dump-target (concatenate 'string "src/xemacs" target-extension))
    (setq exe-target "src/xemacs"
          dump-target "src/xemacs.dmp"))

  (setq exe-target (expand-file-name exe-target build-directory))
  (setq dump-target (expand-file-name dump-target build-directory))

  ;; Not currently used.
;   (setq dump-target-out-of-date-wrt-exe-target
; 	(cond ((eq dump-target nil) t)
; 	      (temacs-exe (file-newer-than-file-p temacs-exe dump-target))
; 	      ((eq data-file nil) t)
; 	      (t (file-newer-than-file-p dump-target data-file))))
;   (setq dump-target-exists (or (and temacs-exe dump-target)
; 			       (and data-file dump-target))))

  ;; Path setup
  (let ((package-preloaded-file-list
	 (packages-collect-package-dumped-lisps late-package-load-path)))
 
    (setq preloaded-file-list
 	  (append package-preloaded-file-list
 		  preloaded-file-list
 		  packages-hardcoded-lisp)))

  (load (expand-file-name "site-packages" source-directory) t t)
  (setq preloaded-file-list
	(append packages-hardcoded-lisp
		preloaded-file-list
		site-load-packages))

  ;; First, delete any .elc file that is dependent on XEmacs configuration
  ;; but older than the most recent time that XEmacs was configured
  ;; (as revealed by src/config.h)
  (setq files-to-process lisp-files-dependent-on-configuration)
  (let ((config-file (expand-file-name "src/config.h" build-directory)))
    (while files-to-process
      (let* ((arg (car files-to-process))
	     (elc-file (expand-file-name (concatenate 'string arg ".elc")
					 source-lisp)))
	(when (file-newer-than-file-p config-file elc-file)
	  ;; no error if file doesn't exist or for some reason we can't
	  ;; remove it
	  (condition-case nil
	      (delete-file elc-file)
	    (file-error nil)))
	(setq files-to-process (cdr files-to-process)))))
  
  ;; bytecomp, byte-optimize, autoload, etc. are mentioned specially
  ;; in the lisp-files-need* variables.
  (setq files-to-process (append lisp-files-needed-for-byte-compilation
				 lisp-files-needing-early-byte-compilation
				 additional-dump-dependencies
				 preloaded-file-list))
  (while files-to-process
    (let* ((arg (car files-to-process))
	   (arg-is-dump-dependency
	    (or (member arg preloaded-file-list)
		(member arg additional-dump-dependencies)))
	   (arg-sans-extension (update-elc-chop-extension arg))
	   (full-arg (locate-library arg-sans-extension))
	   (full-arg-sans-extension
	    (if (eq full-arg nil)
		(progn
		  (format-into 'external-debugging-output
			       "\nError: Library file %s not found\n" arg)
		  ;; Uncomment in case of trouble
		  ;;(format-into 'external-debugging-output
		  ;;             "\nlate-package-hierarchies: %S"
		  ;;             late-package-hierarchies)
		  ;;(format-into 'external-debugging-output
                  ;;             "\nguessed-roots: %S"
                  ;;             (paths-find-emacs-roots
                  ;;               invocation-directory invocation-name)))
		  (backtrace)
		  (kill-emacs))
	      (update-elc-chop-extension full-arg)))
	   (full-arg-el (concatenate 'string full-arg-sans-extension ".el"))
	   (full-arg-elc (concatenate 'string full-arg-sans-extension ".elc")))
	   
      ; (print full-arg-el)

      ;; now check if .el or .elc is newer than the dumped exe.
      ;; if so, need to redump.
      (when (and dump-target arg-is-dump-dependency
                 (eq dump-target-out-of-date-wrt-dump-files nil)
		 ;; no need to check for existence of either of the files
		 ;; because of the definition of file-newer-than-file-p.
		 (or (file-newer-than-file-p full-arg-el dump-target)
		     (file-newer-than-file-p full-arg-elc dump-target)))
	(setq dump-target-out-of-date-wrt-dump-files t))

      (if (and (eq (member (file-name-nondirectory arg)
			   unbytecompiled-lisp-files) nil)
	       (eq (member (file-name-nondirectory arg)
			   additional-dump-dependencies) nil)
	       (eq (member full-arg-el processed) nil)
	       ;; no need to check for existence of either of the files
	       ;; because of the definition of file-newer-than-file-p.
	       (file-newer-than-file-p full-arg-el full-arg-elc))
	  (setq processed (cons full-arg-el processed)))

      (setq files-to-process (cdr files-to-process))))

  ;; Check if we need to rebuild the auto-autoloads.el files -- that is,
  ;; if ANY .el files have changed.
  (let ((dirs-to-check (list source-lisp source-lisp-mule)))
    (while dirs-to-check
      (let* ((dir (car dirs-to-check))
	     (full-dir (expand-file-name dir))
	     (all-files-in-dir (directory-files full-dir t "\\.el$" nil t))
	     (autoload-file
	      (expand-file-name "auto-autoloads.el" full-dir))
	     (autoload-is-mule (equal dir source-lisp-mule)))
	(while all-files-in-dir
	  (let* ((full-arg (car all-files-in-dir)))
	    ;; custom-load.el always gets regenerated so don't let that
	    ;; trigger us.
	    (when (and (eq
			(member
			 (file-name-nondirectory full-arg)
			 lisp-files-ignored-when-checking-for-autoload-updating
			 )
			nil)
		       (file-newer-than-file-p full-arg autoload-file))
	      (if autoload-is-mule
		  (setq need-to-rebuild-mule-autoloads t)
		(setq need-to-rebuild-autoloads t))))
	  (setq all-files-in-dir (cdr all-files-in-dir))))
      (setq dirs-to-check (cdr dirs-to-check))))

  ;; Check for the module autoloads separately, given the need to run
  ;; directory-files on subdirectories.
  (mapc
   #'(lambda (full-dir)
       (mapc #'(lambda (full-arg)
		 (when (file-newer-than-file-p full-arg aa-modules)
		   (setq need-to-rebuild-module-autoloads t)))
	     (directory-files full-dir t "\\.c$" nil t)))
   (directory-files source-modules t nil t 'subdirs))

  (if dump-target-out-of-date-wrt-dump-files
      (condition-case nil
	  (write-region-internal
	   "foo" nil (expand-file-name "src/NEEDTODUMP" build-directory))
	(file-error nil)))

  )

(when (or need-to-rebuild-autoloads
	  ;; no real need for the following check either, because if the file
	  ;; doesn't exist, need-to-rebuild-autoloads gets set above.  but
	  ;; it's only one call, so it won't slow things down much and it keeps
	  ;; the logic cleaner.
	  (eq (file-exists-p aa-lisp) nil)
	  ;; no need to check for file-exists of .elc due to definition
	  ;; of file-newer-than-file-p
	  (file-newer-than-file-p aa-lisp aac-lisp))
  (setq need-to-recompile-autoloads t))

(when (or need-to-rebuild-mule-autoloads
	  ;; not necessary but ...  see comment above.
	  (eq (file-exists-p aa-lisp-mule) nil)
	  ;; no need to check for file-exists of .elc due to definition
	  ;; of file-newer-than-file-p
	  (file-newer-than-file-p aa-lisp-mule aac-lisp-mule))
  (setq need-to-recompile-mule-autoloads t))

(when (or need-to-rebuild-module-autoloads
	  ;; not necessary but ...  see comment above.
	  (eq (file-exists-p aa-modules) nil)
	  ;; no need to check for file-exists of .elc due to definition
	  ;; of file-newer-than-file-p
	  (file-newer-than-file-p aa-modules aac-modules))
  (setq need-to-recompile-module-autoloads t))

(setq update-elc-files-to-compile (append update-elc-files-to-compile
					  (nreverse processed)))

;(print update-elc-files-to-compile)

(let ((do-autoload-commands
       (append
	(if (or need-to-rebuild-autoloads
		need-to-rebuild-mule-autoloads
                need-to-rebuild-module-autoloads)
	    (list "-l" "autoload"))
	(if need-to-rebuild-autoloads
	    (list "-f" "batch-update-directory-autoloads"
		  "auto" source-lisp))
	(if need-to-rebuild-mule-autoloads
	    (list "-f" "batch-update-directory-autoloads"
		  "mule" source-lisp-mule))
	(if need-to-rebuild-module-autoloads
	    (list "-eval"
		  (concatenate 'string
		   "(update-autoload-files '("
		   (mapconcat #'prin1-to-string
			      (mapcan
			       #'(lambda (full-dir)
				   (unless (member*
					    (file-name-nondirectory full-dir)
					    '("." "..") :test #'equal)
				     (directory-files full-dir
						      t "\\.c$" nil t)))
			       (directory-files source-modules
						t nil t 'subdirs))
			      " ")
		   ") \"modules\" " (prin1-to-string aa-modules) ")")))
	(if need-to-recompile-autoloads
	    (list "-f" "batch-byte-compile-one-file"
		  aa-lisp))
	(if need-to-recompile-mule-autoloads
	    (list "-f" "batch-byte-compile-one-file"
		  aa-lisp-mule))
	(if need-to-recompile-module-autoloads
	    (list "-f" "batch-byte-compile-one-file"
		  aa-modules)))))
  (cond ((and (eq update-elc-files-to-compile nil)
	      (eq need-to-rebuild-autoloads nil)
	      (eq need-to-rebuild-mule-autoloads nil)
	      (eq need-to-recompile-autoloads nil)
	      (eq need-to-recompile-mule-autoloads nil))
	 ;; (1) Nothing to do at all.
	 )
	((eq update-elc-files-to-compile nil)
	 ;; (2) We have no files to byte-compile, but we do need to regenerate
	 ;;     and compile the auto-autoloads file. Don't pass this on to
	 ;;     update-elc-2.el to do, since that gives dependency problems
	 ;;     with parallel builds (make -j and friends). Completely fine to
	 ;;     use the compiled Lisp infrastructure for this, though, since we
	 ;;     know it's up to date.
	 (setq command-line-args
	       (append
		'("-l" "loadup-el.el" "run-temacs"
		  "-batch" "-no-packages" "-no-autoloads"
		  "-eval" "(setq stack-trace-on-error t)"
		  "-eval" "(setq load-always-display-messages t)"
		  "-l" "bytecomp.elc" "-l" "autoload.elc")
		do-autoload-commands))
	 (write-sequence "\nNeed to regenerate auto-autoload files... "
			 'external-debugging-output)
	 (let ((load-ignore-elc-files nil)
	       (purify-flag nil))
	   (load "loadup.el")))
	(t
	 (let ((bc-bootstrap
		(mapcar #'(lambda (arg) 
			    (concatenate 'string
                                         (update-elc-chop-extension
                                          (locate-library arg)) ".el"))
			lisp-files-needed-for-byte-compilation))
	       (bootstrap-other
		(mapcar #'(lambda (arg) 
			    (concatenate 'string
                                         (update-elc-chop-extension
                                          (locate-library arg)) ".el"))
			lisp-files-needing-early-byte-compilation)))
	   (setq inhibit-autoloads t)
	   ;; if bytecomp or byte-optimize need recompiling, then load
	   ;; the .el version of them first, recompile them, and reload
	   ;; the .elc versions to recompile everything else (so we won't
	   ;; be waiting until the cows come home).  we need to set
	   ;; load-ignore-elc-files because byte-optimize gets autoloaded
	   ;; from bytecomp.
	   (let ((recompile-bc-bootstrap
                  (mapcan
                   #'(lambda (arg)
                       (when (member arg update-elc-files-to-compile)
                         (append '("-f" "batch-byte-compile-one-file")
                                 (list arg))))
                   bc-bootstrap))
		 (recompile-bootstrap-other
                  (mapcan
                   #'(lambda (arg)
                       (when (member arg update-elc-files-to-compile)
                         (append '("-f" "batch-byte-compile-one-file")
                                 (list arg))))
                   bootstrap-other)))
	     (mapc
	      #'(lambda (arg)
		  (setq update-elc-files-to-compile
			(delete* arg update-elc-files-to-compile
				 :test (if default-file-system-ignore-case
					   #'equalp
					 #'equal))))
	      (append bc-bootstrap bootstrap-other))
	     (setq command-line-args
		   (append
		    '("-l" "loadup-el.el" "run-temacs"
		      "-batch" "-no-packages" "-no-autoloads"
		      "-eval" "(setq stack-trace-on-error t)"
		      "-eval" "(setq load-always-display-messages t)")
		    (when recompile-bc-bootstrap
		      (append
		       '("-eval" "(setq load-ignore-elc-files t)"
			 "-l" "bytecomp")
		       recompile-bc-bootstrap
		       '("-eval" "(setq load-ignore-elc-files nil)")))
		    '("-l" "bytecomp")
		    ;; likewise, recompile autoload.el etc. if out-of-date.
		    recompile-bootstrap-other
		    ;; then generate autoloads for lisp and maybe lisp/mule.
		    do-autoload-commands
		    ;; now load the autoloads and compile alles anderes.
		    '("-eval" "(setq inhibit-autoloads nil)"
		      "-f" "startup-load-autoloads"
		      "-f" "batch-byte-compile")
		    update-elc-files-to-compile
		    ))))

	 ;;(print command-line-args)
	 (load "loadup-el.el"))))

(kill-emacs)

;;; update-elc.el ends here
