parquet_version.f90 Source File


Source Code

!===========================================
! Author: Elmo Tempel (elmo.tempel@ut.ee)
!===========================================
!> The version of parquet-fortran itself, and nothing else.
!>
!> This module is a LEAF: it imports `parquet_settings_base` for the library's
!> informational output channel and nothing further, so it reaches neither
!> `parquet_bindings` nor the Arrow/Parquet C++ stack. That is what lets a
!> program built on one of the Arrow-free tiers -- `parquet_random`,
!> `parquet_argsort`, `parquet_sampling`, `parquet_columns`, `parquet_strings`,
!> `parquet_temporal`, `parquet_sorting` -- report which parquet-fortran it was
!> built against, with `use parquet_version` and no other cost.
!>
!> **Only `use parquet` re-exports this procedure.** Every other entry module
!> leaves it here deliberately, so that none of them grows a dependency for a
!> string that is fixed at compile time; a program importing one of those writes
!> a second `use parquet_version` line. This is a deliberate exception to the
!> rule that each module re-exports the settings its own code reads -- a version
!> string is not a setting, and nothing in this library reads it.
!>
!> It does re-export the two OUTPUT settings, `verbosity` and `message_stream`,
!> because this module can print: `parquet_get_version` emits a remark on a
!> development build whose `RELEASE_VERSION` substitution did not happen, and a
!> program importing nothing else has to be able to quiet or redirect it. That
!> is the ordinary rule applying, not a second exception to it.
!>
!> For the version of the Arrow and Parquet C++ libraries actually linked, call
!> `parquet_get_arrow_version` instead (`parquet_settings`, and so also
!> `parquet_io` and `parquet`). That query needs the C++ boundary, which is
!> exactly what this module is defined not to have.
module parquet_version
    use parquet_settings_base, only: parquet_emit_info, &
        parquet_set_verbosity, parquet_get_verbosity, &
        parquet_set_message_stream, parquet_get_message_stream
    implicit none
    private

    public :: parquet_get_version
    !
    ! The output pair, re-exported for the same reason every other emitting tier re-exports it:
    ! this module CAN print -- parquet_get_version emits the development-build remark below -- and
    ! a program whose only import is `use parquet_version` must still be able to silence it or
    ! send it elsewhere. Reaching parquet_settings for that would put the C++ boundary back into
    ! an otherwise two-file, Arrow-free import, which is the whole thing this module exists to
    ! avoid. Costs nothing: parquet_settings_base is already imported and already in the
    ! footprint. See doc/pages/operating/choosing-a-module.md's settings table, which lists it.
    public :: parquet_set_verbosity, parquet_get_verbosity
    public :: parquet_set_message_stream, parquet_get_message_stream

    !> The hand-maintained release string, "vX.Y.Z (date)". Kept in step with VERSION.txt by the
    !! release checklist in CONTRIBUTING.md; parquet_get_version compares the two and remarks when
    !! they disagree, which is how a build that skipped fpm's macro substitution announces itself.
    character(len=*), parameter :: cversion = "v2.0.0 (2026-08-24)" !! version info
#ifndef RELEASE_VERSION
#  define RELEASE_VERSION 0.1
#endif

contains

    !> Returns this library's own version string. Default (mode absent): the
    !> RELEASE_VERSION build macro's bare release number; emits an informational
    !> remark first if that disagrees with cversion (a hand-maintained
    !> "vX.Y.Z (date)" string), which signals a build that skipped fpm's macro
    !> substitution or a version bump missed on one side. mode="internal"
    !> instead returns cversion verbatim. Any other mode value is an error --
    !> in particular the linked Arrow and Parquet C++ library versions are
    !> reported by parquet_get_arrow_version, not by this procedure.
    subroutine parquet_get_version(ver_string, mode)
        implicit none
        character(len=:), allocatable, intent(out) :: ver_string !! resulting version string.
        character(len=*), intent(in), optional :: mode
        !! "internal"; absent = default RELEASE_VERSION behavior.
        integer :: i
        character(len=:), allocatable :: preview
        !
! Accept solution from https://stackoverflow.com/questions/31649691/stringify-macro-with-gnu-gfortran
! which provides the easiest way to pass a macro to a string in Fortran complying with both
! gfortran traditional cpp and the standard cpp syntaxes
#ifdef NAGFOR
! NAG drives -fpp, a Fortran preprocessor implementing NEITHER the standard cpp `#`
! stringification operator NOR gfortran's traditional-cpp continuation trick, so no spelling of
! the macro pair below compiles here (confirmed against NAG 7.2: the first gives
! "Invalid character '#'", the second "Unrecognised statement"). Take the release number from
! cversion instead. The consequence is deliberate rather than a gap: the development-build remark
! below compares ver_string against that same substring, so it can never fire under NAG -- the
! question it asks, "did fpm substitute RELEASE_VERSION?", is one this preprocessor cannot answer.
        ver_string = cversion(2:index(cversion, " ") - 1)
#else
#  ifdef __GFORTRAN__
#    define STRINGIFY_START(X) "&
#    define STRINGIFY_END(X) &X"
#  else
#    define STRINGIFY_(X) #X
#    define STRINGIFY_START(X) &
#    define STRINGIFY_END(X) STRINGIFY_(X)
#  endif

        ver_string = STRINGIFY_START(RELEASE_VERSION)
        STRINGIFY_END(RELEASE_VERSION)
#endif
        !
        i = index(cversion, " ")
        !
        if (cversion(2:i-1) /= ver_string) then ! GCOVR_EXCL_START -- gcov attribution artifact
            ! A remark rather than a warning: it says something about how this copy of the library
            ! was BUILT, not about the caller's data, and it fires on every call in a build whose
            ! macro substitution did not happen. So it goes through the informational channel, which
            ! `verbosity="silent"` quiets while leaving real warnings alone.
            call parquet_emit_info("note: this is a development build of parquet-fortran " // &
                "(library version " // trim(cversion) // ", RELEASE_VERSION " // trim(ver_string) // ")")
        end if ! GCOVR_EXCL_STOP
        !
        if (present(mode)) then
            select case (mode)
            case ("internal")
                ver_string = trim(cversion)
            case default
                ! Only a short preview of the offending value goes into the message, never the whole
                ! (unboundedly long) text: ifx 2026.1.1's ERROR STOP runtime corrupts memory once the
                ! composed message reaches 8192 bytes, and `mode` is caller-supplied with no cap.
                if (len(mode) > 100) then
                    preview = mode(1:100) // "..."
                else
                    preview = mode
                end if
                error stop "parquet_get_version: invalid mode '" // preview // &
                    "' (must be 'internal'; for the linked Arrow/Parquet C++ library versions " // &
                    "call parquet_get_arrow_version)"
            end select
        else
            ver_string = trim(ver_string)
        end if
        !
    end subroutine parquet_get_version

end module parquet_version