emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* Useful package? Compat.el
@ 2021-10-11 10:36 Timothy
  2021-10-11 14:28 ` Russell Adams
  2023-01-27 13:23 ` [POLL] Use compat.el in Org? (was: Useful package? Compat.el) Ihor Radchenko
  0 siblings, 2 replies; 36+ messages in thread
From: Timothy @ 2021-10-11 10:36 UTC (permalink / raw)
  To: Org-mode


[-- Attachment #1.1: Type: text/plain, Size: 408 bytes --]

Hi,

I’ve recently come across an interesting looking library available on ELPA,
<https://git.sr.ht/~pkal/compat>. I’m thinking in future this could allow us to
both use newer features and also support older versions of Emacs, e.g.

Org 10.X is developed for Emacs 28.1 and newer, but supports Emacs 24.3 and
newer with compat.el.

Just tossing this out as an idea :)

All the best,
Timothy

[-- Attachment #1.2: Type: text/html, Size: 2920 bytes --]

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Useful package? Compat.el
  2021-10-11 10:36 Useful package? Compat.el Timothy
@ 2021-10-11 14:28 ` Russell Adams
  2021-10-11 14:40   ` Timothy
  2023-01-27 13:23 ` [POLL] Use compat.el in Org? (was: Useful package? Compat.el) Ihor Radchenko
  1 sibling, 1 reply; 36+ messages in thread
From: Russell Adams @ 2021-10-11 14:28 UTC (permalink / raw)
  To: emacs-orgmode

On Mon, Oct 11, 2021 at 06:36:50PM +0800, Timothy wrote:
> I’ve recently come across an interesting looking library available on ELPA,
> <https://git.sr.ht/~pkal/compat>. I’m thinking in future this could allow us to
> both use newer features and also support older versions of Emacs, e.g.
>
> Org 10.X is developed for Emacs 28.1 and newer, but supports Emacs 24.3 and
> newer with compat.el.

That's interesting, using a library to wrap new Emacs functionality
and implement a common replacement instead of custom work-arounds in
local code.

Do you have any examples of where this could shorten code in Org?

I believe it's a tall order to add a dependency, but if it eliminates
large chunks of backward compatibility code then maybe that's
something to consider.

Thanks.

------------------------------------------------------------------
Russell Adams                            RLAdams@AdamsInfoServ.com

PGP Key ID:     0x1160DCB3           http://www.adamsinfoserv.com/

Fingerprint:    1723 D8CA 4280 1EC9 557F  66E8 1154 E018 1160 DCB3


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Useful package? Compat.el
  2021-10-11 14:28 ` Russell Adams
@ 2021-10-11 14:40   ` Timothy
  2021-10-11 18:04     ` Joost Kremers
  0 siblings, 1 reply; 36+ messages in thread
From: Timothy @ 2021-10-11 14:40 UTC (permalink / raw)
  To: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 1072 bytes --]

Hi Russel,

> Do you have any examples of where this could shorten code in Org?

I don’t have any examples on hand, but Org is already rolling some compat code
for old Emacs versions, and we’ve had issues where people have accidentally used
functions which are “too new” (but a few years old, and so nobody picked it up
till someone raised an issue). I can also personally recall times when I’ve had
to refactor code to make it uglier and more verbose to avoid using newer
functions. All of this is why I’m thinking this could be an interesting idea.

> I believe it’s a tall order to add a dependency, but if it eliminates
> large chunks of backward compatibility code then maybe that’s
> something to consider.

I think the way to do this would be as a “soft” dependency, i.e. not needed for
anyone running a recent version of Emacs, but needed if you want to use Org with
an old version of Emacs. Not sure how this would best be done, but if this were
to be useful, this is how I’d imagine it working.

All the best,
Timothy

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Useful package? Compat.el
  2021-10-11 14:40   ` Timothy
@ 2021-10-11 18:04     ` Joost Kremers
  0 siblings, 0 replies; 36+ messages in thread
From: Joost Kremers @ 2021-10-11 18:04 UTC (permalink / raw)
  To: Timothy; +Cc: emacs-orgmode


On Mon, Oct 11 2021, Timothy wrote:
> I think the way to do this would be as a “soft” dependency, i.e. not needed for
> anyone running a recent version of Emacs, but needed if you want to use Org with
> an old version of Emacs. Not sure how this would best be done, but if this were
> to be useful, this is how I’d imagine it working.

IIUC you can simply specify a dependency on `compat` and the package itself will
make sure only the necessary compatibility functions are loaded given the Emacs
version it's running in. So if you're running Emacs 28, nothing is loaded; if
you're running Emacs 27, `compat-28.1` is loaded to ensure packages written for
Emacs 28 can still be used; if you're running Emacs 26, both `compat-27.1` and
`compat-28.1` are loaded, etc.

-- 
Joost Kremers
Life has its moments


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [POLL] Use compat.el in Org? (was: Useful package? Compat.el)
  2021-10-11 10:36 Useful package? Compat.el Timothy
  2021-10-11 14:28 ` Russell Adams
@ 2023-01-27 13:23 ` Ihor Radchenko
  2023-01-27 13:34   ` [POLL] Use compat.el in Org? Bastien Guerry
                     ` (3 more replies)
  1 sibling, 4 replies; 36+ messages in thread
From: Ihor Radchenko @ 2023-01-27 13:23 UTC (permalink / raw)
  To: Timothy, Bastien, Kyle Meyer; +Cc: Org-mode, Daniel Mendler

Timothy <tecosaur@gmail.com> writes:

> I’ve recently come across an interesting looking library available on ELPA,
> <https://git.sr.ht/~pkal/compat>. I’m thinking in future this could allow us to
> both use newer features and also support older versions of Emacs, e.g.
>
> Org 10.X is developed for Emacs 28.1 and newer, but supports Emacs 24.3 and
> newer with compat.el.
>
> Just tossing this out as an idea :)

I have recently been contacted by the current compat.el maintainer
asking if we are willing to adapt compat.el in Org.

Pros:

1. We will have less headache maintaining org-compat.el.
2. We will get an ability to use functions and macros from newer Emacs
   versions almost for free.
   
Cons:

1. If compat.el happens to lack support of some function, we will need
   to contribute to compat.el directly and synchronize Org releases with
   compat.el releases.

WDYT?

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [POLL] Use compat.el in Org?
  2023-01-27 13:23 ` [POLL] Use compat.el in Org? (was: Useful package? Compat.el) Ihor Radchenko
@ 2023-01-27 13:34   ` Bastien Guerry
  2023-01-27 20:38     ` Tim Cross
  2023-01-28 16:04   ` [POLL] Use compat.el in Org? (was: Useful package? Compat.el) Kyle Meyer
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 36+ messages in thread
From: Bastien Guerry @ 2023-01-27 13:34 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Timothy, Kyle Meyer, Org-mode, Daniel Mendler

Hi Ihor,

Ihor Radchenko <yantar92@posteo.net> writes:

> I have recently been contacted by the current compat.el maintainer
> asking if we are willing to adapt compat.el in Org.

Very nice!

> WDYT?

As long as we keep our promise in terms of backward compatibility with
older Emacs versions, I'm all for it.

-- 
 Bastien


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [POLL] Use compat.el in Org?
  2023-01-27 13:34   ` [POLL] Use compat.el in Org? Bastien Guerry
@ 2023-01-27 20:38     ` Tim Cross
  2023-01-27 21:38       ` Daniel Mendler
  0 siblings, 1 reply; 36+ messages in thread
From: Tim Cross @ 2023-01-27 20:38 UTC (permalink / raw)
  To: Bastien Guerry
  Cc: Ihor Radchenko, Timothy, Kyle Meyer, Daniel Mendler,
	emacs-orgmode

Bastien Guerry <bzg@gnu.org> writes:

> Hi Ihor,
>
> Ihor Radchenko <yantar92@posteo.net> writes:
>
>> I have recently been contacted by the current compat.el maintainer
>> asking if we are willing to adapt compat.el in Org.
>
> Very nice!
>
>> WDYT?
>
> As long as we keep our promise in terms of backward compatibility with
> older Emacs versions, I'm all for it.

I would agree. I would also add that even with the use of this package,
I don't think we should use it to increase the number of versions we
support as support is not as simple as dropping in a compatibility
library. These libraries come with a cost. Often, compatibility code
does not perform as well and/or is much more complicated and more likely
to have bugs. The more a version of emacs needs to rely on this library
to run org-mode, the higher the likelihood performance will be degraded
or unexpected new bugs are found.

So, use the library, but keep the existing policy of officially
supporting only the previous two major releases. If org does work with
even older versions, that is great, but not supported.


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [POLL] Use compat.el in Org?
  2023-01-27 20:38     ` Tim Cross
@ 2023-01-27 21:38       ` Daniel Mendler
  2023-01-27 22:29         ` Samuel Wales
  0 siblings, 1 reply; 36+ messages in thread
From: Daniel Mendler @ 2023-01-27 21:38 UTC (permalink / raw)
  To: Tim Cross, Bastien Guerry
  Cc: Ihor Radchenko, Timothy, Kyle Meyer, emacs-orgmode

On 1/27/23 21:38, Tim Cross wrote:
>> As long as we keep our promise in terms of backward compatibility with
>> older Emacs versions, I'm all for it.
> 
> I would agree. I would also add that even with the use of this package,
> I don't think we should use it to increase the number of versions we
> support as support is not as simple as dropping in a compatibility
> library. 

True. The Compat package cannot fix bugs below the Elisp level or
provide APIs which cannot be backported, e.g., big integer support. If
Org relies on behavior of the Emacs display engine or the C core of a
certain Emacs version, Compat cannot help.

The advantage would be that the maintenance burden of org-compat would
be reduced. Many packages can share the backported functions by
depending on Compat, which will increase robustness and reduce the risk
of unexpected bugs. The community only has to maintain a single set of
backported functions in a single package, instead of scattering
compatibility code across many packages.

> These libraries come with a cost. Often, compatibility code
> does not perform as well and/or is much more complicated and more likely
> to have bugs. The more a version of emacs needs to rely on this library
> to run org-mode, the higher the likelihood performance will be degraded
> or unexpected new bugs are found.

To give some context about the stability aspect - many backported
compatibility functions are copied verbatim from newer Emacs versions.
Every compatibility function provided by Compat is covered by tests,
which are executed via CI on all supported Emacs versions (>= 24.4). I
make sure that no functions are backported which perform much worse such
that they would introduce performance bugs.

Daniel


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [POLL] Use compat.el in Org?
  2023-01-27 21:38       ` Daniel Mendler
@ 2023-01-27 22:29         ` Samuel Wales
  0 siblings, 0 replies; 36+ messages in thread
From: Samuel Wales @ 2023-01-27 22:29 UTC (permalink / raw)
  To: Daniel Mendler
  Cc: Tim Cross, Bastien Guerry, Ihor Radchenko, Timothy, Kyle Meyer,
	emacs-orgmode

what daniel said sgtm

On 1/27/23, Daniel Mendler <mail@daniel-mendler.de> wrote:
> On 1/27/23 21:38, Tim Cross wrote:
>>> As long as we keep our promise in terms of backward compatibility with
>>> older Emacs versions, I'm all for it.
>>
>> I would agree. I would also add that even with the use of this package,
>> I don't think we should use it to increase the number of versions we
>> support as support is not as simple as dropping in a compatibility
>> library.
>
> True. The Compat package cannot fix bugs below the Elisp level or
> provide APIs which cannot be backported, e.g., big integer support. If
> Org relies on behavior of the Emacs display engine or the C core of a
> certain Emacs version, Compat cannot help.
>
> The advantage would be that the maintenance burden of org-compat would
> be reduced. Many packages can share the backported functions by
> depending on Compat, which will increase robustness and reduce the risk
> of unexpected bugs. The community only has to maintain a single set of
> backported functions in a single package, instead of scattering
> compatibility code across many packages.
>
>> These libraries come with a cost. Often, compatibility code
>> does not perform as well and/or is much more complicated and more likely
>> to have bugs. The more a version of emacs needs to rely on this library
>> to run org-mode, the higher the likelihood performance will be degraded
>> or unexpected new bugs are found.
>
> To give some context about the stability aspect - many backported
> compatibility functions are copied verbatim from newer Emacs versions.
> Every compatibility function provided by Compat is covered by tests,
> which are executed via CI on all supported Emacs versions (>= 24.4). I
> make sure that no functions are backported which perform much worse such
> that they would introduce performance bugs.
>
> Daniel
>
>


-- 
The Kafka Pandemic

A blog about science, health, human rights, and misopathy:
https://thekafkapandemic.blogspot.com


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)
  2023-01-27 13:23 ` [POLL] Use compat.el in Org? (was: Useful package? Compat.el) Ihor Radchenko
  2023-01-27 13:34   ` [POLL] Use compat.el in Org? Bastien Guerry
@ 2023-01-28 16:04   ` Kyle Meyer
  2023-01-30 11:35   ` Greg Minshall
  2023-04-01 10:31   ` [PATCH] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)) Ihor Radchenko
  3 siblings, 0 replies; 36+ messages in thread
From: Kyle Meyer @ 2023-01-28 16:04 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Timothy, Bastien, Org-mode, Daniel Mendler

Ihor Radchenko writes:

> I have recently been contacted by the current compat.el maintainer
> asking if we are willing to adapt compat.el in Org.

I'm in favor of Org using Compat.  And grepping around emacs.git, there
are already two bundled packages (erc and python) with

  (require 'compat nil 'noerror)

so I don't expect there would be objections/questions from the Emacs
side.


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)
  2023-01-27 13:23 ` [POLL] Use compat.el in Org? (was: Useful package? Compat.el) Ihor Radchenko
  2023-01-27 13:34   ` [POLL] Use compat.el in Org? Bastien Guerry
  2023-01-28 16:04   ` [POLL] Use compat.el in Org? (was: Useful package? Compat.el) Kyle Meyer
@ 2023-01-30 11:35   ` Greg Minshall
  2023-01-30 19:33     ` Ihor Radchenko
  2023-04-01 10:31   ` [PATCH] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)) Ihor Radchenko
  3 siblings, 1 reply; 36+ messages in thread
From: Greg Minshall @ 2023-01-30 11:35 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Timothy, Bastien, Kyle Meyer, Org-mode, Daniel Mendler

Ihor,

> Cons:
> 
> 1. If compat.el happens to lack support of some function, we will need
>    to contribute to compat.el directly and synchronize Org releases with
>    compat.el releases.

would a separate "org-compat.el" (in addition to compat.el) somehow
solve this?  (i worry about the synch'ing.)

cheers, Greg


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)
  2023-01-30 11:35   ` Greg Minshall
@ 2023-01-30 19:33     ` Ihor Radchenko
  2023-01-30 19:40       ` Greg Minshall
  0 siblings, 1 reply; 36+ messages in thread
From: Ihor Radchenko @ 2023-01-30 19:33 UTC (permalink / raw)
  To: Greg Minshall; +Cc: Timothy, Bastien, Kyle Meyer, Org-mode, Daniel Mendler

Greg Minshall <minshall@umich.edu> writes:

>> 1. If compat.el happens to lack support of some function, we will need
>>    to contribute to compat.el directly and synchronize Org releases with
>>    compat.el releases.
>
> would a separate "org-compat.el" (in addition to compat.el) somehow
> solve this?  (i worry about the synch'ing.)

That's what we already do.
Using compat.el means that we can remove some of the functions from
org-compat.el and instead rely on compat.el where the same functions are
maintained more carefully.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)
  2023-01-30 19:33     ` Ihor Radchenko
@ 2023-01-30 19:40       ` Greg Minshall
  2023-01-30 21:38         ` Daniel Mendler
  0 siblings, 1 reply; 36+ messages in thread
From: Greg Minshall @ 2023-01-30 19:40 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Timothy, Bastien, Kyle Meyer, Org-mode, Daniel Mendler

Ihor,

> >> 1. If compat.el happens to lack support of some function, we will need
> >>    to contribute to compat.el directly and synchronize Org releases with
> >>    compat.el releases.
> >
> > would a separate "org-compat.el" (in addition to compat.el) somehow
> > solve this?  (i worry about the synch'ing.)
> 
> That's what we already do.
> Using compat.el means that we can remove some of the functions from
> org-compat.el and instead rely on compat.el where the same functions are
> maintained more carefully.

i see, yes.  i'm just thinking that, for a given release CUR (like i
know anything about org-mode release procedures!)  we would use whatever
has been available in compat.el since release CUR-n (for whatever n we
use -- 2?), and supplement that, in org-compat.el, with whatever other
compatibility features *we* (org-mode) need to support releases [CUR-n
.. CUR].

(and, presumably, contribute whatever might be appropriate from
org-compat.el to compat.el, so we can prune it out from org-compat.el at
some future point in time.)

i'm *only* thinking of trying to de-couple org-mode development from
that of compat.el, in the mindset that "less cross-dependencies" ==
"less complication".  if that makes sense.

cheers, Greg


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)
  2023-01-30 19:40       ` Greg Minshall
@ 2023-01-30 21:38         ` Daniel Mendler
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel Mendler @ 2023-01-30 21:38 UTC (permalink / raw)
  To: Greg Minshall, Ihor Radchenko; +Cc: Timothy, Bastien, Kyle Meyer, Org-mode

On 1/30/23 20:40, Greg Minshall wrote:
> i see, yes.  i'm just thinking that, for a given release CUR (like i
> know anything about org-mode release procedures!)  we would use whatever
> has been available in compat.el since release CUR-n (for whatever n we
> use -- 2?), and supplement that, in org-compat.el, with whatever other
> compatibility features *we* (org-mode) need to support releases [CUR-n
> .. CUR].
> 
> (and, presumably, contribute whatever might be appropriate from
> org-compat.el to compat.el, so we can prune it out from org-compat.el at
> some future point in time.)

Sounds good. But note that Compat will at some point have sufficient
coverage of the existing APIs, such that Org may not miss anything. From
then on new additions will only be made at the time of a new Emacs
release. The current Compat already supports many Emacs 29 APIs, which
is in time for the upcoming pretest. Compat won't add any APIs which are
still part of the unstable development master branch (before the branch
cut and feature freeze), which reduces the need for coordination.

If you are missing something right now, and want to introduce and use a
new backported function, e.g., something new from Emacs 29, instead of
going via the org-compat indirection, you could also make an addition
directly to Compat, a Compat release can be prepared a short while
after, and then you start using the new function in the Org development
version.

Daniel


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-01-27 13:23 ` [POLL] Use compat.el in Org? (was: Useful package? Compat.el) Ihor Radchenko
                     ` (2 preceding siblings ...)
  2023-01-30 11:35   ` Greg Minshall
@ 2023-04-01 10:31   ` Ihor Radchenko
  2023-04-01 11:38     ` Daniel Mendler
  2023-04-02 16:37     ` Max Nikulin
  3 siblings, 2 replies; 36+ messages in thread
From: Ihor Radchenko @ 2023-04-01 10:31 UTC (permalink / raw)
  To: Timothy; +Cc: Bastien, Kyle Meyer, Org-mode, Daniel Mendler

[-- Attachment #1: Type: text/plain, Size: 713 bytes --]

Ihor Radchenko <yantar92@posteo.net> writes:

> I have recently been contacted by the current compat.el maintainer
> asking if we are willing to adapt compat.el in Org.

See the attached patch set adding support of compat.el.

I had to update Org's build system to handle third-party packages.
Please, give it a close check (first patch).

The second patch adds the actual Elisp part and replaces some
compatibility functions with what is provided by compat.el.
Note that not all the functions are available in compat.el for now.
Daniel, you might want to take a look.

The last patch adds the necessary explanation for users who install Org
from git. Now, they must install compat.el manually to make Org work.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Upgrade-Org-build-system-to-handle-third-party-depen.patch --]
[-- Type: text/x-patch, Size: 6315 bytes --]

From f95433f53878e8371bb28a045fdb5d06cf0877b9 Mon Sep 17 00:00:00 2001
Message-Id: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680344979.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sat, 1 Apr 2023 12:00:48 +0200
Subject: [PATCH 1/3] Upgrade Org build system to handle third-party
 dependencies

* mk/default.mk (pkgdir): New variable holding the location of
third-party packages to be downloaded if necessary during compilation.
(EFLAGS): New variable holding extra flags to be passed to Emacs
executable when running make.
(EPACKAGES): List of packages to be installed (unless already present
in the `load-path') during compilation.
(package-install):
(INSTALL_PACKAGES): New command to download and install missing packages.
(BATCH): Update, setting default package location to pkgdir.
* mk/targets.mk (uppkg): New target to download install missing
packages.
(check test):
(compile compile-dirty): Use the new uppkg target.
(cleanpkg): New target cleaning up the downloaded packages.
(cleanall): Use the new target.
(.PHONY):
(CONF_BASE):
(CONF_DEST):
(CONF_CALL): Update according to the new variables and targets.
* .gitignore: Ignore the downloaded packages.

This commit paves the way towards third-party built-time dependencies
for Org.  In particular, towards including compat.el dependency.

According to EPACKAGES, we can auto-download necessary packages,
unless they are manually specified via -L switches in EFLAGS.

Link: https://orgmode.org/list/87v8ks6rhf.fsf@localhost
---
 .gitignore    |  1 +
 mk/default.mk | 24 +++++++++++++++++++++++-
 mk/targets.mk | 24 ++++++++++++++++--------
 3 files changed, 40 insertions(+), 9 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4bb81c359..a58670c90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,6 +71,7 @@ t
 auto
 tmp
 TODO
+/pkg-deps
 
 # and collateral damage from Emacs
 
diff --git a/mk/default.mk b/mk/default.mk
index fa46661e8..997b22b66 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -31,6 +31,15 @@ GIT_BRANCH =
 TMPDIR ?= /tmp
 testdir = $(TMPDIR)/tmp-orgtest
 
+# Where to store Org dependencies
+pkgdir = $(shell pwd)/pkg-deps
+
+# Extra flags to be passed to Emacs
+EFLAGS ?=
+
+# Third-party packages to install when running make
+EPACKAGES ?=
+
 # Configuration for testing
 # Verbose ERT summary by default for Emacs-28 and above.
 # To override:
@@ -72,12 +81,22 @@ REPRO_ARGS ?=
 req-ob-lang = --eval '(require '"'"'ob-$(ob-lang))'
 lst-ob-lang = ($(ob-lang) . t)
 req-extra   = --eval '(require '"'"'$(req))'
+package-install = --eval '(unless (require '"'"'$(package) nil t) (message "%s" load-path) (package-install '"'"'$(package)))'
 BTEST_RE   ?= \\(org\\|ob\\|ox\\)
 BTEST_LOAD  = \
 	--eval '(add-to-list '"'"'load-path (concat default-directory "lisp"))' \
 	--eval '(add-to-list '"'"'load-path (concat default-directory "testing"))'
 BTEST_INIT  = $(BTEST_PRE) $(BTEST_LOAD) $(BTEST_POST)
 
+ifeq (,$(EPACKAGES))
+INSTALL_PACKAGES =
+else
+INSTALL_PACKAGES = \
+	$(BATCH) \
+        --eval '(package-refresh-contents)' \
+        $(foreach package,$(EPACKAGES),$(package-install))
+endif
+
 BTEST = $(BATCH) $(BTEST_INIT) \
 	  -l org-batch-test-init \
 	  --eval '(setq \
@@ -120,7 +139,10 @@ EMACSQ  = $(EMACS)  -Q
 
 # Using emacs in batch mode.
 BATCH	= $(EMACSQ) -batch \
-	  --eval '(setq vc-handled-backends nil org-startup-folded nil org-element-cache-persistent nil)'
+	  $(EFLAGS) \
+	  --eval '(setq vc-handled-backends nil org-startup-folded nil org-element-cache-persistent nil)' \
+          --eval '(make-directory "$(pkgdir)" t)' \
+	  --eval '(setq package-user-dir "$(pkgdir)")' --eval '(package-initialize)'
 
 # Emacs must be started in toplevel directory
 BATCHO	= $(BATCH) \
diff --git a/mk/targets.mk b/mk/targets.mk
index 0bd293d68..ab8b830bb 100644
--- a/mk/targets.mk
+++ b/mk/targets.mk
@@ -27,21 +27,21 @@ ifneq ($(GITSTATUS),)
   GITVERSION := $(GITVERSION:.dirty=).dirty
 endif
 
-.PHONY:	all oldorg update update2 up0 up1 up2 single $(SUBDIRS) \
+.PHONY:	all oldorg update update2 up0 up1 up2 uppkg single $(SUBDIRS) \
 	check test install $(INSTSUB) \
 	info html pdf card refcard doc docs \
 	autoloads cleanall clean $(CLEANDIRS:%=clean%) \
 	clean-install cleanelc cleandirs \
-	cleanlisp cleandoc cleandocs cleantest \
+	cleanlisp cleandoc cleandocs cleantest cleanpkg \
 	compile compile-dirty uncompiled \
 	config config-test config-exe config-all config-eol config-version \
 	vanilla repro
 
-CONF_BASE = EMACS DESTDIR ORGCM ORG_MAKE_DOC
-CONF_DEST = lispdir infodir datadir testdir
+CONF_BASE = EMACS DESTDIR ORGCM ORG_MAKE_DOC EPACKAGES
+CONF_DEST = lispdir infodir datadir testdir pkgdir
 CONF_TEST = BTEST_PRE BTEST_POST BTEST_OB_LANGUAGES BTEST_EXTRA BTEST_RE
 CONF_EXEC = CP MKDIR RM RMR FIND CHMOD SUDO PDFTEX TEXI2PDF TEXI2HTML MAKEINFO INSTALL_INFO
-CONF_CALL = BATCH BATCHL ELC ELCDIR NOBATCH BTEST MAKE_LOCAL_MK MAKE_ORG_INSTALL MAKE_ORG_VERSION
+CONF_CALL = BATCH BATCHL ELC ELCDIR NOBATCH INSTALL_PACKAGES BTEST MAKE_LOCAL_MK MAKE_ORG_INSTALL MAKE_ORG_VERSION
 config-eol:: EOL = \#
 config-eol:: config-all
 config config-all::
@@ -86,7 +86,7 @@ local.mk:
 
 all compile::
 	$(foreach dir, doc lisp, $(MAKE) -C $(dir) clean;)
-compile compile-dirty::
+compile compile-dirty:: uppkg
 	$(MAKE) -C lisp $@
 all clean-install::
 	$(foreach dir, $(SUBDIRS), $(MAKE) -C $(dir) $@;)
@@ -94,7 +94,7 @@ all clean-install::
 vanilla:
 	-@$(NOBATCH) &
 
-check test::	compile
+check test::	uppkg compile
 check test test-dirty::
 	-$(MKDIR) $(testdir)
 	TMPDIR=$(testdir) $(BTEST)
@@ -102,6 +102,11 @@ ifeq ($(TEST_NO_AUTOCLEAN),) # define this variable to leave $(testdir) around f
 	$(MAKE) cleantest
 endif
 
+uppkg::
+	-$(MKDIR) -p $(pkgdir)
+	-$(FIND) $(pkgdir) \(  -name \*.elc \) -exec $(RM) {} +
+	$(INSTALL_PACKAGES)
+
 up0 up1 up2::
 	git checkout $(GIT_BRANCH)
 	git remote update
@@ -134,7 +139,7 @@ cleandirs:
 
 clean:	cleanlisp cleandoc
 
-cleanall: cleandirs cleantest
+cleanall: cleandirs cleantest cleanpkg
 	-$(FIND) . \( -name \*~ -o -name \*# -o -name .#\* \) -exec $(RM) {} +
 	-$(FIND) $(CLEANDIRS) \( -name \*~ -o -name \*.elc \) -exec $(RM) {} +
 
@@ -159,3 +164,6 @@ cleantest:
 	  $(FIND) $(testdir) -type d -exec $(CHMOD) u+w {} + && \
 	  $(RMR) $(testdir) ; \
 	}
+
+cleanpkg:
+	-$(RMR) $(pkgdir)
-- 
2.39.1


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-Use-compat.el-library-instead-of-ad-hoc-compatibilit.patch --]
[-- Type: text/x-patch, Size: 34712 bytes --]

From 6c4c71426c15b6307cecc1ebffc75dbd6d5135b4 Mon Sep 17 00:00:00 2001
Message-Id: <6c4c71426c15b6307cecc1ebffc75dbd6d5135b4.1680344979.git.yantar92@posteo.net>
In-Reply-To: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680344979.git.yantar92@posteo.net>
References: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680344979.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sat, 1 Apr 2023 12:10:13 +0200
Subject: [PATCH 2/3] Use compat.el library instead of ad-hoc compatibility
 function set

* mk/default.mk (EPACKAGES): Demand compat library during compile time.
* lisp/org.el: Add compat dependency.
(org-fill-paragraph):
* lisp/org-compat.el: Implement compat.el compatibility layer.
Obsolete org-* compatibility functions that are already available in
compat.el: `org-file-has-changed-p', `org-string-equal-ignore-case',
`org-file-name-concat', `org-directory-empty-p',
`org-string-clean-whitespace', `org-format-prompt', `org-xor',
`org-string-distance', `org-buffer-hash'.
* lisp/ob-core.el (org-babel-results-keyword): Use functions provided
by compat.el.
(org-babel-insert-result):
* lisp/oc-basic.el (org-cite-basic--parse-bibliography):
* lisp/oc.el (org-cite-adjust-note):
* lisp/ol-gnus.el (org-gnus-group-link):
(org-gnus-article-link):
(org-gnus-store-link):
* lisp/ol.el:
(org-store-link):
* lisp/org-attach.el:
* lisp/org-capture.el:
(org-capture-fill-template):
* lisp/org-fold-core.el (org-fold-core-next-visibility-change):
* lisp/org-lint.el:
* lisp/org-persist.el (org-persist-directory):
(org-persist-read:file):
(org-persist-read:url):
(org-persist--load-index):
(org-persist-write:file):
(org-persist-write:index):
(org-persist--merge-index-with-disk):
(org-persist-read):
(org-persist-write):
(org-persist-write-all):
(org-persist--gc-persist-file):
(org-persist-gc):
* lisp/org-refile.el (org-refile-get-location):
* lisp/ox.el (org-export-resolve-radio-link):
* testing/lisp/test-ol.el (test-org-link/toggle-link-display):
* testing/lisp/test-org-capture.el (test-org-capture/abort):

Link: https://orgmode.org/list/87v8ks6rhf.fsf@localhost
---
 lisp/ob-core.el                  |   8 +-
 lisp/oc-basic.el                 |   4 +-
 lisp/oc.el                       |   2 +-
 lisp/ol-gnus.el                  |   8 +-
 lisp/ol.el                       |   4 +-
 lisp/org-attach.el               |   2 +-
 lisp/org-capture.el              |   8 +-
 lisp/org-compat.el               | 220 +++++++++----------------------
 lisp/org-fold-core.el            |   2 +-
 lisp/org-lint.el                 |   2 +-
 lisp/org-persist.el              |  38 +++---
 lisp/org-refile.el               |   2 +-
 lisp/org.el                      |   6 +-
 lisp/ox.el                       |   6 +-
 mk/default.mk                    |   2 +-
 testing/lisp/test-ol.el          |  10 +-
 testing/lisp/test-org-capture.el |   2 +-
 17 files changed, 115 insertions(+), 211 deletions(-)

