LoginSignup
5
1

More than 1 year has passed since last update.

fpm (Fortran Package Manager)のプロジェクトの作成例

Last updated at Posted at 2021-12-15

概要

本記事では簡単なプロジェクトを作成して,fpmの使用方法やマニフェスト(fpm.toml)の作成方法を説明します.

fpmの使用例やマニフェスト(fpm.toml)の設定項目について説明しました.fpmも徐々に機能が増えているので,それらをどう使うかが判りにくいという意見もあるかと思います.そういった声に応えられれば幸いです.

環境

  • Windows 10
  • fpm 0.5.0
  • gfortran 10.3.0 (TDM-GCC-64)

Windowsで実行し,動作確認をしています.パスの区切りは\になっています.Unix系OSで動かす場合には,ドライブレターを無視し,パスの区切りを/に置き換えてください.

プロジェクトの作成

プロジェクト概要

NaNInfなど,浮動小数点には数値ではない値があります.それらを取り扱うライブラリnon_numberを作成します.なお,本記事の目的はfpmの使い方などを説明することですので,その目的からずれないように,ライブラリは作り込みません.単精度および倍精度の浮動小数点数を受け取って,それらの値がNaNであるか否かを判別する関数is_nanのみを作成します.同じ機能を持った関数ieee_is_nanがFortran標準で用意されています1ieee_が冗長なので,短い名前で呼べるようにしたということです.

実装は簡単で,関数内でieee_is_nanを呼んでその結果を返しているだけです.異なる型に対応するために,interfaceを利用しています.

    interface is_nan
        module procedure :: is_nan_real32
        module procedure :: is_nan_real64
    end interface

contains

    logical function is_nan_real32(x)
        implicit none
        real(real32), intent(in) :: x

        is_nan_real32 = ieee_is_nan(x)
    end function is_nan_real32

    logical function is_nan_real64(x)
        implicit none
        real(real64), intent(in) :: x

        is_nan_real64 = ieee_is_nan(x)
    end function is_nan_real64

fpmプロジェクトの作成

fpm newを実行してプロジェクトを作成します.ライブラリを作成するので--libオプションを付けて実行します.

D:\project> fpm new non_number --lib
 + mkdir non_number
 + cd non_number
 + mkdir non_number\src
 + git init non_number
Initialized empty Git repository in D:/project/non_number/.git/
fpm: Leaving directory 'D:'

D:\project> cd non_number

D:\project\non_number>

non_numberディレクトリ以下は下記のようになっています.--libオプション(もしくは--src)を付けているので,ライブラリとしてビルドされるソースを配置するsrcディレクトリのみが作成されています.

.
├── README.md
├── fpm.toml
└── src
    └── non_number.f90

プロジェクトのビルド

ソースの編集

non_number.f90を,先述の通り編集します.

src\non_number.f90
module non_number
    use, intrinsic :: iso_fortran_env
    use, intrinsic :: ieee_arithmetic
    implicit none
    private
    public :: is_nan

    interface is_nan
        module procedure :: is_nan_real32
        module procedure :: is_nan_real64
    end interface
contains
    logical function is_nan_real32(x)
        implicit none
        real(real32), intent(in) :: x

        is_nan_real32 = ieee_is_nan(x)
    end function is_nan_real32

    logical function is_nan_real64(x)
        implicit none
        real(real64), intent(in) :: x

        is_nan_real64 = ieee_is_nan(x)
    end function is_nan_real64
end module non_number

また,プロジェクトルートに作成されたマニフェストファイルfpm.tomlも編集します.license, author, copyrightを書き換えました.

fpm.toml
name = "non_number"
version = "0.1.0"
license = "MIT"
author = "Implicit None"
copyright = "Copyright 2021, Implicit None"

[build]
auto-executables = true
auto-tests = true
auto-examples = true

[install]
library = false

ビルド

fpm buildを実行してビルドします.

D:\project\non_number>fpm build
 + mkdir build\dependencies
 + mkdir build\gfortran_2A42023B310FA28D
 + mkdir build\gfortran_2A42023B310FA28D\non_number\
 + gfortran -c .\.\src\non_number.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\src_non_number.f90.o
 + ar -rs build\gfortran_2A42023B310FA28D\non_number\libnon_number.a @build\gfortran_2A42023B310FA28D\non_number\libnon_number.a.resp
ar: creating build\gfortran_2A42023B310FA28D\non_number\libnon_number.a

出力を見ると,non_number.f90のビルドに成功してlibnon_number.aが作成されていることが確認できました.

プロジェクトのテスト

テストの作成

さて,ライブラリが作成できたとして,それをリンクしてアプリケーションを作り,それを実行してみるまで実行内容が正しいか判らないという状況はよろしくありません.プロジェクト内でテストを行い,正しさを保証しておく必要があります.

既存のfpmプロジェクトにテストを追加するには,--backfillオプション付きでfpm newを実行します.

D:\project\non_number> cd ..

D:\project> fpm new non_number --test --backfill
backfilling non_number
 + cd non_number
<INFO>   non_number\README.md already exists. Not overwriting
 + mkdir non_number\test
<INFO>   non_number\fpm.toml already exists. Not overwriting
 + git init non_number
Reinitialized existing Git repository in D:/project/non_number/.git/
fpm: Leaving directory 'D:'

testディレクトリが追加されました.

.
├── README.md
├── build\
├── fpm.toml
├── src
│   └── non_number.f90
└── test
    └── check.f90

