# Dealing with version numbers in Fortran

**URL:** <https://fortran-lang.discourse.group/t/dealing-with-version-numbers-in-fortran/7096>\
**Category:** Uncategorized\
**Created:** [January 2, 2024, 2:06pm UTC](https://fortran-lang.discourse.group/t/dealing-with-version-numbers-in-fortran/7096 "2024-01-02T14:06:04Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![samharrison7](https://yyz2.discourse-cdn.com/free1/user_avatar/fortran-lang.discourse.group/samharrison7/32/3868_2.png) [@samharrison7](https://fortran-lang.discourse.group/u/samharrison7)\
**Post date:** [January 2, 2024, 2:06pm UTC](https://fortran-lang.discourse.group/t/dealing-with-version-numbers-in-fortran/7096/1 "2024-01-02T14:06:04Z")

</div>

Hi all,

I’m interested in knowing other people’s approach to versioning software in Fortran. Specifically, how to tell the Fortran code itself what version it is (rather than how to release versions in GitHub).

I have [a model](https://github.com/NERC-CEH/nanofase), and currently I tell the model what version it is through a `MODEL_VERSION` variable in a [separate module](https://github.com/NERC-CEH/nanofase/blob/develop/src/VersionModule.f90). To avoid having to update this manually each time I create a new version, I get my build script to run `git describe --tags` and rewrite the `MODEL_VERSION` variable with the output. For example, [in CMake I use regex to rewrite the VersionModule.f90 file](https://github.com/NERC-CEH/nanofase/blob/develop/CMakeLists.txt#L42-L45).

This is a bit clunky (and requires the user to have Git installed - not ideal), but it’s worked for a while now. However, I’m now building the model with `fpm` and, as far as I can tell, `fpm` doesn’t (yet?) have support for custom build scripts, so I can’t easily take the same approach.

I was going to ask a question along the lines of “how do I get fpm to rewrite my `MODEL_VERSION` variable”, but I thought a broader question about different approaches to versioning Fortran code would be more useful. To my mind, the obvious options are:

- Don’t tell the code what version it is (not good for reproducibility)
- Manually update the a version variable (`MODEL_VERSION` in my case) each time I create a new release.
- My approach: Write a script to update the version number each time the code is built. How could I do this in `fpm`?

Thanks!

---

<div class="post-metadata">

**Author:** ![Beliavsky](https://avatars.discourse-cdn.com/v4/letter/b/ba8739/32.png) [@Beliavsky](https://fortran-lang.discourse.group/u/Beliavsky)\
**Post date:** [January 2, 2024, 2:13pm UTC](https://fortran-lang.discourse.group/t/dealing-with-version-numbers-in-fortran/7096/2 "2024-01-02T14:13:40Z")

</div>

There is a project

> **[GitHub - minhqdao/version-f: Semantic Versioning 2.0 in Fortran](https://github.com/minhqdao/version-f)**
>
> Semantic Versioning 2.0 in Fortran. Contribute to minhqdao/version-f development by creating an account on GitHub.

In general I track such projects in [Fortran Tools](https://github.com/Beliavsky/Fortran-Tools).

---

<div class="post-metadata">

**Author:** ![davidpfister](https://yyz2.discourse-cdn.com/free1/user_avatar/fortran-lang.discourse.group/davidpfister/32/3949_2.png) [@davidpfister](https://fortran-lang.discourse.group/u/davidpfister)\
**Post date:** [January 2, 2024, 3:17pm UTC](https://fortran-lang.discourse.group/t/dealing-with-version-numbers-in-fortran/7096/3 "2024-01-02T15:17:32Z")

</div>

I would usually use github `Release` for version control and include a .rc file to my project to embed the build version.  
In other words I use a script that:

1. Retrieves the latest version on the github repo

```powershell
function Get-LastTag {
	param(
		[parameter(Mandatory=$true, Position=0)][string] $repositoryName,
		[parameter(Mandatory=$true)][string] $gitHubOrganization,
		[parameter(Mandatory=$true)][string] $gitHubApiKey
    )
	
	process {
		
		try {
			$releasesUri = "https://api.github.com/repos/$gitHubOrganization/$repositoryName/releases";
			[hashtable] $gitHubHead = @{ Authorization = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($gitHubApiKey + ":x-oauth-basic")); }
			$lastTag = (Invoke-RestMethod -Method GET -Uri $releasesUri -Headers $gitHubHead)[0].tag_name 
		}
		catch {
			$lastTag = "0.0.0.0"
		}

		if($lastTag -notmatch "^\d*\.\d*\.\d*\.\d*$") { 
			throw "Tag not well formed : $($lastTag)" 
		}
		return $lastTag
	}
}

```

1. Increment the version number. I use major.minor.sprint.revision
2. Update the .rc file
3. Compile and make the pull request to publish the new release with the updated version number.

```powershell
function Set-NewTag {
	param(
		[parameter(Mandatory=$true)][string] $repositoryName,
		[parameter(Mandatory=$true)][string] $gitHubOrganization,
		[parameter(Mandatory=$true)][string] $gitHubApiKey,
		[parameter(Mandatory=$true)][string] $targetBranch,
		[parameter(Mandatory=$true)][string] $tagVersion,
		[parameter(Mandatory=$true)][string] $releaseNotes,
		[parameter(Mandatory=$true)][bool] $isPrerelease 
    )
	
	process {

		If($releaseNotes = "") {$releaseNotes = " "}
		
		try {
			$releaseData = @{
						tag_name = $tagVersion;
						target_commitish = $targetBranch;
						name = $tagVersion;
						body = $releaseNotes;
						draft = $false;
						prerelease = $isPrerelease;
					}
					
			$releaseParams = @{
				Uri = "https://api.github.com/repos/$gitHubOrganization/$repositoryName/releases";
				Method = "POST";
				Headers = @{ Authorization = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($gitHubApiKey + ":x-oauth-basic")); };
				ContentType = "application/json";
				Body = (ConvertTo-Json $releaseData -Compress)
			}

			Invoke-RestMethod @releaseParams 
		
		} catch {
			throw "Error during Set-NewTag - Exception : $($_.Exception.Message)"
		}
    }
}

```

That works fine on Windows, but .rc file are not mandatory here. The version can also be hard coded in any source file I guess.  
Scripts are in powershell but despite the ugly syntax they should be pretty easy to follow.

---

<div class="post-metadata">

**Author:** ![samharrison7](https://yyz2.discourse-cdn.com/free1/user_avatar/fortran-lang.discourse.group/samharrison7/32/3868_2.png) [@samharrison7](https://fortran-lang.discourse.group/u/samharrison7)\
**Post date:** [January 3, 2024, 12:04pm UTC](https://fortran-lang.discourse.group/t/dealing-with-version-numbers-in-fortran/7096/4 "2024-01-03T12:04:19Z")

</div>

Thanks both, they’re interesting approaches.

I’ve just come up with a workaround for `fpm`. I was already using [response files](https://urbanjost.github.io/M_CLI2/set_args.3m_cli2.html) to set build options, and I have just learnt that you can include system commands, so as a simple example, I can write a response file like the following (this would be to install the model):

```bash
system git describe --tags > VERSION
options install
options --profile release

```

Then I can run `fpm @install` to run this. This writes the output of `git describe --tags` to the file `VERSION`. The model would need to read this file, but that’s easily achieved, though as it would be read at runtime, it requires the file to be always locatable by the model (hmm…). Or I could modify the response file script to write directly to the `VersionModule.f90` file, but that might raise more cross-platform compatibility issues.

---

<div class="post-metadata">

**Author:** ![RonShepard](https://avatars.discourse-cdn.com/v4/letter/r/a3d4f5/32.png) [@RonShepard](https://fortran-lang.discourse.group/u/RonShepard)\
**Post date:** [January 3, 2024, 5:07pm UTC](https://fortran-lang.discourse.group/t/dealing-with-version-numbers-in-fortran/7096/5 "2024-01-03T17:07:01Z")

</div>

Here is a shell script that I use to put various compile time environment strings info into my codes.

```auto
#!/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

```

This script now assumes a git repository, but previous versions have used svn, cvs, etc. I also notice that this version sidesteps the ifort bug for the intrinsics `compiler_version()` and `compiler_options()`. I have not tested lately, maybe those are fixed now in newer ifort/ifx.

On unix/posix machines, if the script is called cte\_mod.sh, then it is invoked as

```auto
cte_mod.sh > cte_mod.F90

```

The output is a fortran file that can be compiled and linked with your code. There is an embedded main program that can be activated with the preprocessor macro -DEXE that you can use to test the module; this creates a one-file stand-alone program.

This is part of a larger program system, so you should change the line that defines the XcolverX string to something else:

```auto
XcolverX='Insert String Here'

```

I normally use make to build the codes that use this module, so within Makefile I define cte\_mod.o as a dependency, and cte\_mod.F90 as a .PHONY target. This forces a new cte\_mod.F90 to be created and compiled every time a code is built, so the info is never out of date or inconsistent.

---

<div class="post-metadata">

**Author:** ![samharrison7](https://yyz2.discourse-cdn.com/free1/user_avatar/fortran-lang.discourse.group/samharrison7/32/3868_2.png) [@samharrison7](https://fortran-lang.discourse.group/u/samharrison7)\
**Post date:** [January 3, 2024, 6:31pm UTC](https://fortran-lang.discourse.group/t/dealing-with-version-numbers-in-fortran/7096/6 "2024-01-03T18:31:28Z")

</div>

Thanks, that’s a nice approach and kind of the way I’ve been going (though in a much simpler way).

I need Windows support too and so I can’t really get away without having to create a separate batch script or similar for this. I did try using the `echo` command in a response file (as per above) to write a `.f90` file, but it turns out there’s enough differences between the command on Linux and Windows to make this approach difficult (e.g. different escape characters for new lines…). Maybe I should succumb to having different build scripts for different platforms.

---

<div class="post-metadata">

**Author:** ![jeff.irwin](https://yyz2.discourse-cdn.com/free1/user_avatar/fortran-lang.discourse.group/jeff.irwin/32/3673_2.png) [@jeff.irwin](https://fortran-lang.discourse.group/u/jeff.irwin)\
**Post date:** [January 6, 2024, 1:45am UTC](https://fortran-lang.discourse.group/t/dealing-with-version-numbers-in-fortran/7096/7 "2024-01-06T01:45:20Z")

</div>

Bash scripts, such as @RonShepard’s, can run on Windows too! If you have git installed, then you can run bash scripts within Git Bash on Windows.

I haven’t dealt with the version number problem, but I do use a single shared bash script as a universal build script which runs on Windows, Linux, and macOS. You can even use if/else logic within the script to perform slightly different actions on different OS’s, e.g. setting a different file path, or using a different CMake generator. I have an example build script here: [syntran/build.sh at main · JeffIrwin/syntran · GitHub](https://github.com/JeffIrwin/syntran/blob/main/build.sh#L18-L32)

Another approach would be to use python as a universal build script language. I prefer bash because python is a bit of a heavy dependency just for a build script, and it’s not always easy to install.

---

<div class="post-metadata">

**Author:** ![samharrison7](https://yyz2.discourse-cdn.com/free1/user_avatar/fortran-lang.discourse.group/samharrison7/32/3868_2.png) [@samharrison7](https://fortran-lang.discourse.group/u/samharrison7)\
**Post date:** [January 6, 2024, 9:13am UTC](https://fortran-lang.discourse.group/t/dealing-with-version-numbers-in-fortran/7096/8 "2024-01-06T09:13:58Z")

</div>

> [@jeff.irwin](#):
>
> Bash scripts, such as @RonShepard’s, can run on Windows too! If you have git installed, then you can run bash scripts within Git Bash on Windows

Yeah that’s a good point, though I was trying to avoid the end user from needing Git Bash (or similar) installed. Ideally they could do everything in PowerShell/cmd.

> [@jeff.irwin](#):
>
> Another approach would be to use python as a universal build script language. I prefer bash because python is a bit of a heavy dependency just for a build script, and it’s not always easy to install.

This is definitely something I’m thinking about. We already have a few Python scripts that are used for compiling data for the model, so it wouldn’t be too much of a leap to require Python for the build too. Something to ponder on!

---

<div class="post-metadata">

**Author:** ![RonShepard](https://avatars.discourse-cdn.com/v4/letter/r/a3d4f5/32.png) [@RonShepard](https://fortran-lang.discourse.group/u/RonShepard)\
**Post date:** [January 6, 2024, 1:52pm UTC](https://fortran-lang.discourse.group/t/dealing-with-version-numbers-in-fortran/7096/10 "2024-01-06T13:52:59Z")

</div>

You can also use the de facto standard preprocessor to substitute strings within the fortran source code, but for characters, you have to be careful with the quotes. Here is an example.

```auto
program xxx
   character(*), parameter :: version = "&
        &VERSION"
   write(*,'(a,1x,i0)') version, len(version)
end program xxx

$ gfortran -DVERSION=1.23.4 version.F90 && a.out
1.23.4 6

```

If you put the leading quote on the same line as the macro (`version="VERSION"`), then that blocks the preprocessor substitution. Also, this is kind of an edge case for the preprocessor, so I don’t know how portable this particular behavior is in general. That is why I ended up using sed to do the substitutions in my codes.

---

<div class="post-metadata">

**Author:** ![feenberg](https://avatars.discourse-cdn.com/v4/letter/f/c2a13f/32.png) [@feenberg](https://fortran-lang.discourse.group/u/feenberg)\
**Post date:** [January 6, 2024, 1:56pm UTC](https://fortran-lang.discourse.group/t/dealing-with-version-numbers-in-fortran/7096/11 "2024-01-06T13:56:03Z")

</div>

I haven’t used fpm yet, but I have been using this shell code

```auto
set date=`date +%Y%m%d`
cat *.for | m4 -P -Dcompdate=$date >taxsim.f

```

to put the compilation date in the source. I have a write statement:

```auto
write(*,*) compdate 0d-1

```

so that I can compile with or without a non-empty compdate macro variable. It compiles under linux, freebsd, osx and windows for me with gfortran) , and no user has reported difficulties compiling on any other host.

If you posted the problematic echo command, I wonder if someone here wouldn’t offer a solution to getting that to work on multiple prlatforms.
