packageguileguix

How to package the Fortran Package Manager for GNU Guix


I would like to create a Guix package of the Fortran Package Manager (FPM).

Recommended compilation steps

This is the recommended compilation procedure, taken (with slight modifications) from the FPM documentation.

  1. Clone the project, enter and checkout the latest release
git clone https://github.com/fortran-lang/fpm
cd fpm
git checkout v0.10.1
  1. Download the bootstrap version of fpm:

wget https://github.com/fortran-lang/fpm/releases/download/v0.8.0/fpm-0.8.0.F90 -0 fpm.F90

  1. Build the bootstrap version:
mkdir -p build/bootstrap
gfortran -J build/bootstrap -o build/bootstrap/fpm fpm.F90
  1. Install

./build/bootstrap/fpm install [--prefix custom/path]

This command will use the local fpm.toml file to compile and install the latest release of fpm.

What I came up with, so far

I think the best approach is to use the trivial-build-system, due to the specificity of the compilation process. So far, I have a package file with this content:

;; fortran-fpm.scm

(define-module (fortran-fpm)
  #:use-module (guix packages)
  #:use-module (guix git-download)
  #:use-module (guix build-system trivial)
  #:use-module (guix licenses)
  #:use-module (guix gexp)
  #:use-module (guix build utils)
  #:use-module (guix utils)
  ;; #:use-module (guix store)
  #:use-module (guix download)
  ;; #:use-module (guix modules)
  ;; #:use-module (guix derivations)
  #:use-module (gnu packages guile)
  #:use-module (gnu packages gcc))

(define-public fortran-fpm
  (package
   (name "fortran-fpm")
   (version "0.10.1")
   (source
    (origin
     (method git-fetch)
     (uri (git-reference
           (url "https://github.com/fortran-lang/fpm.git")
           (commit version)))
     (sha256
      (base32
       "1sh6mn1ajnyf1a5flcf8fpixn5al2spqpaxgw9g1yp4p36mfd8wz"))))
   (build-system trivial-build-system)
   (arguments
    (list
     #:builder
     (with-imported-modules
      '((guix build utils))
      #~(begin
      (use-modules (guix build utils))
      (let* ((source (assoc-ref %build-inputs "source"))
         (fpm-bootstrap-url "https://github.com/fortran-lang/fpm/releases/download/v0.8.0/fpm-0.8.0.F90")
         (bootstrap-build-dir "/build/bootstrap")
         (fpm-bootstrap-file "fpm.F90"))
        (chdir source)
        (invoke "wget" fpm-bootstrap-url "-O"
            fpm-bootstrap-file)
            (mkdir-p bootstrap-build-dir)
            (invoke "gfortran" "-J" bootstrap-build-dir "-o" (string-append bootstrap-build-dir "/fpm") fpm-bootstrap-file)
            (invoke (string-append bootstrap-build-dir "/fpm") "install"))
      ))))
   (inputs
    `(("gfortran-toolchain" ,gfortran)))
   (synopsis "Fortran Package Manager")
   (description
    "Fortran Package Manager (fpm) is a package manager and build system for Fortran.
Its key goal is to improve the user experience of Fortran programmers.
It does so by making it easier to build your Fortran program or library,
run the executables, tests, and examples, and distribute it as a dependency to other Fortran projects.
Fpm's user interface is modeled after Rust's Cargo, so if you're familiar with that tool,
you will feel at home with fpm.
Fpm's long term vision is to nurture and grow the ecosystem of modern Fortran applications and libraries.")
   (home-page "https://fpm.fortran-lang.org")
   (license expat)))

fortran-fpm ;; just for the purpose of testing it with --install-from-file

The error

I try to install using guix package -f fortran-fpm.scm --keep-failed. The build process fails, I extract the .drv.bz2 produced by the build process. Its content is:

In execvp of wget: No such file or directory
Backtrace:
           2 (primitive-load "/gnu/store/z2qpwsicr8wffhcig4m4c2ypyg1?")
In ice-9/eval.scm:
    619:8  1 (_ #(#(#(#(#<directory (guile-user) 7ffff78?> ?) ?) ?) ?))
In guix/build/utils.scm:
    822:6  0 (invoke "wget" "https://github.com/fortran-lang/fpm/re?" ?)

guix/build/utils.scm:822:6: In procedure invoke:
ERROR:
  1. &invoke-error:
      program: "wget"
      arguments: ("https://github.com/fortran-lang/fpm/releases/download/v0.8.0/fpm-0.8.0.F90" "-O" "fpm.F90")
      exit-status: 127
      term-signal: #f
      stop-signal: #f

So, apparently, wget is not in scope (first line of the log), am I wrong?

Note also that the kept derivation (if it is the correct term) folder /tmp/guix-build-fortran-fpm-0.10.1.drv-0/ is empty.

I was also considering url-fetch and download-to-store, instead of resorting to a system call, but I don't know how to make either of them work.

Question summary

  1. Which is the most suitable way to download a file within a builder?
  2. Which module(s) should I include in the builder in order to download such file?

Any other comment is welcome.

EDIT: Working solution

After following Noé Lopez's suggestions and some debugging with Copilot's assistance, I finally managed to get guix install the package with the following package definition:

(define-module (fortran-fpm)
  #:use-module (guix packages)
  #:use-module (guix git-download)
  #:use-module (guix build-system copy)
  #:use-module (guix licenses)
  #:use-module (guix gexp)
  #:use-module (guix download)
  #:use-module (gnu packages gcc))

(define-public fortran-fpm
  (package
   (name "fortran-fpm")
   (version "0.10.1")
   (source
    (origin
     (method git-fetch)
     (uri (git-reference
           (url "https://github.com/fortran-lang/fpm.git")
           (commit version)))
     (sha256
      (base32
       "1sh6mn1ajnyf1a5flcf8fpixn5al2spqpaxgw9g1yp4p36mfd8wz"))))
   (build-system copy-build-system)
   (arguments
    (list
     #:phases
     #~(modify-phases
    %standard-phases
    (add-after 'patch-generated-file-shebangs 'build
           (lambda* (#:key #:allow-other-keys)
                (let* ((source (assoc-ref %build-inputs "source"))
                   (build-dir (string-append source "/../build"))
                   (bootstrap-dir
                    (string-append build-dir "/bootstrap"))
                   (fpm-bootstrap-file
                    #$(this-package-input "bootstrap"))
                   (out (assoc-ref %outputs "out")))
                  (mkdir-p bootstrap-dir)
                  (invoke "gfortran"
                      "-J" bootstrap-dir
                      "-o" (string-append bootstrap-dir "/fpm")
                      fpm-bootstrap-file)
                  (invoke (string-append bootstrap-dir "/fpm")
                      "install"
                      "--prefix" out))))
    (add-before
     'build 'patch-fpm-toml
     (lambda* (#:key inputs #:allow-other-keys)
          (use-modules (ice-9 textual-ports) (srfi srfi-1))
          (let* ((deps-dir "local-deps")
             (deps `(("toml-f" . "toml-f")
                 ("M_CLI2" . "M_CLI2")
                 ("fortran-regex" . "fortran-regex")
                 ("jonquil" . "jonquil")
                 ("fortran-shlex" . "fortran-shlex"))))
            (mkdir-p deps-dir)
            ;; Symlink each dependency into local-deps/
            (for-each
             (lambda (dep)
               (let* ((name (car dep))
                  (dir (assoc-ref inputs name)))
             (symlink dir (string-append deps-dir "/" (cdr dep)))))
             deps)
            (substitute*
             "fpm.toml"
             (("toml-f\\.git.*")
              (string-append "toml-f.path = \"" deps-dir "/toml-f\"\n"))
             (("M_CLI2\\.git.*")
              (string-append "M_CLI2.path = \"" deps-dir "/M_CLI2\"\n"))
             (("fortran-regex\\.git.*")
              (string-append "fortran-regex.path = \"" deps-dir "/fortran-regex\"\n"))
             (("jonquil\\.git.*")
              (string-append "jonquil.path = \"" deps-dir "/jonquil\"\n"))
             (("fortran-shlex\\.git.*")
              (string-append "fortran-shlex.path = \"" deps-dir "/fortran-shlex\"\n"))
             (("^.*\\.rev.*") "")
             (("^.*\\.tag.*") "")
             )))
     )
    (delete 'install)
    )
     )
    )
   (inputs
    `(("gfortran-toolchain" ,gfortran)
      ("bootstrap"
       ,(origin
     (method url-fetch)
     (uri "https://github.com/fortran-lang/fpm/releases/download/v0.10.1/fpm-0.10.1.F90")
     (sha256
      (base32
       "1jp1hh5yjz3ghk5f1ixz06x6jl8mhh3hm89vla4yi9y2c1dx0lvm"))
     (file-name "fpm.F90")))
      ;; ("git" ,git)
      ("toml-f"
       ,(origin
     (method git-fetch)
     (uri (git-reference
           (url "https://github.com/toml-f/toml-f")
           (commit "d7b892b1d074b7cfc5d75c3e0eb36ebc1f7958c1")))
     (sha256
      (base32
       "07r78dk0q7xxrh3fjfrsx5jmf6ap21b4d5qld6ga3awki0cm75z8"))))
      ("M_CLI2"
       ,(origin
     (method git-fetch)
     (uri (git-reference
           (url "https://github.com/urbanjost/M_CLI2.git")
           (commit "7264878cdb1baff7323cc48596d829ccfe7751b8")))
     (sha256
      (base32
       "06y7zndb0qcnyq7rxwhq0vv0j4dzkdw9hqphkp13k1zqf3vz8z28"))))
      ("fortran-regex"
       ,(origin
     (method git-fetch)
     (uri (git-reference
           (url "https://github.com/perazz/fortran-regex")
           (commit "1.1.2")))
     (sha256
      (base32
       "183vxa1082kkg48rl75nxkm8j67vxpak3347dfzfbbxi0wyfklba"))))
      ("jonquil"
       ,(origin
     (method git-fetch)
     (uri (git-reference
           (url "https://github.com/toml-f/jonquil")
           (commit "4fbd4cf34d577c0fd25e32667ee9e41bf231ece8")))
     (sha256
      (base32
       "1zk3rpl5npk4qaslcbp9nay6p9dsl3sqv2m0zc6337mxqa4dmjm0"))))
      ("fortran-shlex"
       ,(origin
     (method git-fetch)
     (uri (git-reference
           (url "https://github.com/perazz/fortran-shlex")
           (commit "2.0.0")))
     (sha256
      (base32
       "0sygyjqwxyh974bmp8n5bxjs9nsdfavy5k3vhmx2v9bbl31jqk8a"))))
      ))
   (synopsis "Fortran Package Manager")
   (description
    "Fortran Package Manager (fpm) is a package manager and build system for Fortran.
Its key goal is to improve the user experience of Fortran programmers.
It does so by making it easier to build your Fortran program or library,
run the executables, tests, and examples, and distribute it as a dependency to other Fortran projects.
Fpm's user interface is modeled after Rust's Cargo, so if you're familiar with that tool,
you will feel at home with fpm.
Fpm's long term vision is to nurture and grow the ecosystem of modern Fortran applications and libraries.")
   (home-page "https://fpm.fortran-lang.org")
   (license expat)))

Big thank you to Noé Lopez again :D


Solution

  • Indeed wget is not in scope, it would have to be in your inputs (in this case native-inputs) to be in scope.

    Additionally, guix does not allow downloading files from the build environment for reproducibility. You need to have it as an input, either in the origin or in the inputs. In this way, its hash is checked and you are sure to be always using the same file (it might be modified on the server, breaking the package in the future). So for example, you could do:

    (define-public fortran-fpm
      (package
       (name "fortran-fpm")
       (version "0.10.1")
       (source
        (origin
         (method git-fetch)
         (uri (git-reference
               (url "https://github.com/fortran-lang/fpm.git")
               (commit version)))
         (sha256
          (base32
           "1sh6mn1ajnyf1a5flcf8fpixn5al2spqpaxgw9g1yp4p36mfd8wz"))))
       (build-system trivial-build-system)
       (arguments
        (list
         #:builder
         (with-imported-modules
          '((guix build utils))
          #~(begin
          (use-modules (guix build utils))
          (let* ((source (assoc-ref %build-inputs "source"))
             (bootstrap-build-dir "/build/bootstrap")
             (fpm-bootstrap-file #$(this-package-input "bootstrap")))
            (chdir source)
                (mkdir-p bootstrap-build-dir)
                (invoke "gfortran" "-J" bootstrap-build-dir "-o" (string-append bootstrap-build-dir "/fpm") fpm-bootstrap-file)
                (invoke (string-append bootstrap-build-dir "/fpm") "install"))
          ))))
       (inputs
        `(("gfortran-toolchain" ,gfortran)
          ("bootstrap" ,(origin …)))
       (synopsis "Fortran Package Manager")
       (description
        "Fortran Package Manager (fpm) is a package manager and build system for Fortran.
    Its key goal is to improve the user experience of Fortran programmers.
    It does so by making it easier to build your Fortran program or library,
    run the executables, tests, and examples, and distribute it as a dependency to other Fortran projects.
    Fpm's user interface is modeled after Rust's Cargo, so if you're familiar with that tool,
    you will feel at home with fpm.
    Fpm's long term vision is to nurture and grow the ecosystem of modern Fortran applications and libraries.")
       (home-page "https://fpm.fortran-lang.org")
       (license expat)))
    

    Lastly, using trivial-build-system is probably not a good idea, as you will be missing out on a lot of things gnu-build-system sets up for you. I recommend you use the copy-build-system (which is based on gnu-build-system) and use modify-phases to add your build steps, you can find a lot of examples of this in the Guix source.