In our build system I often specify "compile-dirty" so that I don't trigger a world rebuild as happens with "compile". However, this is not strictly correct. If I where to modify a macro in a file and then "compile-dirty", then some of the "elc" files would have the older definition of the macro.
The correct way to handle this is to build out a dependency tree so that Make is aware of exactly what ".el" files should trigger which ".elc" regeneration. As far as I know, for compiling, the only thing we care about is top-level requires. So if we can pull out the top level requires from every file, we can build our dependency tree. I've done this using a simple regex and it seems to work great! With a two exceptions I've found. 1. `org-macs' defines the macro `org-with-point-at' which contains the text "(require 'org-element-ast)". So a hidden require. This occurred in org-compat so I just added the require to the top of the file so it's no longer hidden. 2. For reasons beyond my comprehension, `org-babel-do-load-languages' and `org-load-export-backends' manage to get run at compile time but I'm not really sure what to do about that. Not sure if `org-modules' gets used at compile time. I did not investigate. However, just building a dependency tree doesn't solve all of our issues because of the loaddefs file. The loaddefs file depends on every single source file and all elc files depend on the loaddefs file so we are now back where we started with every change causing a world rebuild. The obvious solution is to simply not depend on loading the loaddefs file during compilation and only load it at runtime. This means the loaddefs file is still regenerated with every source change but that it doesn't cause a world rebuild. This seems to work just fine after adding in a few more `require's and a few `declare-function's. While I did put a lot of work into this change, I still feel like there is much I didn't investigate. I very much expect there to be some glaring flaws in what I've done here. Everything mostly passes CI Morgan (except Emacs 28 and LANG!=english as has been previously discussed).
>From de742a35d7a9c7c66ef371bedae5d3f1cb6bcaf0 Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Sun, 16 Aug 2026 15:06:25 -0400 Subject: [PATCH 1/7] lisp/Makefile: Remove a recursive make * lisp/Makefile (all, compile, compile-dirty): Change from a recursive make recipe to a normal one to make future changes a little easier. Pull out the check for the value of $(ORGCM) to the top level. (print-compile): New target to handle printing some information. Add to .PHONY. --- lisp/Makefile | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lisp/Makefile b/lisp/Makefile index 32739f8de..e85348447 100644 --- a/lisp/Makefile +++ b/lisp/Makefile @@ -13,20 +13,22 @@ LISPN := $(filter-out $(LISPB) $(LISPN:%el=%eln),$(LISPF:%el=%eln)) _ORGCM_ := dirall single native source slint1 slint2 -include local.mk -.PHONY: all compile compile-dirty \ +.PHONY: all compile compile-dirty print-compile \ $(_ORGCM_) $(_ORGCM_:%=compile-%) \ autoloads \ install clean cleanauto cleanall cleanelc clean-install -# do not clean here, done in toplevel make -all compile compile-dirty:: autoloads - @$(info ========= Compiling lisp files using '$(ORGCM)' target) ifeq ($(filter-out $(_ORGCM_),$(ORGCM)),) - $(MAKE) compile-$(ORGCM) else $(error ORGCM has illegal value $(ORGCM) (valid: $(_ORGCM_))) endif +print-compile: + @$(info ========= Compiling lisp files using '$(ORGCM)' target) + +# do not clean here, done in toplevel make +all compile compile-dirty:: autoloads print-compile compile-$(ORGCM) + compile-dirall: dirall compile-single: single $(LISPC) compile-native: native $(LISPN) base-commit: 6916affedfcfb75739f946e29ee5e2da5c1d8c50 -- 2.54.0
>From 94cccd8c23628a4d479896e1f77045bf4a34aca4 Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Sun, 16 Aug 2026 14:50:43 -0400 Subject: [PATCH 2/7] lisp/Makefile: Generate lisp prerequisites * lisp/Makefile (source, slint1): Set "SKIP_DEPS" to ignore prerequisites in these targets. (./deps, ./deps/%.d): New targets that automatically generate prerequisites for the lisp code. Include these files unless "SKIP_DEPS" isn't empty. * .gitignore: Add new directory "lisp/deps/" --- .gitignore | 1 + lisp/Makefile | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 0eb5d834a..afca66f83 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ local*.mk .gitattributes mk/x11idle ChangeLog +lisp/deps/ # Files generated during `make packages/org` in a clone of `elpa.git`. diff --git a/lisp/Makefile b/lisp/Makefile index e85348447..b0861a343 100644 --- a/lisp/Makefile +++ b/lisp/Makefile @@ -46,10 +46,10 @@ native: @$(info ==================== $@ ====================) source: cleanelc @$(info ==================== $@ ====================) - @$(foreach elc,$(LISPC),$(MAKE) $(elc) && $(RM) $(elc);) + @$(foreach elc,$(LISPC),$(MAKE) SKIP_DEPS=yes $(elc) && $(RM) $(elc);) slint1: @$(info ==================== $@ ====================) - @$(foreach elc,$(LISPC),$(RM) $(elc); $(MAKE) $(elc);) + @$(foreach elc,$(LISPC),$(RM) $(elc); $(MAKE) SKIP_DEPS=yes $(elc);) %.elc: %.el @$(info Compiling single $(abspath $<)...) @@ -88,3 +88,23 @@ clean-install: if [ -d $(DESTDIR)$(lispdir) ] ; then \ $(RM) $(DESTDIR)$(lispdir)/org*.el* $(DESTDIR)$(lispdir)/ob*.el* $(DESTDIR)$(lispdir)/ol*.el* $(DESTDIR)$(lispdir)/ox*.el* ; \ fi ; + +# This generates a bunch of tiny Makefiles from the top level requires +# to determine dependencies. Example file (deps/ob-perl.d): +# ob-perl.elc : org-macs.elc ob.elc +# ob-perl.eln : org-macs.eln ob.eln + +./deps: + $(MKDIR) $@ +./deps/%.d: %.el | ./deps + -@$(RM) $@ + @for ext in "elc" "eln"; do \ + sed -n -e "1s/^.*/$*.$$ext :/p" -e "s/^(require '\(o[bclrx][^)]*\)).*$$/\1.$$ext/p" < $< \ + | tr '\n' ' ' >> $@; \ + echo "" >> $@; \ + done + +ifeq ($(SKIP_DEPS),) +include $(LISPF:%.el=./deps/%.d) +else +endif -- 2.54.0
>From 65d8f4851548d7b02a6f61bd45ee813bcdfb76df Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Sun, 16 Aug 2026 14:05:42 -0400 Subject: [PATCH 3/7] Manually load dependencies provided by org-loaddefs.el Currently org.el loads "org-loaddefs.el". If I remove that load then I need these additions to quell warnings. This is done in preparation to no longer load the org-loaddefs at compile time. * lisp/org-agenda.el: Require 'org-clock', 'org-duration', and 'org-timer'. Declare the functions 'org-mobile-push', 'org-mobile-pull', and 'org-attach'. * lisp/org-attach.el: Declare the function 'org-element-context'. * lisp/org-capture.el: Require 'org-datetree'. Declare the functions 'org-clock-in' and 'org-clock-out'. * lisp/org-clock.el: Require 'org-duration'. Declare the functions 'org-add-archive-files' and 'org-element-at-point'. * lisp/org-colview.el: Require 'org-duration', 'org-id', 'org-num', and 'ox-beamer'. Declare the functions 'org-clock-sum', and 'org-element-at-point'. * lisp/org-compat.el: Require 'org-element-ast'. * lisp/org-id.el: Declare the functions 'org-element-cache-reset' and 'org-element-at-point'. * lisp/org-lint.el: Require 'org-duration'. * lisp/org-mobile.el: Require 'org-archive'. * lisp/org-timer.el: Require 'org-duration'. --- lisp/org-agenda.el | 6 ++++++ lisp/org-attach.el | 1 + lisp/org-capture.el | 3 +++ lisp/org-clock.el | 3 +++ lisp/org-colview.el | 6 ++++++ lisp/org-compat.el | 1 + lisp/org-id.el | 2 ++ lisp/org-lint.el | 1 + lisp/org-mobile.el | 1 + lisp/org-timer.el | 1 + 10 files changed, 25 insertions(+) diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el index a426d4f5b..9c122b49c 100644 --- a/lisp/org-agenda.el +++ b/lisp/org-agenda.el @@ -55,6 +55,9 @@ (require 'org-macs) (require 'org-refile) (require 'org-element) +(require 'org-clock) +(require 'org-duration) +(require 'org-timer) (declare-function diary-add-to-list "diary-lib" (date string specifier &optional marker globcolor literal)) @@ -88,6 +91,9 @@ (declare-function org-add-archive-files "org-archive" (files)) (declare-function org-capture "org-capture" (&optional goto keys)) (declare-function org-clock-modify-effort-estimate "org-clock" (&optional value)) +(declare-function org-mobile-push "org-mobile" ()) +(declare-function org-mobile-pull "org-mobile" ()) +(declare-function org-attach "org-attach" ()) (defvar calendar-mode-map) (defvar org-clock-current-task) diff --git a/lisp/org-attach.el b/lisp/org-attach.el index f44340fc7..fb35f685e 100644 --- a/lisp/org-attach.el +++ b/lisp/org-attach.el @@ -49,6 +49,7 @@ (declare-function org-element-end "org-element" (node)) (declare-function org-element-contents-begin "org-element" (node)) (declare-function org-element-contents-end "org-element" (node)) +(declare-function org-element-context "org-element" (&optional element)) (declare-function org-element-type-p "org-element-ast" (node types)) (declare-function org-inlinetask-goto-beginning "org-inlinetask" ()) (declare-function org-inlinetask-in-task-p "org-inlinetask" ()) diff --git a/lisp/org-capture.el b/lisp/org-capture.el index 9712244df..dc6751817 100644 --- a/lisp/org-capture.el +++ b/lisp/org-capture.el @@ -53,10 +53,13 @@ (require 'cl-lib) (require 'org) (require 'org-refile) +(require 'org-datetree) (declare-function org-at-encrypted-entry-p "org-crypt" ()) (declare-function org-at-table-p "org-table" (&optional table-type)) (declare-function org-clock-update-mode-line "org-clock" (&optional refresh)) +(declare-function org-clock-in "org-clock" (&optional select start-time)) +(declare-function org-clock-out "org-clock" (&optional switch-to-state fail-quietly at-time)) (declare-function org-datetree-find-date-create "org-datetree" (date &optional keep-restriction)) (declare-function org-datetree-find-month-create "org-datetree" (d &optional keep-restriction)) (declare-function org-datetree-find-create-hierarchy "org-datetree" (hier-pairs &optional keep-restriction legacy-prop)) diff --git a/lisp/org-clock.el b/lisp/org-clock.el index 75ecb1ba9..18c600c45 100644 --- a/lisp/org-clock.el +++ b/lisp/org-clock.el @@ -33,12 +33,15 @@ (require 'cl-lib) (require 'org) +(require 'org-duration) (declare-function calendar-iso-to-absolute "cal-iso" (date)) +(declare-function org-add-archive-files "org-archive" (files)) (declare-function notifications-notify "notifications" (&rest params)) (declare-function org-element-property "org-element-ast" (property node)) (declare-function org-element-contents-end "org-element" (node)) (declare-function org-element-end "org-element" (node)) +(declare-function org-element-at-point "org-element" (&optional pom cached-only)) (declare-function org-element-type "org-element-ast" (node &optional anonymous)) (declare-function org-element-type-p "org-element-ast" (node types)) (defvar org-element-use-cache) diff --git a/lisp/org-colview.el b/lisp/org-colview.el index d5548802f..264a9059e 100644 --- a/lisp/org-colview.el +++ b/lisp/org-colview.el @@ -35,13 +35,19 @@ (require 'cl-lib) (require 'org) +(require 'org-duration) +(require 'org-id) +(require 'org-num) +(require 'ox-beamer) (declare-function org-agenda-redo "org-agenda" (&optional all)) (declare-function org-agenda-do-context-action "org-agenda" ()) +(declare-function org-clock-sum "org-clock" (&optional tstart tend headline-filter propname)) (declare-function org-clock-sum-today "org-clock" (&optional headline-filter)) (declare-function org-element-extract "org-element-ast" (node)) (declare-function org-element-interpret-data "org-element" (data)) (declare-function org-element-map "org-element" (data types fun &optional info first-match no-recursion with-affiliated no-undefer)) +(declare-function org-element-at-point "org-element" (&optional pom cached-only)) (declare-function org-element-parse-secondary-string "org-element" (string restriction &optional parent)) (declare-function org-element-property "org-element-ast" (property node)) (declare-function org-element-restriction "org-element" (element)) diff --git a/lisp/org-compat.el b/lisp/org-compat.el index 75e772a17..0d630ac17 100644 --- a/lisp/org-compat.el +++ b/lisp/org-compat.el @@ -33,6 +33,7 @@ (require 'cl-lib) (require 'seq) (require 'org-macs) +(require 'org-element-ast) ;; Required by some of the macros used from org-macs (eval-when-compile (require 'subr-x)) ; Emacs < 28 diff --git a/lisp/org-id.el b/lisp/org-id.el index 40bf5fa4b..742c5be58 100644 --- a/lisp/org-id.el +++ b/lisp/org-id.el @@ -80,6 +80,8 @@ (declare-function message-make-fqdn "message" ()) (declare-function org-goto-location "org-goto" (&optional _buf help)) +(declare-function org-element-cache-reset "org-element" (&optional all no-persistence)) +(declare-function org-element-at-point "org-element" (&optional pom cached-only)) ;;; Customization diff --git a/lisp/org-lint.el b/lisp/org-lint.el index df5c0d578..56be114e8 100644 --- a/lisp/org-lint.el +++ b/lisp/org-lint.el @@ -95,6 +95,7 @@ (require 'oc) (require 'ol) (require 'org-attach) +(require 'org-duration) (require 'org-macro) (require 'org-fold) (require 'ox) diff --git a/lisp/org-mobile.el b/lisp/org-mobile.el index c8c7b8955..e22325430 100644 --- a/lisp/org-mobile.el +++ b/lisp/org-mobile.el @@ -37,6 +37,7 @@ (require 'cl-lib) (require 'org) (require 'org-agenda) +(require 'org-archive) (require 'ol) ;;; Code: diff --git a/lisp/org-timer.el b/lisp/org-timer.el index 8eb6a8eb7..17844e698 100644 --- a/lisp/org-timer.el +++ b/lisp/org-timer.el @@ -40,6 +40,7 @@ (require 'cl-lib) (require 'org-clock) +(require 'org-duration) (defvar org-timer-start-time nil "Start time for the running timer.") -- 2.54.0
>From 50dcce1d3c98c6c1289ccb38d8bd1a384189d430 Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Sun, 16 Aug 2026 18:18:07 -0400 Subject: [PATCH 4/7] lisp/org.el: Don't load "org-loaddefs.el" * lisp/org.el: Don't load "org-loaddefs.el". --- lisp/org.el | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lisp/org.el b/lisp/org.el index 2071a9dc0..085c330e2 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -84,16 +84,6 @@ org-inlinetask-min-level (require 'format-spec) (require 'thingatpt) -(condition-case nil - (load (concat (file-name-directory load-file-name) - "org-loaddefs") - nil t nil t) - (error - (message "WARNING: No org-loaddefs.el file could be found from where org.el is loaded.") - (sit-for 3) - (message "You need to run \"make\" or \"make autoloads\" from Org lisp directory") - (sit-for 3))) - (require 'org-macs) (require 'org-compat) (require 'org-keys) -- 2.54.0
>From a2e8c859d889a1e22ecff88764b4e395e295d873 Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Sun, 16 Aug 2026 14:54:05 -0400 Subject: [PATCH 5/7] lisp/Makefile: Remove dependencies on the loaddefs * lisp/Makefile (all, compile, compile-dirty): Build the loaddefs last. (%.elc, %.eln): Add a dependency on "org-version.el" to absolutley ensure it is built. ($(LISPV)): Remove dependency on the loaddefs. --- lisp/Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lisp/Makefile b/lisp/Makefile index b0861a343..4ddc02ff3 100644 --- a/lisp/Makefile +++ b/lisp/Makefile @@ -27,7 +27,7 @@ print-compile: @$(info ========= Compiling lisp files using '$(ORGCM)' target) # do not clean here, done in toplevel make -all compile compile-dirty:: autoloads print-compile compile-$(ORGCM) +all compile compile-dirty:: $(LISPV) print-compile compile-$(ORGCM) $(LISPI) compile-dirall: dirall compile-single: single $(LISPC) @@ -51,17 +51,17 @@ slint1: @$(info ==================== $@ ====================) @$(foreach elc,$(LISPC),$(RM) $(elc); $(MAKE) SKIP_DEPS=yes $(elc);) -%.elc: %.el +%.elc: %.el $(LISPV) @$(info Compiling single $(abspath $<)...) -@$(ELC) $< -%.eln: %.el +%.eln: %.el $(LISPV) @$(info Native compiling single $(abspath $<)...) -@$(ELN) $< autoloads: cleanauto $(LISPI) $(LISPV) -$(LISPV): $(LISPF) +$(LISPV): @$(info ========= Auto-generating Org version number) @$(info org-version: $(ORGVERSION) ($(GITVERSION))) @$(RM) $(@) -- 2.54.0
>From 94092de0f8f5fc55b45932b5228a6513aff648c5 Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Sun, 16 Aug 2026 14:57:56 -0400 Subject: [PATCH 6/7] Build System: Don't clean unnecessarily * lisp/Makefile (autoloads): Don't clean up these files when generating them. * mk/targets.mk (all, compile): Don't run clean in the lisp directory before a compilation. --- lisp/Makefile | 2 +- mk/targets.mk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lisp/Makefile b/lisp/Makefile index 4ddc02ff3..f5305792f 100644 --- a/lisp/Makefile +++ b/lisp/Makefile @@ -59,7 +59,7 @@ slint1: @$(info Native compiling single $(abspath $<)...) -@$(ELN) $< -autoloads: cleanauto $(LISPI) $(LISPV) +autoloads: $(LISPI) $(LISPV) $(LISPV): @$(info ========= Auto-generating Org version number) diff --git a/mk/targets.mk b/mk/targets.mk index a8bec6813..9b045f61e 100644 --- a/mk/targets.mk +++ b/mk/targets.mk @@ -90,7 +90,7 @@ local.mk: -@$(MAKE_LOCAL_MK) all compile:: - $(foreach dir, doc lisp, $(MAKE) -C $(dir) clean;) + $(MAKE) -C doc clean compile compile-dirty:: $(MAKE) -C lisp $@ all clean-install:: -- 2.54.0
>From 0576c989bb7a43bf2b9091d70a05cf05297f5dde Mon Sep 17 00:00:00 2001 From: Morgan Smith <[email protected]> Date: Sun, 16 Aug 2026 15:15:08 -0400 Subject: [PATCH 7/7] lisp/Makefile: Don't ignore compile errors * lisp/Makefile (source): Use "set -e" and replace a "&&" with a semicolon in order to ensure that any error in any step of this recipe is detected. (%.elc, %.eln): Remove the leading "-" which caused errors to be ignored. --- lisp/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lisp/Makefile b/lisp/Makefile index f5305792f..71d2edb65 100644 --- a/lisp/Makefile +++ b/lisp/Makefile @@ -46,18 +46,18 @@ native: @$(info ==================== $@ ====================) source: cleanelc @$(info ==================== $@ ====================) - @$(foreach elc,$(LISPC),$(MAKE) SKIP_DEPS=yes $(elc) && $(RM) $(elc);) + @set -e; $(foreach elc,$(LISPC),$(MAKE) SKIP_DEPS=yes $(elc); $(RM) $(elc);) slint1: @$(info ==================== $@ ====================) @$(foreach elc,$(LISPC),$(RM) $(elc); $(MAKE) SKIP_DEPS=yes $(elc);) %.elc: %.el $(LISPV) @$(info Compiling single $(abspath $<)...) - -@$(ELC) $< + @$(ELC) $< %.eln: %.el $(LISPV) @$(info Native compiling single $(abspath $<)...) - -@$(ELN) $< + @$(ELN) $< autoloads: $(LISPI) $(LISPV) -- 2.54.0
