Ajouter des dépendances

Ce tutoriel concerne l’utilisation des dépendances avec fpm et comment réutiliser des projets fpm existants.

Utiliser la librairie standard

Nous commençons un nouveau projet avec fpm où nous voulons construire une application en ligne de commandes pour lire un fichier, trouver un certain motif et le remplacer. Comme nous ne voulons pas écrire la fonction de remplacement nous-mêmes, nous allons utiliser la bibliothèque standard Fortran (stdlib) comme dépendance. Dans le manifeste du paquet, nous définissons stdlib dans la table dependencies :

fpm.toml
name = "demo"
version = "0.1.0"

[dependencies]
stdlib.git = "https://github.com/fortran-lang/stdlib"
stdlib.branch = "stdlib-fpm"

Créons maintenant un module avec une procédure pour effectuer la substitution. Cela nécessite trois étapes :

  1. lire une ligne complète sur une unité Fortran

  2. remplacer le motif dans la chaîne

  3. écrire la nouvelle chaîne sur une sortie

Nous utiliserons à cet effet la fonction replace_all du module stdlib_strings. L’implémentation est présentée ici

src/demo.f90
module demo
  use stdlib_io, only : getline
  use stdlib_strings, only : replace_all
  implicit none
  private

  public :: substitute

contains

  !> Read all lines from input, replace pattern and print it to output
  subroutine substitute(input, output, pattern, replacement)
    !> Formatted input unit
    integer, intent(in) :: input
    !> Formatted output unit
    integer, intent(in) :: output
    !> Pattern to replace in input
    character(len=*), intent(in) :: pattern
    !> Replacement for pattern in output
    character(len=*), intent(in) :: replacement

    character(len=:), allocatable :: line
    integer :: stat

    do
      call getline(input, line, stat)
      if (stat /= 0) exit
      write(output, '(a)') replace_all(line, pattern, replacement)
    end do
  end subroutine substitute

end module demo

Enfin, nous avons besoin de gérer la ligne de commandes pour utiliser notre nouvelle fonction.

app/main.f90
program main
  use, intrinsic :: iso_fortran_env, only : output_unit
  use demo, only : substitute
  implicit none
  character(len=256) :: pattern, replacement, input_file
  integer :: input

  call get_command_argument(1, pattern)
  call get_command_argument(2, replacement)
  call get_command_argument(3, input_file)

  open(newunit=input, file=input_file, status='old')
  call substitute(input, output_unit, trim(pattern), trim(replacement))
  close(input)
end program main

Nous pouvons vérifier la gestion de la ligne de commandes en exécutant avec fpm :

❯ fpm run -- demo substitute fpm.toml
name = "substitute"
version = "0.1.0"

[dependencies]
stdlib.git = "https://github.com/fortran-lang/stdlib"
stdlib.branch = "stdlib-fpm"

Ajouter un environnement de test

Avant de continuer à implémenter de nouvelles fonctionnalités, nous voulons ajouter quelques tests pour vérifier que notre implémentation continue de fonctionner au fur et à mesure que nous la modifions. Un environnement de test minimaliste est disponible avec test-drive. Comme l’environnement de tests n’est nécessaire que pour le développement du paquet lui-même, mais pas pour d’autres paquets qui pourraient à l’avenir utiliser nos modules, nous ajoutons une dépendance locale. Le paquet test-drive est ajouté dans la table dev-dependencies comme indiqué ci-dessous

fpm.toml
name = "demo"
version = "0.1.0"

[dependencies]
stdlib.git = "https://github.com/fortran-lang/stdlib"
stdlib.branch = "stdlib-fpm"

[dev-dependencies]
test-drive.git = "https://github.com/fortran-lang/test-drive"
test-drive.tag = "v0.4.0"

Note

Pour une dépendance de développement de ce type, nous imposons une version précise en spécifiant le tag que nous voulons utiliser.

Maintenant nous pouvons écrire un test unitaire simple. Puisque notre fonction fonctionne avec des numéros d’unités, nous allons créer des fichiers temporaires non nommés (« scratch ») pour créer l’entrée et capturer la sortie. Pour l’instant, nous allons simplement ajouter la substitution d’une ligne comme unique test

test/main.f90
module test_demo
  use demo, only : substitute
  use stdlib_io, only : getline
  use testdrive, only : error_type, unittest_type, new_unittest, check
  implicit none
  private

  public :: collect_demo

