Abhängigkeiten hinzufügen
Inhalt
Abhängigkeiten hinzufügen¶
Dieses Tutorial behandelt die Benutzung von Abhängigkeiten mit fpm und wie ein existierendes fpm-Projekt wiederverwenden werden kann.
Benutzung der Standardbibliothek¶
Wir beginnen mit einem neuen Projekt mit fpm, wir möchten eine Kommandozeilenanwendung bauen, die eine Datei liest, einebestimmtes Suchmuster findet und ersetzt. Da wir nicht die Funktion zum Ersetzen selbst schreiben wollen, werden wir die Standardbibliothek (stdlib) als Abhängigkeit benutzen. Im Paketmanifest definieren wir stdlib in der depedencies Tabelle:
name = "demo"
version = "0.1.0"
[dependencies]
stdlib = "*"
Zuerst müssen wir ein Modul mit einer Prozedur erstellen, das die Substitution durchführt. Diese benötigt drei Schritte:
- eine ganze Zeile von einer Adresse lesen 
- ein Muster in einem String ersetzen 
- einen neuen String in der Ausgabe schreiben 
Wir werden die replace_all-Funktion aus dem stdlib_strings-Modul für diesen Zweck verwenden. Die Implementierung ist hier angegeben
module demo
  use stdlib_io, only : get_line
  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 get_line(input, line, stat)
      if (stat /= 0) exit
      write(output, '(a)') replace_all(line, pattern, replacement)
    end do
  end subroutine substitute
end module demo
Zuletzt brauchen wir einen Kommandozeilen-Treiber, um unsere neue Funktion zu nutzen.
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
Wir können unseren Kommandozeilen-Treiber über fpm testen:
❯ fpm run -- demo substitute fpm.toml
name = "substitute"
version = "0.1.0"
[dependencies]
stdlib = "*"
Hinzufügen eines Test-Frameworks¶
Bevor wir weitermachen, um neue Funktionen zu implementieren, möchten wir einige Tests hinzufügen, um zu verifizieren, dass unsere Implementierung weiterhin funktioniert, wenn wir es ändern. Ein minimalistes Test-Framework ist verfügbar mit [test-drive]. Da das Test-Framework nur benötigt wird, wenn wir das Paket selbst entwickeln, aber nicht für andere Pakete, die in der Zukunft unsere Module nutzen, fügen wir eine lokale Abhängigkeit hinzu. Das test-drive-Paket wird in der dev-dependencies-Tabelle wie unten angegeben hinzugefügt
name = "demo"
version = "0.1.0"
[dependencies]
stdlib = "*"
[dev-dependencies]
test-drive.git = "https://github.com/fortran-lang/test-drive"
test-drive.tag = "v0.4.0"
Bemerkung
Für eine Entwicklungsabhängigkeit wie das Test-Framework wählen wir eine strikte Versions Einschränkung, indem wir den tag angeben, den wir benutzen wollen.
Wir können nun einen einfachen Test schreiben, da unsere Funktion mit Units arbeitet, werden wir temporäre Units erstellen, um den Eingang und die Ausgabe zu erfassen. Für den Moment werden wir eine einfache Ersetzung in einer Zeile als Test hinzufügen
module test_demo
  use demo, only : substitute
  use stdlib_io, only : get_line
  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 get_line(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
Wir führen unseren neuen Test mit fpm aus
❯ fpm test
  Starting substitute ... (1/1)
       ... substitute [PASSED]
Die Erstellung der temporären Units für mehrere Unit-Tests wird repetitiv sein, diese Aufgaben können in einer separatem Prozedur erledigt werden und in mehreren Tests wiederverwendet werden.
Ziel-spezifische Abhängigkeiten¶
Abhängigkeiten können auch nur für bestimmte Ziele verwendet werden. Dies kann verwendet werden, um ein Kommandozeilen-Interface-Paket hinzuzufügen, das nur für die Ausführungsdatei benutzt wird, aber nicht Teil der Bibliotheksabhängigkeiten ist.
name = "demo"
version = "0.1.0"
[dependencies]
stdlib = "*"
[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"
Wir restrukturieren unsere Hauptprogramm etwas, um [M_CLI2] zu benutzen was die Kommandozeileneingabe verarbeitet. Das unnamed-Feld enthält alle positionsbezogenen Kommandozeilenargumente, wir benutzen die ersten zwei als Muster und Ersetzung, und verwenden alle weiteren Argumente als Eingabe. Wir fügen auch eine Option hinzu, um die Ausgabe zu umleiten. Unser finales Hauptprogramm sieht wie folgt aus
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
Wir führen einen kurzen Test mit fpm aus
❯ fpm run -- demo substitute fpm.toml
name = "substitute"
version = "0.1.0"
[dependencies]
stdlib = "*"
[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"
Die Ausgabe sieht wie erwartet aus, mit zwei Ersetzungen.
Zusammenfassung
In diesem Kurs haben wir gelernt, wie man
- von einem anderen fpm-Projekt im Paketmanifest abhängen 
- Hinzufügen von Entwicklungsabhängigkeiten zum Testen 
- Verwenden von Abhängigkeiten für ausführbare Dateien