テストを単一のファイルに書くというルールはないので,既定のファイルcheck.f90を削除して,単精度実数用のis_nanに対するテストtest_is_nan_real32.f90と倍精度実数用のis_nanに対するテストtest_is_nan_real64.f90を追加します.

.
├── README.md
├── build\
├── fpm.toml
├── src
│   └── non_number.f90
└── test
    ├── test_is_nan_real32.f90
    └── test_is_nan_real64.f90

テストの中でやっていることは簡単です.

test_is_nan_real32.f90の中では,二つのサブルーチンreturns_true_for_fp32_number_that_is_nan, returns_false_for_fp32_number_that_is_not_nanを実行しています.

returns_true_for_fp32_number_that_is_nanの中では,ieee_value(0., ieee_quiet_nan)で発生させたNaNis_nanに渡し,その戻り値が.true.かを判定し,真であれば成功,偽であればerror stopで実行を停止し手います.

retval = is_nan(ieee_value(0., ieee_quiet_nan))
if (retval .eqv. .true.) then
    print '(A)', TAB//"True"
else
    print '(A)', TAB//"False"
    error stop
end if

returns_false_for_fp32_number_that_is_not_nanの中では,少し複雑な処理をしています.is_nanNaN以外を渡した時に.false.を返すことを想定していますが,一つの値だけでテストを行って.false.が返ってきたとしても,その値以外.true.が返ってくることを否定できません.一方で,全ての値でテストを行うことは現実的ではありません.広範囲の値をテストするために,[0,1]の乱数に単精度実数の最大値をかけて大きな値を出し,その値を-2で除していくことにより,正負の幅広い範囲の値をテストできるようにしました.

また,戻り値は,一つでも.true.が返るとテストが失敗するように,前の値の戻り値と.or.を取るようにしています.本来は,retvalを論理型の配列として,all(retval .eqv. .false.)で判定する方が好ましいのですが.

integer(int32) :: i
real(real32) :: x

retval = .false.

call random_number(x)
x = x*huge(x)
do i = 1, 200
    retval = is_nan(x) .or. retval
    x = -x/2.
end do
test_is_nan_real32.f90
test\test_is_nan_real32.f90
program test_is_nan_real32
    use, intrinsic :: iso_fortran_env
    use, intrinsic :: ieee_arithmetic
    use :: non_number
    implicit none

    character, parameter :: TAB = achar(int(z'09'))

    call returns_true_for_fp32_number_that_is_nan()
    call returns_false_for_fp32_number_that_is_not_nan()
contains
    subroutine returns_true_for_fp32_number_that_is_nan()
        implicit none
        logical :: retval

        print '(A)', "is_nan returns true for fp32 number that is nan"

        retval = is_nan(ieee_value(0., ieee_quiet_nan))
        if (retval .eqv. .true.) then
            print '(A)', TAB//"True"
        else
            print '(A)', TAB//"False"
            error stop
        end if
    end subroutine returns_true_for_fp32_number_that_is_nan

    subroutine returns_false_for_fp32_number_that_is_not_nan()
        implicit none
        logical :: retval

        print '(A)', "is_nan returns false for fp32 number that is not nan"

        block
            integer(int32) :: i
            real(real32) :: x

            retval = .false.

            call random_number(x)
            x = x*huge(x)
            do i = 1, 200
                retval = is_nan(x) .or. retval
                x = -x/2.
            end do
        end block

        if (retval .eqv. .false.) then
            print '(A)', TAB//"True"
        else
            print '(A)', TAB//"False"
            error stop
        end if
    end subroutine returns_false_for_fp32_number_that_is_not_nan
end program test_is_nan_real32
test_is_nan_real64.f90
test\test_is_nan_real64.f90
program test_is_nan_real64
    use, intrinsic :: iso_fortran_env
    use, intrinsic :: ieee_arithmetic
    use :: non_number
    implicit none

    character, parameter :: TAB = achar(int(z'09'))

    call returns_true_for_fp64_number_that_is_nan
    call returns_false_for_fp64_number_that_is_not_nan()
contains
    subroutine returns_true_for_fp64_number_that_is_nan()
        implicit none
        logical :: retval

        print '(A)', "is_nan returns true for fp64 number that is nan"

        retval = is_nan(ieee_value(0d0, ieee_quiet_nan))
        if (retval .eqv. .true.) then
            print '(A)', TAB//"True"
        else
            print '(A)', TAB//"False"
            error stop
        end if
    end subroutine returns_true_for_fp64_number_that_is_nan

    subroutine returns_false_for_fp64_number_that_is_not_nan()
        implicit none
        logical :: retval

        print '(A)', "is_nan returns false for fp64 number that is not nan"

        block
            integer(int32) :: i
            real(real64) :: x

            retval = .false.

            call random_number(x)
            x = x*huge(x)
            do i = 1, 500
                retval = is_nan(x) .or. retval
                x = -x/20d0
            end do
        end block

        if (retval .eqv. .false.) then
            print '(A)', TAB//"True"
        else
            print '(A)', TAB//"False"
            error stop
        end if
    end subroutine returns_false_for_fp64_number_that_is_not_nan
end program test_is_nan_real64

テストの実行

fpm buildを実行してもテストはビルドされません.テストのビルドは,fpm testでテストを実行する際に行われます.

テストのビルドと実行を分けたい場合には,fpm test --listを利用します.fpm test --listは,プロジェクトに用意されたテストの一覧を確認するコマンドですが,テストの一覧を作成するためにビルドされるという挙動を利用して,ビルドと実行を分けています.

fpm test --listを実行すると,テストがビルドされた後,2個のテストtest_is_nan_real32, test_is_nan_real64が表示されます.