contains

  !> Collect all exported unit tests
  subroutine collect_demo(testsuite)
    !> Collection of tests
    type(unittest_type), allocatable, intent(out) :: testsuite(:)

    testsuite = [new_unittest("substitute", test_substitute)]
  end subroutine collect_demo

  !> Check substitution of a single line
  subroutine test_substitute(error)
    !> Error handling
    type(error_type), allocatable, intent(out) :: error
    integer :: input, output, stat
    character(len=:), allocatable :: line
    open(newunit=input, status="scratch")
    write(input, '(a)') "This is a valid test"
    rewind(input)

    open(newunit=output, status="scratch")
    call substitute(input, output, "test", "example")
    close(input)

    rewind(output)
    call getline(output, line, stat)
    close(output)

    call check(error, line, "This is a valid example")
  end subroutine test_substitute
end module test_demo

program tester
  use, intrinsic :: iso_fortran_env, only : error_unit
  use testdrive, only : run_testsuite
  use test_demo, only : collect_demo
  implicit none
  integer :: stat

  stat = 0
  call run_testsuite(collect_demo, error_unit, stat)

  if (stat > 0) then
    write(error_unit, '(i0, 1x, a)') stat, "test(s) failed!"
    error stop
  end if

end program tester

Nous lançons notre nouveau test avec fpm

❯ fpm test
  Starting substitute ... (1/1)
       ... substitute [PASSED]

La création des fichiers temporaires pour plusieurs tests unitaires sera répétitive. Ce genre de tâches peut généralement être effectué dans une procédure séparée et réutilisée dans plusieurs tests.

Dépendances spécifiques au système d’exploitation

Les dépendances peuvent également être utilisées uniquement pour des cibles spécifiques. Par exemple pour ajouter un paquet d’interface en ligne de commandes, qui n’est utilisé que pour l’exécutable mais ne fait pas partie des dépendances de la bibliothèque.

fpm.toml
name = "demo"
version = "0.1.0"

[dependencies]
stdlib.git = "https://github.com/fortran-lang/stdlib"
stdlib.branch = "stdlib-fpm"

[dev-dependencies]
test-drive.git = "https://github.com/fortran-lang/test-drive"
test-drive.tag = "v0.4.0"

[[executable]]
name = "demo"
[executable.dependencies]
M_CLI2.git = "https://github.com/urbanjost/M_CLI2"

Nous restructurons un peu notre programme principal pour utiliser M_CLI2 pour gérer l’entrée de la ligne de commandes. Le tableau unnamed contient tous les arguments positionnels de la ligne de commandes, nous utilisons toujours les deux premiers comme motif et remplacement, et utilisons tous les autres arguments comme entrées. Nous ajoutons également une option pour rediriger la sortie. Notre programme principal final se présente ainsi :

app/main.f90
program main
  use, intrinsic :: iso_fortran_env, only : output_unit
  use demo, only : substitute
  use m_cli2, only : set_args, unnamed, sget
  implicit none
  character(len=:), allocatable :: input_file, output_file, pattern, replacement
  integer :: input, output, i

  call set_args("--output:o ''")

  output_file = trim(sget("output"))
  if (len(output_file) > 0) then
    open(file=output_file, newunit=output)
  else
    output = output_unit
  end if

  pattern = trim(unnamed(1))
  replacement = trim(unnamed(2))

  do i = 3, size(unnamed)
    input_file = trim(unnamed(i))
    open(file=input_file, newunit=input, status='old')
    call substitute(input, output_unit, trim(pattern), trim(replacement))
    close(input)
  end do

  if (output /= output_unit) close(output)
end program main

Encore une fois nous vérifions le bon fonctionnement avec fpm

❯ fpm run -- demo substitute fpm.toml
name = "substitute"
version = "0.1.0"

[dependencies]
stdlib.git = "https://github.com/fortran-lang/stdlib"
stdlib.branch = "stdlib-fpm"

[dev-dependencies]
test-drive.git = "https://github.com/fortran-lang/test-drive"
test-drive.tag = "v0.4.0"

[[executable]]
name = "substitute"
[executable.dependencies]
M_CLI2.git = "https://github.com/urbanjost/M_CLI2"

La sortie comporte deux substitutions comme attendu.

Résumé

Dans ce tutoriel vous avez appris à

  • mettre un autre projet fpm en dépendance dans le manifeste de votre projet

  • utiliser des dépendances pour ajouter des tests

  • utiliser des dépendances ne concernant que l’exécutable