Subject: [bug#49828] [PATCH 06/20] guix: Add ContentDB importer.

From: Maxime Devos <[email-masked]>
Date: Tue, 10 Aug 2021 13:02:42 +0200

Hi,

I re-ran "./pre-inst-env guix build [all-minetest-mods]" and it turns
out "minetest-unified-inventory" retains a reference to the corresponding
git checkout.  Using 'strip-store-file-name' instead of 'basename'
in 'guess-mod-name' in ‘build-system: Add 'minetest-mod-build-system'’
fixed this.

Leo Prikler schreef op ma 09-08-2021 om 23:45 [+0200]:
> Hi,
> 
> [...]
> In other words, taking minetest-uri as is currently doesn't seem too
> good of an idea.  Does the plain git/vcs updater work for those
> packages then?  I assume at least some of the packages should be tagged
> properly in git, are they not?

FWIW, there is no git/vcs updater, but there is a GitHub updater
(a git updater looking at tags in a git repository if the package
origin uses the git-fetch method could be written though).

The GitHub updater could theoretically work, but look at the output:

./pre-inst-env guix refresh minetest-{unified-inventory,unifieddyes,worldedit,throwing-arrows,throwing,technic,pipeworks,mobs-animal,mobs,mesecons,homedecor-modpack,ethereal,coloredwood,basic-
materials}

gnu/packages/minetest.scm:29:2: warning: no updater for minetest-basic-materials
gnu/packages/minetest.scm:54:2: warning: no updater for minetest-coloredwood
gnu/packages/minetest.scm:87:4: warning: no updater for minetest-ethereal
gnu/packages/minetest.scm:111:2: warning: no updater for minetest-homedecor-modpack
gnu/packages/minetest.scm:147:13: minetest-mesecons would be upgraded from 1.2.1-0.db58797 to 2017.03.05
gnu/packages/minetest.scm:178:2: warning: no updater for minetest-mobs
gnu/packages/minetest.scm:207:2: warning: no updater for minetest-mobs-animal
gnu/packages/minetest.scm:234:2: warning: no updater for minetest-pipeworks
gnu/packages/minetest.scm:265:2: warning: 'github' updater failed to determine available releases for minetest-technic
gnu/packages/minetest.scm:302:13: warning: 2020-08-14 is greater than the latest known version of minetest-throwing (1.1)
gnu/packages/minetest.scm:330:15: warning: 1.1-0.059cc89 is greater than the latest known version of minetest-throwing-arrows (1.1)
gnu/packages/minetest.scm:355:13: 1.3 is already the latest version of minetest-worldedit
gnu/packages/minetest.scm:376:2: warning: no updater for minetest-unifieddyes
gnu/packages/minetest.scm:408:13: minetest-unified-inventory would be upgraded from 2021-03-25-1 to 20180810

Apparently, the git repos of minetest mods often don't keep version numbers,
or aren't on github, or uses multiple version schemes simultanuously
(x.y[.z] version numbers + dates) ... "minetest-worldedit" is properly
tagged though.  For the other cases, an updater for ContentDB packages
may be required.

I modified the definition of 'minetest-throwing' to use "1.1" as 'version'
(the commit remains the same).

> All in all, I think I'm rather content with this patch now, but I have
> a final nitpick w.r.t the updater.  "upstream-name" is a weird name for
> a property that will supposedly be used by only one updater (and even
> if different updaters were to use it, would not each one have slightly
> different, but perhaps sometimes overlapping expectations for that
> field?)  I think a better solution would be to set home-page to the
> ContentDB page, assuming that is acceptable.  If not, perhaps having a
> "refresh-url" that is well defined over all refreshers and supersedes
> home-page if specified might be a more appropriate solution.  

'upstream-name' is used by the refresher of "egg", "cpan" and "opam".
I searched for "package-properties" in (guix import ...), and the only
package property used by refreshers in 'upstream-name'.  So using
anything other than 'upstream-name' for Minetest would be unusual.

The documentation of "guix refresh" mentions:

   Sometimes the upstream name differs from the package name used in
Guix, and ‘guix refresh’ needs a little help.  Most updaters honor the
‘upstream-name’ property in package definitions, which can be used to
that effect:

     (define-public network-manager
       (package
         (name "network-manager")
         ;; ...
         (properties '((upstream-name . "NetworkManager")))))

So using a different package property than 'upstream-name' would
be unexpected.

I assume 'refresh-url' would be
"https://content.minetest.net/api/packages/AUTHOR/NAME" for minetest
packages, "https://github.com/ocaml/opam-repository/packages/NAME"
for opam packages?  That seems rather verbose though, and every
refresher would have a different idea of how to fetch the package
description from the URL and interpret it, so this doesn't seem
to be an improvement over 'upstream-name' to me.

About pointing the home page to content.minetest.net: that would
work for the refresher, but the ContentDB page isn't really the
home page, not unlike how the pypi page for python packages isn't
the home page.  Likewise for opam and ocaml.  Compare

https://content.minetest.net/packages/Jeija/mesecons/

with https://mesecons.net/, which one seems more ‘homey’?  I'd
say the latter is the home page.

The revised patch series is attached.  Only
‘build-system: Add 'minetest-mod-build-system'’ and ‘gnu: Add minetest-throwing’
have been changed.  It can also be found at
<https://notabug.org/maximed/guix-gnunet/src/minetest-3>.

Greetings,
Maxime.
From 292dce14ea4811f1554965d83af5e33687cd00b7 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
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(-)

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 <[email-masked]>
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

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 <[email-masked]>
+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 <[email-masked]>
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

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 <[email-masked]>
+;;; 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 545499de52c6869df498b0df5d5b403d05448fe3 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
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 | 226 +++++++++++++++++++++++++++
 4 files changed, 335 insertions(+)
 create mode 100644 guix/build-system/minetest.scm
 create mode 100644 guix/build/minetest-build-system.scm

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 <[email-masked]>
+;;;
+;;; 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..4968d3f560
--- /dev/null
+++ b/guix/build/minetest-build-system.scm
@@ -0,0 +1,226 @@
+;;; Copyright © 2021 Maxime Devos <[email-masked]>
+;;;
+;;; 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"))
+           ;; Don't retain a reference to the store.
+           (file-name (strip-store-file-name 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 8cf2245d9ecdd6f8dd65d5bf2b9bf6532511ca5b Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
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(+)

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 65614c021ad33f4116d631a7e24a356d9236803b Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
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         | 456 +++++++++++++++++++++++++++++++
 guix/scripts/import.scm          |   3 +-
 guix/scripts/import/minetest.scm | 117 ++++++++
 po/guix/POTFILES.in              |   1 +
 tests/minetest.scm               | 355 ++++++++++++++++++++++++
 7 files changed, 966 insertions(+), 1 deletion(-)
 create mode 100644 guix/import/minetest.scm
 create mode 100644 guix/scripts/import/minetest.scm
 create mode 100644 tests/minetest.scm

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..854e282b38 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 hash 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..e1f8487b75
--- /dev/null
+++ b/guix/import/minetest.scm
@@ -0,0 +1,456 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Maxime Devos <[email-masked]>
+;;;
+;;; 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 (%construct-full-name author name)
+  (string-append author "/" name))
+
+(define (package-full-name package)
+  "Given a <package> object, return the corresponding AUTHOR/NAME string."
+  (%construct-full-name (package-author package) (package-name package)))
+
+(define (package-keys-full-name package)
+  "Given a <package-keys> object, return the corresponding AUTHOR/NAME string."
+  (%construct-full-name (package-keys-author package)
+                        (package-keys-name package)))
+
+(define (contentdb->package-name author/name)
+  "Given the AUTHOR/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 (author/name->name author/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-full-name one))
+          ((too . many)
+           (warning (G_ "~a is ambigious, presuming ~a (other options include: ~a)~%")
+                    name (package-keys-full-name too)
+                    (map package-keys-full-name many))
+           (package-keys-full-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)
+  "Compute the hash of FILE."
+  (let-values (((port get-hash) (open-sha256-port)))
+    (write-file file port)
+    (force-output port)
+    (get-hash)))
+
+(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))
+     (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)))))))
+         (file-name (git-file-name name version))))
+     (build-system minetest-mod-build-system)
+     ,@(maybe-propagated-inputs (map contentdb->package-name inputs))
+     (home-page ,home-page)
+     (synopsis ,(delete-cr synopsis))
+     (description ,(delete-cr description))
+     (license ,(if (eq? media-license license)
+                   license
+                   `(list ,media-license ,license)))
+     ;; The Minetest updater (not yet in Guix; it requires not-yet-submitted
+     ;; patches to (guix upstream) that require some work) needs to know both
+     ;; the author name and mod name for efficiency.
+     (properties ,(list 'quasiquote `((upstream-name . ,author/name))))))
+
+(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)))
+
+;; If the default sort key is changed, make sure to modify 'show-help'
+;; in (guix scripts import minetest) appropriately as well.
+(define %default-sort-key "score")
+
+(define* (sort-packages packages #:key (sort %default-sort-key))
+  "Sort PACKAGES by SORT, in descending order."
+  (define package->key
+    (match sort
+      ("score" package-score)
+      ("downloads" package-downloads)))
+  (define (greater x y)
+    (> (package->key x) (package->key y)))
+  (sort-list packages greater))
+
+(define builtin-mod?
+  (let ((%builtin-mods
+         (alist->hash-table
+          (map (lambda (x) (cons x #t))
+               '("beds" "binoculars" "boats" "bones" "bucket" "butterflies"
+                 "carts" "creative" "default" "doors" "dungeon_loot" "dye"
+                 "env_sounds" "farming" "fire" "fireflies" "flowers"
+                 "game_commands" "give_initial_stuff" "map" "mtg_craftguide"
+                 "player_api" "screwdriver" "sethome" "sfinv" "spawn" "stairs"
+                 "tnt" "vessels" "walls" "weather" "wool" "xpanes")))))
+    (lambda (mod)
+      "Is MOD provided by the default minetest subgame?"
+      (hash-ref %builtin-mods mod))))
+
+(define* (important-dependencies dependencies author/name
+                                 #:key (sort %default-sort-key))
+  "Return the hard dependencies of AUTHOR/NAME in the association list
+DEPENDENCIES as a list of AUTHOR/NAME strings."
+  (define dependency-list
+    (assoc-ref dependencies author/name))
+  (filter-map
+   (lambda (dependency)
+     (and (not (dependency-optional? dependency))
+          (not (builtin-mod? (dependency-name dependency)))
+          ;; The dependency information contains symbolic names
+          ;; that can be ‘provided’ by multiple mods, so we need to choose one
+          ;; of the implementations.
+          (let* ((implementations
+                  (par-map contentdb-fetch (dependency-packages dependency)))
+                 ;; Fetching package information about the packages is racy:
+                 ;; some packages might be removed from ContentDB between the
+                 ;; construction of DEPENDENCIES and the call to
+                 ;; 'contentdb-fetch'.  So filter out #f.
+                 ;;
+                 ;; Filter out ‘games’ that include the requested mod -- it's
+                 ;; the mod itself we want.
+                 (mods (filter (lambda (p) (and=> p package-mod?))
+                               implementations))
+                 (sorted-mods (sort-packages mods #:sort sort)))
+            (match sorted-mods
+              ((package) (package-full-name package))
+              ((too . many)
+               (warning
+                (G_ "The dependency ~a of ~a has multiple different implementations ~a.~%")
+                (dependency-name dependency)
+                author/name
+                (map package-full-name sorted-mods))
+               (match sort
+                 ("score"
+                  (warning
+                   (G_ "The implementation with the highest score will be choosen!~%")))
+                 ("downloads"
+                  (warning
+                   (G_ "The implementation that has been downloaded the most will be choosen!~%"))))
+               (package-full-name too))
+              (()
+               (warning
+                (G_ "The dependency ~a of ~a does not have any implementation.  It will be ignored!~%")
+                (dependency-name dependency) author/name)
+               #f)))))
+   dependency-list))
+
+(define* (%minetest->guix-package author/name #:key (sort %default-sort-key))
+  "Fetch the metadata for AUTHOR/NAME from https://content.minetest.net, and
+return the 'package' S-expression corresponding to that package, or raise an
+exception on failure.  On success, also return the upstream dependencies as a
+list of AUTHOR/NAME strings."
+  ;; Call 'author/name->name' to verify that AUTHOR/NAME seems reasonable.
+  (author/name->name author/name)
+  (define package (contentdb-fetch author/name))
+  (unless package
+    (leave (G_ "no package metadata for ~a on ContentDB~%") author/name))
+  (define dependencies (contentdb-fetch-dependencies author/name))
+  (unless dependencies
+    (leave (G_ "no dependency information for ~a on ContentDB~%") author/name))
+  (define release (latest-release author/name))
+  (unless release
+    (leave (G_ "no release of ~a on ContentDB~%") author/name))
+  (define important-upstream-dependencies
+    (important-dependencies dependencies author/name #:sort sort))
+  (values (make-minetest-sexp author/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)
+                              (spdx-string->license
+                               (package-media-license package))
+                              (spdx-string->license
+                               (package-license package)))
+          important-upstream-dependencies))
+
+(define minetest->guix-package
+  (memoize %minetest->guix-package))
+
+(define* (minetest-recursive-import author/name #:key (sort %default-sort-key))
+  (define* (minetest->guix-package* author/name #:key repo version)
+    (minetest->guix-package author/name #:sort sort))
+  (recursive-import author/name
+                    #:repo->guix-package minetest->guix-package*
+                    #:guix-name contentdb->package-name))
diff --git a/guix/scripts/import.scm b/guix/scripts/import.scm
index f53d1ac1f4..b369a362d0 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"
+                    "minetest"))
 
 (define (resolve-importer name)
   (let ((module (resolve-interface
diff --git a/guix/scripts/import/minetest.scm b/guix/scripts/import/minetest.scm
new file mode 100644
index 0000000000..5f204d90fc
--- /dev/null
+++ b/guix/scripts/import/minetest.scm
@@ -0,0 +1,117 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2014 David Thompson <[email-masked]>
+;;; Copyright © 2018 Ricardo Wurmus <[email-masked]>
+;;; Copyright © 2021 Maxime Devos <[email-masked]>
+;;;
+;;; 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 minetest)
+  #:use-module (guix ui)
+  #:use-module (guix utils)
+  #:use-module (guix scripts)
+  #:use-module (guix import minetest)
+  #: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-minetest))
+
+
+;;;
+;;; Command-line options.
+;;;
+
+(define %default-options
+  `((sort . ,%default-sort-key)))
+
+(define (show-help)
+  (display (G_ "Usage: guix import minetest 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"))
+  (display (G_ "
+      --sort=KEY         when choosing between multiple implementations,
+                         choose the one with the highest value for KEY
+                         (one of \"score\" (standard) or \"downloads\")"))
+  (newline)
+  (show-bug-report-information))
+
+(define (verify-sort-order sort)
+  "Verify SORT can be used to sort mods by."
+  (unless (member sort '("score" "downloads" "reviews"))
+    (leave (G_ "~a: not a valid key to sort by~%") sort))
+  sort)
+
+(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 minetest")))
+         (option '(#\r "recursive") #f #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'recursive #t result)))
+         (option '("sort") #t #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'sort (verify-sort-order arg) result)))
+         %standard-import-options))
+
+
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-import-minetest . 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
+      ((name)
+       (with-error-handling
+         (let* ((sort (assoc-ref opts 'sort))
+                (author/name (elaborate-contentdb-name name #:sort sort)))
+           (if (assoc-ref opts 'recursive)
+               ;; Recursive import
+               (filter-map package->definition
+                           (minetest-recursive-import author/name #:sort sort))
+               ;; Single import
+               (minetest->guix-package author/name #:sort sort)))))
+      (()
+       (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/minetest.scm b/tests/minetest.scm
new file mode 100644
index 0000000000..6ae476fe5f
--- /dev/null
+++ b/tests/minetest.scm
@@ -0,0 +1,355 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Maxime Devos <[email-masked]>
+;;;
+;;; 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-minetest)
+  #:use-module (guix memoization)
+  #:use-module (guix import minetest)
+  #: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-26)
+  #:use-module (srfi srfi-34)
+  #: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 '())
+                            (upstream-name "Author/foo")
+                            #: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)
+     (properties
+      ,(list 'quasiquote
+             `((upstream-name . ,upstream-name))))))
+
+(define* (make-package-json #:key
+                            (author "Author")
+                            (name "foo")
+                            (media-license "CC-BY-SA-4.0")
+                            (license "LGPL-3.0-or-later")
+                            (short-description "synopsis")
+                            (long-description "description")
+                            (repo "https://example.org/foo.git")
+                            (website "https://example.org/foo")
+                            (forums 321)
+                            (score 987.654)
+                            (downloads 123)
+                            (type "mod")
+                            #:allow-other-keys)
+  `(("author" . ,author)
+    ("content_warnings" . #())
+    ("created_at" . "2018-05-23T19:58:07.422108")
+    ("downloads" . ,downloads)
+    ("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" . ,score)
+    ("screenshots" . #())
+    ("short_description" . ,short-description)
+    ("state" . "APPROVED")
+    ("tags" . #("some" "tags"))
+    ("thumbnail" . null)
+    ("title" . "The name")
+    ("type" . ,type)
+    ("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* (make-packages-keys-json #:key (author "Author")
+                                  (name "Name")
+                                  (type "mod"))
+  `(("author" . ,author)
+    ("name" . ,name)
+    ("type" . ,type)))
+
+(define (call-with-packages thunk . argument-lists)
+  ;; Don't reuse results from previous tests.
+  (invalidate-memoization! contentdb-fetch)
+  (invalidate-memoization! minetest->guix-package)
+  (define (scm->json-port scm)
+    (open-input-string (scm->json-string scm)))
+  (define (handle-package url requested-author requested-name . rest)
+    (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))
+    (scm->json-port
+     (apply (match rest
+              (("") make-package-json)
+              (("dependencies" "") make-dependencies-json)
+              (("releases" "") make-releases-json)
+              (_ (error "TODO ~a" rest)))
+            relevant-argument-list)))
+  (define (handle-mod-search sort)
+    ;; Produce search results, sorted by SORT in descending order.
+    (define arguments->key
+      (match sort
+        ("score" (lambda* (#:key (score 987.654) #:allow-other-keys)
+                   score))
+        ("downloads" (lambda* (#:key (downloads 123) #:allow-other-keys)
+                       downloads))))
+    (define argument-list->key (cut apply arguments->key <>))
+    (define (greater x y)
+      (> (argument-list->key x) (argument-list->key y)))
+    (define sorted-argument-lists (sort-list argument-lists greater))
+    (define* (arguments->json #:key (author "Author") (name "Foo") (type "mod")
+                              #:allow-other-keys)
+      (and (string=? type "mod")
+           `(("author" . ,author)
+             ("name" . ,name)
+             ("type" . ,type))))
+    (define argument-list->json (cut apply arguments->json <>))
+    (scm->json-port
+     (list->vector (filter-map argument-list->json sorted-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 #\/))
+           (match components
+             ((author name . rest)
+              (apply handle-package url author name rest))
+             (((? (cut string-prefix? "?type=mod&q=" <>) query))
+              (handle-mod-search
+               (cond ((string-contains query "sort=score") "score")
+                     ((string-contains query "sort=downloads") "downloads")
+                     (#t (error "search query ~a has unknown sort key"
+                                query)))))
+             (_
+              (error "the URL ~a should have an author and name component"
+                     url)))))
+        (parameterize ((%contentdb-api "mock://api/"))
+          (thunk))))
+
+(define* (minetest->guix-package* #:key (author "Author") (name "foo")
+                                  (sort %default-sort-key)
+                                  #:allow-other-keys)
+  (minetest->guix-package (string-append author "/" name) #:sort sort))
+
+(define (imported-package-sexp* primary-arguments . secondary-arguments)
+  "Ask the importer to import a package specified by PRIMARY-ARGUMENTS,
+during a dynamic where that package and the packages specified by
+SECONDARY-ARGUMENTS are available on ContentDB."
+  (apply call-with-packages
+         (lambda ()
+           ;; The memoization cache is reset by call-with-packages
+           (apply minetest->guix-package* primary-arguments))
+   primary-arguments
+   secondary-arguments))
+
+(define (imported-package-sexp . extra-arguments)
+  "Ask the importer to import a package specified by EXTRA-ARGUMENTS,
+during a dynamic extent where that package is available on ContentDB."
+  (imported-package-sexp* 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)))
+
+(define-syntax-rule (test-package* test-case primary-arguments extra-arguments
+                                   ...)
+  (test-equal test-case
+    (apply make-package-sexp primary-arguments)
+    (imported-package-sexp* primary-arguments extra-arguments ...)))
+
+(test-begin "minetest")
+
+
+;; Package names
+(test-package "minetest->guix-package")
+(test-package "minetest->guix-package, _ → - in package name"
+              #:name "foo_bar"
+              #:guix-name "minetest-foo-bar"
+              #:upstream-name "Author/foo_bar")
+
+(test-equal "elaborate names, unambigious"
+  "Jeija/mesecons"
+  (call-with-packages
+   (cut elaborate-contentdb-name "mesecons")
+   '(#:name "mesecons" #:author "Jeija")
+   '(#:name "something" #:author "else")))
+
+(test-equal "elaborate name, ambigious (highest score)"
+  "Jeija/mesecons"
+  (call-with-packages
+   ;; #:sort "score" is the default
+   (cut elaborate-contentdb-name "mesecons")
+   '(#:name "mesecons" #:author "Jeijc" #:score 777)
+   '(#:name "mesecons" #:author "Jeijb" #:score 888)
+   '(#:name "mesecons" #:author "Jeija" #:score 999)))
+
+
+(test-equal "elaborate name, ambigious (most downloads)"
+  "Jeija/mesecons"
+  (call-with-packages
+   (cut elaborate-contentdb-name "mesecons" #:sort "downloads")
+   '(#:name "mesecons" #:author "Jeijc" #:downloads 777)
+   '(#:name "mesecons" #:author "Jeijb" #:downloads 888)
+   '(#:name "mesecons" #:author "Jeija" #:downloads 999)))
+
+
+;; Determining the home page
+(test-package "minetest->guix-package, website is used as home page"
+              #:home-page "web://site"
+              #:website "web://site")
+(test-package "minetest->guix-package, if absent, the forum is used"
+              #:home-page '(minetest-topic 628)
+              #:forums 628
+              #:website 'null)
+(test-package "minetest->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 "minetest->guix-package, all home page information absent"
+              #:home-page #f
+              #:forums 'null
+              #:website 'null
+              #:repo 'null)
+
+
+
+;; Dependencies
+(test-package* "minetest->guix-package, unambigious dependency"
+  (list #:requirements '(("mesecons" #f
+                          ("Jeija/mesecons"
+                           "some-modpack/containing-mese")))
+        #:inputs '("minetest-mesecons"))
+  (list #:author "Jeija" #:name "mesecons")
+  (list #:author "some-modpack" #:name "containing-mese" #:type "modpack"))
+
+(test-package* "minetest->guix-package, ambigious dependency (highest score)"
+  (list #:name "frobnicate"
+        #:guix-name "minetest-frobnicate"
+        #:upstream-name "Author/frobnicate"
+        #:requirements '(("frob" #f
+                          ("Author/foo" "Author/bar")))
+        ;; #:sort "score" is the default
+        #:inputs '("minetest-bar"))
+  (list #:author "Author" #:name "foo" #:score 0)
+  (list #:author "Author" #:name "bar" #:score 9999))
+
+(test-package* "minetest->guix-package, ambigious dependency (most downloads)"
+  (list #:name "frobnicate"
+        #:guix-name "minetest-frobnicate"
+        #:upstream-name "Author/frobnicate"
+        #:requirements '(("frob" #f
+                          ("Author/foo" "Author/bar")))
+        #:inputs '("minetest-bar")
+        #:sort "downloads")
+  (list #:author "Author" #:name "foo" #:downloads 0)
+  (list #:author "Author" #:name "bar" #:downloads 9999))
+
+(test-package "minetest->guix-package, optional dependency"
+              #:requirements '(("mesecons" #t
+                                ("Jeija/mesecons"
+                                 "some-modpack/containing-mese")))
+              #:inputs '())
+
+
+;; License
+(test-package "minetest->guix-package, identical licenses"
+              #:guix-license 'license:lgpl3+
+              #:license "LGPL-3.0-or-later"
+              #:media-license "LGPL-3.0-or-later")
+
+;; Sorting
+(let* ((make-package
+        (lambda arguments
+          (json->package (apply make-package-json arguments))))
+       (x (make-package #:score 0))
+       (y (make-package #:score 1))
+       (z (make-package #:score 2)))
+  (test-equal "sort-packages, already sorted"
+    (list z y x)
+    (sort-packages (list z y x)))
+  (test-equal "sort-packages, reverse"
+    (list z y x)
+    (sort-packages (list x y z))))
+
+(test-end "minetest")
-- 
2.32.0

From eab75f7b436f6fd069262462841080bf692851d0 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sat, 31 Jul 2021 20:00:45 +0200
Subject: [PATCH 07/20] gnu: Add minetest-mesecons.

* gnu/packages/minetest.scm (minetest-mesecons): New variable.
---
 gnu/packages/minetest.scm | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index f8aca3005c..d99efa4625 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -24,3 +24,39 @@
 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))
+    (properties `((upstream-name . "Jeija/mesecons"))))))
-- 
2.32.0

From 9b3d85969d5ac46a9b0e9b0f8cb347ec66df9d08 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sat, 31 Jul 2021 20:23:50 +0200
Subject: [PATCH 08/20] gnu: Add minetest-basic-materials.

* gnu/packages/minetest.scm (minetest-basic-materials): New variable.
---
 gnu/packages/minetest.scm | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index d99efa4625..97d4d3c9ac 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -25,6 +25,31 @@ 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))
+    (properties `((upstream-name . "VanessaE/basic_materials")))))
+
 (define-public minetest-mesecons
   ;; The release on ContentDB does not have its own version number.
   (let ((commit "db5879706d04d3480bc4863ce0c03fa73e5f10c7")
-- 
2.32.0

From 52ed6ed145b054cf049a745d4c3c2ad85ebca57f Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sat, 31 Jul 2021 20:40:24 +0200
Subject: [PATCH 09/20] gnu: Add minetest-unifieddyes.

* gnu/packages/minetest.scm (minetest-unifieddyes): New variable.
---
 gnu/packages/minetest.scm | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 97d4d3c9ac..8b9eb30a6a 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -85,3 +85,32 @@ with different rules and mechanics.")
     ;; <https://github.com/minetest-mods/mesecons/issues/575>.
     (license (list license:lgpl3+ license:cc-by-sa3.0))
     (properties `((upstream-name . "Jeija/mesecons"))))))
+
+(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+)
+    (properties `((upstream-name . "VanessaE/unifieddyes")))))
-- 
2.32.0

From fd414b9da2fc746730536b7770240f2c32fdbee1 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sat, 31 Jul 2021 23:39:02 +0200
Subject: [PATCH 10/20] gnu: Add minetest-pipeworks.

* gnu/packages/minetest.scm (minetest-pipeworks): New variable.
---
 gnu/packages/minetest.scm | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 8b9eb30a6a..782166daef 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -86,6 +86,37 @@ with different rules and mechanics.")
     (license (list license:lgpl3+ license:cc-by-sa3.0))
     (properties `((upstream-name . "Jeija/mesecons"))))))
 
+(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))
+    (properties `((upstream-name . "VanessaE/pipeworks")))))
+
 (define-public minetest-unifieddyes
   (package
     (name "minetest-unifieddyes")
-- 
2.32.0

From b9b98dc8bec8784e0941989da1f9cce7b5063412 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sun, 1 Aug 2021 00:50:12 +0200
Subject: [PATCH 11/20] gnu: Add minetest-coloredwood.

* gnu/packages/minetest.scm (minetest-coloredwood): New variable.
---
 gnu/packages/minetest.scm | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 782166daef..4c542c6061 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -50,6 +50,34 @@ like steel bars and chains, wire, plastic strips and sheets, and more.")
      (list license:cc-by-sa4.0 license:lgpl3))
     (properties `((upstream-name . "VanessaE/basic_materials")))))
 
+(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))
+    (properties `((upstream-name . "VanessaE/coloredwood")))))
+
 (define-public minetest-mesecons
   ;; The release on ContentDB does not have its own version number.
   (let ((commit "db5879706d04d3480bc4863ce0c03fa73e5f10c7")
-- 
2.32.0

From fdb72067e442979319220ca833f8e5088246eccd Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sun, 1 Aug 2021 01:24:48 +0200
Subject: [PATCH 12/20] gnu: Add minetest-ethereal.

* gnu/packages/minetest.scm (minetest-ethereal): New variable.
---
 gnu/packages/minetest.scm | 32 ++++++++++++++++++++++++++++++--
 1 file changed, 30 insertions(+), 2 deletions(-)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 4c542c6061..a1aa90276d 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -78,6 +78,35 @@ coloured and cut wood shapes are provided as well.")
      (list license:cc-by-sa4.0 license:lgpl3))
     (properties `((upstream-name . "VanessaE/coloredwood")))))
 
+(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))
+      (properties `((upstream-name . "TenPlus1/ethereal"))))))
+
 (define-public minetest-mesecons
   ;; The release on ContentDB does not have its own version number.
   (let ((commit "db5879706d04d3480bc4863ce0c03fa73e5f10c7")
@@ -157,8 +186,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

From e9ddc33e346bbb1723bc77d396c3c6daff4e69ad Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sun, 1 Aug 2021 13:40:56 +0200
Subject: [PATCH 13/20] gnu: Add minetest-technic.

* gnu/packages/minetest.scm (minetest-technic): New variable.
---
 gnu/packages/minetest.scm | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index a1aa90276d..bb914f32b4 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -174,6 +174,41 @@ breakers simulate a player punching a node.")
     (license (list license:cc-by-sa4.0 license:lgpl3))
     (properties `((upstream-name . "VanessaE/pipeworks")))))
 
+(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))
+    (properties `((upstream-name . "RealBadAngel/technic")))))
+
 (define-public minetest-unifieddyes
   (package
     (name "minetest-unifieddyes")
-- 
2.32.0

From f28bb06110b08f1f2816a748f8b1d4fe48371914 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sun, 1 Aug 2021 14:16:27 +0200
Subject: [PATCH 14/20] gnu: Add minetest-throwing.

* gnu/packages/minetest.scm (minetest-throwing): New variable.
---
 gnu/packages/minetest.scm | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index bb914f32b4..4566d94489 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -209,6 +209,33 @@ powered.")
                    license:wtfpl2))
     (properties `((upstream-name . "RealBadAngel/technic")))))
 
+(define-public minetest-throwing
+  ;; The latest release on ContentDB is ahead of the latet
+  ;; tagged commit.
+  (let ((commit "31f0cf5f868673dc82f24ddc432b45c9cd282d27")
+        (revision "0"))
+    (package
+      (name "minetest-throwing")
+      (version (git-version "1.1" revision commit))
+      (source
+       (origin
+         (method git-fetch)
+         (uri (git-reference
+               (url "https://github.com/minetest-mods/throwing")
+               (commit commit)))
+         (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)
+      (properties `((upstream-name . "Palige/throwing"))))))
+
 (define-public minetest-unifieddyes
   (package
     (name "minetest-unifieddyes")
-- 
2.32.0

From 3a54d4daa59cfa29f336071407816e40c5fbab7e Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sun, 1 Aug 2021 14:34:12 +0200
Subject: [PATCH 15/20] gnu: Add minetest-throwing-arrows.

* gnu/packages/minetest.scm
  (minetest-throwing-arrows): New variable.
---
 gnu/packages/minetest.scm | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 4566d94489..1f785098f9 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -236,6 +236,36 @@ arrow and bow, but @code{minetest-throwing-arrows} does.")
       (license license:mpl2.0)
       (properties `((upstream-name . "Palige/throwing"))))))
 
+(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

From 826415b801ae96cc622233092252fea96b624a46 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sun, 1 Aug 2021 17:39:36 +0200
Subject: [PATCH 16/20] gnu: Add minetest-unified-inventory.

* gnu/packages/minetest.scm
  (minetest-unified-inventory): New variable.
---
 gnu/packages/minetest.scm | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 1f785098f9..d6ff6c9e47 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -293,3 +293,38 @@ Minetest game, this mod has become an extension of the default mod an a library
 for general colour handling.")
     (license license:gpl2+)
     (properties `((upstream-name . "VanessaE/unifieddyes")))))
+
+(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+))
+    (properties `((upstream-name . "RealBadAngel/unified_inventory")))))
-- 
2.32.0

From 75a8e7069310d6a4ae3468d6cf26621413ff98f2 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sun, 1 Aug 2021 17:58:24 +0200
Subject: [PATCH 17/20] gnu: Add minetest-worldedit.

* gnu/packages/minetest.scm (minetest-worldedit): New variable.
---
 gnu/packages/minetest.scm | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index d6ff6c9e47..700a9b2872 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -266,6 +266,29 @@ 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)
+    (properties `((upstream-name . "sfan5/worldedit")))))
+
 (define-public minetest-unifieddyes
   (package
     (name "minetest-unifieddyes")
-- 
2.32.0

From be4ec577c21e90496754c5b5735efd0904417bd4 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sun, 1 Aug 2021 18:53:54 +0200
Subject: [PATCH 18/20] gnu: Add minetest-mobs.

* gnu/packages/minetest.scm (minetest-mobs): New variable.
---
 gnu/packages/minetest.scm | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 700a9b2872..d36ca06ef5 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -143,6 +143,35 @@ with different rules and mechanics.")
     (license (list license:lgpl3+ license:cc-by-sa3.0))
     (properties `((upstream-name . "Jeija/mesecons"))))))
 