D:\project\non_number> fpm test --list
 + gfortran -c test\test_is_nan_real32.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\test_test_is_nan_real32.f90.o
 + gfortran -c test\test_is_nan_real64.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\test_test_is_nan_real64.f90.o
 + mkdir build\gfortran_2A42023B310FA28D\test\
 + gfortran  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single  build\gfortran_2A42023B310FA28D\non_number\test_test_is_nan_real32.f90.o build\gfortran_2A42023B310FA28D\non_number\libnon_number.a -o build\gfortran_2A42023B310FA28D\test\test_is_nan_real32.exe
 + gfortran  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single  build\gfortran_2A42023B310FA28D\non_number\test_test_is_nan_real64.f90.o build\gfortran_2A42023B310FA28D\non_number\libnon_number.a -o build\gfortran_2A42023B310FA28D\test\test_is_nan_real64.exe
 Matched names:
test_is_nan_real32      test_is_nan_real64

テストのビルドが完了したら,fpm testでテストを実行します.

D:\project\non_number> fpm test
is_nan returns true for fp32 number that is nan
        True
is_nan returns false for fp32 number that is not nan
        True
is_nan returns true for fp64 number that is nan
        True
is_nan returns false for fp64 number that is not nan
        True

テストを選択して実行するには,--targetを利用します.

:\project\non_number> fpm test --target test_is_nan_real32
is_nan returns true for fp32 number that is nan
        True
is_nan returns false for fp32 number that is not nan
        True

D:\project\non_number> fpm test --target test_is_nan_real64
is_nan returns true for fp64 number that is nan
        True
is_nan returns false for fp64 number that is not nan
        True

ローカルに作成したfpmプロジェクトの参照

ライブラリnon_numberを作成し,テストを実行して実装の正しさを確認しました.テストの中で戻り値の判定を計4回書いていました.これは良い実装ではないので,戻り値の判定をテストの外に切り出すことにします.

checkプロジェクト

切り出した際にnon_numberと連携しやすくするために,別のfpmプロジェクトとして,non_numberプロジェクトのローカルに構築します.

fpmの新規プロジェクトの作成は挙動がよく判らないところがあります.例えば,プロジェクトの下に新しいプロジェクトを作成しようとすると,必ずプロジェクトルートに作られます.

下記の実行例では,non_numberディレクトリに下にutilディレクトリを作成し,その中にfpmプロジェクトcheckを作成しようとしました.しかし,checknon_numberディレクトリ直下に作られてしまいました.

D:\project\non_number> mkdir util

D:\project\non_number> cd util

D:\project\non_number\util> fpm new check --lib
fpm: Entering directory 'D:\project\non_number'
 + mkdir check
 + cd check
 + mkdir check\src
 + git init check
Initialized empty Git repository in D:/project/non_number/check/.git/
fpm: Leaving directory 'D:\project\non_number'

既存プロジェクトの下にサブディレクトリを切ってプロジェクトを作成するには,プロジェクト名にサブディレクトリ名を含めて新規作成を行います.このとき,パスの区切りは/です.

D:\project\non_number>fpm new util/check --lib
 + mkdir util\check
 + cd util/check
 + mkdir util\check\src
 + git init util/check
Initialized empty Git repository in D:/project/non_number/util/check/.git/
.
├── README.md
├── build\
├── fpm.toml
├── src
│   └── non_number.f90
├── test
│   ├── test_is_nan_real32.f90
│   └── test_is_nan_real64.f90
└── util
    └── check
        ├── README.md
        ├── fpm.toml
        └── src
            └── check.f90
util\check\src\check.f90
module util_check
    implicit none
    private
    public :: check

    character, public, parameter :: TAB = achar(int(z'09'))

contains
    subroutine check(condition)
        implicit none
        logical, intent(in) :: condition

        if (condition) then
            print '(A)', TAB//"True"
        else
            print '(A)', TAB//"False"
            error stop
        end if
    end subroutine check
end module util_check
util\check\fpm.toml
name = "check"
version = "0.1.0"
license = "MIT"
author = "Implicit None"
copyright = "Copyright 2021, Implicit None"

[build]
auto-executables = true
auto-tests = true
auto-examples = true

[install]
library = false

テストも修正します.モジュールをuseし,重複している定数を削除し,結果の判定をしている箇所をサブルーチン呼出しに置き換えます.

test_is_nan_real32.f90
test\test_is_nan_real32.f90
program test_is_nan_real32
    use, intrinsic :: iso_fortran_env
    use, intrinsic :: ieee_arithmetic
    use :: non_number
    use :: util_check
    implicit none

    call returns_true_for_fp32_number_that_is_nan()
    call returns_false_for_fp32_number_that_is_not_nan()
contains
    subroutine returns_true_for_fp32_number_that_is_nan()
        implicit none
        logical :: retval

        print '(A)', "is_nan returns true for fp32 number that is nan"

        retval = is_nan(ieee_value(0., ieee_quiet_nan))

        call check(retval .eqv. .true.)
    end subroutine returns_true_for_fp32_number_that_is_nan

    subroutine returns_false_for_fp32_number_that_is_not_nan()
        implicit none
        logical :: retval

        print '(A)', "is_nan returns false for fp32 number that is not nan"

        block
            integer(int32) :: i
            real(real32) :: x

            retval = .false.

            call random_number(x)
            x = x*huge(x)
            do i = 1, 200
                retval = is_nan(x) .or. retval
                x = -x/2.
            end do
        end block

        call check(retval .eqv. .false.)
    end subroutine returns_false_for_fp32_number_that_is_not_nan
