[PATCH 00/20] Add minetest mods

DoneSubmitted by Maxime Devos.
Details
3 participants
  • Leo Prikler
  • Maxime Devos
  • Andrew Ward
Owner
unassigned
Severity
normal
M
M
Maxime Devos wrote on 2 Aug 2021 17:46
(address . guix-patches@gnu.org)
e186977b4c00c4b883a633186810d4a98b7f68d5.camel@telenet.be
Hi Guix,

This patch series adds a bunch of Minetest mods, but first,
it patches Minetest such that the mods will actually be found.
An importer for Minetest mods from ContentDB
(https://content.minetest.net) is included as well.

I have verified:

* "guix lint" doesn't find any issues,
except ‘no updated found for ...’ and some incorrect
‘... can be upgraded to’.

* the source code doesn't appear to contain any malware

* the license information

* "./pre-inst-env guix build ALL-THE-NEW-PACKAGES" succeeds

* "make check 'TESTS=tests/contentdb.scm'" succeeds

I've also constructed an environment (./pre-inst-env guix environment --ad-hoc)
containing all the mods and minetest, started minetest, created a new world,
enabled all the mods, and placed some random nodes and tried out "worldedit".
This seems to work, but I did not yet try out every mod.

I also constructed an environment with only "minetest" and tried out the
built-in ContentDB installer. It still appears to work.

(The actual patches will be sent once debbugs gives me an issue number)

Maxime Devos (20):
gnu: minetest: Respect --without-tests.
gnu: minetest: Search for mods in MINETEST_MOD_PATH.
gnu: minetest: New package module.
build-system: Add 'minetest-mod-build-system'.
build-system: minetest: Don't retain references to "bash-minimal".
guix: Add ContentDB importer.
gnu: Add minetest-mesecons.
gnu: Add minetest-basic-materials.
gnu: Add minetest-unifieddyes.
gnu: Add minetest-pipeworks.
gnu: Add minetest-coloredwood.
gnu: Add minetest-ethereal.
gnu: Add minetest-technic.
gnu: Add minetest-throwing.
gnu: Add minetest-throwing-arrows.
gnu: Add minetest-unified-inventory.
gnu: Add minetest-worldedit.
gnu: Add minetest-mobs.
gnu: Add minetest-mobs-animal.
gnu: Add minetest-homedecor-modpack.

Makefile.am | 4 +
doc/guix.texi | 32 ++
gnu/local.mk | 2 +
gnu/packages/games.scm | 14 +-
gnu/packages/minetest.scm | 423 ++++++++++++++++++
...vironment-variable-MINETEST_MOD_PATH.patch | 115 +++++
guix/build-system/minetest.scm | 62 +++
guix/import/contentdb.scm | 310 +++++++++++++
guix/scripts/import.scm | 3 +-
guix/scripts/import/contentdb.scm | 106 +++++
po/guix/POTFILES.in | 1 +
tests/contentdb.scm | 227 ++++++++++
12 files changed, 1293 insertions(+), 6 deletions(-)
create mode 100644 gnu/packages/minetest.scm
create mode 100644 gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch
create mode 100644 guix/build-system/minetest.scm
create mode 100644 guix/import/contentdb.scm
create mode 100644 guix/scripts/import/contentdb.scm
create mode 100644 tests/contentdb.scm
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYQgTaxccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7sgiAP0SdZF6fOlA9dBKarATKOjmCQ0Y
plNBwoXkpyKYQzfWAgD/bgLjBSGyAl3rBqsPhmdugfpf+JrO4WDZNTWDqP8P+wc=
=YSTa
-----END PGP SIGNATURE-----


M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 01/20] gnu: minetest: Respect --without-tests.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-1-maximedevos@telenet.be
* gnu/packages/games.scm
(minetest)[arguments]<#:phases>{check}: Use 'tests?' instead
of ',(%current-target-system)'. Remove trailing #t.
---
gnu/packages/games.scm | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)

Toggle diff (23 lines)
diff --git a/gnu/packages/games.scm b/gnu/packages/games.scm
index 8c6b5523f1..3e7086b398 100644
--- a/gnu/packages/games.scm
+++ b/gnu/packages/games.scm
@@ -3590,13 +3590,12 @@ match, cannon keep, and grave-itation pit.")
                      (string-append (getcwd) "/games")) ; for check
              #t))
          (replace 'check
-           (lambda _
+           (lambda* (#:key tests? #:allow-other-keys)
              ;; Thanks to our substitutions, the tests should also run
              ;; when invoked on the target outside of `guix build'.
-             (unless ,(%current-target-system)
+             (when tests?
                (setenv "HOME" "/tmp")
-               (invoke "src/minetest" "--run-unittests"))
-             #t)))))
+               (invoke "src/minetest" "--run-unittests")))))))
     (native-search-paths
      (list (search-path-specification
             (variable "MINETEST_SUBGAME_PATH")
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 02/20] gnu: minetest: Search for mods in MINETEST_MOD_PATH.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-2-maximedevos@telenet.be
* gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch:
New file.
* gnu/packages/games.scm
(minetest)[source]{patches}: Add it.
(minetest)[native-search-paths]: Add "MINETEST_MOD_PATH".
* gnu/local.mk (dist_patch_DATA): Add the patch.
---
gnu/local.mk | 1 +
gnu/packages/games.scm | 7 +-
...vironment-variable-MINETEST_MOD_PATH.patch | 115 ++++++++++++++++++
3 files changed, 122 insertions(+), 1 deletion(-)
create mode 100644 gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch

Toggle diff (160 lines)
diff --git a/gnu/local.mk b/gnu/local.mk
index c80a9af78c..d96d4e3dbc 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -801,6 +801,7 @@ dist_patch_DATA =						\
   %D%/packages/patches/abseil-cpp-fix-gtest.patch		\
   %D%/packages/patches/abseil-cpp-fix-strerror_test.patch	\
   %D%/packages/patches/adb-add-libraries.patch			\
+  %D%/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch	\
   %D%/packages/patches/aegis-constness-error.patch         	\
   %D%/packages/patches/aegis-perl-tempdir1.patch           	\
   %D%/packages/patches/aegis-perl-tempdir2.patch           	\
diff --git a/gnu/packages/games.scm b/gnu/packages/games.scm
index 3e7086b398..2f3285c6ea 100644
--- a/gnu/packages/games.scm
+++ b/gnu/packages/games.scm
@@ -3553,6 +3553,7 @@ match, cannon keep, and grave-itation pit.")
                (base32
                 "062ilb7s377q3hwfhl8q06vvcw2raydz5ljzlzwy2dmyzmdcndb8"))
               (modules '((guix build utils)))
+              (patches (search-patches "Add-environment-variable-MINETEST_MOD_PATH.patch"))
               (snippet
                '(begin
                   ;; Delete bundled libraries.
@@ -3599,7 +3600,11 @@ match, cannon keep, and grave-itation pit.")
     (native-search-paths
      (list (search-path-specification
             (variable "MINETEST_SUBGAME_PATH")
-            (files '("share/minetest/games")))))
+            (files '("share/minetest/games")))
+           (search-path-specification
+            (variable "MINETEST_MOD_PATH")
+            (files '("share/minetest/mods"))
+            (separator #f))))
     (native-inputs
      `(("pkg-config" ,pkg-config)))
     (inputs
diff --git a/gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch b/gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch
new file mode 100644
index 0000000000..8478a7bf72
--- /dev/null
+++ b/gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch
@@ -0,0 +1,115 @@
+From 6eb753c5bf67764890856cf23a67c0bf65973c16 Mon Sep 17 00:00:00 2001
+From: Maxime Devos <maximedevos@telenet.be>
+Date: Thu, 29 Jul 2021 22:24:50 +0200
+Subject: [PATCH] Add environment variable MINETEST_MOD_PATH
+
+This adds an environment variable MINETEST_MOD_PATH.
+When it exists, Minetest will look there for mods
+in addition to ~/.minetest/mods/.
+
+This patch as-is is not yet ready for upstream, because:
+
+  * the GUI will only display mods in MINETEST_MOD_PATH
+    or mods in ~/.minetest/mods, it won't combine the two
+
+  * the GUI for installing mods from ContentDB is disabled
+    when MINETEST_MOD_PATH is set, because otherwise Minetest
+    would try to install mods in the store.
+
+  * MINETEST_MOD_PATH can only have a single component
+---
+ builtin/mainmenu/dlg_contentstore.lua |  7 +++++++
+ src/content/subgames.cpp              |  3 +++
+ src/script/lua_api/l_mainmenu.cpp     | 14 +++++++++++++-
+ src/script/lua_api/l_mainmenu.h       |  2 ++
+ 4 files changed, 25 insertions(+), 1 deletion(-)
+
+diff --git a/builtin/mainmenu/dlg_contentstore.lua b/builtin/mainmenu/dlg_contentstore.lua
+index 7096c9187..c4a2cbd18 100644
+--- a/builtin/mainmenu/dlg_contentstore.lua
++++ b/builtin/mainmenu/dlg_contentstore.lua
+@@ -22,6 +22,13 @@ if not core.get_http_api then
+ 	end
+ 	return
+ end
++if core.mod_path_set() then
++	function create_store_dlg()
++		return messagebox("store",
++				fgettext("Mods from ContentDB cannot be installed when mods from Guix are also installed"))
++	end
++	return
++end
+ 
+ -- Unordered preserves the original order of the ContentDB API,
+ -- before the package list is ordered based on installed state.
+diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp
+index e9dc609b0..1809f189e 100644
+--- a/src/content/subgames.cpp
++++ b/src/content/subgames.cpp
+@@ -110,6 +110,9 @@ SubgameSpec findSubgame(const std::string &id)
+ 	std::set<std::string> mods_paths;
+ 	if (!user_game)
+ 		mods_paths.insert(share + DIR_DELIM + "mods");
++	const char *env_mod_path = getenv("MINETEST_MOD_PATH");
++	if (env_mod_path)
++		mods_paths.insert(std::string(env_mod_path));
+ 	if (user != share || user_game)
+ 		mods_paths.insert(user + DIR_DELIM + "mods");
+ 
+diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp
+index ad00de1c4..737550c42 100644
+--- a/src/script/lua_api/l_mainmenu.cpp
++++ b/src/script/lua_api/l_mainmenu.cpp
+@@ -495,9 +495,19 @@ int ModApiMainMenu::l_get_user_path(lua_State *L)
+ /******************************************************************************/
+ int ModApiMainMenu::l_get_modpath(lua_State *L)
+ {
++	const char *c_modpath = getenv("MINETEST_MOD_PATH");
+ 	std::string modpath = fs::RemoveRelativePathComponents(
+ 		porting::path_user + DIR_DELIM + "mods" + DIR_DELIM);
+-	lua_pushstring(L, modpath.c_str());
++	if (c_modpath == NULL)
++		c_modpath = modpath.c_str();
++	lua_pushstring(L, c_modpath);
++	return 1;
++}
++
++/******************************************************************************/
++int ModApiMainMenu::l_mod_path_set(lua_State *L)
++{
++	lua_pushboolean(L, NULL != getenv("MINETEST_MOD_PATH"));
+ 	return 1;
+ }
+ 
+@@ -855,6 +865,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
+ 	API_FCT(get_mapgen_names);
+ 	API_FCT(get_user_path);
+ 	API_FCT(get_modpath);
++	API_FCT(mod_path_set);
+ 	API_FCT(get_clientmodpath);
+ 	API_FCT(get_gamepath);
+ 	API_FCT(get_texturepath);
+@@ -888,6 +899,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top)
+ 	API_FCT(get_mapgen_names);
+ 	API_FCT(get_user_path);
+ 	API_FCT(get_modpath);
++	API_FCT(mod_path_set);
+ 	API_FCT(get_clientmodpath);
+ 	API_FCT(get_gamepath);
+ 	API_FCT(get_texturepath);
+diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h
+index ec2d20da2..719c26077 100644
+--- a/src/script/lua_api/l_mainmenu.h
++++ b/src/script/lua_api/l_mainmenu.h
+@@ -112,6 +112,8 @@ class ModApiMainMenu: public ModApiBase
+ 
+ 	static int l_get_modpath(lua_State *L);
+ 
++	static int l_mod_path_set(lua_State *L);
++
+ 	static int l_get_clientmodpath(lua_State *L);
+ 
+ 	static int l_get_gamepath(lua_State *L);
+-- 
+2.32.0
+
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 03/20] gnu: minetest: New package module.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-3-maximedevos@telenet.be
Aside from the 'minetest-topic' procedure which will be used
for the 'home-page' field of some packages, this module is
currently empty. The 'contentdb' importer defined in the
following patches will be used to populate this module.

* gnu/packages/minetest.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
---
gnu/local.mk | 1 +
gnu/packages/minetest.scm | 26 ++++++++++++++++++++++++++
2 files changed, 27 insertions(+)
create mode 100644 gnu/packages/minetest.scm

Toggle diff (46 lines)
diff --git a/gnu/local.mk b/gnu/local.mk
index d96d4e3dbc..5de08b1b09 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -383,6 +383,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/packages/mercury.scm			\
   %D%/packages/mes.scm				\
   %D%/packages/messaging.scm			\
+  %D%/packages/minetest.scm			\
   %D%/packages/mingw.scm			\
   %D%/packages/microcom.scm			\
   %D%/packages/moe.scm				\
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
new file mode 100644
index 0000000000..f8aca3005c
--- /dev/null
+++ b/gnu/packages/minetest.scm
@@ -0,0 +1,26 @@
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+(define-module (gnu packages minetest)
+  #:use-module (guix packages)
+  #:use-module (guix git-download)
+  #:use-module (guix build-system minetest)
+  #:use-module ((guix licenses) #:prefix license:))
+
+(define-public (minetest-topic topic-id)
+  "Return an URL (as a string) pointing to the forum topic with
+numeric identifier TOPIC-ID on the official Minetest forums."
+  (string-append "https://forum.minetest.net/viewtopic.php?t="
+                 (number->string topic-id)))
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 04/20] build-system: Add 'minetest-mod-build-system'.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-4-maximedevos@telenet.be
* guix/build-system/minetest.scm: New module.
* Makefile.am (MODULES): Add it.
* doc/guix.texi (Build Systems): Document 'minetest-mod-build-system'.
---
Makefile.am | 1 +
doc/guix.texi | 7 +++++
guix/build-system/minetest.scm | 53 ++++++++++++++++++++++++++++++++++
3 files changed, 61 insertions(+)
create mode 100644 guix/build-system/minetest.scm

Toggle diff (91 lines)
diff --git a/Makefile.am b/Makefile.am
index d5ec909213..f6fae09579 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -141,6 +141,7 @@ MODULES =					\
   guix/build-system/go.scm			\
   guix/build-system/meson.scm			\
   guix/build-system/minify.scm			\
+  guix/build-system/minetest.scm		\
   guix/build-system/asdf.scm			\
   guix/build-system/copy.scm			\
   guix/build-system/glib-or-gtk.scm		\
diff --git a/doc/guix.texi b/doc/guix.texi
index b3c16e6507..43c248234d 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -7895,6 +7895,13 @@ declaration.  Its default value is @code{(default-maven-plugins)} which is
 also exported.
 @end defvr
 
+@defvr {Scheme Variable} minetest-mod-build-system
+This variable is exported by @code{(guix build-system minetest)}.  It
+implements a build procedure for @uref{https://www.minetest.net, Minetest}
+mods, which consists of copying lua code, images and other resources to
+the location Minetest searches for mods.
+@end defvr
+
 @defvr {Scheme Variable} minify-build-system
 This variable is exported by @code{(guix build-system minify)}.  It
 implements a minification procedure for simple JavaScript packages.
diff --git a/guix/build-system/minetest.scm b/guix/build-system/minetest.scm
new file mode 100644
index 0000000000..29866ced6d
--- /dev/null
+++ b/guix/build-system/minetest.scm
@@ -0,0 +1,53 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build-system minetest)
+  #:use-module (guix build-system copy)
+  #:use-module (guix build-system)
+  #:export (minetest-mod-build-system))
+
+;;
+;; Build procedure for minetest mods.  This is implemented as an extension
+;; of ‘copy-build-system’.
+;;
+;; Code:
+
+(define (guix-name->mod-name package-name)
+  ;; The "minetest-" prefix is useless.  Don't make it appear in the
+  ;; ‘select mods’ menu when "modpack.conf" or "mod.conf" do not have
+  ;; the "name" field set.
+  (if (string-prefix? "minetest-" package-name)
+      (substring package-name 9)
+      package-name))
+
+(define (lower-mod name . arguments)
+  (define lower (build-system-lower copy-build-system))
+  (apply lower
+    name
+    #:install-plan
+    `'(("." ,(string-append "share/minetest/mods/"
+                            (guix-name->mod-name name))))
+    arguments))
+
+(define minetest-mod-build-system
+  (build-system
+    (name 'minetest-mod)
+    (description "The build system for minetest mods")
+    (lower lower-mod)))
+
+;;; minetest.scm ends here
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 05/20] build-system: minetest: Don't retain references to "bash-minimal".
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-5-maximedevos@telenet.be
* guix/build-system/minetest.scm
(%standard-phases): New variable. Delete "patch-source-shebangs"
phase.
(lower-mod): Set #:phases to value of new variable.
---
guix/build-system/minetest.scm | 9 +++++++++
1 file changed, 9 insertions(+)

Toggle diff (29 lines)
diff --git a/guix/build-system/minetest.scm b/guix/build-system/minetest.scm
index 29866ced6d..993c5631eb 100644
--- a/guix/build-system/minetest.scm
+++ b/guix/build-system/minetest.scm
@@ -35,6 +35,14 @@
       (substring package-name 9)
       package-name))
 
+(define %standard-phases
+  ;; The source code sometimes contains shell scripts which are used for
+  ;; development but not at run time (e.g. listnodes.sh in
+  ;; minetest-homedecor-modpack).  Don't make them retain a reference
+  ;; to bash-minimal.
+  '(modify-phases (@ (guix build copy-build-system) %standard-phases)
+     (delete 'patch-source-shebangs)))
+
 (define (lower-mod name . arguments)
   (define lower (build-system-lower copy-build-system))
   (apply lower
@@ -42,6 +50,7 @@
     #:install-plan
     `'(("." ,(string-append "share/minetest/mods/"
                             (guix-name->mod-name name))))
+    #:phases %standard-phases
     arguments))
 
 (define minetest-mod-build-system
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 09/20] gnu: Add minetest-unifieddyes.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-9-maximedevos@telenet.be
* gnu/packages/minetest.scm (minetest-unifieddyes): New variable.
---
gnu/packages/minetest.scm | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)

Toggle diff (38 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index a97065ad4c..b845e5a2b3 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -83,3 +83,31 @@ with different rules and mechanics.")
     ;; correct the inconsistency:
     ;; <https://github.com/minetest-mods/mesecons/issues/575>.
     (license (list license:lgpl3+ license:cc-by-sa3.0)))))
+
+(define-public minetest-unifieddyes
+  (package
+    (name "minetest-unifieddyes")
+    ;; Upstream uses dates as version numbers.
+    (version "2021-04-20-1")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://gitlab.com/VanessaE/unifieddyes")
+             (commit "ff3b2d30fa0df5c7181fdd401b989de6271c3bb3")))
+       (sha256
+        (base32
+         "0rba9n192xcpmxwnq7ixb6mn32gkpic247j3w4mwinrqcyscacsv"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (propagated-inputs
+     `(("minetest-basic-materials" ,minetest-basic-materials)))
+    (home-page (minetest-topic 2178))
+    (synopsis
+     "Unified Dyes expands the standard dye set of Minetest to up to 256 colours")
+    (description "The purpose of this mod originally was to supply a complete
+set of colours for Minetest mod authors to use for colourised nodes or
+reference in recipes.  Since the advent of the default dyes mod in the standard
+Minetest game, this mod has become an extension of the default mod an a library
+for general colour handling.")
+    (license license:gpl2+)))
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 08/20] gnu: Add minetest-basic-materials.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-8-maximedevos@telenet.be
* gnu/packages/minetest.scm (minetest-basic-materials): New variable.
---
gnu/packages/minetest.scm | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)

Toggle diff (37 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 72e04c4b33..a97065ad4c 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -25,6 +25,30 @@ numeric identifier TOPIC-ID on the official Minetest forums."
   (string-append "https://forum.minetest.net/viewtopic.php?t="
                  (number->string topic-id)))
 
+(define-public minetest-basic-materials
+  (package
+    (name "minetest-basic-materials")
+    ;; Upstream uses dates as version numbers.
+    (version "2021-01-30")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://gitlab.com/VanessaE/basic_materials.git")
+             (commit "e72665b2ed98d7be115779a32d35e6d9ffa231bd")))
+       (sha256
+        (base32 "0v6l3lrjgshy4sccjhfhmfxc3gk0cdy73qb02i9wd2vw506v5asx"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (home-page (minetest-topic 21000))
+    (synopsis "Some \"basic\" materials and items for other Minetest mods to use")
+    (description
+     "The Minetest mod \"basic_materials\" provides a small selection of
+\"basic\" materials and items that other mods should use when possible -- things
+like steel bars and chains, wire, plastic strips and sheets, and more.")
+    (license
+     (list license:cc-by-sa4.0 license:lgpl3))))
+
 (define-public minetest-mesecons
   ;; The release on ContentDB does not have its own version number.
   (let ((commit "db5879706d04d3480bc4863ce0c03fa73e5f10c7")
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 07/20] gnu: Add minetest-mesecons.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-7-maximedevos@telenet.be
* gnu/packages/minetest.scm (minetest-mesecons): New variable.
---
gnu/packages/minetest.scm | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)

Toggle diff (45 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index f8aca3005c..72e04c4b33 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -24,3 +24,38 @@
 numeric identifier TOPIC-ID on the official Minetest forums."
   (string-append "https://forum.minetest.net/viewtopic.php?t="
                  (number->string topic-id)))
+
+(define-public minetest-mesecons
+  ;; The release on ContentDB does not have its own version number.
+  (let ((commit "db5879706d04d3480bc4863ce0c03fa73e5f10c7")
+        (revision "0"))
+  (package
+    (name "minetest-mesecons")
+    (version (git-version "1.2.1" revision commit))
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://github.com/minetest-mods/mesecons")
+             (commit commit)))
+       (sha256
+        (base32 "04m9s9l3frw1lgki41hgvjsw2zkrvfv0sy750b6j12arzb3lv645"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (home-page "https://mesecons.net")
+    (synopsis
+     "Digital circuitry for Minetest, including wires, buttons and lights")
+    (description
+     "Mesecons is a mod for Minetest implementing various items related
+to digital circuitry, such as wires, buttons, lights and programmable
+controllers.  Among other things, there are also pistons, solar panels,
+pressure plates and note blocks.
+
+Mesecons has a similar goal to Redstone in Minecraft, but works in its own way,
+with different rules and mechanics.")
+    ;; LGPL for code, CC-BY-SA for textures.
+    ;; The README.md and COPYING.txt disagree about the "+" in license:lgpl3+.
+    ;; For now, assume README.md is correct.  Upstream has been asked to
+    ;; correct the inconsistency:
+    ;; <https://github.com/minetest-mods/mesecons/issues/575>.
+    (license (list license:lgpl3+ license:cc-by-sa3.0)))))
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 10/20] gnu: Add minetest-pipeworks.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-10-maximedevos@telenet.be
* gnu/packages/minetest.scm (minetest-pipeworks): New variable.
---
gnu/packages/minetest.scm | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)

Toggle diff (43 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index b845e5a2b3..a281eddea0 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -84,6 +84,36 @@ with different rules and mechanics.")
     ;; <https://github.com/minetest-mods/mesecons/issues/575>.
     (license (list license:lgpl3+ license:cc-by-sa3.0)))))
 
+(define-public minetest-pipeworks
+  (package
+    (name "minetest-pipeworks")
+    ;; Upstream uses dates as version numbers.
+    (version "2021-04-14-1")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://gitlab.com/VanessaE/pipeworks")
+             (commit "db6d1bd9c109e1e543b97cc3fa8a11400da23bcd")))
+       (sha256
+        (base32 "1flhcnf17dn1v86kcg47a1n4cb0lybd11ncxrkxn3wmf10ibsrm0"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (propagated-inputs
+     `(("minetest-basic-materials" ,minetest-basic-materials)))
+    (home-page (minetest-topic 2155))
+    (synopsis "Pipes, item-transport tubes and related devices for Minetest")
+    (description
+     "Pipeworks is a mod for Minetest implementing 3D pipes and tubes for
+transporting liquids and items and some related devices.  Pipes and tubes can
+go horizontally or vertically.  Item tubes can also be used for sorting items
+and extracting items from chests or putting items in chests.  Autocrafters can
+automatically follow craft recipes to make new items and can be fed by item
+tubes.  Deployers can place items in the world as a player would.  Node
+breakers simulate a player punching a node.")
+    ;; CC-BY-SA for textures, LGPL for code
+    (license (list license:cc-by-sa4.0 license:lgpl3))))
+
 (define-public minetest-unifieddyes
   (package
     (name "minetest-unifieddyes")
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 13/20] gnu: Add minetest-technic.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-13-maximedevos@telenet.be
* gnu/packages/minetest.scm (minetest-technic): New variable.
---
gnu/packages/minetest.scm | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)

Toggle diff (47 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 12e15ee946..c901c5fcc7 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -169,6 +169,40 @@ breakers simulate a player punching a node.")
     ;; CC-BY-SA for textures, LGPL for code
     (license (list license:cc-by-sa4.0 license:lgpl3))))
 
+(define-public minetest-technic
+  (package
+    (name "minetest-technic")
+    ;; Upstream doesn't keep version numbers, so use the release
+    ;; date on ContentDB instead.
+    (version "2021-04-15")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://github.com/minetest-mods/technic")
+             (commit "1c219487d3f4dd03c01ff9aa1f298c7c18c7e189")))
+       (sha256
+        (base32 "1k9hdgzp7jnhsk6rgrlrv1lr5xrmh8ln4wv6r25v6f0fwbyj57sf"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (propagated-inputs
+     `(("minetest-pipeworks" ,minetest-pipeworks)
+       ("minetest-basic-materials" ,minetest-basic-materials)))
+    (home-page (minetest-topic 2538))
+    (synopsis "Machinery and automation for Minetest")
+    (description
+     "This Minetest mod adds machinery and automation to Minetest.
+It adds various ores that can be processed for constructing various
+machinery, such as power generators, force field emitters, quarries
+and a workshop for repairing tools.  Most machines are electrically
+powered.")
+    ;; CC BY-SA 3.0: some texture
+    ;; WTFPL: some textures
+    ;; CC BY-SA3.0: some textures
+    ;; CC BY-SA4.0: some sounds
+    (license (list license:lgpl2.1+ license:cc-by-sa3.0 license:cc-by-sa4.0
+                   license:wtfpl2))))
+
 (define-public minetest-unifieddyes
   (package
     (name "minetest-unifieddyes")
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 11/20] gnu: Add minetest-coloredwood.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-11-maximedevos@telenet.be
* gnu/packages/minetest.scm (minetest-coloredwood): New variable.
---
gnu/packages/minetest.scm | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)

Toggle diff (40 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index a281eddea0..fd2e78ade8 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -49,6 +49,33 @@ like steel bars and chains, wire, plastic strips and sheets, and more.")
     (license
      (list license:cc-by-sa4.0 license:lgpl3))))
 
+(define-public minetest-coloredwood
+  (package
+    (name "minetest-coloredwood")
+    ;; Upstream uses dates as version numbers.
+    (version "2021-04-14-1")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://gitlab.com/VanessaE/coloredwood")
+             (commit "be4df6fc889419155bed8638bbb752493e78cbd5")))
+       (sha256
+        (base32 "1swirfk6b4xfbiwv8adyw5yl2lyfpp8ymfipzq9ivyvmif8nl3ki"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (propagated-inputs
+     `(("minetest-unifieddyes" ,minetest-unifieddyes)))
+    (home-page (minetest-topic 2411))
+    (synopsis "Painted wood in Minetest")
+    (description
+     "This Minetest mod provides hundreds of colours of wood and fences to
+Minetest, using Unified Dyes.  If the \"moreblocks\" mod is active,
+coloured and cut wood shapes are provided as well.")
+    (license
+     ;; LGPL for code, CC-BY-SA for textures
+     (list license:cc-by-sa4.0 license:lgpl3))))
+
 (define-public minetest-mesecons
   ;; The release on ContentDB does not have its own version number.
   (let ((commit "db5879706d04d3480bc4863ce0c03fa73e5f10c7")
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 14/20] gnu: Add minetest-throwing.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-14-maximedevos@telenet.be
* gnu/packages/minetest.scm (minetest-throwing): New variable.
---
gnu/packages/minetest.scm | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)

Toggle diff (35 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index c901c5fcc7..25038a5f6b 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -203,6 +203,28 @@ powered.")
     (license (list license:lgpl2.1+ license:cc-by-sa3.0 license:cc-by-sa4.0
                    license:wtfpl2))))
 
+(define-public minetest-throwing
+  (package
+    (name "minetest-throwing")
+    (version "2020-08-14")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://github.com/minetest-mods/throwing")
+             (commit "31f0cf5f868673dc82f24ddc432b45c9cd282d27")))
+       (sha256
+        (base32 "1s5kkr6rxxv2dhbbjzv62gw1s617hnpjavw1v9fv11v3mgigdfjb"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (home-page (minetest-topic 16365))
+    (synopsis "API for throwing things in Minetest")
+    (description
+     "This Minetest mod provides an API for registering throwable things and
+throwing things like arrows.  However, this mod does not provide an actual
+arrow and bow, but @code{minetest-throwing-arrows} does.")
+    (license license:mpl2.0)))
+
 (define-public minetest-unifieddyes
   (package
     (name "minetest-unifieddyes")
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 16/20] gnu: Add minetest-unified-inventory.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-16-maximedevos@telenet.be
* gnu/packages/minetest.scm
(minetest-unified-inventory): New variable.
---
gnu/packages/minetest.scm | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)

Toggle diff (44 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 6b792bf071..2c028e3e87 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -281,3 +281,37 @@ reference in recipes.  Since the advent of the default dyes mod in the standard
 Minetest game, this mod has become an extension of the default mod an a library
 for general colour handling.")
     (license license:gpl2+)))
+
+(define-public minetest-unified-inventory
+  (package
+    (name "minetest-unified-inventory")
+    ;; Upstream doesn't keep version numbers, so use the release title
+    ;; on ContentDB instead.
+    (version "2021-03-25-1")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://github.com/minetest-mods/unified_inventory")
+             (commit "c044f5e3b08f0c68ab028d757b2fa63d9a1b0370")))
+       (sha256
+        (base32 "198g945gzbfl0kps46gwjw0c601l3b3wvn4c7dw8manskri1jr4g"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (home-page (minetest-topic 12767))
+    (synopsis "Replace the default inventory in Minetest and add a crafting guide")
+    (description
+     "The Unified Inventory Minetest mod relaces the default survival an
+creative inventory.  It includes a node, item and tool browser, a crafting
+guide, a trash and refill slot for creative mode, bags and waypoints for keeping
+track of important locations.")
+    ;; CC-BY: some textures and icons
+    ;; CC-BY-SA: some textures and icons
+    ;; LGLPL2.1+: code and some textures
+    ;; GPL2+: some textures
+    ;; GPL3: bags.lua
+    ;; GFDL: some icons
+    ;; public domain, CC0: some icons
+    (license (list license:gpl3 license:gpl2+ license:lgpl2.1+ license:cc-by3.0
+                   license:cc-by4.0 license:cc-by-sa3.0 license:public-domain
+                   license:cc0 license:fdl1.2+))))
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 12/20] gnu: Add minetest-ethereal.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-12-maximedevos@telenet.be
* gnu/packages/minetest.scm (minetest-ethereal): New variable.
---
gnu/packages/minetest.scm | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)

