;;; lisp/cli/sync.el --- synchronize config command -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:

(load! "packages")


;;
;;; Variables

(defvar doom-after-sync-hook ()
  "Hooks run after 'doom sync' synchronizes the user's config with Doom.")

(defvar doom-before-sync-hook ()
  "Hooks run before 'doom sync' synchronizes the user's config with Doom.")

(defvar doom-cli-sync-info-file (file-name-concat doom-profile-data-dir "sync"))


;;
;;; Commands

(defcli! ((sync s))
    ((noenvvar? ("-e") "Don't regenerate the envvar file")
     (update?   ("-u") "Update all installed packages after syncing")
     (noupdate? ("-U") "Don't update any packages")
     (purge?    ("--gc") "Purge orphaned package repos & regraft them")
     (jobs      ("-j" "--jobs" num) "How many threads to use for native compilation")
     (rebuild?  ("-b" "--rebuild") "Rebuild all installed packages, unconditionally")
     (nobuild?  ("-B") "Don't rebuild packages when hostname or Emacs version has changed")
     &context context)
  "Synchronize your config with Doom Emacs.

This is the equivalent of running autoremove, install, autoloads, then
recompile. Run this whenever you:

  1. Modify your `doom!' block,
  2. Add or remove `package!' blocks to your config,
  3. Add or remove autoloaded functions in module autoloaded files,
  4. Update Doom outside of Doom (e.g. with git),
  5. Move your Doom config (either $EMACSDIR or $DOOMDIR) to a new location.
  6. When you up (or down) grade Emacs itself.

It will ensure that unneeded packages are removed, all needed packages are
installed, autoloads files are up-to-date and no byte-compiled files have gone
stale.

OPTIONS:
  -j, --jobs
    Defaults to the maximum number of threads (or 1, if your CPU's threadcount
    can't be determined)."
  :benchmark t
  (when (doom-profiles-bootloadable-p)
    (call! '(profiles sync "--reload")))
  (when jobs
    (setq native-comp-async-jobs-number (truncate jobs)))
  (run-hooks 'doom-before-sync-hook)
  (add-hook 'kill-emacs-hook #'doom-sync--abort-warning-h)
  (print! (item "Using Emacs %s @ %s") emacs-version (path invocation-directory invocation-name))
  (print! (start "Synchronizing %S profile..." ) (or (car doom-profile) "default"))
  (unwind-protect
      (print-group!
        ;; If the user has up/downgraded Emacs since last sync, or copied their
        ;; config to a different system, then their packages need to be
        ;; recompiled. This is necessary because Emacs byte-code is not
        ;; necessarily back/forward compatible across major versions, and many
        ;; packages bake in hardcoded data at compile-time.
        (pcase-let ((`(,old-version . ,old-host) (doom-file-read doom-cli-sync-info-file :by 'read :noerror t))
                    (to-rebuild nil))
          (when (and old-version (not (equal old-version emacs-version)))
            (print! (warn "Emacs version has changed since last sync (from %s to %s)") old-version emacs-version)
            (setq to-rebuild t))
          (when (and old-host (not (equal old-host (system-name))))
            (print! (warn "Your system has changed since last sync"))
            (setq to-rebuild t))
          (when (and to-rebuild (not (doom-cli-context-suppress-prompts-p context)))
            (cond (nobuild?
                   (print! (warn "Packages must be rebuilt, but -B has prevented it. Skipping...")))
                  ((doom-cli-context-get context 'upgrading)
                   (print! (warn "Packages will be rebuilt"))
                   (setq rebuild? t))
                  ((y-or-n-p (format! "  %s" "Installed packages must be rebuilt. Do so now?"))
                   (setq rebuild? t))
                  ((exit! 0)))))
        (when (and (not noenvvar?)
                   (file-exists-p doom-env-file))
          (call! '(env)))
        (doom-packages-ensure rebuild?)
        (unless noupdate? (doom-packages-update (not update?)))
        (doom-packages-purge purge? purge? purge? purge? purge?)
        (when (doom-profile-generate)
          (print! (item "Restart Emacs or use 'M-x doom/reload' for changes to take effect"))
          (run-hooks 'doom-after-sync-hook))
        (when (or rebuild? (not (file-exists-p doom-cli-sync-info-file)))
          (with-temp-file doom-cli-sync-info-file
            (prin1 (cons emacs-version (system-name)) (current-buffer))))
        t)
    (remove-hook 'kill-emacs-hook #'doom-sync--abort-warning-h)))


;;
;;; Helpers

(defun doom-sync--abort-warning-h ()
  (print! (warn "Script was abruptly aborted, leaving Doom in an incomplete state!"))
  (print! (item "Run 'doom sync' to repair it.")))

(provide 'doom-cli-sync)
;;; sync.el ends here