end program test_is_nan_real32
test_is_nan_real64.f90
test\test_is_nan_real64.f90
program test_is_nan_real64
    use, intrinsic :: iso_fortran_env
    use, intrinsic :: ieee_arithmetic
    use :: non_number
    use :: util_check
    implicit none

    call returns_true_for_fp64_number_that_is_nan
    call returns_false_for_fp64_number_that_is_not_nan()
contains
    subroutine returns_true_for_fp64_number_that_is_nan()
        implicit none
        logical :: retval

        print '(A)', "is_nan returns true for fp64 number that is nan"

        retval = is_nan(ieee_value(0d0, ieee_quiet_nan))
        call check(retval .eqv. .true.)
    end subroutine returns_true_for_fp64_number_that_is_nan

    subroutine returns_false_for_fp64_number_that_is_not_nan()
        implicit none
        logical :: retval

        print '(A)', "is_nan returns false for fp64 number that is not nan"

        block
            integer(int32) :: i
            real(real64) :: x

            retval = .false.

            call random_number(x)
            x = x*huge(x)
            do i = 1, 500
                retval = is_nan(x) .or. retval
                x = -x/20d0
            end do
        end block

        call check(retval .eqv. .false.)
    end subroutine returns_false_for_fp64_number_that_is_not_nan
end program test_is_nan_real64

ローカルプロジェクトの参照

ローカルにおかれたfpmプロジェクトを参照するには,プロジェクトのマニフェストfpm.toml[dependencies]の項目を設け,そこにプロジェクト名と場所を記述します.

fpm.toml
name = "non_number"
version = "0.1.0"
license = "MIT"
author = "Implicit None"
copyright = "Copyright 2021, Implicit None"

[build]
auto-executables = true
auto-tests = true
auto-examples = true
external-modules = ["stdlib_stats"]

[install]
library = false

[dependencies]
check = {path = "util/check"}

もう一度ビルドして実行したところ,正しく動作しているようです.本来はcheckプロジェクトでもテストをしなければなりませんが,都合により省略します.

D:\project\non_number> fpm test --list
 + mkdir build\gfortran_2A42023B310FA28D\test\
 + gfortran  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single  build\gfortran_2A42023B310FA28D\non_number\test_test_is_nan_real32.f90.o build\gfortran_2A42023B310FA28D\non_number\libnon_number.a -o build\gfortran_2A42023B310FA28D\test\test_is_nan_real32.exe
 + gfortran -c test\test_is_nan_real64.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\test_test_is_nan_real64.f90.o
 + gfortran  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single  build\gfortran_2A42023B310FA28D\non_number\test_test_is_nan_real64.f90.o build\gfortran_2A42023B310FA28D\non_number\libnon_number.a -o build\gfortran_2A42023B310FA28D\test\test_is_nan_real64.exe
 Matched names:
test_is_nan_real32      test_is_nan_real64

D:\project\non_number> fpm test
is_nan returns true for fp32 number that is nan
        True
is_nan returns false for fp32 number that is not nan
        True
is_nan returns true for fp64 number that is nan
        True
is_nan returns false for fp64 number that is not nan
        True

依存関係の正確な記述

上で作成したutil_checkモジュールは,non_numberモジュール内では参照せず,テストでのみ参照していました.

テストのみが必要とするfpmプロジェクトを参照する設定として,[test.dependencies]が用意されています.

fpm.toml[dependencies]を削除し,[test.dependencies]の設定を記述してみます.

fpm.toml
name = "non_number"
version = "0.1.0"
license = "MIT"
author = "Implicit None"
copyright = "Copyright 2021, Implicit None"

[build]
auto-executables = true
auto-tests = true
auto-examples = true

[install]
library = false

[test.dependencies]
check = {path = "util/check"}

しかし,この設定でビルドし直しても,util_checkモジュールが参照できないというエラーが出て,ビルドできません.

D:\project\non_number> rmdir /S build
build、よろしいですか (Y/N)? Y

D:\project\non_number> fpm build
 + mkdir build\dependencies
 + mkdir build\gfortran_2A42023B310FA28D
 + mkdir build\gfortran_2A42023B310FA28D\non_number\
 + gfortran -c .\.\src\non_number.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\src_non_number.f90.o
 + ar -rs build\gfortran_2A42023B310FA28D\non_number\libnon_number.a @build\gfortran_2A42023B310FA28D\non_number\libnon_number.a.resp
ar: creating build\gfortran_2A42023B310FA28D\non_number\libnon_number.a

D:\project\non_number> fpm test --list
<ERROR>*cmd_run*:targets error:Unable to find source for module dependency: "util_check" used by "test\test_is_nan_real32.f90"
STOP 1

設定ファイルの書き方の記事でも言及しましたが,テストのみが必要とするプロジェクトを記述するには,少なくとも一つはテスト設定を書く必要があります.

fpm.toml
name = "non_number"
version = "0.1.0"
license = "MIT"
author = "Implicit None"
copyright = "Copyright 2021, Implicit None"

[build]
auto-executables = true
auto-tests = true
auto-examples = true

[install]
library = false

[[test]]
name = "test_is_nan_real32"
source-dir = "test"
main = "test_is_nan_real32.f90"

[test.dependencies]
check = {path = "util/check"}

ここでは,単精度実数用のis_nanに対するテストのみを指定していますが,この設定があれば倍精度実数用のis_nanもビルドされるようになります.これはTOMLの書式の都合でしょうが,改善してほしいところです.