Toggle diff (51 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index fd2e78ade8..12e15ee946 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -76,6 +76,34 @@ coloured and cut wood shapes are provided as well.")
      ;; LGPL for code, CC-BY-SA for textures
      (list license:cc-by-sa4.0 license:lgpl3))))
 
+(define-public minetest-ethereal
+  ;; ContentDB release 2021-07-28 is slightly ahead of the
+  ;; initial version 1.29 -- i.e., some released changes have been
+  ;; made to version 1.29 without a corresponding version bump.
+  (let ((commit "7670c1da9274901f57f6682384af2b3bae005a86")
+        (revision "0"))
+    (package
+      (name "minetest-ethereal")
+      (version (git-version "1.29" revision commit))
+      (source
+       (origin
+         (method git-fetch)
+         (uri (git-reference
+               (url "https://notabug.org/TenPlus1/ethereal")
+               (commit commit)))
+         (sha256
+          (base32 "1hal8bq4fydsip7s8rqz4vlaaqy9rhzxmryd0j2qnqm9286yjgkk"))
+         (file-name (git-file-name name version))))
+      (build-system minetest-mod-build-system)
+      (home-page (minetest-topic 14638))
+      (synopsis "The Ethereal mod adds many new biomes to Minetest")
+      (description
+       "The Ethereal Minetest mod uses the v7 map generator to add many new
+biomes to the world.  It adds new trees, plants, food items, tweaks and some
+special items, intending to make an interesting adventure.")
+      ;; CC0: some textures
+      (license (list license:cc0 license:expat)))))
+
 (define-public minetest-mesecons
   ;; The release on ContentDB does not have its own version number.
   (let ((commit "db5879706d04d3480bc4863ce0c03fa73e5f10c7")
@@ -153,8 +181,7 @@ breakers simulate a player punching a node.")
              (url "https://gitlab.com/VanessaE/unifieddyes")
              (commit "ff3b2d30fa0df5c7181fdd401b989de6271c3bb3")))
        (sha256
-        (base32
-         "0rba9n192xcpmxwnq7ixb6mn32gkpic247j3w4mwinrqcyscacsv"))
+        (base32 "0rba9n192xcpmxwnq7ixb6mn32gkpic247j3w4mwinrqcyscacsv"))
        (file-name (git-file-name name version))))
     (build-system minetest-mod-build-system)
     (propagated-inputs
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 17/20] gnu: Add minetest-worldedit.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-17-maximedevos@telenet.be
* gnu/packages/minetest.scm (minetest-worldedit): New variable.
---
gnu/packages/minetest.scm | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)

Toggle diff (35 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 2c028e3e87..0e16743823 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -255,6 +255,28 @@ arrow and bow, but @code{minetest-throwing-arrows} does.")
 replacement for the throwing mod by PilzAdam that uses the throwing API.")
       (license license:mpl2.0))))
 
+(define-public minetest-worldedit
+  (package
+    (name "minetest-worldedit")
+    (version "1.3")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://github.com/Uberi/Minetest-WorldEdit")
+             (commit "2f26fb76459c587868199160b9d7b5d6d7852e50")))
+       (sha256
+        (base32 "0lsvihkixi2na1b0vmml9vwgs0g24hqqshl73ffhkzh6jsq4cagq"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (home-page (minetest-topic 572))
+    (synopsis "In-game world editor for Minetest")
+    (description
+     "WorldEdit is a mod for Minetest.  It allows for creating various
+geometric shapes and copying regions.  It can also export and import regions
+to and from the file system.")
+    (license license:agpl3)))
+
 (define-public minetest-unifieddyes
   (package
     (name "minetest-unifieddyes")
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 18/20] gnu: Add minetest-mobs.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-18-maximedevos@telenet.be
* gnu/packages/minetest.scm (minetest-mobs): New variable.
---
gnu/packages/minetest.scm | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)

Toggle diff (41 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 0e16743823..4910fa72bb 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -139,6 +139,34 @@ with different rules and mechanics.")
     ;; <https://github.com/minetest-mods/mesecons/issues/575>.
     (license (list license:lgpl3+ license:cc-by-sa3.0)))))
 
+(define-public minetest-mobs
+  (package
+    (name "minetest-mobs")
+    ;; Upstream does not tag release, so use the ContentDB release
+    ;; title instead.
+    (version "2021-07-22")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://notabug.org/TenPlus1/mobs_redo")
+             (commit "9f46182bb4b1a390f9a140bc2b443f3cda702332")))
+       (sha256
+        (base32 "026kqjis4lipgskjivb3jh9ris3iz80vy2q1jvgxhxmfghjjzp4j"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (home-page (minetest-topic 9917))
+    (synopsis "Mob library for Minetest mods, for animals, monsters etc.")
+    (description
+     "This Minetest mod provides an API for adding mods (moving entities
+like animals and monsters), but does not include any mobs itself.  To actually
+add some mobs, a mod like e.g. @code{mobs_animal} provided by the
+@code{minetest-mobs-animal} package needs to be enabled.")
+    ;; CC0: mob_swing.ogg
+    ;; CC-BY 3.0: mob_spell.ogg
+    ;; Expat: everything else
+    (license (list license:expat license:cc0 license:cc-by3.0))))
+
 (define-public minetest-pipeworks
   (package
     (name "minetest-pipeworks")
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 19/20] gnu: Add minetest-mobs-animal.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-19-maximedevos@telenet.be
* gnu/packages/minetest.scm (minetest-mobs-animal): New variable.
---
gnu/packages/minetest.scm | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)

Toggle diff (39 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 4910fa72bb..fcc3648795 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -167,6 +167,32 @@ add some mobs, a mod like e.g. @code{mobs_animal} provided by the
     ;; Expat: everything else
     (license (list license:expat license:cc0 license:cc-by3.0))))
 
