1# SPDX-License-Identifier: BSD-3-Clause
2
3# Generates header for which version is taken from (in order of precedence):
4# 	1) .tarball-version file
5#	2) git
6#
7# Version is checked during configuration step and for every target
8# that has check_version_h target as dependency
9
10cmake_minimum_required(VERSION 3.13)
11
12set(VERSION_CMAKE_PATH ${CMAKE_CURRENT_LIST_DIR}/version.cmake)
13
14
15# In an ideal world, every CI engine records the most basic and most
16# important information:
17# - current date and time
18# - git version of the pull request
19# - git version of the moving branch it's being merged with
20#
21# In the real world, some CI results use a random timezone without
22# telling which one or don't provide any time at all.
23string(TIMESTAMP build_start_time UTC)
24message(STATUS "SOF version.cmake starting at ${build_start_time} UTC")
25
26# Most CI engines test a temporary merge of the pull request with a
27# moving target: the latest target branch. In that case the HEAD SHA is
28# very volatile and the --parents are much more relevant and useful.
29#
30# --no-abbrev-commit because some git servers like github allow fetching
31# by full length SHA (others forbid this entirely, see last page of
32# `git help fetch-pack`)
33message(STATUS "${SOF_ROOT_SOURCE_DIRECTORY} is at git commit with parent(s):")
34# Note execute_process() failures are ignored by default (missing git...)
35execute_process(
36	COMMAND git -C "${SOF_ROOT_SOURCE_DIRECTORY}"
37		log --parents --no-abbrev-commit --decorate -n 1 HEAD
38	)
39
40
41# Don't confuse this manual _input_ file with the other, output file of
42# the same name auto-generated in the top _build_ directory by "make
43# dist", see dist.cmake
44set(TARBALL_VERSION_FILE_NAME ".tarball-version")
45
46set(TARBALL_VERSION_SOURCE_PATH "${SOF_ROOT_SOURCE_DIRECTORY}/${TARBALL_VERSION_FILE_NAME}")
47
48if(EXISTS ${TARBALL_VERSION_SOURCE_PATH})
49	file(STRINGS ${TARBALL_VERSION_SOURCE_PATH} lines ENCODING "UTF-8")
50	list(GET lines 0 GIT_TAG)
51	list(GET lines 1 GIT_LOG_HASH)
52	message(STATUS "Found ${TARBALL_VERSION_SOURCE_PATH}")
53else()
54	# execute_process() errors are not fatal by default!
55	execute_process(
56	        COMMAND git describe --tags --abbrev=12 --match v* --dirty
57		WORKING_DIRECTORY ${SOF_ROOT_SOURCE_DIRECTORY}
58		OUTPUT_VARIABLE GIT_TAG
59		OUTPUT_STRIP_TRAILING_WHITESPACE
60	)
61
62	execute_process(COMMAND git log --pretty=format:%h -1
63		WORKING_DIRECTORY ${SOF_ROOT_SOURCE_DIRECTORY}
64		OUTPUT_VARIABLE GIT_LOG_HASH
65		OUTPUT_STRIP_TRAILING_WHITESPACE
66	)
67endif()
68
69if(NOT GIT_TAG MATCHES "^v")
70	message(WARNING
71	  "git describe found ${GIT_TAG} / nothing starting with 'v'. Shallow clone?")
72	set(GIT_TAG "v0.0-0-g0000")
73endif()
74
75message(STATUS "GIT_TAG / GIT_LOG_HASH : ${GIT_TAG} / ${GIT_LOG_HASH}")
76
77string(REGEX MATCH "^v([0-9]+)[.]([0-9]+)([.]([0-9]+))?" ignored "${GIT_TAG}")
78set(SOF_MAJOR ${CMAKE_MATCH_1})
79set(SOF_MINOR ${CMAKE_MATCH_2})
80set(SOF_MICRO ${CMAKE_MATCH_4})
81
82if(NOT SOF_MICRO MATCHES "^[0-9]+$")
83	set(SOF_MICRO 0)
84endif()
85
86string(SUBSTRING "${GIT_LOG_HASH}" 0 5 SOF_TAG)
87if(NOT SOF_TAG)
88	set(SOF_TAG 0)
89endif()
90
91# Calculate source hash value, used to check ldc file and firmware compatibility
92if(EXISTS ${SOF_ROOT_SOURCE_DIRECTORY}/.git/)
93	set(SOURCE_HASH_DIR "${SOF_ROOT_BINARY_DIRECTORY}/source_hash")
94	file(MAKE_DIRECTORY ${SOURCE_HASH_DIR})
95
96	# When building with Zephyr, add a few extra files so the XTOS
97	# and Zephyr .ldc files (which have different content!) do not
98	# end up with the exact same "source checksum". The concept of
99	# source checksum is flawed (see issue #3890) but this is much
100	# better than nothing at all.
101	#
102	# Warning: ZEPHYR_CURRENT_MODULE_DIR is _undefined_ the first
103	# time we're run and _defined empty_ the second time.  To
104	# understand why look at the check_version_h target below.
105	if("${ZEPHYR_CURRENT_MODULE_DIR}" STREQUAL "")
106	  set(_sof_zephyr_dir "")
107	else()
108	  set(_sof_zephyr_dir "zephyr/")
109	endif()
110
111	# list tracked files from src directory
112	execute_process(COMMAND git ls-files src/ scripts/ ${_sof_zephyr_dir}
113			WORKING_DIRECTORY ${SOF_ROOT_SOURCE_DIRECTORY}
114			OUTPUT_FILE "${SOURCE_HASH_DIR}/tracked_file_list"
115		)
116	# calculate hash of each listed files (from file version saved in file system)
117	execute_process(COMMAND git hash-object --stdin-paths
118			WORKING_DIRECTORY ${SOF_ROOT_SOURCE_DIRECTORY}
119			INPUT_FILE "${SOURCE_HASH_DIR}/tracked_file_list"
120			OUTPUT_FILE "${SOURCE_HASH_DIR}/tracked_file_hash_list"
121		)
122	# then calculate single hash of previously calculated hash list
123	execute_process(COMMAND git hash-object --stdin
124			WORKING_DIRECTORY ${SOF_ROOT_SOURCE_DIRECTORY}
125			OUTPUT_STRIP_TRAILING_WHITESPACE
126			INPUT_FILE "${SOURCE_HASH_DIR}/tracked_file_hash_list"
127			OUTPUT_VARIABLE SOF_SRC_HASH_LONG
128		)
129	string(SUBSTRING ${SOF_SRC_HASH_LONG} 0 8 SOF_SRC_HASH)
130	message(STATUS "Source content hash: ${SOF_SRC_HASH}. \
131Notes:
132  - by design, source hash is broken by Kconfig changes. See #3890.
133  - Source hash is also broken by _asymmetric_ autocrlf=input, see
134    #5917 and reverted #5920.")
135else() # Zephyr, tarball,...
136	if(NOT "${GIT_LOG_HASH}" STREQUAL "")
137		string(SUBSTRING "${GIT_LOG_HASH}" 0 8 SOF_SRC_HASH)
138	else()
139		set(SOF_SRC_HASH "baadf00d")
140	endif()
141	message(WARNING "${SOF_ROOT_SOURCE_DIRECTORY}/.git not found, \
142source content hash cannot computed for the .ldc. Using SOF_SRC_HASH=${SOF_SRC_HASH} \
143from GIT_LOG_HASH instead")
144endif()
145
146# for SOF_BUILD
147include(${CMAKE_CURRENT_LIST_DIR}/version-build-counter.cmake)
148
149# (Re)-generate "${VERSION_H_PATH}" but overwrite the old one only if
150# different to avoid a full rebuild.  TODO: check how Zephyr solves this
151# problem, see Zephyr commit 91709778a4878c
152#
153# This function is called only below; not supposed to be used elsewhere.
154# This entire file is run at CMake configure time _and_ invoked
155# again at build time.
156function(sof_check_version_h)
157	string(CONCAT header_content
158		"#define SOF_MAJOR ${SOF_MAJOR}\n"
159		"#define SOF_MINOR ${SOF_MINOR}\n"
160		"#define SOF_MICRO ${SOF_MICRO}\n"
161		"#define SOF_TAG \"${SOF_TAG}\"\n"
162		"#define SOF_BUILD ${SOF_BUILD}\n"
163		"#define SOF_GIT_TAG \"${GIT_TAG}\"\n"
164		"#define SOF_SRC_HASH 0x${SOF_SRC_HASH}\n"
165	)
166
167	if(EXISTS "${VERSION_H_PATH}")
168		file(READ "${VERSION_H_PATH}" old_version_content)
169		if("${header_content}" STREQUAL "${old_version_content}")
170			message(STATUS "Unchanged ${VERSION_H_PATH}")
171			return()
172		endif()
173	endif()
174
175	file(WRITE "${VERSION_H_PATH}" "${header_content}")
176	message(STATUS "Generated new ${VERSION_H_PATH}")
177endfunction()
178
179# This ${VERSION_CMAKE_PATH} file is run in two (very) different ways:
180#
181# 1. explicitly included by some other, top-level CMakeLists.txt file, and
182# 2. directly and "recursively" invoking itself with 'cmake -P myself' here.
183#
184# Add this check_version_h target only in case 1. (no "infinite recursion")
185if("${CMAKE_SCRIPT_MODE_FILE}" STREQUAL "")
186	add_custom_target(
187		check_version_h
188		BYPRODUCTS ${VERSION_H_PATH}
189		COMMAND ${CMAKE_COMMAND}
190			-DVERSION_H_PATH=${VERSION_H_PATH}
191			-DSOF_ROOT_SOURCE_DIRECTORY=${SOF_ROOT_SOURCE_DIRECTORY}
192			-DSOF_ROOT_BINARY_DIRECTORY=${SOF_ROOT_BINARY_DIRECTORY}
193			-DZEPHYR_CURRENT_MODULE_DIR=${ZEPHYR_CURRENT_MODULE_DIR}
194			-P ${VERSION_CMAKE_PATH}
195		COMMENT "cmake -P ${VERSION_CMAKE_PATH}"
196		VERBATIM
197		USES_TERMINAL
198	)
199endif()
200
201sof_check_version_h()
202