D:\project\non_number> fpm test --list
 + gfortran -c .\util/check\src\check.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\util_check_src_check.f90.o
 + ar -rs build\gfortran_2A42023B310FA28D\non_number\libnon_number.a @build\gfortran_2A42023B310FA28D\non_number\libnon_number.a.resp
 + gfortran -c test\test_is_nan_real32.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\test_test_is_nan_real32.f90.o
 + gfortran -c test\test_is_nan_real64.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\test_test_is_nan_real64.f90.o
 + mkdir build\gfortran_2A42023B310FA28D\test\
 + gfortran  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single  build\gfortran_2A42023B310FA28D\non_number\test_test_is_nan_real32.f90.o build\gfortran_2A42023B310FA28D\non_number\libnon_number.a -o build\gfortran_2A42023B310FA28D\test\test_is_nan_real32.exe
 + gfortran  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single  build\gfortran_2A42023B310FA28D\non_number\test_test_is_nan_real64.f90.o build\gfortran_2A42023B310FA28D\non_number\libnon_number.a -o build\gfortran_2A42023B310FA28D\test\test_is_nan_real64.exe
 Matched names:
test_is_nan_real32      test_is_nan_real64

D:\project\non_number> fpm test
is_nan returns true for fp32 number that is nan
        True
is_nan returns false for fp32 number that is not nan
        True
is_nan returns true for fp64 number that is nan
        True
is_nan returns false for fp64 number that is not nan
        True

プロジェクトのデモ

デモの作成

ライブラリの作成方法を示すデモを作成しておくと,ライブラリを利用するユーザの助けになります.

既存のfpmプロジェクトにデモを追加するには,テストを追加したの同様に,--backfillオプション付きでfpm newを実行します.

D:\project\non_number> cd ..

D:\project> fpm new non_number --example --backfill
backfilling non_number
 + cd non_number
<INFO>   non_number\README.md already exists. Not overwriting
 + mkdir non_number\example
<INFO>   non_number\fpm.toml already exists. Not overwriting
 + git init non_number
Reinitialized existing Git repository in D:/project/non_number/.git/
fpm: Leaving directory 'D:'

exampleディレクトリが追加されました.

.
├── README.md
├── build\
├── example
│   └── demo.f90
├── fpm.toml
├── src\
├── test\
└── util
    └── check\

デモの作成については,テストと同じです.単一のファイルに書くというルールはありません.今後,複数のデモを追加することを想定して,既定のファイル名demo.f90ではなくdemo_is_nan.f90を用いる事にします.

.
├── README.md
├── build\
├── example
│   └── demo_is_nan.f90
├── fpm.toml
├── src\
├── test\
└── util
    └── check\

デモとして,配列の平均値meanに長さ0の配列を渡した際に発生するNaNを捕まえる処理を実装しました.

demo_is_nan.f90
example\demo_is_nan.f90
program demo_is_nan
    use, intrinsic :: iso_fortran_env
    use :: non_number
    use :: util_check
    implicit none

    character(*), parameter :: fmt = '(A,*(g0:,","))'

    real(real32), allocatable :: x(:)
    real(real32) :: x_mean

    block
        integer(int32) :: i
        x = [(i, i=1, 10)]
        x_mean = mean(x)

        print fmt, "vals = ", x
        print fmt, "mean = ", x_mean
    end block

    block
        x = [real(real32) ::]
        x_mean = mean(x)

        if (.not. is_nan(x_mean)) then
            print *, TAB//"value error: x_mean is nan"
        end if
    end block

contains
    real(real32) function mean(x)
        implicit none
        real(real32), intent(in) :: x(:)

        mean = sum(x)/size(x)
    end function mean
end program demo_is_nan

デモの実行

デモはテストとは異なり,fpm buildでビルドされます.

D:\project\non_number> fpm build
 + gfortran -c example\demo_is_nan.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\example_demo_is_nan.f90.o
 + mkdir build\gfortran_2A42023B310FA28D\example\
 + gfortran  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single  build\gfortran_2A42023B310FA28D\non_number\example_demo_is_nan.f90.o build\gfortran_2A42023B310FA28D\non_number\libnon_number.a -o build\gfortran_2A42023B310FA28D\example\demo_is_nan.exe

実行は,--exampleオプション付きでfpm runを実行します.

D:\project\non_number> fpm run --example
vals = 1.00000000,2.00000000,3.00000000,4.00000000,5.00000000,6.00000000,7.00000000,8.00000000,9.00000000,10.0000000
mean = 5.50000000
 value error: x_mean is nan

デモの一覧を確認するには,fpm run --example --listを実行します.デモを選択して実行するには,--targetを利用します.

D:\project\non_number> fpm run --example --list
 Matched names:
demo_is_nan

オンラインのfpmプロジェクトの参照

関数meanはFortranの標準ライブラリstdlibに実装されているので,わざわざ作る必要はありません.stdlibはgithubにリポジトリが存在するので,それを参照します.参照するには,プロジェクトのマニフェストfpm.toml[dependencies]の項目を設け,そこにプロジェクト名と場所を記述します.

fpm.toml
name = "non_number"
version = "0.1.0"
license = "MIT"
author = "Implicit None"
copyright = "Copyright 2021, Implicit None"

[build]
auto-executables = true
auto-tests = true
auto-examples = true

[install]
library = false

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

[[test]]
name = "test_is_nan_real32"
source-dir = "test"
main = "test_is_nan_real32.f90"

[test.dependencies]
check = {path = "util/check"}

