Toggle diff (484 lines)
diff --git a/Makefile.am b/Makefile.am
index 85a22be99c..9ca92c407c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -141,6 +141,7 @@ MODULES = \
guix/build-system/cmake.scm \
guix/build-system/dub.scm \
guix/build-system/dune.scm \
+ guix/build-system/elm.scm \
guix/build-system/emacs.scm \
guix/build-system/font.scm \
guix/build-system/go.scm \
@@ -192,6 +193,7 @@ MODULES = \
guix/build/cmake-build-system.scm \
guix/build/dub-build-system.scm \
guix/build/dune-build-system.scm \
+ guix/build/elm-build-system.scm \
guix/build/emacs-build-system.scm \
guix/build/meson-build-system.scm \
guix/build/minify-build-system.scm \
@@ -472,6 +474,7 @@ SCM_TESTS = \
tests/derivations.scm \
tests/discovery.scm \
tests/egg.scm \
+ tests/elm.scm \
tests/elpa.scm \
tests/file-systems.scm \
tests/gem.scm \
diff --git a/doc/contributing.texi b/doc/contributing.texi
index 862dcbf12a..555b9bb961 100644
--- a/doc/contributing.texi
+++ b/doc/contributing.texi
@@ -447,6 +447,7 @@ Packaging Guidelines
* Perl Modules:: Little pearls.
* Java Packages:: Coffee break.
* Rust Crates:: Beware of oxidation.
+* Elm Packages:: Trees of browser code
* Fonts:: Fond of fonts.
@end menu
@@ -898,6 +899,87 @@ Rust Crates
Rust compiler, or the test suite may have atrophied since it was released.
+@node Elm Packages
+@subsection Elm Packages
+
+@cindex Elm
+Elm applications can be named like other software: their names need not
+mention Elm.
+
+Packages in the Elm sense (see @code{elm-build-system} under @ref{Build
+Systems}) are required use names of the format
+@var{author}@code{/}@var{project}, where both the @var{author} and the
+@var{project} may contain hyphens internally, and the @var{author} sometimes
+contains uppercase letters.
+
+To form the Guix package name from the upstream name, we follow a convention
+similar to Python packages (@pxref{Python Modules}), adding an @code{elm-}
+prefix unless the name would already begin with @code{elm-}.
+
+In many cases we can reconstruct an Elm package's upstream name heuristically,
+but, since conversion to a Guix-style name involves a loss of information,
+this is not always possible. Care should be taken to add the
+@code{'upstream-name} property when necessary so that tools
+will work correctly. The most notable scenarios
+when explicitly specifying the upstream name is necessary are:
+
+@enumerate
+@item
+When the @var{author} is @code{elm} and the @var{project} contains one or more
+hyphens, as with @code{elm/virtual-dom}; and
+
+@item
+When the @var{author} contains hyphens or uppercase letters, as with
+@code{Elm-Canvas/raster-shapes}---unless the @var{author} is
+@code{elm-explorations}, which is handled as a special case, so packages like
+@code{elm-explorations/markdown} do @emph{not} need to use the
+@code{'upstream-name} property.
+@end enumerate
+
+The module @code{(guix build-system elm)} provides the following utilities for
+working with names and related conventions:
+
+@deffn {Scheme procedure} elm-package-origin @var{elm-name} @var{version} @
+ @var{hash}
+Returns a Git origin using the repository naming and tagging regime required
+for a published Elm package with the upstream name @var{elm-name} at version
+@var{version} with sha256 checksum @var{hash}.
+
+For example:
+@lisp
+(package
+ (name "elm-html")
+ (version "1.0.0")
+ (source
+ (elm-package-origin
+ "elm/html"
+ version
+ (base32 "15k1679ja57vvlpinpv06znmrxy09lbhzfkzdc89i01qa8c4gb4a")))
+ ...)
+@end lisp
+@end deffn
+
+@deffn {Scheme procedure} elm->package-name @var{elm-name}
+Returns the Guix-style package name for an Elm package with upstream name
+@var{elm-name}.
+
+Note that there is more than one possible @var{elm-name} for which
+@code{elm->package-name} will produce a given result.
+@end deffn
+
+@deffn {Scheme procedure} guix-package->elm-name @var{package}
+Given an Elm @var{package}, returns the possibly-inferred upstream name, or
+@code{#f} the upstream name is not specified via the @code{'upstream-name}
+property and can not be inferred by @code{infer-elm-package-name}.
+@end deffn
+
+@deffn {Scheme procedure} infer-elm-package-name @var{guix-name}
+Given the @var{guix-name} of an Elm package, returns the inferred upstream
+name, or @code{#f} if the upstream name can't be inferred. If the result is
+not @code{#f}, supplying it to @code{elm->package-name} would produce
+@var{guix-name}.
+@end deffn
+
@node Fonts
@subsection Fonts
diff --git a/doc/guix.texi b/doc/guix.texi
index c007c93dd3..63fb647045 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -102,6 +102,7 @@
Copyright @copyright{} 2021 Josselin Poiret@*
Copyright @copyright{} 2022 Remco van 't Veer@*
Copyright @copyright{} 2022 Aleksandr Vityazev@*
+Copyright @copyright{} 2022 Philip M@sup{c}Grath@*
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3 or
@@ -8717,6 +8718,57 @@ Build Systems
@end defvr
+@defvr {Scheme variable} elm-build-system
+This variable is exported by @code{(guix build-system elm)}. It implements a
+build procedure for @url{https://elm-lang.org, Elm} packages similar to
+@samp{elm install}.
+
+The build system adds an Elm compiler package to the set of inputs. The
+default compiler package (currently @code{elm}) can be overridden
+using the @code{#:elm} argument. Additionally, Elm packages needed by the
+build system itself are added as implicit inputs if they are not already
+present: to suppress this behavior, use the
+@code{#:implicit-elm-package-inputs?} argument, which is primarily useful for
+bootstrapping.
+
+The @code{"dependencies"} and @code{"test-dependencies"} in an Elm package's
+@file{elm.json} file correspond to @code{propagated-inputs} and @code{inputs},
+respectively.
+
+Elm requires a particular structure for package names: @pxref{Elm Packages}
+for more details, including utilities provided by @code{(guix build-system
+elm)}.
+
+There are currently a few noteworthy limitations to @code{elm-build-system}:
+
+@itemize
+@item
+The build system is focused on @dfn{packages} in the Elm sense of the word:
+Elm @dfn{projects} which declare @code{@{ "type": "package" @}} in their
+@file{elm.json} files. Using @code{elm-build-system} to build Elm
+@dfn{applications} (which declare @code{@{ "type": "application" @}}) is
+possible, but requires ad-hoc modifications to the build phases.
+
+@item
+Elm supports multiple versions of a package coexisting simultaneously under
+@env{ELM_HOME}, but this does not yet work well with @code{elm-build-system}.
+This limitation primarily affects Elm applications, because they specify
+exact versions for their dependencies, whereas Elm packages specify supported
+version ranges. As a workaround, you can use
+the @code{patch-application-dependencies} procedure provided by
+@code{(guix build elm-build-system)} to rewrite their @file{elm.json} files to
+refer to the package versions actually present in the build environment.
+Alternatively, Guix package transformations (@pxref{Defining Package
+Variants}) could be used to rewrite an application's entire dependency graph.
+
+@item
+We are not yet able to run tests for Elm projects because neither
+@url{https://github.com/mpizenberg/elm-test-rs, @command{elm-test-rs}} nor the
+Node.js-based @url{https://github.com/rtfeldman/node-test-runner,
+@command{elm-test}} runner has been packaged for Guix yet.
+@end itemize
+@end defvr
+
@defvr {Scheme Variable} go-build-system
This variable is exported by @code{(guix build-system go)}. It
implements a build procedure for Go packages using the standard
diff --git a/gnu/local.mk b/gnu/local.mk
index de044bdbff..94590ab5b5 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -1024,6 +1024,7 @@ dist_patch_DATA = \
%D%/packages/patches/einstein-build.patch \
%D%/packages/patches/elfutils-tests-ptrace.patch \
%D%/packages/patches/elixir-path-length.patch \
+ %D%/packages/patches/elm-offline-package-registry.patch \
%D%/packages/patches/elm-reactor-static-files.patch \
%D%/packages/patches/elogind-revert-polkit-detection.patch \
%D%/packages/patches/emacs-exec-path.patch \
diff --git a/gnu/packages/elm.scm b/gnu/packages/elm.scm
index a3863e6e6f..35bdcc65f5 100644
--- a/gnu/packages/elm.scm
+++ b/gnu/packages/elm.scm
@@ -25,6 +25,7 @@ (define-module (gnu packages elm)
#:use-module (gnu packages haskell-xyz)
#:use-module (gnu packages haskell-web)
#:use-module (guix build-system haskell)
+ #:use-module (guix build-system elm)
#:use-module (guix gexp)
#:use-module (guix git-download)
#:use-module ((guix licenses) #:prefix license:)
@@ -53,7 +54,8 @@ (define-public elm
(sha256
(base32 "1rdg3xp3js9xadclk3cdypkscm5wahgsfmm4ldcw3xswzhw6ri8w"))
(patches
- (search-patches "elm-reactor-static-files.patch"))))
+ (search-patches "elm-reactor-static-files.patch"
+ "elm-offline-package-registry.patch"))))
(build-system haskell-build-system)
(arguments
(list
diff --git a/gnu/packages/patches/elm-offline-package-registry.patch b/gnu/packages/patches/elm-offline-package-registry.patch
new file mode 100644
index 0000000000..761ec69878
--- /dev/null
+++ b/gnu/packages/patches/elm-offline-package-registry.patch
@@ -0,0 +1,71 @@
+From 06563409e6f2b1cca7bc1b27e31efd07a7569da8 Mon Sep 17 00:00:00 2001
+From: Philip McGrath <philip@philipmcgrath.com>
+Date: Thu, 14 Apr 2022 22:41:04 -0400
+Subject: [PATCH] minimal support for offline builds
+
+Normally, Elm performs HTTP requests before building to obtain or
+update its list of all registed packages and their versions.
+This is problematic in the Guix build environment.
+
+This patch causes Elm to check if the `GUIX_ELM_OFFLINE_REGISTRY_FILE`
+is set and, if so, to use the contents of the file it specifies as
+though it were the response from
+https://package.elm-lang.org/all-packages.
+
+This patch does not attempt to add more general support for offline
+builds. In particular, it does not attempt to support incremental
+updates to the package registry cache file. See also discussion at
+https://discourse.elm-lang.org/t/private-package-tool-spec/6779/25.
+---
+ builder/src/Deps/Registry.hs | 25 +++++++++++++++++++++----
+ 1 file changed, 21 insertions(+), 4 deletions(-)
+
+diff --git a/builder/src/Deps/Registry.hs b/builder/src/Deps/Registry.hs
+index 8d7def98..70cf3622 100644
+--- a/builder/src/Deps/Registry.hs
++++ b/builder/src/Deps/Registry.hs
+@@ -18,6 +18,8 @@ import Control.Monad (liftM2)
+ import Data.Binary (Binary, get, put)
+ import qualified Data.List as List
+ import qualified Data.Map.Strict as Map
++import System.Environment as Env
++import qualified Data.ByteString as BS
+
+ import qualified Deps.Website as Website
+ import qualified Elm.Package as Pkg
+@@ -190,13 +192,28 @@ getVersions' name (Registry _ versions) =
+ post :: Http.Manager -> String -> D.Decoder x a -> (a -> IO b) -> IO (Either Exit.RegistryProblem b)
+ post manager path decoder callback =
+ let
+- url = Website.route path []
+- in
+- Http.post manager url [] Exit.RP_Http $
+- \body ->
++ mkBodyCallback url body =
+ case D.fromByteString decoder body of
+ Right a -> Right <$> callback a
+ Left _ -> return $ Left $ Exit.RP_Data url body
++ postOnline url cb =
++ Http.post manager url [] Exit.RP_Http cb
++ performPost f url =
++ f url (mkBodyCallback url)
++ in
++ do
++ maybeFile <- Env.lookupEnv "GUIX_ELM_OFFLINE_REGISTRY_FILE"
++ case (path, maybeFile) of
++ ( "/all-packages", Just file ) ->
++ performPost postOffline file
++ ( _, _ ) ->
++ -- don't know how to handle other endpoints yet
++ performPost postOnline (Website.route path [])
++
++postOffline :: String -> (BS.ByteString -> IO a) -> IO a
++postOffline file callback = do
++ body <- BS.readFile file
++ callback body
+
+
+
+--
+2.32.0
+
diff --git a/guix/build-system/elm.scm b/guix/build-system/elm.scm
new file mode 100644
index 0000000000..b54954bf4e
--- /dev/null
+++ b/guix/build-system/elm.scm
@@ -0,0 +1,172 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2022 Philip McGrath <philip@philipmcgrath.com>
+;;;
+;;; 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 elm)
+ #:use-module (guix store)
+ #:use-module (guix utils)
+ #:use-module (guix packages)
+ #:use-module (guix gexp)
+ #:use-module (guix monads)
+ #:use-module (guix search-paths)
+ #:use-module (guix git-download)
+ #:use-module (guix build-system)
+ #:use-module (guix build-system gnu)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
+ #:export (elm->package-name
+ guix-package->elm-name
+ infer-elm-package-name
+ elm-package-origin
+ %elm-build-system-modules
+ %elm-default-modules
+ elm-build
+ elm-build-system))
+
+(define (elm->package-name name)
+ "Given the NAME of an Elm package, return a Guix-style package name."
+ (let ((converted
+ (string-join (string-split (string-downcase name) #\/) "-")))
+ (if (string-prefix? "elm-" converted)
+ converted
+ (string-append "elm-" converted))))
+
+(define (guix-package->elm-name package)
+ "Given an Elm PACKAGE, return the possibly-inferred upstream name, or #f the
+upstream name is not specified and can't be inferred."
+ (or (assoc-ref (package-properties package) 'upstream-name)
+ (infer-elm-package-name (package-name package))))
+
+(define (infer-elm-package-name guix-name)
+ "Given the GUIX-NAME of an Elm package, return the inferred upstream name,
+or #f if it can't be inferred. If the result is not #f, supplying it to
+'elm->package-name' would produce GUIX-NAME.
+
+See also 'guix-package->elm-name', which respects the 'upstream-name'
+property."
+ (define (parts-join part0 parts)
+ (string-join (cons part0 parts) "-"))
+ (match (string-split guix-name #\-)
+ (("elm" "explorations" part0 parts ...)
+ (string-append "elm-explorations/"
+ (parts-join part0 parts)))
+ (("elm" owner part0 parts ...)
+ (string-append owner "/" (parts-join part0 parts)))
+ (("elm" repo)
+ (string-append "elm/" repo))
+ (_
+ #f)))
+
+(define (elm-package-origin elm-name version hash)
+ "Return an origin for the Elm package with upstream name ELM-NAME at the
+given VERSION with sha256 checksum HASH."
+ ;; elm requires this very specific repository structure and tagging regime
+ (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url (string-append "https://github.com/" elm-name))
+ (commit version)))
+ (file-name (git-file-name (elm->package-name elm-name) version))
+ (sha256 hash)))
+
+(define %elm-build-system-modules
+ ;; Build-side modules imported by default.
+ `((guix build elm-build-system)
+ (guix build json)
+ (guix build union)
+ ,@%gnu-build-system-modules))
+
+(define %elm-default-modules
+ ;; Modules in scope in the build-side environment.
+ '((guix build elm-build-system)
+ (guix build utils)
+ (guix build json)
+ (guix build union)))
+
+(define (default-elm)
+ "Return the default Elm package for builds."
+ ;; Lazily resolve the binding to avoid a circular dependency.
+ (let ((elm (resolve-interface '(gnu packages elm))))
+ (module-ref elm 'elm)))
+
+(define* (lower name
+ #:key source inputs native-inputs outputs system target
+ (implicit-elm-package-inputs? #t)
+ (elm (default-elm))
+ #:allow-other-keys
+ #:rest arguments)
+ "Return a bag for NAME."
+ (define private-keywords
+ '(#:target #:implicit-elm-package-inputs? #:elm #:inputs #:native-inputs))
+ (cond
+ (target
+ ;; Cross-compilation is not yet supported. It should be easy, though,
+ ;; since the build products are all platform-independent.
+ #f)
+ (else
+ (bag
+ (name name)
+ (system system)
+ (host-inputs
+ `(,@(if source
+ `(("source" ,source))
+ '())
+ ,@inputs
+ ("elm" ,elm)
+ ;; TODO: probably don't need most of (standard-packages)
+ ,@(standard-packages)))
+ (outputs outputs)
+ (build elm-build)
+ (arguments (strip-keyword-arguments private-keywords arguments))))))
+
+(define* (elm-build name inputs
+ #:key
+ source
+ (tests? #t)
+ (phases '%standard-phases)
+ (outputs '("out"))
+ (search-paths '())
+ (system (%current-system))
+ (guile #f)
+ (imported-modules %elm-build-system-modules)
+ (modules %elm-default-modules))
+ "Build SOURCE using ELM."
+ (define builder
+ (with-imported-modules imported-modules
+ #~(begin
+ (use-modules #$@(sexp->gexp modules))
+ (elm-build #:name #$name
+ #:source #+source
+ #:system #$system
+ #:tests? #$tests?
+ #:phases #$phases
+ #:outputs #$(outputs->gexp outputs)
+ #:search-paths '#$(sexp->gexp
+ (map search-path-specification->sexp
+ search-paths))
+ #:inputs #$(input-tuples->gexp inputs)))))
+ (mlet %store-monad ((guile (package->derivation (or guile (default-guile))
+ system #:graft? #f)))
+ (gexp->derivation name builder
+ #:system system
+ #:guile-for-build guile)))
+
+(define elm-build-system
+ (build-system