minetest.scm 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2021, 2022 Maxime Devos <maximedevos@telenet.be>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (guix import minetest)
  19. #:use-module (ice-9 match)
  20. #:use-module (ice-9 receive)
  21. #:use-module (ice-9 hash-table)
  22. #:use-module (srfi srfi-1)
  23. #:use-module (srfi srfi-2)
  24. #:use-module (srfi srfi-11)
  25. #:use-module (srfi srfi-26)
  26. #:use-module ((guix packages) #:prefix package:)
  27. #:use-module (guix upstream)
  28. #:use-module (guix utils)
  29. #:use-module (guix ui)
  30. #:use-module (guix i18n)
  31. #:use-module (guix memoization)
  32. #:use-module (guix serialization)
  33. #:use-module (guix import utils)
  34. #:use-module (guix import json)
  35. #:use-module ((gcrypt hash) #:select (open-sha256-port port-sha256))
  36. #:use-module (json)
  37. #:use-module (guix base32)
  38. #:use-module (guix git)
  39. #:use-module ((guix git-download) #:prefix download:)
  40. #:use-module (guix hash)
  41. #:use-module (guix store)
  42. #:export (%default-sort-key
  43. %contentdb-api
  44. json->package
  45. contentdb-fetch
  46. elaborate-contentdb-name
  47. minetest-package?
  48. latest-minetest-release
  49. minetest->guix-package
  50. minetest-recursive-import
  51. sort-packages
  52. %minetest-updater))
  53. ;; The ContentDB API is documented at
  54. ;; <https://content.minetest.net>.
  55. (define %contentdb-api
  56. (make-parameter "https://content.minetest.net/api/"))
  57. (define (string-or-false x)
  58. (and (string? x) x))
  59. (define (natural-or-false x)
  60. (and (exact-integer? x) (>= x 0) x))
  61. ;; Descriptions on ContentDB use carriage returns, but Guix doesn't.
  62. (define (delete-cr text)
  63. (string-delete #\cr text))
  64. ;; Mocked by tests.
  65. (define par-map (@ (ice-9 threads) par-map))
  66. (set! par-map par-map)
  67. ;;;
  68. ;;; JSON mappings
  69. ;;;
  70. ;; Minetest package.
  71. ;;
  72. ;; API endpoint: /packages/AUTHOR/NAME/
  73. (define-json-mapping <package> make-package package?
  74. json->package
  75. (author package-author) ; string
  76. (creation-date package-creation-date ; string
  77. "created_at")
  78. (downloads package-downloads) ; integer
  79. (forums package-forums "forums" natural-or-false)
  80. (issue-tracker package-issue-tracker "issue_tracker") ; string
  81. (license package-license) ; string
  82. (long-description package-long-description "long_description") ; string
  83. (maintainers package-maintainers ; list of strings
  84. "maintainers" vector->list)
  85. (media-license package-media-license "media_license") ; string
  86. (name package-name) ; string
  87. (provides package-provides ; list of strings
  88. "provides" vector->list)
  89. (release package-release) ; integer
  90. (repository package-repository "repo" string-or-false)
  91. (score package-score) ; flonum
  92. (screenshots package-screenshots "screenshots" vector->list) ; list of strings
  93. (short-description package-short-description "short_description") ; string
  94. (state package-state) ; string
  95. (tags package-tags "tags" vector->list) ; list of strings
  96. (thumbnail package-thumbnail) ; string
  97. (title package-title) ; string
  98. (type package-type) ; string
  99. (url package-url) ; string
  100. (website package-website "website" string-or-false))
  101. (define-json-mapping <release> make-release release?
  102. json->release
  103. ;; If present, a git commit identified by its hash
  104. (commit release-commit "commit" string-or-false)
  105. (downloads release-downloads) ; integer
  106. (id release-id) ; integer
  107. (max-minetest-version release-max-minetest-version string-or-false)
  108. (min-minetest-version release-min-minetest-version string-or-false)
  109. (release-date release-data) ; string
  110. (title release-title) ; string
  111. (url release-url)) ; string
  112. (define-json-mapping <dependency> make-dependency dependency?
  113. json->dependency
  114. (optional? dependency-optional? "is_optional") ; bool
  115. (name dependency-name) ; string
  116. (packages dependency-packages "packages" vector->list)) ; list of strings
  117. ;; A structure returned by the /api/packages/?fmt=keys endpoint
  118. (define-json-mapping <package-keys> make-package-keys package-keys?
  119. json->package-keys
  120. (author package-keys-author) ; string
  121. (name package-keys-name) ; string
  122. (type package-keys-type)) ; string
  123. (define (package-mod? package)
  124. "Is the ContentDB package PACKAGE a mod?"
  125. ;; ContentDB also has ‘games’ and ‘texture packs’.
  126. (string=? (package-type package) "mod"))
  127. ;;;
  128. ;;; Manipulating names of packages
  129. ;;;
  130. ;;; There are three kind of names:
  131. ;;;
  132. ;;; * names of guix packages, e.g. minetest-basic-materials.
  133. ;;; * names of mods on ContentDB, e.g. basic_materials
  134. ;;; * a combination of author and mod name on ContentDB, e.g. VanessaE/basic_materials
  135. ;;;
  136. (define (%construct-full-name author name)
  137. (string-append author "/" name))
  138. (define (package-full-name package)
  139. "Given a <package> object, return the corresponding AUTHOR/NAME string."
  140. (%construct-full-name (package-author package) (package-name package)))
  141. (define (package-keys-full-name package)
  142. "Given a <package-keys> object, return the corresponding AUTHOR/NAME string."
  143. (%construct-full-name (package-keys-author package)
  144. (package-keys-name package)))
  145. (define (contentdb->package-name author/name)
  146. "Given the AUTHOR/NAME of a package on ContentDB, return a Guix-compliant
  147. name for the package."
  148. ;; The author is not included, as the names of popular mods
  149. ;; tend to be unique.
  150. (string-append "minetest-" (snake-case (author/name->name author/name))))
  151. (define (author/name->name author/name)
  152. "Extract NAME from the AUTHOR/NAME string, or raise an error if AUTHOR/NAME
  153. is ill-formatted."
  154. (match (string-split author/name #\/)
  155. ((author name)
  156. (when (string-null? author)
  157. (leave
  158. (G_ "In ~a: author names must consist of at least a single character.~%")
  159. author/name))
  160. (when (string-null? name)
  161. (leave
  162. (G_ "In ~a: mod names must consist of at least a single character.~%")
  163. author/name))
  164. name)
  165. ((too many . components)
  166. (leave
  167. (G_ "In ~a: author names and mod names may not contain forward slashes.~%")
  168. author/name))
  169. ((name)
  170. (if (string-null? name)
  171. (leave (G_ "mod names may not be empty.~%"))
  172. (leave (G_ "The name of the author is missing in ~a.~%")
  173. author/name)))))
  174. (define* (elaborate-contentdb-name name #:key (sort %default-sort-key))
  175. "If NAME is an AUTHOR/NAME string, return it. Otherwise, try to determine
  176. the author and return an appropriate AUTHOR/NAME string. If that fails,
  177. raise an exception."
  178. (if (or (string-contains name "/") (string-null? name))
  179. ;; Call 'author/name->name' to verify that NAME seems reasonable
  180. ;; and raise an appropriate exception if it isn't.
  181. (begin
  182. (author/name->name name)
  183. name)
  184. (let* ((package-keys (contentdb-query-packages name #:sort sort))
  185. (correctly-named
  186. (filter (lambda (package-key)
  187. (string=? name (package-keys-name package-key)))
  188. package-keys)))
  189. (match correctly-named
  190. ((one) (package-keys-full-name one))
  191. ((too . many)
  192. (warning (G_ "~a is ambiguous, presuming ~a (other options include: ~a)~%")
  193. name (package-keys-full-name too)
  194. (map package-keys-full-name many))
  195. (package-keys-full-name too))
  196. (()
  197. (leave (G_ "No mods with name ~a were found.~%") name))))))
  198. ;;;
  199. ;;; API endpoints
  200. ;;;
  201. (define contentdb-fetch
  202. (mlambda (author/name)
  203. "Return a <package> record for package AUTHOR/NAME, or #f on failure."
  204. (and=> (json-fetch
  205. (string-append (%contentdb-api) "packages/" author/name "/"))
  206. json->package)))
  207. (define (contentdb-fetch-releases author/name)
  208. "Return a list of <release> records for package NAME by AUTHOR, or #f
  209. on failure."
  210. (and=> (json-fetch (string-append (%contentdb-api) "packages/" author/name
  211. "/releases/"))
  212. (lambda (json)
  213. (map json->release (vector->list json)))))
  214. (define (latest-release author/name)
  215. "Return the latest source release for package NAME by AUTHOR,
  216. or #f if this package does not exist."
  217. (and=> (contentdb-fetch-releases author/name)
  218. car))
  219. (define (contentdb-fetch-dependencies author/name)
  220. "Return an alist of lists of <dependency> records for package NAME by AUTHOR
  221. and possibly some other packages as well, or #f on failure."
  222. (define url (string-append (%contentdb-api) "packages/" author/name
  223. "/dependencies/"))
  224. (and=> (json-fetch url)
  225. (lambda (json)
  226. (map (match-lambda
  227. ((key . value)
  228. (cons key (map json->dependency (vector->list value)))))
  229. json))))
  230. (define* (contentdb-query-packages q #:key
  231. (type "mod")
  232. (limit 50)
  233. (sort %default-sort-key)
  234. (order "desc"))
  235. "Search ContentDB for Q (a string). Sort by SORT, in ascending order
  236. if ORDER is \"asc\" or descending order if ORDER is \"desc\". TYPE must
  237. be \"mod\", \"game\" or \"txp\", restricting the search results to
  238. respectively mods, games and texture packs. Limit to at most LIMIT
  239. results. The return value is a list of <package-keys> records."
  240. ;; XXX does Guile have something for constructing (and, when necessary,
  241. ;; escaping) query strings?
  242. (define url (string-append (%contentdb-api) "packages/?type=" type
  243. "&q=" q "&fmt=keys"
  244. "&limit=" (number->string limit)
  245. "&order=" order
  246. "&sort=" sort))
  247. (let ((json (json-fetch url)))
  248. (if json
  249. (map json->package-keys (vector->list json))
  250. (leave
  251. (G_ "The package search API doesn't exist anymore.~%")))))
  252. ;; XXX copied from (guix import elpa)
  253. (define* (download-git-repository url ref)
  254. "Fetch the given REF from the Git repository at URL."
  255. (with-store store
  256. (latest-repository-commit store url #:ref ref)))
  257. (define (make-minetest-sexp author/name version repository commit
  258. inputs home-page synopsis
  259. description media-license license)
  260. "Return a S-expression for the minetest package with the given author/NAME,
  261. VERSION, REPOSITORY, COMMIT, INPUTS, HOME-PAGE, SYNOPSIS, DESCRIPTION,
  262. MEDIA-LICENSE and LICENSE."
  263. `(package
  264. (name ,(contentdb->package-name author/name))
  265. (version ,version)
  266. (source
  267. (origin
  268. (method git-fetch)
  269. (uri (git-reference
  270. (url ,repository)
  271. (commit ,commit)))
  272. (sha256
  273. (base32
  274. ;; The git commit is not always available.
  275. ,(and commit
  276. (bytevector->nix-base32-string
  277. (file-hash*
  278. (download-git-repository repository
  279. `(commit . ,commit))
  280. ;; 'download-git-repository' already filtered out the '.git'
  281. ;; directory.
  282. #:select? (const #true)
  283. #:recursive? #true)))))
  284. (file-name (git-file-name name version))))
  285. (build-system minetest-mod-build-system)
  286. ,@(maybe-propagated-inputs (map contentdb->package-name inputs))
  287. (home-page ,home-page)
  288. (synopsis ,(delete-cr synopsis))
  289. (description ,(beautify-description (delete-cr description)))
  290. (license ,(if (eq? media-license license)
  291. license
  292. `(list ,media-license ,license)))
  293. ;; The Minetest updater (not yet in Guix; it requires not-yet-submitted
  294. ;; patches to (guix upstream) that require some work) needs to know both
  295. ;; the author name and mod name for efficiency.
  296. (properties ,(list 'quasiquote `((upstream-name . ,author/name))))))
  297. (define (package-home-page package)
  298. "Guess the home page of the ContentDB package PACKAGE.
  299. In order of preference, try the 'website', the forum topic on the
  300. official Minetest forum and the Git repository (if any)."
  301. (define (topic->url-sexp topic)
  302. ;; 'minetest-topic' is a procedure defined in (gnu packages minetest)
  303. `(minetest-topic ,topic))
  304. (or (package-website package)
  305. (and=> (package-forums package) topic->url-sexp)
  306. (package-repository package)))
  307. (define (release-version release)
  308. "Guess the version of RELEASE from the release title."
  309. (define title (release-title release))
  310. (if (string-prefix? "v" title)
  311. ;; Remove "v" prefix from release titles like ‘v1.0.1’.
  312. (substring title 1)
  313. title))
  314. (define (version-style version)
  315. "Determine the kind of version number VERSION is -- a date, or a conventional
  316. conventional version number."
  317. (define dots? (->bool (string-index version #\.)))
  318. (define hyphens? (->bool (string-index version #\-)))
  319. (match (cons dots? hyphens?)
  320. ((#true . #false) 'regular) ; something like "0.1"
  321. ((#false . #false) 'regular) ; single component version number
  322. ((#true . #true) 'regular) ; result of 'git-version'
  323. ((#false . #true) 'date))) ; something like "2021-01-25"
  324. ;; If the default sort key is changed, make sure to modify 'show-help'
  325. ;; in (guix scripts import minetest) appropriately as well.
  326. (define %default-sort-key "score")
  327. (define* (sort-packages packages #:key (sort %default-sort-key))
  328. "Sort PACKAGES by SORT, in descending order."
  329. (define package->key
  330. (match sort
  331. ("score" package-score)
  332. ("downloads" package-downloads)))
  333. (define (greater x y)
  334. (> (package->key x) (package->key y)))
  335. (sort-list packages greater))
  336. (define builtin-mod?
  337. (let ((%builtin-mods
  338. (alist->hash-table
  339. (map (lambda (x) (cons x #t))
  340. '("beds" "binoculars" "boats" "bones" "bucket" "butterflies"
  341. "carts" "creative" "default" "doors" "dungeon_loot" "dye"
  342. "env_sounds" "farming" "fire" "fireflies" "flowers"
  343. "game_commands" "give_initial_stuff" "map" "mtg_craftguide"
  344. "player_api" "screwdriver" "sethome" "sfinv" "spawn" "stairs"
  345. "tnt" "vessels" "walls" "weather" "wool" "xpanes")))))
  346. (lambda (mod)
  347. "Is MOD provided by the default minetest subgame?"
  348. (hash-ref %builtin-mods mod))))
  349. (define* (important-dependencies dependencies author/name
  350. #:key (sort %default-sort-key))
  351. "Return the hard dependencies of AUTHOR/NAME in the association list
  352. DEPENDENCIES as a list of AUTHOR/NAME strings."
  353. (define dependency-list
  354. (assoc-ref dependencies author/name))
  355. ;; A mod can have multiple dependencies implemented by the same mod,
  356. ;; so remove duplicate mod names.
  357. (define (filter-deduplicate-map f list)
  358. (delete-duplicates (filter-map f list)))
  359. (filter-deduplicate-map
  360. (lambda (dependency)
  361. (and (not (dependency-optional? dependency))
  362. (not (builtin-mod? (dependency-name dependency)))
  363. ;; The dependency information contains symbolic names
  364. ;; that can be ‘provided’ by multiple mods, so we need to choose one
  365. ;; of the implementations.
  366. (let* ((implementations
  367. (par-map contentdb-fetch (dependency-packages dependency)))
  368. ;; Fetching package information about the packages is racy:
  369. ;; some packages might be removed from ContentDB between the
  370. ;; construction of DEPENDENCIES and the call to
  371. ;; 'contentdb-fetch'. So filter out #f.
  372. ;;
  373. ;; Filter out ‘games’ that include the requested mod -- it's
  374. ;; the mod itself we want.
  375. (mods (filter (lambda (p) (and=> p package-mod?))
  376. implementations))
  377. (sorted-mods (sort-packages mods #:sort sort)))
  378. (match sorted-mods
  379. ((package) (package-full-name package))
  380. ((too . many)
  381. (warning
  382. (G_ "The dependency ~a of ~a has multiple different implementations ~a.~%")
  383. (dependency-name dependency)
  384. author/name
  385. (map package-full-name sorted-mods))
  386. (match sort
  387. ("score"
  388. (warning
  389. (G_ "The implementation with the highest score will be choosen!~%")))
  390. ("downloads"
  391. (warning
  392. (G_ "The implementation that has been downloaded the most will be choosen!~%"))))
  393. (package-full-name too))
  394. (()
  395. (warning
  396. (G_ "The dependency ~a of ~a does not have any implementation. It will be ignored!~%")
  397. (dependency-name dependency) author/name)
  398. #f)))))
  399. dependency-list))
  400. (define* (%minetest->guix-package author/name #:key (sort %default-sort-key))
  401. "Fetch the metadata for AUTHOR/NAME from https://content.minetest.net, and
  402. return the 'package' S-expression corresponding to that package, or raise an
  403. exception on failure. On success, also return the upstream dependencies as a
  404. list of AUTHOR/NAME strings."
  405. ;; Call 'author/name->name' to verify that AUTHOR/NAME seems reasonable.
  406. (author/name->name author/name)
  407. (define package (contentdb-fetch author/name))
  408. (unless package
  409. (leave (G_ "no package metadata for ~a on ContentDB~%") author/name))
  410. (define dependencies (contentdb-fetch-dependencies author/name))
  411. (unless dependencies
  412. (leave (G_ "no dependency information for ~a on ContentDB~%") author/name))
  413. (define release (latest-release author/name))
  414. (unless release
  415. (leave (G_ "no release of ~a on ContentDB~%") author/name))
  416. (define important-upstream-dependencies
  417. (important-dependencies dependencies author/name #:sort sort))
  418. (values (make-minetest-sexp author/name
  419. (release-version release)
  420. (package-repository package)
  421. (release-commit release)
  422. important-upstream-dependencies
  423. (package-home-page package)
  424. (package-short-description package)
  425. (package-long-description package)
  426. (spdx-string->license
  427. (package-media-license package))
  428. (spdx-string->license
  429. (package-license package)))
  430. important-upstream-dependencies))
  431. (define minetest->guix-package
  432. (memoize %minetest->guix-package))
  433. (define* (minetest-recursive-import author/name #:key (sort %default-sort-key))
  434. (define* (minetest->guix-package* author/name #:key repo version)
  435. (minetest->guix-package author/name #:sort sort))
  436. (recursive-import author/name
  437. #:repo->guix-package minetest->guix-package*
  438. #:guix-name contentdb->package-name))
  439. (define (minetest-package? pkg)
  440. "Is PKG a Minetest mod on ContentDB?"
  441. (and (string-prefix? "minetest-" (package:package-name pkg))
  442. (assq-ref (package:package-properties pkg) 'upstream-name)))
  443. (define (latest-minetest-release pkg)
  444. "Return an <upstream-source> for the latest release of the package PKG,
  445. or #false if the latest release couldn't be determined."
  446. (define author/name
  447. (assq-ref (package:package-properties pkg) 'upstream-name))
  448. (define contentdb-package (contentdb-fetch author/name)) ; TODO warn if #f?
  449. (define release (latest-release author/name))
  450. (define source (package:package-source pkg))
  451. (and contentdb-package release
  452. (release-commit release) ; not always set
  453. ;; Only continue if both the old and new version number are both
  454. ;; dates or regular version numbers, as two different styles confuses
  455. ;; the logic for determining which version is newer.
  456. (eq? (version-style (release-version release))
  457. (version-style (package:package-version pkg)))
  458. (upstream-source
  459. (package (package:package-name pkg))
  460. (version (release-version release))
  461. (urls (download:git-reference
  462. (url (package-repository contentdb-package))
  463. (commit (release-commit release)))))))
  464. (define %minetest-updater
  465. (upstream-updater
  466. (name 'minetest)
  467. (description "Updater for Minetest packages on ContentDB")
  468. (pred minetest-package?)
  469. (latest latest-minetest-release)))