demo_is_nan.f90use :: stdlib_statsを追記し,ソース内のmeanの定義を削除します.

fpm buildを実行すると,githubからstdlibが取得され,同時にビルド・リンクが行われます.

D:\project\non_number>fpm build
Initialized empty Git repository in D:/project/non_number/build/dependencies/stdlib/.git/
remote: Enumerating objects: 91, done.
remote: Counting objects: 100% (91/91), done.
remote: Compressing objects: 100% (83/83), done.
remote: Total 91 (delta 21), reused 43 (delta 7), pack-reused 0
Unpacking objects: 100% (91/91), 202.57 KiB | 1.07 MiB/s, done.
From https://github.com/fortran-lang/stdlib
 * branch            stdlib-fpm -> FETCH_HEAD
:(中略)
 + gfortran  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single  build\gfortran_2A42023B310FA28D\non_number\example_demo_is_nan.f90.o build\gfortran_2A42023B310FA28D\non_number\libnon_number.a -o build\gfortran_2A42023B310FA28D\example\demo_is_nan.exe

D:\project\non_number> fpm run --example --target demo_is_nan
vals = 1.00000000,2.00000000,3.00000000,4.00000000,5.00000000,6.00000000,7.00000000,8.00000000,9.00000000,10.0000000
mean = 5.50000000
 value error: x_mean is nan

依存関係の正確な記述

stdlibは,non_numberモジュール内でもテストでも参照せず,exampleでのみ参照していました.

デモのみが必要とするfpmプロジェクトを参照する設定として,[example.dependencies]が用意されています.[dependencies]の代わりに[example.dependencies]を記述するのですが,これもテストと同じで,デモのみが必要とするプロジェクトを記述するには,少なくとも一つはデモ設定を書く必要があります.

fpm.toml
name = "non_number"
version = "0.1.0"
license = "MIT"
author = "Implicit None"
copyright = "Copyright 2021, Implicit None"

[build]
auto-executables = true
auto-tests = true
auto-examples = true

[install]
library = false

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

[[test]]
name = "test_is_nan_real32"
source-dir = "test"
main = "test_is_nan_real32.f90"

[test.dependencies]
check = {path = "util/check"}
D:\project\non_number> rmdir /S build
build、よろしいですか (Y/N)? Y

D:\project\non_number> fpm build
 + mkdir build\dependencies
<ERROR>*cmd_build*:target error:Unable to find source for module dependency: "stdlib_stats" used by "example\demo_is_nan.f90"
STOP 1

デモの設定を追記してビルドすると,ビルドに成功します.

fpm.toml
name = "non_number"
version = "0.1.0"
license = "MIT"
author = "Implicit None"
copyright = "Copyright 2021, Implicit None"

[build]
auto-executables = true
auto-tests = true
auto-examples = true

[install]
library = false

[[example]]
name = "demo_is_nan"
source-dir = "example"
main = "demo_is_nan.f90"

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

[[test]]
name = "test_is_nan_real32"
source-dir = "test"
main = "test_is_nan_real32.f90"

[test.dependencies]
check = {path = "util/check"}
D:\project\non_number> fpm build
 + mkdir build\dependencies
Initialized empty Git repository in D:/project/non_number/build/dependencies/stdlib/.git/
remote: Enumerating objects: 91, done.
remote: Counting objects: 100% (91/91), done.
remote: Compressing objects: 100% (83/83), done.
remote: Total 91 (delta 21), reused 43 (delta 7), pack-reused 0
Unpacking objects: 100% (91/91), 202.57 KiB | 1.12 MiB/s, done.
From https://github.com/fortran-lang/stdlib
 * branch            stdlib-fpm -> FETCH_HEAD
:(中略)
 + gfortran  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single  build\gfortran_2A42023B310FA28D\non_number\example_demo_is_nan.f90.o build\gfortran_2A42023B310FA28D\non_number\libnon_number.a -o build\gfortran_2A42023B310FA28D\example\demo_is_nan.exe

D:\project\non_number> fpm run --example --target demo_is_nan
vals = 1.00000000,2.00000000,3.00000000,4.00000000,5.00000000,6.00000000,7.00000000,8.00000000,9.00000000,10.0000000
mean = 5.50000000
 value error: x_mean is nan

インストール済みライブラリの参照

先ほどは,githubにあるstdlibを参照しましたが,既にインストールしている場合もあります.

その場合は,[build]の項目でlinkおよびexternal-modulesを設定します.

fpm.toml
name = "non_number"
version = "0.1.0"
license = "MIT"
author = "Implicit None"
copyright = "Copyright 2021, Implicit None"

[build]
auto-executables = true
auto-tests = true
auto-examples = true
external-modules = ["stdlib_stats"]
link = "stdlib"

[install]
library = false

[[test]]
name = "test_is_nan_real32"
source-dir = "test"
main = "test_is_nan_real32.f90"

[test.dependencies]
check = {path = "util/check"}

ライブラリおよびモジュール(.mod)の置き場所にパスが通っている場合は,ビルドの際にオプションを追加する必要はありません.

D:\project\non_number> rmdir /S build
build、よろしいですか (Y/N)? Y

D:\project\non_number> fpm build
 + mkdir build\dependencies
 + mkdir build\gfortran_2A42023B310FA28D
 + mkdir build\gfortran_2A42023B310FA28D\non_number\
 + gfortran -c .\.\src\non_number.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\src_non_number.f90.o
 + gfortran -c .\util/check\src\check.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\util_check_src_check.f90.o
 + ar -rs build\gfortran_2A42023B310FA28D\non_number\libnon_number.a @build\gfortran_2A42023B310FA28D\non_number\libnon_number.a.resp