+(define-public minetest-mobs-animal
+  (package
+    (name "minetest-mobs-animal")
+    ;; Upstream does not use version numbers, so use the release title
+    ;; from ContentDB instead;
+    (version "2021-07-24")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://notabug.org/TenPlus1/mobs_animal")
+             (commit "c2fa3e300c79c7dd80b6fe91a8b5082bb6b3d934")))
+       (sha256
+        (base32 "1j719f079ia9vjxrmjrcj8s6jvaz5kgs1r4dh66z8ql6s70kx7vh"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (propagated-inputs
+     `(("minetest-mobs" ,minetest-mobs)))
+    (home-page "https://notabug.org/TenPlus1/mobs_animal")
+    (synopsis "Add animals to Minetest")
+    (description
+     "This Minetest mod adds various animals to Minetest, such as bees,
+bunnies, chickens, cows, kittens, rats, sheep, warthogs, penguins and pandas.")
+    ;; CC0: some textures and sounds
+    (license (list license:cc0 license:expat))))
+
 (define-public minetest-pipeworks
   (package
     (name "minetest-pipeworks")
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 20/20] gnu: Add minetest-homedecor-modpack.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-20-maximedevos@telenet.be
* gnu/packages/minetest.scm
(minetest-homedecor-modpack): New variable.
---
gnu/packages/minetest.scm | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)

Toggle diff (43 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index fcc3648795..7c483acd0c 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -104,6 +104,36 @@ special items, intending to make an interesting adventure.")
       ;; CC0: some textures
       (license (list license:cc0 license:expat)))))
 
+(define-public minetest-homedecor-modpack
+  (package
+    (name "minetest-homedecor-modpack")
+    ;; Upstream doesn't tag releases, so use the release title from
+    ;; ContentDB as version.
+    (version "2021-03-27-1")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://gitlab.com/VanessaE/homedecor_modpack")
+             (commit "9ffe2b7d691133e1a067546574fbe7364fd02f32")))
+       (sha256
+        (base32 "1lfajqvc2adf9hqskghky4arccqzpjw4i9a01hv4qcckvivm04ag"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (propagated-inputs
+     `(("minetest-basic-materials" ,minetest-basic-materials)
+       ("minetest-unifieddyes" ,minetest-unifieddyes)))
+    (home-page (minetest-topic 2041))
+    (synopsis "Home decor mod for Minetest")
+    (description
+     ;; TRANSLATORS: ‘homedecor’ is the name is the name of a Minetest mod
+     ;; and should not be translated.
+     "The homedecor Minetest mod provides a large seleection of items that
+might be found inside and around homes, such as sofas, chairs, tables, fences
+and a variety of other stuff.")
+    (license
+     (list license:cc-by-sa4.0 license:lgpl3))))
+
 (define-public minetest-mesecons
   ;; The release on ContentDB does not have its own version number.
   (let ((commit "db5879706d04d3480bc4863ce0c03fa73e5f10c7")
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 06/20] guix: Add ContentDB importer.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-6-maximedevos@telenet.be
* guix/import/contentdb.scm: New file.
* guix/scripts/import/contentdb.scm: New file.
* tests/contentdb.scm: New file.
* Makefile.am (MODULES, SCM_TESTS): Register them.
* po/guix/POTFILES.in: Likewise.
* doc/guix.texi (Invoking guix import): Document it.
---
Makefile.am | 3 +
doc/guix.texi | 25 +++
guix/import/contentdb.scm | 310 ++++++++++++++++++++++++++++++
guix/scripts/import.scm | 3 +-
guix/scripts/import/contentdb.scm | 106 ++++++++++
po/guix/POTFILES.in | 1 +
tests/contentdb.scm | 227 ++++++++++++++++++++++
7 files changed, 674 insertions(+), 1 deletion(-)
create mode 100644 guix/import/contentdb.scm
create mode 100644 guix/scripts/import/contentdb.scm
create mode 100644 tests/contentdb.scm

Toggle diff (753 lines)
diff --git a/Makefile.am b/Makefile.am
index f6fae09579..b9265c154d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -261,6 +261,7 @@ MODULES =					\
   guix/import/json.scm				\
   guix/import/kde.scm				\
   guix/import/launchpad.scm   			\
+  guix/import/contentdb.scm   			\
   guix/import/opam.scm				\
   guix/import/print.scm				\
   guix/import/pypi.scm				\
@@ -303,6 +304,7 @@ MODULES =					\
   guix/scripts/import/go.scm			\
   guix/scripts/import/hackage.scm		\
   guix/scripts/import/json.scm  		\
+  guix/scripts/import/contentdb.scm  		\
   guix/scripts/import/opam.scm			\
   guix/scripts/import/pypi.scm			\
   guix/scripts/import/stackage.scm		\
@@ -445,6 +447,7 @@ SCM_TESTS =					\
   tests/channels.scm				\
   tests/combinators.scm			\
   tests/containers.scm				\
+  tests/contentdb.scm				\
   tests/cpan.scm				\
   tests/cpio.scm				\
   tests/cran.scm				\
diff --git a/doc/guix.texi b/doc/guix.texi
index 43c248234d..d06c9b73c5 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -11313,6 +11313,31 @@ and generate package expressions for all those packages that are not yet
 in Guix.
 @end table
 
+@item contentdb
+@cindex ContentDB
+Import metadata from @uref{https://content.minetest.net, ContentDB}.
+Information is taken from the JSON-formatted metadata provided through
+@uref{https://content.minetest.net/help/api/, ContentDB's API} and
+includes most relevant information, including dependencies.  There are
+some caveats, however.  The license information on ContentDB does not
+distinguish between GPLvN-only and GPLvN-or-later.  The commit id is
+sometimes missing.  The descriptions are in the Markdown format, but
+Guix uses Texinfo instead.  Texture packs and subgames are unsupported.
+
+The command below imports metadata for the Mesecons mod by Jeija:
+
+@example
+guix import contentdb Jeija mesecons
+@end example
+
+@table @code
+@item --recursive
+@itemx -r
+Traverse the dependency graph of the given upstream package recursively
+and generate package expressions for all those packages that are not yet
+in Guix.
+@end table
+
 @item cpan
 @cindex CPAN
 Import metadata from @uref{https://www.metacpan.org/, MetaCPAN}.
diff --git a/guix/import/contentdb.scm b/guix/import/contentdb.scm
new file mode 100644
index 0000000000..1a36a09c92
--- /dev/null
+++ b/guix/import/contentdb.scm
@@ -0,0 +1,310 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet;be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix import contentdb)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 receive)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-2)
+  #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-26)
+  #:use-module (guix utils)
+  #:use-module (guix memoization)
+  #:use-module (guix serialization)
+  #:use-module (guix import utils)
+  #:use-module (guix import json)
+  #:use-module ((gcrypt hash) #:select (open-sha256-port port-sha256))
+  #:use-module (json)
+  #:use-module (guix base32)
+  #:use-module (guix git)
+  #:use-module (guix store)
+  #:use-module ((guix licenses) #:prefix license:)
+  #:export (%contentdb-api
+            contentdb->guix-package
+            contentdb-recursive-import))
+
+;; The ContentDB API is documented at
+;; <https://content.minetest.net>.
+
+(define %contentdb-api
+  (make-parameter "https://content.minetest.net/api/"))
+
+(define (string-or-false x)
+  (and (string? x) x))
+
+(define (natural-or-false x)
+  (and (exact-integer? x) (>= x 0) x))
+
+;; Descriptions on ContentDB use carriage returns, but Guix doesn't.
+(define (delete-cr text)
+  (string-delete #\cr text))
+
+;; Minetest package.
+;;
+;; API endpoint: /packages/AUTHOR/NAME/
+(define-json-mapping <package> make-package package?
+  json->package
+  (author            package-author) ; string
+  (creation-date     package-creation-date ; string
+                     "created_at")
+  (downloads         package-downloads) ; integer
+  (forums            package-forums "forums" natural-or-false) ; natural | #f
+  (issue-tracker     package-issue-tracker "issue_tracker") ; string
+  (license           package-license) ; string
+  (long-description  package-long-description "long_description") ; string
+  (maintainers       package-maintainers ; list of strings
+                     "maintainers" vector->list)
+  (media-license     package-media-license "media_license") ; string
+  (name              package-name) ; string
+  (provides          package-provides ; list of strings
+                     "provides" vector->list)
+  (release           package-release) ; integer
+  (repository        package-repository "repo" string-or-false) ; string | #f
+  (score             package-score) ; flonum
+  (screenshots       package-screenshots "screenshots" vector->list) ; list of strings
+  (short-description package-short-description "short_description") ; string
+  (state             package-state) ; string
+  (tags              package-tags "tags" vector->list) ; list of strings
+  (thumbnail         package-thumbnail) ; string
+  (title             package-title) ; string
+  (type              package-type) ; string
+  (url               package-url) ; string
+  (website           package-website "website" string-or-false)) ; string | #f
+
+(define-json-mapping <release> make-release release?
+  json->release
+  (commit               release-commit "commit" string-or-false) ; string | #f
+  (downloads            release-downloads) ; integer
+  (id                   release-id) ; integer
+  (max-minetest-version release-max-minetest-version) ; string | #f
+  (min-minetest-version release-min-minetest-version) ; string | #f
+  (release-date         release-data) ; string
+  (title                release-title) ; string
+  (url                  release-url)) ; string
+
+(define-json-mapping <dependency> make-dependency dependency?
+  json->dependency
+  (optional? dependency-optional? "is_optional") ; #t | #f
+  (name dependency-name) ; string
+  (packages dependency-packages "packages" vector->list)) ; list of strings
+
+(define (contentdb-fetch author name)
+  "Return a <package> record for package NAME by AUTHOR, or #f on failure."
+  (and=> (json-fetch
+          (string-append (%contentdb-api) "packages/" author "/" name "/"))
+         json->package))
+
+(define (contentdb-fetch-releases author name)
+  "Return a list of <release> records for package NAME by AUTHOR, or #f
+on failure."
+  (and=> (json-fetch (string-append (%contentdb-api) "packages/" author "/" name
+                                    "/releases/"))
+         (lambda (json)
+           (map json->release (vector->list json)))))
+
+(define (latest-release author name)
+  "Return the latest source release for package NAME by AUTHOR,
+or #f if this package does not exist."
+  (and=> (contentdb-fetch-releases author name)
+         car))
+
+(define (contentdb-fetch-dependencies author name)
+  "Return an alist of lists of <dependency> records for package NAME by AUTHOR
+and possibly some other packages as well, or #f on failure."
+  (define url (string-append (%contentdb-api) "packages/" author "/" name
+                             "/dependencies/"))
+  (and=> (json-fetch url)
+         (lambda (json)
+           (map (match-lambda
+                  ((key . value)
+                   (cons key (map json->dependency (vector->list value)))))
+                json))))
+
+(define (contentdb->package-name name)
+  "Given the NAME of a package on ContentDB, return a Guix-compliant name for the
+package."
+  ;; The author is not included, as the names of popular mods
+  ;; tend to be unique.
+  (string-append "minetest-" (snake-case name)))
+
+;; XXX copied from (guix import elpa)
+(define* (download-git-repository url ref)
+  "Fetch the given REF from the Git repository at URL."
+  (with-store store
+    (latest-repository-commit store url #:ref ref)))
+
+;; XXX adapted from (guix scripts hash)
+(define (file-hash file select? recursive?)
+  ;; Compute the hash of FILE.
+  (if recursive?
+      (let-values (((port get-hash) (open-sha256-port)))
+        (write-file file port #:select? select?)
+        (force-output port)
+        (get-hash))
+      (call-with-input-file file port-sha256)))
+;; XXX likewise.
+(define (vcs-file? file stat)
+  (case (stat:type stat)
+    ((directory)
+     (member (basename file) '(".bzr" ".git" ".hg" ".svn" "CVS")))
+    ((regular)
+     ;; Git sub-modules have a '.git' file that is a regular text file.
+     (string=? (basename file) ".git"))
+    (else
+     #f)))
+
+(define (make-minetest-sexp name version repository commit
+                            inputs home-page synopsis
+                            description media-license license)
+  "Return a S-expression for the minetest package with the given NAME,
+VERSION, REPOSITORY, COMMIT, INPUTS, HOME-PAGE, SYNOPSIS, DESCRIPTION,
+MEDIA-LICENSE and LICENSE."
+  `(package
+     (name ,(contentdb->package-name name))
+     (version ,version)
+     (source
+       (origin
+         (method git-fetch)
+         (uri (git-reference
+                (url ,repository)
+                (commit ,commit)))
+         (sha256
+          (base32
+           ;; The commit id is not always available.
+           ,(and commit
+                 (bytevector->nix-base32-string
+                  (file-hash
+                   (download-git-repository repository `(commit . ,commit))
+                   (negate vcs-file?) #t)))))
+         (file-name (git-file-name name version))))
+     (build-system minetest-mod-build-system)
+     ,@(maybe-propagated-inputs
+        (map (compose contentdb->package-name cdr) inputs))
+     (home-page ,home-page)
+     (synopsis ,(delete-cr synopsis))
+     (description ,(delete-cr description))
+     (license ,(if (eq? media-license license)
+                   (license->symbol license)
+                   `(list ,(license->symbol media-license)
+                          ,(license->symbol license))))))
+
+(define (package-home-page package)
+  "Guess the home page of the ContentDB package PACKAGE.
+
+In order of preference, try the 'website', the forum topic on the
+official Minetest forum and the Git repository (if any)."
+  (define (topic->url-sexp topic)
+    ;; 'minetest-topic' is a procedure defined in (gnu packages minetest)
+    `(minetest-topic ,topic))
+  (or (package-website package)
+      (and=> (package-forums package) topic->url-sexp)
+      (package-repository package)))
+
+(define (important-dependencies dependencies author name)
+  (define dependency-list
+    (assoc-ref dependencies (string-append author "/" name)))
+  (filter-map
+   (lambda (dependency)
+     (and (not (dependency-optional? dependency))
+          ;; "default" must be provided by the 'subgame' in use
+          ;; and does not refer to a specific minetest mod.
+          ;; "doors", "bucket" ... are provided by the default minetest
+          ;; subgame.
+          (not (member (dependency-name dependency)
+                       '("default" "doors" "beds" "bucket" "doors" "farming"
+                         "flowers" "stairs" "xpanes")))
+          ;; Dependencies often have only one implementation.
+          (let* ((/name (string-append "/" (dependency-name dependency)))
+                 (likewise-named-implementations
+                  (filter (cut string-suffix? /name <>)
+                          (dependency-packages dependency)))
+                 (implementation
+                  (and (not (null? likewise-named-implementations))
+                       (first likewise-named-implementations))))
+            (and implementation
+                 (apply cons (string-split implementation #\/))))))
+   dependency-list))
+
+(define* (%contentdb->guix-package author name)
+  "Fetch the metadata for NAME by AUTHOR from https://content.minetest.net, and
+return the 'package' S-expression corresponding to that package, or #f on failure.
+On success, also return the upstream dependencies as a list of
+(AUTHOR . NAME) pairs."
+  (and-let* ((package (contentdb-fetch author name))
+             (dependencies (contentdb-fetch-dependencies author name))
+             (release (latest-release author name)))
+    (let ((important-upstream-dependencies
+           (important-dependencies dependencies author name)))
+      (values (make-minetest-sexp name
+                                  (release-title release) ; version
+                                  (package-repository package)
+                                  (release-commit release)
+                                  important-upstream-dependencies
+                                  (package-home-page package)
+                                  (package-short-description package)
+                                  (package-long-description package)
+                                  (string->license
+                                   (package-media-license package))
+                                  (string->license
+                                   (package-license package)))
+              important-upstream-dependencies))))
+
+(define contentdb->guix-package
+  (memoize %contentdb->guix-package))
+
+(define (contentdb-recursive-import author name)
+  ;; recursive-import expects upstream package names to be strings,
+  ;; so do some conversions.
+  (define (split-author/name author/name)
+    (string-split author/name #\/))
+  (define (author+name->author/name author+name)
+    (string-append (car author+name) "/" (cdr author+name)))
+  (define* (contentdb->guix-package* author/name #:key repo version)
+    (receive (package . maybe-dependencies)
+        (apply contentdb->guix-package (split-author/name author/name))
+      (and package
+           (receive (dependencies)
+               (apply values maybe-dependencies)
+             (values package
+                     (map author+name->author/name dependencies))))))
+  (recursive-import (author+name->author/name (cons author name))
+                    #:repo->guix-package contentdb->guix-package*
+                    #:guix-name
+                    (lambda (author/name)
+                      (contentdb->package-name
+                       (second (split-author/name author/name))))))
+
+;; A list of license names is available at
+;; <https://content.minetest.net/api/licenses/>.
+(define (string->license str)
+  "Convert the string STR into a license object."
+  (match str
+    ("GPLv3"        license:gpl3)
+    ("GPLv2"        license:gpl2)
+    ("ISC"          license:isc)
+    ;; "MIT" means the Expat license on ContentDB,
+    ;; see <https://github.com/minetest/contentdb/issues/326#issuecomment-890143784>.
+    ("MIT"          license:expat)
+    ("CC BY-SA 3.0" license:cc-by-sa3.0)
+    ("CC BY-SA 4.0" license:cc-by-sa4.0)
+    ("LGPLv2.1"     license:lgpl2.1)
+    ("LGPLv3"       license:lgpl3)
+    ("MPL 2.0"      license:mpl2.0)
+    ("ZLib"         license:zlib)
+    ("Unlicense"    license:unlicense)
+    (_ #f)))
diff --git a/guix/scripts/import.scm b/guix/scripts/import.scm
index f53d1ac1f4..015677e719 100644
--- a/guix/scripts/import.scm
+++ b/guix/scripts/import.scm
@@ -77,7 +77,8 @@ rather than \\n."
 ;;;
 
 (define importers '("gnu" "pypi" "cpan" "hackage" "stackage" "egg" "elpa"
-                    "gem" "go" "cran" "crate" "texlive" "json" "opam"))
+                    "gem" "go" "cran" "crate" "texlive" "json" "opam"
+                    "contentdb"))
 
 (define (resolve-importer name)
   (let ((module (resolve-interface
diff --git a/guix/scripts/import/contentdb.scm b/guix/scripts/import/contentdb.scm
new file mode 100644
index 0000000000..4170fff950
--- /dev/null
+++ b/guix/scripts/import/contentdb.scm
@@ -0,0 +1,106 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2014 David Thompson <davet@gnu.org>
+;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix scripts import contentdb)
+  #:use-module (guix ui)
+  #:use-module (guix utils)
+  #:use-module (guix scripts)
+  #:use-module (guix import contentdb)
+  #:use-module (guix import utils)
+  #:use-module (guix scripts import)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-37)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 format)
+  #:export (guix-import-contentdb))
+
+
+;;;
+;;; Command-line options.
+;;;
+
+(define %default-options
+  '())
+
+(define (show-help)
+  (display (G_ "Usage: guix import contentdb AUTHOR NAME
+Import and convert the Minetest mod NAME by AUTHOR from ContentDB.\n"))
+  (display (G_ "
+  -h, --help             display this help and exit"))
+  (display (G_ "
+  -r, --recursive        import packages recursively"))
+  (display (G_ "
+  -V, --version          display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define %options
+  ;; Specification of the command-line options.
+  (cons* (option '(#\h "help") #f #f
+                 (lambda args
+                   (show-help)
+                   (exit 0)))
+         (option '(#\V "version") #f #f
+                 (lambda args
+                   (show-version-and-exit "guix import contentdb")))
+         (option '(#\r "recursive") #f #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'recursive #t result)))
+         %standard-import-options))
+
+
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-import-contentdb . args)
+  (define (parse-options)
+    ;; Return the alist of option values.
+    (args-fold* args %options
+                (lambda (opt name arg result)
+                  (leave (G_ "~A: unrecognized option~%") name))
+                (lambda (arg result)
+                  (alist-cons 'argument arg result))
+                %default-options))
+
+  (let* ((opts (parse-options))
+         (args (filter-map (match-lambda
+                            (('argument . value)
+                             value)
+                            (_ #f))
+                           (reverse opts))))
+    (match args
+      ((author name)
+       (with-error-handling
+         (if (assoc-ref opts 'recursive)
+             ;; Recursive import
+             (filter-map package->definition
+                         (contentdb-recursive-import author name))
+             ;; Single import
+             (let ((sexp (contentdb->guix-package author name)))
+               (unless sexp
+                 (leave (G_ "failed to download meta-data for package '~a' by '~a'~%")
+                        name author))
+               sexp))))
+      (()
+       (leave (G_ "too few arguments~%")))
+      ((many ...)
+       (leave (G_ "too many arguments~%"))))))
diff --git a/po/guix/POTFILES.in b/po/guix/POTFILES.in
index a3bced1a8f..f25a7b4802 100644
--- a/po/guix/POTFILES.in
+++ b/po/guix/POTFILES.in
@@ -60,6 +60,7 @@ guix/scripts/git.scm
 guix/scripts/git/authenticate.scm
 guix/scripts/hash.scm
 guix/scripts/import.scm
+guix/scripts/import/contentdb.scm
 guix/scripts/import/cran.scm
 guix/scripts/import/elpa.scm
 guix/scripts/pull.scm
diff --git a/tests/contentdb.scm b/tests/contentdb.scm
new file mode 100644
index 0000000000..1293ac40cf
--- /dev/null
+++ b/tests/contentdb.scm
@@ -0,0 +1,227 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (test-contentdb)
+  #:use-module (guix memoization)
+  #:use-module (guix import contentdb)
+  #:use-module (guix import utils)
+  #:use-module (guix tests)
+  #:use-module (json)
+  #:use-module (ice-9 match)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-64))
+
+
+;; Some procedures for populating a ‘fake’ ContentDB server.
+
+(define* (make-package-sexp #:key
+                            (guix-name "minetest-foo")
+                            (home-page "https://example.org/foo")
+                            (repo "https://example.org/foo.git")
+                            (synopsis "synopsis")
+                            (guix-description "description")
+                            (guix-license '(list license:cc-by-sa4.0 license:lgpl3))
+                            (inputs '())
+                            #:allow-other-keys)
+  `(package
+     (name ,guix-name)
+     ;; This is not a proper version number but ContentDB does not include
+     ;; version numbers.
+     (version "2021-07-25")
+     (source
+      (origin
+        (method git-fetch)
+        (uri (git-reference
+              (url ,(and (not (eq? repo 'null)) repo))
+              (commit #f)))
+        (sha256
+         (base32 #f))
+        (file-name (git-file-name name version))))
+     (build-system minetest-mod-build-system)
+     ,@(maybe-propagated-inputs inputs)
+     (home-page ,home-page)
+     (synopsis ,synopsis)
+     (description ,guix-description)
+     (license ,guix-license)))
+
+(define* (make-package-json #:key
+                            (author "Author")
+                            (name "foo")
+                            (media-license "CC BY-SA 4.0")
+                            (license "LGPLv3")
+                            (short-description "synopsis")
+                            (long-description "description")
+                            (repo "https://example.org/foo.git")
+                            (website "https://example.org/foo")
+                            (forums 321)
+                            #:allow-other-keys)
+  `(("author" . ,author)
+    ("content_warnings" . #())
+    ("created_at" . "2018-05-23T19:58:07.422108")
+    ("downloads" . 123)
+    ("forums" . ,forums)
+    ("issue_tracker" . "https://example.org/foo/issues")
+    ("license" . ,license)
+    ("long_description" . ,long-description)
+    ("maintainers" . #("maintainer"))
+    ("media_license" . ,media-license)
+    ("name" . ,name)
+    ("provides" . #("stuff"))
+    ("release" . 456)
+    ("repo" . ,repo)
+    ("score" . ,987.654)
+    ("screenshots" . #())
+    ("short_description" . ,short-description)
+    ("state" . "APPROVED")
+    ("tags" . #("some" "tags"))
+    ("thumbnail" . null)
+    ("title" . "The name")
+    ("type" . "mod")
+    ("url" . ,(string-append "https://content.minetest.net/packages/"
+                             author "/" name "/download/"))
+    ("website" . ,website)))
+
+(define* (make-releases-json #:key (commit #f) (title "") #:allow-other-keys)
+  `#((("commit" . ,commit)
+      ("downloads" . 469)
+      ("id" . 8614)
+      ("max_minetest_version" . null)
+      ("min_minetest_version" . null)
+      ("release_date" . "2021-07-25T01:10:23.207584")
+      ("title" . "2021-07-25"))))
+
+(define* (make-dependencies-json #:key (author "Author")
+                                 (name "foo")
+                                 (requirements '(("default" #f ())))
+                                 #:allow-other-keys)
+  `((,(string-append author "/" name)
+     . ,(list->vector
+         (map (match-lambda
+                ((symbolic-name optional? implementations)
+                 `(("is_optional" . ,optional?)
+                   ("name" . ,symbolic-name)
+                   ("packages" . ,(list->vector implementations)))))
+              requirements)))
+    ("something/else" . #())))
+
+(define (call-with-packages thunk . argument-lists)
+  (mock ((guix http-client) http-fetch
+         (lambda* (url #:key headers)
+           (unless (string-prefix? "mock://api/packages/" url)
+             (error "the URL ~a should not be used" url))
+           (define resource
+             (substring url (string-length "mock://api/packages/")))
+           (define components (string-split resource #\/))
+           (unless (>= (length components) 2)
+             (error "the URL ~a should have an author and name component" url))
+           (define requested-author (list-ref components 0))
+           (define requested-name (list-ref components 1))
+           (define rest (cddr components))
+           (define relevant-argument-list
+             (any (lambda (argument-list)
+                    (apply (lambda* (#:key (author "Author") (name "foo")
+                                     #:allow-other-keys)
+                             (and (equal? requested-author author)
+                                  (equal? requested-name name)
+                                  argument-list))
+                           argument-list))
+                  argument-lists))
+           (when (not relevant-argument-list)
+             (error "the package ~a/~a should be irrelevant, but ~a is fetched"
+                    requested-author requested-name url))
+           (define (scm->json-port scm)
+             (open-input-string (scm->json-string scm)))
+           (scm->json-port
+            (apply (match rest
+                     (("") make-package-json)
+                     (("dependencies" "") make-dependencies-json)
+                     (("releases" "") make-releases-json)
+                     (_ (error "TODO ~a" rest)))
+                   relevant-argument-list))))
+        (parameterize ((%contentdb-api "mock://api/"))
+          (thunk))))
+
+(define* (contentdb->guix-package* #:key (author "Author") (name "foo")
+                                   #:allow-other-keys)
+  (contentdb->guix-package author name))
+
+(define (imported-package-sexp . extra-arguments)
+  (call-with-packages
+   (lambda ()
+     ;; Don't reuse results from previous tests.
+     (invalidate-memoization! contentdb->guix-package)
+     (apply contentdb->guix-package* extra-arguments))
+   extra-arguments))
+
+(define-syntax-rule (test-package test-case . extra-arguments)
+  (test-equal test-case
+    (make-package-sexp . extra-arguments)
+    (imported-package-sexp . extra-arguments)))
+
+(test-begin "contentdb")
+
+
+;; Package names
+(test-package "contentdb->guix-package")
+(test-package "contentdb->guix-package, _ → - in package name"
+              #:name "foo_bar"
+              #:guix-name "minetest-foo-bar")
+
+
+;; Determining the home page
+(test-package "contentdb->guix-package, website is used as home page"
+              #:home-page "web://site"
+              #:website "web://site")
+(test-package "contentdb->guix-package, if absent, the forum is used"
+              #:home-page '(minetest-topic 628)
+              #:forums 628
+              #:website 'null)
+(test-package "contentdb->guix-package, if absent, the git repo is used"
+              #:home-page "https://github.com/minetest-mods/mesecons"
+              #:forums 'null
+              #:website 'null
+              #:repo "https://github.com/minetest-mods/mesecons")
+(test-package "contentdb->guix-package, all home page information absent"
+              #:home-page #f
+              #:forums 'null
+              #:website 'null
+              #:repo 'null)
+
+
+
+;; Dependencies
+(test-package "contentdb->guix-package, dependency"
+              #:requirements '(("mesecons" #f
+                                ("Jeija/mesecons"
+                                 "some-modpack/containing-mese")))
+              #:inputs '("minetest-mesecons"))
+
+(test-package "contentdb->guix-package, optional dependency"
+              #:requirements '(("mesecons" #t
+                                ("Jeija/mesecons"
+                                 "some-modpack/containing-mese")))
+              #:inputs '())
+
+
+;; License
+(test-package "contentdb->guix-package, identical licenses"
+              #:guix-license 'license:lgpl3
+              #:license "LGPLv3"
+              #:media-license "LGPLv3")
+
+(test-end "contentdb")
-- 
2.32.0
M
M
Maxime Devos wrote on 2 Aug 2021 17:50
[PATCH 15/20] gnu: Add minetest-throwing-arrows.
(address . 49828@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210802155019.6122-15-maximedevos@telenet.be
* gnu/packages/minetest.scm
(minetest-throwing-arrows): New variable.
---
gnu/packages/minetest.scm | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)

Toggle diff (43 lines)
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 25038a5f6b..6b792bf071 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -225,6 +225,36 @@ throwing things like arrows.  However, this mod does not provide an actual
 arrow and bow, but @code{minetest-throwing-arrows} does.")
     (license license:mpl2.0)))
 
+(define-public minetest-throwing-arrows
+  ;; There is only one tagged commit (version 1.1),
+  ;; there are no releases on ContentDB and the latest
+  ;; commit has a compatibility fix for Minetest 5.4.0-dev.
+  (let ((commit "059cc897af0aebfbd2c54ac5588f2b842f44f159")
+        (revision "0"))
+    (package
+      (name "minetest-throwing-arrows")
+      (version (git-version "1.1" revision commit))
+      (source
+       (origin
+         (method git-fetch)
+         (uri (git-reference
+               (url "https://github.com/minetest-mods/throwing_arrows")
+               (commit commit)))
+         (sha256
+          (base32 "0m2pmccpfxn878zd00pmrpga2h6gknz4f3qprck0fq94mksmwqs3"))
+         (file-name (git-file-name name version))))
+      (build-system minetest-mod-build-system)
+      (propagated-inputs
+       `(("minetest-throwing" ,minetest-throwing)))
+      (home-page (minetest-topic 16365))
+      (synopsis "Arrows and bows for Minetest")
+      (description
+       ;; TRANSLATORS: "throwing" is the name of a Minetest mod and should
+       ;; not be translated.
+       "This mod adds arrows and bows to Minetest.  It is a compatible
+replacement for the throwing mod by PilzAdam that uses the throwing API.")
+      (license license:mpl2.0))))
+
 (define-public minetest-unifieddyes
   (package
     (name "minetest-unifieddyes")
-- 
2.32.0
L
L
Leo Prikler wrote on 2 Aug 2021 19:14
Re: [PATCH 01/20] gnu: minetest: Respect --without-tests.
48cb75464f69573a09652853fff2581618b4f83a.camel@student.tugraz.at
Hi Maxime,

Am Montag, den 02.08.2021, 17:50 +0200 schrieb Maxime Devos:
Toggle quote (3 lines)
> * gnu/packages/games.scm
> (minetest)[arguments]<#:phases>{check}: Use 'tests?' instead
> of ',(%current-target-system)'. Remove trailing #t.
For context, (%current-target-system) is used because tests only work
in native builds. If this can't be reflected in the value of tests?,
we should have both checks, imo.
Toggle quote (26 lines)
> ---
> gnu/packages/games.scm | 7 +++----
> 1 file changed, 3 insertions(+), 4 deletions(-)
>
> diff --git a/gnu/packages/games.scm b/gnu/packages/games.scm
> index 8c6b5523f1..3e7086b398 100644
> --- a/gnu/packages/games.scm
> +++ b/gnu/packages/games.scm
> @@ -3590,13 +3590,12 @@ match, cannon keep, and grave-itation pit.")
> (string-append (getcwd) "/games")) ; for check
> #t))
> (replace 'check
> - (lambda _
> + (lambda* (#:key tests? #:allow-other-keys)
> ;; Thanks to our substitutions, the tests should also
> run
> ;; when invoked on the target outside of `guix build'.
> - (unless ,(%current-target-system)
> + (when tests?
> (setenv "HOME" "/tmp")
> - (invoke "src/minetest" "--run-unittests"))
> - #t)))))
> + (invoke "src/minetest" "--run-unittests")))))))
> (native-search-paths
> (list (search-path-specification
> (variable "MINETEST_SUBGAME_PATH")
M
M
Maxime Devos wrote on 2 Aug 2021 19:18
dd916a85f1e2e899f28d1b319d4ba1621f6af5a6.camel@telenet.be
Leo Prikler schreef op ma 02-08-2021 om 19:14 [+0200]:
Toggle quote (10 lines)
> Hi Maxime,
>
> Am Montag, den 02.08.2021, 17:50 +0200 schrieb Maxime Devos:
> > * gnu/packages/games.scm
> > (minetest)[arguments]<#:phases>{check}: Use 'tests?' instead
> > of ',(%current-target-system)'. Remove trailing #t.
> For context, (%current-target-system) is used because tests only work
> in native builds. If this can't be reflected in the value of tests?,
> we should have both checks, imo.

'cmake-cross-build' sets '#:tests?' to #f by default, so I don't think
both checks are needed.

Greetings,
Maxime.
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYQgo+BccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7vyBAQDuG54PobZUCCFWmxu2H6nMJvUA
z5QKHpGTgVmZtGiCrgEAyYr1FZpOYrAQZF4DYcnrypfCvRDsF4dKMmVwToI4OQs=
=9o2U
-----END PGP SIGNATURE-----


L
L
Leo Prikler wrote on 2 Aug 2021 19:22
3e901dfca163765a9a4ed93eedde53893a0ce364.camel@student.tugraz.at
Am Montag, den 02.08.2021, 19:18 +0200 schrieb Maxime Devos:
Toggle quote (13 lines)
> Leo Prikler schreef op ma 02-08-2021 om 19:14 [+0200]:
> > Hi Maxime,
> >
> > Am Montag, den 02.08.2021, 17:50 +0200 schrieb Maxime Devos:
> > > * gnu/packages/games.scm
> > > (minetest)[arguments]<#:phases>{check}: Use 'tests?' instead
> > > of ',(%current-target-system)'. Remove trailing #t.
> > For context, (%current-target-system) is used because tests only
> > work in native builds. If this can't be reflected in the value of
> > tests?, we should have both checks, imo.
>
> 'cmake-cross-build' sets '#:tests?' to #f by default, so I don't
> think both checks are needed.
In that case nvm and thanks for clearing that up :)
L
L
Leo Prikler wrote on 2 Aug 2021 19:28
Re: [PATCH 02/20] gnu: minetest: Search for mods in MINETEST_MOD_PATH.
87207455fefb91bb3e12fdb3209f28f11dab92e0.camel@student.tugraz.at
Hi Maxime,

Am Montag, den 02.08.2021, 17:50 +0200 schrieb Maxime Devos:
Toggle quote (85 lines)
> * gnu/packages/patches/Add-environment-variable-
> MINETEST_MOD_PATH.patch:
> New file.
> * gnu/packages/games.scm
> (minetest)[source]{patches}: Add it.
> (minetest)[native-search-paths]: Add "MINETEST_MOD_PATH".
> * gnu/local.mk (dist_patch_DATA): Add the patch.
> ---
> gnu/local.mk | 1 +
> gnu/packages/games.scm | 7 +-
> ...vironment-variable-MINETEST_MOD_PATH.patch | 115
> ++++++++++++++++++
> 3 files changed, 122 insertions(+), 1 deletion(-)
> create mode 100644 gnu/packages/patches/Add-environment-variable-
> MINETEST_MOD_PATH.patch
>
> diff --git a/gnu/local.mk b/gnu/local.mk
> index c80a9af78c..d96d4e3dbc 100644
> --- a/gnu/local.mk
> +++ b/gnu/local.mk
> @@ -801,6 +801,7 @@ dist_patch_DATA =
> \
> %D%/packages/patches/abseil-cpp-fix-gtest.patch \
> %D%/packages/patches/abseil-cpp-fix-strerror_test.patch \
> %D%/packages/patches/adb-add-libraries.patch
> \
> + %D%/packages/patches/Add-environment-variable-
> MINETEST_MOD_PATH.patch \
> %D%/packages/patches/aegis-constness-error.patch \
> %D%/packages/patches/aegis-perl-tempdir1.patch \
> %D%/packages/patches/aegis-perl-tempdir2.patch \
> diff --git a/gnu/packages/games.scm b/gnu/packages/games.scm
> index 3e7086b398..2f3285c6ea 100644
> --- a/gnu/packages/games.scm
> +++ b/gnu/packages/games.scm
> @@ -3553,6 +3553,7 @@ match, cannon keep, and grave-itation pit.")
> (base32
> "062ilb7s377q3hwfhl8q06vvcw2raydz5ljzlzwy2dmyzmdcndb
> 8"))
> (modules '((guix build utils)))
> + (patches (search-patches "Add-environment-variable-
> MINETEST_MOD_PATH.patch"))
> (snippet
> '(begin
> ;; Delete bundled libraries.
> @@ -3599,7 +3600,11 @@ match, cannon keep, and grave-itation pit.")
> (native-search-paths
> (list (search-path-specification
> (variable "MINETEST_SUBGAME_PATH")
> - (files '("share/minetest/games")))))
> + (files '("share/minetest/games")))
> + (search-path-specification
> + (variable "MINETEST_MOD_PATH")
> + (files '("share/minetest/mods"))
> + (separator #f))))
> (native-inputs
> `(("pkg-config" ,pkg-config)))
> (inputs
> diff --git a/gnu/packages/patches/Add-environment-variable-
> MINETEST_MOD_PATH.patch b/gnu/packages/patches/Add-environment-
> variable-MINETEST_MOD_PATH.patch
> new file mode 100644
> index 0000000000..8478a7bf72
> --- /dev/null
> +++ b/gnu/packages/patches/Add-environment-variable-
> MINETEST_MOD_PATH.patch
> @@ -0,0 +1,115 @@
> +From 6eb753c5bf67764890856cf23a67c0bf65973c16 Mon Sep 17 00:00:00
> 2001
> +From: Maxime Devos <maximedevos@telenet.be>
> +Date: Thu, 29 Jul 2021 22:24:50 +0200
> +Subject: [PATCH] Add environment variable MINETEST_MOD_PATH
> +
> +This adds an environment variable MINETEST_MOD_PATH.
> +When it exists, Minetest will look there for mods
> +in addition to ~/.minetest/mods/.
> +
> +This patch as-is is not yet ready for upstream, because:
> +
> + * the GUI will only display mods in MINETEST_MOD_PATH
> + or mods in ~/.minetest/mods, it won't combine the two
> +
> + * the GUI for installing mods from ContentDB is disabled
> + when MINETEST_MOD_PATH is set, because otherwise Minetest
> + would try to install mods in the store.
These two are fine for a "Guix-only" patch, although I do think we
should still read ~/.minetest/mods for backwards compatibility.

Toggle quote (1 lines)
> + * MINETEST_MOD_PATH can only have a single component
This one seems kinda arbitrary, though, and does not fit well with
MINETEST_SUBGAME_PATH.

Toggle quote (38 lines)
> +---
> + builtin/mainmenu/dlg_contentstore.lua | 7 +++++++
> + src/content/subgames.cpp | 3 +++
> + src/script/lua_api/l_mainmenu.cpp | 14 +++++++++++++-
> + src/script/lua_api/l_mainmenu.h | 2 ++
> + 4 files changed, 25 insertions(+), 1 deletion(-)
> +
> +diff --git a/builtin/mainmenu/dlg_contentstore.lua
> b/builtin/mainmenu/dlg_contentstore.lua
> +index 7096c9187..c4a2cbd18 100644
> +--- a/builtin/mainmenu/dlg_contentstore.lua
> ++++ b/builtin/mainmenu/dlg_contentstore.lua
> +@@ -22,6 +22,13 @@ if not core.get_http_api then
> + end
> + return
> + end
> ++if core.mod_path_set() then
> ++ function create_store_dlg()
> ++ return messagebox("store",
> ++ fgettext("Mods from ContentDB cannot be
> installed when mods from Guix are also installed"))
> ++ end
> ++ return
> ++end
> +
> + -- Unordered preserves the original order of the ContentDB API,
> + -- before the package list is ordered based on installed state.
> +diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp
> +index e9dc609b0..1809f189e 100644
> +--- a/src/content/subgames.cpp
> ++++ b/src/content/subgames.cpp
> +@@ -110,6 +110,9 @@ SubgameSpec findSubgame(const std::string &id)
> + std::set<std::string> mods_paths;
> + if (!user_game)
> + mods_paths.insert(share + DIR_DELIM + "mods");
> ++ const char *env_mod_path = getenv("MINETEST_MOD_PATH");
> ++ if (env_mod_path)
> ++ mods_paths.insert(std::string(env_mod_path));
Here, I would instead use an std::istringstream together with
std::getline(<>, <>, ':') to get the components of MINETEST_MOD_PATH
and insert each of them. Either that or copy whatever is used for
MINETEST_SUBGAME_PATH.
Toggle quote (67 lines)
> + if (user != share || user_game)
> + mods_paths.insert(user + DIR_DELIM + "mods");
> +
> +diff --git a/src/script/lua_api/l_mainmenu.cpp
> b/src/script/lua_api/l_mainmenu.cpp
> +index ad00de1c4..737550c42 100644
> +--- a/src/script/lua_api/l_mainmenu.cpp
> ++++ b/src/script/lua_api/l_mainmenu.cpp
> +@@ -495,9 +495,19 @@ int ModApiMainMenu::l_get_user_path(lua_State
> *L)
> +
> /********************************************************************
> **********/
> + int ModApiMainMenu::l_get_modpath(lua_State *L)
> + {
> ++ const char *c_modpath = getenv("MINETEST_MOD_PATH");
> + std::string modpath = fs::RemoveRelativePathComponents(
> + porting::path_user + DIR_DELIM + "mods" + DIR_DELIM);
> +- lua_pushstring(L, modpath.c_str());
> ++ if (c_modpath == NULL)
> ++ c_modpath = modpath.c_str();
> ++ lua_pushstring(L, c_modpath);
> ++ return 1;
> ++}
> ++
> ++/******************************************************************
> ************/
> ++int ModApiMainMenu::l_mod_path_set(lua_State *L)
> ++{
> ++ lua_pushboolean(L, NULL != getenv("MINETEST_MOD_PATH"));
> + return 1;
> + }
> +
> +@@ -855,6 +865,7 @@ void ModApiMainMenu::Initialize(lua_State *L,
> int top)
> + API_FCT(get_mapgen_names);
> + API_FCT(get_user_path);
> + API_FCT(get_modpath);
> ++ API_FCT(mod_path_set);
> + API_FCT(get_clientmodpath);
> + API_FCT(get_gamepath);
> + API_FCT(get_texturepath);
> +@@ -888,6 +899,7 @@ void ModApiMainMenu::InitializeAsync(lua_State
> *L, int top)
> + API_FCT(get_mapgen_names);
> + API_FCT(get_user_path);
> + API_FCT(get_modpath);
> ++ API_FCT(mod_path_set);
> + API_FCT(get_clientmodpath);
> + API_FCT(get_gamepath);
> + API_FCT(get_texturepath);
> +diff --git a/src/script/lua_api/l_mainmenu.h
> b/src/script/lua_api/l_mainmenu.h
> +index ec2d20da2..719c26077 100644
> +--- a/src/script/lua_api/l_mainmenu.h
> ++++ b/src/script/lua_api/l_mainmenu.h
> +@@ -112,6 +112,8 @@ class ModApiMainMenu: public ModApiBase
> +
> + static int l_get_modpath(lua_State *L);
> +
> ++ static int l_mod_path_set(lua_State *L);
> ++
> + static int l_get_clientmodpath(lua_State *L);
> +
> + static int l_get_gamepath(lua_State *L);
> +--
> +2.32.0
What are these modpaths used for? For mod installation or for querying
mod existence? If it's the former, you could leave them as-is, similar
to how elpa stays enabled in Emacs.

Regards,
M
M
Maxime Devos wrote on 2 Aug 2021 19:53
97f899a616812a0086a68ee97c36d1531e04c2e3.camel@telenet.be
Toggle quote (11 lines)
> > +This patch as-is is not yet ready for upstream, because:
> > +
> > + * the GUI will only display mods in MINETEST_MOD_PATH
> > + or mods in ~/.minetest/mods, it won't combine the two
> > +
> > + * the GUI for installing mods from ContentDB is disabled
> > + when MINETEST_MOD_PATH is set, because otherwise Minetest
> > + would try to install mods in the store.
> These two are fine for a "Guix-only" patch, although I do think we
> should still read ~/.minetest/mods for backwards compatibility.

~/.minetest/mods is still read when MINETEST_MOD_PATH is unset.
MINETEST_MOD_PATH is only set when some mod is actually installed.
So backwards compatibility should be ok.

Toggle quote (4 lines)
> > + * MINETEST_MOD_PATH can only have a single component
> This one seems kinda arbitrary, though, and does not fit well with
> MINETEST_SUBGAME_PATH.

Yes, I know. I didn't know how to adjust pkgmgr.lua and dlg_contentstore.lua
to support multiple components, though I have an idea now to try.

Toggle quote (19 lines)
> > + -- Unordered preserves the original order of the ContentDB API,
> > + -- before the package list is ordered based on installed state.
> > +diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp
> > +index e9dc609b0..1809f189e 100644
> > +--- a/src/content/subgames.cpp
> > ++++ b/src/content/subgames.cpp
> > +@@ -110,6 +110,9 @@ SubgameSpec findSubgame(const std::string &id)
> > + std::set<std::string> mods_paths;
> > + if (!user_game)
> > + mods_paths.insert(share + DIR_DELIM + "mods");
> > ++ const char *env_mod_path = getenv("MINETEST_MOD_PATH");
> > ++ if (env_mod_path)
> > ++ mods_paths.insert(std::string(env_mod_path));

> Here, I would instead use an std::istringstream together with
> std::getline(<>, <>, ':') to get the components of MINETEST_MOD_PATH
> and insert each of them. Either that or copy whatever is used for
> MINETEST_SUBGAME_PATH.

Minetest has a class 'Strfnd' supporting iteration. Using that,
it should be easy to allow MINETEST_MOD_PATH to contain ":" seperators.
However, the GUI client code (pkgmgr.lua)
uses some other logic for determining which mods exists, and currently,
that logic does not support ":" separators.

Toggle quote (69 lines)
> > +diff --git a/src/script/lua_api/l_mainmenu.cpp
> > b/src/script/lua_api/l_mainmenu.cpp
> > +index ad00de1c4..737550c42 100644
> > +--- a/src/script/lua_api/l_mainmenu.cpp
> > ++++ b/src/script/lua_api/l_mainmenu.cpp
> > +@@ -495,9 +495,19 @@ int ModApiMainMenu::l_get_user_path(lua_State
> > *L)
> > +
> > /********************************************************************
> > **********/
> > + int ModApiMainMenu::l_get_modpath(lua_State *L)
> > + {
> > ++ const char *c_modpath = getenv("MINETEST_MOD_PATH");
> > + std::string modpath = fs::RemoveRelativePathComponents(
> > + porting::path_user + DIR_DELIM + "mods" + DIR_DELIM);
> > +- lua_pushstring(L, modpath.c_str());
> > ++ if (c_modpath == NULL)
> > ++ c_modpath = modpath.c_str();
> > ++ lua_pushstring(L, c_modpath);
> > ++ return 1;
> > ++}
> > ++
> > ++/******************************************************************
> > ************/
> > ++int ModApiMainMenu::l_mod_path_set(lua_State *L)
> > ++{
> > ++ lua_pushboolean(L, NULL != getenv("MINETEST_MOD_PATH"));
> > + return 1;
> > + }
> > +
> > +@@ -855,6 +865,7 @@ void ModApiMainMenu::Initialize(lua_State *L,
> > int top)
> > + API_FCT(get_mapgen_names);
> > + API_FCT(get_user_path);
> > + API_FCT(get_modpath);
> > ++ API_FCT(mod_path_set);
> > + API_FCT(get_clientmodpath);
> > + API_FCT(get_gamepath);
> > + API_FCT(get_texturepath);
> > +@@ -888,6 +899,7 @@ void ModApiMainMenu::InitializeAsync(lua_State
> > *L, int top)
> > + API_FCT(get_mapgen_names);
> > + API_FCT(get_user_path);
> > + API_FCT(get_modpath);
> > ++ API_FCT(mod_path_set);
> > + API_FCT(get_clientmodpath);
> > + API_FCT(get_gamepath);
> > + API_FCT(get_texturepath);
> > +diff --git a/src/script/lua_api/l_mainmenu.h
> > b/src/script/lua_api/l_mainmenu.h
> > +index ec2d20da2..719c26077 100644
> > +--- a/src/script/lua_api/l_mainmenu.h
> > ++++ b/src/script/lua_api/l_mainmenu.h
> > +@@ -112,6 +112,8 @@ class ModApiMainMenu: public ModApiBase
> > +
> > + static int l_get_modpath(lua_State *L);
> > +
> > ++ static int l_mod_path_set(lua_State *L);
> > ++
> > + static int l_get_clientmodpath(lua_State *L);
> > +
> > + static int l_get_gamepath(lua_State *L);
> > +--
> > +2.32.0

> What are these modpaths used for? For mod installation or for querying
> mod existence? If it's the former, you could leave them as-is, similar
> to how elpa stays enabled in Emacs.

It is only used by the GUI. The GUI looks in the directory returned by
"get_modpath" for two things:

(1) to determine which mods exist (and to find their descriptions, their
dependency list, screenshots ...). Only the mods that exist there
can be enabled.

(2) to determine where mods must be installed when using Minetest's
built-in installer.

Greetings,
Maxime.
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYQgxCxccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7lSKAQCBoMLuhHqfcmtrdC/q6tt0xpoo
Zi9WrbtkA1Wh6ZCJCgEAkApax6ucUtN7lMKzpzbhrSXX6rOpnAIRwCbtTtBf9Aw=
=OLAf
-----END PGP SIGNATURE-----


L
L
Leo Prikler wrote on 2 Aug 2021 20:47
19c7cec42e57899c62ba6b4ff7f297a3e5bc2be4.camel@student.tugraz.at
Am Montag, den 02.08.2021, 19:53 +0200 schrieb Maxime Devos:
Toggle quote (14 lines)
> > > +This patch as-is is not yet ready for upstream, because:
> > > +
> > > + * the GUI will only display mods in MINETEST_MOD_PATH
> > > + or mods in ~/.minetest/mods, it won't combine the two
> > > +
> > > + * the GUI for installing mods from ContentDB is disabled
> > > + when MINETEST_MOD_PATH is set, because otherwise Minetest
> > > + would try to install mods in the store.
> > These two are fine for a "Guix-only" patch, although I do think we
> > should still read ~/.minetest/mods for backwards compatibility.
>
> ~/.minetest/mods is still read when MINETEST_MOD_PATH is unset.
> MINETEST_MOD_PATH is only set when some mod is actually installed.
> So backwards compatibility should be ok.
I mean in the sense of "have some mods in ~/.minetest/mods, that aren't
yet packaged in Guix and have the rest sit in the profile where they
belong". Not everyone will like the the import to manifest approach
that is needed while you're waiting for review.
Toggle quote (35 lines)
> > > + * MINETEST_MOD_PATH can only have a single component
> > This one seems kinda arbitrary, though, and does not fit well with
> > MINETEST_SUBGAME_PATH.
>
> Yes, I know. I didn't know how to adjust pkgmgr.lua and
> dlg_contentstore.lua
> to support multiple components, though I have an idea now to try.
>
> > > + -- Unordered preserves the original order of the ContentDB API,
> > > + -- before the package list is ordered based on installed state.
> > > +diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp
> > > +index e9dc609b0..1809f189e 100644
> > > +--- a/src/content/subgames.cpp
> > > ++++ b/src/content/subgames.cpp
> > > +@@ -110,6 +110,9 @@ SubgameSpec findSubgame(const std::string
> > > &id)
> > > + std::set<std::string> mods_paths;
> > > + if (!user_game)
> > > + mods_paths.insert(share + DIR_DELIM + "mods");
> > > ++ const char *env_mod_path = getenv("MINETEST_MOD_PATH");
> > > ++ if (env_mod_path)
> > > ++ mods_paths.insert(std::string(env_mod_path));
> > Here, I would instead use an std::istringstream together with
> > std::getline(<>, <>, ':') to get the components of
> > MINETEST_MOD_PATH
> > and insert each of them. Either that or copy whatever is used for
> > MINETEST_SUBGAME_PATH.
>
> Minetest has a class 'Strfnd' supporting iteration. Using that,
> it should be easy to allow MINETEST_MOD_PATH to contain ":"
> seperators.
> However, the GUI client code (pkgmgr.lua)
> uses some other logic for determining which mods exists, and
> currently,
> that logic does not support ":" separators.
Hmm, how important is the GUI side here? Can we sneek mods into it?

Toggle quote (84 lines)
> > > +diff --git a/src/script/lua_api/l_mainmenu.cpp
> > > b/src/script/lua_api/l_mainmenu.cpp
> > > +index ad00de1c4..737550c42 100644
> > > +--- a/src/script/lua_api/l_mainmenu.cpp
> > > ++++ b/src/script/lua_api/l_mainmenu.cpp
> > > +@@ -495,9 +495,19 @@ int
> > > ModApiMainMenu::l_get_user_path(lua_State
> > > *L)
> > > +
> > > /****************************************************************
> > > ****
> > > **********/
> > > + int ModApiMainMenu::l_get_modpath(lua_State *L)
> > > + {
> > > ++ const char *c_modpath = getenv("MINETEST_MOD_PATH");
> > > + std::string modpath = fs::RemoveRelativePathComponents(
> > > + porting::path_user + DIR_DELIM + "mods" +
> > > DIR_DELIM);
> > > +- lua_pushstring(L, modpath.c_str());
> > > ++ if (c_modpath == NULL)
> > > ++ c_modpath = modpath.c_str();
> > > ++ lua_pushstring(L, c_modpath);
> > > ++ return 1;
> > > ++}
> > > ++
> > > ++/**************************************************************
> > > ****
> > > ************/
> > > ++int ModApiMainMenu::l_mod_path_set(lua_State *L)
> > > ++{
> > > ++ lua_pushboolean(L, NULL !=
> > > getenv("MINETEST_MOD_PATH"));
> > > + return 1;
> > > + }
> > > +
> > > +@@ -855,6 +865,7 @@ void ModApiMainMenu::Initialize(lua_State
> > > *L,
> > > int top)
> > > + API_FCT(get_mapgen_names);
> > > + API_FCT(get_user_path);
> > > + API_FCT(get_modpath);
> > > ++ API_FCT(mod_path_set);
> > > + API_FCT(get_clientmodpath);
> > > + API_FCT(get_gamepath);
> > > + API_FCT(get_texturepath);
> > > +@@ -888,6 +899,7 @@ void
> > > ModApiMainMenu::InitializeAsync(lua_State
> > > *L, int top)
> > > + API_FCT(get_mapgen_names);
> > > + API_FCT(get_user_path);
> > > + API_FCT(get_modpath);
> > > ++ API_FCT(mod_path_set);
> > > + API_FCT(get_clientmodpath);
> > > + API_FCT(get_gamepath);
> > > + API_FCT(get_texturepath);
> > > +diff --git a/src/script/lua_api/l_mainmenu.h
> > > b/src/script/lua_api/l_mainmenu.h
> > > +index ec2d20da2..719c26077 100644
> > > +--- a/src/script/lua_api/l_mainmenu.h
> > > ++++ b/src/script/lua_api/l_mainmenu.h
> > > +@@ -112,6 +112,8 @@ class ModApiMainMenu: public ModApiBase
> > > +
> > > + static int l_get_modpath(lua_State *L);
> > > +
> > > ++ static int l_mod_path_set(lua_State *L);
> > > ++
> > > + static int l_get_clientmodpath(lua_State *L);
> > > +
> > > + static int l_get_gamepath(lua_State *L);
> > > +--
> > > +2.32.0
> > What are these modpaths used for? For mod installation or for
> > querying mod existence? If it's the former, you could leave them
> > as-is, similar to how elpa stays enabled in Emacs.
>
> It is only used by the GUI. The GUI looks in the directory returned
> by "get_modpath" for two things:
>
> (1) to determine which mods exist (and to find their descriptions,
> their dependency list, screenshots ...). Only the mods that
> exist there can be enabled.
>
> (2) to determine where mods must be installed when using Minetest's
> built-in installer.
I see. Is this the same GUI for all parts or different GUIs? I think
if we can handle the world creation parts, everything should be fine,
no?

Regards,
L
L
Leo Prikler wrote on 3 Aug 2021 11:17
Re: [PATCH 05/20] build-system: minetest: Don't retain references to "bash-minimal".
60c5062a7debff22cee27198c2548605fd7441e0.camel@student.tugraz.at
Hi,

I'd merge this and 04/20 into a single patch. 04/20 does of its own
give a good incentive as to why a new build system is to be used (this
could instead be handled by the importer), with this phase added it
makes slightly more sense.

OTOH, perhaps we shouldn't install those shell scripts in the first
place? Perhaps we can instead make the importer generate packages
based directly on copy-build-system, in which those static strings are
already evaluated. WDYT?

Am Montag, den 02.08.2021, 17:50 +0200 schrieb Maxime Devos:
Toggle quote (39 lines)
> * guix/build-system/minetest.scm
> (%standard-phases): New variable. Delete "patch-source-shebangs"
> phase.
> (lower-mod): Set #:phases to value of new variable.
> ---
> guix/build-system/minetest.scm | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/guix/build-system/minetest.scm b/guix/build-
> system/minetest.scm
> index 29866ced6d..993c5631eb 100644
> --- a/guix/build-system/minetest.scm
> +++ b/guix/build-system/minetest.scm
> @@ -35,6 +35,14 @@
> (substring package-name 9)
> package-name))
>
> +(define %standard-phases
> + ;; The source code sometimes contains shell scripts which are used
> for
> + ;; development but not at run time (e.g. listnodes.sh in
> + ;; minetest-homedecor-modpack). Don't make them retain a
> reference
> + ;; to bash-minimal.
> + '(modify-phases (@ (guix build copy-build-system) %standard-
> phases)
> + (delete 'patch-source-shebangs)))
> +
> (define (lower-mod name . arguments)
> (define lower (build-system-lower copy-build-system))
> (apply lower
> @@ -42,6 +50,7 @@
> #:install-plan
> `'(("." ,(string-append "share/minetest/mods/"
> (guix-name->mod-name name))))
> + #:phases %standard-phases
> arguments))
>
> (define minetest-mod-build-system
M
M
Maxime Devos wrote on 3 Aug 2021 13:09
Re: [PATCH 02/20] gnu: minetest: Search for mods in MINETEST_MOD_PATH.
9cc691b6c242e31edcc1215d259eab9306715708.camel@telenet.be
Hi,

I've modified this patch such that:

* the GUI will display mods in MINETEST_MOD_PATH as well as
mods in ~/.minetest/mods

* the built-in installer works even if MINETEST_MOD_PATH is set

* MINETEST_MOD_PATH can contain multiple components

Please tell if there are other issues

I'll look into the upstream procedure for submitting patches.

Leo Prikler schreef op ma 02-08-2021 om 20:47 [+0200]:
Toggle quote (20 lines)
> Am Montag, den 02.08.2021, 19:53 +0200 schrieb Maxime Devos:
> > > > +This patch as-is is not yet ready for upstream, because:
> > > > +
> > > > + * the GUI will only display mods in MINETEST_MOD_PATH
> > > > + or mods in ~/.minetest/mods, it won't combine the two
> > > > +
> > > > + * the GUI for installing mods from ContentDB is disabled
> > > > + when MINETEST_MOD_PATH is set, because otherwise Minetest
> > > > + would try to install mods in the store.
> > > These two are fine for a "Guix-only" patch, although I do think we
> > > should still read ~/.minetest/mods for backwards compatibility.
> >
> > ~/.minetest/mods is still read when MINETEST_MOD_PATH is unset.
> > MINETEST_MOD_PATH is only set when some mod is actually installed.
> > So backwards compatibility should be ok.
> I mean in the sense of "have some mods in ~/.minetest/mods, that aren't
> yet packaged in Guix and have the rest sit in the profile where they
> belong". Not everyone will like the the import to manifest approach
> that is needed while you're waiting for review.

All three points should be addresed by the new patch.

Toggle quote (29 lines)
> > > > + -- Unordered preserves the original order of the ContentDB API,
> > > > + -- before the package list is ordered based on installed state.
> > > > +diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp
> > > > +index e9dc609b0..1809f189e 100644
> > > > +--- a/src/content/subgames.cpp
> > > > ++++ b/src/content/subgames.cpp
> > > > +@@ -110,6 +110,9 @@ SubgameSpec findSubgame(const std::string
> > > > &id)
> > > > + std::set<std::string> mods_paths;
> > > > + if (!user_game)
> > > > + mods_paths.insert(share + DIR_DELIM + "mods");
> > > > ++ const char *env_mod_path = getenv("MINETEST_MOD_PATH");
> > > > ++ if (env_mod_path)
> > > > ++ mods_paths.insert(std::string(env_mod_path));
> > > Here, I would instead use an std::istringstream together with
> > > std::getline(<>, <>, ':') to get the components of
> > > MINETEST_MOD_PATH
> > > and insert each of them. Either that or copy whatever is used for
> > > MINETEST_SUBGAME_PATH.
> >
> > Minetest has a class 'Strfnd' supporting iteration. Using that,
> > it should be easy to allow MINETEST_MOD_PATH to contain ":"
> > seperators.
> > However, the GUI client code (pkgmgr.lua)
> > uses some other logic for determining which mods exists, and
> > currently,
> > that logic does not support ":" separators.
> Hmm, how important is the GUI side here? Can we sneek mods into it?

The GUI side is very important. All mods are disabled by default,
and in the GUI you need to choose which mods to enable. If the GUI
doesn't know about the existence of the mod, then the mod cannot be
enabled.

Toggle quote (13 lines)
> > It is only used by the GUI. The GUI looks in the directory returned
> > by "get_modpath" for two things:
> >
> > (1) to determine which mods exist (and to find their descriptions,
> > their dependency list, screenshots ...). Only the mods that
> > exist there can be enabled.
> >
> > (2) to determine where mods must be installed when using Minetest's
> > built-in installer.
> I see. Is this the same GUI for all parts or different GUIs? I think
> if we can handle the world creation parts, everything should be fine,
> no?

The ‘mod selecter’ (1) and installer (2) have separate graphical interfaces,
but they use some common code 'pkgmgr.lua'. In the new patch, 'get_modpath()'
(a string) is where mods should be installed by the built-in installer,
and 'get_modpaths()' (a list of strings) is where mods can be found.
Both interfaces work with the new patch.

Greetings,
Maxime.
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYQkj2xccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7mtAAQDGW+NY+YUBnMEwjmM9wq0EuDdz
FzKLVcL/MrbshcqHRAEA93Na0s6Hd86yVCYA+0bfLnMfWmTspPKxulNfFIuv7gE=
=v97R
-----END PGP SIGNATURE-----


M
M
Maxime Devos wrote on 3 Aug 2021 13:10
52f3bab58e39a8d6cd9db175f2abf883008a69f0.camel@telenet.be
I forgot to attach the patch.
From e999b5ef71d393eddd5767a108a7bd864ff6ec50 Mon Sep 17 00:00:00 2001
From: Maxime Devos <maximedevos@telenet.be>
Date: Sat, 31 Jul 2021 18:08:44 +0200
Subject: [PATCH 02/20] gnu: minetest: Search for mods in MINETEST_MOD_PATH.

* gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch:
New file.
* gnu/packages/games.scm
(minetest)[source]{patches}: Add it.
(minetest)[native-search-paths]: Add "MINETEST_MOD_PATH".
* gnu/local.mk (dist_patch_DATA): Add the patch.
---
gnu/local.mk | 1 +
gnu/packages/games.scm | 6 +-
...vironment-variable-MINETEST_MOD_PATH.patch | 162 ++++++++++++++++++
3 files changed, 168 insertions(+), 1 deletion(-)
create mode 100644 gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch

Toggle diff (206 lines)
diff --git a/gnu/local.mk b/gnu/local.mk
index c80a9af78c..d96d4e3dbc 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -801,6 +801,7 @@ dist_patch_DATA =						\
   %D%/packages/patches/abseil-cpp-fix-gtest.patch		\
   %D%/packages/patches/abseil-cpp-fix-strerror_test.patch	\
   %D%/packages/patches/adb-add-libraries.patch			\
+  %D%/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch	\
   %D%/packages/patches/aegis-constness-error.patch         	\
   %D%/packages/patches/aegis-perl-tempdir1.patch           	\
   %D%/packages/patches/aegis-perl-tempdir2.patch           	\
diff --git a/gnu/packages/games.scm b/gnu/packages/games.scm
index 3e7086b398..6a30c53e32 100644
--- a/gnu/packages/games.scm
+++ b/gnu/packages/games.scm
@@ -3553,6 +3553,7 @@ match, cannon keep, and grave-itation pit.")
                (base32
                 "062ilb7s377q3hwfhl8q06vvcw2raydz5ljzlzwy2dmyzmdcndb8"))
               (modules '((guix build utils)))
+              (patches (search-patches "Add-environment-variable-MINETEST_MOD_PATH.patch"))
               (snippet
                '(begin
                   ;; Delete bundled libraries.
@@ -3599,7 +3600,10 @@ match, cannon keep, and grave-itation pit.")
     (native-search-paths
      (list (search-path-specification
             (variable "MINETEST_SUBGAME_PATH")
-            (files '("share/minetest/games")))))
+            (files '("share/minetest/games")))
+           (search-path-specification
+            (variable "MINETEST_MOD_PATH")
+            (files '("share/minetest/mods")))))
     (native-inputs
      `(("pkg-config" ,pkg-config)))
     (inputs
diff --git a/gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch b/gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch
new file mode 100644
index 0000000000..8d72c42bf9
--- /dev/null
+++ b/gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch
@@ -0,0 +1,162 @@
+From dccaae3aebedb5178201ba818b8264fdb9e5e832 Mon Sep 17 00:00:00 2001
+From: Maxime Devos <maximedevos@telenet.be>
+Date: Tue, 3 Aug 2021 01:00:23 +0200
+Subject: [PATCH] Add environment variable MINETEST_MOD_PATH
+
+This adds an environment variable MINETEST_MOD_PATH.
+When it exists, Minetest will look there for mods
+in addition to ~/.minetest/mods/.  Mods can still be
+installed to ~/.minetest/mods/ with the built-in installer.
+---
+ builtin/mainmenu/pkgmgr.lua       |  7 +++----
+ doc/menu_lua_api.txt              |  8 +++++++-
+ src/content/subgames.cpp          | 11 +++++++++++
+ src/script/lua_api/l_mainmenu.cpp | 31 +++++++++++++++++++++++++++++++
+ src/script/lua_api/l_mainmenu.h   |  2 ++
+ 5 files changed, 54 insertions(+), 5 deletions(-)
+
+diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua
+index 787936e31..d8fba0ebe 100644
+--- a/builtin/mainmenu/pkgmgr.lua
++++ b/builtin/mainmenu/pkgmgr.lua
+@@ -682,10 +682,9 @@ function pkgmgr.preparemodlist(data)
+ 	local game_mods = {}
+ 
+ 	--read global mods
+-	local modpath = core.get_modpath()
+-
+-	if modpath ~= nil and
+-		modpath ~= "" then
++	local modpaths = core.get_modpaths()
++	--XXX what was ‘modpath ~= ""’ and ‘modpath ~= nil’ for?
++	for _,modpath in ipairs(modpaths) do
+ 		get_mods(modpath,global_mods)
+ 	end
+ 
+diff --git a/doc/menu_lua_api.txt b/doc/menu_lua_api.txt
+index b3975bc1d..132444b14 100644
+--- a/doc/menu_lua_api.txt
++++ b/doc/menu_lua_api.txt
+@@ -218,7 +218,13 @@ Package - content which is downloadable from the content db, may or may not be i
+     * returns path to global user data,
+       the directory that contains user-provided mods, worlds, games, and texture packs.
+ * core.get_modpath() (possible in async calls)
+-    * returns path to global modpath
++    * returns path to global modpath, where mods can be installed
++* core.get_modpaths() (possible in async calls)
++    * returns list of paths to global modpaths, where mods have been installed
++
++      The difference with "core.get_modpath" is that no mods should be installed in these
++      directories by Minetest -- they might be read-only.
++
+ * core.get_clientmodpath() (possible in async calls)
+     * returns path to global client-side modpath
+ * core.get_gamepath() (possible in async calls)
+diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp
+index e9dc609b0..d73f95a1f 100644
+--- a/src/content/subgames.cpp
++++ b/src/content/subgames.cpp
+@@ -61,6 +61,12 @@ std::string getSubgamePathEnv()
+ 	return subgame_path ? std::string(subgame_path) : "";
+ }
+ 
++std::string getModPathEnv()
++{
++	char *mod_path = getenv("MINETEST_MOD_PATH");
++	return mod_path ? std::string(mod_path) : "";
++}
++
+ SubgameSpec findSubgame(const std::string &id)
+ {
+ 	if (id.empty())
+@@ -110,6 +116,11 @@ SubgameSpec findSubgame(const std::string &id)
+ 	std::set<std::string> mods_paths;
+ 	if (!user_game)
+ 		mods_paths.insert(share + DIR_DELIM + "mods");
++
++	Strfnd mod_search_paths(getModPathEnv());
++	while (!mod_search_paths.at_end())
++		mods_paths.insert(mod_search_paths.next(PATH_DELIM));
++
+ 	if (user != share || user_game)
+ 		mods_paths.insert(user + DIR_DELIM + "mods");
+ 
+diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp
+index 3e9709bde..c1647195f 100644
+--- a/src/script/lua_api/l_mainmenu.cpp
++++ b/src/script/lua_api/l_mainmenu.cpp
+@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
+ #include "lua_api/l_internal.h"
+ #include "common/c_content.h"
+ #include "cpp_api/s_async.h"
++#include "util/strfnd.h"
+ #include "gui/guiEngine.h"
+ #include "gui/guiMainMenu.h"
+ #include "gui/guiKeyChangeMenu.h"
+@@ -502,6 +503,34 @@ int ModApiMainMenu::l_get_modpath(lua_State *L)
+ 	return 1;
+ }
+ 
++/******************************************************************************/
++int ModApiMainMenu::l_get_modpaths(lua_State *L)
++{
++	const char *c_modpath = getenv("MINETEST_MOD_PATH");
++	if (c_modpath == NULL)
++		c_modpath = "";
++	int index = 1;
++	lua_newtable(L);
++	// XXX: for some reason, simply writing
++	// Strfnd mod_search_paths (std::string(c_modpath));
++	// leads to a compilation error:
++	//
++	// request for member ‘at_end’ in ‘mod_search_paths’, which is of
++	// non-class type ‘Strfnd(std::__cxx11::string)
++	// {aka BasicStrfnd<char>(std::__cxx11::basic_string<char>)}’
++	std::string modpath = std::string(c_modpath);
++	Strfnd mod_search_paths(modpath);
++	while (!mod_search_paths.at_end()) {
++		std::string component = mod_search_paths.next(PATH_DELIM);
++		lua_pushstring(L, component.c_str());
++		lua_rawseti(L, -2, index);
++		index++;
++	}
++	ModApiMainMenu::l_get_modpath(L);
++	lua_rawseti(L, -2, index);
++	return 1;
++}
++
+ /******************************************************************************/
+ int ModApiMainMenu::l_get_clientmodpath(lua_State *L)
+ {
+@@ -949,6 +978,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
+ 	API_FCT(get_mapgen_names);
+ 	API_FCT(get_user_path);
+ 	API_FCT(get_modpath);
++	API_FCT(get_modpaths);
+ 	API_FCT(get_clientmodpath);
+ 	API_FCT(get_gamepath);
+ 	API_FCT(get_texturepath);
+@@ -983,6 +1013,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top)
+ 	API_FCT(get_mapgen_names);
+ 	API_FCT(get_user_path);
+ 	API_FCT(get_modpath);
++	API_FCT(get_modpaths);
+ 	API_FCT(get_clientmodpath);
+ 	API_FCT(get_gamepath);
+ 	API_FCT(get_texturepath);
+diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h
+index 33ac9e721..a6a54a2cb 100644
+--- a/src/script/lua_api/l_mainmenu.h
++++ b/src/script/lua_api/l_mainmenu.h
+@@ -112,6 +112,8 @@ class ModApiMainMenu: public ModApiBase
+ 
+ 	static int l_get_modpath(lua_State *L);
+ 
++	static int l_get_modpaths(lua_State *L);
++
+ 	static int l_get_clientmodpath(lua_State *L);
+ 
+ 	static int l_get_gamepath(lua_State *L);
+-- 
+2.32.0
+
-- 
2.32.0
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYQkkIRccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7mlyAPsFn6nqWCEelOR1kN8D9OBNzKkV
HnD6xNoYjUyHz511QQD9HUUvVSakqVCzG5pMtlfzNALIetgFFpOzmmxVcYjWrQk=
=uyPA
-----END PGP SIGNATURE-----


L
L
Leo Prikler wrote on 3 Aug 2021 13:54
b3837ab98faba2a7cbb8174e3a2b3daa04be9627.camel@student.tugraz.at
Hi,

Am Dienstag, den 03.08.2021, 13:10 +0200 schrieb Maxime Devos:
Toggle quote (2 lines)
> (patches (search-patches "Add-environment-variable-
> MINETEST_MOD_PATH.patch"))
This line is a bit long. Even if might look a little weird, I think
it's better split in three.

Toggle quote (10 lines)
> ++ // XXX: for some reason, simply writing
> ++ // Strfnd mod_search_paths (std::string(c_modpath));
> ++ // leads to a compilation error:
> ++ //
> ++ // request for member ‘at_end’ in ‘mod_search_paths’, which is
> of
> ++ // non-class type ‘Strfnd(std::__cxx11::string)
> ++ // {aka BasicStrfnd<char>(std::__cxx11::basic_string<char>)}’
> ++ std::string modpath = std::string(c_modpath);
> ++ Strfnd mod_search_paths(modpath);
Try Strfnd mod_search_paths{modpath}. The normal bracket style
confuses C++, because it can also be parsed as a function declaration.

Toggle quote (10 lines)
> + * core.get_modpath() (possible in async calls)
> +- * returns path to global modpath
> ++ * returns path to global modpath, where mods can be installed
> ++* core.get_modpaths() (possible in async calls)
> ++ * returns list of paths to global modpaths, where mods have
> been installed
> ++
> ++ The difference with "core.get_modpath" is that no mods should
> be installed in these
> ++ directories by Minetest -- they might be read-only.
This is a somewhat weird interface imo. I think core.get_modpath
should be the first element of core.get_modpaths and documented in that
way, so that any GUI that deals with "all known mods" needs to simply
call the latter, whereas any GUI that deals with installing can
comfortably use either the former or whatever Lua has for car. WDYT?

Otherwise LGTM.
M
M
Maxime Devos wrote on 3 Aug 2021 13:59
Re: [PATCH 05/20] build-system: minetest: Don't retain references to "bash-minimal".
265c85f914757066aee6b6933ba58bf1abd2bc84.camel@telenet.be
Leo Prikler schreef op di 03-08-2021 om 11:17 [+0200]:
Toggle quote (7 lines)
> Hi,
>
> I'd merge this and 04/20 into a single patch. 04/20 does of its own
> give a good incentive as to why a new build system is to be used (this
> could instead be handled by the importer), with this phase added it
> makes slightly more sense.

As an argument for having a 'minetest-mod-build-system', consider
some ways 'minetest-mod-build-system' could be improved in the future:

* a phase could be added to minimise PNG images (e.g. using 'optipng')
* likewise, for lua code
* some basic tests could be added (e.g. creating a new world and
loading the mod, testing that Minetest doesn't raise an error during
mod loading)

Also, having "#:install-plan '(("." "share/minetest/mods/the-mod-name"))"
appear in every package definition seems rather repetitive to me.

The idea behind "04/20" and "05/20" being separate patches, is to start
with a basic "minetest-mod-build-system" and gradually improve it.
The small improvements (currently only one, i.e., 05/20) could be reviewed
separately from each other and whether there should be a
"minetest-mod-build-system" at all.

E.g., see attached a patch that sets #:allowed-references '(), ensuring
nothing sneaks into the closure. Another change I'm thinking of, is including
only "tar", "gzip" and the like as implicit inputs, and not "bash" or "coreutils",
though that's probably useless if shebang patching has been disabled.

Toggle quote (5 lines)
> OTOH, perhaps we shouldn't install those shell scripts in the first
> place? Perhaps we can instead make the importer generate packages
> based directly on copy-build-system, in which those static strings are
> already evaluated. WDYT?

Directly using 'copy-build-system' makes it more difficult to make the
improvements listed above. I don't know what you mean with ‘in which those
static strings are already evaluated’ -- what are ‘those static strings’ here?

I suppose it is possible to exclude shell scripts from installation, but
just installing everything (and disabling shebang patching) seems simpler.

Greetings,
Maxime.
From eef6cb11a923458cba50bbc4e6440c0b2f372da2 Mon Sep 17 00:00:00 2001
From: Maxime Devos <maximedevos@telenet.be>
Date: Tue, 3 Aug 2021 13:50:49 +0200
Subject: [PATCH 1/2] build-system/copy: Support #:allowed-references.

* guix/build-system/copy.scm
(copy-build): Add #:allowed-references argument.
(copy-build)[canonicalize-reference]: New procedure.
---
guix/build-system/copy.scm | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)

Toggle diff (41 lines)
diff --git a/guix/build-system/copy.scm b/guix/build-system/copy.scm
index d1bf8fb654..1cd0a95150 100644
--- a/guix/build-system/copy.scm
+++ b/guix/build-system/copy.scm
@@ -92,8 +92,22 @@
                      (system (%current-system))
                      (imported-modules %copy-build-system-modules)
                      (modules '((guix build copy-build-system)
-                                (guix build utils))))
+                                (guix build utils)))
+                     allowed-references)
   "Build SOURCE using INSTALL-PLAN, and with INPUTS."
+  ;; XXX: procedure copied from (guix build-system gnu)
+  (define canonicalize-reference
+    (match-lambda
+     ((? package? p)
+      (derivation->output-path (package-derivation store p system
+                                                   #:graft? #f)))
+     (((? package? p) output)
+      (derivation->output-path (package-derivation store p system
+                                                   #:graft? #f)
+                               output))
+     ((? string? output)
+      output)))
+
   (define builder
     `(begin
        (use-modules ,@modules)
@@ -131,6 +145,10 @@
                                 #:system system
                                 #:inputs inputs
                                 #:modules imported-modules
+                                #:allowed-references
+                                (and allowed-references
+                                     (map canonicalize-reference
+                                          allowed-references))
                                 #:outputs outputs
                                 #:guile-for-build guile-for-build))
 
-- 
2.32.0
From f383aa1c886701631f6ae924a93e13cdad2eaa59 Mon Sep 17 00:00:00 2001
From: Maxime Devos <maximedevos@telenet.be>
Date: Tue, 3 Aug 2021 13:52:37 +0200
Subject: [PATCH 2/2] build-system/minetest: Don't let anything sneak into the
closure.

* guix/build-system/minetest.scm (lower-mod): Set #:allowed-references
to the empty list.
---
guix/build-system/minetest.scm | 2 ++
1 file changed, 2 insertions(+)

Toggle diff (15 lines)
diff --git a/guix/build-system/minetest.scm b/guix/build-system/minetest.scm
index 993c5631eb..1d63e5ffcf 100644
--- a/guix/build-system/minetest.scm
+++ b/guix/build-system/minetest.scm
@@ -51,6 +51,8 @@
     `'(("." ,(string-append "share/minetest/mods/"
                             (guix-name->mod-name name))))
     #:phases %standard-phases
+    ;; Ensure nothing sneaks into the closure.
+    #:allowed-references '()
     arguments))
 
 (define minetest-mod-build-system
-- 
2.32.0
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYQkvlBccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7m0uAQDHvPNuKE0Kzqph1mQNRU+T4JHR
4ugvCqtJRxebG0S91wD/VO97KSiom/jmERbObhUUGG+tcHsz9YDaSgDXgktMHwU=
=QwE9
-----END PGP SIGNATURE-----


L
L
Leo Prikler wrote on 3 Aug 2021 14:28
8d13a1057f368f47eef8da538c554a379892cacc.camel@student.tugraz.at
Hi,

Am Dienstag, den 03.08.2021, 13:59 +0200 schrieb Maxime Devos:
Toggle quote (21 lines)
> Leo Prikler schreef op di 03-08-2021 om 11:17 [+0200]:
> > Hi,
> >
> > I'd merge this and 04/20 into a single patch. 04/20 does of its
> > own
> > give a good incentive as to why a new build system is to be used
> > (this
> > could instead be handled by the importer), with this phase added it
> > makes slightly more sense.
>
> As an argument for having a 'minetest-mod-build-system', consider
> some ways 'minetest-mod-build-system' could be improved in the
> future:
>
> * a phase could be added to minimise PNG images (e.g. using
> 'optipng')
> * likewise, for lua code
> * some basic tests could be added (e.g. creating a new world and
> loading the mod, testing that Minetest doesn't raise an error
> during
> mod loading)
Of course there's more that can be done here, but this just reaffirms
my earlier point, that the build system on its own as it is in 04/20
looks rather unfinished. I'd personally prefer introducing it as one
whole as it gives a bigger picture of the whole thing rather than
digging into every detail.

Toggle quote (3 lines)
> Also, having "#:install-plan '(("." "share/minetest/mods/the-mod-
> name"))"
> appear in every package definition seems rather repetitive to me.
Perhaps, but it's not like there aren't other groups of things that can
be implemented trivially in terms of copy-build-system.

Toggle quote (7 lines)
> The idea behind "04/20" and "05/20" being separate patches, is to
> start
> with a basic "minetest-mod-build-system" and gradually improve it.
> The small improvements (currently only one, i.e., 05/20) could be
> reviewed
> separately from each other and whether there should be a
> "minetest-mod-build-system" at all.
See above.

Toggle quote (7 lines)
> E.g., see attached a patch that sets #:allowed-references '(),
> ensuring
> nothing sneaks into the closure. Another change I'm thinking of, is
> including
> only "tar", "gzip" and the like as implicit inputs, and not "bash" or
> "coreutils",
> though that's probably useless if shebang patching has been disabled.
IMO that patch should also be merged "as one" with the others.

Toggle quote (10 lines)
> > OTOH, perhaps we shouldn't install those shell scripts in the first
> > place? Perhaps we can instead make the importer generate packages
> > based directly on copy-build-system, in which those static strings
> > are
> > already evaluated. WDYT?
>
> Directly using 'copy-build-system' makes it more difficult to make
> the improvements listed above. I don't know what you mean with ‘in
> which those static strings are already evaluated’ -- what are ‘those
> static strings’ here?
‘Those static strings’ are exactly "the-mod-name" in
Toggle quote (1 lines)
> #:install-plan '(("." "share/minetest/mods/the-mod-name"))
I'm still not quite convinced, that whatever improvements you seem
there to be can't be made by using a good enough include regexp.

Toggle quote (3 lines)
> I suppose it is possible to exclude shell scripts from installation,
> but just installing everything (and disabling shebang patching) seems
> simpler.
Likewise leaving shell references in there and using plain copy-build-
system would be simpler than making a new build system, wouldn't it? I
don't think that's a good reason not to make a proper install plan.

Greetings
M
M
Maxime Devos wrote on 5 Aug 2021 13:01
7680df67d8be80c1d55771407256c28b76e0d836.camel@telenet.be
Hi,

The attached patch squashes "PATCH 04" and "PATCH 05" together
and extends the build system. It now has an install plan only installing
what's necessary (Lua code, PNG images, some configuration files ...),
a 'check' build phase verifying Minetest can actually load the mod,
and a 'minimise-png' phase minimising PNG images.

The mod name for ‘(("." "share/minetest/mods/the-mod-name"))’ can now
be determined exactly in most cases (Minetest doesn't really care but
the directory name can appear in the GUI in some cases).

Greetings,
Maxime.
From e21ef78f6fdfd6d3952b2cc4f0fb1fa8b59ae5e1 Mon Sep 17 00:00:00 2001
From: Maxime Devos <maximedevos@telenet.be>
Date: Sat, 31 Jul 2021 13:52:39 +0200
Subject: [PATCH] build-system: Add 'minetest-mod-build-system'.

* guix/build-system/minetest.scm: New module.
* guix/build/minetest-build-system.scm: Likewise.
* Makefile.am (MODULES): Add them.
* doc/guix.texi (Build Systems): Document 'minetest-mod-build-system'.
---
Makefile.am | 2 +
doc/guix.texi | 8 +
guix/build-system/minetest.scm | 87 +++++++++++
guix/build/minetest-build-system.scm | 220 +++++++++++++++++++++++++++
4 files changed, 317 insertions(+)
create mode 100644 guix/build-system/minetest.scm
create mode 100644 guix/build/minetest-build-system.scm

Toggle diff (360 lines)
diff --git a/Makefile.am b/Makefile.am
index d5ec909213..f4439ce93b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -141,6 +141,7 @@ MODULES =					\
   guix/build-system/go.scm			\
   guix/build-system/meson.scm			\
   guix/build-system/minify.scm			\
+  guix/build-system/minetest.scm		\
   guix/build-system/asdf.scm			\
   guix/build-system/copy.scm			\
   guix/build-system/glib-or-gtk.scm		\
@@ -203,6 +204,7 @@ MODULES =					\
   guix/build/gnu-dist.scm			\
   guix/build/guile-build-system.scm		\
   guix/build/maven-build-system.scm		\
+  guix/build/minetest-build-system.scm		\
   guix/build/node-build-system.scm		\
   guix/build/perl-build-system.scm		\
   guix/build/python-build-system.scm		\
diff --git a/doc/guix.texi b/doc/guix.texi
index b3c16e6507..f7dba4f293 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -7895,6 +7895,14 @@ declaration.  Its default value is @code{(default-maven-plugins)} which is
 also exported.
 @end defvr
 
+@defvr {Scheme Variable} minetest-mod-build-system
+This variable is exported by @code{(guix build-system minetest)}.  It
+implements a build procedure for @uref{https://www.minetest.net, Minetest}
+mods, which consists of copying lua code, images and other resources to
+the location Minetest searches for mods.  The build system also minimises
+PNG images and verifies that Minetest can load the mod without errors.
+@end defvr
+
 @defvr {Scheme Variable} minify-build-system
 This variable is exported by @code{(guix build-system minify)}.  It
 implements a minification procedure for simple JavaScript packages.
diff --git a/guix/build-system/minetest.scm b/guix/build-system/minetest.scm
new file mode 100644
index 0000000000..e99cc411c9
--- /dev/null
+++ b/guix/build-system/minetest.scm
@@ -0,0 +1,87 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build-system minetest)
+  #:use-module (guix build-system copy)
+  #:use-module (guix build-system gnu)
+  #:use-module (guix build-system)
+  #:use-module (guix utils)
+  #:export (minetest-mod-build-system))
+
+;;
+;; Build procedure for minetest mods.  This is implemented as an extension
+;; of ‘copy-build-system’.
+;;
+;; Code:
+
+;; Lazily resolve the bindings to avoid circular dependencies.
+(define (default-optipng)
+  ;; Lazily resolve the binding to avoid a circular dependency.
+  (module-ref (resolve-interface '(gnu packages image)) 'optipng))
+
+(define (default-minetest)
+  (module-ref (resolve-interface '(gnu packages games)) 'minetest))
+
+(define (default-xvfb-run)
+  (module-ref (resolve-interface '(gnu packages xorg)) 'xvfb-run))
+
+(define %minetest-build-system-modules
+  ;; Build-side modules imported by default.
+  `((guix build minetest-build-system)
+    ,@%copy-build-system-modules))
+
+(define %default-modules
+  ;; Modules in scope in the build-side environment.
+  '((guix build gnu-build-system)
+    (guix build minetest-build-system)
+    (guix build utils)))
+
+(define (standard-minetest-packages)
+  "Return the list of (NAME PACKAGE OUTPUT) or (NAME PACKAGE) tuples of
+standard packages used as implicit inputs of the Minetest build system."
+  `(("xvfb-run" ,(default-xvfb-run))
+    ("optipng" ,(default-optipng))
+    ("minetest" ,(default-minetest))
+    ,@(filter (lambda (input)
+                (member (car input)
+                        '("libc" "tar" "gzip" "bzip2" "xz" "locales")))
+              (standard-packages))))
+
+(define (lower-mod name . arguments)
+  (define lower (build-system-lower gnu-build-system))
+  (apply lower
+         name
+         #:imported-modules %minetest-build-system-modules
+         #:modules %default-modules
+         #:phases '%standard-phases
+         #:implicit-inputs? #f
+         ;; Mods are architecture-independent.
+         #:target #f
+         ;; Ensure nothing sneaks into the closure.
+         #:allowed-references '()
+         (substitute-keyword-arguments arguments
+           ((#:native-inputs native-inputs '())
+            (append native-inputs (standard-minetest-packages))))))
+
+(define minetest-mod-build-system
+  (build-system
+    (name 'minetest-mod)
+    (description "The build system for minetest mods")
+    (lower lower-mod)))
+
+;;; minetest.scm ends here
diff --git a/guix/build/minetest-build-system.scm b/guix/build/minetest-build-system.scm
new file mode 100644
index 0000000000..e0c11e91f6
--- /dev/null
+++ b/guix/build/minetest-build-system.scm
@@ -0,0 +1,220 @@
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build minetest-build-system)
+  #:use-module (guix build utils)
+  #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 receive)
+  #:use-module (ice-9 regex)
+  #:use-module ((guix build gnu-build-system) #:prefix gnu:)
+  #:export (%standard-phases
+            mod-install-plan minimise-png read-mod-name check))
+
+(define (mod-install-plan mod-name)
+  `(("." ,(string-append "share/minetest/mods/" mod-name)
+     ;; Only install files that will actually be used at run time.
+     ;; This can save a little disk space.
+     ;;
+     ;; See <https://github.com/minetest/minetest/blob/master/doc/lua_api.txt>
+     ;; for an incomple list of files that can be found in mods.
+     #:include ("mod.conf" "modpack.conf" "settingtypes.txt" "depends.txt"
+                "description.txt")
+     #:include-regexp (".lua$" ".png$" ".ogg$" ".obj$" ".b3d$" ".tr$"
+                       ".mts$"))))
+
+(define* (guess-mod-name #:key inputs #:allow-other-keys)
+  "Try to determine the name of the mod or modpack that is being built.
+If it is unknown, make an educated guess."
+  ;; Minetest doesn't care about the directory names in "share/minetest/mods"
+  ;; so there is no technical problem if the directory names don't match
+  ;; the mod names.  The directory can appear in the GUI if the modpack
+  ;; doesn't have the 'name' set though, so try to make the guess.
+  (define (guess)
+    (let* ((source (assoc-ref inputs "source"))
+           (file-name (basename source))
+           ;; The "minetest-" prefix is not informative, so strip it.
+           (file-name (if (string-prefix? "minetest-" file-name)
+                          (substring file-name (string-length "minetest-"))
+                          file-name))
+           ;; Strip "-checkout" suffixes of git checkouts.
+           (file-name (if (string-suffix? "-checkout" file-name)
+                          (substring file-name
+                                     0
+                                     (- (string-length file-name)
+                                        (string-length "-minetest")))
+                          file-name))
+           (first-dot (string-index file-name #\.))
+           ;; If the source code is in an archive (.tar.gz, .zip, ...),
+           ;; strip the extension.
+           (file-name (if first-dot
+                          (substring file-name 0 first-dot)
+                          file-name)))
+      (format (current-error-port)
+              "warning: the modpack ~a did not set 'name' in 'modpack.conf'~%"
+              file-name)
+      file-name))
+  (cond ((file-exists? "mod.conf")
+         (read-mod-name "mod.conf"))
+        ((file-exists? "modpack.conf")
+         (read-mod-name "modpack.conf" guess))
+        (#t (guess))))
+
+(define* (install #:key inputs #:allow-other-keys #:rest arguments)
+  (apply (@@ (guix build copy-build-system) install)
+         #:install-plan (mod-install-plan (apply guess-mod-name arguments))
+         arguments))
+
+(define %png-magic-bytes
+  ;; Magic bytes of PNG images, see ‘5.2 PNG signatures’ in
+  ;; ‘Portable Network Graphics (PNG) Specification (Second Edition)’
+  ;; on <https://www.w3.org/TR/PNG/>.
+  #vu8(137 80 78 71 13 10 26 10))
+
+(define png-file?
+  ((@@ (guix build utils) file-header-match) %png-magic-bytes))
+
+(define* (minimise-png #:key inputs native-inputs #:allow-other-keys)
+  "Minimise PNG images found in the working directory."
+  (define optipng (which "optipng"))
+  (define (optimise image)
+    (format #t "Optimising ~a~%" image)
+    (make-file-writable (dirname image))
+    (make-file-writable image)
+    (define old-size (stat:size (stat image)))
+    ;; The mod "technic" has a file "technic_music_player_top.png" that
+    ;; actually is a JPEG file, see
+    ;; <https://github.com/minetest-mods/technic/issues/590>.
+    (if (png-file? image)
+        (invoke optipng "-o4" "-quiet" image)
+        (format #t "warning: skipping ~a because it's not actually a PNG image~%"
+                image))
+    (define new-size (stat:size (stat image)))
+    (values old-size new-size))
+  (define files (find-files "." ".png$"))
+  (let loop ((total-old-size 0)
+             (total-new-size 0)
+             (images (find-files "." ".png$")))
+    (cond ((pair? images)
+           (receive (old-size new-size)
+               (optimise (car images))
+             (loop (+ total-old-size old-size)
+                   (+ total-new-size new-size)
+                   (cdr images))))
+          ((= total-old-size 0)
+           (format #t "There were no PNG images to minimisation."))
+          (#t
+           (format #t "Minimisation reduced size of images by ~,2f% (~,2f MiB to ~,2f MiB)~%"
+                   (* 100.0 (- 1 (/ total-new-size total-old-size)))
+                   (/ total-old-size (expt 1024 2))
+                   (/ total-new-size (expt 1024 2)))))))
+
+(define name-regexp (make-regexp "^name[ ]*=(.+)$"))
+
+(define* (read-mod-name mod.conf #:optional not-found)
+  "Read the name of a mod from MOD.CONF.  If MOD.CONF
+does not have a name field and NOT-FOUND is #false, raise an
+error.  If NOT-FOUND is TRUE, call NOT-FOUND instead."
+  (call-with-input-file mod.conf
+    (lambda (port)
+      (let loop ()
+        (define line (read-line port))
+        (if (eof-object? line)
+            (if not-found
+                (not-found)
+                (error "~a does not have a 'name' field" mod.conf))
+            (let ((match (regexp-exec name-regexp line)))
+              (if (regexp-match? match)
+                  (string-trim-both (match:substring match 1) #\ )
+                  (loop))))))))
+
+(define* (check #:key outputs tests? #:allow-other-keys)
+  "Test whether the mod loads.  The mod must first be installed first."
+  (define (all-mod-names directories)
+    (append-map
+     (lambda (directory)
+       (map read-mod-name (find-files directory "mod.conf")))
+     directories))
+  (when tests?
+    (mkdir "guix_testworld")
+    ;; Add the mod to the mod search path, such that Minetest can find it.
+    (setenv "MINETEST_MOD_PATH"
+            (list->search-path-as-string
+             (cons
+              (string-append (assoc-ref outputs "out") "/share/minetest/mods")
+              (search-path-as-string->list
+               (or (getenv "MINETEST_MOD_PATH") "")))
+             ":"))
+    (with-directory-excursion "guix_testworld"
+      (setenv "HOME" (getcwd))
+      ;; Create a world in which all mods are loaded.
+      (call-with-output-file "world.mt"
+        (lambda (port)
+          (display
+           "gameid = minetest
+world_name = guix_testworld
+backend = sqlite3
+player_backend = sqlite3
+auth_backend = sqlite3
+" port)
+          (for-each
+           (lambda (mod)
+             (format port "load_mod_~a = true~%" mod))
+           (all-mod-names (search-path-as-string->list
+                           (getenv "MINETEST_MOD_PATH"))))))
+      (receive (port pid)
+          ((@@ (guix build utils) open-pipe-with-stderr)
+           "xvfb-run" "--" "minetest" "--info" "--world" "." "--go")
+        (format #t "Started Minetest with all mods loaded for testing~%")
+        ;; Scan the output for error messages.
+        ;; When the player has joined the server, stop minetest.
+        (define (error? line)
+          (and (string? line)
+               (string-contains line ": ERROR[")))
+        (define (stop? line)
+          (and (string? line)
+               (string-contains line "ACTION[Server]: singleplayer [127.0.0.1] joins game.")))
+        (let loop ()
+          (match (read-line port)
+            ((? error? line)
+             (error "minetest raised an error: ~a" line))
+            ((? stop?)
+             (kill pid SIGINT)
+             (close-port port)
+             (waitpid pid))
+            ((? string? line)
+             (display line)
+             (newline)
+             (loop))
+            ((? eof-object?)
+             (error "minetest didn't start"))))))))
+
+(define %standard-phases
+  (modify-phases gnu:%standard-phases
+    (delete 'bootstrap)
+    (delete 'configure)
+    (add-before 'build 'minimise-png minimise-png)
+    (delete 'build)
+    (delete 'check)
+    (replace 'install install)
+    ;; The 'check' phase requires the mod to be installed,
+    ;; so move the 'check' phase after the 'install' phase.
+    (add-after 'install 'check check)))
+
+;;; minetest-build-system.scm ends here
-- 
2.32.0
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYQvFGxccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7qh7AP43aRW2aGQVMi002RcK/NdmRrgv
EarxVF3D33D3+XD+ngEAgOGwjaft59kMY4JSBuv+pdB5qMaQ0fkxO/okTrhK+Ac=
=DEDF
-----END PGP SIGNATURE-----


L
L
Leo Prikler wrote on 5 Aug 2021 14:04
2a49c566e1ecc280db79bcda1e893547216dd83a.camel@student.tugraz.at
Hi,

Am Donnerstag, den 05.08.2021, 13:01 +0200 schrieb Maxime Devos:
Toggle quote (19 lines)
> Hi,
>
> The attached patch squashes "PATCH 04" and "PATCH 05" together
> and extends the build system. It now has an install plan only
> installing
> what's necessary (Lua code, PNG images, some configuration files
> ...),
> a 'check' build phase verifying Minetest can actually load the mod,
> and a 'minimise-png' phase minimising PNG images.
>
> The mod name for ‘(("." "share/minetest/mods/the-mod-name"))’ can now
> be determined exactly in most cases (Minetest doesn't really care but
> the directory name can appear in the GUI in some cases).
>
> Greetings,
> Maxime.

> +mods, which consists of copying lua code, images and other resources
> to
s/lua/Lua/ :)

Toggle quote (5 lines)
> +(define* (install #:key inputs #:allow-other-keys #:rest arguments)
> + (apply (@@ (guix build copy-build-system) install)
> + #:install-plan (mod-install-plan (apply guess-mod-name
> arguments))
> + arguments))
@@ is a code smell, as far as Guix is concerned. Rather import copy-
build-system with the copy: prefix.

Toggle quote (2 lines)
> +(define png-file?
> + ((@@ (guix build utils) file-header-match) %png-magic-bytes))
Likewise import (guix build utils) directly.

Toggle quote (15 lines)
> +(define (lower-mod name . arguments)
> + (define lower (build-system-lower gnu-build-system))
> + (apply lower
> + name
> + #:imported-modules %minetest-build-system-modules
> + #:modules %default-modules
> + #:phases '%standard-phases
> + #:implicit-inputs? #f
> + ;; Mods are architecture-independent.
> + #:target #f
> + ;; Ensure nothing sneaks into the closure.
> + #:allowed-references '()
> + (substitute-keyword-arguments arguments
> + ((#:native-inputs native-inputs '())
> + (append native-inputs (standard-minetest-packages))))))
This appears a little confusing. On first glance, it does not seem to
allow overriding e.g. #:phases, but on a second look using `apply'
together with shallowly substituted arguments would enable that. The
only thing that's missing imo is that #:implicit-inputs? is not
honoured for (standard-minetest-packages) -- I think you might want to
rectify that.

Otherwise looks pretty good to me.
M
M
Maxime Devos wrote on 5 Aug 2021 15:16
8a8699af5e530cff4bab22aad4e5fa1862ebffc7.camel@telenet.be
Leo Prikler schreef op do 05-08-2021 om 14:04 [+0200]:
Toggle quote (4 lines)
> > +mods, which consists of copying lua code, images and other resources
> > to
> s/lua/Lua/ :)

Fixed.

Toggle quote (8 lines)
> > +(define* (install #:key inputs #:allow-other-keys #:rest arguments)
> > + (apply (@@ (guix build copy-build-system) install)
> > + #:install-plan (mod-install-plan (apply guess-mod-name
> > arguments))
> > + arguments))
> @@ is a code smell, as far as Guix is concerned. Rather import copy-
> build-system with the copy: prefix.

'copy-build-system' does not export 'install', so I have to use '@@' here.
Modifying 'copy-build-system' to export 'install' would presumably entail
a many rebuilds.

Toggle quote (4 lines)
> > +(define png-file?
> > + ((@@ (guix build utils) file-header-match) %png-magic-bytes))
> Likewise import (guix build utils) directly.

Likewise.

Toggle quote (20 lines)
> > +(define (lower-mod name . arguments)
> > + (define lower (build-system-lower gnu-build-system))
> > + (apply lower
> > + name
> > + #:imported-modules %minetest-build-system-modules
> > + #:modules %default-modules
> > + #:phases '%standard-phases
> > + #:implicit-inputs? #f
> > + ;; Mods are architecture-independent.
> > + #:target #f
> > + ;; Ensure nothing sneaks into the closure.
> > + #:allowed-references '()
> > + (substitute-keyword-arguments arguments
> > + ((#:native-inputs native-inputs '())
> > + (append native-inputs (standard-minetest-packages))))))

> This appears a little confusing. On first glance, it does not seem to
> allow overriding e.g. #:phases, but on a second look using `apply'
> together with shallowly substituted arguments would enable that.

I modified the patch to move more things into 'substitute-keyword-arguments'
to reduce confusion.

Toggle quote (5 lines)
> The
> only thing that's missing imo is that #:implicit-inputs? is not
> honoured for (standard-minetest-packages) -- I think you might want to
> rectify that.

I modified 'lower-mod' to _not_ add 'standard-minetest-packages' to
'native-inputs' when #:implicit-inputs? is false, though I don't see
why a package definition for a Minetest mod would do that.

I also found a spellig bug ("to minimisation" -> "to minimise") which
is now fixed.

btw, I submitted the "MINETEST_MOD_PATH" patch upstream, with your suggestion
for "{std::string(...)}" construction:

Greetings,
Maxime.
From dbd9cf53d359b461c01176e3170ba0663ce9007c Mon Sep 17 00:00:00 2001
From: Maxime Devos <maximedevos@telenet.be>
Date: Sat, 31 Jul 2021 13:52:39 +0200
Subject: [PATCH] build-system: Add 'minetest-mod-build-system'.

* guix/build-system/minetest.scm: New module.
* guix/build/minetest-build-system.scm: Likewise.
* Makefile.am (MODULES): Add them.
* doc/guix.texi (Build Systems): Document 'minetest-mod-build-system'.
---
Makefile.am | 2 +
doc/guix.texi | 8 +
guix/build-system/minetest.scm | 99 ++++++++++++
guix/build/minetest-build-system.scm | 220 +++++++++++++++++++++++++++
4 files changed, 329 insertions(+)
create mode 100644 guix/build-system/minetest.scm
create mode 100644 guix/build/minetest-build-system.scm

Toggle diff (372 lines)
diff --git a/Makefile.am b/Makefile.am
index d5ec909213..f4439ce93b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -141,6 +141,7 @@ MODULES =					\
   guix/build-system/go.scm			\
   guix/build-system/meson.scm			\
   guix/build-system/minify.scm			\
+  guix/build-system/minetest.scm		\
   guix/build-system/asdf.scm			\
   guix/build-system/copy.scm			\
   guix/build-system/glib-or-gtk.scm		\
@@ -203,6 +204,7 @@ MODULES =					\
   guix/build/gnu-dist.scm			\
   guix/build/guile-build-system.scm		\
   guix/build/maven-build-system.scm		\
+  guix/build/minetest-build-system.scm		\
   guix/build/node-build-system.scm		\
   guix/build/perl-build-system.scm		\
   guix/build/python-build-system.scm		\
diff --git a/doc/guix.texi b/doc/guix.texi
index b3c16e6507..d44ecc2005 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -7895,6 +7895,14 @@ declaration.  Its default value is @code{(default-maven-plugins)} which is
 also exported.
 @end defvr
 
+@defvr {Scheme Variable} minetest-mod-build-system
+This variable is exported by @code{(guix build-system minetest)}.  It
+implements a build procedure for @uref{https://www.minetest.net, Minetest}
+mods, which consists of copying Lua code, images and other resources to
+the location Minetest searches for mods.  The build system also minimises
+PNG images and verifies that Minetest can load the mod without errors.
+@end defvr
+
 @defvr {Scheme Variable} minify-build-system
 This variable is exported by @code{(guix build-system minify)}.  It
 implements a minification procedure for simple JavaScript packages.
diff --git a/guix/build-system/minetest.scm b/guix/build-system/minetest.scm
new file mode 100644
index 0000000000..98bcbc9e0c
--- /dev/null
+++ b/guix/build-system/minetest.scm
@@ -0,0 +1,99 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build-system minetest)
+  #:use-module (guix build-system copy)
+  #:use-module (guix build-system gnu)
+  #:use-module (guix build-system)
+  #:use-module (guix utils)
+  #:export (minetest-mod-build-system))
+
+;;
+;; Build procedure for minetest mods.  This is implemented as an extension
+;; of ‘copy-build-system’.
+;;
+;; Code:
+
+;; Lazily resolve the bindings to avoid circular dependencies.
+(define (default-optipng)
+  ;; Lazily resolve the binding to avoid a circular dependency.
+  (module-ref (resolve-interface '(gnu packages image)) 'optipng))
+
+(define (default-minetest)
+  (module-ref (resolve-interface '(gnu packages games)) 'minetest))
+
+(define (default-xvfb-run)
+  (module-ref (resolve-interface '(gnu packages xorg)) 'xvfb-run))
+
+(define %minetest-build-system-modules
+  ;; Build-side modules imported by default.
+  `((guix build minetest-build-system)
+    ,@%copy-build-system-modules))
+
+(define %default-modules
+  ;; Modules in scope in the build-side environment.
+  '((guix build gnu-build-system)
+    (guix build minetest-build-system)
+    (guix build utils)))
+
+(define (standard-minetest-packages)
+  "Return the list of (NAME PACKAGE OUTPUT) or (NAME PACKAGE) tuples of
+standard packages used as implicit inputs of the Minetest build system."
+  `(("xvfb-run" ,(default-xvfb-run))
+    ("optipng" ,(default-optipng))
+    ("minetest" ,(default-minetest))
+    ,@(filter (lambda (input)
+                (member (car input)
+                        '("libc" "tar" "gzip" "bzip2" "xz" "locales")))
+              (standard-packages))))
+
+(define* (lower-mod name #:key (implicit-inputs? #t) #:allow-other-keys
+                    #:rest arguments)
+  (define lower (build-system-lower gnu-build-system))
+  (apply lower
+         name
+         (substitute-keyword-arguments arguments
+           ;; minetest-mod-build-system adds implicit inputs by itself,
+           ;; so don't let gnu-build-system add its own implicit inputs
+           ;; as well.
+           ((#:implicit-inputs? implicit-inputs? #t)
+            #f)
+           ((#:imported-modules imported-modules %minetest-build-system-modules)
+            imported-modules)
+           ((#:modules modules %default-modules)
+            modules)
+           ((#:phases phases '%standard-phases)
+            phases)
+           ;; Mods are architecture-independent.
+           ((#:target target #f) #f)
+           ;; Ensure nothing sneaks into the closure.
+           ((#:allowed-references allowed-references '())
+            allowed-references)
+           ;; Add the implicit inputs.
+           ((#:native-inputs native-inputs '())
+            (if implicit-inputs?
+                (append native-inputs (standard-minetest-packages))
+                native-inputs)))))
+
+(define minetest-mod-build-system
+  (build-system
+    (name 'minetest-mod)
+    (description "The build system for minetest mods")
+    (lower lower-mod)))
+
+;;; minetest.scm ends here
diff --git a/guix/build/minetest-build-system.scm b/guix/build/minetest-build-system.scm
new file mode 100644
index 0000000000..e9eb491b1b
--- /dev/null
+++ b/guix/build/minetest-build-system.scm
@@ -0,0 +1,220 @@
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build minetest-build-system)
+  #:use-module (guix build utils)
+  #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 receive)
+  #:use-module (ice-9 regex)
+  #:use-module ((guix build gnu-build-system) #:prefix gnu:)
+  #:export (%standard-phases
+            mod-install-plan minimise-png read-mod-name check))
+
+(define (mod-install-plan mod-name)
+  `(("." ,(string-append "share/minetest/mods/" mod-name)
+     ;; Only install files that will actually be used at run time.
+     ;; This can save a little disk space.
+     ;;
+     ;; See <https://github.com/minetest/minetest/blob/master/doc/lua_api.txt>
+     ;; for an incomple list of files that can be found in mods.
+     #:include ("mod.conf" "modpack.conf" "settingtypes.txt" "depends.txt"
+                "description.txt")
+     #:include-regexp (".lua$" ".png$" ".ogg$" ".obj$" ".b3d$" ".tr$"
+                       ".mts$"))))
+
+(define* (guess-mod-name #:key inputs #:allow-other-keys)
+  "Try to determine the name of the mod or modpack that is being built.
+If it is unknown, make an educated guess."
+  ;; Minetest doesn't care about the directory names in "share/minetest/mods"
+  ;; so there is no technical problem if the directory names don't match
+  ;; the mod names.  The directory can appear in the GUI if the modpack
+  ;; doesn't have the 'name' set though, so try to make the guess.
+  (define (guess)
+    (let* ((source (assoc-ref inputs "source"))
+           (file-name (basename source))
+           ;; The "minetest-" prefix is not informative, so strip it.
+           (file-name (if (string-prefix? "minetest-" file-name)
+                          (substring file-name (string-length "minetest-"))
+                          file-name))
+           ;; Strip "-checkout" suffixes of git checkouts.
+           (file-name (if (string-suffix? "-checkout" file-name)
+                          (substring file-name
+                                     0
+                                     (- (string-length file-name)
+                                        (string-length "-minetest")))
+                          file-name))
+           (first-dot (string-index file-name #\.))
+           ;; If the source code is in an archive (.tar.gz, .zip, ...),
+           ;; strip the extension.
+           (file-name (if first-dot
+                          (substring file-name 0 first-dot)
+                          file-name)))
+      (format (current-error-port)
+              "warning: the modpack ~a did not set 'name' in 'modpack.conf'~%"
+              file-name)
+      file-name))
+  (cond ((file-exists? "mod.conf")
+         (read-mod-name "mod.conf"))
+        ((file-exists? "modpack.conf")
+         (read-mod-name "modpack.conf" guess))
+        (#t (guess))))
+
+(define* (install #:key inputs #:allow-other-keys #:rest arguments)
+  (apply (@@ (guix build copy-build-system) install)
+         #:install-plan (mod-install-plan (apply guess-mod-name arguments))
+         arguments))
+
+(define %png-magic-bytes
+  ;; Magic bytes of PNG images, see ‘5.2 PNG signatures’ in
+  ;; ‘Portable Network Graphics (PNG) Specification (Second Edition)’
+  ;; on <https://www.w3.org/TR/PNG/>.
+  #vu8(137 80 78 71 13 10 26 10))
+
+(define png-file?
+  ((@@ (guix build utils) file-header-match) %png-magic-bytes))
+
+(define* (minimise-png #:key inputs native-inputs #:allow-other-keys)
+  "Minimise PNG images found in the working directory."
+  (define optipng (which "optipng"))
+  (define (optimise image)
+    (format #t "Optimising ~a~%" image)
+    (make-file-writable (dirname image))
+    (make-file-writable image)
+    (define old-size (stat:size (stat image)))
+    ;; The mod "technic" has a file "technic_music_player_top.png" that
+    ;; actually is a JPEG file, see
+    ;; <https://github.com/minetest-mods/technic/issues/590>.
+    (if (png-file? image)
+        (invoke optipng "-o4" "-quiet" image)
+        (format #t "warning: skipping ~a because it's not actually a PNG image~%"
+                image))
+    (define new-size (stat:size (stat image)))
+    (values old-size new-size))
+  (define files (find-files "." ".png$"))
+  (let loop ((total-old-size 0)
+             (total-new-size 0)
+             (images (find-files "." ".png$")))
+    (cond ((pair? images)
+           (receive (old-size new-size)
+               (optimise (car images))
+             (loop (+ total-old-size old-size)
+                   (+ total-new-size new-size)
+                   (cdr images))))
+          ((= total-old-size 0)
+           (format #t "There were no PNG images to minimise."))
+          (#t
+           (format #t "Minimisation reduced size of images by ~,2f% (~,2f MiB to ~,2f MiB)~%"
+                   (* 100.0 (- 1 (/ total-new-size total-old-size)))
+                   (/ total-old-size (expt 1024 2))
+                   (/ total-new-size (expt 1024 2)))))))
+
+(define name-regexp (make-regexp "^name[ ]*=(.+)$"))
+
+(define* (read-mod-name mod.conf #:optional not-found)
+  "Read the name of a mod from MOD.CONF.  If MOD.CONF
+does not have a name field and NOT-FOUND is #false, raise an
+error.  If NOT-FOUND is TRUE, call NOT-FOUND instead."
+  (call-with-input-file mod.conf
+    (lambda (port)
+      (let loop ()
+        (define line (read-line port))
+        (if (eof-object? line)
+            (if not-found
+                (not-found)
+                (error "~a does not have a 'name' field" mod.conf))
+            (let ((match (regexp-exec name-regexp line)))
+              (if (regexp-match? match)
+                  (string-trim-both (match:substring match 1) #\ )
+                  (loop))))))))
+
+(define* (check #:key outputs tests? #:allow-other-keys)
+  "Test whether the mod loads.  The mod must first be installed first."
+  (define (all-mod-names directories)
+    (append-map
+     (lambda (directory)
+       (map read-mod-name (find-files directory "mod.conf")))
+     directories))
+  (when tests?
+    (mkdir "guix_testworld")
+    ;; Add the mod to the mod search path, such that Minetest can find it.
+    (setenv "MINETEST_MOD_PATH"
+            (list->search-path-as-string
+             (cons
+              (string-append (assoc-ref outputs "out") "/share/minetest/mods")
+              (search-path-as-string->list
+               (or (getenv "MINETEST_MOD_PATH") "")))
+             ":"))
+    (with-directory-excursion "guix_testworld"
+      (setenv "HOME" (getcwd))
+      ;; Create a world in which all mods are loaded.
+      (call-with-output-file "world.mt"
+        (lambda (port)
+          (display
+           "gameid = minetest
+world_name = guix_testworld
+backend = sqlite3
+player_backend = sqlite3
+auth_backend = sqlite3
+" port)
+          (for-each
+           (lambda (mod)
+             (format port "load_mod_~a = true~%" mod))
+           (all-mod-names (search-path-as-string->list
+                           (getenv "MINETEST_MOD_PATH"))))))
+      (receive (port pid)
+          ((@@ (guix build utils) open-pipe-with-stderr)
+           "xvfb-run" "--" "minetest" "--info" "--world" "." "--go")
+        (format #t "Started Minetest with all mods loaded for testing~%")
+        ;; Scan the output for error messages.
+        ;; When the player has joined the server, stop minetest.
+        (define (error? line)
+          (and (string? line)
+               (string-contains line ": ERROR[")))
+        (define (stop? line)
+          (and (string? line)
+               (string-contains line "ACTION[Server]: singleplayer [127.0.0.1] joins game.")))
+        (let loop ()
+          (match (read-line port)
+            ((? error? line)
+             (error "minetest raised an error: ~a" line))
+            ((? stop?)
+             (kill pid SIGINT)
+             (close-port port)
+             (waitpid pid))
+            ((? string? line)
+             (display line)
+             (newline)
+             (loop))
+            ((? eof-object?)
+             (error "minetest didn't start"))))))))
+
+(define %standard-phases
+  (modify-phases gnu:%standard-phases
+    (delete 'bootstrap)
+    (delete 'configure)
+    (add-before 'build 'minimise-png minimise-png)
+    (delete 'build)
+    (delete 'check)
+    (replace 'install install)
+    ;; The 'check' phase requires the mod to be installed,
+    ;; so move the 'check' phase after the 'install' phase.
+    (add-after 'install 'check check)))
+
+;;; minetest-build-system.scm ends here
-- 
2.32.0
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYQvkpBccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7hzrAP4pOr0s5FrCSmJecUOUWWaZkIRJ
0nWWx385tCHjXpvrhgEA3W6VDonCeLCkYuSEwfZcSvRVnDTuPRNXyLKmm3RI9AA=
=1pgf
-----END PGP SIGNATURE-----


L
L
Leo Prikler wrote on 5 Aug 2021 15:42
1a63353207ea0cbccaaf3138d42148fe5a98da62.camel@student.tugraz.at
Am Donnerstag, den 05.08.2021, 15:16 +0200 schrieb Maxime Devos:
Toggle quote (15 lines)
> [...]
> > > +(define* (install #:key inputs #:allow-other-keys #:rest
> > > arguments)
> > > + (apply (@@ (guix build copy-build-system) install)
> > > + #:install-plan (mod-install-plan (apply guess-mod-name
> > > arguments))
> > > + arguments))
> > @@ is a code smell, as far as Guix is concerned. Rather import
> > copy-build-system with the copy: prefix.
>
> 'copy-build-system' does not export 'install', so I have to use '@@'
> here.
> Modifying 'copy-build-system' to export 'install' would presumably
> entail
> a many rebuilds.
I think the thing that's usually done here is fetching through
%standard-phases.
I.e. (define copy:install (assoc-ref copy-build-system:%standard-phases
'install))

Toggle quote (5 lines)
> > > +(define png-file?
> > > + ((@@ (guix build utils) file-header-match) %png-magic-bytes))
> > Likewise import (guix build utils) directly.
>
> Likewise.
In that case fine, but make a note to move the variable and procedure
over to (guix build utils).

Toggle quote (23 lines)
> > > +(define (lower-mod name . arguments)
> > > + (define lower (build-system-lower gnu-build-system))
> > > + (apply lower
> > > + name
> > > + #:imported-modules %minetest-build-system-modules
> > > + #:modules %default-modules
> > > + #:phases '%standard-phases
> > > + #:implicit-inputs? #f
> > > + ;; Mods are architecture-independent.
> > > + #:target #f
> > > + ;; Ensure nothing sneaks into the closure.
> > > + #:allowed-references '()
> > > + (substitute-keyword-arguments arguments
> > > + ((#:native-inputs native-inputs '())
> > > + (append native-inputs (standard-minetest-
> > > packages))))))
> > This appears a little confusing. On first glance, it does not seem
> > to
> > allow overriding e.g. #:phases, but on a second look using `apply'
> > together with shallowly substituted arguments would enable that.
>
> I modified the patch to move more things into 'substitute-keyword-
> arguments' to reduce confusion.
LGTM.

Toggle quote (12 lines)
> > The
> > only thing that's missing imo is that #:implicit-inputs? is not
> > honoured for (standard-minetest-packages) -- I think you might want
> > to
> > rectify that.
>
> I modified 'lower-mod' to _not_ add 'standard-minetest-packages' to
> 'native-inputs' when #:implicit-inputs? is false, though I don't see
> why a package definition for a Minetest mod would do that.
>
> I also found a spellig bug ("to minimisation" -> "to minimise") which
> is now fixed.
The new lower-mod mostly LGTM, but
Toggle quote (2 lines)
> + ;; Mods are architecture-independent.
> + ((#:target target #f) #f)
should be `target' imho. What if the mod e.g. actually builds a shared
object and somehow uses Lua's very dynamic FFI to load it? (Even if
that's not currently possible, it might be in the future). Setting it
to #f by default OTOH sounds very reasonable to me.

Toggle quote (3 lines)
> btw, I submitted the "MINETEST_MOD_PATH" patch upstream, with your
> suggestion for "{std::string(...)}" construction:
> <https://github.com/minetest/minetest/pull/11515>;.
Nice, perhaps in 5.5 we will be able to enjoy this out-of-the-box.

Greetings
M
M
Maxime Devos wrote on 5 Aug 2021 16:41
c436498dbd7f7d83fe364156e73dfa7951fb95ed.camel@telenet.be
Leo Prikler schreef op do 05-08-2021 om 15:42 [+0200]:
Toggle quote (21 lines)
> Am Donnerstag, den 05.08.2021, 15:16 +0200 schrieb Maxime Devos:
> > [...]
> > > > +(define* (install #:key inputs #:allow-other-keys #:rest
> > > > arguments)
> > > > + (apply (@@ (guix build copy-build-system) install)
> > > > + #:install-plan (mod-install-plan (apply guess-mod-name
> > > > arguments))
> > > > + arguments))
> > > @@ is a code smell, as far as Guix is concerned. Rather import
> > > copy-build-system with the copy: prefix.
> >
> > 'copy-build-system' does not export 'install', so I have to use '@@'
> > here.
> > Modifying 'copy-build-system' to export 'install' would presumably
> > entail
> > a many rebuilds.
> I think the thing that's usually done here is fetching through
> %standard-phases.
> I.e. (define copy:install (assoc-ref copy-build-system:%standard-phases
> 'install))

Done.

Toggle quote (8 lines)
> > > > +(define png-file?
> > > > + ((@@ (guix build utils) file-header-match) %png-magic-bytes))
> > > Likewise import (guix build utils) directly.
> >
> > Likewise.
> In that case fine, but make a note to move the variable and procedure
> over to (guix build utils).

I made a note.

Toggle quote (9 lines)
> The new lower-mod mostly LGTM, but
> > + ;; Mods are architecture-independent.
> > + ((#:target target #f) #f)

> should be `target' imho. What if the mod e.g. actually builds a shared
> object and somehow uses Lua's very dynamic FFI to load it? (Even if
> that's not currently possible, it might be in the future). Setting it
> to #f by default OTOH sounds very reasonable to me.

'target' is set by 'make-bag' in (guix build-system), so if the code above
is made

((#:target target #f) target)

then #:target will always be set to some triplet. Mostly harmless, but a
bit a waste of disk space since this leads to (slightly) different derivations
depending on the value of "target".

I don't think any mods use Lua's FFI, but some mods use 'os.execute',
which takes a file name referring to an executable, which might need to be
absolutised in Guix, and therefore some mods are architecture-dependent.

It dropped the ((#:target target #f) #f). I noticed "#:implicit-cross-inputs?"
wasn't set to #f. That has been corrected as well.

Greetings,
Maxime.
From 93aa8e1976e762d30be70aef6d5c50b1d06ca4be Mon Sep 17 00:00:00 2001
From: Maxime Devos <maximedevos@telenet.be>
Date: Sat, 31 Jul 2021 13:52:39 +0200
Subject: [PATCH] build-system: Add 'minetest-mod-build-system'.

* guix/build-system/minetest.scm: New module.
* guix/build/minetest-build-system.scm: Likewise.
* Makefile.am (MODULES): Add them.
* doc/guix.texi (Build Systems): Document 'minetest-mod-build-system'.
---
Makefile.am | 2 +
doc/guix.texi | 8 +
guix/build-system/minetest.scm | 99 ++++++++++++
guix/build/minetest-build-system.scm | 225 +++++++++++++++++++++++++++
4 files changed, 334 insertions(+)
create mode 100644 guix/build-system/minetest.scm
create mode 100644 guix/build/minetest-build-system.scm

Toggle diff (377 lines)
diff --git a/Makefile.am b/Makefile.am
index d5ec909213..f4439ce93b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -141,6 +141,7 @@ MODULES =					\
   guix/build-system/go.scm			\
   guix/build-system/meson.scm			\
   guix/build-system/minify.scm			\
+  guix/build-system/minetest.scm		\
   guix/build-system/asdf.scm			\
   guix/build-system/copy.scm			\
   guix/build-system/glib-or-gtk.scm		\
@@ -203,6 +204,7 @@ MODULES =					\
   guix/build/gnu-dist.scm			\
   guix/build/guile-build-system.scm		\
   guix/build/maven-build-system.scm		\
+  guix/build/minetest-build-system.scm		\
   guix/build/node-build-system.scm		\
   guix/build/perl-build-system.scm		\
   guix/build/python-build-system.scm		\
diff --git a/doc/guix.texi b/doc/guix.texi
index b3c16e6507..d44ecc2005 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -7895,6 +7895,14 @@ declaration.  Its default value is @code{(default-maven-plugins)} which is
 also exported.
 @end defvr
 
+@defvr {Scheme Variable} minetest-mod-build-system
+This variable is exported by @code{(guix build-system minetest)}.  It
+implements a build procedure for @uref{https://www.minetest.net, Minetest}
+mods, which consists of copying Lua code, images and other resources to
+the location Minetest searches for mods.  The build system also minimises
+PNG images and verifies that Minetest can load the mod without errors.
+@end defvr
+
 @defvr {Scheme Variable} minify-build-system
 This variable is exported by @code{(guix build-system minify)}.  It
 implements a minification procedure for simple JavaScript packages.
diff --git a/guix/build-system/minetest.scm b/guix/build-system/minetest.scm
new file mode 100644
index 0000000000..f33e97559d
--- /dev/null
+++ b/guix/build-system/minetest.scm
@@ -0,0 +1,99 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build-system minetest)
+  #:use-module (guix build-system copy)
+  #:use-module (guix build-system gnu)
+  #:use-module (guix build-system)
+  #:use-module (guix utils)
+  #:export (minetest-mod-build-system))
+
+;;
+;; Build procedure for minetest mods.  This is implemented as an extension
+;; of ‘copy-build-system’.
+;;
+;; Code:
+
+;; Lazily resolve the bindings to avoid circular dependencies.
+(define (default-optipng)
+  ;; Lazily resolve the binding to avoid a circular dependency.
+  (module-ref (resolve-interface '(gnu packages image)) 'optipng))
+
+(define (default-minetest)
+  (module-ref (resolve-interface '(gnu packages games)) 'minetest))
+
+(define (default-xvfb-run)
+  (module-ref (resolve-interface '(gnu packages xorg)) 'xvfb-run))
+
+(define %minetest-build-system-modules
+  ;; Build-side modules imported by default.
+  `((guix build minetest-build-system)
+    ,@%copy-build-system-modules))
+
+(define %default-modules
+  ;; Modules in scope in the build-side environment.
+  '((guix build gnu-build-system)
+    (guix build minetest-build-system)
+    (guix build utils)))
+
+(define (standard-minetest-packages)
+  "Return the list of (NAME PACKAGE OUTPUT) or (NAME PACKAGE) tuples of
+standard packages used as implicit inputs of the Minetest build system."
+  `(("xvfb-run" ,(default-xvfb-run))
+    ("optipng" ,(default-optipng))
+    ("minetest" ,(default-minetest))
+    ,@(filter (lambda (input)
+                (member (car input)
+                        '("libc" "tar" "gzip" "bzip2" "xz" "locales")))
+              (standard-packages))))
+
+(define* (lower-mod name #:key (implicit-inputs? #t) #:allow-other-keys
+                    #:rest arguments)
+  (define lower (build-system-lower gnu-build-system))
+  (apply lower
+         name
+         (substitute-keyword-arguments arguments
+           ;; minetest-mod-build-system adds implicit inputs by itself,
+           ;; so don't let gnu-build-system add its own implicit inputs
+           ;; as well.
+           ((#:implicit-inputs? implicit-inputs? #t)
+            #f)
+           ((#:implicit-cross-inputs? implicit-cross-inputs? #t)
+            #f)
+           ((#:imported-modules imported-modules %minetest-build-system-modules)
+            imported-modules)
+           ((#:modules modules %default-modules)
+            modules)
+           ((#:phases phases '%standard-phases)
+            phases)
+           ;; Ensure nothing sneaks into the closure.
+           ((#:allowed-references allowed-references '())
+            allowed-references)
+           ;; Add the implicit inputs.
+           ((#:native-inputs native-inputs '())
+            (if implicit-inputs?
+                (append native-inputs (standard-minetest-packages))
+                native-inputs)))))
+
+(define minetest-mod-build-system
+  (build-system
+    (name 'minetest-mod)
+    (description "The build system for minetest mods")
+    (lower lower-mod)))
+
+;;; minetest.scm ends here
diff --git a/guix/build/minetest-build-system.scm b/guix/build/minetest-build-system.scm
new file mode 100644
index 0000000000..b051d9c288
--- /dev/null
+++ b/guix/build/minetest-build-system.scm
@@ -0,0 +1,225 @@
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build minetest-build-system)
+  #:use-module (guix build utils)
+  #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 receive)
+  #:use-module (ice-9 regex)
+  #:use-module ((guix build gnu-build-system) #:prefix gnu:)
+  #:use-module ((guix build copy-build-system) #:prefix copy:)
+  #:export (%standard-phases
+            mod-install-plan minimise-png read-mod-name check))
+
+;; (guix build copy-build-system) does not export 'install'.
+(define copy:install
+  (assoc-ref copy:%standard-phases 'install))
+
+(define (mod-install-plan mod-name)
+  `(("." ,(string-append "share/minetest/mods/" mod-name)
+     ;; Only install files that will actually be used at run time.
+     ;; This can save a little disk space.
+     ;;
+     ;; See <https://github.com/minetest/minetest/blob/master/doc/lua_api.txt>
+     ;; for an incomple list of files that can be found in mods.
+     #:include ("mod.conf" "modpack.conf" "settingtypes.txt" "depends.txt"
+                "description.txt")
+     #:include-regexp (".lua$" ".png$" ".ogg$" ".obj$" ".b3d$" ".tr$"
+                       ".mts$"))))
+
+(define* (guess-mod-name #:key inputs #:allow-other-keys)
+  "Try to determine the name of the mod or modpack that is being built.
+If it is unknown, make an educated guess."
+  ;; Minetest doesn't care about the directory names in "share/minetest/mods"
+  ;; so there is no technical problem if the directory names don't match
+  ;; the mod names.  The directory can appear in the GUI if the modpack
+  ;; doesn't have the 'name' set though, so try to make the guess.
+  (define (guess)
+    (let* ((source (assoc-ref inputs "source"))
+           (file-name (basename source))
+           ;; The "minetest-" prefix is not informative, so strip it.
+           (file-name (if (string-prefix? "minetest-" file-name)
+                          (substring file-name (string-length "minetest-"))
+                          file-name))
+           ;; Strip "-checkout" suffixes of git checkouts.
+           (file-name (if (string-suffix? "-checkout" file-name)
+                          (substring file-name
+                                     0
+                                     (- (string-length file-name)
+                                        (string-length "-minetest")))
+                          file-name))
+           (first-dot (string-index file-name #\.))
+           ;; If the source code is in an archive (.tar.gz, .zip, ...),
+           ;; strip the extension.
+           (file-name (if first-dot
+                          (substring file-name 0 first-dot)
+                          file-name)))
+      (format (current-error-port)
+              "warning: the modpack ~a did not set 'name' in 'modpack.conf'~%"
+              file-name)
+      file-name))
+  (cond ((file-exists? "mod.conf")
+         (read-mod-name "mod.conf"))
+        ((file-exists? "modpack.conf")
+         (read-mod-name "modpack.conf" guess))
+        (#t (guess))))
+
+(define* (install #:key inputs #:allow-other-keys #:rest arguments)
+  (apply copy:install
+         #:install-plan (mod-install-plan (apply guess-mod-name arguments))
+         arguments))
+
+(define %png-magic-bytes
+  ;; Magic bytes of PNG images, see ‘5.2 PNG signatures’ in
+  ;; ‘Portable Network Graphics (PNG) Specification (Second Edition)’
+  ;; on <https://www.w3.org/TR/PNG/>.
+  #vu8(137 80 78 71 13 10 26 10))
+
+(define png-file?
+  ((@@ (guix build utils) file-header-match) %png-magic-bytes))
+
+(define* (minimise-png #:key inputs native-inputs #:allow-other-keys)
+  "Minimise PNG images found in the working directory."
+  (define optipng (which "optipng"))
+  (define (optimise image)
+    (format #t "Optimising ~a~%" image)
+    (make-file-writable (dirname image))
+    (make-file-writable image)
+    (define old-size (stat:size (stat image)))
+    ;; The mod "technic" has a file "technic_music_player_top.png" that
+    ;; actually is a JPEG file, see
+    ;; <https://github.com/minetest-mods/technic/issues/590>.
+    (if (png-file? image)
+        (invoke optipng "-o4" "-quiet" image)
+        (format #t "warning: skipping ~a because it's not actually a PNG image~%"
+                image))
+    (define new-size (stat:size (stat image)))
+    (values old-size new-size))
+  (define files (find-files "." ".png$"))
+  (let loop ((total-old-size 0)
+             (total-new-size 0)
+             (images (find-files "." ".png$")))
+    (cond ((pair? images)
+           (receive (old-size new-size)
+               (optimise (car images))
+             (loop (+ total-old-size old-size)
+                   (+ total-new-size new-size)
+                   (cdr images))))
+          ((= total-old-size 0)
+           (format #t "There were no PNG images to minimise."))
+          (#t
+           (format #t "Minimisation reduced size of images by ~,2f% (~,2f MiB to ~,2f MiB)~%"
+                   (* 100.0 (- 1 (/ total-new-size total-old-size)))
+                   (/ total-old-size (expt 1024 2))
+                   (/ total-new-size (expt 1024 2)))))))
+
+(define name-regexp (make-regexp "^name[ ]*=(.+)$"))
+
+(define* (read-mod-name mod.conf #:optional not-found)
+  "Read the name of a mod from MOD.CONF.  If MOD.CONF
+does not have a name field and NOT-FOUND is #false, raise an
+error.  If NOT-FOUND is TRUE, call NOT-FOUND instead."
+  (call-with-input-file mod.conf
+    (lambda (port)
+      (let loop ()
+        (define line (read-line port))
+        (if (eof-object? line)
+            (if not-found
+                (not-found)
+                (error "~a does not have a 'name' field" mod.conf))
+            (let ((match (regexp-exec name-regexp line)))
+              (if (regexp-match? match)
+                  (string-trim-both (match:substring match 1) #\ )
+                  (loop))))))))
+
+(define* (check #:key outputs tests? #:allow-other-keys)
+  "Test whether the mod loads.  The mod must first be installed first."
+  (define (all-mod-names directories)
+    (append-map
+     (lambda (directory)
+       (map read-mod-name (find-files directory "mod.conf")))
+     directories))
+  (when tests?
+    (mkdir "guix_testworld")
+    ;; Add the mod to the mod search path, such that Minetest can find it.
+    (setenv "MINETEST_MOD_PATH"
+            (list->search-path-as-string
+             (cons
+              (string-append (assoc-ref outputs "out") "/share/minetest/mods")
+              (search-path-as-string->list
+               (or (getenv "MINETEST_MOD_PATH") "")))
+             ":"))
+    (with-directory-excursion "guix_testworld"
+      (setenv "HOME" (getcwd))
+      ;; Create a world in which all mods are loaded.
+      (call-with-output-file "world.mt"
+        (lambda (port)
+          (display
+           "gameid = minetest
+world_name = guix_testworld
+backend = sqlite3
+player_backend = sqlite3
+auth_backend = sqlite3
+" port)
+          (for-each
+           (lambda (mod)
+             (format port "load_mod_~a = true~%" mod))
+           (all-mod-names (search-path-as-string->list
+                           (getenv "MINETEST_MOD_PATH"))))))
+      (receive (port pid)
+          ((@@ (guix build utils) open-pipe-with-stderr)
+           "xvfb-run" "--" "minetest" "--info" "--world" "." "--go")
+        (format #t "Started Minetest with all mods loaded for testing~%")
+        ;; Scan the output for error messages.
+        ;; When the player has joined the server, stop minetest.
+        (define (error? line)
+          (and (string? line)
+               (string-contains line ": ERROR[")))
+        (define (stop? line)
+          (and (string? line)
+               (string-contains line "ACTION[Server]: singleplayer [127.0.0.1] joins game.")))
+        (let loop ()
+          (match (read-line port)
+            ((? error? line)
+             (error "minetest raised an error: ~a" line))
+            ((? stop?)
+             (kill pid SIGINT)
+             (close-port port)
+             (waitpid pid))
+            ((? string? line)
+             (display line)
+             (newline)
+             (loop))
+            ((? eof-object?)
+             (error "minetest didn't start"))))))))
+
+(define %standard-phases
+  (modify-phases gnu:%standard-phases
+    (delete 'bootstrap)
+    (delete 'configure)
+    (add-before 'build 'minimise-png minimise-png)
+    (delete 'build)
+    (delete 'check)
+    (replace 'install install)
+    ;; The 'check' phase requires the mod to be installed,
+    ;; so move the 'check' phase after the 'install' phase.
+    (add-after 'install 'check check)))
+
+;;; minetest-build-system.scm ends here
-- 
2.32.0
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYQv4hRccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7qirAP4y0BJ+gayUfa13V0P/UyMnd2LZ
vpUoYbOnpIJEPrqFjAD/Q57U2qHY19AcYGpQxshSM5UxkPO1G9GA5qdifNDEoQA=
=I2NP
-----END PGP SIGNATURE-----


A
A
Andrew Ward wrote on 5 Aug 2021 14:46
[PATCH 00/20] Add minetest mods
(address . 49828@debbugs.gnu.org)
68444560-b887-4c0c-af36-7f8cc609eda9@email.android.com
Attachment: file
L
L
Leo Prikler wrote on 5 Aug 2021 17:15
Re: [PATCH 05/20] build-system: minetest: Don't retain references to "bash-minimal".
96a038c941308c8ec427d4b37bb333b32a7d0204.camel@student.tugraz.at
Am Donnerstag, den 05.08.2021, 16:41 +0200 schrieb Maxime Devos:
Toggle quote (27 lines)
> Leo Prikler schreef op do 05-08-2021 om 15:42 [+0200]:
> > Am Donnerstag, den 05.08.2021, 15:16 +0200 schrieb Maxime Devos:
> > > [...]
> > > > > +(define* (install #:key inputs #:allow-other-keys #:rest
> > > > > arguments)
> > > > > + (apply (@@ (guix build copy-build-system) install)
> > > > > + #:install-plan (mod-install-plan (apply guess-mod-
> > > > > name
> > > > > arguments))
> > > > > + arguments))
> > > > @@ is a code smell, as far as Guix is concerned. Rather import
> > > > copy-build-system with the copy: prefix.
> > >
> > > 'copy-build-system' does not export 'install', so I have to use
> > > '@@'
> > > here.
> > > Modifying 'copy-build-system' to export 'install' would
> > > presumably
> > > entail
> > > a many rebuilds.
> > I think the thing that's usually done here is fetching through
> > %standard-phases.
> > I.e. (define copy:install (assoc-ref copy-build-system:%standard-
> > phases
> > 'install))
>
> Done.
LGTM.

Toggle quote (11 lines)
> > > > > +(define png-file?
> > > > > + ((@@ (guix build utils) file-header-match) %png-magic-
> > > > > bytes))
> > > > Likewise import (guix build utils) directly.
> > >
> > > Likewise.
> > In that case fine, but make a note to move the variable and
> > procedure
> > over to (guix build utils).
>
> I made a note.
I'm not seeing it, did you send the right file?

Toggle quote (19 lines)
> > The new lower-mod mostly LGTM, but
> > > + ;; Mods are architecture-independent.
> > > + ((#:target target #f) #f)
> > should be `target' imho. What if the mod e.g. actually builds a
> > shared
> > object and somehow uses Lua's very dynamic FFI to load it? (Even
> > if
> > that's not currently possible, it might be in the future). Setting
> > it
> > to #f by default OTOH sounds very reasonable to me.
>
> 'target' is set by 'make-bag' in (guix build-system), so if the code
> above is made
>
> ((#:target target #f) target)
>
> then #:target will always be set to some triplet. Mostly harmless,
> but a bit a waste of disk space since this leads to (slightly)
> different derivations depending on the value of "target".
I think deduplication should take care of that, but yeah.

Toggle quote (7 lines)
> I don't think any mods use Lua's FFI, but some mods use 'os.execute',
> which takes a file name referring to an executable, which might need
> to be absolutised in Guix, and therefore some mods are architecture-
> dependent.
>
> It dropped the ((#:target target #f) #f). I noticed "#:implicit-
> cross-inputs?" wasn't set to #f. That has been corrected as well.
Good catch.

Since my only remaining complaint is somewhat minor, you don't need to
resend this patch; I'll have a look at the importer later (or maybe
someone else gets to do that in between), but I won't cover the package
definitions for the mods. If they work for you and you checked the
licenses, they're probably going to be fine.

Greetings
L
L
Leo Prikler wrote on 5 Aug 2021 18:41
Re: [PATCH 06/20] guix: Add ContentDB importer.
74fcdc1aec1e92afec81c84930cff6ac831f72b1.camel@student.tugraz.at
Hi,

Am Montag, den 02.08.2021, 17:50 +0200 schrieb Maxime Devos:
Toggle quote (30 lines)
> * guix/import/contentdb.scm: New file.
> * guix/scripts/import/contentdb.scm: New file.
> * tests/contentdb.scm: New file.
> * Makefile.am (MODULES, SCM_TESTS): Register them.
> * po/guix/POTFILES.in: Likewise.
> * doc/guix.texi (Invoking guix import): Document it.
> [...]
> diff --git a/doc/guix.texi b/doc/guix.texi
> index 43c248234d..d06c9b73c5 100644
> --- a/doc/guix.texi
> +++ b/doc/guix.texi
> @@ -11313,6 +11313,31 @@ and generate package expressions for all
> those packages that are not yet
> in Guix.
> @end table
>
> +@item contentdb
> +@cindex ContentDB
> +Import metadata from @uref{https://content.minetest.net, ContentDB}.
> +Information is taken from the JSON-formatted metadata provided
> through
> +@uref{https://content.minetest.net/help/api/, ContentDB's API} and
> +includes most relevant information, including dependencies. There
> are
> +some caveats, however. The license information on ContentDB does
> not
> +distinguish between GPLvN-only and GPLvN-or-later. The commit id is
> +sometimes missing. The descriptions are in the Markdown format, but
> +Guix uses Texinfo instead. Texture packs and subgames are
> unsupported.
What is the "commit id"? Is it the hash? A tag? Anything that
resolves to a commit?

Also, since ContentDB sounds fairly generic (a database of content?),
perhaps we ought to call this the "minetest" importer instead?

Toggle quote (28 lines)
> [...]
> +;; The ContentDB API is documented at
> +;; <https://content.minetest.net>;.
> +
> +(define %contentdb-api
> + (make-parameter "https://content.minetest.net/api/"))
> +
> +(define (string-or-false x)
> + (and (string? x) x))
> +
> +(define (natural-or-false x)
> + (and (exact-integer? x) (>= x 0) x))
> +
> +;; Descriptions on ContentDB use carriage returns, but Guix doesn't.
> +(define (delete-cr text)
> + (string-delete #\cr text))
> +
> +;; Minetest package.
> +;;
> +;; API endpoint: /packages/AUTHOR/NAME/
> +(define-json-mapping <package> make-package package?
> + json->package
> + (author package-author) ; string
> + (creation-date package-creation-date ; string
> + "created_at")
> + (downloads package-downloads) ; integer
> + (forums package-forums "forums" natural-or-false) ;
> natural | #f
This comment and some others like it seem to simply be repeating
already present information. Is there a use for them? Should we
instead provide a third argument on every field to verify/enforce the
type?
Toggle quote (43 lines)
> + (issue-tracker package-issue-tracker "issue_tracker") ; string
> + (license package-license) ; string
> + (long-description package-long-description "long_description") ;
> string
> + (maintainers package-maintainers ; list of strings
> + "maintainers" vector->list)
> + (media-license package-media-license "media_license") ; string
> + (name package-name) ; string
> + (provides package-provides ; list of strings
> + "provides" vector->list)
> + (release package-release) ; integer
> + (repository package-repository "repo" string-or-false) ;
> string | #f
> + (score package-score) ; flonum
> + (screenshots package-screenshots "screenshots" vector->list)
> ; list of strings
> + (short-description package-short-description "short_description")
> ; string
> + (state package-state) ; string
> + (tags package-tags "tags" vector->list) ; list of
> strings
> + (thumbnail package-thumbnail) ; string
> + (title package-title) ; string
> + (type package-type) ; string
> + (url package-url) ; string
> + (website package-website "website" string-or-false)) ;
> string | #f
> +
> +(define-json-mapping <release> make-release release?
> + json->release
> + (commit release-commit "commit" string-or-false) ;
> string | #f
> + (downloads release-downloads) ; integer
> + (id release-id) ; integer
> + (max-minetest-version release-max-minetest-version) ; string | #f
> + (min-minetest-version release-min-minetest-version) ; string | #f
> + (release-date release-data) ; string
> + (title release-title) ; string
> + (url release-url)) ; string
> +
> +(define-json-mapping <dependency> make-dependency dependency?
> + json->dependency
> + (optional? dependency-optional? "is_optional") ; #t | #f
Also known as "boolean".
Toggle quote (11 lines)
> + (name dependency-name) ; string
> + (packages dependency-packages "packages" vector->list)) ; list of
> strings
> +
> +(define (contentdb-fetch author name)
> + "Return a <package> record for package NAME by AUTHOR, or #f on
> failure."
> + (and=> (json-fetch
> + (string-append (%contentdb-api) "packages/" author "/"
> name "/"))
> + json->package))
Is there a reason for author and name to be separate keys? For me it
makes more sense to take AUTHOR/NAME as a singular search string from
users and then perform queries based on that. If ContentDB allows
searching, we might also resolve NAME to a singular package where
possible and otherwise error out, telling the user to choose one.

Toggle quote (29 lines)
> [...]
> +
> +(define (important-dependencies dependencies author name)
> + (define dependency-list
> + (assoc-ref dependencies (string-append author "/" name)))
> + (filter-map
> + (lambda (dependency)
> + (and (not (dependency-optional? dependency))
> + ;; "default" must be provided by the 'subgame' in use
> + ;; and does not refer to a specific minetest mod.
> + ;; "doors", "bucket" ... are provided by the default
> minetest
> + ;; subgame.
> + (not (member (dependency-name dependency)
> + '("default" "doors" "beds" "bucket" "doors"
> "farming"
> + "flowers" "stairs" "xpanes")))
> + ;; Dependencies often have only one implementation.
> + (let* ((/name (string-append "/" (dependency-name
> dependency)))
> + (likewise-named-implementations
> + (filter (cut string-suffix? /name <>)
> + (dependency-packages dependency)))
> + (implementation
> + (and (not (null? likewise-named-implementations))
> + (first likewise-named-implementations))))
> + (and implementation
> + (apply cons (string-split implementation #\/))))))
> + dependency-list))
What exactly does the likewise-named-implementations bit do here?

Toggle quote (5 lines)
> +(define (contentdb-recursive-import author name)
> + ;; recursive-import expects upstream package names to be strings,
> + ;; so do some conversions.
> + (define (split-author/name author/name)
> + (string-split author/name #\/))
+1 for my author/name splitting, as it's already required for recursive
imports.
Toggle quote (40 lines)
> + (define (author+name->author/name author+name)
> + (string-append (car author+name) "/" (cdr author+name)))
> + (define* (contentdb->guix-package* author/name #:key repo version)
> + (receive (package . maybe-dependencies)
> + (apply contentdb->guix-package (split-author/name
> author/name))
> + (and package
> + (receive (dependencies)
> + (apply values maybe-dependencies)
> + (values package
> + (map author+name->author/name
> dependencies))))))
> + (recursive-import (author+name->author/name (cons author name))
> + #:repo->guix-package contentdb->guix-package*
> + #:guix-name
> + (lambda (author/name)
> + (contentdb->package-name
> + (second (split-author/name author/name))))))
> +
> +;; A list of license names is available at
> +;; <https://content.minetest.net/api/licenses/>;.
> +(define (string->license str)
> + "Convert the string STR into a license object."
> + (match str
> + ("GPLv3" license:gpl3)
> + ("GPLv2" license:gpl2)
> + ("ISC" license:isc)
> + ;; "MIT" means the Expat license on ContentDB,
> + ;; see <
> https://github.com/minetest/contentdb/issues/326#issuecomment-890143784>
> ;.
> + ("MIT" license:expat)
> + ("CC BY-SA 3.0" license:cc-by-sa3.0)
> + ("CC BY-SA 4.0" license:cc-by-sa4.0)
> + ("LGPLv2.1" license:lgpl2.1)
> + ("LGPLv3" license:lgpl3)
> + ("MPL 2.0" license:mpl2.0)
> + ("ZLib" license:zlib)
> + ("Unlicense" license:unlicense)
> + (_ #f)))
The link mentions, that ContentDB now supports all SPDX identifiers.
Do we have a SPDX->Guix converter lying around in some other importer
that we could use as default case here (especially w.r.t. "or later")

WDYT?
M
M
Maxime Devos wrote on 5 Aug 2021 23:10
Re: [bug#49828] [PATCH 00/20] Add minetest mods
12b8c0746b9db7d9e744ad350aa8e1b37e80ad2a.camel@telenet.be
Hi,

Andrew Ward schreef op do 05-08-2021 om 13:46 [+0100]:
Toggle quote (7 lines)
> Hi, I'm a Minetest core dev and the creator of ContentDB (the service the powers
> the built-in mod/game/etc manager in Minetest).
>
> It's very annoying when distros include mods but then never update them.
> It causes confusion with users, as mods change over time and there can be breakages.
> So, I'd like to make sure that these packages are kept in sync with ContentDB.

When the mod is being ‘built’ in Guix, the 'check' phase will start Minetest
with a new world where the mod and its dependencies are activated. It is verified
there are no errors in the log. That should help with detecting breakage.
It actually detected some breakage: ".mts" files were not being installed, leading
to errors at load time. This is fixed now.

To keep in sync, there is "guix refresh minetest-MODA minetest-MODB ..." which
can automatically upgrade packages to the latest version. It currently doesn't
know about ContentDB however, so it has to fall-back to the generic updaters
which do not appear to work well with Minetest mods:

gnu/packages/minetest.scm:345:13: 1.3 is already the latest version of minetest-worldedit
gnu/packages/minetest.scm:365:2: warning: no updater for minetest-unifieddyes
gnu/packages/minetest.scm:173:2: warning: no updater for minetest-mobs
gnu/packages/minetest.scm:143:13: minetest-mesecons would be upgraded from 1.2.1-0.db58797 to 2017.03.05
^ this is actually a downgrade IIRC
gnu/packages/minetest.scm:227:2: warning: no updater for minetest-pipeworks

Hopefully that can be improved in the future (maybe keep the "release date" in
the 'properties' field of the Guix package and use that to determine if an update
is available?).

Toggle quote (4 lines)
> ContentDB also provides two other features: it only provides you with versions of
> mods/games that support your Minetest version. It also handles dependencies, so I
> hope this is set up correctly.

The ContentDB->guix importer handles dependencies. It reads the dependencies
from ContentDB (with the /api/packages/AUTHOR/NAME/dependencies/ API) and
adds the hard dependencies to 'propagated-inputs', so "minetest-mobs-animal"
has "minetest-mobs" in its 'propagated-inputs'. See also the 'check' phase
mentioned above.

Toggle quote (4 lines)
> If you are disabling ContentDB completely, you should have the vast majority of mods,
> games, and texture packs available - especially all the hard dependencies needed, as
> not having these will cause load issues. Minetest is all about customisation.

In the original patch series I sent, ContentDB was indeed disabled completely when
guix-installed mods are present. But with the revised patch (using the MINETEST_MOD_PATH
longer the case --- ContentDB and Guix can be used together.

I tested installing mods with both Minetest's built-in installer and guix. They can
be used together.

Toggle quote (4 lines)
> I recommend adding a button and core.open_url call to the guix dialog that replaces
> the content store. It should link to a page describing how to install mods using guix,
> and how to allow use of ContentDB again if the user desires

ContentDB and mods installed with guix can be used together with the revised patches.
It would be useful though to add some kind of dialog or something, telling the user
that mods can be installed with guix, and how to do so.

Toggle quote (2 lines)
> Anyway, sorry for being critical - it's great to see more distro support and integration

Greetings,
Maxime (aka maximed aka emixa-d).
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYQxT1RccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7hH5AP0abbCPcvJAOeEqQE8r094M/CTB
AGxqiqRcFjgawb1FPAEAxgjkosWm1zDCZ8tDT6YfodqUe9zrSmNo0jtY156Vugo=
=dI9n
-----END PGP SIGNATURE-----


M
M
Maxime Devos wrote on 7 Aug 2021 20:31
Re: [PATCH 06/20] guix: Add ContentDB importer.
e534dbff9977126289db0c0eb6cb1281613a877b.camel@telenet.be
Leo Prikler schreef op do 05-08-2021 om 18:41 [+0200]:
Toggle quote (31 lines)
> Hi,
>
> Am Montag, den 02.08.2021, 17:50 +0200 schrieb Maxime Devos:
> > * guix/import/contentdb.scm: New file.
> > * guix/scripts/import/contentdb.scm: New file.
> > * tests/contentdb.scm: New file.
> > * Makefile.am (MODULES, SCM_TESTS): Register them.
> > * po/guix/POTFILES.in: Likewise.
> > * doc/guix.texi (Invoking guix import): Document it.
> > [...]
> > diff --git a/doc/guix.texi b/doc/guix.texi
> > index 43c248234d..d06c9b73c5 100644
> > --- a/doc/guix.texi
> > +++ b/doc/guix.texi
> > @@ -11313,6 +11313,31 @@ and generate package expressions for all
> > those packages that are not yet
> > in Guix.
> > @end table
> >
> > +@item contentdb
> > +@cindex ContentDB
> > +Import metadata from @uref{https://content.minetest.net, ContentDB}.
> > +Information is taken from the JSON-formatted metadata provided
> > through
> > +@uref{https://content.minetest.net/help/api/, ContentDB's API} and
> > +includes most relevant information, including dependencies. There
> > are
> > +some caveats, however. The license information on ContentDB does
> > not
> > +distinguish between GPLvN-only and GPLvN-or-later.

ContentDB uses SPDX license identifiers now, and distinguishes between
GPL-N-only and GPL-N-or-later, so I adjusted the documentation appropriately.

Toggle quote (7 lines)
> > The commit id is
> > +sometimes missing. The descriptions are in the Markdown format, but
> > +Guix uses Texinfo instead. Texture packs and subgames are
> > unsupported.
> What is the "commit id"? Is it the hash? A tag? Anything that
> resolves to a commit?

It's the SHA-1 of the Git commit. I changes this to ‘the commit's SHA-1’.

Toggle quote (3 lines)
> Also, since ContentDB sounds fairly generic (a database of content?),
> perhaps we ought to call this the "minetest" importer instead?

Technically, minetest has another mod repository as well:
https://bower.minetest.org/. It's unmoderated though, and
https://content.minetest.net has some moderation and seems more
‘official’ (it's integrated in Minetest itself). I replaced
(guix import contentdb) with (guix import minetest), likewise
for (guix script import minetest) and tests/minetest.scm.

Toggle quote (16 lines)
> > +;; Minetest package.
> > +;;
> > +;; API endpoint: /packages/AUTHOR/NAME/
> > +(define-json-mapping <package> make-package package?
> > + json->package
> > + (author package-author) ; string
> > + (creation-date package-creation-date ; string
> > + "created_at")
> > + (downloads package-downloads) ; integer
> > + (forums package-forums "forums" natural-or-false) ;
> > natural | #f
> This comment and some others like it seem to simply be repeating
> already present information. Is there a use for them? Should we
> instead provide a third argument on every field to verify/enforce the
> type?

I first added the ‘; natural-or-false’. I only added the procedure
"natural-false" later. Indeed, ‘; natural-or-false’ is redundant.
I removed the redundant ones in the revised patch.

I don't think there is need to verify types for each field.
Most aren't used by Guix. If a type check would fail, that would
presumably mean the type check guix is incorrect (or not up-to-date).
Except for perhaps a backtrace, ill-typed fields are harmless.

Toggle quote (11 lines)
> > +(define (contentdb-fetch author name)
> > + "Return a <package> record for package NAME by AUTHOR, or #f on
> > failure."
> > + (and=> (json-fetch
> > + (string-append (%contentdb-api) "packages/" author "/"
> > name "/"))
> > + json->package))
> Is there a reason for author and name to be separate keys? For me it
> makes more sense to take AUTHOR/NAME as a singular search string from
> users and then perform queries based on that.

Not really actually, AUTHOR tends to go togehter with NAME except for
some exceptions. I modified the code such that AUTHOR/NAME is a single
string. It simplified code somewhat.

Toggle quote (4 lines)
> If ContentDB allows
> searching, we might also resolve NAME to a singular package where
> possible and otherwise error out, telling the user to choose one.

ContentDB allows searching. I wrote some a procedure 'elaborate-contentdb-name'
used by (guix scripts import contentdb) that resolves "mesecons" to "Jeija/mesecons",
using the search API and added some tests. If there are multiple candidates,
the one with the highest ‘score’ is choosen (alternatively, --sort=downloads can
be used instead).

Toggle quote (16 lines)
> > +(define (important-dependencies dependencies author name)
> > + (define dependency-list
> > + (assoc-ref dependencies (string-append author "/" name)))
> > + (filter-map
> > + (lambda (dependency)
> > + (and (not (dependency-optional? dependency))
> > + ;; "default" must be provided by the 'subgame' in use
> > + ;; and does not refer to a specific minetest mod.
> > + ;; "doors", "bucket" ... are provided by the default
> > minetest
> > + ;; subgame.
> > + (not (member (dependency-name dependency)
> > + '("default" "doors" "beds" "bucket" "doors"
> > "farming"
> > + "flowers" "stairs" "xpanes")))

I tested this some more, and it appears that some mods depend on "dyes",
which is part of the default Minetest game, so I added all the mods
provided by the default (sub?)game. The list began looking a little
long, so I replaced it with a hash table.

Toggle quote (14 lines)
> > + ;; Dependencies often have only one implementation.
> > + (let* ((/name (string-append "/" (dependency-name
> > dependency)))
> > + (likewise-named-implementations
> > + (filter (cut string-suffix? /name <>)
> > + (dependency-packages dependency)))
> > + (implementation
> > + (and (not (null? likewise-named-implementations))
> > + (first likewise-named-implementations))))
> > + (and implementation
> > + (apply cons (string-split implementation #\/))))))
> > + dependency-list))
> What exactly does the likewise-named-implementations bit do here?

The list returned by 'dependency-packages' not only contains the mod
we need, but possibly also various ‘subgames’ that include that mod.
Filtering on '/name' filters out these subgames we don't need.

Also, theoretically another mod could implement the same interface.
The filtering would filter out the alternative implementations.

Anyway, I changes the implementation a bit. It now explicitely
filters out ‘subgames’ and ‘texture packs’ using the ‘package-mod?’
procedure. The resulting list tends to consist of only a single
element. If it consists of multiple, the one with the highest score
(or the one with the highest download count, depending on --sort)
will be choosen (and a warning is printed).

Toggle quote (8 lines)
> > +;; A list of license names is available at
> > +;; <https://content.minetest.net/api/licenses/>;;.
> > +(define (string->license str)
> > + "Convert the string STR into a license object." [...]
> The link mentions, that ContentDB now supports all SPDX identifiers.
> Do we have a SPDX->Guix converter lying around in some other importer
> that we could use as default case here (especially w.r.t. "or later")

There's a a converter in (guix import utils): spdx-string->license.
The old license identifiers appear to be removed, now only SPDX information
is available. I modified the code to use spdx->string-license and removed
string->license.

It turns out it does not recognise GPL-N-only and GPL-N-or-later,
so I added a patch ‘import/utils: Recognise GPL-3.0-or-later and friends.’.

I tried implementing "guix refresh -t minetest ...". It seems to work, but
requires some changes to (guix upstream) that needs some more work, so I left it
out of the revised patch set. The refresher needs to know the author name
(or perform extra HTTP requests), so I added 'upstream-name' the package properties.

The revised patch series is attached. It can also be found at
the latest MINETEST_MOD_PATH patch. I'll make the patch to export more things in
(guix build utils) later (for core-updates).

reetings,
Maxime.
From 292dce14ea4811f1554965d83af5e33687cd00b7 Mon Sep 17 00:00:00 2001
From: Maxime Devos <maximedevos@telenet.be>
Date: Sat, 31 Jul 2021 14:50:32 +0200
Subject: [PATCH 01/20] gnu: minetest: Respect --without-tests.

* gnu/packages/games.scm
(minetest)[arguments]<#:phases>{check}: Use 'tests?' instead
of ',(%current-target-system)'. Remove trailing #t.
---
gnu/packages/games.scm | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)

Toggle diff (23 lines)
diff --git a/gnu/packages/games.scm b/gnu/packages/games.scm
index 8c6b5523f1..3e7086b398 100644
--- a/gnu/packages/games.scm
+++ b/gnu/packages/games.scm
@@ -3590,13 +3590,12 @@ match, cannon keep, and grave-itation pit.")
                      (string-append (getcwd) "/games")) ; for check
              #t))
          (replace 'check
-           (lambda _
+           (lambda* (#:key tests? #:allow-other-keys)
              ;; Thanks to our substitutions, the tests should also run
              ;; when invoked on the target outside of `guix build'.
-             (unless ,(%current-target-system)
+             (when tests?
                (setenv "HOME" "/tmp")
-               (invoke "src/minetest" "--run-unittests"))
-             #t)))))
+               (invoke "src/minetest" "--run-unittests")))))))
     (native-search-paths
      (list (search-path-specification
             (variable "MINETEST_SUBGAME_PATH")
-- 
2.32.0
From 54222f167107e36cb76f93c551aaee0659d17b40 Mon Sep 17 00:00:00 2001
From: Maxime Devos <maximedevos@telenet.be>
Date: Sat, 31 Jul 2021 18:08:44 +0200
Subject: [PATCH 02/20] gnu: minetest: Search for mods in MINETEST_MOD_PATH.

* gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch:
New file.
* gnu/packages/games.scm
(minetest)[source]{patches}: Add it.
(minetest)[native-search-paths]: Add "MINETEST_MOD_PATH".
* gnu/local.mk (dist_patch_DATA): Add the patch.
---
gnu/local.mk | 1 +
gnu/packages/games.scm | 8 +-
...vironment-variable-MINETEST_MOD_PATH.patch | 156 ++++++++++++++++++
3 files changed, 164 insertions(+), 1 deletion(-)
create mode 100644 gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch

Toggle diff (202 lines)
diff --git a/gnu/local.mk b/gnu/local.mk
index c80a9af78c..d96d4e3dbc 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -801,6 +801,7 @@ dist_patch_DATA =						\
   %D%/packages/patches/abseil-cpp-fix-gtest.patch		\
   %D%/packages/patches/abseil-cpp-fix-strerror_test.patch	\
   %D%/packages/patches/adb-add-libraries.patch			\
+  %D%/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch	\
   %D%/packages/patches/aegis-constness-error.patch         	\
   %D%/packages/patches/aegis-perl-tempdir1.patch           	\
   %D%/packages/patches/aegis-perl-tempdir2.patch           	\
diff --git a/gnu/packages/games.scm b/gnu/packages/games.scm
index 3e7086b398..48d46a0379 100644
--- a/gnu/packages/games.scm
+++ b/gnu/packages/games.scm
@@ -3553,6 +3553,9 @@ match, cannon keep, and grave-itation pit.")
                (base32
                 "062ilb7s377q3hwfhl8q06vvcw2raydz5ljzlzwy2dmyzmdcndb8"))
               (modules '((guix build utils)))
+              (patches
+               (search-patches
+                "Add-environment-variable-MINETEST_MOD_PATH.patch"))
               (snippet
                '(begin
                   ;; Delete bundled libraries.
@@ -3599,7 +3602,10 @@ match, cannon keep, and grave-itation pit.")
     (native-search-paths
      (list (search-path-specification
             (variable "MINETEST_SUBGAME_PATH")
-            (files '("share/minetest/games")))))
+            (files '("share/minetest/games")))
+           (search-path-specification
+            (variable "MINETEST_MOD_PATH")
+            (files '("share/minetest/mods")))))
     (native-inputs
      `(("pkg-config" ,pkg-config)))
     (inputs
diff --git a/gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch b/gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch
new file mode 100644
index 0000000000..a74034a2c5
--- /dev/null
+++ b/gnu/packages/patches/Add-environment-variable-MINETEST_MOD_PATH.patch
@@ -0,0 +1,156 @@
+From d10ea2ad7efc2364a8a2007b4c6d3e85511e2f84 Mon Sep 17 00:00:00 2001
+From: Maxime Devos <maximedevos@telenet.be>
+Date: Tue, 3 Aug 2021 01:00:23 +0200
+Subject: [PATCH] Add environment variable MINETEST_MOD_PATH
+
+This adds an environment variable MINETEST_MOD_PATH.
+When it exists, Minetest will look there for mods
+in addition to ~/.minetest/mods/.  Mods can still be
+installed to ~/.minetest/mods/ with the built-in installer.
+
+With thanks to Leo Prikler.
+---
+ builtin/mainmenu/pkgmgr.lua       |  7 +++----
+ doc/menu_lua_api.txt              |  8 +++++++-
+ src/content/subgames.cpp          | 11 +++++++++++
+ src/script/lua_api/l_mainmenu.cpp | 23 +++++++++++++++++++++++
+ src/script/lua_api/l_mainmenu.h   |  2 ++
+ 5 files changed, 46 insertions(+), 5 deletions(-)
+
+diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua
+index 787936e31..d8fba0ebe 100644
+--- a/builtin/mainmenu/pkgmgr.lua
++++ b/builtin/mainmenu/pkgmgr.lua
+@@ -682,10 +682,9 @@ function pkgmgr.preparemodlist(data)
+ 	local game_mods = {}
+ 
+ 	--read global mods
+-	local modpath = core.get_modpath()
+-
+-	if modpath ~= nil and
+-		modpath ~= "" then
++	local modpaths = core.get_modpaths()
++	--XXX what was ‘modpath ~= ""’ and ‘modpath ~= nil’ for?
++	for _,modpath in ipairs(modpaths) do
+ 		get_mods(modpath,global_mods)
+ 	end
+ 
+diff --git a/doc/menu_lua_api.txt b/doc/menu_lua_api.txt
+index b3975bc1d..132444b14 100644
+--- a/doc/menu_lua_api.txt
++++ b/doc/menu_lua_api.txt
+@@ -218,7 +218,13 @@ Package - content which is downloadable from the content db, may or may not be i
+     * returns path to global user data,
+       the directory that contains user-provided mods, worlds, games, and texture packs.
+ * core.get_modpath() (possible in async calls)
+-    * returns path to global modpath
++    * returns path to global modpath, where mods can be installed
++* core.get_modpaths() (possible in async calls)
++    * returns list of paths to global modpaths, where mods have been installed
++
++      The difference with "core.get_modpath" is that no mods should be installed in these
++      directories by Minetest -- they might be read-only.
++
+ * core.get_clientmodpath() (possible in async calls)
+     * returns path to global client-side modpath
+ * core.get_gamepath() (possible in async calls)
+diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp
+index e9dc609b0..d73f95a1f 100644
+--- a/src/content/subgames.cpp
++++ b/src/content/subgames.cpp
+@@ -61,6 +61,12 @@ std::string getSubgamePathEnv()
+ 	return subgame_path ? std::string(subgame_path) : "";
+ }
+ 
++std::string getModPathEnv()
++{
++	char *mod_path = getenv("MINETEST_MOD_PATH");
++	return mod_path ? std::string(mod_path) : "";
++}
++
+ SubgameSpec findSubgame(const std::string &id)
+ {
+ 	if (id.empty())
+@@ -110,6 +116,11 @@ SubgameSpec findSubgame(const std::string &id)
+ 	std::set<std::string> mods_paths;
+ 	if (!user_game)
+ 		mods_paths.insert(share + DIR_DELIM + "mods");
++
++	Strfnd mod_search_paths(getModPathEnv());
++	while (!mod_search_paths.at_end())
++		mods_paths.insert(mod_search_paths.next(PATH_DELIM));
++
+ 	if (user != share || user_game)
+ 		mods_paths.insert(user + DIR_DELIM + "mods");
+ 
+diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp
+index 3e9709bde..903ac3a22 100644
+--- a/src/script/lua_api/l_mainmenu.cpp
++++ b/src/script/lua_api/l_mainmenu.cpp
+@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
+ #include "lua_api/l_internal.h"
+ #include "common/c_content.h"
+ #include "cpp_api/s_async.h"
++#include "util/strfnd.h"
+ #include "gui/guiEngine.h"
+ #include "gui/guiMainMenu.h"
+ #include "gui/guiKeyChangeMenu.h"
+@@ -502,6 +503,26 @@ int ModApiMainMenu::l_get_modpath(lua_State *L)
+ 	return 1;
+ }
+ 
++/******************************************************************************/
++int ModApiMainMenu::l_get_modpaths(lua_State *L)
++{
++	const char *c_modpath = getenv("MINETEST_MOD_PATH");
++	if (c_modpath == NULL)
++		c_modpath = "";
++	int index = 1;
++	lua_newtable(L);
++	Strfnd mod_search_paths{std::string(c_modpath)};
++	while (!mod_search_paths.at_end()) {
++		std::string component = mod_search_paths.next(PATH_DELIM);
++		lua_pushstring(L, component.c_str());
++		lua_rawseti(L, -2, index);
++		index++;
++	}
++	ModApiMainMenu::l_get_modpath(L);
++	lua_rawseti(L, -2, index);
++	return 1;
++}
++
+ /******************************************************************************/
+ int ModApiMainMenu::l_get_clientmodpath(lua_State *L)
+ {
+@@ -949,6 +970,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
+ 	API_FCT(get_mapgen_names);
+ 	API_FCT(get_user_path);
+ 	API_FCT(get_modpath);
++	API_FCT(get_modpaths);
+ 	API_FCT(get_clientmodpath);
+ 	API_FCT(get_gamepath);
+ 	API_FCT(get_texturepath);
+@@ -983,6 +1005,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top)
+ 	API_FCT(get_mapgen_names);
+ 	API_FCT(get_user_path);
+ 	API_FCT(get_modpath);
++	API_FCT(get_modpaths);
+ 	API_FCT(get_clientmodpath);
+ 	API_FCT(get_gamepath);
+ 	API_FCT(get_texturepath);
+diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h
+index 33ac9e721..a6a54a2cb 100644
+--- a/src/script/lua_api/l_mainmenu.h
++++ b/src/script/lua_api/l_mainmenu.h
+@@ -112,6 +112,8 @@ class ModApiMainMenu: public ModApiBase
+ 
+ 	static int l_get_modpath(lua_State *L);
+ 
++	static int l_get_modpaths(lua_State *L);
++
+ 	static int l_get_clientmodpath(lua_State *L);
+ 
+ 	static int l_get_gamepath(lua_State *L);
+-- 
+2.32.0
+
-- 
2.32.0
From f5148ad853b113db84634912c3eaa936d689eb22 Mon Sep 17 00:00:00 2001
From: Maxime Devos <maximedevos@telenet.be>
Date: Sat, 31 Jul 2021 18:32:50 +0200
Subject: [PATCH 03/20] gnu: minetest: New package module.

Aside from the 'minetest-topic' procedure which will be used
for the 'home-page' field of some packages, this module is
currently empty. The 'contentdb' importer defined in the
following patches will be used to populate this module.

* gnu/packages/minetest.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
---
gnu/local.mk | 1 +
gnu/packages/minetest.scm | 26 ++++++++++++++++++++++++++
2 files changed, 27 insertions(+)
create mode 100644 gnu/packages/minetest.scm

Toggle diff (46 lines)
diff --git a/gnu/local.mk b/gnu/local.mk
index d96d4e3dbc..5de08b1b09 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -383,6 +383,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/packages/mercury.scm			\
   %D%/packages/mes.scm				\
   %D%/packages/messaging.scm			\
+  %D%/packages/minetest.scm			\
   %D%/packages/mingw.scm			\
   %D%/packages/microcom.scm			\
   %D%/packages/moe.scm				\
diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
new file mode 100644
index 0000000000..f8aca3005c
--- /dev/null
+++ b/gnu/packages/minetest.scm
@@ -0,0 +1,26 @@
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+(define-module (gnu packages minetest)
+  #:use-module (guix packages)
+  #:use-module (guix git-download)
+  #:use-module (guix build-system minetest)
+  #:use-module ((guix licenses) #:prefix license:))
+
+(define-public (minetest-topic topic-id)
+  "Return an URL (as a string) pointing to the forum topic with
+numeric identifier TOPIC-ID on the official Minetest forums."
+  (string-append "https://forum.minetest.net/viewtopic.php?t="
+                 (number->string topic-id)))
-- 
2.32.0
From 93aa8e1976e762d30be70aef6d5c50b1d06ca4be Mon Sep 17 00:00:00 2001
From: Maxime Devos <maximedevos@telenet.be>
Date: Sat, 31 Jul 2021 13:52:39 +0200
Subject: [PATCH 04/20] build-system: Add 'minetest-mod-build-system'.

* guix/build-system/minetest.scm: New module.
* guix/build/minetest-build-system.scm: Likewise.
* Makefile.am (MODULES): Add them.
* doc/guix.texi (Build Systems): Document 'minetest-mod-build-system'.
---
Makefile.am | 2 +
doc/guix.texi | 8 +
guix/build-system/minetest.scm | 99 ++++++++++++
guix/build/minetest-build-system.scm | 225 +++++++++++++++++++++++++++
4 files changed, 334 insertions(+)
create mode 100644 guix/build-system/minetest.scm
create mode 100644 guix/build/minetest-build-system.scm

Toggle diff (377 lines)
diff --git a/Makefile.am b/Makefile.am
index d5ec909213..f4439ce93b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -141,6 +141,7 @@ MODULES =					\
   guix/build-system/go.scm			\
   guix/build-system/meson.scm			\
   guix/build-system/minify.scm			\
+  guix/build-system/minetest.scm		\
   guix/build-system/asdf.scm			\
   guix/build-system/copy.scm			\
   guix/build-system/glib-or-gtk.scm		\
@@ -203,6 +204,7 @@ MODULES =					\
   guix/build/gnu-dist.scm			\
   guix/build/guile-build-system.scm		\
   guix/build/maven-build-system.scm		\
+  guix/build/minetest-build-system.scm		\
   guix/build/node-build-system.scm		\
   guix/build/perl-build-system.scm		\
   guix/build/python-build-system.scm		\
diff --git a/doc/guix.texi b/doc/guix.texi
index b3c16e6507..d44ecc2005 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -7895,6 +7895,14 @@ declaration.  Its default value is @code{(default-maven-plugins)} which is
 also exported.
 @end defvr
 
+@defvr {Scheme Variable} minetest-mod-build-system
+This variable is exported by @code{(guix build-system minetest)}.  It
+implements a build procedure for @uref{https://www.minetest.net, Minetest}
+mods, which consists of copying Lua code, images and other resources to
+the location Minetest searches for mods.  The build system also minimises
+PNG images and verifies that Minetest can load the mod without errors.
+@end defvr
+
 @defvr {Scheme Variable} minify-build-system
 This variable is exported by @code{(guix build-system minify)}.  It
 implements a minification procedure for simple JavaScript packages.
diff --git a/guix/build-system/minetest.scm b/guix/build-system/minetest.scm
new file mode 100644
index 0000000000..f33e97559d
--- /dev/null
+++ b/guix/build-system/minetest.scm
@@ -0,0 +1,99 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build-system minetest)
+  #:use-module (guix build-system copy)
+  #:use-module (guix build-system gnu)
+  #:use-module (guix build-system)
+  #:use-module (guix utils)
+  #:export (minetest-mod-build-system))
+
+;;
+;; Build procedure for minetest mods.  This is implemented as an extension
+;; of ‘copy-build-system’.
+;;
+;; Code:
+
+;; Lazily resolve the bindings to avoid circular dependencies.
+(define (default-optipng)
+  ;; Lazily resolve the binding to avoid a circular dependency.
+  (module-ref (resolve-interface '(gnu packages image)) 'optipng))
+
+(define (default-minetest)
+  (module-ref (resolve-interface '(gnu packages games)) 'minetest))
+
+(define (default-xvfb-run)
+  (module-ref (resolve-interface '(gnu packages xorg)) 'xvfb-run))
+
+(define %minetest-build-system-modules
+  ;; Build-side modules imported by default.
+  `((guix build minetest-build-system)
+    ,@%copy-build-system-modules))
+
+(define %default-modules
+  ;; Modules in scope in the build-side environment.
+  '((guix build gnu-build-system)
+    (guix build minetest-build-system)
+    (guix build utils)))
+
+(define (standard-minetest-packages)
+  "Return the list of (NAME PACKAGE OUTPUT) or (NAME PACKAGE) tuples of
+standard packages used as implicit inputs of the Minetest build system."
+  `(("xvfb-run" ,(default-xvfb-run))
+    ("optipng" ,(default-optipng))
+    ("minetest" ,(default-minetest))
+    ,@(filter (lambda (input)
+                (member (car input)
+                        '("libc" "tar" "gzip" "bzip2" "xz" "locales")))
+              (standard-packages))))
+
+(define* (lower-mod name #:key (implicit-inputs? #t) #:allow-other-keys
+                    #:rest arguments)
+  (define lower (build-system-lower gnu-build-system))
+  (apply lower
+         name
+         (substitute-keyword-arguments arguments
+           ;; minetest-mod-build-system adds implicit inputs by itself,
+           ;; so don't let gnu-build-system add its own implicit inputs
+           ;; as well.
+           ((#:implicit-inputs? implicit-inputs? #t)
+            #f)
+           ((#:implicit-cross-inputs? implicit-cross-inputs? #t)
+            #f)
+           ((#:imported-modules imported-modules %minetest-build-system-modules)
+            imported-modules)
+           ((#:modules modules %default-modules)
+            modules)
+           ((#:phases phases '%standard-phases)
+            phases)
+           ;; Ensure nothing sneaks into the closure.
+           ((#:allowed-references allowed-references '())
+            allowed-references)
+           ;; Add the implicit inputs.
+           ((#:native-inputs native-inputs '())
+            (if implicit-inputs?
+                (append native-inputs (standard-minetest-packages))
+                native-inputs)))))
+
+(define minetest-mod-build-system
+  (build-system
+    (name 'minetest-mod)
+    (description "The build system for minetest mods")
+    (lower lower-mod)))
+
+;;; minetest.scm ends here
diff --git a/guix/build/minetest-build-system.scm b/guix/build/minetest-build-system.scm
new file mode 100644
index 0000000000..b051d9c288
--- /dev/null
+++ b/guix/build/minetest-build-system.scm
@@ -0,0 +1,225 @@
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build minetest-build-system)
+  #:use-module (guix build utils)
+  #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 receive)
+  #:use-module (ice-9 regex)
+  #:use-module ((guix build gnu-build-system) #:prefix gnu:)
+  #:use-module ((guix build copy-build-system) #:prefix copy:)
+  #:export (%standard-phases
+            mod-install-plan minimise-png read-mod-name check))
+
+;; (guix build copy-build-system) does not export 'install'.
+(define copy:install
+  (assoc-ref copy:%standard-phases 'install))
+
+(define (mod-install-plan mod-name)
+  `(("." ,(string-append "share/minetest/mods/" mod-name)
+     ;; Only install files that will actually be used at run time.
+     ;; This can save a little disk space.
+     ;;
+     ;; See <https://github.com/minetest/minetest/blob/master/doc/lua_api.txt>
+     ;; for an incomple list of files that can be found in mods.
+     #:include ("mod.conf" "modpack.conf" "settingtypes.txt" "depends.txt"
+                "description.txt")
+     #:include-regexp (".lua$" ".png$" ".ogg$" ".obj$" ".b3d$" ".tr$"
+                       ".mts$"))))
+
+(define* (guess-mod-name #:key inputs #:allow-other-keys)
+  "Try to determine the name of the mod or modpack that is being built.
+If it is unknown, make an educated guess."
+  ;; Minetest doesn't care about the directory names in "share/minetest/mods"
+  ;; so there is no technical problem if the directory names don't match
+  ;; the mod names.  The directory can appear in the GUI if the modpack
+  ;; doesn't have the 'name' set though, so try to make the guess.
+  (define (guess)
+    (let* ((source (assoc-ref inputs "source"))
+           (file-name (basename source))
+           ;; The "minetest-" prefix is not informative, so strip it.
+           (file-name (if (string-prefix? "minetest-" file-name)
+                          (substring file-name (string-length "minetest-"))
+                          file-name))
+           ;; Strip "-checkout" suffixes of git checkouts.
+           (file-name (if (string-suffix? "-checkout" file-name)
+                          (substring file-name
+                                     0
+                                     (- (string-length file-name)
+                                        (string-length "-minetest")))
+                          file-name))
+           (first-dot (string-index file-name #\.))
+           ;; If the source code is in an archive (.tar.gz, .zip, ...),
+           ;; strip the extension.
+           (file-name (if first-dot
+                          (substring file-name 0 first-dot)
+                          file-name)))
+      (format (current-error-port)
+              "warning: the modpack ~a did not set 'name' in 'modpack.conf'~%"
+              file-name)
+      file-name))
+  (cond ((file-exists? "mod.conf")
+         (read-mod-name "mod.conf"))
+        ((file-exists? "modpack.conf")
+         (read-mod-name "modpack.conf" guess))
+        (#t (guess))))
+
+(define* (install #:key inputs #:allow-other-keys #:rest arguments)
+  (apply copy:install
+         #:install-plan (mod-install-plan (apply guess-mod-name arguments))
+         arguments))
+
+(define %png-magic-bytes
+  ;; Magic bytes of PNG images, see ‘5.2 PNG signatures’ in
+  ;; ‘Portable Network Graphics (PNG) Specification (Second Edition)’
+  ;; on <https://www.w3.org/TR/PNG/>.
+  #vu8(137 80 78 71 13 10 26 10))
+
+(define png-file?
+  ((@@ (guix build utils) file-header-match) %png-magic-bytes))
+
+(define* (minimise-png #:key inputs native-inputs #:allow-other-keys)
+  "Minimise PNG images found in the working directory."
+  (define optipng (which "optipng"))
+  (define (optimise image)
+    (format #t "Optimising ~a~%" image)
+    (make-file-writable (dirname image))
+    (make-file-writable image)
+    (define old-size (stat:size (stat image)))
+    ;; The mod "technic" has a file "technic_music_player_top.png" that
+    ;; actually is a JPEG file, see
+    ;; <https://github.com/minetest-mods/technic/issues/590>.
+    (if (png-file? image)
+        (invoke optipng "-o4" "-quiet" image)
+        (format #t "warning: skipping ~a because it's not actually a PNG image~%"
+                image))
+    (define new-size (stat:size (stat image)))
+    (values old-size new-size))
+  (define files (find-files "." ".png$"))
+  (let loop ((total-old-size 0)
+             (total-new-size 0)
+             (images (find-files "." ".png$")))
+    (cond ((pair? images)
+           (receive (old-size new-size)
+               (optimise (car images))
+             (loop (+ total-old-size old-size)
+                   (+ total-new-size new-size)
+                   (cdr images))))
+          ((= total-old-size 0)
+           (format #t "There were no PNG images to minimise."))
+          (#t
+           (format #t "Minimisation reduced size of images by ~,2f% (~,2f MiB to ~,2f MiB)~%"
+                   (* 100.0 (- 1 (/ total-new-size total-old-size)))
+                   (/ total-old-size (expt 1024 2))
+                   (/ total-new-size (expt 1024 2)))))))
+
+(define name-regexp (make-regexp "^name[ ]*=(.+)$"))
+
+(define* (read-mod-name mod.conf #:optional not-found)
+  "Read the name of a mod from MOD.CONF.  If MOD.CONF
+does not have a name field and NOT-FOUND is #false, raise an
+error.  If NOT-FOUND is TRUE, call NOT-FOUND instead."
+  (call-with-input-file mod.conf
+    (lambda (port)
+      (let loop ()
+        (define line (read-line port))
+        (if (eof-object? line)
+            (if not-found
+                (not-found)
+                (error "~a does not have a 'name' field" mod.conf))
+            (let ((match (regexp-exec name-regexp line)))
+              (if (regexp-match? match)
+                  (string-trim-both (match:substring match 1) #\ )
+                  (loop))))))))
+
+(define* (check #:key outputs tests? #:allow-other-keys)
+  "Test whether the mod loads.  The mod must first be installed first."
+  (define (all-mod-names directories)
+    (append-map
+     (lambda (directory)
+       (map read-mod-name (find-files directory "mod.conf")))
+     directories))
+  (when tests?
+    (mkdir "guix_testworld")
+    ;; Add the mod to the mod search path, such that Minetest can find it.
+    (setenv "MINETEST_MOD_PATH"
+            (list->search-path-as-string
+             (cons
+              (string-append (assoc-ref outputs "out") "/share/minetest/mods")
+              (search-path-as-string->list
+               (or (getenv "MINETEST_MOD_PATH") "")))
+             ":"))
+    (with-directory-excursion "guix_testworld"
+      (setenv "HOME" (getcwd))
+      ;; Create a world in which all mods are loaded.
+      (call-with-output-file "world.mt"
+        (lambda (port)
+          (display
+           "gameid = minetest
+world_name = guix_testworld
+backend = sqlite3
+player_backend = sqlite3
+auth_backend = sqlite3
+" port)
+          (for-each
+           (lambda (mod)
+             (format port "load_mod_~a = true~%" mod))
+           (all-mod-names (search-path-as-string->list
+                           (getenv "MINETEST_MOD_PATH"))))))
+      (receive (port pid)
+          ((@@ (guix build utils) open-pipe-with-stderr)
+           "xvfb-run" "--" "minetest" "--info" "--world" "." "--go")
+        (format #t "Started Minetest with all mods loaded for testing~%")
+        ;; Scan the output for error messages.
+        ;; When the player has joined the server, stop minetest.
+        (define (error? line)
+          (and (string? line)
+               (string-contains line ": ERROR[")))
+        (define (stop? line)
+          (and (string? line)
+               (string-contains line "ACTION[Server]: singleplayer [127.0.0.1] joins game.")))
+        (let loop ()
+          (match (read-line port)
+            ((? error? line)
+             (error "minetest raised an error: ~a" line))
+            ((? stop?)
+             (kill pid SIGINT)
+             (close-port port)
+             (waitpid pid))
+            ((? string? line)
+             (display line)
+             (newline)
+             (loop))
+            ((? eof-object?)
+             (error "minetest didn't start"))))))))
+
+(define %standard-phases
+  (modify-phases gnu:%standard-phases
+    (delete 'bootstrap)
+    (delete 'configure)
+    (add-before 'build 'minimise-png minimise-png)
+    (delete 'build)
+    (delete 'check)
+    (replace 'install install)
+    ;; The 'check' phase requires the mod to be installed,
+    ;; so move the 'check' phase after the 'install' phase.
+    (add-after 'install 'check check)))
+
+;;; minetest-build-system.scm ends here
-- 
2.32.0
From 9858d43e51cdfdbf4ca022399469d99f437bf596 Mon Sep 17 00:00:00 2001
From: Maxime Devos <maximedevos@telenet.be>
Date: Thu, 5 Aug 2021 21:00:41 +0200
Subject: [PATCH 05/20] import/utils: Recognise GPL-3.0-or-later and friends.

* guix/import/utils.scm (spdx-string->license): Recognise
GPL-N-only and GPL-N-or-later. Likewise for LGPL and AGPL.
---
guix/import/utils.scm | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)

Toggle diff (57 lines)
diff --git a/guix/import/utils.scm b/guix/import/utils.scm
index d817318a91..d1b8076ddd 100644
--- a/guix/import/utils.scm
+++ b/guix/import/utils.scm
@@ -133,8 +133,14 @@ of the string VERSION is replaced by the symbol 'version."
   ;; Please update guix/licenses.scm when modifying
   ;; this list to avoid mismatches.
   (match str
+    ;; "GPL-N+" has been deprecated in favour of "GPL-N-or-later".
+    ;; "GPL-N" has been deprecated in favour of "GPL-N-only"
+    ;; or "GPL-N-or-later" as appropriate.  Likewise for LGPL
+    ;; and AGPL
     ("AGPL-1.0"                    'license:agpl1)
     ("AGPL-3.0"                    'license:agpl3)
+    ("AGPL-3.0-only"               'license:agpl3)
+    ("AGPL-3.0-or-later"           'license:agpl3+)
     ("Apache-1.1"                  'license:asl1.1)
     ("Apache-2.0"                  'license:asl2.0)
     ("BSL-1.0"                     'license:boost1.0)
@@ -161,11 +167,17 @@ of the string VERSION is replaced by the symbol 'version."
     ("GFDL-1.3"                    'license:fdl1.3+)
     ("Giftware"                    'license:giftware)
     ("GPL-1.0"                     'license:gpl1)
+    ("GPL-1.0-only"                'license:gpl1)
     ("GPL-1.0+"                    'license:gpl1+)
+    ("GPL-1.0-or-later"            'license:gpl1+)
     ("GPL-2.0"                     'license:gpl2)
+    ("GPL-2.0-only"                'license:gpl2)
     ("GPL-2.0+"                    'license:gpl2+)
+    ("GPL-2.0-or-later"            'license:gpl2+)
     ("GPL-3.0"                     'license:gpl3)
+    ("GPL-3.0-only"                'license:gpl3)
     ("GPL-3.0+"                    'license:gpl3+)
+    ("GPL-3.0-or-later"            'license:gpl3+)
     ("ISC"                         'license:isc)
     ("IJG"                         'license:ijg)
     ("Imlib2"                      'license:imlib2)
@@ -173,11 +185,17 @@ of the string VERSION is replaced by the symbol 'version."
     ("IPL-1.0"                     'license:ibmpl1.0)
     ("LAL-1.3"                     'license:lal1.3)
     ("LGPL-2.0"                    'license:lgpl2.0)
+    ("LGPL-2.0-only"               'license:lgpl2.0)
     ("LGPL-2.0+"                   'license:lgpl2.0+)
+    ("LGPL-2.0-or-later"           'license:lgpl2.0+)
     ("LGPL-2.1"                    'license:lgpl2.1)
+    ("LGPL-2.1-only"               'license:lgpl2.1)
     ("LGPL-2.1+"                   'license:lgpl2.1+)
+    ("LGPL-2.1-or-later"           'license:lgpl2.1+)
     ("LGPL-3.0"                    'license:lgpl3)
+    ("LGPL-3.0-only"               'license:lgpl3)
     ("LGPL-3.0+"                   'license:lgpl3+)
+    ("LGPL-3.0-or-later"           'license:lgpl3+)
     ("MPL-1.0"                     'license:mpl1.0)
     ("MPL-1.1"                     'license:mpl1.1)
     ("MPL-2.0"                     'license:mpl2.0)
-- 
2.32.0
From 1ea774e7ff007e60cf612f4f74f656be3de9f1f3 Mon Sep 17 00:00:00 2001
From: Maxime Devos <maximedevos@telenet.be>
Date: Sat, 31 Jul 2021 14:44:11 +0200
Subject: [PATCH 06/20] guix: Add ContentDB importer.

* guix/import/contentdb.scm: New file.
* guix/scripts/import/contentdb.scm: New file.
* tests/contentdb.scm: New file.
* Makefile.am (MODULES, SCM_TESTS): Register them.
* po/guix/POTFILES.in: Likewise.
* doc/guix.texi (Invoking guix import): Document it.
---
Makefile.am | 3 +
doc/guix.texi | 32 +++
guix/import/minetest.scm | 467 +++++++++++++++++++++++++++++++
guix/scripts/import.scm | 3 +-
guix/scripts/import/minetest.scm | 117 ++++++++
po/guix/POTFILES.in | 1 +
tests/minetest.scm | 355 +++++++++++++++++++++++
7 files changed, 977 insertions(+), 1 deletion(-)
create mode 100644 guix/import/minetest.scm
create mode 100644 guix/scripts/import/minetest.scm
create mode 100644 tests/minetest.scm

Toggle diff (1056 lines)
diff --git a/Makefile.am b/Makefile.am
index f4439ce93b..6243583616 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -262,6 +262,7 @@ MODULES =					\
   guix/import/json.scm				\
   guix/import/kde.scm				\
   guix/import/launchpad.scm   			\
+  guix/import/minetest.scm   			\
   guix/import/opam.scm				\
   guix/import/print.scm				\
   guix/import/pypi.scm				\
@@ -304,6 +305,7 @@ MODULES =					\
   guix/scripts/import/go.scm			\
   guix/scripts/import/hackage.scm		\
   guix/scripts/import/json.scm  		\
+  guix/scripts/import/minetest.scm  		\
   guix/scripts/import/opam.scm			\
   guix/scripts/import/pypi.scm			\
   guix/scripts/import/stackage.scm		\
@@ -470,6 +472,7 @@ SCM_TESTS =					\
   tests/import-utils.scm			\
   tests/inferior.scm				\
   tests/lint.scm				\
+  tests/minetest.scm				\
   tests/modules.scm				\
   tests/monads.scm				\
   tests/nar.scm				\
diff --git a/doc/guix.texi b/doc/guix.texi
index d44ecc2005..47861047eb 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -11314,6 +11314,38 @@ and generate package expressions for all those packages that are not yet
 in Guix.
 @end table
 
+@item contentdb
+@cindex minetest
+@cindex ContentDB
+Import metadata from @uref{https://content.minetest.net, ContentDB}.
+Information is taken from the JSON-formatted metadata provided through
+@uref{https://content.minetest.net/help/api/, ContentDB's API} and
+includes most relevant information, including dependencies.  There are
+some caveats, however.  The license information is often incomplete.
+The commit's SHA-1 is sometimes missing.  The descriptions are in the
+Markdown format, but Guix uses Texinfo instead.  Texture packs and
+subgames are unsupported.
+
+The command below imports metadata for the Mesecons mod by Jeija:
+
+@example
+guix import minetest Jeija/mesecons
+@end example
+
+The author name can also be left out:
+
+@example
+guix import minetest mesecons
+@end example
+
+@table @code
+@item --recursive
+@itemx -r
+Traverse the dependency graph of the given upstream package recursively
+and generate package expressions for all those packages that are not yet
+in Guix.
+@end table
+
 @item cpan
 @cindex CPAN
 Import metadata from @uref{https://www.metacpan.org/, MetaCPAN}.
diff --git a/guix/import/minetest.scm b/guix/import/minetest.scm
new file mode 100644
index 0000000000..5728f4613a
--- /dev/null
+++ b/guix/import/minetest.scm
@@ -0,0 +1,467 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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 Guix 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 Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix import minetest)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 receive)
+  #:use-module (ice-9 threads)
+  #:use-module (ice-9 hash-table)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-2)
+  #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-26)
+  #:use-module (guix utils)
+  #:use-module (guix ui)
+  #:use-module (guix i18n)
+  #:use-module (guix memoization)
+  #:use-module (guix serialization)
+  #:use-module (guix import utils)
+  #:use-module (guix import json)
+  #:use-module ((gcrypt hash) #:select (open-sha256-port port-sha256))
+  #:use-module (json)
+  #:use-module (guix base32)
+  #:use-module (guix git)
+  #:use-module (guix store)
+  #:export (%default-sort-key
+            %contentdb-api
+            json->package
+            contentdb-fetch
+            elaborate-contentdb-name
+            minetest->guix-package
+            minetest-recursive-import
+            sort-packages))
+
+;; The ContentDB API is documented at
+;; <https://content.minetest.net>.
+
+(define %contentdb-api
+  (make-parameter "https://content.minetest.net/api/"))
+
+(define (string-or-false x)
+  (and (string? x) x))
+
+(define (natural-or-false x)
+  (and (exact-integer? x) (>= x 0) x))
+
+;; Descriptions on ContentDB use carriage returns, but Guix doesn't.
+(define (delete-cr text)
+  (string-delete #\cr text))
+
+
+
+;;;
+;;; JSON mappings
+;;;
+
+;; Minetest package.
+;;
+;; API endpoint: /packages/AUTHOR/NAME/
+(define-json-mapping <package> make-package package?
+  json->package
+  (author            package-author) ; string
+  (creation-date     package-creation-date ; string
+                     "created_at")
+  (downloads         package-downloads) ; integer
+  (forums            package-forums "forums" natural-or-false)
+  (issue-tracker     package-issue-tracker "issue_tracker") ; string
+  (license           package-license) ; string
+  (long-description  package-long-description "long_description") ; string
+  (maintainers       package-maintainers ; list of strings
+                     "maintainers" vector->list)
+  (media-license     package-media-license "media_license") ; string
+  (name              package-name) ; string
+  (provides          package-provides ; list of strings
+                     "provides" vector->list)
+  (release           package-release) ; integer
+  (repository        package-repository "repo" string-or-false)
+  (score             package-score) ; flonum
+  (screenshots       package-screenshots "screenshots" vector->list) ; list of strings
+  (short-description package-short-description "short_description") ; string
+  (state             package-state) ; string
+  (tags              package-tags "tags" vector->list) ; list of strings
+  (thumbnail         package-thumbnail) ; string
+  (title             package-title) ; string
+  (type              package-type) ; string
+  (url               package-url) ; string
+  (website           package-website "website" string-or-false))
+
+(define-json-mapping <release> make-release release?
+  json->release
+  ;; If present, a git commit identified by its hash
+  (commit               release-commit "commit" string-or-false)
+  (downloads            release-downloads) ; integer
+  (id                   release-id) ; integer
+  (max-minetest-version release-max-minetest-version string-or-false)
+  (min-minetest-version release-min-minetest-version string-or-false)
+  (release-date         release-data) ; string
+  (title                release-title) ; string
+  (url                  release-url)) ; string
+
+(define-json-mapping <dependency> make-dependency dependency?
+  json->dependency
+  (optional? dependency-optional? "is_optional") ; bool
+  (name dependency-name) ; string
+  (packages dependency-packages "packages" vector->list)) ; list of strings
+
+;; A structure returned by the /api/packages/?fmt=keys endpoint
+(define-json-mapping <package/keys> make-package/keys package/keys?
+  json->package/keys
+  (author package/keys-author) ; string
+  (name package/keys-name)     ; string
+  (type package/keys-type))    ; string
+
+(define (package-mod? package)
+  "Is the ContentDB package PACKAGE a mod?"
+  ;; ContentDB also has ‘games’ and ‘texture packs’.
+  (string=? (package-type package) "mod"))
+
+
+
+;;;
+;;; Manipulating names of packages
+;;;
+;;; There are three kind of names:
+;;;
+;;;   * names of guix packages, e.g. minetest-basic-materials.
+;;;   * names of mods on ContentDB, e.g. basic_materials
+;;;   * a combination of author and mod name on ContentDB, e.g. VanessaE/basic_materials
+;;;
+
+(define (package-author/name package)
+  "Given a <package> object, return the corresponding AUTHOR/NAME string."
+  (string-append (package-author package) "/" (package-name package)))
+
+(define (package/keys-author/name package)
+  "Given a <package/keys> object, return the corresponding AUTHOR/NAME string."
+  (string-append (package/keys-author package)
+                 "/" (package/keys-name package)))
+
+(define (contentdb->package-name name)
+  "Given the NAME of a package on ContentDB, return a Guix-compliant name for the
+package."
+  ;; The author is not included, as the names of popular mods
+  ;; tend to be unique.
+  (string-append "minetest-" (snake-case name)))
+
+(define (author/name->name author/name)
+  "Extract NAME from the AUTHOR/NAME string, or raise an error if AUTHOR/NAME
+is ill-formatted."
+  (match (string-split author/name #\/)
+    ((author name)
+     (when (string-null? author)
+       (leave
+        (G_ "In ~a: author names must consist of at least a single character.~%")
+        author/name))
+     (when (string-null? name)
+       (leave
+        (G_ "In ~a: mod names must consist of at least a single character.~%")
+        author/name))
+     name)
+    ((too many . components)
+     (leave
+      (G_ "In ~a: author names and mod names may not contain forward slashes.~%")
+      author/name))
+    ((name)
+     (if (string-null? name)
+         (leave (G_ "mod names may not be empty.~%"))
+         (leave (G_ "The name of the author is missing in ~a.~%")
+                author/name)))))
+
+(define* (elaborate-contentdb-name name #:key (sort %default-sort-key))
+  "If NAME is an AUTHOR/NAME string, return it.  Otherwise, try to determine
+the author and return an appropriate AUTHOR/NAME string.  If that fails,
+raise an exception."
+  (if (or (string-contains name "/") (string-null? name))
+      ;; Call 'author/name->name' to verify that NAME seems reasonable
+      ;; and raise an appropriate exception if it isn't.
+      (begin
+        (author/name->name name)
+        name)
+      (let* ((package/keys (contentdb-query-packages name #:sort sort))
+             (correctly-named
+              (filter (lambda (package/key)
+                        (string=? name (package/keys-name package/key)))
+                      package/keys)))
+        (match correctly-named
+          ((one) (package/keys-author/name one))
+          ((too . many)
+           (warning (G_ "~a is ambigious, presuming ~a (other options include: ~a)~%")
+                    name (package/keys-author/name too)
+                    (map package/keys-author/name many))
+           (package/keys-author/name too))
+          (()
+           (leave (G_ "No mods with name ~a were found.~%") name))))))
+
+
+
+;;;
+;;; API endpoints
+;;;
+
+(define contentdb-fetch
+  (mlambda (author/name)
+    "Return a <package> record for package AUTHOR/NAME, or #f on failure."
+    (and=> (json-fetch
+            (string-append (%contentdb-api) "packages/" author/name "/"))
+           json->package)))
+
+(define (contentdb-fetch-releases author/name)
+  "Return a list of <release> records for package NAME by AUTHOR, or #f
+on failure."
+  (and=> (json-fetch (string-append (%contentdb-api) "packages/" author/name
+                                    "/releases/"))
+         (lambda (json)
+           (map json->release (vector->list json)))))
+
+(define (latest-release author/name)
+  "Return the latest source release for package NAME by AUTHOR,
+or #f if this package does not exist."
+  (and=> (contentdb-fetch-releases author/name)
+         car))
+
+(define (contentdb-fetch-dependencies author/name)
+  "Return an alist of lists of <dependency> records for package NAME by AUTHOR
+and possibly some other packages as well, or #f on failure."
+  (define url (string-append (%contentdb-api) "packages/" author/name
+                             "/dependencies/"))
+  (and=> (json-fetch url)
+         (lambda (json)
+           (map (match-lambda
+                  ((key . value)
+                   (cons key (map json->dependency (vector->list value)))))
+                json))))
+
+(define* (contentdb-query-packages q #:key
+                                   (type "mod")
+                                   (limit 50)
+                                   (sort %default-sort-key)
+                                   (order "desc"))
+  "Search ContentDB for Q (a string).  Sort by SORT, in ascending order
+if ORDER is \"asc\" or descending order if ORDER is \"desc\".  TYPE must
+be \"mod\", \"game\" or \"txp\", restricting thes search results to
+respectively mods, games and texture packs.  Limit to at most LIMIT
+results.  The return value is a list of <package/keys> records."
+  ;; XXX does Guile have something for constructing (and, when necessary,
+  ;; escaping) query strings?
+  (define url (string-append (%contentdb-api) "packages/?type=" type
+                             "&q=" q "&fmt=keys"
+                             "&limit=" (number->string limit)
+                             "&order=" order
+                             "&sort=" sort))
+  (let ((json (json-fetch url)))
+    (if json
+        (map json->package/keys (vector->list json))
+        (leave
+         (G_ "The package search API doesn't exist anymore.~%")))))
+
+
+
+;; XXX copied from (guix import elpa)
+(define* (download-git-repository url ref)
+  "Fetch the given REF from the Git repository at URL."
+  (with-store store
+    (latest-repository-commit store url #:ref ref)))
+
+;; XXX adapted from (guix scripts hash)
+(define (file-hash file select? recursive?)
+  ;; Compute the hash of FILE.
+  (if recursive?
+      (let-values (((port get-hash) (open-sha256-port)))
+        (write-file file port #:select? select?)
+        (force-output port)
+        (get-hash))
+      (call-with-input-file file port-sha256)))
+;; XXX likewise.
+(define (vcs-file? file stat)
+  (case (stat:type stat)
+    ((directory)
+     (member (basename file) '(".bzr" ".git" ".hg" ".svn" "CVS")))
+    ((regular)
+     ;; Git sub-modules have a '.git' file that is a regular text file.
+     (string=? (basename file) ".git"))
+    (else
+     #f)))
+
+(define (make-minetest-sexp author/name version repository commit
+                            inputs home-page synopsis
+                            description media-license license)
+  "Return a S-expression for the minetest package with the given author/NAME,
+VERSION, REPOSITORY, COMMIT, INPUTS, HOME-PAGE, SYNOPSIS, DESCRIPTION,
+MEDIA-LICENSE and LICENSE."
+  `(package
+     (name ,(contentdb->package-name (author/name->name author/name)))
+     (version ,version)
+     (source
+       (origin
+         (method git-fetch)
+         (uri (git-reference
+                (url ,repository)
+                (commit ,commit)))
+         (sha256
+          (base32
+           ;; The git commit is not always available.
+           ,(and commit
+                 (bytevector->nix-base32-string
+                  (file-hash
+                   (download-git-repository repository `(commit . ,commit))
+                   (negate vcs-file?) #t)))))
+         (file-name (git-file-name name version))))
+     (build-system minetest-mod-build-system)
+     ,@(maybe-propagated-inputs
+        (map (compose contentdb->package-name author/name->name) inputs))
+     (home-page ,home-page)
+     (synopsis ,(delete-cr synopsis))
+     (description ,(delete-cr description))