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

Reply via email to