Subject: Re: [PATCH 06/20] guix: Add ContentDB importer.

From: Maxime Devos <[email-masked]>
Date: Sat, 07 Aug 2021 20:31:05 +0200

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

The revised patch series is attached.  It can also be found at
<https://notabug.org/maximed/guix-gnunet/src/minetest-2>.  It includes
the latest MINETEST_MOD_PATH patch.  I'll make the patch to export more things in
(guix build utils) later (for core-updates).

reetings,
Maxime.
From 292dce14ea4811f1554965d83af5e33687cd00b7 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[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 93aa8e1976e762d30be70aef6d5c50b1d06ca4be 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 | 225 +++++++++++++++++++++++++++
 4 files changed, 334 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..b051d9c288
--- /dev/null
+++ b/guix/build/minetest-build-system.scm
@@ -0,0 +1,225 @@
+;;; 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"))
+           (file-name (basename source))
+           ;; The "minetest-" prefix is not informative, so strip it.
+           (file-name (if (string-prefix? "minetest-" file-name)
+                          (substring file-name (string-length "minetest-"))
+                          file-name))
+           ;; Strip "-checkout" suffixes of git checkouts.
+           (file-name (if (string-suffix? "-checkout" file-name)
+                          (substring file-name
+                                     0
+                                     (- (string-length file-name)
+                                        (string-length "-minetest")))
+                          file-name))
+           (first-dot (string-index file-name #\.))
+           ;; If the source code is in an archive (.tar.gz, .zip, ...),
+           ;; strip the extension.
+           (file-name (if first-dot
+                          (substring file-name 0 first-dot)
+                          file-name)))
+      (format (current-error-port)
+              "warning: the modpack ~a did not set 'name' in 'modpack.conf'~%"
+              file-name)
+      file-name))
+  (cond ((file-exists? "mod.conf")
+         (read-mod-name "mod.conf"))
+        ((file-exists? "modpack.conf")
+         (read-mod-name "modpack.conf" guess))
+        (#t (guess))))
+
+(define* (install #:key inputs #:allow-other-keys #:rest arguments)
+  (apply copy:install
+         #:install-plan (mod-install-plan (apply guess-mod-name arguments))
+         arguments))
+
+(define %png-magic-bytes
+  ;; Magic bytes of PNG images, see ‘5.2 PNG signatures’ in
+  ;; ‘Portable Network Graphics (PNG) Specification (Second Edition)’
+  ;; on <https://www.w3.org/TR/PNG/>.
+  #vu8(137 80 78 71 13 10 26 10))
+
+(define png-file?
+  ((@@ (guix build utils) file-header-match) %png-magic-bytes))
+
+(define* (minimise-png #:key inputs native-inputs #:allow-other-keys)
+  "Minimise PNG images found in the working directory."
+  (define optipng (which "optipng"))
+  (define (optimise image)
+    (format #t "Optimising ~a~%" image)
+    (make-file-writable (dirname image))
+    (make-file-writable image)
+    (define old-size (stat:size (stat image)))
+    ;; The mod "technic" has a file "technic_music_player_top.png" that
+    ;; actually is a JPEG file, see
+    ;; <https://github.com/minetest-mods/technic/issues/590>.
+    (if (png-file? image)
+        (invoke optipng "-o4" "-quiet" image)
+        (format #t "warning: skipping ~a because it's not actually a PNG image~%"
+                image))
+    (define new-size (stat:size (stat image)))
+    (values old-size new-size))
+  (define files (find-files "." ".png$"))
+  (let loop ((total-old-size 0)
+             (total-new-size 0)
+             (images (find-files "." ".png$")))
+    (cond ((pair? images)
+           (receive (old-size new-size)
+               (optimise (car images))
+             (loop (+ total-old-size old-size)
+                   (+ total-new-size new-size)
+                   (cdr images))))
+          ((= total-old-size 0)
+           (format #t "There were no PNG images to minimise."))
+          (#t
+           (format #t "Minimisation reduced size of images by ~,2f% (~,2f MiB to ~,2f MiB)~%"
+                   (* 100.0 (- 1 (/ total-new-size total-old-size)))
+                   (/ total-old-size (expt 1024 2))
+                   (/ total-new-size (expt 1024 2)))))))
+
+(define name-regexp (make-regexp "^name[ ]*=(.+)$"))
+
+(define* (read-mod-name mod.conf #:optional not-found)
+  "Read the name of a mod from MOD.CONF.  If MOD.CONF
+does not have a name field and NOT-FOUND is #false, raise an
+error.  If NOT-FOUND is TRUE, call NOT-FOUND instead."
+  (call-with-input-file mod.conf
+    (lambda (port)
+      (let loop ()
+        (define line (read-line port))
+        (if (eof-object? line)
+            (if not-found
+                (not-found)
+                (error "~a does not have a 'name' field" mod.conf))
+            (let ((match (regexp-exec name-regexp line)))
+              (if (regexp-match? match)
+                  (string-trim-both (match:substring match 1) #\ )
+                  (loop))))))))
+
+(define* (check #:key outputs tests? #:allow-other-keys)
+  "Test whether the mod loads.  The mod must first be installed first."
+  (define (all-mod-names directories)
+    (append-map
+     (lambda (directory)
+       (map read-mod-name (find-files directory "mod.conf")))
+     directories))
+  (when tests?
+    (mkdir "guix_testworld")
+    ;; Add the mod to the mod search path, such that Minetest can find it.
+    (setenv "MINETEST_MOD_PATH"
+            (list->search-path-as-string
+             (cons
+              (string-append (assoc-ref outputs "out") "/share/minetest/mods")
+              (search-path-as-string->list
+               (or (getenv "MINETEST_MOD_PATH") "")))
+             ":"))
+    (with-directory-excursion "guix_testworld"
+      (setenv "HOME" (getcwd))
+      ;; Create a world in which all mods are loaded.
+      (call-with-output-file "world.mt"
+        (lambda (port)
+          (display
+           "gameid = minetest
+world_name = guix_testworld
+backend = sqlite3
+player_backend = sqlite3
+auth_backend = sqlite3
+" port)
+          (for-each
+           (lambda (mod)
+             (format port "load_mod_~a = true~%" mod))
+           (all-mod-names (search-path-as-string->list
+                           (getenv "MINETEST_MOD_PATH"))))))
+      (receive (port pid)
+          ((@@ (guix build utils) open-pipe-with-stderr)
+           "xvfb-run" "--" "minetest" "--info" "--world" "." "--go")
+        (format #t "Started Minetest with all mods loaded for testing~%")
+        ;; Scan the output for error messages.
+        ;; When the player has joined the server, stop minetest.
+        (define (error? line)
+          (and (string? line)
+               (string-contains line ": ERROR[")))
+        (define (stop? line)
+          (and (string? line)
+               (string-contains line "ACTION[Server]: singleplayer [127.0.0.1] joins game.")))
+        (let loop ()
+          (match (read-line port)
+            ((? error? line)
+             (error "minetest raised an error: ~a" line))
+            ((? stop?)
+             (kill pid SIGINT)
+             (close-port port)
+             (waitpid pid))
+            ((? string? line)
+             (display line)
+             (newline)
+             (loop))
+            ((? eof-object?)
+             (error "minetest didn't start"))))))))
+
+(define %standard-phases
+  (modify-phases gnu:%standard-phases
+    (delete 'bootstrap)
+    (delete 'configure)
+    (add-before 'build 'minimise-png minimise-png)
+    (delete 'build)
+    (delete 'check)
+    (replace 'install install)
+    ;; The 'check' phase requires the mod to be installed,
+    ;; so move the 'check' phase after the 'install' phase.
+    (add-after 'install 'check check)))
+
+;;; minetest-build-system.scm ends here
-- 
2.32.0

From 9858d43e51cdfdbf4ca022399469d99f437bf596 Mon Sep 17 00:00:00 2001
From: Maxime Devos <[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 1ea774e7ff007e60cf612f4f74f656be3de9f1f3 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         | 467 +++++++++++++++++++++++++++++++
 guix/scripts/import.scm          |   3 +-
 guix/scripts/import/minetest.scm | 117 ++++++++
 po/guix/POTFILES.in              |   1 +
 tests/minetest.scm               | 355 +++++++++++++++++++++++
 7 files changed, 977 insertions(+), 1 deletion(-)
 create mode 100644 guix/import/minetest.scm
 create mode 100644 guix/scripts/import/minetest.scm
 create mode 100644 tests/minetest.scm

diff --git a/Makefile.am b/Makefile.am
index f4439ce93b..6243583616 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -262,6 +262,7 @@ MODULES =					\
   guix/import/json.scm				\
   guix/import/kde.scm				\
   guix/import/launchpad.scm   			\
+  guix/import/minetest.scm   			\
   guix/import/opam.scm				\
   guix/import/print.scm				\
   guix/import/pypi.scm				\
@@ -304,6 +305,7 @@ MODULES =					\
   guix/scripts/import/go.scm			\
   guix/scripts/import/hackage.scm		\
   guix/scripts/import/json.scm  		\
+  guix/scripts/import/minetest.scm  		\
   guix/scripts/import/opam.scm			\
   guix/scripts/import/pypi.scm			\
   guix/scripts/import/stackage.scm		\
@@ -470,6 +472,7 @@ SCM_TESTS =					\
   tests/import-utils.scm			\
   tests/inferior.scm				\
   tests/lint.scm				\
+  tests/minetest.scm				\
   tests/modules.scm				\
   tests/monads.scm				\
   tests/nar.scm				\
diff --git a/doc/guix.texi b/doc/guix.texi
index d44ecc2005..47861047eb 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -11314,6 +11314,38 @@ and generate package expressions for all those packages that are not yet
 in Guix.
 @end table
 
+@item contentdb
+@cindex minetest
+@cindex ContentDB
+Import metadata from @uref{https://content.minetest.net, ContentDB}.
+Information is taken from the JSON-formatted metadata provided through
+@uref{https://content.minetest.net/help/api/, ContentDB's API} and
+includes most relevant information, including dependencies.  There are
+some caveats, however.  The license information is often incomplete.
+The commit's SHA-1 is sometimes missing.  The descriptions are in the
+Markdown format, but Guix uses Texinfo instead.  Texture packs and
+subgames are unsupported.
+
+The command below imports metadata for the Mesecons mod by Jeija:
+
+@example
+guix import minetest Jeija/mesecons
+@end example
+
+The author name can also be left out:
+
+@example
+guix import minetest mesecons
+@end example
+
+@table @code
+@item --recursive
+@itemx -r
+Traverse the dependency graph of the given upstream package recursively
+and generate package expressions for all those packages that are not yet
+in Guix.
+@end table
+
 @item cpan
 @cindex CPAN
 Import metadata from @uref{https://www.metacpan.org/, MetaCPAN}.
diff --git a/guix/import/minetest.scm b/guix/import/minetest.scm
new file mode 100644
index 0000000000..5728f4613a
--- /dev/null
+++ b/guix/import/minetest.scm
@@ -0,0 +1,467 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Maxime Devos <[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 (package-author/name package)
+  "Given a <package> object, return the corresponding AUTHOR/NAME string."
+  (string-append (package-author package) "/" (package-name package)))
+
+(define (package/keys-author/name package)
+  "Given a <package/keys> object, return the corresponding AUTHOR/NAME string."
+  (string-append (package/keys-author package)
+                 "/" (package/keys-name package)))
+
+(define (contentdb->package-name name)
+  "Given the NAME of a package on ContentDB, return a Guix-compliant name for the
+package."
+  ;; The author is not included, as the names of popular mods
+  ;; tend to be unique.
+  (string-append "minetest-" (snake-case name)))
+
+(define (author/name->name author/name)
+  "Extract NAME from the AUTHOR/NAME string, or raise an error if AUTHOR/NAME
+is ill-formatted."
+  (match (string-split author/name #\/)
+    ((author name)
+     (when (string-null? author)
+       (leave
+        (G_ "In ~a: author names must consist of at least a single character.~%")
+        author/name))
+     (when (string-null? name)
+       (leave
+        (G_ "In ~a: mod names must consist of at least a single character.~%")
+        author/name))
+     name)
+    ((too many . components)
+     (leave
+      (G_ "In ~a: author names and mod names may not contain forward slashes.~%")
+      author/name))
+    ((name)
+     (if (string-null? name)
+         (leave (G_ "mod names may not be empty.~%"))
+         (leave (G_ "The name of the author is missing in ~a.~%")
+                author/name)))))
+
+(define* (elaborate-contentdb-name name #:key (sort %default-sort-key))
+  "If NAME is an AUTHOR/NAME string, return it.  Otherwise, try to determine
+the author and return an appropriate AUTHOR/NAME string.  If that fails,
+raise an exception."
+  (if (or (string-contains name "/") (string-null? name))
+      ;; Call 'author/name->name' to verify that NAME seems reasonable
+      ;; and raise an appropriate exception if it isn't.
+      (begin
+        (author/name->name name)
+        name)
+      (let* ((package/keys (contentdb-query-packages name #:sort sort))
+             (correctly-named
+              (filter (lambda (package/key)
+                        (string=? name (package/keys-name package/key)))
+                      package/keys)))
+        (match correctly-named
+          ((one) (package/keys-author/name one))
+          ((too . many)
+           (warning (G_ "~a is ambigious, presuming ~a (other options include: ~a)~%")
+                    name (package/keys-author/name too)
+                    (map package/keys-author/name many))
+           (package/keys-author/name too))
+          (()
+           (leave (G_ "No mods with name ~a were found.~%") name))))))
+
+
+
+;;;
+;;; API endpoints
+;;;
+
+(define contentdb-fetch
+  (mlambda (author/name)
+    "Return a <package> record for package AUTHOR/NAME, or #f on failure."
+    (and=> (json-fetch
+            (string-append (%contentdb-api) "packages/" author/name "/"))
+           json->package)))
+
+(define (contentdb-fetch-releases author/name)
+  "Return a list of <release> records for package NAME by AUTHOR, or #f
+on failure."
+  (and=> (json-fetch (string-append (%contentdb-api) "packages/" author/name
+                                    "/releases/"))
+         (lambda (json)
+           (map json->release (vector->list json)))))
+
+(define (latest-release author/name)
+  "Return the latest source release for package NAME by AUTHOR,
+or #f if this package does not exist."
+  (and=> (contentdb-fetch-releases author/name)
+         car))
+
+(define (contentdb-fetch-dependencies author/name)
+  "Return an alist of lists of <dependency> records for package NAME by AUTHOR
+and possibly some other packages as well, or #f on failure."
+  (define url (string-append (%contentdb-api) "packages/" author/name
+                             "/dependencies/"))
+  (and=> (json-fetch url)
+         (lambda (json)
+           (map (match-lambda
+                  ((key . value)
+                   (cons key (map json->dependency (vector->list value)))))
+                json))))
+
+(define* (contentdb-query-packages q #:key
+                                   (type "mod")
+                                   (limit 50)
+                                   (sort %default-sort-key)
+                                   (order "desc"))
+  "Search ContentDB for Q (a string).  Sort by SORT, in ascending order
+if ORDER is \"asc\" or descending order if ORDER is \"desc\".  TYPE must
+be \"mod\", \"game\" or \"txp\", restricting thes search results to
+respectively mods, games and texture packs.  Limit to at most LIMIT
+results.  The return value is a list of <package/keys> records."
+  ;; XXX does Guile have something for constructing (and, when necessary,
+  ;; escaping) query strings?
+  (define url (string-append (%contentdb-api) "packages/?type=" type
+                             "&q=" q "&fmt=keys"
+                             "&limit=" (number->string limit)
+                             "&order=" order
+                             "&sort=" sort))
+  (let ((json (json-fetch url)))
+    (if json
+        (map json->package/keys (vector->list json))
+        (leave
+         (G_ "The package search API doesn't exist anymore.~%")))))
+
+
+
+;; XXX copied from (guix import elpa)
+(define* (download-git-repository url ref)
+  "Fetch the given REF from the Git repository at URL."
+  (with-store store
+    (latest-repository-commit store url #:ref ref)))
+
+;; XXX adapted from (guix scripts hash)
+(define (file-hash file select? recursive?)
+  ;; Compute the hash of FILE.
+  (if recursive?
+      (let-values (((port get-hash) (open-sha256-port)))
+        (write-file file port #:select? select?)
+        (force-output port)
+        (get-hash))
+      (call-with-input-file file port-sha256)))
+;; XXX likewise.
+(define (vcs-file? file stat)
+  (case (stat:type stat)
+    ((directory)
+     (member (basename file) '(".bzr" ".git" ".hg" ".svn" "CVS")))
+    ((regular)
+     ;; Git sub-modules have a '.git' file that is a regular text file.
+     (string=? (basename file) ".git"))
+    (else
+     #f)))
+
+(define (make-minetest-sexp author/name version repository commit
+                            inputs home-page synopsis
+                            description media-license license)
+  "Return a S-expression for the minetest package with the given author/NAME,
+VERSION, REPOSITORY, COMMIT, INPUTS, HOME-PAGE, SYNOPSIS, DESCRIPTION,
+MEDIA-LICENSE and LICENSE."
+  `(package
+     (name ,(contentdb->package-name (author/name->name author/name)))
+     (version ,version)
+     (source
+       (origin
+         (method git-fetch)
+         (uri (git-reference
+                (url ,repository)
+                (commit ,commit)))
+         (sha256
+          (base32
+           ;; The git commit is not always available.
+           ,(and commit
+                 (bytevector->nix-base32-string
+                  (file-hash
+                   (download-git-repository repository `(commit . ,commit))
+                   (negate vcs-file?) #t)))))
+         (file-name (git-file-name name version))))
+     (build-system minetest-mod-build-system)
+     ,@(maybe-propagated-inputs
+        (map (compose contentdb->package-name author/name->name) inputs))
+     (home-page ,home-page)
+     (synopsis ,(delete-cr synopsis))
+     (description ,(delete-cr description))
+     (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-author/name package))
+              ((too . many)
+               (warning
+                (G_ "The dependency ~a of ~a has multiple different implementations ~a.~%")
+                (dependency-name dependency)
+                author/name
+                (map package-author/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-author/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
+                    (compose contentdb->package-name author/name->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..1ef202c0fd
--- /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 c0a76311be73aef41096f3e52ca329cf2fd2950f 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 fdf99927d0dd0090fe66f04e9a6e7e68f882f2e0 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 6ea28df67b5402634b14867ce334a2b9764e6d40 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 799bf39bb88a16d09a8473a43ea458fa98df86ae 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 93deef7431a986c41b085aa107055b0348a8ed8f 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 42b3daf2756e5eb3f7890b9a43fea90bdff4ba91 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 e8b540369b51ce74c3f9e83179a7a17f17c21117 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 9f2a20d664fb8d280f49db9ca46247de8242f53b 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 | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/gnu/packages/minetest.scm b/gnu/packages/minetest.scm
index bb914f32b4..1444ddf4d8 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -209,6 +209,29 @@ powered.")
                    license:wtfpl2))
     (properties `((upstream-name . "RealBadAngel/technic")))))
 
+(define-public minetest-throwing
+  (package
+    (name "minetest-throwing")
+    (version "2020-08-14")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://github.com/minetest-mods/throwing")
+             (commit "31f0cf5f868673dc82f24ddc432b45c9cd282d27")))
+       (sha256
+        (base32 "1s5kkr6rxxv2dhbbjzv62gw1s617hnpjavw1v9fv11v3mgigdfjb"))
+       (file-name (git-file-name name version))))
+    (build-system minetest-mod-build-system)
+    (home-page (minetest-topic 16365))
+    (synopsis "API for throwing things in Minetest")
+    (description
+     "This Minetest mod provides an API for registering throwable things and
+throwing things like arrows.  However, this mod does not provide an actual
+arrow and bow, but @code{minetest-throwing-arrows} does.")
+    (license license:mpl2.0)
+    (properties `((upstream-name . "Palige/throwing")))))
+
 (define-public minetest-unifieddyes
   (package
     (name "minetest-unifieddyes")
-- 
2.32.0

From f1477ba3edb9729b63041863611704af1621bf29 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 1444ddf4d8..6183367101 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -232,6 +232,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 7188ac29a73be4f15df9977ce75b1dd60d837e44 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 6183367101..042f710c85 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -289,3 +289,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 664aa8de09e10b4699eb1e5ea95bde3b51ca2f36 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 042f710c85..f26e7e0ff3 100644
--- a/gnu/packages/minetest.scm
+++ b/gnu/packages/minetest.scm
@@ -262,6 +262,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 3d2a67158927e10bd2a8169b8199bb6de118c844 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 f26e7e0ff3..2a7bd0081e 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 f7d9cd92bf9eb5043332c1d0ba915b6923c60cc1 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 2a7bd0081e..f49e0080f2 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 33e15111c00d9988d8123a674930a27b61e891b3 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 f49e0080f2..ca9d0ac720 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