Clean up cmake version generation.
[alexxy/gromacs.git] / cmake / gmxGenerateVersionInfo.cmake
1 # Generate Gromacs development build version information.
2 #
3 # The script generates version information for a build from a development
4 # source tree based on git repository information.
5 # It is assumed that by default the script is run in cmake script mode.
6 # If *not* called in script mode but used in generating cache variables,
7 # GEN_VERSION_INFO_INTERNAL has to be set ON.
8 #
9 # The following variables have to be previously defined:
10 # Git_EXECUTABLE        - path to git binary
11 # Git_VERSION           - git version (if not defined it's assumed that >=1.5.3)
12 # PROJECT_VERSION       - hard-coded version string, should have the following structure:
13 #                       VERSION[-dev-SUFFIX] where the VERSION can have any form and the suffix
14 #                       is optional but should start with -dev
15 # PROJECT_SOURCE_DIR    - top level source directory (which has to be in git)
16 # VERSION_C_CMAKEIN     - path to the version.c.cmakein file
17 # VERSION_C_OUT         - path to the version.c output file
18 #
19 # Output:
20 # i)  Script mode: version.c configured from the input version.c.cmakein using
21 # the variables listed below.
22 # ii) Cache variable mode: the varables below are set in cache.
23 #
24 # GMX_PROJECT_VERSION_STR   - version string
25 # GMX_GIT_HEAD_HASH         - git hash of current local HEAD
26 # GMX_GIT_REMOTE_HASH       - git hash of the first ancestor commit from the
27 #                             main Gromacs repository
28 #
29 # Szilard Pall (pszilard@cbr.su.se)
30
31 if("${PROJECT_VERSION}" STREQUAL "")
32     message(FATAL_ERROR "PROJECT_VERSION undefined!")
33 endif()
34 set(VER ${PROJECT_VERSION})
35
36 # if we're generating variables for cache unset the variables
37 if(GEN_VERSION_INFO_INTERNAL)
38     set(GMX_PROJECT_VERSION_STR)
39     set(GMX_GIT_HEAD_HASH)
40     set(GMX_GIT_REMOTE_HASH)
41 endif()
42
43 # bail if the source tree is not in a git repository
44 if(NOT EXISTS "${PROJECT_SOURCE_DIR}/.git")
45     message(FATAL_ERROR "Project source directory ${PROJECT_SOURCE_DIR} not in git")
46 endif()
47
48 # if git executable exists and it's compatible version
49 # build the development version string
50 # this should at some point become VERSION_LESS
51 if(EXISTS "${Git_EXECUTABLE}" AND NOT Git_VERSION STRLESS "1.5.3")
52     # refresh git index
53     execute_process(COMMAND ${Git_EXECUTABLE} update-index -q --refresh
54         WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
55         TIMEOUT 5
56         OUTPUT_QUIET
57         ERROR_VARIABLE EXEC_ERR
58         OUTPUT_STRIP_TRAILING_WHITESPACE
59     )
60
61     # get the full hash of the current HEAD
62     execute_process(COMMAND ${Git_EXECUTABLE} rev-parse HEAD
63         WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
64         OUTPUT_VARIABLE HEAD_HASH
65         ERROR_VARIABLE EXEC_ERR
66         OUTPUT_STRIP_TRAILING_WHITESPACE
67     )
68     set(GMX_GIT_HEAD_HASH ${HEAD_HASH})
69     # extract the shortened hash (7 char)
70     execute_process(COMMAND ${Git_EXECUTABLE} rev-parse --short HEAD
71         WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
72         OUTPUT_VARIABLE HEAD_HASH_SHORT
73         ERROR_VARIABLE EXEC_ERR
74         OUTPUT_STRIP_TRAILING_WHITESPACE
75     )
76
77     # if there are local uncommitted changes, the build gets labeled "dirty"
78     execute_process(COMMAND ${Git_EXECUTABLE} diff-index --name-only HEAD
79         WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
80         OUTPUT_VARIABLE SRC_LOCAL_CHANGES
81         ERROR_VARIABLE EXEC_ERR
82         OUTPUT_STRIP_TRAILING_WHITESPACE
83     )
84     if(NOT "${SRC_LOCAL_CHANGES}" STREQUAL "")
85         set(DIRTY_STR "-dirty")
86         set(GMX_GIT_HEAD_HASH "${GMX_GIT_HEAD_HASH} (dirty)")
87     endif()
88
89     # get the date of the HEAD commit
90     execute_process(COMMAND ${Git_EXECUTABLE} rev-list -n1 "--pretty=format:%ci" HEAD
91         WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
92         OUTPUT_VARIABLE HEAD_DATE
93         ERROR_VARIABLE EXEC_ERR
94         OUTPUT_STRIP_TRAILING_WHITESPACE
95     )
96     string(REGEX REPLACE "\n| " ";" HEAD_DATE "${HEAD_DATE}")
97     list(GET HEAD_DATE 2 HEAD_DATE)
98     string(REGEX REPLACE "-" "" HEAD_DATE "${HEAD_DATE}")
99
100     # compile the version string suffix
101     set(VERSION_STR_SUFFIX "${HEAD_DATE}-${HEAD_HASH_SHORT}${DIRTY_STR}")
102
103     # find the names of remotes that are located on the official gromacs
104     # git/gerrit servers
105     execute_process(COMMAND ${Git_EXECUTABLE} config --get-regexp
106                     "remote\\..*\\.url" "\\.gromacs\\.org[:/].*gromacs(\\.git)?$"
107         WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
108         OUTPUT_VARIABLE GMX_REMOTES
109         ERROR_VARIABLE EXEC_ERR
110         OUTPUT_STRIP_TRAILING_WHITESPACE
111     )
112     # if there are remotes from the gromacs git servers, try to find ancestor
113     # commits of the current HEAD from this remote;
114     # otherwise, label the build "unknown"
115     if("${GMX_REMOTES}" STREQUAL "")
116         set(VERSION_STR_SUFFIX "${VERSION_STR_SUFFIX}-unknown")
117         set(GMX_GIT_REMOTE_HASH "unknown")
118     else()
119         string(REPLACE "\n" ";" GMX_REMOTES ${GMX_REMOTES})
120         # construct a command pipeline that produces a reverse-time-ordered
121         # list of commits and their annotated names in GMX_REMOTES
122         # the max-count limit is there to put an upper bound on the execution time
123         set(BASEREVCOMMAND "COMMAND ${Git_EXECUTABLE} rev-list --max-count=1000 HEAD")
124         foreach(REMOTE ${GMX_REMOTES})
125             string(REGEX REPLACE "remote\\.(.*)\\.url.*" "\\1" REMOTE ${REMOTE})
126             set(BASEREVCOMMAND "${BASEREVCOMMAND} COMMAND ${Git_EXECUTABLE} name-rev --stdin --refs=refs/remotes/${REMOTE}/*")
127         endforeach(REMOTE)
128         # this is necessary for CMake to properly split the variable into
129         # parameters for execute_process().
130         string(REPLACE " " ";" BASEREVCOMMAND ${BASEREVCOMMAND})
131         # find the first ancestor in the list provided by rev-list (not
132         # necessarily the last though) which is in GMX_REMOTE, extract the
133         # hash and the number of commits HEAD is ahead with
134         execute_process(${BASEREVCOMMAND}
135             WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
136             OUTPUT_VARIABLE ANCESTOR_LIST
137         )
138         string(REGEX REPLACE "\n" ";" ANCESTOR_LIST "${ANCESTOR_LIST}")
139
140         set(AHEAD 0)
141         set(GMX_GIT_REMOTE_HASH "")
142         foreach(ANCESTOR ${ANCESTOR_LIST})
143             string(REPLACE "\n" "" HASH_AND_REVNAMES "${ANCESTOR}")
144             string(REPLACE " " ";" HASH_AND_REVNAMES "${HASH_AND_REVNAMES}")
145             list(LENGTH HASH_AND_REVNAMES COUNT)
146             # stop and set the hash if we have a hit, otherwise loop and count
147             # how far ahead is the local repo
148             if(COUNT GREATER 1)
149                 LIST(GET HASH_AND_REVNAMES 0 GMX_GIT_REMOTE_HASH)
150                 break()
151             endif()
152             math(EXPR AHEAD ${AHEAD}+1)
153         endforeach(ANCESTOR)
154         # mark the build "local" if didn't find any commits that are from
155         # remotes/${GMX_REMOTE}/*
156         if("${GMX_GIT_REMOTE_HASH}" STREQUAL "")
157             set(GMX_GIT_REMOTE_HASH "unknown")
158             set(VERSION_STR_SUFFIX "${VERSION_STR_SUFFIX}-local")
159         # don't print the remote hash if there are no local commits
160         elseif("${GMX_GIT_REMOTE_HASH}" STREQUAL "${HEAD_HASH}")
161             set(GMX_GIT_REMOTE_HASH "")
162         else()
163             set(GMX_GIT_REMOTE_HASH "${GMX_GIT_REMOTE_HASH} (${AHEAD} newer local commits)")
164         endif()
165     endif()
166
167     # compile final version string, if there is already a -dev suffix in VER
168     # remove everything after this and replace it with the generated suffix
169     string(REGEX REPLACE "(.*)-dev.*" "\\1" VER "${VER}")
170     set(GMX_PROJECT_VERSION_STR "${VER}-dev-${VERSION_STR_SUFFIX}")
171 else()
172     # the version has to be defined - if not we're not using version.h/.c and set
173     # the GIT related information to "unknown"
174     message(WARNING "Source tree seems to be a repository, but no compatible git is available, using hard-coded version string")
175     set(GMX_PROJECT_VERSION_STR "${PROJECT_VERSION}")
176     set(GMX_GIT_HEAD_HASH "unknown")
177     set(GMX_GIT_REMOTE_HASH "unknown")
178 endif()
179
180 # if we're generating cache variables set these
181 # otherwise it's assumed that it's called in script mode to generate version.c
182 if(GEN_VERSION_INFO_INTERNAL)
183     set(GMX_PROJECT_VERSION_STR ${GMX_PROJECT_VERSION_STR}
184         CACHE STRING "Gromacs version string" FORCE)
185     set(GMX_GIT_HEAD_HASH ${GMX_GIT_HEAD_HASH}${DIRTY_STR}
186         CACHE STRING "Current git HEAD commit object" FORCE)
187     set(GMX_GIT_REMOTE_HASH ${GMX_GIT_REMOTE_HASH}
188         CACHE STRING "Commmit object of the nearest ancestor present in the Gromacs git repository" FORCE)
189     mark_as_advanced(GMX_GIT_HEAD_HASH GMX_GIT_REMOTE_HASH)
190 else()
191     if("${VERSION_C_CMAKEIN}" STREQUAL "")
192         message(FATAL_ERROR "Missing input parameter VERSION_C_CMAKEIN!")
193     endif()
194     if("${VERSION_C_OUT}" STREQUAL "")
195         message(FATAL_ERROR "Missing input parameter VERSION_C_OUT!")
196     endif()
197     # generate version.c
198     configure_file(${VERSION_C_CMAKEIN} ${VERSION_C_OUT})
199 endif()