Subject: bug#78844: 30.1; feature request: public interface for querying builtin packages and versions

From: Xiyue Deng <[email-masked]>
Date: Sat, 28 Jun 2025 17:36:03 -0700

Stefan Monnier <[email-masked]> writes:

>>> FWIW, I'd consider it an error if an entry in
>>> `package--builtin-versions` has a nil "version".
>>> There are no such things as "unversioned packages" in this respect.
>>> So, I think the code is OK but the docstring should not mention that
>>> we return nil for packages without a version.
>>
>> I think in principle you are correct given that the current public
>> functions don't provide a way to obtain such a symbol.  But it's
>> possible that someone can construct a symbol, either by hand or through
>> a new interface that queries all builtin packages, which is in
>> `package--builtins' but not in `package--builtin-versions', and the
>> function will return nil.  Do you think it's worth keeping such
>> possibility into consideration?
>
> Returning nil for packages (aka symbols) which aren't in
> `package--builtin-versions` is fine and I have no objection to
> documenting it.
>
> My objection is to
>
>     if @var{package} does not have a version
>
> but not to
>
>     if @var{package} ... is not a buil-tin package
>

I see.  Though technically people can get a package symbol from
`package--builtins', and hence built-in, but not in
`package--builtin-versions', and hence does not have a version
associated.  Maybe I can be more explicit to say "if @var{package} is
built-in but does not have a version ..."?  (Revised as such.)

> [ BTW, note the typo above.  ]

Thanks for spotting.  Also fixed.

>         Stefan

-- 
Regards,
Xiyue Deng
From c5d8fe074c321cce8194b11d032bd2a80e01d526 Mon Sep 17 00:00:00 2001
From: Xiyue Deng <[email-masked]>
Date: Fri, 20 Jun 2025 21:34:45 -0700
Subject: [PATCH] Add public interfaces for accessing builtin package info
 (Bug#78844)

* lisp/emacs-lisp/package.el (package-builtin-packages)
(package-builtin-package-version): New functions.
* doc/lispref/package.texi: Add a section 'Package Information' with
documentation for the new functions.
* etc/NEWS: Add entry for 'package-versioned-builtin-packages' and
'package-builtin-package-version'.
---
 doc/lispref/package.texi   | 25 +++++++++++++++++++++++++
 etc/NEWS                   |  7 +++++++
 lisp/emacs-lisp/package.el | 15 +++++++++++++++
 3 files changed, 47 insertions(+)

diff --git a/doc/lispref/package.texi b/doc/lispref/package.texi
index c96c2f5d234..5341e3e794a 100644
--- a/doc/lispref/package.texi
+++ b/doc/lispref/package.texi
@@ -424,3 +424,28 @@ Forwards-Compatibility
 the manual installed, you can also read the
 @url{https://elpa.gnu.org/packages/doc/compat.html#Usage, Online
 Compat manual}.
+
+@node Package Information
+@section Get built-in packages and versions
+@cindex package information
+
+Emacs ships built-in packages which may also be packaged externally on
+ELPA.  Knowing whether a built-in package has a higher version than on
+ELPA or other packaging systems may be useful for external packaging
+tools.  Those who would like to query such information can use the
+following functions.
+
+@defun package-versioned-builtin-packages
+This function returns the list of packages that are built-in to Emacs
+and have a version.  The returned list contains one symbol for each
+built-in package that has a version, which are likely also packaged
+externally on ELPA.
+@end defun
+
+@defun package-builtin-package-version package
+This function returns the version of a built-in @var{package}.
+@var{package} should be a symbol.  The returned value is a list of
+integers representing the version of PACKAGE in the format returned by
+the function @code{version-to-list}, or nil if @var{package} is built-in
+but has no version or is not a built-in package.
+@end defun
diff --git a/etc/NEWS b/etc/NEWS
index eb85baae341..5ea5f5314a2 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1965,6 +1965,13 @@ installed, total number of packages from all the package archives, total
 number of packages to upgrade and total number of new packages
 available.
 
++++
+*** New functions to query builtin package information.
+'package-versioned-builtin-packages' returns a list of symbols of
+built-in packages; 'package-builtin-package-version' returns the
+version-list of a given package symbol.  These functions provide stable
+interfaces for external tools to query built-in package information.
+
 ** RCIRC
 
 +++
diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el
index 9bcffb1df0b..ccdfb13cfd7 100644
--- a/lisp/emacs-lisp/package.el
+++ b/lisp/emacs-lisp/package.el
@@ -669,6 +669,21 @@ package-alist
 loaded and/or activated, customize `package-load-list'.")
 (put 'package-alist 'risky-local-variable t)
 
+;;;; Public interfaces for accessing built-in package info
+
+(defun package-versioned-builtin-packages ()
+  "Return a list of all the versioned built-in packages.
+The return value is a list of names of built-in packages represented as
+symbols."
+  (mapcar #'car package--builtin-versions))
+
+(defun package-builtin-package-version (package)
+  "Return the version of a built-in PACKAGE given by its symbol.
+The return value is a list of integers representing the version of
+PACKAGE, in the format returned by `version-to-list', or nil if the
+package is built-in but has no version or is not a built-in package."
+  (alist-get package package--builtin-versions))
+
 ;;;###autoload
 (defvar package-activated-list nil
   ;; FIXME: This should implicitly include all builtin packages.
-- 
2.47.2