ar: creating build\gfortran_2A42023B310FA28D\non_number\libnon_number.a
 + gfortran -c example\demo_is_nan.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\example_demo_is_nan.f90.o
 + mkdir build\gfortran_2A42023B310FA28D\example\
 + gfortran  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single  build\gfortran_2A42023B310FA28D\non_number\example_demo_is_nan.f90.o build\gfortran_2A42023B310FA28D\non_number\libnon_number.a -lstdlib -o build\gfortran_2A42023B310FA28D\example\demo_is_nan.exe

D:\project\non_number> fpm run --example --target demo_is_nan
vals = 1.00000000,2.00000000,3.00000000,4.00000000,5.00000000,6.00000000,7.00000000,8.00000000,9.00000000,10.0000000
mean = 5.50000000
 value error: x_mean is nan

正確な依存関係の記述

インストールされたstdlibをリンクしましたが,ログを見ると,non_numberの静的ライブラリにstdlibがリンクされています.

build\gfortran_2A42023B310FA28D\non_number\libnon_number.a -lstdlib -o

しかし,non_numberはstdlibを必要としておらず,リンクが必要なのは,デモ(demo_is_nan.exe)だけでした.リンクの設定linkは,[[example]][[test]]にも記述できるので,これを利用する事でリンクするターゲットを正確に設定できます.ただし,external-modules[build]にしか記述できません.

fpm.toml
name = "non_number"
version = "0.1.0"
license = "MIT"
author = "Implicit None"
copyright = "Copyright 2021, Implicit None"

[build]
auto-executables = true
auto-tests = true
auto-examples = true
external-modules = ["stdlib_stats"]

[install]
library = false

[[example]]
name = "demo_is_nan"
source-dir = "example"
main = "demo_is_nan.f90"
link = "stdlib"

[[test]]
name = "test_is_nan_real32"
source-dir = "test"
main = "test_is_nan_real32.f90"

[test.dependencies]
check = {path = "util/check"}

[[example]]linkの設定を移動してビルドすると,fpm buildのログの最後1行で,demo_is_nan.exeのリンクの際にstdlibがリンクされていることを確認できます.

D:\project\non_number> rmdir /S build
build、よろしいですか (Y/N)? Y

D:\project\non_number> fpm build
 + mkdir build\dependencies
 + mkdir build\gfortran_2A42023B310FA28D
 + mkdir build\gfortran_E231ED6BEC2C0EC6
 + mkdir build\gfortran_2A42023B310FA28D\non_number\
 + gfortran -c .\.\src\non_number.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\src_non_number.f90.o
 + gfortran -c .\util/check\src\check.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\util_check_src_check.f90.o
 + ar -rs build\gfortran_2A42023B310FA28D\non_number\libnon_number.a @build\gfortran_2A42023B310FA28D\non_number\libnon_number.a.resp
ar: creating build\gfortran_2A42023B310FA28D\non_number\libnon_number.a
 + gfortran -c example\demo_is_nan.f90  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single -J build\gfortran_2A42023B310FA28D -Ibuild\gfortran_2A42023B310FA28D -o build\gfortran_2A42023B310FA28D\non_number\example_demo_is_nan.f90.o
 + mkdir build\gfortran_E231ED6BEC2C0EC6\example\
 + gfortran  -Wall -Wextra -Wimplicit-interface -fPIC -fmax-errors=1 -g -fcheck=bounds -fcheck=array-temps -fbacktrace -fcoarray=single  build\gfortran_2A42023B310FA28D\non_number\example_demo_is_nan.f90.o -lstdlib build\gfortran_2A42023B310FA28D\non_number\libnon_number.a -o build\gfortran_E231ED6BEC2C0EC6\example\demo_is_nan.exe

D:\project\non_number> fpm run --example --target demo_is_nan
vals = 1.00000000,2.00000000,3.00000000,4.00000000,5.00000000,6.00000000,7.00000000,8.00000000,9.00000000,10.0000000
mean = 5.50000000
 value error: x_mean is nan

パスの通っていないライブラリを参照する

先ほどは,システムにインストールされ,パスが通っているライブラリを参照しました.しかし,プロジェクトによっては,プロジェクトのルートにlibincludeディレクトリを作成し,そこにライブラリおよびモジュールを置く場合もあります.この場合は,libincludeディレクトリにパスを通すことは希で,ディレクトリを指定してビルドを行います.

例えば下記のようにライブラリとモジュールファイルを置いた場合は,fpmでビルドする際に,--flagおよび--link-flagを用いて,コンパイラおよびリンカに渡すオプションを明記します.

.
├── README.md
├── example\
├── fpm.toml
├── include
│   └── stdlib_stats.mod
├── lib
│   └── libstdlib.a
├── src\
├── test\
└── util
    └── check\
D:\project\non_number>rmdir /S build
build、よろしいですか (Y/N)? Y

D:\project\non_number>fpm build --flag "-Iinclude" --link-flag "-Llib -lstdlib"
 + mkdir build\dependencies
 + mkdir build\gfortran_D959645BECF235A9
 + mkdir build\gfortran_0C5EEBEEBACDEC93
 + mkdir build\gfortran_D959645BECF235A9\non_number\
 + gfortran -c .\.\src\non_number.f90  -Iinclude -I.\.\include -J build\gfortran_D959645BECF235A9 -Ibuild\gfortran_D959645BECF235A9 -o build\gfortran_D959645BECF235A9\non_number\src_non_number.f90.o
 + gfortran -c .\util/check\src\check.f90  -Iinclude -I.\.\include -J build\gfortran_D959645BECF235A9 -Ibuild\gfortran_D959645BECF235A9 -o build\gfortran_D959645BECF235A9\non_number\util_check_src_check.f90.o
 + ar -rs build\gfortran_D959645BECF235A9\non_number\libnon_number.a @build\gfortran_D959645BECF235A9\non_number\libnon_number.a.resp
