Adicionando dependências

Este tutorial cobre o uso de dependências com o fpm e como reutilizar projetos fpm já existentes.

Usando a biblioteca padrão

Vamos começar com um novo projeto, queremos construir uma aplicação de linha de comando que lê um arquivo, encontra um padrão e o substitui. Dado que não queremos escrever a função de substituição nós mesmos, vamos usar a Biblioteca Padrão do Fortran (stdlib) como dependência. No manifesto do pacote vamos definir stdlib na tabela de dependencies:

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

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

Agora, criaremos o modulo com um procedimento para realizar a substituição. Isso requer três passos:

  1. ler a linha inteira de uma unidade

  2. substituir o padrão na string

  3. escrever a nova string em uma saída

Usaremos a função replace_all do módulo stdlib_strings para este propósito. A implementação é mostrada a seguir

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

Por fim, precisamos de um programa principal para usar nossa nova função.

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

Podemos conferir nosso programa rodando-o com o 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"

Adicionando um framework de testes

Before we continue implementing new features, we want to add some tests to verify that our implementation keeps working as we modify it. A minimalist testing framework is available with test-drive. Since the testing framework is only required when developing the package itself, but not for other packages which might in the future make use of our modules, we add it as a development dependency. The test-drive package is added in the dev-dependencies table as shown below

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"

Nota

Para dependências de desenvolvimento como um framework de testes vamos escolher uma versão fixa através da tag que queremos usar.

Agora podemos escrever um simples teste unitário, já que nossa função funciona com uma unit, vamos criar units do tipo scratch para criar a entrada e capturar a saída. Por enquanto, vamos adicionar uma simples substituição em uma linha como um caso de teste

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

Rodamos o nosso novo teste usando o fpm

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

Criar unidades do tipo scratch para múltiplos teste de unidade pode ser uma tarefa repetitiva, este tipo de coisa pode ser feita em um procedimento a parte e reutilizada em vários testes.

Dependências específicas do alvo

Dependências também podem ser usadas apenas para alvos específicos. Isso pode ser feito para adicionar uma interface de linha de comando que é usada no executável mas não é parte das dependências da biblioteca.

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"

Reestruturamos nosso programa principal um pouco para usar o M_CLI para lidar com a interface de linha de comando. O array unnamed contém todos os argumentos posicionais do comando, vamos usar os dois primeiros como padrão e seu substituto e usar o todos os restantes como entrada. Além disso, adicionamos a opção de redirecionar a saída. Ao final, nosso programa ficará assim

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

Mais uma vez fazemos uma verificação rápida usando o 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"

A saída está conforme o esperado, com duas substituições.

Sumário

Neste tutorial você aprendeu como

  • depender de outro projeto fpm no manifesto do pacote

  • adicionar dependências de desenvolvimento para testes

  • usar dependências para os executáveis