Git version during compilation

I would like my program to print information from git describe.

If I were using Makefile to build it, MWE would look roughly like this:

main.f90

program main
    implicit none
    print *, "Hello world, compiled from ", VERSION
end program main

Makefile

VERSION := $(shell git describe --tags --always --dirty)
FFLAGS=-cpp -DVERSION='"$(VERSION)"'

a.out: main.f90
    gfortran $(FFLAGS) main.f90

Expected output then would be something like Hello world, compiled from 1.2.3-15-hg12d837-dirty.

Is there a way to achieve this in fpm?

Thanks for any comments on this

I do something like:

#ifdef SETGITHASH
#include "GITHASH.txt"
      character(len=*), parameter :: git_hash = GITHASH
#else
      character(len=*), parameter :: git_hash = "Unknown"
#endif

and then in my fpm.toml I have

[preprocess]
[preprocess.cpp]
suffixes = ["F90"]
macros=['SETGITHASH=Yes']

I generate the GITHASH.txt file manually.

Here is a shell script that I use to create a fortran file that has compile time information.

#!/bin/sh

# shell script to create a *.F90 file with embedded compile-time-environment information.

# output is written to stdout.
# usage:  cte_mod.sh > cte_mod.F90

XgitX=`git log -1 --format=format:"%H"`   # git hash for the current commit.
XdateX=`date`
XhostX=`hostname -s`
XunameX=`uname -sr`    # -a is over 132 characters, so keep it short.
XuserX=`whoami`
XcolverX=`cat $COLUMBUS/doc/version`

cat <<EOF | sed \
-e s/XGITX/"$XgitX"/ \
-e s/XDATEX/"$XdateX"/ \
-e s/XHOSTX/"$XhostX"/ \
-e s/XUNAMEX/"$XunameX"/ \
-e s/XUSERX/"$XuserX"/ \
-e s/XCOLVERX/"$XcolverX"/
module cte_mod

   ! this module defines some compile time environment (cte) parameters.

   use, intrinsic :: iso_fortran_env, only: compiler_version, compiler_options

   implicit none

   ! the following constants are defined by the script cte_mod.sh.

   character(len=*), parameter :: cte_git    = 'XGITX'    ! hash for the current git commit.
   character(len=*), parameter :: cte_date   = 'XDATEX'   ! compile date and time.
   character(len=*), parameter :: cte_host   = 'XHOSTX'   ! compile time hostname.
   character(len=*), parameter :: cte_uname  = 'XUNAMEX'  ! OS info at compile time.
   character(len=*), parameter :: cte_user   = 'XUSERX'   ! user name who compiled the code.
   character(len=*), parameter :: cte_colver = 'XCOLVERX' ! COLUMBUS version.
#ifdef __INTEL_COMPILER
   character(len=*), parameter :: cte_cver   = 'ifort unknown version'
   character(len=*), parameter :: cte_copt   = 'ifort options unavailable'
#else
   character(len=*), parameter :: cte_cver   = compiler_version()
   character(len=*), parameter :: cte_copt   = compiler_options()
#endif

contains
   subroutine cte_print( nlist )
      ! print out the entire list of cte* parameters to unit nlist.
      implicit none
      integer, intent(in) :: nlist
      character(len=*), parameter :: cfmt='(2a)'

      write(nlist,cfmt) 'cte_git    = ', cte_git
      write(nlist,cfmt) 'cte_date   = ', cte_date
      write(nlist,cfmt) 'cte_host   = ', cte_host
      write(nlist,cfmt) 'cte_uname  = ', cte_uname
      write(nlist,cfmt) 'cte_user   = ', cte_user
      write(nlist,cfmt) 'cte_colver = ', cte_colver
      write(nlist,cfmt) 'cte_cver   = ', cte_cver
      write(nlist,cfmt) 'cte_copt   = ', cte_copt

      return
   end subroutine cte_print
end module cte_mod

#ifdef EXE
! compile with -DEXE to create a stand-alone test program.
program cte_test
   use, intrinsic :: iso_fortran_env, only: output_unit
   use cte_mod
   implicit none
   call cte_print(output_unit)
end program cte_test
#endif
EOF

I use a makefile to create the file instead of fpm, but the same approach should also work there too. My makefle executes the command

cte_mod.sh > cte_mod.F90

In my case, I execute this command every time I create the executable by using a .PHONY target. I’m not sure how best to do that with fpm. Note that there is a little test driver program built into the cte.F90 file. Also note that the ifort compiler had a bug that prevented a couple of symbols from working, so there is a bypass. That bug might be fixed now, I don’t know.

Thanks a lot to both. This approach works, though I was hoping there would be an option to pass this in fpm.toml – something like

version = "$(git describe --always --tags --dirty)"
...
[preprocess]
[preprocess.cpp]
macros=["VERSION={version}"]

But from GitHub discussion it seems it’s not possible :frowning:

Hi @rzehumat, what you’re suggesting is feasible via the external file mechanism:

git describe --always --tags --dirty > EXTERNAL_FILE
version = "EXTERNAL_FILE"
...
[preprocess]
[preprocess.cpp]
macros=["WITH_VERSION={version}"]

the reason it doesn not work for you is that fpm currently only supports the major.minor.patch format, so when reading the package manifest, it will print an “invalid version” error.

This will likely be improved soon, when more flexible version resolution is implemented.

That reminds me the env2inc program shipped with coco.
It does pretty much the same except that it generates a file to be included with preprocessor macros.
You can probably still find it on Dan Nagle website (this is a web archive because the domain has been taken over by some Russian casino). Otherwise there is a copy on github.
Last time I tried it only worked for gfortran and not intel because the output of compiler_version() is not standardized and the parsing of the version number fails. See this for a more robust parser.

I perhaps should have added a few more comments. That shell script is for one of my production codes, and a programmer would likely add and/or subtract information as appropriate for his application. Also, this is designed for compile-time information, not information that changes at run time, so everything is reduced to read-only public parameter constants which are available through a USE statement. Consistent with the other recent discussion about shells, this script uses POSIX /bin/sh, and the sed script within uses only POSIX functionality, so it should be as portable as possible at that level. Of course, the information that it accesses to create the parameter constants might be OS or filesystem dependent, but that is up to the programmer. Previous versions of this script have accessed information from the RCS and CVS revision control systems, this one uses git. It only extracts the hash code, but of course other information could be added as required for the application.

1 Like