diff --git a/lisp/ob-core.el b/lisp/ob-core.el
index 471887a3a..65d9fdc72 100644
--- a/lisp/ob-core.el
+++ b/lisp/ob-core.el
@@ -145,7 +145,7 @@ (defcustom org-babel-results-keyword "RESULTS"
   :type 'string
   :safe (lambda (v)
 	  (and (stringp v)
-	       (org-string-equal-ignore-case "RESULTS" v))))
+	       (string-equal-ignore-case "RESULTS" v))))
 
 (defcustom org-babel-noweb-wrap-start "<<"
   "String used to begin a noweb reference in a code block.
@@ -2518,7 +2518,7 @@ (defun org-babel-insert-result (result &optional result-params info hash lang ex
 		       ;; Escape contents from "export" wrap.  Wrap
 		       ;; inline results within an export snippet with
 		       ;; appropriate value.
-		       ((org-string-equal-ignore-case type "export")
+		       ((string-equal-ignore-case type "export")
 			(let ((backend (pcase split
 					 (`(,_) "none")
 					 (`(,_ ,b . ,_) b))))
@@ -2529,14 +2529,14 @@ (defun org-babel-insert-result (result &optional result-params info hash lang ex
 					   backend) "@@)}}}")))
 		       ;; Escape contents from "example" wrap.  Mark
 		       ;; inline results as verbatim.
-		       ((org-string-equal-ignore-case type "example")
+		       ((string-equal-ignore-case type "example")
 			(funcall wrap
 				 opening-line closing-line
 				 nil nil
 				 "{{{results(=" "=)}}}"))
 		       ;; Escape contents from "src" wrap.  Mark
 		       ;; inline results as inline source code.
-		       ((org-string-equal-ignore-case type "src")
+		       ((string-equal-ignore-case type "src")
 			(let ((inline-open
 			       (pcase split
 				 (`(,_)
diff --git a/lisp/oc-basic.el b/lisp/oc-basic.el
index 12b627e71..eee354f26 100644
--- a/lisp/oc-basic.el
+++ b/lisp/oc-basic.el
@@ -274,11 +274,11 @@ (defun org-cite-basic--parse-bibliography (&optional info)
       (dolist (file (org-cite-list-bibliography-files))
         (when (file-readable-p file)
           (with-temp-buffer
-            (when (or (org-file-has-changed-p file)
+            (when (or (file-has-changed-p file)
                       (not (gethash file org-cite-basic--file-id-cache)))
               (insert-file-contents file)
               (set-visited-file-name file t)
-              (puthash file (org-buffer-hash) org-cite-basic--file-id-cache))
+              (puthash file (buffer-hash) org-cite-basic--file-id-cache))
             (condition-case nil
                 (unwind-protect
 	            (let* ((file-id (cons file (gethash file org-cite-basic--file-id-cache)))
diff --git a/lisp/oc.el b/lisp/oc.el
index dde6f3a32..f39ae848b 100644
--- a/lisp/oc.el
+++ b/lisp/oc.el
@@ -1029,7 +1029,7 @@ (defun org-cite-adjust-note (citation info &optional rule punct)
                              (match-string 3 previous)))))
       ;; Bail you when there is no quote and either no punctuation, or
       ;; punctuation on both sides.
-      (when (or quote (org-xor punct final-punct))
+      (when (or quote (xor punct final-punct))
         ;; Phase 1: handle punctuation rule.
         (pcase rule
           ((guard (not quote)) nil)
diff --git a/lisp/ol-gnus.el b/lisp/ol-gnus.el
index 7c07ce045..e121cfba3 100644
--- a/lisp/ol-gnus.el
+++ b/lisp/ol-gnus.el
@@ -98,8 +98,8 @@ (defun org-gnus-group-link (group)
 `org-gnus-prefer-web-links' is reversed."
   (let ((unprefixed-group (replace-regexp-in-string "^[^:]+:" "" group)))
     (if (and (string-prefix-p "nntp" group) ;; Only for nntp groups
-	     (org-xor current-prefix-arg
-		      org-gnus-prefer-web-links))
+	     (xor current-prefix-arg
+		  org-gnus-prefer-web-links))
 	(concat "https://groups.google.com/group/" unprefixed-group)
       (concat "gnus:" group))))
 
@@ -116,7 +116,7 @@ (defun org-gnus-article-link (group newsgroups message-id x-no-archive)
 
 If `org-store-link' was called with a prefix arg the meaning of
 `org-gnus-prefer-web-links' is reversed."
-  (if (and (org-xor current-prefix-arg org-gnus-prefer-web-links)
+  (if (and (xor current-prefix-arg org-gnus-prefer-web-links)
 	   newsgroups		  ;make web links only for nntp groups
 	   (not x-no-archive))	  ;and if X-No-Archive isn't set
       (format "https://groups.google.com/groups/search?as_umsgid=%s"
@@ -169,7 +169,7 @@ (defun org-gnus-store-link ()
 	    newsgroups x-no-archive)
        ;; Fetching an article is an expensive operation; newsgroup and
        ;; x-no-archive are only needed for web links.
-       (when (org-xor current-prefix-arg org-gnus-prefer-web-links)
+       (when (xor current-prefix-arg org-gnus-prefer-web-links)
 	 ;; Make sure the original article buffer is up-to-date.
 	 (save-window-excursion (gnus-summary-select-article))
 	 (setq to (or to (gnus-fetch-original-field "To")))
diff --git a/lisp/ol.el b/lisp/ol.el
index 9e4781f6e..3d62a6fa0 100644
--- a/lisp/ol.el
+++ b/lisp/ol.el
@@ -1692,7 +1692,7 @@ (defun org-store-link (arg &optional interactive?)
 				(abbreviate-file-name
 				 (buffer-file-name (buffer-base-buffer)))))
 	   ;; Add a context search string.
-	   (when (org-xor org-link-context-for-files (equal arg '(4)))
+	   (when (xor org-link-context-for-files (equal arg '(4)))
 	     (let* ((element (org-element-at-point))
 		    (name (org-element-property :name element))
 		    (context
@@ -1724,7 +1724,7 @@ (defun org-store-link (arg &optional interactive?)
 			     (abbreviate-file-name
 			      (buffer-file-name (buffer-base-buffer)))))
 	;; Add a context search string.
-	(when (org-xor org-link-context-for-files (equal arg '(4)))
+	(when (xor org-link-context-for-files (equal arg '(4)))
 	  (let ((context (org-link--normalize-string
 			  (or (org-link--context-from-region)
 			      (org-current-line-string))
diff --git a/lisp/org-attach.el b/lisp/org-attach.el
index 8d01eda71..5cc40873c 100644
--- a/lisp/org-attach.el
+++ b/lisp/org-attach.el
@@ -674,7 +674,7 @@ (defun org-attach-sync ()
       (let ((files (org-attach-file-list attach-dir)))
 	(org-attach-tag (not files)))
       (when org-attach-sync-delete-empty-dir
-        (when (and (org-directory-empty-p attach-dir)
+        (when (and (directory-empty-p attach-dir)
                    (if (eq 'query org-attach-sync-delete-empty-dir)
                        (yes-or-no-p "Attachment directory is empty.  Delete?")
                      t))
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index b96e9f336..c5b1c6d50 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -1323,9 +1323,9 @@ (defun org-capture-place-item ()
 	      ;; prioritize the existing list.
 	      (when prepend?
 		(let ((ordered? (eq 'ordered (org-element-property :type item))))
-		  (when (org-xor ordered?
-				 (string-match-p "\\`[A-Za-z0-9]\\([.)]\\)"
-						 template))
+		  (when (xor ordered?
+			     (string-match-p "\\`[A-Za-z0-9]\\([.)]\\)"
+					     template))
 		    (org-cycle-list-bullet (if ordered? "1." "-")))))
 	      ;; Eventually repair the list for proper indentation and
 	      ;; bullets.
@@ -1867,7 +1867,7 @@ (defun org-capture-fill-template (&optional template initial annotation)
 		     (setq org-capture--prompt-history
 			   (gethash prompt org-capture--prompt-history-table))
                      (push (org-completing-read
-                            (org-format-prompt (or prompt "Enter string") default)
+                            (format-prompt (or prompt "Enter string") default)
 			    completions
 			    nil nil nil 'org-capture--prompt-history default)
 			   strings)
diff --git a/lisp/org-compat.el b/lisp/org-compat.el
index c47a4e8c2..046a3db97 100644
--- a/lisp/org-compat.el
+++ b/lisp/org-compat.el
@@ -79,9 +79,6 @@ (declare-function org-fold-region "org-fold" (from to flag &optional spec))
 (declare-function org-fold-show-all "org-fold" (&optional types))
 (declare-function org-fold-show-children "org-fold" (&optional level))
 (declare-function org-fold-show-entry "org-fold" (&optional hide-drawers))
-;; `org-string-equal-ignore-case' is in _this_ file but isn't at the
-;; top-level.
-(declare-function org-string-equal-ignore-case "org-compat" (string1 string2))
 
 (defvar calendar-mode-map)
 (defvar org-complex-heading-regexp)
@@ -95,104 +92,67 @@ (defvar org-table1-hline-regexp)
 (defvar org-fold-core-style)
 
 \f
-;;; Emacs < 29 compatibility
-
-(defvar org-file-has-changed-p--hash-table (make-hash-table :test #'equal)
-  "Internal variable used by `org-file-has-changed-p'.")
-
-(if (fboundp 'file-has-changed-p)
-    (defalias 'org-file-has-changed-p #'file-has-changed-p)
-  (defun org-file-has-changed-p (file &optional tag)
-    "Return non-nil if FILE has changed.
-The size and modification time of FILE are compared to the size
-and modification time of the same FILE during a previous
-invocation of `org-file-has-changed-p'.  Thus, the first invocation
-of `org-file-has-changed-p' always returns non-nil when FILE exists.
-The optional argument TAG, which must be a symbol, can be used to
-limit the comparison to invocations with identical tags; it can be
-the symbol of the calling function, for example."
-    (let* ((file (directory-file-name (expand-file-name file)))
-           (remote-file-name-inhibit-cache t)
-           (fileattr (file-attributes file 'integer))
-	   (attr (and fileattr
-                      (cons (file-attribute-size fileattr)
-		            (file-attribute-modification-time fileattr))))
-	   (sym (concat (symbol-name tag) "@" file))
-	   (cachedattr (gethash sym org-file-has-changed-p--hash-table)))
-      (when (not (equal attr cachedattr))
-        (puthash sym attr org-file-has-changed-p--hash-table)))))
-
-(if (fboundp 'string-equal-ignore-case)
-    (defalias 'org-string-equal-ignore-case #'string-equal-ignore-case)
-  ;; From Emacs subr.el.
-  (defun org-string-equal-ignore-case (string1 string2)
-    "Like `string-equal', but case-insensitive.
-Upper-case and lower-case letters are treated as equal.
-Unibyte strings are converted to multibyte for comparison."
-    (eq t (compare-strings string1 0 nil string2 0 nil t))))
-
-\f
-;;; Emacs < 28.1 compatibility
-
-(if (fboundp 'file-name-concat)
-    (defalias 'org-file-name-concat #'file-name-concat)
-  (defun org-file-name-concat (directory &rest components)
-    "Append COMPONENTS to DIRECTORY and return the resulting string.
-
-Elements in COMPONENTS must be a string or nil.
-DIRECTORY or the non-final elements in COMPONENTS may or may not end
-with a slash -- if they don't end with a slash, a slash will be
-inserted before contatenating."
-    (save-match-data
-      (mapconcat
-       #'identity
-       (delq nil
-             (mapcar
-              (lambda (str)
-                (when (and str (not (seq-empty-p str))
-                           (string-match "\\(.+\\)/?" str))
-                  (match-string 1 str)))
-              (cons directory components)))
-       "/"))))
-
-(if (fboundp 'directory-empty-p)
-    (defalias 'org-directory-empty-p #'directory-empty-p)
-  (defun org-directory-empty-p (dir)
-    "Return t if DIR names an existing directory containing no other files."
-    (and (file-directory-p dir)
-         (null (directory-files dir nil directory-files-no-dot-files-regexp t)))))
-
-(if (fboundp 'string-clean-whitespace)
-    (defalias 'org-string-clean-whitespace #'string-clean-whitespace)
-  ;; From Emacs subr-x.el.
-  (defun org-string-clean-whitespace (string)
-    "Clean up whitespace in STRING.
-All sequences of whitespaces in STRING are collapsed into a
-single space character, and leading/trailing whitespace is
-removed."
-    (let ((blank "[[:blank:]\r\n]+"))
-      (string-trim (replace-regexp-in-string blank " " string t t)
-                   blank blank))))
-
-(if (fboundp 'format-prompt)
-    (defalias 'org-format-prompt #'format-prompt)
-  ;; From Emacs minibuffer.el, inlining
-  ;; `minibuffer-default-prompt-format' value and replacing `length<'
-  ;; (both new in Emacs 28.1).
-  (defun org-format-prompt (prompt default &rest format-args)
-    "Compatibility substitute for `format-prompt'."
-    (concat
-     (if (null format-args)
-         prompt
-       (apply #'format prompt format-args))
-     (and default
-          (or (not (stringp default))
-              (> (length default) 0))
-          (format " (default %s)"
-                  (if (consp default)
-                      (car default)
-                    default)))
-     ": ")))
+;;; compat.el
+
+;; Do not throw an error when not available - assume latest Emacs
+;; version (built-in Org).
+(require 'compat nil 'noerror)
+
+;; Provide compatibility macros when we are a part of Emacs.
+;; See https://elpa.gnu.org/packages/doc/compat.html#Usage
+
+(defmacro org-compat-function (fun)
+  "Return compatibility function symbol for FUN.
+
+If the Emacs version provides a sufficiently recent version of
+FUN, the symbol FUN is returned itself.  Otherwise the macro
+returns the symbol of a compatibility function which supports the
+behavior and calling convention of the current stable Emacs
+version.  For example Compat 29.1 will provide compatibility
+functions which implement the behavior and calling convention of
+Emacs 29.1.
+
+See also `org-compat-call' to directly call compatibility functions."
+  (let ((compat (intern (format "compat--%s" fun))))
+    `#',(if (fboundp compat) compat fun)))
+
+(defmacro org-compat-call (fun &rest args)
+  "Call compatibility function or macro FUN with ARGS.
+
+A good example function is `plist-get' which was extended with an
+additional predicate argument in Emacs 29.1.  The compatibility
+function, which supports this additional argument, can be
+obtained via (compat-function plist-get) and called
+via (compat-call plist-get plist prop predicate).  It is not
+possible to directly call (plist-get plist prop predicate) on
+Emacs older than 29.1, since the original `plist-get' function
+does not yet support the predicate argument.  Note that the
+Compat library never overrides existing functions.
+
+See also `org-compat-function' to lookup compatibility functions."
+  (let ((compat (intern (format "compat--%s" fun))))
+    `(,(if (fboundp compat) compat fun) ,@args)))
+
+;; Obsolete compatibility wrappers used before inclusion of compat.el.
+
+(define-obsolete-function-alias 'org-file-has-changed-p
+  'file-has-changed-p "9.7")
+(define-obsolete-function-alias 'org-string-equal-ignore-case
+  'string-equal-ignore-case "9.7")
+(define-obsolete-function-alias 'org-file-name-concat
+  'file-name-concat "9.7")
+(define-obsolete-function-alias 'org-directory-empty-p
+  'directory-empty-p "9.7")
+(define-obsolete-function-alias 'org-string-clean-whitespace
+  'string-clean-whitespace "9.7")
+(define-obsolete-function-alias 'org-format-prompt
+  'format-prompt "9.7")
+(define-obsolete-function-alias 'org-xor
+  'xor "9.7")
+(define-obsolete-function-alias 'org-string-distance
+  'string-distance "9.7")
+(define-obsolete-function-alias 'org-buffer-hash
+  'buffer-hash "9.7")
 
 \f
 ;;; Emacs < 27.1 compatibility
@@ -210,22 +170,6 @@ (if (version< emacs-version "27.1")
       (replace-buffer-contents source))
   (defalias 'org-replace-buffer-contents #'replace-buffer-contents))
 
-(unless (fboundp 'proper-list-p)
-  ;; `proper-list-p' was added in Emacs 27.1.  The function below is
-  ;; taken from Emacs subr.el 200195e824b^.
-  (defun proper-list-p (object)
-    "Return OBJECT's length if it is a proper list, nil otherwise.
-A proper list is neither circular nor dotted (i.e., its last cdr
-is nil)."
-    (and (listp object) (ignore-errors (length object)))))
-
-(if (fboundp 'xor)
-    ;; `xor' was added in Emacs 27.1.
-    (defalias 'org-xor #'xor)
-  (defsubst org-xor (a b)
-    "Exclusive `or'."
-    (if a (not b) b)))
-
 (unless (fboundp 'pcomplete-uniquify-list)
   ;; The misspelled variant was made obsolete in Emacs 27.1
   (defalias 'pcomplete-uniquify-list 'pcomplete-uniqify-list))
@@ -255,29 +199,6 @@ (defun org--set-faces-extend (faces extend-p)
   (when (fboundp 'set-face-extend)
     (mapc (lambda (f) (set-face-extend f extend-p)) faces)))
 
-(if (fboundp 'string-distance)
-    (defalias 'org-string-distance 'string-distance)
-  (defun org-string-distance (s1 s2)
-    "Return the edit (levenshtein) distance between strings S1 S2."
-    (let* ((l1 (length s1))
-	   (l2 (length s2))
-	   (dist (vconcat (mapcar (lambda (_) (make-vector (1+ l2) nil))
-				  (number-sequence 1 (1+ l1)))))
-	   (in (lambda (i j) (aref (aref dist i) j))))
-      (setf (aref (aref dist 0) 0) 0)
-      (dolist (j (number-sequence 1 l2))
-        (setf (aref (aref dist 0) j) j))
-      (dolist (i (number-sequence 1 l1))
-        (setf (aref (aref dist i) 0) i)
-        (dolist (j (number-sequence 1 l2))
-	  (setf (aref (aref dist i) j)
-	        (min
-	         (1+ (funcall in (1- i) j))
-	         (1+ (funcall in i (1- j)))
-	         (+ (if (equal (aref s1 (1- i)) (aref s2 (1- j))) 0 1)
-		    (funcall in (1- i) (1- j)))))))
-      (funcall in l1 l2))))
-
 (define-obsolete-function-alias 'org-babel-edit-distance 'org-string-distance
   "9.5")
 
@@ -298,23 +219,6 @@ (if (fboundp 'line-number-display-width)
     (defalias 'org-line-number-display-width 'line-number-display-width)
   (defun org-line-number-display-width (&rest _) 0))
 
-(if (fboundp 'buffer-hash)
-    (defalias 'org-buffer-hash 'buffer-hash)
-  (defun org-buffer-hash () (md5 (current-buffer))))
-
-(unless (fboundp 'file-attribute-modification-time)
-  (defsubst file-attribute-modification-time (attributes)
-    "The modification time in ATTRIBUTES returned by `file-attributes'.
-This is the time of the last change to the file's contents, and
-is a Lisp timestamp in the same style as `current-time'."
-    (nth 5 attributes)))
-
-(unless (fboundp 'file-attribute-size)
-  (defsubst file-attribute-size (attributes)
-    "The size (in bytes) in ATTRIBUTES returned by `file-attributes'.
-This is a floating point number if the size is too large for an integer."
-    (nth 7 attributes)))
-
 \f
 ;;; Obsolete aliases (remove them after the next major release).
 
@@ -1416,7 +1320,7 @@ (defun org-mode-flyspell-verify ()
 	  (and log
 	       (let ((drawer (org-element-lineage element '(drawer))))
 		 (and drawer
-		      (org-string-equal-ignore-case
+		      (string-equal-ignore-case
 		       log (org-element-property :drawer-name drawer))))))
 	nil)
        (t
diff --git a/lisp/org-fold-core.el b/lisp/org-fold-core.el
index 43c6b2b74..c699c115b 100644
--- a/lisp/org-fold-core.el
+++ b/lisp/org-fold-core.el
@@ -841,7 +841,7 @@ (defun org-fold-core-next-visibility-change (&optional pos limit ignore-hidden-p
 			  (lambda (p) (next-single-char-property-change p 'invisible nil limit)))))
 	 (next pos))
     (while (and (funcall cmp next limit)
-		(not (org-xor
+		(not (xor
                     invisible-initially?
                     (funcall invisible-p
                              (if previous-p
diff --git a/lisp/org-lint.el b/lisp/org-lint.el
index 0e2967b6c..adc893aff 100644
--- a/lisp/org-lint.el
+++ b/lisp/org-lint.el
@@ -383,7 +383,7 @@ (defun org-lint-duplicate-custom-id (ast)
    ast
    'node-property
    (lambda (property)
-     (and (org-string-equal-ignore-case
+     (and (string-equal-ignore-case
            "CUSTOM_ID" (org-element-property :key property))
 	  (org-element-property :value property)))
    (lambda (property _) (org-element-property :begin property))
diff --git a/lisp/org-persist.el b/lisp/org-persist.el
index 8e73fbc4b..21c09567f 100644
--- a/lisp/org-persist.el
+++ b/lisp/org-persist.el
@@ -279,12 +279,12 @@ (defgroup org-persist nil
 
 (defcustom org-persist-directory
   (expand-file-name
-   (org-file-name-concat
+   (file-name-concat
     (let ((cache-dir (when (fboundp 'xdg-cache-home)
                        (xdg-cache-home))))
       (if (or (seq-empty-p cache-dir)
               (not (file-exists-p cache-dir))
-              (file-exists-p (org-file-name-concat
+              (file-exists-p (file-name-concat
                               user-emacs-directory
                               "org-persist")))
           user-emacs-directory
@@ -675,13 +675,13 @@ (defalias 'org-persist-read:version #'org-persist-read:elisp-data)
 
 (defun org-persist-read:file (_ path __)
   "Read file container from PATH."
-  (when (and path (file-exists-p (org-file-name-concat org-persist-directory path)))
-    (org-file-name-concat org-persist-directory path)))
+  (when (and path (file-exists-p (file-name-concat org-persist-directory path)))
+    (file-name-concat org-persist-directory path)))
 
 (defun org-persist-read:url (_ path __)
   "Read file container from PATH."
-  (when (and path (file-exists-p (org-file-name-concat org-persist-directory path)))
-    (org-file-name-concat org-persist-directory path)))
+  (when (and path (file-exists-p (file-name-concat org-persist-directory path)))
+    (file-name-concat org-persist-directory path)))
 
 (defun org-persist-read:index (cont index-file _)
   "Read index container CONT from INDEX-FILE."
@@ -750,7 +750,7 @@ (defun org-persist--load-index ()
   "Load `org-persist--index'."
   (org-persist-load:index
    `(index ,org-persist--storage-version)
-   (org-file-name-concat org-persist-directory org-persist-index-file)
+   (file-name-concat org-persist-directory org-persist-index-file)
    nil))
 
 ;;;; Writing container data
@@ -804,7 +804,7 @@ (defun org-persist-write:file (c collection)
         (setq path (cadr c)))
       (let* ((persist-file (plist-get collection :persist-file))
              (ext (file-name-extension path))
-             (file-copy (org-file-name-concat
+             (file-copy (file-name-concat
                          org-persist-directory
                          (format "%s-%s.%s" persist-file (md5 path) ext))))
         (unless (file-exists-p file-copy)
@@ -850,7 +850,7 @@ (defun org-persist-write:index (container _)
                  org-persist-directory))))
   (when (file-exists-p org-persist-directory)
     (let ((index-file
-           (org-file-name-concat org-persist-directory org-persist-index-file)))
+           (file-name-concat org-persist-directory org-persist-index-file)))
       (org-persist--merge-index-with-disk)
       (org-persist--write-elisp-file index-file org-persist--index t t)
       (setq org-persist--index-age
@@ -865,7 +865,7 @@ (defun org-persist--save-index ()
 (defun org-persist--merge-index-with-disk ()
   "Merge `org-persist--index' with the current index file on disk."
   (let* ((index-file
-          (org-file-name-concat org-persist-directory org-persist-index-file))
+          (file-name-concat org-persist-directory org-persist-index-file))
          (disk-index
           (and (file-exists-p index-file)
                (org-file-newer-than-p index-file org-persist--index-age)
@@ -887,8 +887,8 @@ (defun org-persist--merge-index (base other)
         (dolist (item (nreverse new))
           (unless (or (memq 'index (mapcar #'car (plist-get item :container)))
                       (not (file-exists-p
-                            (org-file-name-concat org-persist-directory
-                                                  (plist-get item :persist-file))))
+                          (file-name-concat org-persist-directory
+                                            (plist-get item :persist-file))))
                       (member (plist-get item :persist-file) base-files))
             (push item combined)))
         (nreverse combined))
@@ -989,7 +989,7 @@ (cl-defun org-persist-read (container &optional associated hash-must-match load
   (let* ((collection (org-persist--find-index `(:container ,container :associated ,associated)))
          (persist-file
           (when collection
-            (org-file-name-concat
+            (file-name-concat
              org-persist-directory
              (plist-get collection :persist-file))))
          (data nil))
@@ -1077,7 +1077,7 @@ (defun org-persist-write (container &optional associated ignore-return)
                          (run-hook-with-args-until-success 'org-persist-before-write-hook v associated))
                        (plist-get collection :container)))
       (when (or (file-exists-p org-persist-directory) (org-persist--save-index))
-        (let ((file (org-file-name-concat org-persist-directory (plist-get collection :persist-file)))
+        (let ((file (file-name-concat org-persist-directory (plist-get collection :persist-file)))
               (data (mapcar (lambda (c) (cons c (org-persist-write:generic c collection)))
                             (plist-get collection :container))))
           (puthash file data org-persist--write-cache)
@@ -1097,11 +1097,11 @@ (defun org-persist-write-all (&optional associated)
            ;; The container is an `index' container.
            (eq 'index (caar (plist-get (car org-persist--index) :container)))
            (or (not (file-exists-p org-persist-directory))
-               (org-directory-empty-p org-persist-directory)))
+               (directory-empty-p org-persist-directory)))
       ;; Do not write anything, and clear up `org-persist-directory' to reduce
       ;; clutter.
       (when (and (file-exists-p org-persist-directory)
-                 (org-directory-empty-p org-persist-directory))
+                 (directory-empty-p org-persist-directory))
         (delete-directory org-persist-directory))
     ;; Write the data.
     (let (all-containers)
@@ -1142,7 +1142,7 @@ (defun org-persist--gc-persist-file (persist-file)
   "Garbage collect PERSIST-FILE."
   (when (file-exists-p persist-file)
     (delete-file persist-file)
-    (when (org-directory-empty-p (file-name-directory persist-file))
+    (when (directory-empty-p (file-name-directory persist-file))
       (delete-directory (file-name-directory persist-file)))))
 
 (defmacro org-persist-associated-files:generic (container collection)
@@ -1180,7 +1180,7 @@ (defun org-persist-gc ()
   (let (new-index
         (remote-files-num 0)
         (orphan-files
-         (delete (org-file-name-concat org-persist-directory org-persist-index-file)
+         (delete (file-name-concat org-persist-directory org-persist-index-file)
                  (when (file-exists-p org-persist-directory)
                    (directory-files-recursively org-persist-directory ".+")))))
     (dolist (collection org-persist--index)
@@ -1188,7 +1188,7 @@ (defun org-persist-gc ()
              (web-file (and file (string-match-p "\\`https?://" file)))
              (file-remote (when file (file-remote-p file)))
              (persist-file (when (plist-get collection :persist-file)
-                             (org-file-name-concat
+                             (file-name-concat
                               org-persist-directory
                               (plist-get collection :persist-file))))
              (expired? (org-persist--gc-expired-p
diff --git a/lisp/org-refile.el b/lisp/org-refile.el
index 03c351cf6..9797a0633 100644
--- a/lisp/org-refile.el
+++ b/lisp/org-refile.el
@@ -666,7 +666,7 @@ (defun org-refile-get-location (&optional prompt default-buffer new-nodes)
          (prompt (let ((default (or (car org-refile-history)
                                     (and (assoc cbnex tbl) (setq cdef cbnex)
                                          cbnex))))
-                   (org-format-prompt prompt default)))
+                   (format-prompt prompt default)))
 	 pa answ parent-target child parent old-hist)
     (setq old-hist org-refile-history)
     (setq answ (funcall cfunc prompt tbl nil (not new-nodes)
diff --git a/lisp/org.el b/lisp/org.el
index 10ade32dd..e9fa9a241 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -7,7 +7,7 @@ ;;; org.el --- Outline-based notes management and organizer -*- lexical-binding:
 ;; Maintainer: Bastien Guerry <bzg@gnu.org>
 ;; Keywords: outlines, hypermedia, calendar, wp
 ;; URL: https://orgmode.org
-;; Package-Requires: ((emacs "26.1"))
+;; Package-Requires: ((emacs "26.1") (compat "29.1.4.1"))
 
 ;; Version: 9.7-pre
 
@@ -19445,7 +19445,7 @@ (defun org-fill-paragraph (&optional justify region)
 		 (barf-if-buffer-read-only)
 		 (list (when current-prefix-arg 'full) t)))
   (let ((hash (and (not (buffer-modified-p))
-		   (org-buffer-hash))))
+		   (buffer-hash))))
     (cond
      ((and region transient-mark-mode mark-active
 	   (not (eq (region-beginning) (region-end))))
@@ -19470,7 +19470,7 @@ (defun org-fill-paragraph (&optional justify region)
     ;; If we didn't change anything in the buffer (and the buffer was
     ;; previously unmodified), then flip the modification status back
     ;; to "unchanged".
-    (when (and hash (equal hash (org-buffer-hash)))
+    (when (and hash (equal hash (buffer-hash)))
       (set-buffer-modified-p nil))
     ;; Return non-nil.
     t))
diff --git a/lisp/ox.el b/lisp/ox.el
index a6169ea63..203136994 100644
--- a/lisp/ox.el
+++ b/lisp/ox.el
@@ -4626,11 +4626,11 @@ (defun org-export-resolve-radio-link (link info)
 
 Return value can be a radio-target object or nil.  Assume LINK
 has type \"radio\"."
-  (let ((path (org-string-clean-whitespace (org-element-property :path link))))
+  (let ((path (string-clean-whitespace (org-element-property :path link))))
     (org-element-map (plist-get info :parse-tree) 'radio-target
       (lambda (radio)
-	(and (org-string-equal-ignore-case
-	      (org-string-clean-whitespace (org-element-property :value radio))
+	(and (string-equal-ignore-case
+	      (string-clean-whitespace (org-element-property :value radio))
               path)
 	     radio))
       info 'first-match)))
diff --git a/mk/default.mk b/mk/default.mk
index 997b22b66..4ad1a4281 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -38,7 +38,7 @@ pkgdir = $(shell pwd)/pkg-deps
 EFLAGS ?=
 
 # Third-party packages to install when running make
-EPACKAGES ?=
+EPACKAGES ?= compat
 
 # Configuration for testing
 # Verbose ERT summary by default for Emacs-28 and above.
diff --git a/testing/lisp/test-ol.el b/testing/lisp/test-ol.el
index a38d9f979..565539571 100644
--- a/testing/lisp/test-ol.el
+++ b/testing/lisp/test-ol.el
@@ -63,19 +63,19 @@ (ert-deftest test-org-link/toggle-link-display ()
       (dotimes (_ 2)
         (goto-char 1)
         (re-search-forward "\\[")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "example")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "com")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "]")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "\\[")
         (should-not (org-invisible-p))
         (re-search-forward "link")
         (should-not (org-invisible-p))
         (re-search-forward "]")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (org-toggle-link-display)))))
 
 \f
diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el
index 0ed44c6af..6a47b3384 100644
--- a/testing/lisp/test-org-capture.el
+++ b/testing/lisp/test-org-capture.el
@@ -154,7 +154,7 @@ (ert-deftest test-org-capture/abort ()
   "Test aborting a capture process."
   ;; Newly create capture buffer should not be saved.
   (let ((capture-file (make-temp-name
-                       (org-file-name-concat
+                       (file-name-concat
                         temporary-file-directory
                         "org-test"))))
     (unwind-protect
-- 
2.39.1


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #4: 0003-org-manual.org-Document-compat-library-installation.patch --]
[-- Type: text/x-patch, Size: 2242 bytes --]

From cbc781d02021563b2f68b2179813e936ab379446 Mon Sep 17 00:00:00 2001
Message-Id: <cbc781d02021563b2f68b2179813e936ab379446.1680344979.git.yantar92@posteo.net>
In-Reply-To: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680344979.git.yantar92@posteo.net>
References: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680344979.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sat, 1 Apr 2023 12:18:57 +0200
Subject: [PATCH 3/3] org-manual.org: Document compat library installation

* doc/org-manual.org (Using Org's git repository): Document that users
must also install compat library when using git version of Org.
* etc/ORG-NEWS (Org mode now uses =compat.el= third-party package to
support older Emacs versions): Announce amendments to the Org
installation from git sources.
---
 doc/org-manual.org | 4 ++++
 etc/ORG-NEWS       | 7 +++++++
 2 files changed, 11 insertions(+)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 50662669e..aa9886e5f 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -140,6 +140,10 @@ *** Using Org's git repository
 (add-to-list 'load-path "~/src/org-mode/lisp")
 #+end_src
 
+You must also manually install =compat= library required by Org mode.
+Using built-in =package.el=, you can run =M-x package-install <RET>
+compat <RET>=.
+
 You can also compile with =make=, generate the documentation with
 =make doc=, create a local configuration with =make config= and
 install Org with =make install=.  Please run =make help= to get the
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index ac233a986..0302b8cda 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -13,6 +13,13 @@ Please send Org bug reports to mailto:emacs-orgmode@gnu.org.
 
 * Version 9.7 (not released yet)
 ** Important announcements and breaking changes
+*** Org mode now uses =compat.el= third-party package to support older Emacs versions
+
+This change is mostly technical and should not affect most users.
+However, people who install Org from git source might be affected.
+It is now necessary to manually install =compat.el= using Emacs'
+package manager.
+
 *** =python-mode.el (MELPA)= support in =ob-python.el= is removed
 
 =python-mode.el= support has been removed from =ob-python.el=.  The
-- 
2.39.1


[-- Attachment #5: Type: text/plain, Size: 224 bytes --]


-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* Re: [PATCH] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-01 10:31   ` [PATCH] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)) Ihor Radchenko
@ 2023-04-01 11:38     ` Daniel Mendler
  2023-04-01 14:20       ` Max Nikulin
  2023-04-02 16:37     ` Max Nikulin
  1 sibling, 1 reply; 36+ messages in thread
From: Daniel Mendler @ 2023-04-01 11:38 UTC (permalink / raw)
  To: Ihor Radchenko, Timothy; +Cc: Bastien, Kyle Meyer, Org-mode

On 4/1/23 12:31, Ihor Radchenko wrote:
> Ihor Radchenko <yantar92@posteo.net> writes:
> 
>> I have recently been contacted by the current compat.el maintainer
>> asking if we are willing to adapt compat.el in Org.
> 
> See the attached patch set adding support of compat.el.
> 
> I had to update Org's build system to handle third-party packages.
> Please, give it a close check (first patch).
> 
> The second patch adds the actual Elisp part and replaces some
> compatibility functions with what is provided by compat.el.
> Note that not all the functions are available in compat.el for now.
> Daniel, you might want to take a look.
> 
> The last patch adds the necessary explanation for users who install Org
> from git. Now, they must install compat.el manually to make Org work.

From my side, the change looks good. The current Compat version 29.1.4.x
is stable with no known issues.

Daniel


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-01 11:38     ` Daniel Mendler
@ 2023-04-01 14:20       ` Max Nikulin
  2023-04-02  8:52         ` Ihor Radchenko
  0 siblings, 1 reply; 36+ messages in thread
From: Max Nikulin @ 2023-04-01 14:20 UTC (permalink / raw)
  To: emacs-orgmode

On 01/04/2023 18:38, Daniel Mendler wrote:
>  From my side, the change looks good. The current Compat version 29.1.4.x
> is stable with no known issues.

Debian Bookworm and Ubuntu 23.04 (currently frozen testing and beta 
accordingly) have elpa-compat-29.1.3.4 and 29.1.3.2. Are some issues 
expected if a bit earlier version is required?




^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-01 14:20       ` Max Nikulin
@ 2023-04-02  8:52         ` Ihor Radchenko
  2023-04-02 15:31           ` Max Nikulin
  0 siblings, 1 reply; 36+ messages in thread
From: Ihor Radchenko @ 2023-04-02  8:52 UTC (permalink / raw)
  To: Max Nikulin; +Cc: emacs-orgmode

Max Nikulin <manikulin@gmail.com> writes:

> On 01/04/2023 18:38, Daniel Mendler wrote:
>>  From my side, the change looks good. The current Compat version 29.1.4.x
>> is stable with no known issues.
>
> Debian Bookworm and Ubuntu 23.04 (currently frozen testing and beta 
> accordingly) have elpa-compat-29.1.3.4 and 29.1.3.2. Are some issues 
> expected if a bit earlier version is required?

I don't think so. At least, we do not appear to be using anything listed
in NEWS for later Compat versions (https://elpa.gnu.org/packages/compat.html).

And it should not matter anyway. We only need compat when Org mode is
not built-in, but installed by other means. Normally, users will simply
install Org from ELPA where we do not need to worry about older versions
of Compat.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-02  8:52         ` Ihor Radchenko
@ 2023-04-02 15:31           ` Max Nikulin
  2023-04-02 16:04             ` Ihor Radchenko
  0 siblings, 1 reply; 36+ messages in thread
From: Max Nikulin @ 2023-04-02 15:31 UTC (permalink / raw)
  To: emacs-orgmode

On 02/04/2023 15:52, Ihor Radchenko wrote:
> And it should not matter anyway. We only need compat when Org mode is
> not built-in, but installed by other means. Normally, users will simply
> install Org from ELPA where we do not need to worry about older versions
> of Compat.

If dependencies may be satisfied by installing system packages then it 
is easier to run Org from repository clone or to build new version of 
elpa-org package.




^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-02 15:31           ` Max Nikulin
@ 2023-04-02 16:04             ` Ihor Radchenko
  0 siblings, 0 replies; 36+ messages in thread
From: Ihor Radchenko @ 2023-04-02 16:04 UTC (permalink / raw)
  To: Max Nikulin; +Cc: emacs-orgmode

Max Nikulin <manikulin@gmail.com> writes:

> If dependencies may be satisfied by installing system packages then it 
> is easier to run Org from repository clone or to build new version of 
> elpa-org package.

The patch provides EFLAGS variable that will allow supplying flags to
emacs executable. One can just use ~EFLAGS="-L /path/to/local/compat/" make~.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-01 10:31   ` [PATCH] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)) Ihor Radchenko
  2023-04-01 11:38     ` Daniel Mendler
@ 2023-04-02 16:37     ` Max Nikulin
  2023-04-02 17:00       ` [PATCH v2] " Ihor Radchenko
  1 sibling, 1 reply; 36+ messages in thread
From: Max Nikulin @ 2023-04-02 16:37 UTC (permalink / raw)
  To: emacs-orgmode

On 01/04/2023 17:31, Ihor Radchenko wrote:
> 
> See the attached patch set adding support of compat.el.

Ihor, do added makefile rules follow best practices used by other Emacs 
packages in respect to dependencies? I do not like the idea of network 
queries on every make. In some cases I would prefer to specify a 
directory where compat.el is installed, so Makefile should not try to 
manage this directory. My impression is that package management code in 
Emacs is too oriented for interactive usage. E.g. python's pip caches 
downloaded packages or allows to specify a proxy or an alternative 
source. Moreover precise version may be specified while only last 
version is hosted on ELPA. In other package managers version lock is 
often preferred to avoid unexpected effects of spurious update. 
Fortunately ELPA packages are at least reviewed before publication.

Originally I expected that either compat.el would be included into Org 
repository either as a copy of the file or as git submodule.

In addition I am afraid of recursive removal of directories. It is too 
easy to remove too much.

> +pkgdir = $(shell pwd)/pkg-deps

Make has CURDIR variable, but I am unsure if it is safe to use it in 
this context.

> +	-$(FIND) $(pkgdir) \(  -name \*.elc \) -exec $(RM) {} +

find has -delete action. I see that "-exec $(RM)" is already used.

> +cleanpkg:
> +	-$(RMR) $(pkgdir)

Perhaps it is impossible to completely avoid recursive deleting of 
directories, but I still afraid of cases like
https://github.com/MrMEEE/bumblebee-Old-and-abbandoned/issues/123
https://news.ycombinator.com/item?id=9254876

> Subject: [PATCH 2/3] Use compat.el library instead of ad-hoc compatibility
>  function set

It would be easier to review if this patch was split into 2 parts:
- add compat.el dependency (unused)
- replace functions to ones from compat.el



^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v2] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-02 16:37     ` Max Nikulin
@ 2023-04-02 17:00       ` Ihor Radchenko
  2023-04-03  8:46         ` [PATCH v3] " Ihor Radchenko
  2023-04-08 11:15         ` [PATCH v2] " Max Nikulin
  0 siblings, 2 replies; 36+ messages in thread
From: Ihor Radchenko @ 2023-04-02 17:00 UTC (permalink / raw)
  To: Max Nikulin; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 2704 bytes --]


I am attaching a slightly tweaked version of the patch that will make
less use of internet connection (use only when really, really
necessary), fixes variable expansion to be done once instead of in every
make sub-process, and adds some extra info messages.

Max Nikulin <manikulin@gmail.com> writes:

> Ihor, do added makefile rules follow best practices used by other Emacs 
> packages in respect to dependencies?

I know no other Emacs packages that manage dependencies using make.

> I do not like the idea of network queries on every make.

Any better suggestions?

> In some cases I would prefer to specify a directory where compat.el is
> installed, so Makefile should not try to manage this directory.

Sure. See EFLAGS variable. Makefile will not try to manage
-L /path/to/compat directory.

> Originally I expected that either compat.el would be included into Org 
> repository either as a copy of the file or as git submodule.

That will create maintenance nightmare.

> In addition I am afraid of recursive removal of directories. It is too 
> easy to remove too much.
>
>> +pkgdir = $(shell pwd)/pkg-deps
>
> Make has CURDIR variable, but I am unsure if it is safe to use it in 
> this context.

Actually, we need pkgdir := $(shell pwd)/pkg-deps.
CURDIR is wrong because default.mk will trigger evaluation in every make
sub-process as well.

>> +	-$(FIND) $(pkgdir) \(  -name \*.elc \) -exec $(RM) {} +
>
> find has -delete action. I see that "-exec $(RM)" is already used.

I just copied the existing code.
I guess we can use -delete as well. I do not have a lot of experience
with this usage of find. May you share the equivalent find call with
-delete that works with this patch?

>> +cleanpkg:
>> +	-$(RMR) $(pkgdir)
>
> Perhaps it is impossible to completely avoid recursive deleting of 
> directories, but I still afraid of cases like
> https://github.com/MrMEEE/bumblebee-Old-and-abbandoned/issues/123
> https://news.ycombinator.com/item?id=9254876

I do not think that we have any significant risk here.
I would not mind alternative way to implement clean target though. If
you know one.

>> Subject: [PATCH 2/3] Use compat.el library instead of ad-hoc compatibility
>>  function set
>
> It would be easier to review if this patch was split into 2 parts:
> - add compat.el dependency (unused)
> - replace functions to ones from compat.el

Sure, but after spending half an hour trying to decouple this part, I
gave up and decided to leave it as is. And there is nothing fancy in
adding compat dependency in 2/3 patch anyway. Just one (require 'compat
nil t), two macro definitions adviced by compat manual (copy-paste from
compat code), and adding compat to EPACKAGES in makefiles.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Upgrade-Org-build-system-to-handle-third-party-depen.patch --]
[-- Type: text/x-patch, Size: 6315 bytes --]

From f95433f53878e8371bb28a045fdb5d06cf0877b9 Mon Sep 17 00:00:00 2001
Message-Id: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680454654.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sat, 1 Apr 2023 12:00:48 +0200
Subject: [PATCH 1/6] Upgrade Org build system to handle third-party
 dependencies

* mk/default.mk (pkgdir): New variable holding the location of
third-party packages to be downloaded if necessary during compilation.
(EFLAGS): New variable holding extra flags to be passed to Emacs
executable when running make.
(EPACKAGES): List of packages to be installed (unless already present
in the `load-path') during compilation.
(package-install):
(INSTALL_PACKAGES): New command to download and install missing packages.
(BATCH): Update, setting default package location to pkgdir.
* mk/targets.mk (uppkg): New target to download install missing
packages.
(check test):
(compile compile-dirty): Use the new uppkg target.
(cleanpkg): New target cleaning up the downloaded packages.
(cleanall): Use the new target.
(.PHONY):
(CONF_BASE):
(CONF_DEST):
(CONF_CALL): Update according to the new variables and targets.
* .gitignore: Ignore the downloaded packages.

This commit paves the way towards third-party built-time dependencies
for Org.  In particular, towards including compat.el dependency.

According to EPACKAGES, we can auto-download necessary packages,
unless they are manually specified via -L switches in EFLAGS.

Link: https://orgmode.org/list/87v8ks6rhf.fsf@localhost
---
 .gitignore    |  1 +
 mk/default.mk | 24 +++++++++++++++++++++++-
 mk/targets.mk | 24 ++++++++++++++++--------
 3 files changed, 40 insertions(+), 9 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4bb81c359..a58670c90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,6 +71,7 @@ t
 auto
 tmp
 TODO
+/pkg-deps
 
 # and collateral damage from Emacs
 
diff --git a/mk/default.mk b/mk/default.mk
index fa46661e8..997b22b66 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -31,6 +31,15 @@ GIT_BRANCH =
 TMPDIR ?= /tmp
 testdir = $(TMPDIR)/tmp-orgtest
 
+# Where to store Org dependencies
+pkgdir = $(shell pwd)/pkg-deps
+
+# Extra flags to be passed to Emacs
+EFLAGS ?=
+
+# Third-party packages to install when running make
+EPACKAGES ?=
+
 # Configuration for testing
 # Verbose ERT summary by default for Emacs-28 and above.
 # To override:
@@ -72,12 +81,22 @@ REPRO_ARGS ?=
 req-ob-lang = --eval '(require '"'"'ob-$(ob-lang))'
 lst-ob-lang = ($(ob-lang) . t)
 req-extra   = --eval '(require '"'"'$(req))'
+package-install = --eval '(unless (require '"'"'$(package) nil t) (message "%s" load-path) (package-install '"'"'$(package)))'
 BTEST_RE   ?= \\(org\\|ob\\|ox\\)
 BTEST_LOAD  = \
 	--eval '(add-to-list '"'"'load-path (concat default-directory "lisp"))' \
 	--eval '(add-to-list '"'"'load-path (concat default-directory "testing"))'
 BTEST_INIT  = $(BTEST_PRE) $(BTEST_LOAD) $(BTEST_POST)
 
+ifeq (,$(EPACKAGES))
+INSTALL_PACKAGES =
+else
+INSTALL_PACKAGES = \
+	$(BATCH) \
+        --eval '(package-refresh-contents)' \
+        $(foreach package,$(EPACKAGES),$(package-install))
+endif
+
 BTEST = $(BATCH) $(BTEST_INIT) \
 	  -l org-batch-test-init \
 	  --eval '(setq \
@@ -120,7 +139,10 @@ EMACSQ  = $(EMACS)  -Q
 
 # Using emacs in batch mode.
 BATCH	= $(EMACSQ) -batch \
-	  --eval '(setq vc-handled-backends nil org-startup-folded nil org-element-cache-persistent nil)'
+	  $(EFLAGS) \
+	  --eval '(setq vc-handled-backends nil org-startup-folded nil org-element-cache-persistent nil)' \
+          --eval '(make-directory "$(pkgdir)" t)' \
+	  --eval '(setq package-user-dir "$(pkgdir)")' --eval '(package-initialize)'
 
 # Emacs must be started in toplevel directory
 BATCHO	= $(BATCH) \
diff --git a/mk/targets.mk b/mk/targets.mk
index 0bd293d68..ab8b830bb 100644
--- a/mk/targets.mk
+++ b/mk/targets.mk
@@ -27,21 +27,21 @@ ifneq ($(GITSTATUS),)
   GITVERSION := $(GITVERSION:.dirty=).dirty
 endif
 
-.PHONY:	all oldorg update update2 up0 up1 up2 single $(SUBDIRS) \
+.PHONY:	all oldorg update update2 up0 up1 up2 uppkg single $(SUBDIRS) \
 	check test install $(INSTSUB) \
 	info html pdf card refcard doc docs \
 	autoloads cleanall clean $(CLEANDIRS:%=clean%) \
 	clean-install cleanelc cleandirs \
-	cleanlisp cleandoc cleandocs cleantest \
+	cleanlisp cleandoc cleandocs cleantest cleanpkg \
 	compile compile-dirty uncompiled \
 	config config-test config-exe config-all config-eol config-version \
 	vanilla repro
 
-CONF_BASE = EMACS DESTDIR ORGCM ORG_MAKE_DOC
-CONF_DEST = lispdir infodir datadir testdir
+CONF_BASE = EMACS DESTDIR ORGCM ORG_MAKE_DOC EPACKAGES
+CONF_DEST = lispdir infodir datadir testdir pkgdir
 CONF_TEST = BTEST_PRE BTEST_POST BTEST_OB_LANGUAGES BTEST_EXTRA BTEST_RE
 CONF_EXEC = CP MKDIR RM RMR FIND CHMOD SUDO PDFTEX TEXI2PDF TEXI2HTML MAKEINFO INSTALL_INFO
-CONF_CALL = BATCH BATCHL ELC ELCDIR NOBATCH BTEST MAKE_LOCAL_MK MAKE_ORG_INSTALL MAKE_ORG_VERSION
+CONF_CALL = BATCH BATCHL ELC ELCDIR NOBATCH INSTALL_PACKAGES BTEST MAKE_LOCAL_MK MAKE_ORG_INSTALL MAKE_ORG_VERSION
 config-eol:: EOL = \#
 config-eol:: config-all
 config config-all::
@@ -86,7 +86,7 @@ local.mk:
 
 all compile::
 	$(foreach dir, doc lisp, $(MAKE) -C $(dir) clean;)
-compile compile-dirty::
+compile compile-dirty:: uppkg
 	$(MAKE) -C lisp $@
 all clean-install::
 	$(foreach dir, $(SUBDIRS), $(MAKE) -C $(dir) $@;)
@@ -94,7 +94,7 @@ all clean-install::
 vanilla:
 	-@$(NOBATCH) &
 
-check test::	compile
+check test::	uppkg compile
 check test test-dirty::
 	-$(MKDIR) $(testdir)
 	TMPDIR=$(testdir) $(BTEST)
@@ -102,6 +102,11 @@ ifeq ($(TEST_NO_AUTOCLEAN),) # define this variable to leave $(testdir) around f
 	$(MAKE) cleantest
 endif
 
+uppkg::
+	-$(MKDIR) -p $(pkgdir)
+	-$(FIND) $(pkgdir) \(  -name \*.elc \) -exec $(RM) {} +
+	$(INSTALL_PACKAGES)
+
 up0 up1 up2::
 	git checkout $(GIT_BRANCH)
 	git remote update
@@ -134,7 +139,7 @@ cleandirs:
 
 clean:	cleanlisp cleandoc
 
-cleanall: cleandirs cleantest
+cleanall: cleandirs cleantest cleanpkg
 	-$(FIND) . \( -name \*~ -o -name \*# -o -name .#\* \) -exec $(RM) {} +
 	-$(FIND) $(CLEANDIRS) \( -name \*~ -o -name \*.elc \) -exec $(RM) {} +
 
@@ -159,3 +164,6 @@ cleantest:
 	  $(FIND) $(testdir) -type d -exec $(CHMOD) u+w {} + && \
 	  $(RMR) $(testdir) ; \
 	}
+
+cleanpkg:
+	-$(RMR) $(pkgdir)
-- 
2.39.1


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-Use-compat.el-library-instead-of-ad-hoc-compatibilit.patch --]
[-- Type: text/x-patch, Size: 35871 bytes --]

From 6099628c54170b515d8812428fa050f81c0533ec Mon Sep 17 00:00:00 2001
Message-Id: <6099628c54170b515d8812428fa050f81c0533ec.1680454654.git.yantar92@posteo.net>
In-Reply-To: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680454654.git.yantar92@posteo.net>
References: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680454654.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sat, 1 Apr 2023 12:10:13 +0200
Subject: [PATCH 2/6] Use compat.el library instead of ad-hoc compatibility
 function set

* mk/default.mk (EPACKAGES): Demand compat library during compile time.
* lisp/org.el: Add compat dependency.
(org-fill-paragraph):
* lisp/org-compat.el: Implement compat.el compatibility layer.
Obsolete org-* compatibility functions that are already available in
compat.el: `org-file-has-changed-p', `org-string-equal-ignore-case',
`org-file-name-concat', `org-directory-empty-p',
`org-string-clean-whitespace', `org-format-prompt', `org-xor',
`org-string-distance', `org-buffer-hash'.
* lisp/ob-core.el (org-babel-results-keyword): Use functions provided
by compat.el.
(org-babel-insert-result):
* lisp/oc-basic.el (org-cite-basic--parse-bibliography):
* lisp/oc.el (org-cite-adjust-note):
* lisp/ol-gnus.el (org-gnus-group-link):
(org-gnus-article-link):
(org-gnus-store-link):
* lisp/ol.el:
(org-store-link):
* lisp/org-attach.el:
* lisp/org-capture.el:
(org-capture-fill-template):
* lisp/org-fold-core.el (org-fold-core-next-visibility-change):
* lisp/org-lint.el:
* lisp/org-persist.el (org-persist-directory):
(org-persist-read:file):
(org-persist-read:url):
(org-persist--load-index):
(org-persist-write:file):
(org-persist-write:index):
(org-persist--merge-index-with-disk):
(org-persist-read):
(org-persist-write):
(org-persist-write-all):
(org-persist--gc-persist-file):
(org-persist-gc):
* lisp/org-refile.el (org-refile-get-location):
* lisp/ox.el (org-export-resolve-radio-link):
* testing/lisp/test-ol.el (test-org-link/toggle-link-display):
* testing/lisp/test-org-capture.el (test-org-capture/abort):

Link: https://orgmode.org/list/87v8ks6rhf.fsf@localhost
---
 .gitignore                       |   2 +-
 lisp/ob-core.el                  |  10 +-
 lisp/oc-basic.el                 |   6 +-
 lisp/oc.el                       |   2 +-
 lisp/ol-gnus.el                  |   8 +-
 lisp/ol.el                       |   4 +-
 lisp/org-attach.el               |   2 +-
 lisp/org-capture.el              |   8 +-
 lisp/org-compat.el               | 220 +++++++++----------------------
 lisp/org-fold-core.el            |   2 +-
 lisp/org-lint.el                 |   2 +-
 lisp/org-persist.el              |  38 +++---
 lisp/org-refile.el               |   2 +-
 lisp/org.el                      |   6 +-
 lisp/ox.el                       |   6 +-
 mk/default.mk                    |   2 +-
 testing/lisp/test-ol.el          |  10 +-
 testing/lisp/test-org-capture.el |   2 +-
 18 files changed, 118 insertions(+), 214 deletions(-)

diff --git a/.gitignore b/.gitignore
index a58670c90..0d9c5b297 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,6 +48,7 @@ local*.mk
 .gitattributes
 mk/x11idle
 ChangeLog
+pkg-deps/
 
 # Files generated during `make packages/org` in a clone of `elpa.git`.
 
@@ -71,7 +72,6 @@ t
 auto
 tmp
 TODO
-/pkg-deps
 
 # and collateral damage from Emacs
 
diff --git a/lisp/ob-core.el b/lisp/ob-core.el
index 471887a3a..4ef1f2084 100644
--- a/lisp/ob-core.el
+++ b/lisp/ob-core.el
@@ -145,7 +145,7 @@ (defcustom org-babel-results-keyword "RESULTS"
   :type 'string
   :safe (lambda (v)
 	  (and (stringp v)
-	       (org-string-equal-ignore-case "RESULTS" v))))
+	       (string-equal-ignore-case "RESULTS" v))))
 
 (defcustom org-babel-noweb-wrap-start "<<"
   "String used to begin a noweb reference in a code block.
@@ -927,7 +927,7 @@ (defun org-babel-check-src-block ()
 				   (match-string 4))))))
       (dolist (name names)
 	(when (and (not (string= header name))
-		   (<= (org-string-distance header name) too-close)
+		   (<= (string-distance header name) too-close)
 		   (not (member header names)))
 	  (error "Supplied header \"%S\" is suspiciously close to \"%S\""
 		 header name))))
@@ -2518,7 +2518,7 @@ (defun org-babel-insert-result (result &optional result-params info hash lang ex
 		       ;; Escape contents from "export" wrap.  Wrap
 		       ;; inline results within an export snippet with
 		       ;; appropriate value.
-		       ((org-string-equal-ignore-case type "export")
+		       ((string-equal-ignore-case type "export")
 			(let ((backend (pcase split
 					 (`(,_) "none")
 					 (`(,_ ,b . ,_) b))))
@@ -2529,14 +2529,14 @@ (defun org-babel-insert-result (result &optional result-params info hash lang ex
 					   backend) "@@)}}}")))
 		       ;; Escape contents from "example" wrap.  Mark
 		       ;; inline results as verbatim.
-		       ((org-string-equal-ignore-case type "example")
+		       ((string-equal-ignore-case type "example")
 			(funcall wrap
 				 opening-line closing-line
 				 nil nil
 				 "{{{results(=" "=)}}}"))
 		       ;; Escape contents from "src" wrap.  Mark
 		       ;; inline results as inline source code.
-		       ((org-string-equal-ignore-case type "src")
+		       ((string-equal-ignore-case type "src")
 			(let ((inline-open
 			       (pcase split
 				 (`(,_)
diff --git a/lisp/oc-basic.el b/lisp/oc-basic.el
index 12b627e71..1c86f344a 100644
--- a/lisp/oc-basic.el
+++ b/lisp/oc-basic.el
@@ -274,11 +274,11 @@ (defun org-cite-basic--parse-bibliography (&optional info)
       (dolist (file (org-cite-list-bibliography-files))
         (when (file-readable-p file)
           (with-temp-buffer
-            (when (or (org-file-has-changed-p file)
+            (when (or (file-has-changed-p file)
                       (not (gethash file org-cite-basic--file-id-cache)))
               (insert-file-contents file)
               (set-visited-file-name file t)
-              (puthash file (org-buffer-hash) org-cite-basic--file-id-cache))
+              (puthash file (buffer-hash) org-cite-basic--file-id-cache))
             (condition-case nil
                 (unwind-protect
 	            (let* ((file-id (cons file (gethash file org-cite-basic--file-id-cache)))
@@ -488,7 +488,7 @@ (defun org-cite-basic--close-keys (key keys)
   "List cite keys close to KEY in terms of string distance."
   (seq-filter (lambda (k)
                 (>= org-cite-basic-max-key-distance
-                    (org-string-distance k key)))
+                   (string-distance k key)))
               keys))
 
 (defun org-cite-basic--set-keymap (beg end suggestions)
diff --git a/lisp/oc.el b/lisp/oc.el
index dde6f3a32..f39ae848b 100644
--- a/lisp/oc.el
+++ b/lisp/oc.el
@@ -1029,7 +1029,7 @@ (defun org-cite-adjust-note (citation info &optional rule punct)
                              (match-string 3 previous)))))
       ;; Bail you when there is no quote and either no punctuation, or
       ;; punctuation on both sides.
-      (when (or quote (org-xor punct final-punct))
+      (when (or quote (xor punct final-punct))
         ;; Phase 1: handle punctuation rule.
         (pcase rule
           ((guard (not quote)) nil)
diff --git a/lisp/ol-gnus.el b/lisp/ol-gnus.el
index 7c07ce045..e121cfba3 100644
--- a/lisp/ol-gnus.el
+++ b/lisp/ol-gnus.el
@@ -98,8 +98,8 @@ (defun org-gnus-group-link (group)
 `org-gnus-prefer-web-links' is reversed."
   (let ((unprefixed-group (replace-regexp-in-string "^[^:]+:" "" group)))
     (if (and (string-prefix-p "nntp" group) ;; Only for nntp groups
-	     (org-xor current-prefix-arg
-		      org-gnus-prefer-web-links))
+	     (xor current-prefix-arg
+		  org-gnus-prefer-web-links))
 	(concat "https://groups.google.com/group/" unprefixed-group)
       (concat "gnus:" group))))
 
@@ -116,7 +116,7 @@ (defun org-gnus-article-link (group newsgroups message-id x-no-archive)
 
 If `org-store-link' was called with a prefix arg the meaning of
 `org-gnus-prefer-web-links' is reversed."
-  (if (and (org-xor current-prefix-arg org-gnus-prefer-web-links)
+  (if (and (xor current-prefix-arg org-gnus-prefer-web-links)
 	   newsgroups		  ;make web links only for nntp groups
 	   (not x-no-archive))	  ;and if X-No-Archive isn't set
       (format "https://groups.google.com/groups/search?as_umsgid=%s"
@@ -169,7 +169,7 @@ (defun org-gnus-store-link ()
 	    newsgroups x-no-archive)
        ;; Fetching an article is an expensive operation; newsgroup and
        ;; x-no-archive are only needed for web links.
-       (when (org-xor current-prefix-arg org-gnus-prefer-web-links)
+       (when (xor current-prefix-arg org-gnus-prefer-web-links)
 	 ;; Make sure the original article buffer is up-to-date.
 	 (save-window-excursion (gnus-summary-select-article))
 	 (setq to (or to (gnus-fetch-original-field "To")))
diff --git a/lisp/ol.el b/lisp/ol.el
index 9e4781f6e..3d62a6fa0 100644
--- a/lisp/ol.el
+++ b/lisp/ol.el
@@ -1692,7 +1692,7 @@ (defun org-store-link (arg &optional interactive?)
 				(abbreviate-file-name
 				 (buffer-file-name (buffer-base-buffer)))))
 	   ;; Add a context search string.
-	   (when (org-xor org-link-context-for-files (equal arg '(4)))
+	   (when (xor org-link-context-for-files (equal arg '(4)))
 	     (let* ((element (org-element-at-point))
 		    (name (org-element-property :name element))
 		    (context
@@ -1724,7 +1724,7 @@ (defun org-store-link (arg &optional interactive?)
 			     (abbreviate-file-name
 			      (buffer-file-name (buffer-base-buffer)))))
 	;; Add a context search string.
-	(when (org-xor org-link-context-for-files (equal arg '(4)))
+	(when (xor org-link-context-for-files (equal arg '(4)))
 	  (let ((context (org-link--normalize-string
 			  (or (org-link--context-from-region)
 			      (org-current-line-string))
diff --git a/lisp/org-attach.el b/lisp/org-attach.el
index 8d01eda71..5cc40873c 100644
--- a/lisp/org-attach.el
+++ b/lisp/org-attach.el
@@ -674,7 +674,7 @@ (defun org-attach-sync ()
       (let ((files (org-attach-file-list attach-dir)))
 	(org-attach-tag (not files)))
       (when org-attach-sync-delete-empty-dir
-        (when (and (org-directory-empty-p attach-dir)
+        (when (and (directory-empty-p attach-dir)
                    (if (eq 'query org-attach-sync-delete-empty-dir)
                        (yes-or-no-p "Attachment directory is empty.  Delete?")
                      t))
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index b96e9f336..c5b1c6d50 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -1323,9 +1323,9 @@ (defun org-capture-place-item ()
 	      ;; prioritize the existing list.
 	      (when prepend?
 		(let ((ordered? (eq 'ordered (org-element-property :type item))))
-		  (when (org-xor ordered?
-				 (string-match-p "\\`[A-Za-z0-9]\\([.)]\\)"
-						 template))
+		  (when (xor ordered?
+			     (string-match-p "\\`[A-Za-z0-9]\\([.)]\\)"
+					     template))
 		    (org-cycle-list-bullet (if ordered? "1." "-")))))
 	      ;; Eventually repair the list for proper indentation and
 	      ;; bullets.
@@ -1867,7 +1867,7 @@ (defun org-capture-fill-template (&optional template initial annotation)
 		     (setq org-capture--prompt-history
 			   (gethash prompt org-capture--prompt-history-table))
                      (push (org-completing-read
-                            (org-format-prompt (or prompt "Enter string") default)
+                            (format-prompt (or prompt "Enter string") default)
 			    completions
 			    nil nil nil 'org-capture--prompt-history default)
 			   strings)
diff --git a/lisp/org-compat.el b/lisp/org-compat.el
index c47a4e8c2..046a3db97 100644
--- a/lisp/org-compat.el
+++ b/lisp/org-compat.el
@@ -79,9 +79,6 @@ (declare-function org-fold-region "org-fold" (from to flag &optional spec))
 (declare-function org-fold-show-all "org-fold" (&optional types))
 (declare-function org-fold-show-children "org-fold" (&optional level))
 (declare-function org-fold-show-entry "org-fold" (&optional hide-drawers))
-;; `org-string-equal-ignore-case' is in _this_ file but isn't at the
-;; top-level.
-(declare-function org-string-equal-ignore-case "org-compat" (string1 string2))
 
 (defvar calendar-mode-map)
 (defvar org-complex-heading-regexp)
@@ -95,104 +92,67 @@ (defvar org-table1-hline-regexp)
 (defvar org-fold-core-style)
 
 \f
-;;; Emacs < 29 compatibility
-
-(defvar org-file-has-changed-p--hash-table (make-hash-table :test #'equal)
-  "Internal variable used by `org-file-has-changed-p'.")
-
-(if (fboundp 'file-has-changed-p)
-    (defalias 'org-file-has-changed-p #'file-has-changed-p)
-  (defun org-file-has-changed-p (file &optional tag)
-    "Return non-nil if FILE has changed.
-The size and modification time of FILE are compared to the size
-and modification time of the same FILE during a previous
-invocation of `org-file-has-changed-p'.  Thus, the first invocation
-of `org-file-has-changed-p' always returns non-nil when FILE exists.
-The optional argument TAG, which must be a symbol, can be used to
-limit the comparison to invocations with identical tags; it can be
-the symbol of the calling function, for example."
-    (let* ((file (directory-file-name (expand-file-name file)))
-           (remote-file-name-inhibit-cache t)
-           (fileattr (file-attributes file 'integer))
-	   (attr (and fileattr
-                      (cons (file-attribute-size fileattr)
-		            (file-attribute-modification-time fileattr))))
-	   (sym (concat (symbol-name tag) "@" file))
-	   (cachedattr (gethash sym org-file-has-changed-p--hash-table)))
-      (when (not (equal attr cachedattr))
-        (puthash sym attr org-file-has-changed-p--hash-table)))))
-
-(if (fboundp 'string-equal-ignore-case)
-    (defalias 'org-string-equal-ignore-case #'string-equal-ignore-case)
-  ;; From Emacs subr.el.
-  (defun org-string-equal-ignore-case (string1 string2)
-    "Like `string-equal', but case-insensitive.
-Upper-case and lower-case letters are treated as equal.
-Unibyte strings are converted to multibyte for comparison."
-    (eq t (compare-strings string1 0 nil string2 0 nil t))))
-
-\f
-;;; Emacs < 28.1 compatibility
-
-(if (fboundp 'file-name-concat)
-    (defalias 'org-file-name-concat #'file-name-concat)
-  (defun org-file-name-concat (directory &rest components)
-    "Append COMPONENTS to DIRECTORY and return the resulting string.
-
-Elements in COMPONENTS must be a string or nil.
-DIRECTORY or the non-final elements in COMPONENTS may or may not end
-with a slash -- if they don't end with a slash, a slash will be
-inserted before contatenating."
-    (save-match-data
-      (mapconcat
-       #'identity
-       (delq nil
-             (mapcar
-              (lambda (str)
-                (when (and str (not (seq-empty-p str))
-                           (string-match "\\(.+\\)/?" str))
-                  (match-string 1 str)))
-              (cons directory components)))
-       "/"))))
-
-(if (fboundp 'directory-empty-p)
-    (defalias 'org-directory-empty-p #'directory-empty-p)
-  (defun org-directory-empty-p (dir)
-    "Return t if DIR names an existing directory containing no other files."
-    (and (file-directory-p dir)
-         (null (directory-files dir nil directory-files-no-dot-files-regexp t)))))
-
-(if (fboundp 'string-clean-whitespace)
-    (defalias 'org-string-clean-whitespace #'string-clean-whitespace)
-  ;; From Emacs subr-x.el.
-  (defun org-string-clean-whitespace (string)
-    "Clean up whitespace in STRING.
-All sequences of whitespaces in STRING are collapsed into a
-single space character, and leading/trailing whitespace is
-removed."
-    (let ((blank "[[:blank:]\r\n]+"))
-      (string-trim (replace-regexp-in-string blank " " string t t)
-                   blank blank))))
-
-(if (fboundp 'format-prompt)
-    (defalias 'org-format-prompt #'format-prompt)
-  ;; From Emacs minibuffer.el, inlining
-  ;; `minibuffer-default-prompt-format' value and replacing `length<'
-  ;; (both new in Emacs 28.1).
-  (defun org-format-prompt (prompt default &rest format-args)
-    "Compatibility substitute for `format-prompt'."
-    (concat
-     (if (null format-args)
-         prompt
-       (apply #'format prompt format-args))
-     (and default
-          (or (not (stringp default))
-              (> (length default) 0))
-          (format " (default %s)"
-                  (if (consp default)
-                      (car default)
-                    default)))
-     ": ")))
+;;; compat.el
+
+;; Do not throw an error when not available - assume latest Emacs
+;; version (built-in Org).
+(require 'compat nil 'noerror)
+
+;; Provide compatibility macros when we are a part of Emacs.
+;; See https://elpa.gnu.org/packages/doc/compat.html#Usage
+
+(defmacro org-compat-function (fun)
+  "Return compatibility function symbol for FUN.
+
+If the Emacs version provides a sufficiently recent version of
+FUN, the symbol FUN is returned itself.  Otherwise the macro
+returns the symbol of a compatibility function which supports the
+behavior and calling convention of the current stable Emacs
+version.  For example Compat 29.1 will provide compatibility
+functions which implement the behavior and calling convention of
+Emacs 29.1.
+
+See also `org-compat-call' to directly call compatibility functions."
+  (let ((compat (intern (format "compat--%s" fun))))
+    `#',(if (fboundp compat) compat fun)))
+
+(defmacro org-compat-call (fun &rest args)
+  "Call compatibility function or macro FUN with ARGS.
+
+A good example function is `plist-get' which was extended with an
+additional predicate argument in Emacs 29.1.  The compatibility
+function, which supports this additional argument, can be
+obtained via (compat-function plist-get) and called
+via (compat-call plist-get plist prop predicate).  It is not
+possible to directly call (plist-get plist prop predicate) on
+Emacs older than 29.1, since the original `plist-get' function
+does not yet support the predicate argument.  Note that the
+Compat library never overrides existing functions.
+
+See also `org-compat-function' to lookup compatibility functions."
+  (let ((compat (intern (format "compat--%s" fun))))
+    `(,(if (fboundp compat) compat fun) ,@args)))
+
+;; Obsolete compatibility wrappers used before inclusion of compat.el.
+
+(define-obsolete-function-alias 'org-file-has-changed-p
+  'file-has-changed-p "9.7")
+(define-obsolete-function-alias 'org-string-equal-ignore-case
+  'string-equal-ignore-case "9.7")
+(define-obsolete-function-alias 'org-file-name-concat
+  'file-name-concat "9.7")
+(define-obsolete-function-alias 'org-directory-empty-p
+  'directory-empty-p "9.7")
+(define-obsolete-function-alias 'org-string-clean-whitespace
+  'string-clean-whitespace "9.7")
+(define-obsolete-function-alias 'org-format-prompt
+  'format-prompt "9.7")
+(define-obsolete-function-alias 'org-xor
+  'xor "9.7")
+(define-obsolete-function-alias 'org-string-distance
+  'string-distance "9.7")
+(define-obsolete-function-alias 'org-buffer-hash
+  'buffer-hash "9.7")
 
 \f
 ;;; Emacs < 27.1 compatibility
@@ -210,22 +170,6 @@ (if (version< emacs-version "27.1")
       (replace-buffer-contents source))
   (defalias 'org-replace-buffer-contents #'replace-buffer-contents))
 
-(unless (fboundp 'proper-list-p)
-  ;; `proper-list-p' was added in Emacs 27.1.  The function below is
-  ;; taken from Emacs subr.el 200195e824b^.
-  (defun proper-list-p (object)
-    "Return OBJECT's length if it is a proper list, nil otherwise.
-A proper list is neither circular nor dotted (i.e., its last cdr
-is nil)."
-    (and (listp object) (ignore-errors (length object)))))
-
-(if (fboundp 'xor)
-    ;; `xor' was added in Emacs 27.1.
-    (defalias 'org-xor #'xor)
-  (defsubst org-xor (a b)
-    "Exclusive `or'."
-    (if a (not b) b)))
-
 (unless (fboundp 'pcomplete-uniquify-list)
   ;; The misspelled variant was made obsolete in Emacs 27.1
   (defalias 'pcomplete-uniquify-list 'pcomplete-uniqify-list))
@@ -255,29 +199,6 @@ (defun org--set-faces-extend (faces extend-p)
   (when (fboundp 'set-face-extend)
     (mapc (lambda (f) (set-face-extend f extend-p)) faces)))
 
-(if (fboundp 'string-distance)
-    (defalias 'org-string-distance 'string-distance)
-  (defun org-string-distance (s1 s2)
-    "Return the edit (levenshtein) distance between strings S1 S2."
-    (let* ((l1 (length s1))
-	   (l2 (length s2))
-	   (dist (vconcat (mapcar (lambda (_) (make-vector (1+ l2) nil))
-				  (number-sequence 1 (1+ l1)))))
-	   (in (lambda (i j) (aref (aref dist i) j))))
-      (setf (aref (aref dist 0) 0) 0)
-      (dolist (j (number-sequence 1 l2))
-        (setf (aref (aref dist 0) j) j))
-      (dolist (i (number-sequence 1 l1))
-        (setf (aref (aref dist i) 0) i)
-        (dolist (j (number-sequence 1 l2))
-	  (setf (aref (aref dist i) j)
-	        (min
-	         (1+ (funcall in (1- i) j))
-	         (1+ (funcall in i (1- j)))
-	         (+ (if (equal (aref s1 (1- i)) (aref s2 (1- j))) 0 1)
-		    (funcall in (1- i) (1- j)))))))
-      (funcall in l1 l2))))
-
 (define-obsolete-function-alias 'org-babel-edit-distance 'org-string-distance
   "9.5")
 
@@ -298,23 +219,6 @@ (if (fboundp 'line-number-display-width)
     (defalias 'org-line-number-display-width 'line-number-display-width)
   (defun org-line-number-display-width (&rest _) 0))
 
-(if (fboundp 'buffer-hash)
-    (defalias 'org-buffer-hash 'buffer-hash)
-  (defun org-buffer-hash () (md5 (current-buffer))))
-
-(unless (fboundp 'file-attribute-modification-time)
-  (defsubst file-attribute-modification-time (attributes)
-    "The modification time in ATTRIBUTES returned by `file-attributes'.
-This is the time of the last change to the file's contents, and
-is a Lisp timestamp in the same style as `current-time'."
-    (nth 5 attributes)))
-
-(unless (fboundp 'file-attribute-size)
-  (defsubst file-attribute-size (attributes)
-    "The size (in bytes) in ATTRIBUTES returned by `file-attributes'.
-This is a floating point number if the size is too large for an integer."
-    (nth 7 attributes)))
-
 \f
 ;;; Obsolete aliases (remove them after the next major release).
 
@@ -1416,7 +1320,7 @@ (defun org-mode-flyspell-verify ()
 	  (and log
 	       (let ((drawer (org-element-lineage element '(drawer))))
 		 (and drawer
-		      (org-string-equal-ignore-case
+		      (string-equal-ignore-case
 		       log (org-element-property :drawer-name drawer))))))
 	nil)
        (t
diff --git a/lisp/org-fold-core.el b/lisp/org-fold-core.el
index 43c6b2b74..c699c115b 100644
--- a/lisp/org-fold-core.el
+++ b/lisp/org-fold-core.el
@@ -841,7 +841,7 @@ (defun org-fold-core-next-visibility-change (&optional pos limit ignore-hidden-p
 			  (lambda (p) (next-single-char-property-change p 'invisible nil limit)))))
 	 (next pos))
     (while (and (funcall cmp next limit)
-		(not (org-xor
+		(not (xor
                     invisible-initially?
                     (funcall invisible-p
                              (if previous-p
diff --git a/lisp/org-lint.el b/lisp/org-lint.el
index 0e2967b6c..adc893aff 100644
--- a/lisp/org-lint.el
+++ b/lisp/org-lint.el
@@ -383,7 +383,7 @@ (defun org-lint-duplicate-custom-id (ast)
    ast
    'node-property
    (lambda (property)
-     (and (org-string-equal-ignore-case
+     (and (string-equal-ignore-case
            "CUSTOM_ID" (org-element-property :key property))
 	  (org-element-property :value property)))
    (lambda (property _) (org-element-property :begin property))
diff --git a/lisp/org-persist.el b/lisp/org-persist.el
index 8e73fbc4b..21c09567f 100644
--- a/lisp/org-persist.el
+++ b/lisp/org-persist.el
@@ -279,12 +279,12 @@ (defgroup org-persist nil
 
 (defcustom org-persist-directory
   (expand-file-name
-   (org-file-name-concat
+   (file-name-concat
     (let ((cache-dir (when (fboundp 'xdg-cache-home)
                        (xdg-cache-home))))
       (if (or (seq-empty-p cache-dir)
               (not (file-exists-p cache-dir))
-              (file-exists-p (org-file-name-concat
+              (file-exists-p (file-name-concat
                               user-emacs-directory
                               "org-persist")))
           user-emacs-directory
@@ -675,13 +675,13 @@ (defalias 'org-persist-read:version #'org-persist-read:elisp-data)
 
 (defun org-persist-read:file (_ path __)
   "Read file container from PATH."
-  (when (and path (file-exists-p (org-file-name-concat org-persist-directory path)))
-    (org-file-name-concat org-persist-directory path)))
+  (when (and path (file-exists-p (file-name-concat org-persist-directory path)))
+    (file-name-concat org-persist-directory path)))
 
 (defun org-persist-read:url (_ path __)
   "Read file container from PATH."
-  (when (and path (file-exists-p (org-file-name-concat org-persist-directory path)))
-    (org-file-name-concat org-persist-directory path)))
+  (when (and path (file-exists-p (file-name-concat org-persist-directory path)))
+    (file-name-concat org-persist-directory path)))
 
 (defun org-persist-read:index (cont index-file _)
   "Read index container CONT from INDEX-FILE."
@@ -750,7 +750,7 @@ (defun org-persist--load-index ()
   "Load `org-persist--index'."
   (org-persist-load:index
    `(index ,org-persist--storage-version)
-   (org-file-name-concat org-persist-directory org-persist-index-file)
+   (file-name-concat org-persist-directory org-persist-index-file)
    nil))
 
 ;;;; Writing container data
@@ -804,7 +804,7 @@ (defun org-persist-write:file (c collection)
         (setq path (cadr c)))
       (let* ((persist-file (plist-get collection :persist-file))
              (ext (file-name-extension path))
-             (file-copy (org-file-name-concat
+             (file-copy (file-name-concat
                          org-persist-directory
                          (format "%s-%s.%s" persist-file (md5 path) ext))))
         (unless (file-exists-p file-copy)
@@ -850,7 +850,7 @@ (defun org-persist-write:index (container _)
                  org-persist-directory))))
   (when (file-exists-p org-persist-directory)
     (let ((index-file
-           (org-file-name-concat org-persist-directory org-persist-index-file)))
+           (file-name-concat org-persist-directory org-persist-index-file)))
       (org-persist--merge-index-with-disk)
       (org-persist--write-elisp-file index-file org-persist--index t t)
       (setq org-persist--index-age
@@ -865,7 +865,7 @@ (defun org-persist--save-index ()
 (defun org-persist--merge-index-with-disk ()
   "Merge `org-persist--index' with the current index file on disk."
   (let* ((index-file
-          (org-file-name-concat org-persist-directory org-persist-index-file))
+          (file-name-concat org-persist-directory org-persist-index-file))
          (disk-index
           (and (file-exists-p index-file)
                (org-file-newer-than-p index-file org-persist--index-age)
@@ -887,8 +887,8 @@ (defun org-persist--merge-index (base other)
         (dolist (item (nreverse new))
           (unless (or (memq 'index (mapcar #'car (plist-get item :container)))
                       (not (file-exists-p
-                            (org-file-name-concat org-persist-directory
-                                                  (plist-get item :persist-file))))
+                          (file-name-concat org-persist-directory
+                                            (plist-get item :persist-file))))
                       (member (plist-get item :persist-file) base-files))
             (push item combined)))
         (nreverse combined))
@@ -989,7 +989,7 @@ (cl-defun org-persist-read (container &optional associated hash-must-match load
   (let* ((collection (org-persist--find-index `(:container ,container :associated ,associated)))
          (persist-file
           (when collection
-            (org-file-name-concat
+            (file-name-concat
              org-persist-directory
              (plist-get collection :persist-file))))
          (data nil))
@@ -1077,7 +1077,7 @@ (defun org-persist-write (container &optional associated ignore-return)
                          (run-hook-with-args-until-success 'org-persist-before-write-hook v associated))
                        (plist-get collection :container)))
       (when (or (file-exists-p org-persist-directory) (org-persist--save-index))
-        (let ((file (org-file-name-concat org-persist-directory (plist-get collection :persist-file)))
+        (let ((file (file-name-concat org-persist-directory (plist-get collection :persist-file)))
               (data (mapcar (lambda (c) (cons c (org-persist-write:generic c collection)))
                             (plist-get collection :container))))
           (puthash file data org-persist--write-cache)
@@ -1097,11 +1097,11 @@ (defun org-persist-write-all (&optional associated)
            ;; The container is an `index' container.
            (eq 'index (caar (plist-get (car org-persist--index) :container)))
            (or (not (file-exists-p org-persist-directory))
-               (org-directory-empty-p org-persist-directory)))
+               (directory-empty-p org-persist-directory)))
       ;; Do not write anything, and clear up `org-persist-directory' to reduce
       ;; clutter.
       (when (and (file-exists-p org-persist-directory)
-                 (org-directory-empty-p org-persist-directory))
+                 (directory-empty-p org-persist-directory))
         (delete-directory org-persist-directory))
     ;; Write the data.
     (let (all-containers)
@@ -1142,7 +1142,7 @@ (defun org-persist--gc-persist-file (persist-file)
   "Garbage collect PERSIST-FILE."
   (when (file-exists-p persist-file)
     (delete-file persist-file)
-    (when (org-directory-empty-p (file-name-directory persist-file))
+    (when (directory-empty-p (file-name-directory persist-file))
       (delete-directory (file-name-directory persist-file)))))
 
 (defmacro org-persist-associated-files:generic (container collection)
@@ -1180,7 +1180,7 @@ (defun org-persist-gc ()
   (let (new-index
         (remote-files-num 0)
         (orphan-files
-         (delete (org-file-name-concat org-persist-directory org-persist-index-file)
+         (delete (file-name-concat org-persist-directory org-persist-index-file)
                  (when (file-exists-p org-persist-directory)
                    (directory-files-recursively org-persist-directory ".+")))))
     (dolist (collection org-persist--index)
@@ -1188,7 +1188,7 @@ (defun org-persist-gc ()
              (web-file (and file (string-match-p "\\`https?://" file)))
              (file-remote (when file (file-remote-p file)))
              (persist-file (when (plist-get collection :persist-file)
-                             (org-file-name-concat
+                             (file-name-concat
                               org-persist-directory
                               (plist-get collection :persist-file))))
              (expired? (org-persist--gc-expired-p
diff --git a/lisp/org-refile.el b/lisp/org-refile.el
index 03c351cf6..9797a0633 100644
--- a/lisp/org-refile.el
+++ b/lisp/org-refile.el
@@ -666,7 +666,7 @@ (defun org-refile-get-location (&optional prompt default-buffer new-nodes)
          (prompt (let ((default (or (car org-refile-history)
                                     (and (assoc cbnex tbl) (setq cdef cbnex)
                                          cbnex))))
-                   (org-format-prompt prompt default)))
+                   (format-prompt prompt default)))
 	 pa answ parent-target child parent old-hist)
     (setq old-hist org-refile-history)
     (setq answ (funcall cfunc prompt tbl nil (not new-nodes)
diff --git a/lisp/org.el b/lisp/org.el
index 10ade32dd..e9fa9a241 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -7,7 +7,7 @@ ;;; org.el --- Outline-based notes management and organizer -*- lexical-binding:
 ;; Maintainer: Bastien Guerry <bzg@gnu.org>
 ;; Keywords: outlines, hypermedia, calendar, wp
 ;; URL: https://orgmode.org
-;; Package-Requires: ((emacs "26.1"))
+;; Package-Requires: ((emacs "26.1") (compat "29.1.4.1"))
 
 ;; Version: 9.7-pre
 
@@ -19445,7 +19445,7 @@ (defun org-fill-paragraph (&optional justify region)
 		 (barf-if-buffer-read-only)
 		 (list (when current-prefix-arg 'full) t)))
   (let ((hash (and (not (buffer-modified-p))
-		   (org-buffer-hash))))
+		   (buffer-hash))))
     (cond
      ((and region transient-mark-mode mark-active
 	   (not (eq (region-beginning) (region-end))))
@@ -19470,7 +19470,7 @@ (defun org-fill-paragraph (&optional justify region)
     ;; If we didn't change anything in the buffer (and the buffer was
     ;; previously unmodified), then flip the modification status back
     ;; to "unchanged".
-    (when (and hash (equal hash (org-buffer-hash)))
+    (when (and hash (equal hash (buffer-hash)))
       (set-buffer-modified-p nil))
     ;; Return non-nil.
     t))
diff --git a/lisp/ox.el b/lisp/ox.el
index a6169ea63..203136994 100644
--- a/lisp/ox.el
+++ b/lisp/ox.el
@@ -4626,11 +4626,11 @@ (defun org-export-resolve-radio-link (link info)
 
 Return value can be a radio-target object or nil.  Assume LINK
 has type \"radio\"."
-  (let ((path (org-string-clean-whitespace (org-element-property :path link))))
+  (let ((path (string-clean-whitespace (org-element-property :path link))))
     (org-element-map (plist-get info :parse-tree) 'radio-target
       (lambda (radio)
-	(and (org-string-equal-ignore-case
-	      (org-string-clean-whitespace (org-element-property :value radio))
+	(and (string-equal-ignore-case
+	      (string-clean-whitespace (org-element-property :value radio))
               path)
 	     radio))
       info 'first-match)))
diff --git a/mk/default.mk b/mk/default.mk
index 997b22b66..4ad1a4281 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -38,7 +38,7 @@ pkgdir = $(shell pwd)/pkg-deps
 EFLAGS ?=
 
 # Third-party packages to install when running make
-EPACKAGES ?=
+EPACKAGES ?= compat
 
 # Configuration for testing
 # Verbose ERT summary by default for Emacs-28 and above.
diff --git a/testing/lisp/test-ol.el b/testing/lisp/test-ol.el
index a38d9f979..565539571 100644
--- a/testing/lisp/test-ol.el
+++ b/testing/lisp/test-ol.el
@@ -63,19 +63,19 @@ (ert-deftest test-org-link/toggle-link-display ()
       (dotimes (_ 2)
         (goto-char 1)
         (re-search-forward "\\[")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "example")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "com")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "]")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "\\[")
         (should-not (org-invisible-p))
         (re-search-forward "link")
         (should-not (org-invisible-p))
         (re-search-forward "]")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (org-toggle-link-display)))))
 
 \f
diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el
index 0ed44c6af..6a47b3384 100644
--- a/testing/lisp/test-org-capture.el
+++ b/testing/lisp/test-org-capture.el
@@ -154,7 +154,7 @@ (ert-deftest test-org-capture/abort ()
   "Test aborting a capture process."
   ;; Newly create capture buffer should not be saved.
   (let ((capture-file (make-temp-name
-                       (org-file-name-concat
+                       (file-name-concat
                         temporary-file-directory
                         "org-test"))))
     (unwind-protect
-- 
2.39.1


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #4: 0003-org-manual.org-Document-compat-library-installation.patch --]
[-- Type: text/x-patch, Size: 2242 bytes --]

From 25047bc1d6c262aae7467723f12d0ed65c2f0e40 Mon Sep 17 00:00:00 2001
Message-Id: <25047bc1d6c262aae7467723f12d0ed65c2f0e40.1680454654.git.yantar92@posteo.net>
In-Reply-To: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680454654.git.yantar92@posteo.net>
References: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680454654.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sat, 1 Apr 2023 12:18:57 +0200
Subject: [PATCH 3/6] org-manual.org: Document compat library installation

* doc/org-manual.org (Using Org's git repository): Document that users
must also install compat library when using git version of Org.
* etc/ORG-NEWS (Org mode now uses =compat.el= third-party package to
support older Emacs versions): Announce amendments to the Org
installation from git sources.
---
 doc/org-manual.org | 4 ++++
 etc/ORG-NEWS       | 7 +++++++
 2 files changed, 11 insertions(+)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 50662669e..aa9886e5f 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -140,6 +140,10 @@ *** Using Org's git repository
 (add-to-list 'load-path "~/src/org-mode/lisp")
 #+end_src
 
+You must also manually install =compat= library required by Org mode.
+Using built-in =package.el=, you can run =M-x package-install <RET>
+compat <RET>=.
+
 You can also compile with =make=, generate the documentation with
 =make doc=, create a local configuration with =make config= and
 install Org with =make install=.  Please run =make help= to get the
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index ac233a986..0302b8cda 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -13,6 +13,13 @@ Please send Org bug reports to mailto:emacs-orgmode@gnu.org.
 
 * Version 9.7 (not released yet)
 ** Important announcements and breaking changes
+*** Org mode now uses =compat.el= third-party package to support older Emacs versions
+
+This change is mostly technical and should not affect most users.
+However, people who install Org from git source might be affected.
+It is now necessary to manually install =compat.el= using Emacs'
+package manager.
+
 *** =python-mode.el (MELPA)= support in =ob-python.el= is removed
 
 =python-mode.el= support has been removed from =ob-python.el=.  The
-- 
2.39.1


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #5: 0004-mk-Expand-shell-commands-once-only.patch --]
[-- Type: text/x-patch, Size: 2303 bytes --]

From 1515712e093137fe9642c7f1e2ebc0fc71119740 Mon Sep 17 00:00:00 2001
Message-Id: <1515712e093137fe9642c7f1e2ebc0fc71119740.1680454654.git.yantar92@posteo.net>
In-Reply-To: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680454654.git.yantar92@posteo.net>
References: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680454654.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sun, 2 Apr 2023 13:53:47 +0200
Subject: [PATCH 4/6] mk: Expand shell commands once only

* mk/default.mk (pkgdir):
* mk/targets.mk (GITVERSION):
(GITSTATUS):
(DATE):
(YEAR): Only expand the variables once, not in every make sub-process.
Previous code ran pwd/date/git in every make sub-process, slowing
things down significantly and unnecessarily.
---
 mk/default.mk | 2 +-
 mk/targets.mk | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/mk/default.mk b/mk/default.mk
index 4ad1a4281..b43a9b3ac 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -32,7 +32,7 @@ TMPDIR ?= /tmp
 testdir = $(TMPDIR)/tmp-orgtest
 
 # Where to store Org dependencies
-pkgdir = $(shell pwd)/pkg-deps
+pkgdir := $(shell pwd)/pkg-deps
 
 # Extra flags to be passed to Emacs
 EFLAGS ?=
diff --git a/mk/targets.mk b/mk/targets.mk
index ab8b830bb..18faa02f5 100644
--- a/mk/targets.mk
+++ b/mk/targets.mk
@@ -14,15 +14,15 @@ ifneq ($(wildcard .git),)
   # Use the org.el header.
   ORGVERSION := $(patsubst %-dev,%,$(shell $(BATCH) --eval "(require 'lisp-mnt)" \
     --visit lisp/org.el --eval '(princ (lm-header "version"))'))
-  GITVERSION ?= $(shell git describe --match release\* --abbrev=6 HEAD 2>/dev/null || echo  "release_N/A-N/A-$(shell git describe --match release\* --abbrev=6 --always HEAD)")
-  GITSTATUS  ?= $(shell git status -uno --porcelain)
+  GITVERSION := $(shell git describe --match release\* --abbrev=6 HEAD 2>/dev/null || echo  "release_N/A-N/A-$(shell git describe --match release\* --abbrev=6 --always HEAD)")
+  GITSTATUS  := $(shell git status -uno --porcelain)
 else
  -include mk/version.mk
   GITVERSION ?= N/A
   ORGVERSION ?= N/A
 endif
-DATE          = $(shell date +%Y-%m-%d)
-YEAR          = $(shell date +%Y)
+DATE          := $(shell date +%Y-%m-%d)
+YEAR          := $(shell date +%Y)
 ifneq ($(GITSTATUS),)
   GITVERSION := $(GITVERSION:.dirty=).dirty
 endif
-- 
2.39.1


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #6: 0005-mk-Echo-main-compilation-states-when-running-make.patch --]
[-- Type: text/x-patch, Size: 2268 bytes --]

From 14140c127e5b44dd6f8aa76b5f6c946d77610939 Mon Sep 17 00:00:00 2001
Message-Id: <14140c127e5b44dd6f8aa76b5f6c946d77610939.1680454654.git.yantar92@posteo.net>
In-Reply-To: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680454654.git.yantar92@posteo.net>
References: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680454654.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sun, 2 Apr 2023 14:05:22 +0200
Subject: [PATCH 5/6] mk: Echo main compilation states when running make

* lisp/Makefile (all compile compile-dirty):
($(LISPV)):
($(LISPI)): Echo compile stage.
* mk/targets.mk (uppkg): Echo compile stage and hide the installation
command.
---
 lisp/Makefile | 7 +++++--
 mk/targets.mk | 7 ++++---
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/lisp/Makefile b/lisp/Makefile
index f507f18a2..45d8109f0 100644
--- a/lisp/Makefile
+++ b/lisp/Makefile
@@ -20,6 +20,7 @@ _ORGCM_ := dirall single source slint1 slint2
 
 # 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
@@ -52,12 +53,14 @@ slint1:
 autoloads:	cleanauto $(LISPI) $(LISPV)
 
 $(LISPV):	$(LISPF)
-	@echo "org-version: $(ORGVERSION) ($(GITVERSION))"
+	@$(info ========= Auto-generating Org version number)
+	@$(info org-version: $(ORGVERSION) ($(GITVERSION)))
 	@$(RM) $(@)
 	@$(MAKE_ORG_VERSION)
 
 $(LISPI):	$(LISPV) $(LISPF)
-	@echo "org-loaddefs: $(ORGVERSION) ($(GITVERSION))"
+	@$(info ========= Auto-generating Org loaddefs)
+	@$(info org-loaddefs: $(ORGVERSION) ($(GITVERSION)))
 	@$(RM) $(@)
 	@$(MAKE_ORG_INSTALL)
 
diff --git a/mk/targets.mk b/mk/targets.mk
index 18faa02f5..072106237 100644
--- a/mk/targets.mk
+++ b/mk/targets.mk
@@ -103,9 +103,10 @@ ifeq ($(TEST_NO_AUTOCLEAN),) # define this variable to leave $(testdir) around f
 endif
 
 uppkg::
-	-$(MKDIR) -p $(pkgdir)
-	-$(FIND) $(pkgdir) \(  -name \*.elc \) -exec $(RM) {} +
-	$(INSTALL_PACKAGES)
+	$(info ========= Installing required third-party packages)
+	@$(MKDIR) -p $(pkgdir)
+	@$(FIND) $(pkgdir) \(  -name \*.elc \) -exec $(RM) {} +
+	-@$(INSTALL_PACKAGES)
 
 up0 up1 up2::
 	git checkout $(GIT_BRANCH)
-- 
2.39.1


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #7: 0006-mk-Do-not-run-package-refresh-packages-unless-necess.patch --]
[-- Type: text/x-patch, Size: 2322 bytes --]

From dcb8bc6ba68834da94cef9006e6889c75e89f67a Mon Sep 17 00:00:00 2001
Message-Id: <dcb8bc6ba68834da94cef9006e6889c75e89f67a.1680454654.git.yantar92@posteo.net>
In-Reply-To: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680454654.git.yantar92@posteo.net>
References: <f95433f53878e8371bb28a045fdb5d06cf0877b9.1680454654.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sun, 2 Apr 2023 15:11:06 +0200
Subject: [PATCH 6/6] mk: Do not run `package-refresh-packages' unless
 necessary

* mk/default.mk (package-install):
(package-define-refresh-mark):
(package-need-refresh-mark):
(package-refresh-maybe):
(package-install-maybe):
(INSTALL_PACKAGES): Update the command avoiding
`package-refresh-packages' when all the required third-party packages
are already present in the `load-path'.
---
 mk/default.mk | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/mk/default.mk b/mk/default.mk
index b43a9b3ac..67bf96c2e 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -81,7 +81,10 @@ REPRO_ARGS ?=
 req-ob-lang = --eval '(require '"'"'ob-$(ob-lang))'
 lst-ob-lang = ($(ob-lang) . t)
 req-extra   = --eval '(require '"'"'$(req))'
-package-install = --eval '(unless (require '"'"'$(package) nil t) (message "%s" load-path) (package-install '"'"'$(package)))'
+package-define-refresh-mark = --eval '(setq org--compile-packages-missing nil)'
+package-need-refresh-mark = --eval '(unless (require '"'"'$(package) nil t) (setq org--compile-packages-missing t))'
+package-refresh-maybe = --eval '(if org--compile-packages-missing (package-refresh-contents) (message "No third-party packages need to be installed"))'
+package-install-maybe = --eval '(unless (require '"'"'$(package) nil t) (package-install '"'"'$(package)))'
 BTEST_RE   ?= \\(org\\|ob\\|ox\\)
 BTEST_LOAD  = \
 	--eval '(add-to-list '"'"'load-path (concat default-directory "lisp"))' \
@@ -93,8 +96,8 @@ INSTALL_PACKAGES =
 else
 INSTALL_PACKAGES = \
 	$(BATCH) \
-        --eval '(package-refresh-contents)' \
-        $(foreach package,$(EPACKAGES),$(package-install))
+        $(package-define-refresh-mark) $(foreach package,$(EPACKAGES),$(package-need-refresh-mark)) $(package-refresh-maybe) \
+        $(foreach package,$(EPACKAGES),$(package-install-maybe))
 endif
 
 BTEST = $(BATCH) $(BTEST_INIT) \
-- 
2.39.1


[-- Attachment #8: Type: text/plain, Size: 224 bytes --]


-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [PATCH v3] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-02 17:00       ` [PATCH v2] " Ihor Radchenko
@ 2023-04-03  8:46         ` Ihor Radchenko
  2023-04-08 11:15         ` [PATCH v2] " Max Nikulin
  1 sibling, 0 replies; 36+ messages in thread
From: Ihor Radchenko @ 2023-04-03  8:46 UTC (permalink / raw)
  To: Max Nikulin; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 692 bytes --]

Ihor Radchenko <yantar92@posteo.net> writes:

>> It would be easier to review if this patch was split into 2 parts:
>> - add compat.el dependency (unused)
>> - replace functions to ones from compat.el
>
> Sure, but after spending half an hour trying to decouple this part, I
> gave up and decided to leave it as is. And there is nothing fancy in
> adding compat dependency in 2/3 patch anyway. Just one (require 'compat
> nil t), two macro definitions adviced by compat manual (copy-paste from
> compat code), and adding compat to EPACKAGES in makefiles.

After more fiddling with interactive rebase, I managed to split the
patch further.

See the attached.
Hope it is easier to review now.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Upgrade-Org-build-system-to-handle-third-party-depen.patch --]
[-- Type: text/x-patch, Size: 6381 bytes --]

From 313f2b6acdf67f29f4f5f671e8278bb3e566cabf Mon Sep 17 00:00:00 2001
Message-Id: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sat, 1 Apr 2023 12:00:48 +0200
Subject: [PATCH 1/7] Upgrade Org build system to handle third-party
 dependencies

* mk/default.mk (pkgdir): New variable holding the location of
third-party packages to be downloaded if necessary during compilation.
(EFLAGS): New variable holding extra flags to be passed to Emacs
executable when running make.
(EPACKAGES): List of packages to be installed (unless already present
in the `load-path') during compilation.
(package-install):
(INSTALL_PACKAGES): New command to download and install missing packages.
(BATCH): Update, setting default package location to pkgdir.
* mk/targets.mk (uppkg): New target to download install missing
packages.
(check test):
(compile compile-dirty): Use the new uppkg target.
(cleanpkg): New target cleaning up the downloaded packages.
(cleanall): Use the new target.
(.PHONY):
(CONF_BASE):
(CONF_DEST):
(CONF_CALL): Update according to the new variables and targets.
* .gitignore: Ignore the downloaded packages.

This commit paves the way towards third-party built-time dependencies
for Org.  In particular, towards including compat.el dependency.

According to EPACKAGES, we can auto-download necessary packages,
unless they are manually specified via -L switches in EFLAGS.

Link: https://orgmode.org/list/87v8ks6rhf.fsf@localhost
---
 .gitignore    |  1 +
 mk/default.mk | 24 +++++++++++++++++++++++-
 mk/targets.mk | 24 ++++++++++++++++--------
 3 files changed, 40 insertions(+), 9 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4bb81c359..0d9c5b297 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,6 +48,7 @@ local*.mk
 .gitattributes
 mk/x11idle
 ChangeLog
+pkg-deps/
 
 # Files generated during `make packages/org` in a clone of `elpa.git`.
 
diff --git a/mk/default.mk b/mk/default.mk
index fa46661e8..997b22b66 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -31,6 +31,15 @@ GIT_BRANCH =
 TMPDIR ?= /tmp
 testdir = $(TMPDIR)/tmp-orgtest
 
+# Where to store Org dependencies
+pkgdir = $(shell pwd)/pkg-deps
+
+# Extra flags to be passed to Emacs
+EFLAGS ?=
+
+# Third-party packages to install when running make
+EPACKAGES ?=
+
 # Configuration for testing
 # Verbose ERT summary by default for Emacs-28 and above.
 # To override:
@@ -72,12 +81,22 @@ REPRO_ARGS ?=
 req-ob-lang = --eval '(require '"'"'ob-$(ob-lang))'
 lst-ob-lang = ($(ob-lang) . t)
 req-extra   = --eval '(require '"'"'$(req))'
+package-install = --eval '(unless (require '"'"'$(package) nil t) (message "%s" load-path) (package-install '"'"'$(package)))'
 BTEST_RE   ?= \\(org\\|ob\\|ox\\)
 BTEST_LOAD  = \
 	--eval '(add-to-list '"'"'load-path (concat default-directory "lisp"))' \
 	--eval '(add-to-list '"'"'load-path (concat default-directory "testing"))'
 BTEST_INIT  = $(BTEST_PRE) $(BTEST_LOAD) $(BTEST_POST)
 
+ifeq (,$(EPACKAGES))
+INSTALL_PACKAGES =
+else
+INSTALL_PACKAGES = \
+	$(BATCH) \
+        --eval '(package-refresh-contents)' \
+        $(foreach package,$(EPACKAGES),$(package-install))
+endif
+
 BTEST = $(BATCH) $(BTEST_INIT) \
 	  -l org-batch-test-init \
 	  --eval '(setq \
@@ -120,7 +139,10 @@ EMACSQ  = $(EMACS)  -Q
 
 # Using emacs in batch mode.
 BATCH	= $(EMACSQ) -batch \
-	  --eval '(setq vc-handled-backends nil org-startup-folded nil org-element-cache-persistent nil)'
+	  $(EFLAGS) \
+	  --eval '(setq vc-handled-backends nil org-startup-folded nil org-element-cache-persistent nil)' \
+          --eval '(make-directory "$(pkgdir)" t)' \
+	  --eval '(setq package-user-dir "$(pkgdir)")' --eval '(package-initialize)'
 
 # Emacs must be started in toplevel directory
 BATCHO	= $(BATCH) \
diff --git a/mk/targets.mk b/mk/targets.mk
index 0bd293d68..ab8b830bb 100644
--- a/mk/targets.mk
+++ b/mk/targets.mk
@@ -27,21 +27,21 @@ ifneq ($(GITSTATUS),)
   GITVERSION := $(GITVERSION:.dirty=).dirty
 endif
 
-.PHONY:	all oldorg update update2 up0 up1 up2 single $(SUBDIRS) \
+.PHONY:	all oldorg update update2 up0 up1 up2 uppkg single $(SUBDIRS) \
 	check test install $(INSTSUB) \
 	info html pdf card refcard doc docs \
 	autoloads cleanall clean $(CLEANDIRS:%=clean%) \
 	clean-install cleanelc cleandirs \
-	cleanlisp cleandoc cleandocs cleantest \
+	cleanlisp cleandoc cleandocs cleantest cleanpkg \
 	compile compile-dirty uncompiled \
 	config config-test config-exe config-all config-eol config-version \
 	vanilla repro
 
-CONF_BASE = EMACS DESTDIR ORGCM ORG_MAKE_DOC
-CONF_DEST = lispdir infodir datadir testdir
+CONF_BASE = EMACS DESTDIR ORGCM ORG_MAKE_DOC EPACKAGES
+CONF_DEST = lispdir infodir datadir testdir pkgdir
 CONF_TEST = BTEST_PRE BTEST_POST BTEST_OB_LANGUAGES BTEST_EXTRA BTEST_RE
 CONF_EXEC = CP MKDIR RM RMR FIND CHMOD SUDO PDFTEX TEXI2PDF TEXI2HTML MAKEINFO INSTALL_INFO
-CONF_CALL = BATCH BATCHL ELC ELCDIR NOBATCH BTEST MAKE_LOCAL_MK MAKE_ORG_INSTALL MAKE_ORG_VERSION
+CONF_CALL = BATCH BATCHL ELC ELCDIR NOBATCH INSTALL_PACKAGES BTEST MAKE_LOCAL_MK MAKE_ORG_INSTALL MAKE_ORG_VERSION
 config-eol:: EOL = \#
 config-eol:: config-all
 config config-all::
@@ -86,7 +86,7 @@ local.mk:
 
 all compile::
 	$(foreach dir, doc lisp, $(MAKE) -C $(dir) clean;)
-compile compile-dirty::
+compile compile-dirty:: uppkg
 	$(MAKE) -C lisp $@
 all clean-install::
 	$(foreach dir, $(SUBDIRS), $(MAKE) -C $(dir) $@;)
@@ -94,7 +94,7 @@ all clean-install::
 vanilla:
 	-@$(NOBATCH) &
 
-check test::	compile
+check test::	uppkg compile
 check test test-dirty::
 	-$(MKDIR) $(testdir)
 	TMPDIR=$(testdir) $(BTEST)
@@ -102,6 +102,11 @@ ifeq ($(TEST_NO_AUTOCLEAN),) # define this variable to leave $(testdir) around f
 	$(MAKE) cleantest
 endif
 
+uppkg::
+	-$(MKDIR) -p $(pkgdir)
+	-$(FIND) $(pkgdir) \(  -name \*.elc \) -exec $(RM) {} +
+	$(INSTALL_PACKAGES)
+
 up0 up1 up2::
 	git checkout $(GIT_BRANCH)
 	git remote update
@@ -134,7 +139,7 @@ cleandirs:
 
 clean:	cleanlisp cleandoc
 
-cleanall: cleandirs cleantest
+cleanall: cleandirs cleantest cleanpkg
 	-$(FIND) . \( -name \*~ -o -name \*# -o -name .#\* \) -exec $(RM) {} +
 	-$(FIND) $(CLEANDIRS) \( -name \*~ -o -name \*.elc \) -exec $(RM) {} +
 
@@ -159,3 +164,6 @@ cleantest:
 	  $(FIND) $(testdir) -type d -exec $(CHMOD) u+w {} + && \
 	  $(RMR) $(testdir) ; \
 	}
+
+cleanpkg:
+	-$(RMR) $(pkgdir)
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-org-compat-Enable-compat.el.patch --]
[-- Type: text/x-patch, Size: 3395 bytes --]

From 68fb497e93d717df60d26c1e1fb5547e9f972cac Mon Sep 17 00:00:00 2001
Message-Id: <68fb497e93d717df60d26c1e1fb5547e9f972cac.1680511418.git.yantar92@posteo.net>
In-Reply-To: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
References: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Mon, 3 Apr 2023 10:40:00 +0200
Subject: [PATCH 2/7] org-compat: Enable compat.el

* lisp/org-compat.el (compat): Load Compat library, when available.
(org-compat-function):
(org-compat-call): Add compatibility macros available even when Compat
is not available (Org is a part of Emacs).
* lisp/org.el: Add Compat to package-requires.
---
 lisp/org-compat.el | 42 ++++++++++++++++++++++++++++++++++++++++++
 lisp/org.el        |  2 +-
 2 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/lisp/org-compat.el b/lisp/org-compat.el
index c47a4e8c2..e5b22d623 100644
--- a/lisp/org-compat.el
+++ b/lisp/org-compat.el
@@ -94,6 +94,48 @@ (defvar org-table-tab-recognizes-table.el)
 (defvar org-table1-hline-regexp)
 (defvar org-fold-core-style)
 
+\f
+;;; compat.el
+
+;; Do not throw an error when not available - assume latest Emacs
+;; version (built-in Org).
+(require 'compat nil 'noerror)
+
+;; Provide compatibility macros when we are a part of Emacs.
+;; See https://elpa.gnu.org/packages/doc/compat.html#Usage
+
+(defmacro org-compat-function (fun)
+  "Return compatibility function symbol for FUN.
+
+If the Emacs version provides a sufficiently recent version of
+FUN, the symbol FUN is returned itself.  Otherwise the macro
+returns the symbol of a compatibility function which supports the
+behavior and calling convention of the current stable Emacs
+version.  For example Compat 29.1 will provide compatibility
+functions which implement the behavior and calling convention of
+Emacs 29.1.
+
+See also `org-compat-call' to directly call compatibility functions."
+  (let ((compat (intern (format "compat--%s" fun))))
+    `#',(if (fboundp compat) compat fun)))
+
+(defmacro org-compat-call (fun &rest args)
+  "Call compatibility function or macro FUN with ARGS.
+
+A good example function is `plist-get' which was extended with an
+additional predicate argument in Emacs 29.1.  The compatibility
+function, which supports this additional argument, can be
+obtained via (compat-function plist-get) and called
+via (compat-call plist-get plist prop predicate).  It is not
+possible to directly call (plist-get plist prop predicate) on
+Emacs older than 29.1, since the original `plist-get' function
+does not yet support the predicate argument.  Note that the
+Compat library never overrides existing functions.
+
+See also `org-compat-function' to lookup compatibility functions."
+  (let ((compat (intern (format "compat--%s" fun))))
+    `(,(if (fboundp compat) compat fun) ,@args)))
+
 \f
 ;;; Emacs < 29 compatibility
 
diff --git a/lisp/org.el b/lisp/org.el
index 10ade32dd..5e52608ca 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -7,7 +7,7 @@ ;;; org.el --- Outline-based notes management and organizer -*- lexical-binding:
 ;; Maintainer: Bastien Guerry <bzg@gnu.org>
 ;; Keywords: outlines, hypermedia, calendar, wp
 ;; URL: https://orgmode.org
-;; Package-Requires: ((emacs "26.1"))
+;; Package-Requires: ((emacs "26.1") (compat "29.1.4.1"))
 
 ;; Version: 9.7-pre
 
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #4: 0003-Use-compat.el-library-instead-of-ad-hoc-compatibilit.patch --]
[-- Type: text/x-patch, Size: 33408 bytes --]

From 88888d4bc18da596a68698082789bc9ba3eb9ff3 Mon Sep 17 00:00:00 2001
Message-Id: <88888d4bc18da596a68698082789bc9ba3eb9ff3.1680511419.git.yantar92@posteo.net>
In-Reply-To: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
References: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Mon, 3 Apr 2023 10:41:50 +0200
Subject: [PATCH 3/7] Use compat.el library instead of ad-hoc compatibility
 function set

* mk/default.mk (EPACKAGES): Demand compat library during compile time.
* lisp/org.el (org-fill-paragraph):
* lisp/org-compat.el: Obsolete org-* compatibility functions that are
already available in compat.el: `org-file-has-changed-p',
`org-string-equal-ignore-case', `org-file-name-concat',
`org-directory-empty-p', `org-string-clean-whitespace',
`org-format-prompt', `org-xor', `org-string-distance',
`org-buffer-hash'.
* lisp/ob-core.el (org-babel-results-keyword): Use functions provided
by compat.el.
(org-babel-insert-result):
* lisp/oc-basic.el (org-cite-basic--parse-bibliography):
* lisp/oc.el (org-cite-adjust-note):
* lisp/ol-gnus.el (org-gnus-group-link):
(org-gnus-article-link):
(org-gnus-store-link):
* lisp/ol.el:
(org-store-link):
* lisp/org-attach.el:
* lisp/org-capture.el:
(org-capture-fill-template):
* lisp/org-fold-core.el (org-fold-core-next-visibility-change):
* lisp/org-lint.el:
* lisp/org-persist.el (org-persist-directory):
(org-persist-read:file):
(org-persist-read:url):
(org-persist--load-index):
(org-persist-write:file):
(org-persist-write:index):
(org-persist--merge-index-with-disk):
(org-persist-read):
(org-persist-write):
(org-persist-write-all):
(org-persist--gc-persist-file):
(org-persist-gc):
* lisp/org-refile.el (org-refile-get-location):
* lisp/ox.el (org-export-resolve-radio-link):
* testing/lisp/test-ol.el (test-org-link/toggle-link-display):
* testing/lisp/test-org-capture.el (test-org-capture/abort):

Link: https://orgmode.org/list/87v8ks6rhf.fsf@localhost
---
 lisp/ob-core.el                  |  10 +-
 lisp/oc-basic.el                 |   6 +-
 lisp/oc.el                       |   2 +-
 lisp/ol-gnus.el                  |   8 +-
 lisp/ol.el                       |   4 +-
 lisp/org-attach.el               |   2 +-
 lisp/org-capture.el              |   8 +-
 lisp/org-compat.el               | 180 ++++---------------------------
 lisp/org-fold-core.el            |   2 +-
 lisp/org-lint.el                 |   2 +-
 lisp/org-persist.el              |  38 +++----
 lisp/org-refile.el               |   2 +-
 lisp/org.el                      |   4 +-
 lisp/ox.el                       |   6 +-
 mk/default.mk                    |   2 +-
 testing/lisp/test-ol.el          |  10 +-
 testing/lisp/test-org-capture.el |   2 +-
 17 files changed, 75 insertions(+), 213 deletions(-)

diff --git a/lisp/ob-core.el b/lisp/ob-core.el
index 471887a3a..4ef1f2084 100644
--- a/lisp/ob-core.el
+++ b/lisp/ob-core.el
@@ -145,7 +145,7 @@ (defcustom org-babel-results-keyword "RESULTS"
   :type 'string
   :safe (lambda (v)
 	  (and (stringp v)
-	       (org-string-equal-ignore-case "RESULTS" v))))
+	       (string-equal-ignore-case "RESULTS" v))))
 
 (defcustom org-babel-noweb-wrap-start "<<"
   "String used to begin a noweb reference in a code block.
@@ -927,7 +927,7 @@ (defun org-babel-check-src-block ()
 				   (match-string 4))))))
       (dolist (name names)
 	(when (and (not (string= header name))
-		   (<= (org-string-distance header name) too-close)
+		   (<= (string-distance header name) too-close)
 		   (not (member header names)))
 	  (error "Supplied header \"%S\" is suspiciously close to \"%S\""
 		 header name))))
@@ -2518,7 +2518,7 @@ (defun org-babel-insert-result (result &optional result-params info hash lang ex
 		       ;; Escape contents from "export" wrap.  Wrap
 		       ;; inline results within an export snippet with
 		       ;; appropriate value.
-		       ((org-string-equal-ignore-case type "export")
+		       ((string-equal-ignore-case type "export")
 			(let ((backend (pcase split
 					 (`(,_) "none")
 					 (`(,_ ,b . ,_) b))))
@@ -2529,14 +2529,14 @@ (defun org-babel-insert-result (result &optional result-params info hash lang ex
 					   backend) "@@)}}}")))
 		       ;; Escape contents from "example" wrap.  Mark
 		       ;; inline results as verbatim.
-		       ((org-string-equal-ignore-case type "example")
+		       ((string-equal-ignore-case type "example")
 			(funcall wrap
 				 opening-line closing-line
 				 nil nil
 				 "{{{results(=" "=)}}}"))
 		       ;; Escape contents from "src" wrap.  Mark
 		       ;; inline results as inline source code.
-		       ((org-string-equal-ignore-case type "src")
+		       ((string-equal-ignore-case type "src")
 			(let ((inline-open
 			       (pcase split
 				 (`(,_)
diff --git a/lisp/oc-basic.el b/lisp/oc-basic.el
index 12b627e71..1c86f344a 100644
--- a/lisp/oc-basic.el
+++ b/lisp/oc-basic.el
@@ -274,11 +274,11 @@ (defun org-cite-basic--parse-bibliography (&optional info)
       (dolist (file (org-cite-list-bibliography-files))
         (when (file-readable-p file)
           (with-temp-buffer
-            (when (or (org-file-has-changed-p file)
+            (when (or (file-has-changed-p file)
                       (not (gethash file org-cite-basic--file-id-cache)))
               (insert-file-contents file)
               (set-visited-file-name file t)
-              (puthash file (org-buffer-hash) org-cite-basic--file-id-cache))
+              (puthash file (buffer-hash) org-cite-basic--file-id-cache))
             (condition-case nil
                 (unwind-protect
 	            (let* ((file-id (cons file (gethash file org-cite-basic--file-id-cache)))
@@ -488,7 +488,7 @@ (defun org-cite-basic--close-keys (key keys)
   "List cite keys close to KEY in terms of string distance."
   (seq-filter (lambda (k)
                 (>= org-cite-basic-max-key-distance
-                    (org-string-distance k key)))
+                   (string-distance k key)))
               keys))
 
 (defun org-cite-basic--set-keymap (beg end suggestions)
diff --git a/lisp/oc.el b/lisp/oc.el
index dde6f3a32..f39ae848b 100644
--- a/lisp/oc.el
+++ b/lisp/oc.el
@@ -1029,7 +1029,7 @@ (defun org-cite-adjust-note (citation info &optional rule punct)
                              (match-string 3 previous)))))
       ;; Bail you when there is no quote and either no punctuation, or
       ;; punctuation on both sides.
-      (when (or quote (org-xor punct final-punct))
+      (when (or quote (xor punct final-punct))
         ;; Phase 1: handle punctuation rule.
         (pcase rule
           ((guard (not quote)) nil)
diff --git a/lisp/ol-gnus.el b/lisp/ol-gnus.el
index 7c07ce045..e121cfba3 100644
--- a/lisp/ol-gnus.el
+++ b/lisp/ol-gnus.el
@@ -98,8 +98,8 @@ (defun org-gnus-group-link (group)
 `org-gnus-prefer-web-links' is reversed."
   (let ((unprefixed-group (replace-regexp-in-string "^[^:]+:" "" group)))
     (if (and (string-prefix-p "nntp" group) ;; Only for nntp groups
-	     (org-xor current-prefix-arg
-		      org-gnus-prefer-web-links))
+	     (xor current-prefix-arg
+		  org-gnus-prefer-web-links))
 	(concat "https://groups.google.com/group/" unprefixed-group)
       (concat "gnus:" group))))
 
@@ -116,7 +116,7 @@ (defun org-gnus-article-link (group newsgroups message-id x-no-archive)
 
 If `org-store-link' was called with a prefix arg the meaning of
 `org-gnus-prefer-web-links' is reversed."
-  (if (and (org-xor current-prefix-arg org-gnus-prefer-web-links)
+  (if (and (xor current-prefix-arg org-gnus-prefer-web-links)
 	   newsgroups		  ;make web links only for nntp groups
 	   (not x-no-archive))	  ;and if X-No-Archive isn't set
       (format "https://groups.google.com/groups/search?as_umsgid=%s"
@@ -169,7 +169,7 @@ (defun org-gnus-store-link ()
 	    newsgroups x-no-archive)
        ;; Fetching an article is an expensive operation; newsgroup and
        ;; x-no-archive are only needed for web links.
-       (when (org-xor current-prefix-arg org-gnus-prefer-web-links)
+       (when (xor current-prefix-arg org-gnus-prefer-web-links)
 	 ;; Make sure the original article buffer is up-to-date.
 	 (save-window-excursion (gnus-summary-select-article))
 	 (setq to (or to (gnus-fetch-original-field "To")))
diff --git a/lisp/ol.el b/lisp/ol.el
index 9e4781f6e..3d62a6fa0 100644
--- a/lisp/ol.el
+++ b/lisp/ol.el
@@ -1692,7 +1692,7 @@ (defun org-store-link (arg &optional interactive?)
 				(abbreviate-file-name
 				 (buffer-file-name (buffer-base-buffer)))))
 	   ;; Add a context search string.
-	   (when (org-xor org-link-context-for-files (equal arg '(4)))
+	   (when (xor org-link-context-for-files (equal arg '(4)))
 	     (let* ((element (org-element-at-point))
 		    (name (org-element-property :name element))
 		    (context
@@ -1724,7 +1724,7 @@ (defun org-store-link (arg &optional interactive?)
 			     (abbreviate-file-name
 			      (buffer-file-name (buffer-base-buffer)))))
 	;; Add a context search string.
-	(when (org-xor org-link-context-for-files (equal arg '(4)))
+	(when (xor org-link-context-for-files (equal arg '(4)))
 	  (let ((context (org-link--normalize-string
 			  (or (org-link--context-from-region)
 			      (org-current-line-string))
diff --git a/lisp/org-attach.el b/lisp/org-attach.el
index 8d01eda71..5cc40873c 100644
--- a/lisp/org-attach.el
+++ b/lisp/org-attach.el
@@ -674,7 +674,7 @@ (defun org-attach-sync ()
       (let ((files (org-attach-file-list attach-dir)))
 	(org-attach-tag (not files)))
       (when org-attach-sync-delete-empty-dir
-        (when (and (org-directory-empty-p attach-dir)
+        (when (and (directory-empty-p attach-dir)
                    (if (eq 'query org-attach-sync-delete-empty-dir)
                        (yes-or-no-p "Attachment directory is empty.  Delete?")
                      t))
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index b96e9f336..c5b1c6d50 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -1323,9 +1323,9 @@ (defun org-capture-place-item ()
 	      ;; prioritize the existing list.
 	      (when prepend?
 		(let ((ordered? (eq 'ordered (org-element-property :type item))))
-		  (when (org-xor ordered?
-				 (string-match-p "\\`[A-Za-z0-9]\\([.)]\\)"
-						 template))
+		  (when (xor ordered?
+			     (string-match-p "\\`[A-Za-z0-9]\\([.)]\\)"
+					     template))
 		    (org-cycle-list-bullet (if ordered? "1." "-")))))
 	      ;; Eventually repair the list for proper indentation and
 	      ;; bullets.
@@ -1867,7 +1867,7 @@ (defun org-capture-fill-template (&optional template initial annotation)
 		     (setq org-capture--prompt-history
 			   (gethash prompt org-capture--prompt-history-table))
                      (push (org-completing-read
-                            (org-format-prompt (or prompt "Enter string") default)
+                            (format-prompt (or prompt "Enter string") default)
 			    completions
 			    nil nil nil 'org-capture--prompt-history default)
 			   strings)
diff --git a/lisp/org-compat.el b/lisp/org-compat.el
index e5b22d623..046a3db97 100644
--- a/lisp/org-compat.el
+++ b/lisp/org-compat.el
@@ -79,9 +79,6 @@ (declare-function org-fold-region "org-fold" (from to flag &optional spec))
 (declare-function org-fold-show-all "org-fold" (&optional types))
 (declare-function org-fold-show-children "org-fold" (&optional level))
 (declare-function org-fold-show-entry "org-fold" (&optional hide-drawers))
-;; `org-string-equal-ignore-case' is in _this_ file but isn't at the
-;; top-level.
-(declare-function org-string-equal-ignore-case "org-compat" (string1 string2))
 
 (defvar calendar-mode-map)
 (defvar org-complex-heading-regexp)
@@ -136,105 +133,26 @@ (defmacro org-compat-call (fun &rest args)
   (let ((compat (intern (format "compat--%s" fun))))
     `(,(if (fboundp compat) compat fun) ,@args)))
 
-\f
-;;; Emacs < 29 compatibility
-
-(defvar org-file-has-changed-p--hash-table (make-hash-table :test #'equal)
-  "Internal variable used by `org-file-has-changed-p'.")
-
-(if (fboundp 'file-has-changed-p)
-    (defalias 'org-file-has-changed-p #'file-has-changed-p)
-  (defun org-file-has-changed-p (file &optional tag)
-    "Return non-nil if FILE has changed.
-The size and modification time of FILE are compared to the size
-and modification time of the same FILE during a previous
-invocation of `org-file-has-changed-p'.  Thus, the first invocation
-of `org-file-has-changed-p' always returns non-nil when FILE exists.
-The optional argument TAG, which must be a symbol, can be used to
-limit the comparison to invocations with identical tags; it can be
-the symbol of the calling function, for example."
-    (let* ((file (directory-file-name (expand-file-name file)))
-           (remote-file-name-inhibit-cache t)
-           (fileattr (file-attributes file 'integer))
-	   (attr (and fileattr
-                      (cons (file-attribute-size fileattr)
-		            (file-attribute-modification-time fileattr))))
-	   (sym (concat (symbol-name tag) "@" file))
-	   (cachedattr (gethash sym org-file-has-changed-p--hash-table)))
-      (when (not (equal attr cachedattr))
-        (puthash sym attr org-file-has-changed-p--hash-table)))))
-
-(if (fboundp 'string-equal-ignore-case)
-    (defalias 'org-string-equal-ignore-case #'string-equal-ignore-case)
-  ;; From Emacs subr.el.
-  (defun org-string-equal-ignore-case (string1 string2)
-    "Like `string-equal', but case-insensitive.
-Upper-case and lower-case letters are treated as equal.
-Unibyte strings are converted to multibyte for comparison."
-    (eq t (compare-strings string1 0 nil string2 0 nil t))))
-
-\f
-;;; Emacs < 28.1 compatibility
-
-(if (fboundp 'file-name-concat)
-    (defalias 'org-file-name-concat #'file-name-concat)
-  (defun org-file-name-concat (directory &rest components)
-    "Append COMPONENTS to DIRECTORY and return the resulting string.
-
-Elements in COMPONENTS must be a string or nil.
-DIRECTORY or the non-final elements in COMPONENTS may or may not end
-with a slash -- if they don't end with a slash, a slash will be
-inserted before contatenating."
-    (save-match-data
-      (mapconcat
-       #'identity
-       (delq nil
-             (mapcar
-              (lambda (str)
-                (when (and str (not (seq-empty-p str))
-                           (string-match "\\(.+\\)/?" str))
-                  (match-string 1 str)))
-              (cons directory components)))
-       "/"))))
-
-(if (fboundp 'directory-empty-p)
-    (defalias 'org-directory-empty-p #'directory-empty-p)
-  (defun org-directory-empty-p (dir)
-    "Return t if DIR names an existing directory containing no other files."
-    (and (file-directory-p dir)
-         (null (directory-files dir nil directory-files-no-dot-files-regexp t)))))
-
-(if (fboundp 'string-clean-whitespace)
-    (defalias 'org-string-clean-whitespace #'string-clean-whitespace)
-  ;; From Emacs subr-x.el.
-  (defun org-string-clean-whitespace (string)
-    "Clean up whitespace in STRING.
-All sequences of whitespaces in STRING are collapsed into a
-single space character, and leading/trailing whitespace is
-removed."
-    (let ((blank "[[:blank:]\r\n]+"))
-      (string-trim (replace-regexp-in-string blank " " string t t)
-                   blank blank))))
-
-(if (fboundp 'format-prompt)
-    (defalias 'org-format-prompt #'format-prompt)
-  ;; From Emacs minibuffer.el, inlining
-  ;; `minibuffer-default-prompt-format' value and replacing `length<'
-  ;; (both new in Emacs 28.1).
-  (defun org-format-prompt (prompt default &rest format-args)
-    "Compatibility substitute for `format-prompt'."
-    (concat
-     (if (null format-args)
-         prompt
-       (apply #'format prompt format-args))
-     (and default
-          (or (not (stringp default))
-              (> (length default) 0))
-          (format " (default %s)"
-                  (if (consp default)
-                      (car default)
-                    default)))
-     ": ")))
+;; Obsolete compatibility wrappers used before inclusion of compat.el.
+
+(define-obsolete-function-alias 'org-file-has-changed-p
+  'file-has-changed-p "9.7")
+(define-obsolete-function-alias 'org-string-equal-ignore-case
+  'string-equal-ignore-case "9.7")
+(define-obsolete-function-alias 'org-file-name-concat
+  'file-name-concat "9.7")
+(define-obsolete-function-alias 'org-directory-empty-p
+  'directory-empty-p "9.7")
+(define-obsolete-function-alias 'org-string-clean-whitespace
+  'string-clean-whitespace "9.7")
+(define-obsolete-function-alias 'org-format-prompt
+  'format-prompt "9.7")
+(define-obsolete-function-alias 'org-xor
+  'xor "9.7")
+(define-obsolete-function-alias 'org-string-distance
+  'string-distance "9.7")
+(define-obsolete-function-alias 'org-buffer-hash
+  'buffer-hash "9.7")
 
 \f
 ;;; Emacs < 27.1 compatibility
@@ -252,22 +170,6 @@ (if (version< emacs-version "27.1")
       (replace-buffer-contents source))
   (defalias 'org-replace-buffer-contents #'replace-buffer-contents))
 
-(unless (fboundp 'proper-list-p)
-  ;; `proper-list-p' was added in Emacs 27.1.  The function below is
-  ;; taken from Emacs subr.el 200195e824b^.
-  (defun proper-list-p (object)
-    "Return OBJECT's length if it is a proper list, nil otherwise.
-A proper list is neither circular nor dotted (i.e., its last cdr
-is nil)."
-    (and (listp object) (ignore-errors (length object)))))
-
-(if (fboundp 'xor)
-    ;; `xor' was added in Emacs 27.1.
-    (defalias 'org-xor #'xor)
-  (defsubst org-xor (a b)
-    "Exclusive `or'."
-    (if a (not b) b)))
-
 (unless (fboundp 'pcomplete-uniquify-list)
   ;; The misspelled variant was made obsolete in Emacs 27.1
   (defalias 'pcomplete-uniquify-list 'pcomplete-uniqify-list))
@@ -297,29 +199,6 @@ (defun org--set-faces-extend (faces extend-p)
   (when (fboundp 'set-face-extend)
     (mapc (lambda (f) (set-face-extend f extend-p)) faces)))
 
-(if (fboundp 'string-distance)
-    (defalias 'org-string-distance 'string-distance)
-  (defun org-string-distance (s1 s2)
-    "Return the edit (levenshtein) distance between strings S1 S2."
-    (let* ((l1 (length s1))
-	   (l2 (length s2))
-	   (dist (vconcat (mapcar (lambda (_) (make-vector (1+ l2) nil))
-				  (number-sequence 1 (1+ l1)))))
-	   (in (lambda (i j) (aref (aref dist i) j))))
-      (setf (aref (aref dist 0) 0) 0)
-      (dolist (j (number-sequence 1 l2))
-        (setf (aref (aref dist 0) j) j))
-      (dolist (i (number-sequence 1 l1))
-        (setf (aref (aref dist i) 0) i)
-        (dolist (j (number-sequence 1 l2))
-	  (setf (aref (aref dist i) j)
-	        (min
-	         (1+ (funcall in (1- i) j))
-	         (1+ (funcall in i (1- j)))
-	         (+ (if (equal (aref s1 (1- i)) (aref s2 (1- j))) 0 1)
-		    (funcall in (1- i) (1- j)))))))
-      (funcall in l1 l2))))
-
 (define-obsolete-function-alias 'org-babel-edit-distance 'org-string-distance
   "9.5")
 
@@ -340,23 +219,6 @@ (if (fboundp 'line-number-display-width)
     (defalias 'org-line-number-display-width 'line-number-display-width)
   (defun org-line-number-display-width (&rest _) 0))
 
-(if (fboundp 'buffer-hash)
-    (defalias 'org-buffer-hash 'buffer-hash)
-  (defun org-buffer-hash () (md5 (current-buffer))))
-
-(unless (fboundp 'file-attribute-modification-time)
-  (defsubst file-attribute-modification-time (attributes)
-    "The modification time in ATTRIBUTES returned by `file-attributes'.
-This is the time of the last change to the file's contents, and
-is a Lisp timestamp in the same style as `current-time'."
-    (nth 5 attributes)))
-
-(unless (fboundp 'file-attribute-size)
-  (defsubst file-attribute-size (attributes)
-    "The size (in bytes) in ATTRIBUTES returned by `file-attributes'.
-This is a floating point number if the size is too large for an integer."
-    (nth 7 attributes)))
-
 \f
 ;;; Obsolete aliases (remove them after the next major release).
 
@@ -1458,7 +1320,7 @@ (defun org-mode-flyspell-verify ()
 	  (and log
 	       (let ((drawer (org-element-lineage element '(drawer))))
 		 (and drawer
-		      (org-string-equal-ignore-case
+		      (string-equal-ignore-case
 		       log (org-element-property :drawer-name drawer))))))
 	nil)
        (t
diff --git a/lisp/org-fold-core.el b/lisp/org-fold-core.el
index 43c6b2b74..c699c115b 100644
--- a/lisp/org-fold-core.el
+++ b/lisp/org-fold-core.el
@@ -841,7 +841,7 @@ (defun org-fold-core-next-visibility-change (&optional pos limit ignore-hidden-p
 			  (lambda (p) (next-single-char-property-change p 'invisible nil limit)))))
 	 (next pos))
     (while (and (funcall cmp next limit)
-		(not (org-xor
+		(not (xor
                     invisible-initially?
                     (funcall invisible-p
                              (if previous-p
diff --git a/lisp/org-lint.el b/lisp/org-lint.el
index 0e2967b6c..adc893aff 100644
--- a/lisp/org-lint.el
+++ b/lisp/org-lint.el
@@ -383,7 +383,7 @@ (defun org-lint-duplicate-custom-id (ast)
    ast
    'node-property
    (lambda (property)
-     (and (org-string-equal-ignore-case
+     (and (string-equal-ignore-case
            "CUSTOM_ID" (org-element-property :key property))
 	  (org-element-property :value property)))
    (lambda (property _) (org-element-property :begin property))
diff --git a/lisp/org-persist.el b/lisp/org-persist.el
index 8e73fbc4b..21c09567f 100644
--- a/lisp/org-persist.el
+++ b/lisp/org-persist.el
@@ -279,12 +279,12 @@ (defgroup org-persist nil
 
 (defcustom org-persist-directory
   (expand-file-name
-   (org-file-name-concat
+   (file-name-concat
     (let ((cache-dir (when (fboundp 'xdg-cache-home)
                        (xdg-cache-home))))
       (if (or (seq-empty-p cache-dir)
               (not (file-exists-p cache-dir))
-              (file-exists-p (org-file-name-concat
+              (file-exists-p (file-name-concat
                               user-emacs-directory
                               "org-persist")))
           user-emacs-directory
@@ -675,13 +675,13 @@ (defalias 'org-persist-read:version #'org-persist-read:elisp-data)
 
 (defun org-persist-read:file (_ path __)
   "Read file container from PATH."
-  (when (and path (file-exists-p (org-file-name-concat org-persist-directory path)))
-    (org-file-name-concat org-persist-directory path)))
+  (when (and path (file-exists-p (file-name-concat org-persist-directory path)))
+    (file-name-concat org-persist-directory path)))
 
 (defun org-persist-read:url (_ path __)
   "Read file container from PATH."
-  (when (and path (file-exists-p (org-file-name-concat org-persist-directory path)))
-    (org-file-name-concat org-persist-directory path)))
+  (when (and path (file-exists-p (file-name-concat org-persist-directory path)))
+    (file-name-concat org-persist-directory path)))
 
 (defun org-persist-read:index (cont index-file _)
   "Read index container CONT from INDEX-FILE."
@@ -750,7 +750,7 @@ (defun org-persist--load-index ()
   "Load `org-persist--index'."
   (org-persist-load:index
    `(index ,org-persist--storage-version)
-   (org-file-name-concat org-persist-directory org-persist-index-file)
+   (file-name-concat org-persist-directory org-persist-index-file)
    nil))
 
 ;;;; Writing container data
@@ -804,7 +804,7 @@ (defun org-persist-write:file (c collection)
         (setq path (cadr c)))
       (let* ((persist-file (plist-get collection :persist-file))
              (ext (file-name-extension path))
-             (file-copy (org-file-name-concat
+             (file-copy (file-name-concat
                          org-persist-directory
                          (format "%s-%s.%s" persist-file (md5 path) ext))))
         (unless (file-exists-p file-copy)
@@ -850,7 +850,7 @@ (defun org-persist-write:index (container _)
                  org-persist-directory))))
   (when (file-exists-p org-persist-directory)
     (let ((index-file
-           (org-file-name-concat org-persist-directory org-persist-index-file)))
+           (file-name-concat org-persist-directory org-persist-index-file)))
       (org-persist--merge-index-with-disk)
       (org-persist--write-elisp-file index-file org-persist--index t t)
       (setq org-persist--index-age
@@ -865,7 +865,7 @@ (defun org-persist--save-index ()
 (defun org-persist--merge-index-with-disk ()
   "Merge `org-persist--index' with the current index file on disk."
   (let* ((index-file
-          (org-file-name-concat org-persist-directory org-persist-index-file))
+          (file-name-concat org-persist-directory org-persist-index-file))
          (disk-index
           (and (file-exists-p index-file)
                (org-file-newer-than-p index-file org-persist--index-age)
@@ -887,8 +887,8 @@ (defun org-persist--merge-index (base other)
         (dolist (item (nreverse new))
           (unless (or (memq 'index (mapcar #'car (plist-get item :container)))
                       (not (file-exists-p
-                            (org-file-name-concat org-persist-directory
-                                                  (plist-get item :persist-file))))
+                          (file-name-concat org-persist-directory
+                                            (plist-get item :persist-file))))
                       (member (plist-get item :persist-file) base-files))
             (push item combined)))
         (nreverse combined))
@@ -989,7 +989,7 @@ (cl-defun org-persist-read (container &optional associated hash-must-match load
   (let* ((collection (org-persist--find-index `(:container ,container :associated ,associated)))
          (persist-file
           (when collection
-            (org-file-name-concat
+            (file-name-concat
              org-persist-directory
              (plist-get collection :persist-file))))
          (data nil))
@@ -1077,7 +1077,7 @@ (defun org-persist-write (container &optional associated ignore-return)
                          (run-hook-with-args-until-success 'org-persist-before-write-hook v associated))
                        (plist-get collection :container)))
       (when (or (file-exists-p org-persist-directory) (org-persist--save-index))
-        (let ((file (org-file-name-concat org-persist-directory (plist-get collection :persist-file)))
+        (let ((file (file-name-concat org-persist-directory (plist-get collection :persist-file)))
               (data (mapcar (lambda (c) (cons c (org-persist-write:generic c collection)))
                             (plist-get collection :container))))
           (puthash file data org-persist--write-cache)
@@ -1097,11 +1097,11 @@ (defun org-persist-write-all (&optional associated)
            ;; The container is an `index' container.
            (eq 'index (caar (plist-get (car org-persist--index) :container)))
            (or (not (file-exists-p org-persist-directory))
-               (org-directory-empty-p org-persist-directory)))
+               (directory-empty-p org-persist-directory)))
       ;; Do not write anything, and clear up `org-persist-directory' to reduce
       ;; clutter.
       (when (and (file-exists-p org-persist-directory)
-                 (org-directory-empty-p org-persist-directory))
+                 (directory-empty-p org-persist-directory))
         (delete-directory org-persist-directory))
     ;; Write the data.
     (let (all-containers)
@@ -1142,7 +1142,7 @@ (defun org-persist--gc-persist-file (persist-file)
   "Garbage collect PERSIST-FILE."
   (when (file-exists-p persist-file)
     (delete-file persist-file)
-    (when (org-directory-empty-p (file-name-directory persist-file))
+    (when (directory-empty-p (file-name-directory persist-file))
       (delete-directory (file-name-directory persist-file)))))
 
 (defmacro org-persist-associated-files:generic (container collection)
@@ -1180,7 +1180,7 @@ (defun org-persist-gc ()
   (let (new-index
         (remote-files-num 0)
         (orphan-files
-         (delete (org-file-name-concat org-persist-directory org-persist-index-file)
+         (delete (file-name-concat org-persist-directory org-persist-index-file)
                  (when (file-exists-p org-persist-directory)
                    (directory-files-recursively org-persist-directory ".+")))))
     (dolist (collection org-persist--index)
@@ -1188,7 +1188,7 @@ (defun org-persist-gc ()
              (web-file (and file (string-match-p "\\`https?://" file)))
              (file-remote (when file (file-remote-p file)))
              (persist-file (when (plist-get collection :persist-file)
-                             (org-file-name-concat
+                             (file-name-concat
                               org-persist-directory
                               (plist-get collection :persist-file))))
              (expired? (org-persist--gc-expired-p
diff --git a/lisp/org-refile.el b/lisp/org-refile.el
index 03c351cf6..9797a0633 100644
--- a/lisp/org-refile.el
+++ b/lisp/org-refile.el
@@ -666,7 +666,7 @@ (defun org-refile-get-location (&optional prompt default-buffer new-nodes)
          (prompt (let ((default (or (car org-refile-history)
                                     (and (assoc cbnex tbl) (setq cdef cbnex)
                                          cbnex))))
-                   (org-format-prompt prompt default)))
+                   (format-prompt prompt default)))
 	 pa answ parent-target child parent old-hist)
     (setq old-hist org-refile-history)
     (setq answ (funcall cfunc prompt tbl nil (not new-nodes)
diff --git a/lisp/org.el b/lisp/org.el
index 5e52608ca..e9fa9a241 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -19445,7 +19445,7 @@ (defun org-fill-paragraph (&optional justify region)
 		 (barf-if-buffer-read-only)
 		 (list (when current-prefix-arg 'full) t)))
   (let ((hash (and (not (buffer-modified-p))
-		   (org-buffer-hash))))
+		   (buffer-hash))))
     (cond
      ((and region transient-mark-mode mark-active
 	   (not (eq (region-beginning) (region-end))))
@@ -19470,7 +19470,7 @@ (defun org-fill-paragraph (&optional justify region)
     ;; If we didn't change anything in the buffer (and the buffer was
     ;; previously unmodified), then flip the modification status back
     ;; to "unchanged".
-    (when (and hash (equal hash (org-buffer-hash)))
+    (when (and hash (equal hash (buffer-hash)))
       (set-buffer-modified-p nil))
     ;; Return non-nil.
     t))
diff --git a/lisp/ox.el b/lisp/ox.el
index a6169ea63..203136994 100644
--- a/lisp/ox.el
+++ b/lisp/ox.el
@@ -4626,11 +4626,11 @@ (defun org-export-resolve-radio-link (link info)
 
 Return value can be a radio-target object or nil.  Assume LINK
 has type \"radio\"."
-  (let ((path (org-string-clean-whitespace (org-element-property :path link))))
+  (let ((path (string-clean-whitespace (org-element-property :path link))))
     (org-element-map (plist-get info :parse-tree) 'radio-target
       (lambda (radio)
-	(and (org-string-equal-ignore-case
-	      (org-string-clean-whitespace (org-element-property :value radio))
+	(and (string-equal-ignore-case
+	      (string-clean-whitespace (org-element-property :value radio))
               path)
 	     radio))
       info 'first-match)))
diff --git a/mk/default.mk b/mk/default.mk
index 997b22b66..4ad1a4281 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -38,7 +38,7 @@ pkgdir = $(shell pwd)/pkg-deps
 EFLAGS ?=
 
 # Third-party packages to install when running make
-EPACKAGES ?=
+EPACKAGES ?= compat
 
 # Configuration for testing
 # Verbose ERT summary by default for Emacs-28 and above.
diff --git a/testing/lisp/test-ol.el b/testing/lisp/test-ol.el
index a38d9f979..565539571 100644
--- a/testing/lisp/test-ol.el
+++ b/testing/lisp/test-ol.el
@@ -63,19 +63,19 @@ (ert-deftest test-org-link/toggle-link-display ()
       (dotimes (_ 2)
         (goto-char 1)
         (re-search-forward "\\[")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "example")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "com")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "]")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "\\[")
         (should-not (org-invisible-p))
         (re-search-forward "link")
         (should-not (org-invisible-p))
         (re-search-forward "]")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (org-toggle-link-display)))))
 
 \f
diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el
index 0ed44c6af..6a47b3384 100644
--- a/testing/lisp/test-org-capture.el
+++ b/testing/lisp/test-org-capture.el
@@ -154,7 +154,7 @@ (ert-deftest test-org-capture/abort ()
   "Test aborting a capture process."
   ;; Newly create capture buffer should not be saved.
   (let ((capture-file (make-temp-name
-                       (org-file-name-concat
+                       (file-name-concat
                         temporary-file-directory
                         "org-test"))))
     (unwind-protect
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #5: 0004-org-manual.org-Document-compat-library-installation.patch --]
[-- Type: text/x-patch, Size: 2242 bytes --]

From a5a5f3b14ce81279c6ba8d9bad2a58c83ab88ac4 Mon Sep 17 00:00:00 2001
Message-Id: <a5a5f3b14ce81279c6ba8d9bad2a58c83ab88ac4.1680511419.git.yantar92@posteo.net>
In-Reply-To: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
References: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sat, 1 Apr 2023 12:18:57 +0200
Subject: [PATCH 4/7] org-manual.org: Document compat library installation

* doc/org-manual.org (Using Org's git repository): Document that users
must also install compat library when using git version of Org.
* etc/ORG-NEWS (Org mode now uses =compat.el= third-party package to
support older Emacs versions): Announce amendments to the Org
installation from git sources.
---
 doc/org-manual.org | 4 ++++
 etc/ORG-NEWS       | 7 +++++++
 2 files changed, 11 insertions(+)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 50662669e..aa9886e5f 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -140,6 +140,10 @@ *** Using Org's git repository
 (add-to-list 'load-path "~/src/org-mode/lisp")
 #+end_src
 
+You must also manually install =compat= library required by Org mode.
+Using built-in =package.el=, you can run =M-x package-install <RET>
+compat <RET>=.
+
 You can also compile with =make=, generate the documentation with
 =make doc=, create a local configuration with =make config= and
 install Org with =make install=.  Please run =make help= to get the
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index ac233a986..0302b8cda 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -13,6 +13,13 @@ Please send Org bug reports to mailto:emacs-orgmode@gnu.org.
 
 * Version 9.7 (not released yet)
 ** Important announcements and breaking changes
+*** Org mode now uses =compat.el= third-party package to support older Emacs versions
+
+This change is mostly technical and should not affect most users.
+However, people who install Org from git source might be affected.
+It is now necessary to manually install =compat.el= using Emacs'
+package manager.
+
 *** =python-mode.el (MELPA)= support in =ob-python.el= is removed
 
 =python-mode.el= support has been removed from =ob-python.el=.  The
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #6: 0005-mk-Expand-shell-commands-once-only.patch --]
[-- Type: text/x-patch, Size: 2303 bytes --]

From 5bb17634a14bb1af4944f73465552cbfd7068a77 Mon Sep 17 00:00:00 2001
Message-Id: <5bb17634a14bb1af4944f73465552cbfd7068a77.1680511419.git.yantar92@posteo.net>
In-Reply-To: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
References: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sun, 2 Apr 2023 13:53:47 +0200
Subject: [PATCH 5/7] mk: Expand shell commands once only

* mk/default.mk (pkgdir):
* mk/targets.mk (GITVERSION):
(GITSTATUS):
(DATE):
(YEAR): Only expand the variables once, not in every make sub-process.
Previous code ran pwd/date/git in every make sub-process, slowing
things down significantly and unnecessarily.
---
 mk/default.mk | 2 +-
 mk/targets.mk | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/mk/default.mk b/mk/default.mk
index 4ad1a4281..b43a9b3ac 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -32,7 +32,7 @@ TMPDIR ?= /tmp
 testdir = $(TMPDIR)/tmp-orgtest
 
 # Where to store Org dependencies
-pkgdir = $(shell pwd)/pkg-deps
+pkgdir := $(shell pwd)/pkg-deps
 
 # Extra flags to be passed to Emacs
 EFLAGS ?=
diff --git a/mk/targets.mk b/mk/targets.mk
index ab8b830bb..18faa02f5 100644
--- a/mk/targets.mk
+++ b/mk/targets.mk
@@ -14,15 +14,15 @@ ifneq ($(wildcard .git),)
   # Use the org.el header.
   ORGVERSION := $(patsubst %-dev,%,$(shell $(BATCH) --eval "(require 'lisp-mnt)" \
     --visit lisp/org.el --eval '(princ (lm-header "version"))'))
-  GITVERSION ?= $(shell git describe --match release\* --abbrev=6 HEAD 2>/dev/null || echo  "release_N/A-N/A-$(shell git describe --match release\* --abbrev=6 --always HEAD)")
-  GITSTATUS  ?= $(shell git status -uno --porcelain)
+  GITVERSION := $(shell git describe --match release\* --abbrev=6 HEAD 2>/dev/null || echo  "release_N/A-N/A-$(shell git describe --match release\* --abbrev=6 --always HEAD)")
+  GITSTATUS  := $(shell git status -uno --porcelain)
 else
  -include mk/version.mk
   GITVERSION ?= N/A
   ORGVERSION ?= N/A
 endif
-DATE          = $(shell date +%Y-%m-%d)
-YEAR          = $(shell date +%Y)
+DATE          := $(shell date +%Y-%m-%d)
+YEAR          := $(shell date +%Y)
 ifneq ($(GITSTATUS),)
   GITVERSION := $(GITVERSION:.dirty=).dirty
 endif
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #7: 0006-mk-Echo-main-compilation-states-when-running-make.patch --]
[-- Type: text/x-patch, Size: 2268 bytes --]

From 9c22dcd8cb0ea0e17e322bbad3d62975572ea80c Mon Sep 17 00:00:00 2001
Message-Id: <9c22dcd8cb0ea0e17e322bbad3d62975572ea80c.1680511419.git.yantar92@posteo.net>
In-Reply-To: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
References: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sun, 2 Apr 2023 14:05:22 +0200
Subject: [PATCH 6/7] mk: Echo main compilation states when running make

* lisp/Makefile (all compile compile-dirty):
($(LISPV)):
($(LISPI)): Echo compile stage.
* mk/targets.mk (uppkg): Echo compile stage and hide the installation
command.
---
 lisp/Makefile | 7 +++++--
 mk/targets.mk | 7 ++++---
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/lisp/Makefile b/lisp/Makefile
index f507f18a2..45d8109f0 100644
--- a/lisp/Makefile
+++ b/lisp/Makefile
@@ -20,6 +20,7 @@ _ORGCM_ := dirall single source slint1 slint2
 
 # 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
@@ -52,12 +53,14 @@ slint1:
 autoloads:	cleanauto $(LISPI) $(LISPV)
 
 $(LISPV):	$(LISPF)
-	@echo "org-version: $(ORGVERSION) ($(GITVERSION))"
+	@$(info ========= Auto-generating Org version number)
+	@$(info org-version: $(ORGVERSION) ($(GITVERSION)))
 	@$(RM) $(@)
 	@$(MAKE_ORG_VERSION)
 
 $(LISPI):	$(LISPV) $(LISPF)
-	@echo "org-loaddefs: $(ORGVERSION) ($(GITVERSION))"
+	@$(info ========= Auto-generating Org loaddefs)
+	@$(info org-loaddefs: $(ORGVERSION) ($(GITVERSION)))
 	@$(RM) $(@)
 	@$(MAKE_ORG_INSTALL)
 
diff --git a/mk/targets.mk b/mk/targets.mk
index 18faa02f5..072106237 100644
--- a/mk/targets.mk
+++ b/mk/targets.mk
@@ -103,9 +103,10 @@ ifeq ($(TEST_NO_AUTOCLEAN),) # define this variable to leave $(testdir) around f
 endif
 
 uppkg::
-	-$(MKDIR) -p $(pkgdir)
-	-$(FIND) $(pkgdir) \(  -name \*.elc \) -exec $(RM) {} +
-	$(INSTALL_PACKAGES)
+	$(info ========= Installing required third-party packages)
+	@$(MKDIR) -p $(pkgdir)
+	@$(FIND) $(pkgdir) \(  -name \*.elc \) -exec $(RM) {} +
+	-@$(INSTALL_PACKAGES)
 
 up0 up1 up2::
 	git checkout $(GIT_BRANCH)
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #8: 0007-mk-Do-not-run-package-refresh-packages-unless-necess.patch --]
[-- Type: text/x-patch, Size: 2322 bytes --]

From 53da71e055630ef8bc6b1191f11a4ec03dabd8c7 Mon Sep 17 00:00:00 2001
Message-Id: <53da71e055630ef8bc6b1191f11a4ec03dabd8c7.1680511419.git.yantar92@posteo.net>
In-Reply-To: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
References: <313f2b6acdf67f29f4f5f671e8278bb3e566cabf.1680511418.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sun, 2 Apr 2023 15:11:06 +0200
Subject: [PATCH 7/7] mk: Do not run `package-refresh-packages' unless
 necessary

* mk/default.mk (package-install):
(package-define-refresh-mark):
(package-need-refresh-mark):
(package-refresh-maybe):
(package-install-maybe):
(INSTALL_PACKAGES): Update the command avoiding
`package-refresh-packages' when all the required third-party packages
are already present in the `load-path'.
---
 mk/default.mk | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/mk/default.mk b/mk/default.mk
index b43a9b3ac..67bf96c2e 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -81,7 +81,10 @@ REPRO_ARGS ?=
 req-ob-lang = --eval '(require '"'"'ob-$(ob-lang))'
 lst-ob-lang = ($(ob-lang) . t)
 req-extra   = --eval '(require '"'"'$(req))'
-package-install = --eval '(unless (require '"'"'$(package) nil t) (message "%s" load-path) (package-install '"'"'$(package)))'
+package-define-refresh-mark = --eval '(setq org--compile-packages-missing nil)'
+package-need-refresh-mark = --eval '(unless (require '"'"'$(package) nil t) (setq org--compile-packages-missing t))'
+package-refresh-maybe = --eval '(if org--compile-packages-missing (package-refresh-contents) (message "No third-party packages need to be installed"))'
+package-install-maybe = --eval '(unless (require '"'"'$(package) nil t) (package-install '"'"'$(package)))'
 BTEST_RE   ?= \\(org\\|ob\\|ox\\)
 BTEST_LOAD  = \
 	--eval '(add-to-list '"'"'load-path (concat default-directory "lisp"))' \
@@ -93,8 +96,8 @@ INSTALL_PACKAGES =
 else
 INSTALL_PACKAGES = \
 	$(BATCH) \
-        --eval '(package-refresh-contents)' \
-        $(foreach package,$(EPACKAGES),$(package-install))
+        $(package-define-refresh-mark) $(foreach package,$(EPACKAGES),$(package-need-refresh-mark)) $(package-refresh-maybe) \
+        $(foreach package,$(EPACKAGES),$(package-install-maybe))
 endif
 
 BTEST = $(BATCH) $(BTEST_INIT) \
-- 
2.40.0


[-- Attachment #9: Type: text/plain, Size: 224 bytes --]


-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* Re: [PATCH v2] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-02 17:00       ` [PATCH v2] " Ihor Radchenko
  2023-04-03  8:46         ` [PATCH v3] " Ihor Radchenko
@ 2023-04-08 11:15         ` Max Nikulin
  2023-04-08 11:41           ` Ihor Radchenko
  1 sibling, 1 reply; 36+ messages in thread
From: Max Nikulin @ 2023-04-08 11:15 UTC (permalink / raw)
  To: emacs-orgmode

On 03/04/2023 00:00, Ihor Radchenko wrote:
> Max Nikulin writes:
> 
>> Ihor, do added makefile rules follow best practices used by other Emacs
>> packages in respect to dependencies?
> I know no other Emacs packages that manage dependencies using make.

org-ql uses a helper shell script. org-roam and projectile use eldev 
that is procedural, not declarative build system. I have not figured out 
which way magit handles dependencies. I have no more ideas what projects 
may involve unit tests and dependencies.

Actually Org uses make just as interface, not as a declarative build 
system that is able to minimize work by updating only necessary targets. 
Build rules are effectively procedural: complete rebuild on every 
invocation.

>> I do not like the idea of network queries on every make.
> Any better suggestions?

Do not run install dependencies for regular targets like test or 
compile. At least do not do it unless it is explicitly requested by 
command line argument or a variable in local.mk

>>> +pkgdir = $(shell pwd)/pkg-deps
>>
>> Make has CURDIR variable, but I am unsure if it is safe to use it in 
>> this context.
> 
> Actually, we need pkgdir := $(shell pwd)/pkg-deps.
> CURDIR is wrong because default.mk will trigger evaluation in every make
> sub-process as well.

default.mk is included from top level Makefile only. By the way, it is 
better to explicitly express that path is relative to top project 
directory by defining e.g. top_builddir (or abs_top_builddir) at first and

pkgdir = $(top_builddir)/pkg-deps

Today I noticed the following trick in (likely stale) 
https://github.com/org-roam/org-roam/blob/main/default.mk

TOP := $(dir $(lastword $(MAKEFILE_LIST)))

I have not realized if it is safe enough.





^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH v2] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-08 11:15         ` [PATCH v2] " Max Nikulin
@ 2023-04-08 11:41           ` Ihor Radchenko
  2023-04-08 16:37             ` Max Nikulin
  0 siblings, 1 reply; 36+ messages in thread
From: Ihor Radchenko @ 2023-04-08 11:41 UTC (permalink / raw)
  To: Max Nikulin; +Cc: emacs-orgmode

Max Nikulin <manikulin@gmail.com> writes:

>> I know no other Emacs packages that manage dependencies using make.
>
> org-ql uses a helper shell script. org-roam and projectile use eldev 
> that is procedural, not declarative build system. I have not figured out 
> which way magit handles dependencies. I have no more ideas what projects 
> may involve unit tests and dependencies.

I see. Unfortunately, using third-party non-standard packages like
makem.sh or eldev will be more troublesome. We will need to sync Org
repo with them or demand users to install them separately.

Also, eldev is not on ELPA, and we cannot use it at all.

As for makem.sh, AFAIK, it will be even worse wrt network access - it
tries refreshing and downloading some packages by default, unless we
explicitly disable that feature. And under the hood, makem.sh anyway
does the same emacs --eval thing I used in the patch.

>>> I do not like the idea of network queries on every make.
>> Any better suggestions?
>
> Do not run install dependencies for regular targets like test or 
> compile. At least do not do it unless it is explicitly requested by 
> command line argument or a variable in local.mk

compat.el is required for "compile" target. Compilation will simply fail
with older Emacs. And "test" triggers "compile".

>> Actually, we need pkgdir := $(shell pwd)/pkg-deps.
>> CURDIR is wrong because default.mk will trigger evaluation in every make
>> sub-process as well.
>
> default.mk is included from top level Makefile only.

Not sure here. I just noticed that GITVERSION got re-calculated many
times when looking at debug output of make. (It was slowing things down
significantly). GITVERSION is in targets.mk though.

> By the way, it is 
> better to explicitly express that path is relative to top project 
> directory by defining e.g. top_builddir (or abs_top_builddir) at first and
>
> pkgdir = $(top_builddir)/pkg-deps

How to define top_builddir? If also via `pwd`, I see not much difference.

> Today I noticed the following trick in (likely stale) 
> https://github.com/org-roam/org-roam/blob/main/default.mk
>
> TOP := $(dir $(lastword $(MAKEFILE_LIST)))
>
> I have not realized if it is safe enough.

Reading through 6.14 Other Special Variables section of make manual, it
does not look safe - MAKEFILE_LIST may be altered by presence of includes.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH v2] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-08 11:41           ` Ihor Radchenko
@ 2023-04-08 16:37             ` Max Nikulin
  2023-04-13 12:42               ` Ihor Radchenko
  0 siblings, 1 reply; 36+ messages in thread
From: Max Nikulin @ 2023-04-08 16:37 UTC (permalink / raw)
  To: emacs-orgmode

> +;; Package-Requires: ((emacs "26.1") (compat "29.1.4.1"))

Is there a way to express (or (compat "29.1.4.1") (emacs "28.1")) to 
avoid installing compat in the case of sufficiently new emacs? E.g. 
dpkg/apt allows such alternatives.

Early I asked concerning compat-29.1.3. I would prefer to install 
elpa-compat system package to avoid network activity in response to 
make. Required version matters for those who builds packages for 
backport repositories for various linux distributions. It allows to 
decide if it is necessary to provide newer elpa-compat or it is enough 
to package elpa-org.

On 08/04/2023 18:41, Ihor Radchenko wrote:
> 
> I see. Unfortunately, using third-party non-standard packages like
> makem.sh or eldev will be more troublesome. We will need to sync Org
> repo with them or demand users to install them separately.

I was looking for sources of inspiration. I do not suggest to take these 
tools. Perhaps somebody may suggest a better example of build scripts 
for Emacs packages.

>>>> I do not like the idea of network queries on every make.
>>> Any better suggestions?
>>
>> Do not run install dependencies for regular targets like test or
>> compile. At least do not do it unless it is explicitly requested by
>> command line argument or a variable in local.mk
> 
> compat.el is required for "compile" target. Compilation will simply fail
> with older Emacs. And "test" triggers "compile".

There are different types of build systems. Some of them allows to 
specify which dependencies should be pulled, some do not. My expectation 
is that make does not attempt to manage dependencies. For me it is OK to 
type an additional command to install them and to fail otherwise.

In my opinion

> +	@$(FIND) $(pkgdir) \(  -name \*.elc \) -exec $(RM) {} +

command tells that package management capabilities in Emacs are rather 
rudimentary (in comparison to e.g. python toolchain). That is why I am 
in favor to more manual dependency management. Notice that I am not 
against an option to do it from make.

Untested:

$(FIND) $(pkgdir) -name \*.elc -delete

"@" for silencing is intentionally dropped, parenthesis are unnecessary 
for single condition.

>>> Actually, we need pkgdir := $(shell pwd)/pkg-deps.
>>> CURDIR is wrong because default.mk will trigger evaluation in every make
>>> sub-process as well.
>>
>> default.mk is included from top level Makefile only.
> 
> Not sure here. I just noticed that GITVERSION got re-calculated many
> times when looking at debug output of make. (It was slowing things down
> significantly). GITVERSION is in targets.mk though.

GITVERSION is defined as ?=, so it is a variable with deferred 
evaluation. Immediately evaluated variable may defined using :=

>> pkgdir = $(top_builddir)/pkg-deps
> 
> How to define top_builddir? If also via `pwd`, I see not much difference.

I consider it as self-documenting code. Intermediate variable makes it 
apparent for readers that the directory is relative to the top of the 
package file tree.

Since out of source tree builds are not supported, I would consider 
adding emacs version to path. The idea is to allow keeping installed 
packages when switching between several emacs versions.

A note concerning variable name. For me it is associated for the 
directory where current package should be installed. I do not remember 
origin, but I noticed that such meaning is used in arch 
https://wiki.archlinux.org/title/creating_packages#Defining_PKGBUILD_variables 
Perhaps the same name is in gentoo in other sense that makes it suitable 
for storage of dependencies.



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH v2] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-08 16:37             ` Max Nikulin
@ 2023-04-13 12:42               ` Ihor Radchenko
  2023-04-17 17:20                 ` Max Nikulin
  0 siblings, 1 reply; 36+ messages in thread
From: Ihor Radchenko @ 2023-04-13 12:42 UTC (permalink / raw)
  To: Max Nikulin; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 4757 bytes --]

Max Nikulin <manikulin@gmail.com> writes:

>> +;; Package-Requires: ((emacs "26.1") (compat "29.1.4.1"))
>
> Is there a way to express (or (compat "29.1.4.1") (emacs "28.1")) to 
> avoid installing compat in the case of sufficiently new emacs? E.g. 
> dpkg/apt allows such alternatives.

No, AFAIK:

D.8 Conventional Headers for Emacs Libraries

‘Package-Requires’
     If this exists, it names packages on which the current package
     depends for proper operation.  *Note Packaging Basics::.  This is
     used by the package manager both at download time (to ensure that a
     complete set of packages is downloaded) and at activation time (to
     ensure that a package is only activated if all its dependencies
     have been).

     Its format is a list of lists on a single line.  The ‘car’ of each
     sub-list is the name of a package, as a symbol.  The ‘cadr’ of each
     sub-list is the minimum acceptable version number, as a string that
     can be parsed by ‘version-to-list’.  An entry that lacks a version
     (i.e., an entry which is just a symbol, or a sub-list of one
     element) is equivalent to entry with version "0".  For instance:

          ;; Package-Requires: ((gnus "1.0") (bubbles "2.7.2") cl-lib (seq))

     The package code automatically defines a package named ‘emacs’ with
     the version number of the currently running Emacs.  This can be
     used to require a minimal version of Emacs for a package.

> Early I asked concerning compat-29.1.3. I would prefer to install 
> elpa-compat system package to avoid network activity in response to 
> make. Required version matters for those who builds packages for 
> backport repositories for various linux distributions. It allows to 
> decide if it is necessary to provide newer elpa-compat or it is enough 
> to package elpa-org.

We can put older version require now, indeed. However, later we should
not constrain ourselves about bumping compat version as necessary.

Considering Debian lifecycle, it is pretty much guaranteed that we will
require the compat version that is not installed by Debian, eventually.

In any case, see the attached additional patch.

>> compat.el is required for "compile" target. Compilation will simply fail
>> with older Emacs. And "test" triggers "compile".
>
> There are different types of build systems. Some of them allows to 
> specify which dependencies should be pulled, some do not. My expectation 
> is that make does not attempt to manage dependencies. For me it is OK to 
> type an additional command to install them and to fail otherwise.

Sure. And you will have such option (EFLAGS).
However, I decided to enable auto-downloading by default to not break
the previous working compilation instructions.

It may, however, be worth documenting EFLAGS in WORG. See the attached
patch.

> In my opinion
>
>> +	@$(FIND) $(pkgdir) \(  -name \*.elc \) -exec $(RM) {} +
>
> command tells that package management capabilities in Emacs are rather 
> rudimentary (in comparison to e.g. python toolchain). That is why I am 
> in favor to more manual dependency management. Notice that I am not 
> against an option to do it from make.

May you elaborate what you mean by "more manual"? What concrete change
in the patch do you have in mind?

> Untested:
>
> $(FIND) $(pkgdir) -name \*.elc -delete
>
> "@" for silencing is intentionally dropped, parenthesis are unnecessary 
> for single condition.

Looks fine, except that now we have part of the targets using $RM
variable and part of the targets ignoring it. I am not sure if it is a
problem. I am slightly in favour of keeping the existing approach with
$RM.

>>> default.mk is included from top level Makefile only.
>> 
>> Not sure here. I just noticed that GITVERSION got re-calculated many
>> times when looking at debug output of make. (It was slowing things down
>> significantly). GITVERSION is in targets.mk though.
>
> GITVERSION is defined as ?=, so it is a variable with deferred 
> evaluation. Immediately evaluated variable may defined using :=

That's what I did in one of the patches (use :=).

>>> pkgdir = $(top_builddir)/pkg-deps
>> 
>> How to define top_builddir? If also via `pwd`, I see not much difference.
>
> I consider it as self-documenting code. Intermediate variable makes it 
> apparent for readers that the directory is relative to the top of the 
> package file tree.

Agree. Will do.
See the attached.

> Since out of source tree builds are not supported, I would consider 
> adding emacs version to path. The idea is to allow keeping installed 
> packages when switching between several emacs versions.

Done.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-mk-default.mk-pkgdir-Make-it-more-clear-that-we-inde.patch --]
[-- Type: text/x-patch, Size: 904 bytes --]

From c5ba5773e69d16930fc12db6fbe0d907fae926cd Mon Sep 17 00:00:00 2001
Message-Id: <c5ba5773e69d16930fc12db6fbe0d907fae926cd.1681389559.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sat, 8 Apr 2023 19:05:37 +0200
Subject: [PATCH 1/4] mk/default.mk (pkgdir): Make it more clear that we indent
 to use top git dir

* mk/default.mk (top_builddir): New variable storing top-level directory.
(pkgdir): Use the new variable.
---
 mk/default.mk | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/mk/default.mk b/mk/default.mk
index 67bf96c2e..92a3942da 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -32,7 +32,8 @@ TMPDIR ?= /tmp
 testdir = $(TMPDIR)/tmp-orgtest
 
 # Where to store Org dependencies
-pkgdir := $(shell pwd)/pkg-deps
+top_builddir := $(shell pwd)
+pkgdir := $(top_builddir)/pkg-deps
 
 # Extra flags to be passed to Emacs
 EFLAGS ?=
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-mk-Separate-third-party-package-dirs-for-different-E.patch --]
[-- Type: text/x-patch, Size: 2126 bytes --]

From ddebc0e86b43e655b057927ca9869143232c3ee5 Mon Sep 17 00:00:00 2001
Message-Id: <ddebc0e86b43e655b057927ca9869143232c3ee5.1681389559.git.yantar92@posteo.net>
In-Reply-To: <c5ba5773e69d16930fc12db6fbe0d907fae926cd.1681389559.git.yantar92@posteo.net>
References: <c5ba5773e69d16930fc12db6fbe0d907fae926cd.1681389559.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Mon, 10 Apr 2023 19:57:24 +0200
Subject: [PATCH 2/4] mk: Separate third-party package dirs for different Emacs
 versions

* mk/default.mk (EMACS_VERSION): New variable holding current Emacs
version, according to EMACS.
(pkgdir_top): New variable holding top pkgdir, parent of per-Emacs
version directories.
(pkgdir): Set as a sub-directory of pkgdir_top named as Emacs version.
* mk/targets.mk (uppkg): Do not clear .elc files.  This is no longer
needed as we do not need to worry about .elc being used by different
Emacs version.
(cleanpkg): Update, deleting the whole top pkgdir.
---
 mk/default.mk | 4 +++-
 mk/targets.mk | 3 +--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/mk/default.mk b/mk/default.mk
index 92a3942da..bd6efeb6f 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -6,6 +6,7 @@
 
 # Name of your emacs binary
 EMACS	= emacs
+EMACS_VERSION := $(shell $(EMACS) -Q --batch --eval '(message "%s" emacs-version)' 2>&1)
 
 # Where local software is found
 prefix	= /usr/share
@@ -33,7 +34,8 @@ testdir = $(TMPDIR)/tmp-orgtest
 
 # Where to store Org dependencies
 top_builddir := $(shell pwd)
-pkgdir := $(top_builddir)/pkg-deps
+pkgdir_top := $(top_builddir)/pkg-deps
+pkgdir := $(pkgdir_top)/$(EMACS_VERSION)
 
 # Extra flags to be passed to Emacs
 EFLAGS ?=
diff --git a/mk/targets.mk b/mk/targets.mk
index 072106237..2171159ac 100644
--- a/mk/targets.mk
+++ b/mk/targets.mk
@@ -105,7 +105,6 @@ endif
 uppkg::
 	$(info ========= Installing required third-party packages)
 	@$(MKDIR) -p $(pkgdir)
-	@$(FIND) $(pkgdir) \(  -name \*.elc \) -exec $(RM) {} +
 	-@$(INSTALL_PACKAGES)
 
 up0 up1 up2::
@@ -167,4 +166,4 @@ cleantest:
 	}
 
 cleanpkg:
-	-$(RMR) $(pkgdir)
+	-$(RMR) $(pkgdir_top)
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #4: 0003-Makefile-Document-new-targets-in-make-helpall.patch --]
[-- Type: text/x-patch, Size: 1518 bytes --]

From a96ccb3b4d8b534e3d5a7bcccada368b0a220b69 Mon Sep 17 00:00:00 2001
Message-Id: <a96ccb3b4d8b534e3d5a7bcccada368b0a220b69.1681389559.git.yantar92@posteo.net>
In-Reply-To: <c5ba5773e69d16930fc12db6fbe0d907fae926cd.1681389559.git.yantar92@posteo.net>
References: <c5ba5773e69d16930fc12db6fbe0d907fae926cd.1681389559.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Thu, 13 Apr 2023 14:33:16 +0200
Subject: [PATCH 3/4] * Makefile: Document new targets in make helpall

Document the newly added cleanpkg and uppkg targets.
---
 Makefile | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Makefile b/Makefile
index f476a3ea7..5e0e0fdff 100644
--- a/Makefile
+++ b/Makefile
@@ -41,6 +41,7 @@ helpall::
 	$(info Cleaning)
 	$(info ========)
 	$(info make clean          - remove built Org ELisp files and documentation)
+	$(info make cleanpkg      - remove third-party packages downloaded via make uppkg)
 	$(info make cleanall       - remove everything that can be built and all remnants)
 	$(info make clean-install  - remove previous Org installation)
 	$(info )
@@ -81,6 +82,7 @@ helpall::
 	$(info Convenience)
 	$(info ===========)
 	$(info make up0            - pull from upstream)
+	$(info make uppkg          - download third-party packages required for compilation)
 	$(info make up1            - pull from upstream, build and check)
 	$(info make up2            - pull from upstream, build, check and install)
 	$(info make update         - pull from upstream and build)
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #5: 0004-lisp-org.el-Require-lower-Compat-version.patch --]
[-- Type: text/x-patch, Size: 1327 bytes --]

From 0f5b5b445b649d6dda0a2637cc52ef6b6fed90e1 Mon Sep 17 00:00:00 2001
Message-Id: <0f5b5b445b649d6dda0a2637cc52ef6b6fed90e1.1681389559.git.yantar92@posteo.net>
In-Reply-To: <c5ba5773e69d16930fc12db6fbe0d907fae926cd.1681389559.git.yantar92@posteo.net>
References: <c5ba5773e69d16930fc12db6fbe0d907fae926cd.1681389559.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Thu, 13 Apr 2023 14:36:42 +0200
Subject: [PATCH 4/4] * lisp/org.el: Require lower Compat version

Compat 29.1.3.2 is pre-installed on current Ubuntu 23.04.
Do not require later version as it is (1) not yet necessary, (2) will
make life slightly easier for people who do not want to download staff
unnecessarily.

Link: https://list.orgmode.org/orgmode/u09ejk$vi1$1@ciao.gmane.io/
---
 lisp/org.el | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lisp/org.el b/lisp/org.el
index 065813acf..26128815f 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -7,7 +7,7 @@ ;;; org.el --- Outline-based notes management and organizer -*- lexical-binding:
 ;; Maintainer: Bastien Guerry <bzg@gnu.org>
 ;; Keywords: outlines, hypermedia, calendar, wp
 ;; URL: https://orgmode.org
-;; Package-Requires: ((emacs "26.1") (compat "29.1.4.1"))
+;; Package-Requires: ((emacs "26.1") (compat "29.1.3.2"))
 
 ;; Version: 9.7-pre
 
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #6: 0001-dev-org-build-system.org-Document-newly-added-target.patch --]
[-- Type: text/x-patch, Size: 2171 bytes --]

From 1d90f3459b9a51502dc40d683fafb45803335eb4 Mon Sep 17 00:00:00 2001
Message-Id: <1d90f3459b9a51502dc40d683fafb45803335eb4.1681389714.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Thu, 13 Apr 2023 14:32:20 +0200
Subject: [PATCH] * dev/org-build-system.org: Document newly added targets and
 options

(Make Targets =mk/targets.mk=): Document EGLAGS and EPACKAGES variables.
(Cleaning):
(Compatibility and Convenience): Document cleanpkg and uppkg targets.
---
 dev/org-build-system.org | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/dev/org-build-system.org b/dev/org-build-system.org
index 37d07fe5..a3a7d8e8 100644
--- a/dev/org-build-system.org
+++ b/dev/org-build-system.org
@@ -118,6 +118,13 @@ ** Make Targets =mk/targets.mk=
 be useful in some situations.  The following variables are considered
 stable:
 
+- =EFLAGS= :: Additional flags passed to Emacs executable.  Applies to
+              all targets.
+- =EPACKAGES= :: Additional packages to install before compilation.
+                 This is useful to install optional packages for
+                 test suite.  The packages will not be installed, if
+                 already available in the ~load-path~ that can be passed
+                 via =EFLAGS=.
 - =TEST_NO_AUTOCLEAN= :: Define to a non-null value to keep the test
      directory around for inspection.  This is mostly useful for
      debugging the test suite.
@@ -214,6 +221,7 @@ ** Documentation
 ** Cleaning
 
 - =clean= :: Cleans in =lisp/= and =doc/=.
+- =cleanpkg= :: Cleans in =pkg-deps/=.
 - =cleanall= :: Cleans everything that can be cleaned, including
                 several types of backup files, so do not use this when
                 you have active edit sessions!
@@ -224,6 +232,7 @@ ** Compatibility and Convenience
 
 - =up0= :: Updates the current Git branch from upstream by doing a
            =git pull=.
+- =uppkg= :: Download packages to be available for =make compile=.
 - =up1= :: Does =up0= and then builds and checks Org.
 - =up2= :: Does =up1= and installs Org if there was no test error.
 - =update= :: Does =up0= and then builds Org.  Does not test.
-- 
2.40.0


[-- Attachment #7: Type: text/plain, Size: 224 bytes --]


-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* Re: [PATCH v2] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-13 12:42               ` Ihor Radchenko
@ 2023-04-17 17:20                 ` Max Nikulin
  2023-04-20  9:27                   ` Ihor Radchenko
  0 siblings, 1 reply; 36+ messages in thread
From: Max Nikulin @ 2023-04-17 17:20 UTC (permalink / raw)
  To: emacs-orgmode

On 13/04/2023 19:42, Ihor Radchenko wrote:
> Max Nikulin writes:
>> My expectation 
>> is that make does not attempt to manage dependencies. For me it is OK to 
>> type an additional command to install them and to fail otherwise.
> 
> Sure. And you will have such option (EFLAGS).
> However, I decided to enable auto-downloading by default to not break
> the previous working compilation instructions.

For me adding external dependencies is strong enough reason to change 
compiling instructions. My vote is for clear separation of dependency 
management (even if performed through make targets) and 
compiling/testing/etc.

Last years I rarely use make directly to build software besides the 
cases when I am involved in development. I strongly prefer .deb 
packages. Earlier it was usual practice that "make" or "make all" failed 
if dependencies were not provided. Actually often it was configure that 
reported unavailable libraries. So network requests may be more 
surprising for users than failures due to missed dependencies. It is OK 
for e.g. gradle, but not for make to fetch packages. I expect that 
maintainers of packages from Linux distributions would prefer to avoid 
mixing of compiling and loading dependencies as well.

In my opinion, ideally there should be 3 options for dependency management:
1. Completely disabled. If load from default paths failed than it is a 
fatal error.
2. Use specified directory outside of Org tree (~/.emacs.d/elpa by 
default) or any other directory that you named pkgdir. Only dedicated 
target may clean this directory.
3. Install packages to Org source/build directory.

You decided to make 3 the default variant. I believe, it should be 
activated by a variable, e.g. AUTODEP = 1 in local.mk or from command 
line "make compile AUTODEP=1

I think, it is better to require an additional command

make autoloads
make fetch-dependencies
make compile

> +package-install = --eval '(unless (require '"'"'$(package) nil t) (message "%s" load-path) (package-install '"'"'$(package)))'

I do not like that versions of dependencies are ignored. I have noticed 
`package-install-from-buffer'. Perhaps it can be used to generate a stub 
package (e.g. org-build-deps) with Package-Requires line obtained from 
org.el. The only purpose of this package is to pull dependencies. It is 
just an idea, I have not tried such approach.

>> $(FIND) $(pkgdir) -name \*.elc -delete
> 
> Looks fine, except that now we have part of the targets using $RM
> variable and part of the targets ignoring it. I am not sure if it is a
> problem. I am slightly in favour of keeping the existing approach with
> $RM.

I have realized that -delete action is not a part of POSIX 
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html
Anyway you removed this line and I like it since I suspected some issues 
when "make compile" is invoked twice.

>> Is there a way to express (or (compat "29.1.4.1") (emacs "28.1")) to
>> avoid installing compat in the case of sufficiently new emacs? E.g.
>> dpkg/apt allows such alternatives.
> 
> No, AFAIK:

It is sour. Another deficiency of Emacs package management is that 
errors during compiling of dependencies do not cause immediate failure 
of make.

> +EMACS_VERSION := $(shell $(EMACS) -Q --batch --eval '(message "%s" emacs-version)' 2>&1)

Ideally $(BATCH) should be used, but it is defined below. (princ 
emacs-version) is an alternative, but I have not idea which variant is 
better.

> -;; Package-Requires: ((emacs "26.1") (compat "29.1.4.1"))
> +;; Package-Requires: ((emacs "26.1") (compat "29.1.3.2"))

thanks

> Subject: [PATCH 3/7] Use compat.el library instead of ad-hoc compatibility
>  function set
> 
> * mk/default.mk (EPACKAGES): Demand compat library during compile time.

when I asked for more granular commits I expected this change in

> Subject: [PATCH 2/7] org-compat: Enable compat.el

To separate adding dependency and replacing org-compat functions to compat.

Earlier I used the following procedure: git rebase -i, mark some commit 
for edit, git gui, unstage lines from commit, create new commit, reorder 
and squash/fixup commits on next iteration of rebase -i. Perhaps magit 
can do the same tricks as "git gui". If all changes of some file should 
be moved to another commit it should be easier to use git checkout 
COMMIT FILE.



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH v2] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-17 17:20                 ` Max Nikulin
@ 2023-04-20  9:27                   ` Ihor Radchenko
  2023-04-28 15:27                     ` Max Nikulin
  0 siblings, 1 reply; 36+ messages in thread
From: Ihor Radchenko @ 2023-04-20  9:27 UTC (permalink / raw)
  To: Max Nikulin; +Cc: emacs-orgmode

Max Nikulin <manikulin@gmail.com> writes:

>> Sure. And you will have such option (EFLAGS).
>> However, I decided to enable auto-downloading by default to not break
>> the previous working compilation instructions.
>
> For me adding external dependencies is strong enough reason to change 
> compiling instructions. My vote is for clear separation of dependency 
> management (even if performed through make targets) and 
> compiling/testing/etc.
> ...
> In my opinion, ideally there should be 3 options for dependency management:
> 1. Completely disabled. If load from default paths failed than it is a 
> fatal error.

I have no problem with this approach when using system packages.
However, it is almost guaranteed that compat.el is absent in global
load-path as long as compat.el is not built-in.

> 2. Use specified directory outside of Org tree (~/.emacs.d/elpa by 
> default) or any other directory that you named pkgdir. Only dedicated 
> target may clean this directory.

This is mostly an equivalent of -L switch. I do not like the idea of
using ~/.emacs.d/elpa default. It is fragile if this default ever
changes.

> 3. Install packages to Org source/build directory.
>
> You decided to make 3 the default variant. I believe, it should be 
> activated by a variable, e.g. AUTODEP = 1 in local.mk or from command 
> line "make compile AUTODEP=1

It is now activated by EPACKAGES being non-empty.

> I think, it is better to require an additional command
>
> make autoloads
> make fetch-dependencies
> make compile

Maybe. Then, also make doc and make install?
And make repro, which is dislike in particular - make repro is supposed
to be easy to use for users unfamiliar with Emacs internals or typical
GNU make conventions.

>> +package-install = --eval '(unless (require '"'"'$(package) nil t) (message "%s" load-path) (package-install '"'"'$(package)))'
>
> I do not like that versions of dependencies are ignored. I have noticed 
> `package-install-from-buffer'. Perhaps it can be used to generate a stub 
> package (e.g. org-build-deps) with Package-Requires line obtained from 
> org.el. The only purpose of this package is to pull dependencies. It is 
> just an idea, I have not tried such approach.

This sounds fragile. I see no reason to go this far and using so complex
approach.

>> +EMACS_VERSION := $(shell $(EMACS) -Q --batch --eval '(message "%s" emacs-version)' 2>&1)
>
> Ideally $(BATCH) should be used, but it is defined below. (princ 
> emacs-version) is an alternative, but I have not idea which variant is 
> better.

I used $(EMACS) on purpose. $(BATCH) may contain more things, which we
do not want (on purpose) here.

>> Subject: [PATCH 3/7] Use compat.el library instead of ad-hoc compatibility
>>  function set
>> 
>> * mk/default.mk (EPACKAGES): Demand compat library during compile time.
>
> when I asked for more granular commits I expected this change in
>
>> Subject: [PATCH 2/7] org-compat: Enable compat.el
>
> To separate adding dependency and replacing org-compat functions to compat.

For me, PATCH 3/7 grouping is more reasonable. So, I disagree.
Splitting EPACKAGES modification would create transient commit with
non-working Org.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH v2] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-20  9:27                   ` Ihor Radchenko
@ 2023-04-28 15:27                     ` Max Nikulin
  2023-04-30 10:39                       ` [PATCH v4] " Ihor Radchenko
  0 siblings, 1 reply; 36+ messages in thread
From: Max Nikulin @ 2023-04-28 15:27 UTC (permalink / raw)
  To: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 5189 bytes --]

Ihor,

I do not like lengthy emacs commands and make functions to generate 
them. I think, it is better to move such code to a script. A proof of 
concept is attached, however it is rather rough draft

     ./epm.el -Q --epm-dir $(emacs_pkgdir)/emacs-%e install compat
     ./epm.el -Q -L ~/src/compat install compat

or

     export EPMDIR="$HOME/.cache/epm/emacs-%e"
     ./epm.el install compat

to just abort compilation due to absent dependency

     ./epm.el missing compat

On 20/04/2023 16:27, Ihor Radchenko wrote:
> Max Nikulin writes:
> 
>>> Sure. And you will have such option (EFLAGS).

By the way, accordingly to (info "(make) Command Variables") or (info 
"(standards) Command Variables") "Variables for Specifying Commands"
https://www.gnu.org/prep/standards/html_node/Command-Variables.html

it should be EMACSFLAGS rather than EFLAGS.

>> In my opinion, ideally there should be 3 options for dependency management:
>> 1. Completely disabled. If load from default paths failed than it is a
>> fatal error.
> 
> I have no problem with this approach when using system packages.
> However, it is almost guaranteed that compat.el is absent in global
> load-path as long as compat.el is not built-in.

I see that installation attempt is not performed when packages are 
available. However form my point of view it is normal when compilation 
fails when dependency are not provided. It works so for decades for 
applications that use make. To be precise, usually I expect detection of 
missed libraries from configure scrips, but in some cases they are 
missed. Maybe such experience was formed when access to network was limited.

For me it is quite natural that make does try to pull dependencies (at 
least by default) and it is my responsibility to ensure availability of 
necessary libraries.

>> 2. Use specified directory outside of Org tree (~/.emacs.d/elpa by
>> default) or any other directory that you named pkgdir. Only dedicated
>> target may clean this directory.
> 
> This is mostly an equivalent of -L switch.

No, -L is for source directories of package (e.g. git repositories). I 
mean namely alternative `package-user-dir', but not managed by make.

> I do not like the idea of
> using ~/.emacs.d/elpa default. It is fragile if this default ever
> changes.

I still consider it as a reasonable default for a user having just one 
emacs version who is going to build and run org. Both steps may use the 
same package directory. A developer who switches between various emacs 
versions may have set of packages for each emacs version.

>> 3. Install packages to Org source/build directory.
>>
>> You decided to make 3 the default variant. I believe, it should be
>> activated by a variable, e.g. AUTODEP = 1 in local.mk or from command
>> line "make compile AUTODEP=1
> 
> It is now activated by EPACKAGES being non-empty.

And it is non-empty by default because it defines list of build 
dependencies, not whether they should be managed by make.

>> I think, it is better to require an additional command
>>
>> make autoloads
>> make fetch-dependencies
>> make compile
> 
> Maybe. Then, also make doc and make install?

In general "make install" may be executed by root while "make all" is a 
task for regular user. "make doc" is an optional step, so I do not see 
any problem. Ideally it might be

make fetch-dependencies # or specify package directory
# or load path in local.mk
make all

followed by optional doc or install

> And make repro,

I have not justified my point of view to make repro yet.

>> I do not like that versions of dependencies are ignored. I have noticed
>> `package-install-from-buffer'. Perhaps it can be used to generate a stub
>> package (e.g. org-build-deps) with Package-Requires line obtained from
>> org.el. The only purpose of this package is to pull dependencies. It is
>> just an idea, I have not tried such approach.
> 
> This sounds fragile. I see no reason to go this far and using so complex
> approach.

My idea is to ensure that *required* version is installed, not some 
stale one. I have not tried such approach though.

>>> Subject: [PATCH 3/7] Use compat.el library instead of ad-hoc compatibility
>>>   function set
>>>
>>> * mk/default.mk (EPACKAGES): Demand compat library during compile time.
>>
>> when I asked for more granular commits I expected this change in
>>
>>> Subject: [PATCH 2/7] org-compat: Enable compat.el
>>
>> To separate adding dependency and replacing org-compat functions to compat.
> 
> For me, PATCH 3/7 grouping is more reasonable. So, I disagree.
> Splitting EPACKAGES modification would create transient commit with
> non-working Org.

I just do not like that a single line change in default.mk (modification 
of build process) is buried in a large patch (changes of the code). My 
idea was that

> -EPACKAGES ?=
> +EPACKAGES ?= compat

should be in the same commit as

> -;; Package-Requires: ((emacs "26.1"))
> +;; Package-Requires: ((emacs "26.1") (compat "29.1.4.1"))

Currently patch 2 requires compat, but it is provided till patch 3. 
Despite in commit 2 the package does not do anything useful, I 
considered this commit as preparations to actively use introduced 
dependency.

[-- Attachment #2: epm.el --]
[-- Type: text/x-emacs-lisp, Size: 3982 bytes --]

#!/bin/sh
":"; # -*- mode: emacs-lisp; lexical-binding: t; -*-
":"; exec emacs --script "$0" "$@"

(require 'format-spec)
(require 'package)
(require 'subr-x)

(defvar epm-dir nil
  "Overrides `package-user-dir'.")

(defvar epm-verbose nil)

(defun epm-nonempty-p (s)
  (and s (not (string-empty-p s))))

(defun epm-init ()
  (unless (epm-nonempty-p epm-dir)
    (setq epm-dir (getenv "EPMDIR")))
  (when (epm-nonempty-p epm-dir)
    (let* ((fmt-expanded (format-spec epm-dir `((?e . ,emacs-version))))
	   (dir (directory-file-name (expand-file-name fmt-expanded))))
      ;; `package-user-dir' ~/.emacs.d/elpa by default
      ;; `package-directory-list' does not include it
      (setq package-user-dir dir)))
  (package-initialize))

(defun epm-library-unavailable-p (lib)
  (unless (locate-library lib)
    lib))

(defun epm-missing (libs)
  ;; TODO consider `require' catching load errors
  (delq nil (mapcar #'epm-library-unavailable-p libs)))

(defun epm-cmd-help (_cmd _args)
  "List commands."
  (princ "Usage: epm [--dbg|--debug-on-error] [--epm-dir] COMMAND ARGS...

CLI tool to install ELPA packages.

Any Emacs option may be specified, e.g. --quck,-Q or --directory,-L DIR

--dbg, --debug-on-error
    Enable `debug-on-error'

--epm-dir DIR
    Set `package-user-dir'.
    \"%e\" is replaced by `emacs-version'.
    Alternatively EPMDIR environment may be specified.
\n")
  (pcase-dolist (`(,name . ,func) epm-commands)
    (princ (concat name "\n"))
    (princ
     (replace-regexp-in-string
      "\\`\\|\n" "\\1    "
      (documentation func) 'fixedcase nil))
    (princ "\n\n")
    ))

(defun epm-cmd-missing (_ libs)
  "Report not installed libraries and exit with non-zero code."
  (let ((missing (epm-missing libs)))
    (when missing
      (princ (mapconcat #'identity missing " "))
      (princ "\n")
      (kill-emacs 1))))

(defun epm-cmd-install (_ libs)
  "Install packages from LIBS that are not available yet"
  ;; TODO force option or update command
  (let ((missing (epm-missing libs)))
    (when missing
      (package-refresh-contents)
      (make-directory package-user-dir 'parents))
    (dolist (pkg missing)
      (package-install (intern pkg)))))

(defun epm-cmd-report (_ libs)
  "Report paths of available libraries"
  (princ (format "package-user-dir: %s\n" package-user-dir))
  ;; (princ (format "load-path: %s\n" load-path))
  (dolist (name libs)
    ;; (version-to-list version)
    (princ (format "%-20s %s " name
		   (if (package-installed-p (intern name))
		       "package "
		     "        ")))
    (princ (locate-library name))
    (princ "\n")))

(defvar epm-commands
  '(("help" . epm-cmd-help)
    ("install" . epm-cmd-install)
    ("missing" . epm-cmd-missing)
    ("report" . epm-cmd-report)))

;; Perhaps there is a way to use `command-switch-alist'.
(defun epm-args-parse (arg-list)
  (let ((parse-opts t)
	unprocessed
	cmd)
    (while (and arg-list (or parse-opts (not cmd)))
      (pcase (pop arg-list)
	("--"
	 (setq parse-opts nil))
	((and (guard parse-opts) ;; otherwise processed after script exit
	      (or "-L" (pred (lambda (x) (string-prefix-p x "--directory")))))
	 (push
	  (expand-file-name (command-line-normalize-file-name
			     (pop arg-list)))
	  load-path))
	((and (guard parse-opts) "--epm-dir")
	 (setq epm-dir (pop arg-list)))
	((and (guard parse-opts) (or "--dbg" "--debug-on-error"))
	 ;; -d is handled as --display, --debug as --debug-init
	 (setq debug-on-error t))
	((and (guard (not cmd)) (pred (string-match-p "\\`[^-]")) arg)
	 (push arg cmd)
	 (unless parse-opts
	   (push "--" cmd)))
	(arg
	 (if cmd (push arg cmd) (push arg unprocessed)))))
  (cons (nreverse cmd) (nreverse unprocessed))))

(pcase-let ((`(,cmd . ,unprocessed) (epm-args-parse command-line-args-left)))
  (unless (setq command-line-args-left unprocessed)
    (epm-init)
    (let ((func (cdr (assoc (car cmd) epm-commands))))
      (if func
	  (funcall func (car cmd) (cdr cmd))
	(error "Unknown command %s" (car cmd))))))

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v4] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-28 15:27                     ` Max Nikulin
@ 2023-04-30 10:39                       ` Ihor Radchenko
  2023-05-03 12:14                         ` [PATCH] epm.el: A CLI tool for package.el Max Nikulin
  2023-05-06  6:39                         ` [PATCH v4] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)) Max Nikulin
  0 siblings, 2 replies; 36+ messages in thread
From: Ihor Radchenko @ 2023-04-30 10:39 UTC (permalink / raw)
  To: Max Nikulin; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 5147 bytes --]

Max Nikulin <manikulin@gmail.com> writes:

> I do not like lengthy emacs commands and make functions to generate 
> them. I think, it is better to move such code to a script. A proof of 
> concept is attached, however it is rather rough draft
>
>      ./epm.el -Q --epm-dir $(emacs_pkgdir)/emacs-%e install compat
>      ./epm.el -Q -L ~/src/compat install compat

Good idea. Although, we should not overdo this package management thing.
If we really need complex functionality here, we should better just use
cask/eldev instead of re-inventing the wheel.

Are you willing to improve the draft to be ready for upstream?
I will provide some inline comments below.

> By the way, accordingly to (info "(make) Command Variables") or (info 
> "(standards) Command Variables") "Variables for Specifying Commands"
> https://www.gnu.org/prep/standards/html_node/Command-Variables.html
>
> it should be EMACSFLAGS rather than EFLAGS.

Fair point. I now rearranged the commits as you asked and incorporated
this change. See the attached.

Note that also I changed the way compat.el is loaded, making Org throw
an error for older Emacs versions. This will produce more useful error
if someone attempts to load Org as is, without installing compat.el in
older Emacs.

>> I have no problem with this approach when using system packages.
>> However, it is almost guaranteed that compat.el is absent in global
>> load-path as long as compat.el is not built-in.
>
> I see that installation attempt is not performed when packages are 
> available. However form my point of view it is normal when compilation 
> fails when dependency are not provided. It works so for decades for 
> applications that use make. To be precise, usually I expect detection of 
> missed libraries from configure scrips, but in some cases they are 
> missed. Maybe such experience was formed when access to network was limited.
>
> For me it is quite natural that make does try to pull dependencies (at 
> least by default) and it is my responsibility to ensure availability of 
> necessary libraries.

I think that we need to zoom out a bit and discuss the contexts where
Org build system is used:

1. During Org development, by developers who know what they are doing
2. By ordinary users, not necessarily familiar with GNU make and all the
   associated build process conventions.

Org developers may need to use the whole spectrum of make targets, and
will generally benefit from isolation of Org from the main Emacs
configuration folder. For example, my `package-user-dir' contains a
number of forks with additional patches applied - it is not the
environment I want to develop (and test) Org in.

Org users will likely use make autoloads, make, make docs, and make
repro. Here, it will make sense to re-use default .emacs.d and package
directory when running make, as ordinary users running make are most
likely aiming to build Org for their own usage. However, make repro and
optionally make docs should avoid re-using user packages as it may cause
inconsistent results if the `package-user-dir' is messed up.

One way to handle the above scenarios might be your idea with AUTODEP.
By default, it will be "auto":
 - make compile, docs  :: Re-use default `package-user-dir'
 - make repro    :: Auto-download and ignore `package-user-dir'
 - other targets :: Prompt the user
Alternatives will be meant to be used as

    AUTODEP=download/user/no make target.

triggering unconditional downloading, using `package-user-dir', and not
using any guess, correspondingly.

WDYT?

>>> I do not like that versions of dependencies are ignored. I have noticed
>>> `package-install-from-buffer'. Perhaps it can be used to generate a stub
>>> package (e.g. org-build-deps) with Package-Requires line obtained from
>>> org.el. The only purpose of this package is to pull dependencies. It is
>>> just an idea, I have not tried such approach.
>> 
>> This sounds fragile. I see no reason to go this far and using so complex
>> approach.
>
> My idea is to ensure that *required* version is installed, not some 
> stale one. I have not tried such approach though.

I think that it is stretching a bit beyond the complexity we should
allow within Org build system. In your scenario, I can simply do
make cleanpkg and re-download the latest dependencies.

Again, package management is not something we want to overdo. If
really necessary, we will use cask or other proper Elisp development
tool.

> #!/bin/sh
> ":"; # -*- mode: emacs-lisp; lexical-binding: t; -*-
> ":"; exec emacs --script "$0" "$@"

Let's not lock to bash. AFAIK, our makefiles can currently work on
Windows. Using /bin/sh will lead to regression.

So, a simple --batch script for Emacs will be better here.

> (defun epm-init ()
>   (unless (epm-nonempty-p epm-dir)
>     (setq epm-dir (getenv "EPMDIR")))
>   (when (epm-nonempty-p epm-dir)
>     (let* ((fmt-expanded (format-spec epm-dir `((?e . ,emacs-version))))
> 	   (dir (directory-file-name (expand-file-name fmt-expanded))))
>       ;; `package-user-dir' ~/.emacs.d/elpa by default
>       ;; `package-directory-list' does not include it

What does this comment refer to?


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: v4-0001-Upgrade-Org-build-system-to-handle-third-party-de.patch --]
[-- Type: text/x-patch, Size: 7555 bytes --]

From 3946dbb956afcd005cba0f1899acae5a74a109d4 Mon Sep 17 00:00:00 2001
Message-Id: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sat, 1 Apr 2023 12:00:48 +0200
Subject: [PATCH v4 1/8] Upgrade Org build system to handle third-party
 dependencies

* mk/default.mk (pkgdir_top): New variable holding the location of
third-party packages to be downloaded if necessary during compilation.
(EMACS_VERSION): New variable holding current Emacs version, according
to EMACS.
(pkgdir): New variable holding subdir where the third-party packages
are downloaded, according to EMACS_VERSION.
(EMACSFLAGS): New variable holding extra flags to be passed to Emacs
executable when running make.  We follow "PROGRAMFLAGS" convention as
requested by GNU standards:
https://www.gnu.org/prep/standards/html_node/Command-Variables.html
(EPACKAGES): List of packages to be installed (unless already present
in the `load-path') during compilation.
(package-install):
(INSTALL_PACKAGES): New command to download and install missing packages.
(EMACSQ): Update, setting default package location to pkgdir.
* mk/targets.mk (uppkg): New target to download install missing
packages.
(check test):
(repro):
(compile compile-dirty): Use the new uppkg target.
(cleanpkg): New target cleaning up the downloaded packages.
(cleanall): Use the new target.
(.PHONY):
(CONF_BASE):
(CONF_DEST):
(CONF_CALL): Update according to the new variables and targets.
* .gitignore: Ignore the downloaded packages.

This commit paves the way towards third-party built-time dependencies
for Org.  In particular, towards including compat.el dependency.

According to EPACKAGES, we can auto-download necessary packages,
unless they are manually specified via -L switches in EMACSFLAGS.

Link: https://orgmode.org/list/87v8ks6rhf.fsf@localhost
---
 .gitignore    |  1 +
 mk/default.mk | 31 ++++++++++++++++++++++++++++++-
 mk/targets.mk | 25 ++++++++++++++++---------
 3 files changed, 47 insertions(+), 10 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4bb81c359..0d9c5b297 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,6 +48,7 @@ local*.mk
 .gitattributes
 mk/x11idle
 ChangeLog
+pkg-deps/
 
 # Files generated during `make packages/org` in a clone of `elpa.git`.
 
diff --git a/mk/default.mk b/mk/default.mk
index fa46661e8..393d7336a 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -6,6 +6,7 @@
 
 # Name of your emacs binary
 EMACS	= emacs
+EMACS_VERSION := $(shell $(EMACS) -Q --batch --eval '(message "%s" emacs-version)' 2>&1)
 
 # Where local software is found
 prefix	= /usr/share
@@ -31,6 +32,17 @@ GIT_BRANCH =
 TMPDIR ?= /tmp
 testdir = $(TMPDIR)/tmp-orgtest
 
+# Where to store Org dependencies
+top_builddir := $(shell pwd)
+pkgdir_top := $(top_builddir)/pkg-deps
+pkgdir := $(pkgdir_top)/$(EMACS_VERSION)
+
+# Extra flags to be passed to Emacs
+EMACSFLAGS ?=
+
+# Third-party packages to install when running make
+EPACKAGES ?=
+
 # Configuration for testing
 # Verbose ERT summary by default for Emacs-28 and above.
 # To override:
@@ -72,12 +84,25 @@ REPRO_ARGS ?=
 req-ob-lang = --eval '(require '"'"'ob-$(ob-lang))'
 lst-ob-lang = ($(ob-lang) . t)
 req-extra   = --eval '(require '"'"'$(req))'
+package-define-refresh-mark = --eval '(setq org--compile-packages-missing nil)'
+package-need-refresh-mark = --eval '(unless (require '"'"'$(package) nil t) (setq org--compile-packages-missing t))'
+package-refresh-maybe = --eval '(if org--compile-packages-missing (package-refresh-contents) (message "No third-party packages need to be installed"))'
+package-install-maybe = --eval '(unless (require '"'"'$(package) nil t) (package-install '"'"'$(package)))'
 BTEST_RE   ?= \\(org\\|ob\\|ox\\)
 BTEST_LOAD  = \
 	--eval '(add-to-list '"'"'load-path (concat default-directory "lisp"))' \
 	--eval '(add-to-list '"'"'load-path (concat default-directory "testing"))'
 BTEST_INIT  = $(BTEST_PRE) $(BTEST_LOAD) $(BTEST_POST)
 
+ifeq (,$(EPACKAGES))
+INSTALL_PACKAGES =
+else
+INSTALL_PACKAGES = \
+	$(BATCH) \
+        $(package-define-refresh-mark) $(foreach package,$(EPACKAGES),$(package-need-refresh-mark)) $(package-refresh-maybe) \
+        $(foreach package,$(EPACKAGES),$(package-install-maybe))
+endif
+
 BTEST = $(BATCH) $(BTEST_INIT) \
 	  -l org-batch-test-init \
 	  --eval '(setq \
@@ -116,7 +141,11 @@ REPRO = $(NOBATCH) $(REPRO_INIT) $(REPRO_ARGS)
 
 # start Emacs with no user and site configuration
 # EMACSQ = -vanilla # XEmacs
-EMACSQ  = $(EMACS)  -Q
+EMACSQ  = $(EMACS)  -Q \
+	  $(EMACSFLAGS) \
+	  --eval '(setq vc-handled-backends nil org-startup-folded nil org-element-cache-persistent nil)' \
+          --eval '(make-directory "$(pkgdir)" t)' \
+	  --eval '(setq package-user-dir "$(pkgdir)")' --eval '(package-initialize)'
 
 # Using emacs in batch mode.
 BATCH	= $(EMACSQ) -batch \
diff --git a/mk/targets.mk b/mk/targets.mk
index 06016561c..18140c5c0 100644
--- a/mk/targets.mk
+++ b/mk/targets.mk
@@ -27,21 +27,21 @@ ifneq ($(GITSTATUS),)
   GITVERSION := $(GITVERSION:.dirty=).dirty
 endif
 
-.PHONY:	all oldorg update update2 up0 up1 up2 single $(SUBDIRS) \
+.PHONY:	all oldorg update update2 up0 up1 up2 uppkg single $(SUBDIRS) \
 	check test install $(INSTSUB) \
 	info html pdf card refcard doc docs \
 	autoloads cleanall clean $(CLEANDIRS:%=clean%) \
 	clean-install cleanelc cleandirs \
-	cleanlisp cleandoc cleandocs cleantest \
+	cleanlisp cleandoc cleandocs cleantest cleanpkg \
 	compile compile-dirty uncompiled \
 	config config-test config-exe config-all config-eol config-version \
 	vanilla repro
 
-CONF_BASE = EMACS DESTDIR ORGCM ORG_MAKE_DOC
-CONF_DEST = lispdir infodir datadir testdir
+CONF_BASE = EMACS DESTDIR ORGCM ORG_MAKE_DOC EPACKAGES
+CONF_DEST = lispdir infodir datadir testdir pkgdir
 CONF_TEST = BTEST_PRE BTEST_POST BTEST_OB_LANGUAGES BTEST_EXTRA BTEST_RE
 CONF_EXEC = CP MKDIR RM RMR FIND CHMOD SUDO PDFTEX TEXI2PDF TEXI2HTML MAKEINFO INSTALL_INFO
-CONF_CALL = BATCH BATCHL ELC ELCDIR NOBATCH BTEST MAKE_LOCAL_MK MAKE_ORG_INSTALL MAKE_ORG_VERSION
+CONF_CALL = BATCH BATCHL ELC ELCDIR NOBATCH INSTALL_PACKAGES BTEST MAKE_LOCAL_MK MAKE_ORG_INSTALL MAKE_ORG_VERSION
 config-eol:: EOL = \#
 config-eol:: config-all
 config config-all::
@@ -86,7 +86,7 @@ local.mk:
 
 all compile::
 	$(foreach dir, doc lisp, $(MAKE) -C $(dir) clean;)
-compile compile-dirty::
+compile compile-dirty:: uppkg
 	$(MAKE) -C lisp $@
 all clean-install::
 	$(foreach dir, $(SUBDIRS), $(MAKE) -C $(dir) $@;)
@@ -94,7 +94,7 @@ all clean-install::
 vanilla:
 	-@$(NOBATCH) &
 
-check test::	compile
+check test::	uppkg compile
 check test test-dirty::
 	-$(MKDIR) $(testdir)
 	TMPDIR=$(testdir) $(BTEST)
@@ -102,6 +102,10 @@ ifeq ($(TEST_NO_AUTOCLEAN),) # define this variable to leave $(testdir) around f
 	$(MAKE) cleantest
 endif
 
+uppkg::
+	@$(MKDIR) -p $(pkgdir)
+	-@$(INSTALL_PACKAGES)
+
 up0 up1 up2::
 	git checkout $(GIT_BRANCH)
 	git remote update
@@ -126,7 +130,7 @@ $(INSTSUB):
 autoloads: lisp
 	$(MAKE) -C $< $@
 
-repro: cleanall autoloads
+repro: cleanall uppkg autoloads
 	-@$(REPRO) &
 
 cleandirs:
@@ -134,7 +138,7 @@ cleandirs:
 
 clean:	cleanlisp cleandoc
 
-cleanall: cleandirs cleantest
+cleanall: cleandirs cleantest cleanpkg
 	-$(FIND) . \( -name \*~ -o -name \*# -o -name .#\* \) -exec $(RM) {} +
 	-$(FIND) $(CLEANDIRS) \( -name \*~ -o -name \*.elc \) -exec $(RM) {} +
 
@@ -159,3 +163,6 @@ cleantest:
 	  $(FIND) $(testdir) -type d -exec $(CHMOD) u+w {} + && \
 	  $(RMR) $(testdir) ; \
 	}
+
+cleanpkg:
+	-$(RMR) $(pkgdir_top)
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: v4-0002-org-compat-Add-optional-compat.el-support.patch --]
[-- Type: text/x-patch, Size: 2869 bytes --]

From f30e75e3b14b3cb955d2dcd11f210cfaceef07b6 Mon Sep 17 00:00:00 2001
Message-Id: <f30e75e3b14b3cb955d2dcd11f210cfaceef07b6.1682849409.git.yantar92@posteo.net>
In-Reply-To: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
References: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Mon, 3 Apr 2023 10:40:00 +0200
Subject: [PATCH v4 2/8] org-compat: Add optional compat.el support

* lisp/org-compat.el (compat): Load Compat library, when available.
(org-compat-function):
(org-compat-call): Add compatibility macros available even when Compat
is not available (Org is a part of Emacs).
---
 lisp/org-compat.el | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/lisp/org-compat.el b/lisp/org-compat.el
index 34b27546d..9c286961a 100644
--- a/lisp/org-compat.el
+++ b/lisp/org-compat.el
@@ -94,6 +94,48 @@ (defvar org-table-tab-recognizes-table.el)
 (defvar org-table1-hline-regexp)
 (defvar org-fold-core-style)
 
+\f
+;;; compat.el
+
+;; Do not throw an error when not available - assume latest Emacs
+;; version (built-in Org).
+(require 'compat nil 'noerror)
+
+;; Provide compatibility macros when we are a part of Emacs.
+;; See https://elpa.gnu.org/packages/doc/compat.html#Usage
+
+(defmacro org-compat-function (fun)
+  "Return compatibility function symbol for FUN.
+
+If the Emacs version provides a sufficiently recent version of
+FUN, the symbol FUN is returned itself.  Otherwise the macro
+returns the symbol of a compatibility function which supports the
+behavior and calling convention of the current stable Emacs
+version.  For example Compat 29.1 will provide compatibility
+functions which implement the behavior and calling convention of
+Emacs 29.1.
+
+See also `org-compat-call' to directly call compatibility functions."
+  (let ((compat (intern (format "compat--%s" fun))))
+    `#',(if (fboundp compat) compat fun)))
+
+(defmacro org-compat-call (fun &rest args)
+  "Call compatibility function or macro FUN with ARGS.
+
+A good example function is `plist-get' which was extended with an
+additional predicate argument in Emacs 29.1.  The compatibility
+function, which supports this additional argument, can be
+obtained via (compat-function plist-get) and called
+via (compat-call plist-get plist prop predicate).  It is not
+possible to directly call (plist-get plist prop predicate) on
+Emacs older than 29.1, since the original `plist-get' function
+does not yet support the predicate argument.  Note that the
+Compat library never overrides existing functions.
+
+See also `org-compat-function' to lookup compatibility functions."
+  (let ((compat (intern (format "compat--%s" fun))))
+    `(,(if (fboundp compat) compat fun) ,@args)))
+
 \f
 ;;; Emacs < 29 compatibility
 
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #4: v4-0003-Add-compat.el-dependency.patch --]
[-- Type: text/x-patch, Size: 1808 bytes --]

From 03902b1efe931145870c64f517442b49f7ec92a3 Mon Sep 17 00:00:00 2001
Message-Id: <03902b1efe931145870c64f517442b49f7ec92a3.1682849409.git.yantar92@posteo.net>
In-Reply-To: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
References: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sat, 29 Apr 2023 11:34:00 +0200
Subject: [PATCH v4 3/8] Add compat.el dependency

* mk/default.mk (EPACKAGES): Demand compat library during compile time.
* lisp/org.el: Add Compat to package-requires.

Compat 29.1.3.2 is pre-installed on current Ubuntu 23.04.
Do not require later version as it is (1) not yet necessary, (2) will
make life slightly easier for people who do not want to download staff
unnecessarily.

Link: https://list.orgmode.org/orgmode/u09ejk$vi1$1@ciao.gmane.io/
---
 lisp/org.el   | 2 +-
 mk/default.mk | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lisp/org.el b/lisp/org.el
index 78040a8e3..162982405 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -7,7 +7,7 @@ ;;; org.el --- Outline-based notes management and organizer -*- lexical-binding:
 ;; Maintainer: Bastien Guerry <bzg@gnu.org>
 ;; Keywords: outlines, hypermedia, calendar, wp
 ;; URL: https://orgmode.org
-;; Package-Requires: ((emacs "26.1"))
+;; Package-Requires: ((emacs "26.1") (compat "29.1.3.2"))
 
 ;; Version: 9.7-pre
 
diff --git a/mk/default.mk b/mk/default.mk
index 393d7336a..cc5775362 100644
--- a/mk/default.mk
+++ b/mk/default.mk
@@ -41,7 +41,7 @@ pkgdir := $(pkgdir_top)/$(EMACS_VERSION)
 EMACSFLAGS ?=
 
 # Third-party packages to install when running make
-EPACKAGES ?=
+EPACKAGES ?= compat
 
 # Configuration for testing
 # Verbose ERT summary by default for Emacs-28 and above.
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #5: v4-0004-Use-compat.el-library-instead-of-ad-hoc-compatibi.patch --]
[-- Type: text/x-patch, Size: 32940 bytes --]

From 239c8626b95c19ec1ac466b14876b826c37b4986 Mon Sep 17 00:00:00 2001
Message-Id: <239c8626b95c19ec1ac466b14876b826c37b4986.1682849409.git.yantar92@posteo.net>
In-Reply-To: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
References: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Mon, 3 Apr 2023 10:41:50 +0200
Subject: [PATCH v4 4/8] Use compat.el library instead of ad-hoc compatibility
 function set

* lisp/org.el (org-fill-paragraph):
* lisp/org-compat.el: Obsolete org-* compatibility functions that are
already available in compat.el: `org-file-has-changed-p',
`org-string-equal-ignore-case', `org-file-name-concat',
`org-directory-empty-p', `org-string-clean-whitespace',
`org-format-prompt', `org-xor', `org-string-distance',
`org-buffer-hash'.
* lisp/ob-core.el (org-babel-results-keyword): Use functions provided
by compat.el.
(org-babel-insert-result):
* lisp/oc-basic.el (org-cite-basic--parse-bibliography):
* lisp/oc.el (org-cite-adjust-note):
* lisp/ol-gnus.el (org-gnus-group-link):
(org-gnus-article-link):
(org-gnus-store-link):
* lisp/ol.el:
(org-store-link):
* lisp/org-attach.el:
* lisp/org-capture.el:
(org-capture-fill-template):
* lisp/org-fold-core.el (org-fold-core-next-visibility-change):
* lisp/org-lint.el:
* lisp/org-persist.el (org-persist-directory):
(org-persist-read:file):
(org-persist-read:url):
(org-persist--load-index):
(org-persist-write:file):
(org-persist-write:index):
(org-persist--merge-index-with-disk):
(org-persist-read):
(org-persist-write):
(org-persist-write-all):
(org-persist--gc-persist-file):
(org-persist-gc):
* lisp/org-refile.el (org-refile-get-location):
* lisp/ox.el (org-export-resolve-radio-link):
* testing/lisp/test-ol.el (test-org-link/toggle-link-display):
* testing/lisp/test-org-capture.el (test-org-capture/abort):

Link: https://orgmode.org/list/87v8ks6rhf.fsf@localhost
---
 lisp/ob-core.el                  |  10 +-
 lisp/oc-basic.el                 |   6 +-
 lisp/oc.el                       |   2 +-
 lisp/ol-gnus.el                  |   8 +-
 lisp/ol.el                       |   4 +-
 lisp/org-attach.el               |   2 +-
 lisp/org-capture.el              |   8 +-
 lisp/org-compat.el               | 180 ++++---------------------------
 lisp/org-fold-core.el            |   2 +-
 lisp/org-lint.el                 |   2 +-
 lisp/org-persist.el              |  38 +++----
 lisp/org-refile.el               |   2 +-
 lisp/org.el                      |   4 +-
 lisp/ox.el                       |   6 +-
 testing/lisp/test-ol.el          |  10 +-
 testing/lisp/test-org-capture.el |   2 +-
 16 files changed, 74 insertions(+), 212 deletions(-)

diff --git a/lisp/ob-core.el b/lisp/ob-core.el
index a2a84ad20..9034feb7e 100644
--- a/lisp/ob-core.el
+++ b/lisp/ob-core.el
@@ -145,7 +145,7 @@ (defcustom org-babel-results-keyword "RESULTS"
   :type 'string
   :safe (lambda (v)
 	  (and (stringp v)
-	       (org-string-equal-ignore-case "RESULTS" v))))
+	       (string-equal-ignore-case "RESULTS" v))))
 
 (defcustom org-babel-noweb-wrap-start "<<"
   "String used to begin a noweb reference in a code block.
@@ -927,7 +927,7 @@ (defun org-babel-check-src-block ()
 				   (match-string 4))))))
       (dolist (name names)
 	(when (and (not (string= header name))
-		   (<= (org-string-distance header name) too-close)
+		   (<= (string-distance header name) too-close)
 		   (not (member header names)))
 	  (error "Supplied header \"%S\" is suspiciously close to \"%S\""
 		 header name))))
@@ -2521,7 +2521,7 @@ (defun org-babel-insert-result (result &optional result-params info hash lang ex
 		       ;; Escape contents from "export" wrap.  Wrap
 		       ;; inline results within an export snippet with
 		       ;; appropriate value.
-		       ((org-string-equal-ignore-case type "export")
+		       ((string-equal-ignore-case type "export")
 			(let ((backend (pcase split
 					 (`(,_) "none")
 					 (`(,_ ,b . ,_) b))))
@@ -2532,14 +2532,14 @@ (defun org-babel-insert-result (result &optional result-params info hash lang ex
 					   backend) "@@)}}}")))
 		       ;; Escape contents from "example" wrap.  Mark
 		       ;; inline results as verbatim.
-		       ((org-string-equal-ignore-case type "example")
+		       ((string-equal-ignore-case type "example")
 			(funcall wrap
 				 opening-line closing-line
 				 nil nil
 				 "{{{results(=" "=)}}}"))
 		       ;; Escape contents from "src" wrap.  Mark
 		       ;; inline results as inline source code.
-		       ((org-string-equal-ignore-case type "src")
+		       ((string-equal-ignore-case type "src")
 			(let ((inline-open
 			       (pcase split
 				 (`(,_)
diff --git a/lisp/oc-basic.el b/lisp/oc-basic.el
index fdcb5bda2..88bce4418 100644
--- a/lisp/oc-basic.el
+++ b/lisp/oc-basic.el
@@ -274,11 +274,11 @@ (defun org-cite-basic--parse-bibliography (&optional info)
       (dolist (file (org-cite-list-bibliography-files))
         (when (file-readable-p file)
           (with-temp-buffer
-            (when (or (org-file-has-changed-p file)
+            (when (or (file-has-changed-p file)
                       (not (gethash file org-cite-basic--file-id-cache)))
               (insert-file-contents file)
               (set-visited-file-name file t)
-              (puthash file (org-buffer-hash) org-cite-basic--file-id-cache))
+              (puthash file (buffer-hash) org-cite-basic--file-id-cache))
             (condition-case nil
                 (unwind-protect
 	            (let* ((file-id (cons file (gethash file org-cite-basic--file-id-cache)))
@@ -488,7 +488,7 @@ (defun org-cite-basic--close-keys (key keys)
   "List cite keys close to KEY in terms of string distance."
   (seq-filter (lambda (k)
                 (>= org-cite-basic-max-key-distance
-                    (org-string-distance k key)))
+                   (string-distance k key)))
               keys))
 
 (defun org-cite-basic--set-keymap (beg end suggestions)
diff --git a/lisp/oc.el b/lisp/oc.el
index 4b1420271..9b96ef8d3 100644
--- a/lisp/oc.el
+++ b/lisp/oc.el
@@ -1029,7 +1029,7 @@ (defun org-cite-adjust-note (citation info &optional rule punct)
                              (match-string 3 previous)))))
       ;; Bail you when there is no quote and either no punctuation, or
       ;; punctuation on both sides.
-      (when (or quote (org-xor punct final-punct))
+      (when (or quote (xor punct final-punct))
         ;; Phase 1: handle punctuation rule.
         (pcase rule
           ((guard (not quote)) nil)
diff --git a/lisp/ol-gnus.el b/lisp/ol-gnus.el
index 7c07ce045..e121cfba3 100644
--- a/lisp/ol-gnus.el
+++ b/lisp/ol-gnus.el
@@ -98,8 +98,8 @@ (defun org-gnus-group-link (group)
 `org-gnus-prefer-web-links' is reversed."
   (let ((unprefixed-group (replace-regexp-in-string "^[^:]+:" "" group)))
     (if (and (string-prefix-p "nntp" group) ;; Only for nntp groups
-	     (org-xor current-prefix-arg
-		      org-gnus-prefer-web-links))
+	     (xor current-prefix-arg
+		  org-gnus-prefer-web-links))
 	(concat "https://groups.google.com/group/" unprefixed-group)
       (concat "gnus:" group))))
 
@@ -116,7 +116,7 @@ (defun org-gnus-article-link (group newsgroups message-id x-no-archive)
 
 If `org-store-link' was called with a prefix arg the meaning of
 `org-gnus-prefer-web-links' is reversed."
-  (if (and (org-xor current-prefix-arg org-gnus-prefer-web-links)
+  (if (and (xor current-prefix-arg org-gnus-prefer-web-links)
 	   newsgroups		  ;make web links only for nntp groups
 	   (not x-no-archive))	  ;and if X-No-Archive isn't set
       (format "https://groups.google.com/groups/search?as_umsgid=%s"
@@ -169,7 +169,7 @@ (defun org-gnus-store-link ()
 	    newsgroups x-no-archive)
        ;; Fetching an article is an expensive operation; newsgroup and
        ;; x-no-archive are only needed for web links.
-       (when (org-xor current-prefix-arg org-gnus-prefer-web-links)
+       (when (xor current-prefix-arg org-gnus-prefer-web-links)
 	 ;; Make sure the original article buffer is up-to-date.
 	 (save-window-excursion (gnus-summary-select-article))
 	 (setq to (or to (gnus-fetch-original-field "To")))
diff --git a/lisp/ol.el b/lisp/ol.el
index e2bf90acd..d7056abde 100644
--- a/lisp/ol.el
+++ b/lisp/ol.el
@@ -1692,7 +1692,7 @@ (defun org-store-link (arg &optional interactive?)
 				(abbreviate-file-name
 				 (buffer-file-name (buffer-base-buffer)))))
 	   ;; Add a context search string.
-	   (when (org-xor org-link-context-for-files (equal arg '(4)))
+	   (when (xor org-link-context-for-files (equal arg '(4)))
 	     (let* ((element (org-element-at-point))
 		    (name (org-element-property :name element))
 		    (context
@@ -1724,7 +1724,7 @@ (defun org-store-link (arg &optional interactive?)
 			     (abbreviate-file-name
 			      (buffer-file-name (buffer-base-buffer)))))
 	;; Add a context search string.
-	(when (org-xor org-link-context-for-files (equal arg '(4)))
+	(when (xor org-link-context-for-files (equal arg '(4)))
 	  (let ((context (org-link--normalize-string
 			  (or (org-link--context-from-region)
 			      (org-current-line-string))
diff --git a/lisp/org-attach.el b/lisp/org-attach.el
index 824af69da..581708f5f 100644
--- a/lisp/org-attach.el
+++ b/lisp/org-attach.el
@@ -678,7 +678,7 @@ (defun org-attach-sync ()
       (let ((files (org-attach-file-list attach-dir)))
 	(org-attach-tag (not files)))
       (when org-attach-sync-delete-empty-dir
-        (when (and (org-directory-empty-p attach-dir)
+        (when (and (directory-empty-p attach-dir)
                    (if (eq 'query org-attach-sync-delete-empty-dir)
                        (yes-or-no-p "Attachment directory is empty.  Delete?")
                      t))
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index b96e9f336..c5b1c6d50 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -1323,9 +1323,9 @@ (defun org-capture-place-item ()
 	      ;; prioritize the existing list.
 	      (when prepend?
 		(let ((ordered? (eq 'ordered (org-element-property :type item))))
-		  (when (org-xor ordered?
-				 (string-match-p "\\`[A-Za-z0-9]\\([.)]\\)"
-						 template))
+		  (when (xor ordered?
+			     (string-match-p "\\`[A-Za-z0-9]\\([.)]\\)"
+					     template))
 		    (org-cycle-list-bullet (if ordered? "1." "-")))))
 	      ;; Eventually repair the list for proper indentation and
 	      ;; bullets.
@@ -1867,7 +1867,7 @@ (defun org-capture-fill-template (&optional template initial annotation)
 		     (setq org-capture--prompt-history
 			   (gethash prompt org-capture--prompt-history-table))
                      (push (org-completing-read
-                            (org-format-prompt (or prompt "Enter string") default)
+                            (format-prompt (or prompt "Enter string") default)
 			    completions
 			    nil nil nil 'org-capture--prompt-history default)
 			   strings)
diff --git a/lisp/org-compat.el b/lisp/org-compat.el
index 9c286961a..c40510c35 100644
--- a/lisp/org-compat.el
+++ b/lisp/org-compat.el
@@ -79,9 +79,6 @@ (declare-function org-fold-region "org-fold" (from to flag &optional spec))
 (declare-function org-fold-show-all "org-fold" (&optional types))
 (declare-function org-fold-show-children "org-fold" (&optional level))
 (declare-function org-fold-show-entry "org-fold" (&optional hide-drawers))
-;; `org-string-equal-ignore-case' is in _this_ file but isn't at the
-;; top-level.
-(declare-function org-string-equal-ignore-case "org-compat" (string1 string2))
 
 (defvar calendar-mode-map)
 (defvar org-complex-heading-regexp)
@@ -136,105 +133,26 @@ (defmacro org-compat-call (fun &rest args)
   (let ((compat (intern (format "compat--%s" fun))))
     `(,(if (fboundp compat) compat fun) ,@args)))
 
-\f
-;;; Emacs < 29 compatibility
-
-(defvar org-file-has-changed-p--hash-table (make-hash-table :test #'equal)
-  "Internal variable used by `org-file-has-changed-p'.")
-
-(if (fboundp 'file-has-changed-p)
-    (defalias 'org-file-has-changed-p #'file-has-changed-p)
-  (defun org-file-has-changed-p (file &optional tag)
-    "Return non-nil if FILE has changed.
-The size and modification time of FILE are compared to the size
-and modification time of the same FILE during a previous
-invocation of `org-file-has-changed-p'.  Thus, the first invocation
-of `org-file-has-changed-p' always returns non-nil when FILE exists.
-The optional argument TAG, which must be a symbol, can be used to
-limit the comparison to invocations with identical tags; it can be
-the symbol of the calling function, for example."
-    (let* ((file (directory-file-name (expand-file-name file)))
-           (remote-file-name-inhibit-cache t)
-           (fileattr (file-attributes file 'integer))
-	   (attr (and fileattr
-                      (cons (file-attribute-size fileattr)
-		            (file-attribute-modification-time fileattr))))
-	   (sym (concat (symbol-name tag) "@" file))
-	   (cachedattr (gethash sym org-file-has-changed-p--hash-table)))
-      (when (not (equal attr cachedattr))
-        (puthash sym attr org-file-has-changed-p--hash-table)))))
-
-(if (fboundp 'string-equal-ignore-case)
-    (defalias 'org-string-equal-ignore-case #'string-equal-ignore-case)
-  ;; From Emacs subr.el.
-  (defun org-string-equal-ignore-case (string1 string2)
-    "Like `string-equal', but case-insensitive.
-Upper-case and lower-case letters are treated as equal.
-Unibyte strings are converted to multibyte for comparison."
-    (eq t (compare-strings string1 0 nil string2 0 nil t))))
-
-\f
-;;; Emacs < 28.1 compatibility
-
-(if (fboundp 'file-name-concat)
-    (defalias 'org-file-name-concat #'file-name-concat)
-  (defun org-file-name-concat (directory &rest components)
-    "Append COMPONENTS to DIRECTORY and return the resulting string.
-
-Elements in COMPONENTS must be a string or nil.
-DIRECTORY or the non-final elements in COMPONENTS may or may not end
-with a slash -- if they don't end with a slash, a slash will be
-inserted before contatenating."
-    (save-match-data
-      (mapconcat
-       #'identity
-       (delq nil
-             (mapcar
-              (lambda (str)
-                (when (and str (not (seq-empty-p str))
-                           (string-match "\\(.+\\)/?" str))
-                  (match-string 1 str)))
-              (cons directory components)))
-       "/"))))
-
-(if (fboundp 'directory-empty-p)
-    (defalias 'org-directory-empty-p #'directory-empty-p)
-  (defun org-directory-empty-p (dir)
-    "Return t if DIR names an existing directory containing no other files."
-    (and (file-directory-p dir)
-         (null (directory-files dir nil directory-files-no-dot-files-regexp t)))))
-
-(if (fboundp 'string-clean-whitespace)
-    (defalias 'org-string-clean-whitespace #'string-clean-whitespace)
-  ;; From Emacs subr-x.el.
-  (defun org-string-clean-whitespace (string)
-    "Clean up whitespace in STRING.
-All sequences of whitespaces in STRING are collapsed into a
-single space character, and leading/trailing whitespace is
-removed."
-    (let ((blank "[[:blank:]\r\n]+"))
-      (string-trim (replace-regexp-in-string blank " " string t t)
-                   blank blank))))
-
-(if (fboundp 'format-prompt)
-    (defalias 'org-format-prompt #'format-prompt)
-  ;; From Emacs minibuffer.el, inlining
-  ;; `minibuffer-default-prompt-format' value and replacing `length<'
-  ;; (both new in Emacs 28.1).
-  (defun org-format-prompt (prompt default &rest format-args)
-    "Compatibility substitute for `format-prompt'."
-    (concat
-     (if (null format-args)
-         prompt
-       (apply #'format prompt format-args))
-     (and default
-          (or (not (stringp default))
-              (> (length default) 0))
-          (format " (default %s)"
-                  (if (consp default)
-                      (car default)
-                    default)))
-     ": ")))
+;; Obsolete compatibility wrappers used before inclusion of compat.el.
+
+(define-obsolete-function-alias 'org-file-has-changed-p
+  'file-has-changed-p "9.7")
+(define-obsolete-function-alias 'org-string-equal-ignore-case
+  'string-equal-ignore-case "9.7")
+(define-obsolete-function-alias 'org-file-name-concat
+  'file-name-concat "9.7")
+(define-obsolete-function-alias 'org-directory-empty-p
+  'directory-empty-p "9.7")
+(define-obsolete-function-alias 'org-string-clean-whitespace
+  'string-clean-whitespace "9.7")
+(define-obsolete-function-alias 'org-format-prompt
+  'format-prompt "9.7")
+(define-obsolete-function-alias 'org-xor
+  'xor "9.7")
+(define-obsolete-function-alias 'org-string-distance
+  'string-distance "9.7")
+(define-obsolete-function-alias 'org-buffer-hash
+  'buffer-hash "9.7")
 
 \f
 ;;; Emacs < 27.1 compatibility
@@ -252,22 +170,6 @@ (if (version< emacs-version "27.1")
       (replace-buffer-contents source))
   (defalias 'org-replace-buffer-contents #'replace-buffer-contents))
 
-(unless (fboundp 'proper-list-p)
-  ;; `proper-list-p' was added in Emacs 27.1.  The function below is
-  ;; taken from Emacs subr.el 200195e824b^.
-  (defun proper-list-p (object)
-    "Return OBJECT's length if it is a proper list, nil otherwise.
-A proper list is neither circular nor dotted (i.e., its last cdr
-is nil)."
-    (and (listp object) (ignore-errors (length object)))))
-
-(if (fboundp 'xor)
-    ;; `xor' was added in Emacs 27.1.
-    (defalias 'org-xor #'xor)
-  (defsubst org-xor (a b)
-    "Exclusive `or'."
-    (if a (not b) b)))
-
 (unless (fboundp 'pcomplete-uniquify-list)
   ;; The misspelled variant was made obsolete in Emacs 27.1
   (defalias 'pcomplete-uniquify-list 'pcomplete-uniqify-list))
@@ -297,29 +199,6 @@ (defun org--set-faces-extend (faces extend-p)
   (when (fboundp 'set-face-extend)
     (mapc (lambda (f) (set-face-extend f extend-p)) faces)))
 
-(if (fboundp 'string-distance)
-    (defalias 'org-string-distance 'string-distance)
-  (defun org-string-distance (s1 s2)
-    "Return the edit (levenshtein) distance between strings S1 S2."
-    (let* ((l1 (length s1))
-	   (l2 (length s2))
-	   (dist (vconcat (mapcar (lambda (_) (make-vector (1+ l2) nil))
-				  (number-sequence 1 (1+ l1)))))
-	   (in (lambda (i j) (aref (aref dist i) j))))
-      (setf (aref (aref dist 0) 0) 0)
-      (dolist (j (number-sequence 1 l2))
-        (setf (aref (aref dist 0) j) j))
-      (dolist (i (number-sequence 1 l1))
-        (setf (aref (aref dist i) 0) i)
-        (dolist (j (number-sequence 1 l2))
-	  (setf (aref (aref dist i) j)
-	        (min
-	         (1+ (funcall in (1- i) j))
-	         (1+ (funcall in i (1- j)))
-	         (+ (if (equal (aref s1 (1- i)) (aref s2 (1- j))) 0 1)
-		    (funcall in (1- i) (1- j)))))))
-      (funcall in l1 l2))))
-
 (define-obsolete-function-alias 'org-babel-edit-distance 'org-string-distance
   "9.5")
 
@@ -340,23 +219,6 @@ (if (fboundp 'line-number-display-width)
     (defalias 'org-line-number-display-width 'line-number-display-width)
   (defun org-line-number-display-width (&rest _) 0))
 
-(if (fboundp 'buffer-hash)
-    (defalias 'org-buffer-hash 'buffer-hash)
-  (defun org-buffer-hash () (md5 (current-buffer))))
-
-(unless (fboundp 'file-attribute-modification-time)
-  (defsubst file-attribute-modification-time (attributes)
-    "The modification time in ATTRIBUTES returned by `file-attributes'.
-This is the time of the last change to the file's contents, and
-is a Lisp timestamp in the same style as `current-time'."
-    (nth 5 attributes)))
-
-(unless (fboundp 'file-attribute-size)
-  (defsubst file-attribute-size (attributes)
-    "The size (in bytes) in ATTRIBUTES returned by `file-attributes'.
-This is a floating point number if the size is too large for an integer."
-    (nth 7 attributes)))
-
 \f
 ;;; Obsolete aliases (remove them after the next major release).
 
@@ -1460,7 +1322,7 @@ (defun org-mode-flyspell-verify ()
 	  (and log
 	       (let ((drawer (org-element-lineage element '(drawer))))
 		 (and drawer
-		      (org-string-equal-ignore-case
+		      (string-equal-ignore-case
 		       log (org-element-property :drawer-name drawer))))))
 	nil)
        (t
diff --git a/lisp/org-fold-core.el b/lisp/org-fold-core.el
index 43c6b2b74..c699c115b 100644
--- a/lisp/org-fold-core.el
+++ b/lisp/org-fold-core.el
@@ -841,7 +841,7 @@ (defun org-fold-core-next-visibility-change (&optional pos limit ignore-hidden-p
 			  (lambda (p) (next-single-char-property-change p 'invisible nil limit)))))
 	 (next pos))
     (while (and (funcall cmp next limit)
-		(not (org-xor
+		(not (xor
                     invisible-initially?
                     (funcall invisible-p
                              (if previous-p
diff --git a/lisp/org-lint.el b/lisp/org-lint.el
index dc18b657c..3aa148e69 100644
--- a/lisp/org-lint.el
+++ b/lisp/org-lint.el
@@ -383,7 +383,7 @@ (defun org-lint-duplicate-custom-id (ast)
    ast
    'node-property
    (lambda (property)
-     (and (org-string-equal-ignore-case
+     (and (string-equal-ignore-case
            "CUSTOM_ID" (org-element-property :key property))
 	  (org-element-property :value property)))
    (lambda (property _) (org-element-property :begin property))
diff --git a/lisp/org-persist.el b/lisp/org-persist.el
index d8b7dc4a1..45031c1dc 100644
--- a/lisp/org-persist.el
+++ b/lisp/org-persist.el
@@ -279,12 +279,12 @@ (defgroup org-persist nil
 
 (defcustom org-persist-directory
   (expand-file-name
-   (org-file-name-concat
+   (file-name-concat
     (let ((cache-dir (when (fboundp 'xdg-cache-home)
                        (xdg-cache-home))))
       (if (or (seq-empty-p cache-dir)
               (not (file-exists-p cache-dir))
-              (file-exists-p (org-file-name-concat
+              (file-exists-p (file-name-concat
                               user-emacs-directory
                               "org-persist")))
           user-emacs-directory
@@ -675,13 +675,13 @@ (defalias 'org-persist-read:version #'org-persist-read:elisp-data)
 
 (defun org-persist-read:file (_ path __)
   "Read file container from PATH."
-  (when (and path (file-exists-p (org-file-name-concat org-persist-directory path)))
-    (org-file-name-concat org-persist-directory path)))
+  (when (and path (file-exists-p (file-name-concat org-persist-directory path)))
+    (file-name-concat org-persist-directory path)))
 
 (defun org-persist-read:url (_ path __)
   "Read file container from PATH."
-  (when (and path (file-exists-p (org-file-name-concat org-persist-directory path)))
-    (org-file-name-concat org-persist-directory path)))
+  (when (and path (file-exists-p (file-name-concat org-persist-directory path)))
+    (file-name-concat org-persist-directory path)))
 
 (defun org-persist-read:index (cont index-file _)
   "Read index container CONT from INDEX-FILE."
@@ -750,7 +750,7 @@ (defun org-persist--load-index ()
   "Load `org-persist--index'."
   (org-persist-load:index
    `(index ,org-persist--storage-version)
-   (org-file-name-concat org-persist-directory org-persist-index-file)
+   (file-name-concat org-persist-directory org-persist-index-file)
    nil))
 
 ;;;; Writing container data
@@ -805,7 +805,7 @@ (defun org-persist-write:file (c collection)
         (setq path (cadr c)))
       (let* ((persist-file (plist-get collection :persist-file))
              (ext (file-name-extension path))
-             (file-copy (org-file-name-concat
+             (file-copy (file-name-concat
                          org-persist-directory
                          (format "%s-%s.%s" persist-file (md5 path) ext))))
         (unless (file-exists-p file-copy)
@@ -851,7 +851,7 @@ (defun org-persist-write:index (container _)
                  org-persist-directory))))
   (when (file-exists-p org-persist-directory)
     (let ((index-file
-           (org-file-name-concat org-persist-directory org-persist-index-file)))
+           (file-name-concat org-persist-directory org-persist-index-file)))
       (org-persist--merge-index-with-disk)
       (org-persist--write-elisp-file index-file org-persist--index t t)
       (setq org-persist--index-age
@@ -866,7 +866,7 @@ (defun org-persist--save-index ()
 (defun org-persist--merge-index-with-disk ()
   "Merge `org-persist--index' with the current index file on disk."
   (let* ((index-file
-          (org-file-name-concat org-persist-directory org-persist-index-file))
+          (file-name-concat org-persist-directory org-persist-index-file))
          (disk-index
           (and (file-exists-p index-file)
                (org-file-newer-than-p index-file org-persist--index-age)
@@ -888,8 +888,8 @@ (defun org-persist--merge-index (base other)
         (dolist (item (nreverse new))
           (unless (or (memq 'index (mapcar #'car (plist-get item :container)))
                       (not (file-exists-p
-                            (org-file-name-concat org-persist-directory
-                                                  (plist-get item :persist-file))))
+                          (file-name-concat org-persist-directory
+                                            (plist-get item :persist-file))))
                       (member (plist-get item :persist-file) base-files))
             (push item combined)))
         (nreverse combined))
@@ -990,7 +990,7 @@ (cl-defun org-persist-read (container &optional associated hash-must-match load
   (let* ((collection (org-persist--find-index `(:container ,container :associated ,associated)))
          (persist-file
           (when collection
-            (org-file-name-concat
+            (file-name-concat
              org-persist-directory
              (plist-get collection :persist-file))))
          (data nil))
@@ -1078,7 +1078,7 @@ (defun org-persist-write (container &optional associated ignore-return)
                          (run-hook-with-args-until-success 'org-persist-before-write-hook v associated))
                        (plist-get collection :container)))
       (when (or (file-exists-p org-persist-directory) (org-persist--save-index))
-        (let ((file (org-file-name-concat org-persist-directory (plist-get collection :persist-file)))
+        (let ((file (file-name-concat org-persist-directory (plist-get collection :persist-file)))
               (data (mapcar (lambda (c) (cons c (org-persist-write:generic c collection)))
                             (plist-get collection :container))))
           (puthash file data org-persist--write-cache)
@@ -1098,11 +1098,11 @@ (defun org-persist-write-all (&optional associated)
            ;; The container is an `index' container.
            (eq 'index (caar (plist-get (car org-persist--index) :container)))
            (or (not (file-exists-p org-persist-directory))
-               (org-directory-empty-p org-persist-directory)))
+               (directory-empty-p org-persist-directory)))
       ;; Do not write anything, and clear up `org-persist-directory' to reduce
       ;; clutter.
       (when (and (file-exists-p org-persist-directory)
-                 (org-directory-empty-p org-persist-directory))
+                 (directory-empty-p org-persist-directory))
         (delete-directory org-persist-directory))
     ;; Write the data.
     (let (all-containers)
@@ -1143,7 +1143,7 @@ (defun org-persist--gc-persist-file (persist-file)
   "Garbage collect PERSIST-FILE."
   (when (file-exists-p persist-file)
     (delete-file persist-file)
-    (when (org-directory-empty-p (file-name-directory persist-file))
+    (when (directory-empty-p (file-name-directory persist-file))
       (delete-directory (file-name-directory persist-file)))))
 
 (defmacro org-persist-associated-files:generic (container collection)
@@ -1181,7 +1181,7 @@ (defun org-persist-gc ()
   (let (new-index
         (remote-files-num 0)
         (orphan-files
-         (delete (org-file-name-concat org-persist-directory org-persist-index-file)
+         (delete (file-name-concat org-persist-directory org-persist-index-file)
                  (when (file-exists-p org-persist-directory)
                    (directory-files-recursively org-persist-directory ".+")))))
     (dolist (collection org-persist--index)
@@ -1189,7 +1189,7 @@ (defun org-persist-gc ()
              (web-file (and file (string-match-p "\\`https?://" file)))
              (file-remote (when file (file-remote-p file)))
              (persist-file (when (plist-get collection :persist-file)
-                             (org-file-name-concat
+                             (file-name-concat
                               org-persist-directory
                               (plist-get collection :persist-file))))
              (expired? (org-persist--gc-expired-p
diff --git a/lisp/org-refile.el b/lisp/org-refile.el
index 03c351cf6..9797a0633 100644
--- a/lisp/org-refile.el
+++ b/lisp/org-refile.el
@@ -666,7 +666,7 @@ (defun org-refile-get-location (&optional prompt default-buffer new-nodes)
          (prompt (let ((default (or (car org-refile-history)
                                     (and (assoc cbnex tbl) (setq cdef cbnex)
                                          cbnex))))
-                   (org-format-prompt prompt default)))
+                   (format-prompt prompt default)))
 	 pa answ parent-target child parent old-hist)
     (setq old-hist org-refile-history)
     (setq answ (funcall cfunc prompt tbl nil (not new-nodes)
diff --git a/lisp/org.el b/lisp/org.el
index 162982405..15fdd2212 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -19486,7 +19486,7 @@ (defun org-fill-paragraph (&optional justify region)
 		 (barf-if-buffer-read-only)
 		 (list (when current-prefix-arg 'full) t)))
   (let ((hash (and (not (buffer-modified-p))
-		   (org-buffer-hash))))
+		   (buffer-hash))))
     (cond
      ((and region transient-mark-mode mark-active
 	   (not (eq (region-beginning) (region-end))))
@@ -19511,7 +19511,7 @@ (defun org-fill-paragraph (&optional justify region)
     ;; If we didn't change anything in the buffer (and the buffer was
     ;; previously unmodified), then flip the modification status back
     ;; to "unchanged".
-    (when (and hash (equal hash (org-buffer-hash)))
+    (when (and hash (equal hash (buffer-hash)))
       (set-buffer-modified-p nil))
     ;; Return non-nil.
     t))
diff --git a/lisp/ox.el b/lisp/ox.el
index d75a80a2e..a3f37b5e8 100644
--- a/lisp/ox.el
+++ b/lisp/ox.el
@@ -4643,11 +4643,11 @@ (defun org-export-resolve-radio-link (link info)
 
 Return value can be a radio-target object or nil.  Assume LINK
 has type \"radio\"."
-  (let ((path (org-string-clean-whitespace (org-element-property :path link))))
+  (let ((path (string-clean-whitespace (org-element-property :path link))))
     (org-element-map (plist-get info :parse-tree) 'radio-target
       (lambda (radio)
-	(and (org-string-equal-ignore-case
-	      (org-string-clean-whitespace (org-element-property :value radio))
+	(and (string-equal-ignore-case
+	      (string-clean-whitespace (org-element-property :value radio))
               path)
 	     radio))
       info 'first-match)))
diff --git a/testing/lisp/test-ol.el b/testing/lisp/test-ol.el
index a38d9f979..565539571 100644
--- a/testing/lisp/test-ol.el
+++ b/testing/lisp/test-ol.el
@@ -63,19 +63,19 @@ (ert-deftest test-org-link/toggle-link-display ()
       (dotimes (_ 2)
         (goto-char 1)
         (re-search-forward "\\[")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "example")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "com")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "]")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (re-search-forward "\\[")
         (should-not (org-invisible-p))
         (re-search-forward "link")
         (should-not (org-invisible-p))
         (re-search-forward "]")
-        (should-not (org-xor org-link-descriptive (org-invisible-p)))
+        (should-not (xor org-link-descriptive (org-invisible-p)))
         (org-toggle-link-display)))))
 
 \f
diff --git a/testing/lisp/test-org-capture.el b/testing/lisp/test-org-capture.el
index 0ed44c6af..6a47b3384 100644
--- a/testing/lisp/test-org-capture.el
+++ b/testing/lisp/test-org-capture.el
@@ -154,7 +154,7 @@ (ert-deftest test-org-capture/abort ()
   "Test aborting a capture process."
   ;; Newly create capture buffer should not be saved.
   (let ((capture-file (make-temp-name
-                       (org-file-name-concat
+                       (file-name-concat
                         temporary-file-directory
                         "org-test"))))
     (unwind-protect
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #6: v4-0005-org-manual.org-Document-compat-library-installati.patch --]
[-- Type: text/x-patch, Size: 2228 bytes --]

From 1f086a879fcaeaca3e8b4e1c6a9b6c686e3a2fdf Mon Sep 17 00:00:00 2001
Message-Id: <1f086a879fcaeaca3e8b4e1c6a9b6c686e3a2fdf.1682849409.git.yantar92@posteo.net>
In-Reply-To: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
References: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sat, 1 Apr 2023 12:18:57 +0200
Subject: [PATCH v4 5/8] org-manual.org: Document compat library installation

* doc/org-manual.org (Using Org's git repository): Document that users
must also install compat library when using git version of Org.
* etc/ORG-NEWS (Org mode now uses =compat.el= third-party package to
support older Emacs versions): Announce amendments to the Org
installation from git sources.
---
 doc/org-manual.org | 4 ++++
 etc/ORG-NEWS       | 7 +++++++
 2 files changed, 11 insertions(+)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 007126714..89fdd3d0f 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -140,6 +140,10 @@ *** Using Org's git repository
 (add-to-list 'load-path "~/src/org-mode/lisp")
 #+end_src
 
+You must also manually install =compat= library required by Org mode.
+Using built-in =package.el=, you can run =M-x package-install <RET>
+compat <RET>=.
+
 You can also compile with =make=, generate the documentation with
 =make doc=, create a local configuration with =make config= and
 install Org with =make install=.  Please run =make help= to get the
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 03894f128..6fd117ef7 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -13,6 +13,13 @@ Please send Org bug reports to mailto:emacs-orgmode@gnu.org.
 
 * Version 9.7 (not released yet)
 ** Important announcements and breaking changes
+*** Org mode now uses =compat.el= third-party package to support older Emacs versions
+
+This change is mostly technical and should not affect most users.
+However, people who install Org from git source might be affected.
+It is now necessary to manually install =compat.el= using Emacs'
+package manager.
+
 *** "Priority" used to sort items in agenda is renamed to "urgency"
 
 Previously, ~priority-up~ and ~priority-down~ in
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #7: v4-0006-mk-Echo-main-compilation-states-when-running-make.patch --]
[-- Type: text/x-patch, Size: 2057 bytes --]

From 3ff50698fd9c0d9cf9795f3a7fcef9b3b7730061 Mon Sep 17 00:00:00 2001
Message-Id: <3ff50698fd9c0d9cf9795f3a7fcef9b3b7730061.1682849409.git.yantar92@posteo.net>
In-Reply-To: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
References: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Sun, 2 Apr 2023 14:05:22 +0200
Subject: [PATCH v4 6/8] mk: Echo main compilation states when running make

* lisp/Makefile (all compile compile-dirty):
($(LISPV)):
($(LISPI)): Echo compile stage.
* mk/targets.mk (uppkg): Echo compile stage and hide the installation
command.
---
 lisp/Makefile | 7 +++++--
 mk/targets.mk | 1 +
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/lisp/Makefile b/lisp/Makefile
index f507f18a2..45d8109f0 100644
--- a/lisp/Makefile
+++ b/lisp/Makefile
@@ -20,6 +20,7 @@ _ORGCM_ := dirall single source slint1 slint2
 
 # 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
@@ -52,12 +53,14 @@ slint1:
 autoloads:	cleanauto $(LISPI) $(LISPV)
 
 $(LISPV):	$(LISPF)
-	@echo "org-version: $(ORGVERSION) ($(GITVERSION))"
+	@$(info ========= Auto-generating Org version number)
+	@$(info org-version: $(ORGVERSION) ($(GITVERSION)))
 	@$(RM) $(@)
 	@$(MAKE_ORG_VERSION)
 
 $(LISPI):	$(LISPV) $(LISPF)
-	@echo "org-loaddefs: $(ORGVERSION) ($(GITVERSION))"
+	@$(info ========= Auto-generating Org loaddefs)
+	@$(info org-loaddefs: $(ORGVERSION) ($(GITVERSION)))
 	@$(RM) $(@)
 	@$(MAKE_ORG_INSTALL)
 
diff --git a/mk/targets.mk b/mk/targets.mk
index 18140c5c0..ddd959c61 100644
--- a/mk/targets.mk
+++ b/mk/targets.mk
@@ -103,6 +103,7 @@ ifeq ($(TEST_NO_AUTOCLEAN),) # define this variable to leave $(testdir) around f
 endif
 
 uppkg::
+	$(info ========= Installing required third-party packages)
 	@$(MKDIR) -p $(pkgdir)
 	-@$(INSTALL_PACKAGES)
 
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #8: v4-0007-Makefile-Document-new-targets-in-make-helpall.patch --]
[-- Type: text/x-patch, Size: 1521 bytes --]

From 6e0254d985970e6a96ad9f0f9a8f9ec377b9dce2 Mon Sep 17 00:00:00 2001
Message-Id: <6e0254d985970e6a96ad9f0f9a8f9ec377b9dce2.1682849409.git.yantar92@posteo.net>
In-Reply-To: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
References: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Thu, 13 Apr 2023 14:33:16 +0200
Subject: [PATCH v4 7/8] * Makefile: Document new targets in make helpall

Document the newly added cleanpkg and uppkg targets.
---
 Makefile | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Makefile b/Makefile
index f476a3ea7..5e0e0fdff 100644
--- a/Makefile
+++ b/Makefile
@@ -41,6 +41,7 @@ helpall::
 	$(info Cleaning)
 	$(info ========)
 	$(info make clean          - remove built Org ELisp files and documentation)
+	$(info make cleanpkg      - remove third-party packages downloaded via make uppkg)
 	$(info make cleanall       - remove everything that can be built and all remnants)
 	$(info make clean-install  - remove previous Org installation)
 	$(info )
@@ -81,6 +82,7 @@ helpall::
 	$(info Convenience)
 	$(info ===========)
 	$(info make up0            - pull from upstream)
+	$(info make uppkg          - download third-party packages required for compilation)
 	$(info make up1            - pull from upstream, build and check)
 	$(info make up2            - pull from upstream, build, check and install)
 	$(info make update         - pull from upstream and build)
-- 
2.40.0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #9: v4-0008-lisp-org-compat.el-Throw-error-without-compat-in-.patch --]
[-- Type: text/x-patch, Size: 1476 bytes --]

From 4316fc76877bea60d89e32af0caf14d4da363f69 Mon Sep 17 00:00:00 2001
Message-Id: <4316fc76877bea60d89e32af0caf14d4da363f69.1682849409.git.yantar92@posteo.net>
In-Reply-To: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
References: <3946dbb956afcd005cba0f1899acae5a74a109d4.1682849409.git.yantar92@posteo.net>
From: Ihor Radchenko <yantar92@posteo.net>
Date: Tue, 18 Apr 2023 13:05:53 +0200
Subject: [PATCH v4 8/8] * lisp/org-compat.el: Throw error without compat in
 older Emacs

Demand compat when using older Emacs.  This is a better approach
compared to (require 'compat nil 'noerror) because not erring on the
`require' would generate a bunch of cryptic errors about functions not
being defined from user perspective.
---
 lisp/org-compat.el | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/lisp/org-compat.el b/lisp/org-compat.el
index c40510c35..205fb30af 100644
--- a/lisp/org-compat.el
+++ b/lisp/org-compat.el
@@ -94,9 +94,10 @@ (defvar org-fold-core-style)
 \f
 ;;; compat.el
 
-;; Do not throw an error when not available - assume latest Emacs
-;; version (built-in Org).
-(require 'compat nil 'noerror)
+;; Use compat for older Emacs, supplying helpful error message if
+;; compat is not available.
+(when (version< emacs-version "29")
+  (org-require-package 'compat))
 
 ;; Provide compatibility macros when we are a part of Emacs.
 ;; See https://elpa.gnu.org/packages/doc/compat.html#Usage
-- 
2.40.0


[-- Attachment #10: Type: text/plain, Size: 224 bytes --]


-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [PATCH] epm.el: A CLI tool for package.el
  2023-04-30 10:39                       ` [PATCH v4] " Ihor Radchenko
@ 2023-05-03 12:14                         ` Max Nikulin
  2023-05-04 10:24                           ` Ihor Radchenko
  2023-05-06  6:39                         ` [PATCH v4] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)) Max Nikulin
  1 sibling, 1 reply; 36+ messages in thread
From: Max Nikulin @ 2023-05-03 12:14 UTC (permalink / raw)
  To: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 4229 bytes --]

On 30/04/2023 17:39, Ihor Radchenko wrote:
> Max Nikulin writes:
> 
>>       ./epm.el -Q --epm-dir $(emacs_pkgdir)/emacs-%e install compat
> 
> Good idea. Although, we should not overdo this package management thing.
> If we really need complex functionality here, we should better just use
> cask/eldev instead of re-inventing the wheel.

I have not tried cask or eldev, so I can not reason on supposed workflow.

> Are you willing to improve the draft to be ready for upstream?

See the attachment. Interface is subject to change to better fit 
particular use cases.

> I think that we need to zoom out a bit and discuss the contexts where
> Org build system is used:
> 
> 1. During Org development, by developers who know what they are doing

...and who prefer failure to files installed to unexpected directory 
when configuration contains a mistake or it is not activated.

> 2. By ordinary users, not necessarily familiar with GNU make and all the
>     associated build process conventions.

For those who are not familiar with convention any target may be 
specified in docs, keeping usual meaning of the default "all" target.

> For example, my `package-user-dir' contains a
> number of forks with additional patches applied - it is not the
> environment I want to develop (and test) Org in.

I think, for ordinary user it is better to keep build time and runtime 
`package-user-dir' the same. Developers, especially when multiple 
versions are involved, should have isolated sandboxes similar to 
"python3 -m venv" and activating scripts setting proper environment 
variables.

> Org users will likely use make autoloads, make, make docs, and make
> repro.

make autoloads should be necessary only to run org uncompiled. My 
impression is that some bugs may exist, so make clean and make autoloads 
are necessary during updates.

> However, make repro and
> optionally make docs should avoid re-using user packages as it may cause
> inconsistent results if the `package-user-dir' is messed up.

I agree concerning "make repro", but unsure for docs.

> One way to handle the above scenarios might be your idea with AUTODEP.
> By default, it will be "auto":
>   - make compile, docs  :: Re-use default `package-user-dir'
>   - make repro    :: Auto-download and ignore `package-user-dir'
>   - other targets :: Prompt the user
> Alternatives will be meant to be used as
> 
>      AUTODEP=download/user/no make target.
> 
> triggering unconditional downloading, using `package-user-dir', and not
> using any guess, correspondingly.

In general I agree that strategy may depend on specified target. The 
only issue that make allows to specify several targets. An I am unsure 
concerning user prompt.

I have realized that as soon as build dependencies are involved, 
Makefile should use emacs -q, not emacs -Q since -Q excludes site-lisp 
directories created by e.g. elpa-compat debian package.

> I think that it is stretching a bit beyond the complexity we should
> allow within Org build system. In your scenario, I can simply do
> make cleanpkg and re-download the latest dependencies.

I would prefer clear error message that package version is not 
satisfactory. However such feature may be added later.

>> #!/bin/sh
>> ":"; # -*- mode: emacs-lisp; lexical-binding: t; -*-
>> ":"; exec emacs --script "$0" "$@"
> 
> Let's not lock to bash. AFAIK, our makefiles can currently work on
> Windows. Using /bin/sh will lead to regression.

It is POSIX shell, not BASH. I am unsure if make can be used on windows 
when e.g. cygwin is not available. Makefiles are full of POSIX tool 
invocations.

Anyway, since emacs binary is customizable in Makefile, the correct way 
to use this script from Makefile is like (perhaps with more flags)

$(EMACS_Q) --script mk/epm.el --epm-dir $(EPMDIR) missing compat

A convenience way

mk/epm.el -q report compat htmlize

is for default emacs from PATH when a user works with shell prompt.


>> 	   (dir (directory-file-name (expand-file-name fmt-expanded))))
>>        ;; `package-user-dir' ~/.emacs.d/elpa by default
>>        ;; `package-directory-list' does not include it
> 
> What does this comment refer to?

To lack of my experience with package.el and site-lisp infrastructure.

[-- Attachment #2: 0001-epm.el-A-CLI-tool-for-package.el.patch --]
[-- Type: text/x-patch, Size: 9177 bytes --]

From 6e0d73abf527901df080f0f5d7d272722d89c87a Mon Sep 17 00:00:00 2001
From: Max Nikulin <manikulin@gmail.com>
Date: Wed, 3 May 2023 18:39:49 +0700
Subject: [PATCH] epm.el: A CLI tool for package.el

* mk/epm.el: A helper to install build time dependencies from ELPA.
---
 mk/epm.el | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 243 insertions(+)
 create mode 100755 mk/epm.el

diff --git a/mk/epm.el b/mk/epm.el
new file mode 100755
index 000000000..2816702bb
--- /dev/null
+++ b/mk/epm.el
@@ -0,0 +1,243 @@
+#!/bin/sh
+":"; # -*- mode: emacs-lisp; lexical-binding: t; -*-
+":"; exec emacs --script "$0" "$@"
+;;; epm.el --- Emacs package management helper for Org Mode
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; Author: Max Nikulin <manikulin@gmail.com>
+;; Created: 2 May 2023
+;; Keywords: maint, tools
+;; Package-Requires: ((emacs "26.1"))
+;; URL: https://orgmode.org
+;; Version: 0.1
+
+;; This file is not part of GNU Emacs.
+;;
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; epm.el is an attempt to create a tool that allows simple package
+;; management outside of interactive Emacs session.  Its purpose
+;; is dependency management for make based workflow for building
+;; and testing and for continuous integration (CI) systems.
+;; It install not available yet packages from ELPA.
+;;
+;; Before compiling or running Org mode uncompiled it is necessary
+;; to install dependencies.  If libraries are already available
+;; in your `load-path' then the following commands should be no-op.
+;;
+;; As a user who prefers to use Org mode version from git
+;; likely you would prefer default ~/emacs.d/elpa directory
+;; for installed packages
+;;
+;;     mk/epm.el -q install compat
+;;
+;; If you are a developer and you need to separate environment from
+;; main Emacs configuration, you may choose some alternative
+;; package directory and use %e substitution for Emacs version.
+;;
+;;     export EPMDIR="$HOME/.cache/epm/emacs-%e"
+;;     /path/to/emacs-src/emacs -q --script mk/epm.el install compat
+;;
+;; or
+;;
+;;     /path/to/emacs-src/emacs -q --script mk/epm.el \
+;;         --epm-dir "$HOME/.cache/epm/emacs-%e" install compat
+;;
+;; Of course, you should specify location to overriden `package-user-dir'
+;; in local.mk.
+;;
+;; When a bug is suspected, it is better to install dependencies
+;; e.g. to TMPDIR to avoid issues with content of user init directory.
+;;
+;; If you prefer to avoid ELPA packages and this script, you still have
+;; --directory/-L option and EMACSLOADPATH environment variable
+;; to specify where required libraries may be loaded.
+;;
+;; Since required dependency may be installed e.g. as elpa-compat Debian
+;; package, it is not recommended to use --quick/-Q or --no-site-lisp/-nsl
+;; options, prefer --no-user-init/-q instead unless you suspect some
+;; issue with site-lisp directories.
+;;
+;; Limitations:
+;; - package.el allows to check if minimum version requirement is satisfied
+;;   for a package, but I have not found API to check it for a library from
+;;   `load-path'.
+;; - Upgrading of a package is not implemented and I am unsure if convenient
+;;   API exists.
+;; - Ideally istead of library list it should be possible to specify .el file
+;;   and dependencies should be taken from the Package-Requires header.
+;;
+;; To get list of libraries that are not available run
+;;
+;;     mk/epm.el -q missing compat
+;;
+;; Non-zero exit code means missing dependencies, its list is printed to stdout.
+;; To check which file will be loaded try
+;;
+;;     mk/epm.el -q report htmlize compat
+;;
+;; Overview of available commands are provided by
+;;
+;;     mk/epm.el -q help
+
+;;; Code:
+
+(require 'format-spec)
+(require 'package)
+(require 'subr-x)
+
+(defvar epm-dir nil
+  "Overrides `package-user-dir' and EPMDIR environment.")
+
+(defun epm--get-script-name ()
+  "Guess command line argument spefifying this script.
+
+Real argument is not available:
+`argi' is \"-scriptload\", `argval' is local variable of `command-line-1',
+`load-file-name' is absolute path, `file-relative-name' is too aggressive
+and adds \"..\" to root."
+  (let ((relative (file-relative-name load-file-name
+                                      command-line-default-directory)))
+    (if (string-suffix-p load-file-name relative)
+        load-file-name
+      relative)))
+
+(defvar epm-script-name (epm--get-script-name)
+  "Name of epm.el as it appears in Emacs command line options")
+
+(defun epm-nonempty-p (s)
+  (and s (not (string-empty-p s))))
+
+(defun epm-init ()
+  (unless (epm-nonempty-p epm-dir)
+    (setq epm-dir (getenv "EPMDIR")))
+  (when (epm-nonempty-p epm-dir)
+    (let* ((fmt-expanded (format-spec epm-dir `((?e . ,emacs-version))))
+	   (dir (directory-file-name (expand-file-name fmt-expanded command-line-default-directory))))
+      ;; `package-user-dir' ~/.emacs.d/elpa by default even with -Q
+      ;; `package-directory-list' does not include `package-user-dir'.
+      (setq package-user-dir dir)))
+  ;; TODO (load site-run-file 'no-error 'no-message)
+  ;; may be necessary to load elpa-* deb packages when -Q option
+  ;; is used. See Info node "(elisp) Init File".
+  (package-initialize))
+
+(defun epm-library-unavailable-p (lib)
+  (unless (locate-library lib)
+    lib))
+
+(defun epm-missing (libs)
+  ;; TODO consider `require' catching load errors
+  (delq nil (mapcar #'epm-library-unavailable-p libs)))
+
+(defun epm-cmd-help (_cmd _args)
+  "List commands."
+  (princ (format "\
+Usage: %s [--dbg|--debug-on-error] [--epm-dir DIR] COMMAND ARGS...
+   or: %s --script %s [--dbg|--debug-on-error] [--epm-dir DIR] COMMAND ARGS...
+
+A CLI tool to install ELPA packages.
+
+Any Emacs option may be specified, e.g. --quck,-Q, --no-init-file,-q,
+or --directory,-L DIR
+
+--dbg, --debug-on-error
+    Enable `debug-on-error'
+
+--epm-dir DIR
+    Set `package-user-dir'.
+    \"%%e\" is replaced by `emacs-version'.
+    Alternatively EPMDIR environment variable may be specified.
+\n"
+                 epm-script-name (car command-line-args) epm-script-name))
+  (pcase-dolist (`(,name . ,func) epm-commands)
+    (princ name)
+    (terpri)
+    (princ
+     (replace-regexp-in-string
+      "\\`\\|\n" "\\1    "
+      (documentation func) 'fixedcase nil))
+    (terpri)
+    (terpri)))
+
+(defun epm-cmd-missing (_ libs)
+  "Report not installed libraries and exit with non-zero code."
+  (let ((missing (epm-missing libs)))
+    (when missing
+      (princ (mapconcat #'identity missing " "))
+      (terpri)
+      (kill-emacs 1))))
+
+(defun epm-cmd-install (_ libs)
+  "Install packages from LIBS that are not available yet"
+  ;; TODO force option or update command
+  (let ((missing (epm-missing libs)))
+    (when missing
+      (package-refresh-contents)
+      (make-directory package-user-dir 'parents))
+    (dolist (pkg missing)
+      (package-install (intern pkg)))))
+
+(defun epm-cmd-report (_ libs)
+  "Report paths of available libraries"
+  (princ (format "package-user-dir: %s\n" package-user-dir))
+  ;; (princ (format "load-path: %s\n" load-path))
+  (dolist (name libs)
+    ;; (version-to-list version)
+    (princ (format "%-20s %s " name
+		   (if (package-installed-p (intern name))
+		       "package "
+		     "        ")))
+    (princ (locate-library name))
+    (terpri)))
+
+(defvar epm-commands
+  '(("help" . epm-cmd-help)
+    ("install" . epm-cmd-install)
+    ("missing" . epm-cmd-missing)
+    ("report" . epm-cmd-report)))
+
+(defun epm-command-line-function ()
+  "Handle command line options and arguments specific to epm.
+
+Implements a handler for `command-line-functions'."
+  ;; There is no easy to determine if "--" argument has been processed earlier.
+  ;; TODO "--option=value" is handled by `command-line-1' only for standard arguments.
+  (pcase argi
+    ("--epm-dir"
+     (setq epm-dir (pop command-line-args-left))
+     t)
+    ((or "--dbg" "--debug-on-error")
+     ;; -d is handled as --display, --debug as --debug-init
+     (setq debug-on-error t)
+     t)
+    ((pred (string-match-p "\\`[^-]"))
+     (epm-init)
+     (let ((func (cdr (assoc argi epm-commands)))
+           (cmd-args command-line-args-left))
+       (if (not func)
+           (error "Unknown command %s" argi)
+         (setq command-line-args-left nil)
+         (funcall func argi cmd-args)))
+     t)))
+
+(push #'epm-command-line-function command-line-functions)
+
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
+;;; epm.el ends here
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 36+ messages in thread

* Re: [PATCH] epm.el: A CLI tool for package.el
  2023-05-03 12:14                         ` [PATCH] epm.el: A CLI tool for package.el Max Nikulin
@ 2023-05-04 10:24                           ` Ihor Radchenko
  2023-05-04 16:16                             ` Max Nikulin
  0 siblings, 1 reply; 36+ messages in thread
From: Ihor Radchenko @ 2023-05-04 10:24 UTC (permalink / raw)
  To: Max Nikulin; +Cc: emacs-orgmode

Max Nikulin <manikulin@gmail.com> writes:

>> Good idea. Although, we should not overdo this package management thing.
>> If we really need complex functionality here, we should better just use
>> cask/eldev instead of re-inventing the wheel.
>
> I have not tried cask or eldev, so I can not reason on supposed workflow.

Cask basically solves dependency management, including tracking outdated
dependencies. See
https://cask.readthedocs.io/en/latest/guide/usage.html#quickstart
It can also be used from Makefile.
The downside is that it has to be installed separately.

>> Are you willing to improve the draft to be ready for upstream?
>
> See the attachment. Interface is subject to change to better fit 
> particular use cases.

Thanks!

>> 2. By ordinary users, not necessarily familiar with GNU make and all the
>>     associated build process conventions.
>
> For those who are not familiar with convention any target may be 
> specified in docs, keeping usual meaning of the default "all" target.

I am mostly concerned about the existing users who are already settled
on running the default "make". Their workflow will be broken with your
suggestion. 

>> However, make repro and
>> optionally make docs should avoid re-using user packages as it may cause
>> inconsistent results if the `package-user-dir' is messed up.
>
> I agree concerning "make repro", but unsure for docs.

I am also unsure about docs. Might be either way.

> In general I agree that strategy may depend on specified target. The 
> only issue that make allows to specify several targets.

I am not sure what will be the problem there. make all is also
technically several targets. And running something like
 make compile repro is a bit silly anyway.

> An I am unsure concerning user prompt.

My main concern is for the users who changed their elpa directory
location. They might run into issues unexpectedly.

>> I think that it is stretching a bit beyond the complexity we should
>> allow within Org build system. In your scenario, I can simply do
>> make cleanpkg and re-download the latest dependencies.
>
> I would prefer clear error message that package version is not 
> satisfactory. However such feature may be added later.

I think package.el technically suffers from the same problem. I do not
think that we need to work around it ahead of Emacs. Not until it proves
necessary. (and even then, we should better contribute upstream anyway)

>>> #!/bin/sh
>>> ":"; # -*- mode: emacs-lisp; lexical-binding: t; -*-
>>> ":"; exec emacs --script "$0" "$@"
>> 
>> Let's not lock to bash. AFAIK, our makefiles can currently work on
>> Windows. Using /bin/sh will lead to regression.
>
> It is POSIX shell, not BASH. I am unsure if make can be used on windows 
> when e.g. cygwin is not available. Makefiles are full of POSIX tool 
> invocations.

I thought that more portable way is using "env".

> +(defun epm-nonempty-p (s)
> +  (and s (not (string-empty-p s))))

Can just use `seq-empty-p'.

> +  ;; TODO (load site-run-file 'no-error 'no-message)
> +  ;; may be necessary to load elpa-* deb packages when -Q option
> +  ;; is used. See Info node "(elisp) Init File".

Given the explanation in the top comment, is this necessary?

> +(defun epm-library-unavailable-p (lib)
> +  (unless (locate-library lib)
> +    lib))

> +(defun epm-missing (libs)
> +  ;; TODO consider `require' catching load errors
> +  (delq nil (mapcar #'epm-library-unavailable-p libs)))

Maybe just (cl-remove-if #'locate-library libs)?

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH] epm.el: A CLI tool for package.el
  2023-05-04 10:24                           ` Ihor Radchenko
@ 2023-05-04 16:16                             ` Max Nikulin
  2023-05-05  9:39                               ` Ihor Radchenko
  0 siblings, 1 reply; 36+ messages in thread
From: Max Nikulin @ 2023-05-04 16:16 UTC (permalink / raw)
  To: emacs-orgmode

On 04/05/2023 17:24, Ihor Radchenko wrote:
> Max Nikulin writes:
>> For those who are not familiar with convention any target may be
>> specified in docs, keeping usual meaning of the default "all" target.
> 
> I am mostly concerned about the existing users who are already settled
> on running the default "make". Their workflow will be broken with your
> suggestion.

I hope, it is possible to achieve helpful error message with a link 
explaining change.

>> An I am unsure concerning user prompt.
> 
> My main concern is for the users who changed their elpa directory
> location. They might run into issues unexpectedly.

My opinion is that an error is better for make than user prompt. Try to 
load from default `package-user-dir' but install there only if it is 
explicitly enabled.

>>>> #!/bin/sh
>>>> ":"; # -*- mode: emacs-lisp; lexical-binding: t; -*-
>>>> ":"; exec emacs --script "$0" "$@"
>>>
>>> Let's not lock to bash. AFAIK, our makefiles can currently work on
>>> Windows. Using /bin/sh will lead to regression.
> 
> I thought that more portable way is using "env".

Either env would invoke sh anyway (like in org publish scripts) or it is 
necessary to use env -S feature of relatively recent GNU env. For 
cmd.exe it is better to provide a .bat file running

     emacs --script epm.el ...(something for POSIX $@)

>> +(defun epm-nonempty-p (s)
>> +  (and s (not (string-empty-p s))))
> 
> Can just use `seq-empty-p'.

I am neutral in respect to such change. I just decided to minimize 
dependencies.

>> +  ;; TODO (load site-run-file 'no-error 'no-message)
>> +  ;; may be necessary to load elpa-* deb packages when -Q option
>> +  ;; is used. See Info node "(elisp) Init File".
> 
> Given the explanation in the top comment, is this necessary?

Do you have any arguments against switching from emacs -Q to emacs -q? I 
am still unsure if it is safe enough, but not doing it is not better. I 
am considering adding -q to this script when it is called directly. 
Unfortunately Emacs does not have an option to override effect of -q.




^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH] epm.el: A CLI tool for package.el
  2023-05-04 16:16                             ` Max Nikulin
@ 2023-05-05  9:39                               ` Ihor Radchenko
  0 siblings, 0 replies; 36+ messages in thread
From: Ihor Radchenko @ 2023-05-05  9:39 UTC (permalink / raw)
  To: Max Nikulin; +Cc: emacs-orgmode

Max Nikulin <manikulin@gmail.com> writes:

> Do you have any arguments against switching from emacs -Q to emacs -q? I 
> am still unsure if it is safe enough, but not doing it is not better. I 
> am considering adding -q to this script when it is called directly. 
> Unfortunately Emacs does not have an option to override effect of -q.

Given that --batch implies -q, it should be relatively safe. Maybe
except make repro, where we want to minimize environment effects.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH v4] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el))
  2023-04-30 10:39                       ` [PATCH v4] " Ihor Radchenko
  2023-05-03 12:14                         ` [PATCH] epm.el: A CLI tool for package.el Max Nikulin
@ 2023-05-06  6:39                         ` Max Nikulin
  1 sibling, 0 replies; 36+ messages in thread
From: Max Nikulin @ 2023-05-06  6:39 UTC (permalink / raw)
  To: emacs-orgmode

On 30/04/2023 17:39, Ihor Radchenko wrote:
> Date: Mon, 3 Apr 2023 10:41:50 +0200
> Subject: [PATCH v4 4/8] Use compat.el library instead of ad-hoc compatibility
>   function set

Ihor, I have not noticed removal of

> ;; `flatten-tree' was added in Emacs 27.1.
> (defalias 'org-protocol-flatten
>   (if (fboundp 'flatten-tree) 'flatten-tree

from org-protocol.el

Perhaps I will post more comments, so there is no point to send updated 
patch set in response to this message.



^ permalink raw reply	[flat|nested] 36+ messages in thread

end of thread, other threads:[~2023-05-06  6:48 UTC | newest]

Thread overview: 36+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2021-10-11 10:36 Useful package? Compat.el Timothy
2021-10-11 14:28 ` Russell Adams
2021-10-11 14:40   ` Timothy
2021-10-11 18:04     ` Joost Kremers
2023-01-27 13:23 ` [POLL] Use compat.el in Org? (was: Useful package? Compat.el) Ihor Radchenko
2023-01-27 13:34   ` [POLL] Use compat.el in Org? Bastien Guerry
2023-01-27 20:38     ` Tim Cross
2023-01-27 21:38       ` Daniel Mendler
2023-01-27 22:29         ` Samuel Wales
2023-01-28 16:04   ` [POLL] Use compat.el in Org? (was: Useful package? Compat.el) Kyle Meyer
2023-01-30 11:35   ` Greg Minshall
2023-01-30 19:33     ` Ihor Radchenko
2023-01-30 19:40       ` Greg Minshall
2023-01-30 21:38         ` Daniel Mendler
2023-04-01 10:31   ` [PATCH] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)) Ihor Radchenko
2023-04-01 11:38     ` Daniel Mendler
2023-04-01 14:20       ` Max Nikulin
2023-04-02  8:52         ` Ihor Radchenko
2023-04-02 15:31           ` Max Nikulin
2023-04-02 16:04             ` Ihor Radchenko
2023-04-02 16:37     ` Max Nikulin
2023-04-02 17:00       ` [PATCH v2] " Ihor Radchenko
2023-04-03  8:46         ` [PATCH v3] " Ihor Radchenko
2023-04-08 11:15         ` [PATCH v2] " Max Nikulin
2023-04-08 11:41           ` Ihor Radchenko
2023-04-08 16:37             ` Max Nikulin
2023-04-13 12:42               ` Ihor Radchenko
2023-04-17 17:20                 ` Max Nikulin
2023-04-20  9:27                   ` Ihor Radchenko
2023-04-28 15:27                     ` Max Nikulin
2023-04-30 10:39                       ` [PATCH v4] " Ihor Radchenko
2023-05-03 12:14                         ` [PATCH] epm.el: A CLI tool for package.el Max Nikulin
2023-05-04 10:24                           ` Ihor Radchenko
2023-05-04 16:16                             ` Max Nikulin
2023-05-05  9:39                               ` Ihor Radchenko
2023-05-06  6:39                         ` [PATCH v4] Add compat.el support to Org (was: [POLL] Use compat.el in Org? (was: Useful package? Compat.el)) Max Nikulin

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).