+(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))
+    (properties `((upstream-name . "TenPlus1/mobs")))))
+
 (define-public minetest-pipeworks
   (package
     (name "minetest-pipeworks")
-- 
2.32.0

From a7baa3adb190936f8f99521f577ae9cef5211ab2 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sun, 1 Aug 2021 19:43:47 +0200
Subject: [PATCH 19/20] gnu: Add minetest-mobs-animal.

* gnu/packages/minetest.scm (minetest-mobs-animal): New variable.
---
 gnu/packages/minetest.scm | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index d36ca06ef5..3cd14a6df8 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -172,6 +172,33 @@ add some mobs, a mod like e.g. @code{mobs_animal} provided by the
     (license (list license:expat license:cc0 license:cc-by3.0))
     (properties `((upstream-name . "TenPlus1/mobs")))))
 
+(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))
+    (properties `((upstream-name . "TenPlus1/mobs_animal")))))
+
 (define-public minetest-pipeworks
   (package
     (name "minetest-pipeworks")
-- 
2.32.0

From dbca87ad489d883487047af2c2a957a665096477 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[email-masked]>
Date: Sun, 1 Aug 2021 20:03:07 +0200
Subject: [PATCH 20/20] gnu: Add minetest-homedecor-modpack.

* gnu/packages/minetest.scm
  (minetest-homedecor-modpack): New variable.
---
 gnu/packages/minetest.scm | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index 3cd14a6df8..3bd640da45 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -107,6 +107,37 @@ special items, intending to make an interesting adventure.")
       (license (list license:cc0 license:expat))
       (properties `((upstream-name . "TenPlus1/ethereal"))))))
 
+(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))
+    (properties `((upstream-name . "VanessaE/homedecor_modpack")))))
+
 (define-public minetest-mesecons
   ;; The release on ContentDB does not have its own version number.
   (let ((commit "db5879706d04d3480bc4863ce0c03fa73e5f10c7")
-- 
2.32.0