ar: creating build\gfortran_D959645BECF235A9\non_number\libnon_number.a
 + gfortran -c example\demo_is_nan.f90  -Iinclude -I.\.\include -J build\gfortran_D959645BECF235A9 -Ibuild\gfortran_D959645BECF235A9 -o build\gfortran_D959645BECF235A9\non_number\example_demo_is_nan.f90.o
 + mkdir build\gfortran_0C5EEBEEBACDEC93\example\
 + gfortran  -Iinclude -I.\.\include  -Llib -lstdlib build\gfortran_D959645BECF235A9\non_number\example_demo_is_nan.f90.o -lstdlib build\gfortran_D959645BECF235A9\non_number\libnon_number.a -o build\gfortran_0C5EEBEEBACDEC93\example\demo_is_nan.exe

ログからわかるように,fpmは,プロジェクトルートのincludeを標準でインクルードディレクトリとして扱うオプションを付与している(-Iinclude -I.\.\includeとなってインクルードディレクトリの指定が2回現れている)ので,--flag "-Iinclude"は必要ありません.

インクルードディレクトリの名前を変更するには,ライブラリ設定[library]include-dirで名前を指定します.

コンパイラオプション付きでビルドした場合,デモやテストを実行するときにも同じオプションが必要です.

インストールの設定

ビルドしたnon_numberのインストールもfpmで可能です.fpmは,標準ではアプリケーション(実行ファイル)のインストールしかしてくれませんが,[install]の項目でlibraryを設定すると,ライブラリおよびモジュールをインストールしてくれるようになります.

fpm.toml
name = "non_number"
version = "0.1.0"
license = "MIT"
author = "Implicit None"
copyright = "Copyright 2021, Implicit None"

[build]
auto-executables = true
auto-tests = true
auto-examples = true
external-modules = ["stdlib_stats"]

[install]
library = true

[[example]]
name = "demo_is_nan"
source-dir = "example"
main = "demo_is_nan.f90"
link = "stdlib"

[[test]]
name = "test_is_nan_real32"
source-dir = "test"
main = "test_is_nan_real32.f90"

[test.dependencies]
check = {path = "util/check"}

参照fpmプロジェクトのコンパイラオプションの指定

fpmは,現状ではマニフェストfpm.tomlにコンパイラオプションを記述できません.一方で,ビルド時に--flagで指定したコンパイラオプションは,参照しているfpmプロジェクトのビルドにも適用されます.そのため,参照しているライブラリのビルドにオプションが必要な場合は,ビルドの際にまとめて指定します.

fpmはまだ発展途上なので,プリプロセスを多用するような複雑なプロジェクトはまだ作らない方がよいでしょう.他のビルドツールを用いて複雑なビルド設定を構築しておき,条件を限定したfpmプロジェクト用のソースを作成するのも,現状ではやむなしです.

マニフェスト作成の現状の方針

fpmを利用する事で,プロジェクトの構築が非常に楽になりました.しかし,まだまだ発展途上であるため,煩わしいところもあります.

煩わしさを低減するために,著者らのマニフェスト(fpm.toml)作成の方針を紹介します.

ディレクトリ名は標準(app, src, test, example)から変更しない

  • ディレクトリ名を変更すると設定の煩わしさが跳ね上がる.

外部ライブラリを参照する場合は,可能な限りfpmプロジェクトをオンラインから取得する

  • fpmプロジェクトを参照すれば,external-moduleslinkの設定やを記述する必要がなくなる.
  • プロジェクトをビルドするための環境構築が必要なくなり,ビルドや配布が簡単になる.
  • fpmプロジェクトでないライブラリでも,複雑ではない場合はfpmプロジェクトに変換して利用する.
    • githubリポジトリの場合は,forkしてfpmプロジェクトのブランチを作成する.
    • 労力に見合った恩恵がある.
  • 参照するライブラリが大規模でビルドに時間がかかる場合は,予めビルドしてローカルにインストールする.
    • 多用途で他のプロジェクトからも利用できるライブラリの場合は,プロジェクトと異なる場所に置いてパスを通しておく.
    • 単一のプロジェクトでしか使わない場合は,fpm installを用いてプロジェクトのルートにコピーする.

参照するfpmプロジェクトは[dependencies]にまとめて書く

  • 依存関係を正確に表現するために,[executable.dependencies], [test.dependencies], [example.dependencies]は利用できるが,利用するために他の設定を書く必要があって煩わしい.
  • 作成するアプリケーションやライブラリが,所属組織外で広く使われることを想定している場合は,依存関係を正確に記述する.

外部ライブラリのリンクの設定は[build]にまとめて書く

  • リンクの設定の記述に,[[executable]], [[test]], [[example]]を利用できるが,利用するために他の設定を書く必要があって煩わしい.
  • external-modules[build]にしか書けないので,linkの設定もそれほどき気を遣わなくてもよい.
  • 作成するアプリケーションやライブラリが,所属組織外で広く使われることを想定している場合は,依存関係を正確に記述する.
  1. コンパイラによってはisNanが方言として提供されています.

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1