Subject: [bug#67260] [PATCH v5 2/6] gnu: emacs: Don't hash file names in native compilation.

From: Liliana Marie Prikler <[email-masked]>
Date: Sun, 21 Jan 2024 13:12:42 +0100

* gnu/packages/patches/emacs-native-comp-fix-filenames.patch: New file.
* gnu/local.mk (dist_patch_DATA): Register it here.
* gnu/packages/emacs.scm (emacs-minimal)[source]: Use it here.

Change-Id: I2b7f6b45742a985760f0097bb53910f068e3d8e5
---
 gnu/local.mk                                  |   1 +
 gnu/packages/emacs.scm                        |   1 +
 .../emacs-native-comp-fix-filenames.patch     | 197 ++++++++++++++++++
 3 files changed, 199 insertions(+)
 create mode 100644 gnu/packages/patches/emacs-native-comp-fix-filenames.patch

diff --git a/gnu/local.mk b/gnu/local.mk
index 3d1afd4555..7e6a0c5006 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -1121,6 +1121,7 @@ dist_patch_DATA =						\
   %D%/packages/patches/emacs-kv-fix-tests.patch	\
   %D%/packages/patches/emacs-lispy-fix-thread-last-test.patch   \
   %D%/packages/patches/emacs-native-comp-driver-options.patch   \
+  %D%/packages/patches/emacs-native-comp-fix-filenames.patch   \
   %D%/packages/patches/emacs-next-exec-path.patch   \
   %D%/packages/patches/emacs-next-native-comp-driver-options.patch   \
   %D%/packages/patches/emacs-pasp-mode-quote-file-names.patch  \
diff --git a/gnu/packages/emacs.scm b/gnu/packages/emacs.scm
index e4119ec21d..182de0204d 100644
--- a/gnu/packages/emacs.scm
+++ b/gnu/packages/emacs.scm
@@ -111,6 +111,7 @@ (define-public emacs-minimal
               (patches (search-patches "emacs-exec-path.patch"
                                        "emacs-fix-scheme-indent-function.patch"
                                        "emacs-native-comp-driver-options.patch"
+                                       "emacs-native-comp-fix-filenames.patch"
                                        "emacs-pgtk-super-key-fix.patch"))
               (modules '((guix build utils)))
               (snippet
diff --git a/gnu/packages/patches/emacs-native-comp-fix-filenames.patch b/gnu/packages/patches/emacs-native-comp-fix-filenames.patch
new file mode 100644
index 0000000000..714b707e47
--- /dev/null
+++ b/gnu/packages/patches/emacs-native-comp-fix-filenames.patch
@@ -0,0 +1,197 @@
+Upstream hashes both the absolute file name and the content of a file
+to derive the name for the natively compiled files.  This breaks the
+staged install used in guix, as any $GUIX_PROFILE is distinct from
+the build directory.  It also breaks grafts, as hardcoded store file
+names get rewritten; thus changing the file hash.
+
+Index: emacs-29.1/src/comp.c
+===================================================================
+--- emacs-29.1.orig/src/comp.c
++++ emacs-29.1/src/comp.c
+@@ -4396,26 +4396,18 @@ DEFUN ("comp-el-to-eln-rel-filename", Fc
+        Scomp_el_to_eln_rel_filename, 1, 1, 0,
+        doc: /* Return the relative name of the .eln file for FILENAME.
+ FILENAME must exist, and if it's a symlink, the target must exist.
+-If FILENAME is compressed, it must have the \".gz\" extension,
+-and Emacs must have been compiled with zlib; the file will be
+-uncompressed on the fly to hash its contents.
+-Value includes the original base name, followed by 2 hash values,
+-one for the file name and another for its contents, followed by .eln.  */)
++FILENAME is resolved relative to `load-path' and only the suffix of
++the first matching path is kept.  If FILENAME is not found to be relative
++to any directory `load-path', it is used as-is to construct the return
++value.  Further, if the NATIVE_COMP_BOGUS_DIRS environment variable is set,
++the first matching prefix mentioned in it will be stripped as well. */)
+   (Lisp_Object filename)
+ {
+   CHECK_STRING (filename);
+ 
+-  /* Resolve possible symlinks in FILENAME, so that path_hash below
+-     always compares equal. (Bug#44701).  */
+-  filename = Fexpand_file_name (filename, Qnil);
+-  char *file_normalized = realpath (SSDATA (ENCODE_FILE (filename)), NULL);
+-  if (file_normalized)
+-    {
+-      filename = DECODE_FILE (make_unibyte_string (file_normalized,
+-						   strlen (file_normalized)));
+-      xfree (file_normalized);
+-    }
++  Lisp_Object rel_name = filename;
+ 
++  filename = Fexpand_file_name (filename, Qnil);
+   if (NILP (Ffile_exists_p (filename)))
+     xsignal1 (Qfile_missing, filename);
+ 
+@@ -4423,64 +4415,53 @@ one for the file name and another for it
+   filename = Fw32_long_file_name (filename);
+ #endif
+ 
+-  Lisp_Object content_hash = comp_hash_source_file (filename);
+-
+-  if (suffix_p (filename, ".gz"))
+-    filename = Fsubstring (filename, Qnil, make_fixnum (-3));
+-
+-  /* We create eln filenames with an hash in order to look-up these
+-     starting from the source filename, IOW have a relation
+-
+-     /absolute/path/filename.el + content ->
+-     eln-cache/filename-path_hash-content_hash.eln.
++  Lisp_Object tail = Vload_path;
++  Lisp_Object name_len = Flength (filename);
+ 
+-     'dlopen' can return the same handle if two shared with the same
+-     filename are loaded in two different times (even if the first was
+-     deleted!).  To prevent this scenario the source file content is
+-     included in the hashing algorithm.
+-
+-     As at any point in time no more then one file can exist with the
+-     same filename, should be possible to clean up all
+-     filename-path_hash-* except the most recent one (or the new one
+-     being recompiled).
+-
+-     As installing .eln files compiled during the build changes their
+-     absolute path we need an hashing mechanism that is not sensitive
+-     to that.  For this we replace if match PATH_DUMPLOADSEARCH or
+-     *PATH_REL_LOADSEARCH with '//' before computing the hash.  */
+-
+-  if (NILP (loadsearch_re_list))
++  FOR_EACH_TAIL_SAFE (tail)
+     {
+-      Lisp_Object sys_re =
+-	concat2 (build_string ("\\`[[:ascii:]]+"),
+-		 Fregexp_quote (build_string ("/" PATH_REL_LOADSEARCH "/")));
+-      Lisp_Object dump_load_search =
+-	Fexpand_file_name (build_string (PATH_DUMPLOADSEARCH "/"), Qnil);
+-#ifdef WINDOWSNT
+-      dump_load_search = Fw32_long_file_name (dump_load_search);
+-#endif
+-      loadsearch_re_list = list2 (sys_re, Fregexp_quote (dump_load_search));
+-    }
+-
+-  Lisp_Object lds_re_tail = loadsearch_re_list;
+-  FOR_EACH_TAIL (lds_re_tail)
+-    {
+-      Lisp_Object match_idx =
+-	Fstring_match (XCAR (lds_re_tail), filename, Qnil, Qnil);
+-      if (BASE_EQ (match_idx, make_fixnum (0)))
++      Lisp_Object len = Flength (XCAR (tail));
++      if (XFIXNUM (name_len) < XFIXNUM (len))
++	continue;
++      else if (EQ (Qt, Fcompare_strings (filename, make_fixnum (0), len,
++					 XCAR (tail), make_fixnum (0), len,
++					 Qnil)))
+ 	{
+-	  filename =
+-	    Freplace_match (build_string ("//"), Qt, Qt, filename, Qnil);
++	  filename = Fsubstring (filename, Fadd1 (len), Qnil);
+ 	  break;
+ 	}
+     }
+-  Lisp_Object separator = build_string ("-");
+-  Lisp_Object path_hash = comp_hash_string (filename);
+-  filename = concat2 (Ffile_name_nondirectory (Fsubstring (filename, Qnil,
+-							   make_fixnum (-3))),
+-		      separator);
+-  Lisp_Object hash = concat3 (path_hash, separator, content_hash);
+-  return concat3 (filename, hash, build_string (NATIVE_ELISP_SUFFIX));
++
++  if (Ffile_name_absolute_p (filename))
++    filename = rel_name;
++
++  Lisp_Object bogus_dirs =
++    Fgetenv_internal (build_string ("NATIVE_COMP_BOGUS_DIRS"), Qnil);
++
++  if (!NILP (bogus_dirs))
++  {
++    tail = CALL2I (split-string, bogus_dirs, build_string (":"));
++
++    FOR_EACH_TAIL_SAFE (tail)
++      {
++	Lisp_Object len = Flength (XCAR (tail));
++	if (XFIXNUM (name_len) < XFIXNUM (len))
++	  continue;
++	else if (EQ (Qt, Fcompare_strings (filename, make_fixnum (0), len,
++					   XCAR (tail), make_fixnum (0), len,
++					   Qnil)))
++	  {
++	    filename = Fsubstring (filename, Fadd1 (len), Qnil);
++	    break;
++	  }
++      }
++  }
++
++  if (suffix_p (filename, ".gz"))
++    filename = Fsubstring (filename, Qnil, make_fixnum (-3));
++
++  return concat2(Fsubstring (filename, Qnil, make_fixnum (-3)),
++		 build_string (NATIVE_ELISP_SUFFIX));
+ }
+ 
+ DEFUN ("comp-el-to-eln-filename", Fcomp_el_to_eln_filename,
+@@ -4494,13 +4475,7 @@ If BASE-DIR is non-nil, use it as the di
+ non-absolute BASE-DIR is interpreted as relative to `invocation-directory'.
+ If BASE-DIR is omitted or nil, look for the first writable directory
+ in `native-comp-eln-load-path', and use as BASE-DIR its subdirectory
+-whose name is given by `comp-native-version-dir'.
+-If FILENAME specifies a preloaded file, the directory for the .eln
+-file is the \"preloaded/\" subdirectory of the directory determined
+-as described above.  FILENAME is considered to be a preloaded file if
+-the value of `comp-file-preloaded-p' is non-nil, or if FILENAME
+-appears in the value of the environment variable LISP_PRELOADED;
+-the latter is supposed to be used by the Emacs build procedure.  */)
++whose name is given by `comp-native-version-dir'. */)
+   (Lisp_Object filename, Lisp_Object base_dir)
+ {
+   Lisp_Object source_filename = filename;
+@@ -4541,19 +4516,7 @@ the latter is supposed to be used by the
+ 
+   if (!file_name_absolute_p (SSDATA (base_dir)))
+     base_dir = Fexpand_file_name (base_dir, Vinvocation_directory);
+-
+-  /* In case the file being compiled is found in 'LISP_PRELOADED' or
+-     `comp-file-preloaded-p' is non-nil target for output the
+-     'preloaded' subfolder.  */
+-  Lisp_Object lisp_preloaded =
+-    Fgetenv_internal (build_string ("LISP_PRELOADED"), Qnil);
+   base_dir = Fexpand_file_name (Vcomp_native_version_dir, base_dir);
+-  if (comp_file_preloaded_p
+-      || (!NILP (lisp_preloaded)
+-	  && !NILP (Fmember (CALL1I (file-name-base, source_filename),
+-			     Fmapcar (intern_c_string ("file-name-base"),
+-				      CALL1I (split-string, lisp_preloaded))))))
+-    base_dir = Fexpand_file_name (build_string ("preloaded"), base_dir);
+ 
+   return Fexpand_file_name (filename, base_dir);
+ }
+Index: emacs-29.1/src/Makefile.in
+===================================================================
+--- emacs-29.1.orig/src/Makefile.in
++++ emacs-29.1/src/Makefile.in
+@@ -553,6 +553,7 @@ shortlisp := $(filter-out ${shortlisp_fi
+ ## We don't really need to sort, but may as well use it to remove duplicates.
+ shortlisp := loaddefs.el loadup.el $(sort ${shortlisp})
+ export LISP_PRELOADED = ${shortlisp}
++export NATIVE_COMP_BOGUS_DIRS
+ lisp = $(addprefix ${lispsource}/,${shortlisp})
+ 
+ ## Construct full set of libraries to be linked.
-- 
2.41.0