Repository navigation
Expand file tree
/
Copy pathversion.cmake
More file actions
204 lines (189 loc) · 7.69 KB
/
Copy pathversion.cmake
File metadata and controls
204 lines (189 loc) · 7.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
include_guard(GLOBAL)
# The option is off by default so that a developer's build of a tagged commit
# is not stamped as a release.
option(RP6502_VERSION_FROM_GIT "Take the version from a tag on HEAD" OFF)
set(RP6502_VERSION_VALUE "")
if(DEFINED RP6502_VERSION AND NOT RP6502_VERSION STREQUAL "")
set(RP6502_VERSION_VALUE "${RP6502_VERSION}")
elseif(RP6502_VERSION_FROM_GIT)
find_package(Git QUIET)
if(Git_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_LIST_DIR}
describe --tags --exact-match HEAD
OUTPUT_VARIABLE _tag OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _rc ERROR_QUIET)
if(_rc EQUAL 0)
string(REGEX REPLACE "^v" "" RP6502_VERSION_VALUE "${_tag}")
endif()
unset(_tag)
unset(_rc)
endif()
endif()
# RP6502_CI is removed from the cache so that a later configure without
# -DRP6502_CI does not reuse another run's id.
set(RP6502_CI_VALUE "")
if(DEFINED RP6502_CI AND NOT RP6502_CI STREQUAL "")
set(RP6502_CI_VALUE "${RP6502_CI}")
endif()
unset(RP6502_CI CACHE)
# RP6502_GIT is removed from the cache so that a later configure without
# -DRP6502_GIT does not reuse an old commit's hash.
set(RP6502_GIT_VALUE "")
if(DEFINED RP6502_GIT AND NOT RP6502_GIT STREQUAL "")
set(RP6502_GIT_VALUE "${RP6502_GIT}")
endif()
unset(RP6502_GIT CACHE)
set(RP6502_STAMP_SCRIPT ${CMAKE_BINARY_DIR}/rp6502_version_stamp.cmake)
file(WRITE ${RP6502_STAMP_SCRIPT} [[
if(STAMP_VERSION)
set(_stamp_bare "${STAMP_VERSION}")
set(_stamp "Version ${STAMP_VERSION}")
elseif(STAMP_CI)
set(_stamp_bare "CI ${STAMP_CI}")
set(_stamp "${_stamp_bare}")
elseif(STAMP_GIT)
set(_stamp_bare "GIT ${STAMP_GIT}")
set(_stamp "${_stamp_bare}")
else()
string(TIMESTAMP _stamp_bare "%b %d %Y %H:%M:%S %Z")
set(_stamp "${_stamp_bare}")
endif()
]])
set(RP6502_GEN_VERSION_SCRIPT ${CMAKE_BINARY_DIR}/gen_rp6502_version_header.cmake)
file(WRITE ${RP6502_GEN_VERSION_SCRIPT} [[
include("${STAMP_SCRIPT}")
set(_new "#undef RP6502_VERSION\n#define RP6502_VERSION \"${_stamp}\"\n")
string(APPEND _new "#undef RP6502_VERSION_BARE\n#define RP6502_VERSION_BARE \"${_stamp_bare}\"\n")
set(_old "")
if(EXISTS "${OUTPUT}")
file(READ "${OUTPUT}" _old)
endif()
if(NOT _new STREQUAL _old)
file(WRITE "${OUTPUT}" "${_new}")
endif()
]])
# These configure-time copies are for the Windows version.rc and the macOS
# Info.plist, which are configured rather than compiled, so a developer's build
# is stamped there with the time of the configure rather than of the build.
set(STAMP_VERSION "${RP6502_VERSION_VALUE}")
set(STAMP_CI "${RP6502_CI_VALUE}")
set(STAMP_GIT "${RP6502_GIT_VALUE}")
include(${RP6502_STAMP_SCRIPT})
set(RP6502_VERSION_STAMP "${_stamp}")
set(RP6502_VERSION_BARE "${_stamp_bare}")
unset(STAMP_VERSION)
unset(STAMP_CI)
unset(STAMP_GIT)
unset(_stamp)
unset(_stamp_bare)
# Windows FILEVERSION takes four integers and macOS CFBundleVersion takes
# dotted integers. A CI run id, a commit hash and a timestamp fit neither, so
# only a numeric tagged version fills these forms, and the free-form version
# strings beside them hold the full stamp.
set(RP6502_VERSION_DOTTED "0.0")
if(RP6502_VERSION_VALUE MATCHES "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$")
set(RP6502_VERSION_DOTTED "${RP6502_VERSION_VALUE}")
endif()
set(RP6502_VERSION_QUAD "0,0,0,0")
if(RP6502_VERSION_VALUE MATCHES "^([0-9]+)\\.([0-9]+)(\\.([0-9]+))?$")
set(_patch "${CMAKE_MATCH_4}")
if(NOT _patch)
set(_patch 0)
endif()
set(RP6502_VERSION_QUAD "${CMAKE_MATCH_1},${CMAKE_MATCH_2},${_patch},0")
unset(_patch)
endif()
# ARGN names further targets that are part of the program, such as a static
# library it links. Without them the header is regenerated only for changes to
# tgt, so a developer's build could report a time from before the code it is
# running.
#
# The header is a BYPRODUCT because CMake's Ninja generator adds an OUTPUT
# named in OBJECT_DEPENDS to the target's cmake_object_order_depends, which
# every object of the target waits on. The command depends on those objects,
# so an OUTPUT header would be a dependency cycle.
function(rp6502_use_version_header tgt src)
set(hdr ${CMAKE_CURRENT_BINARY_DIR}/rp6502_version.h)
set(stamp ${CMAKE_CURRENT_BINARY_DIR}/rp6502_version.stamp)
target_include_directories(${tgt} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
set(_src "${src}")
if(NOT IS_ABSOLUTE "${_src}")
set(_src "${CMAKE_CURRENT_SOURCE_DIR}/${_src}")
endif()
get_filename_component(_src_name "${_src}" NAME)
# With any other generator the header is written once per configure. That
# leaves only a developer's timestamp stale, because the tagged, CI and GIT
# forms do not change between builds.
if(NOT CMAKE_GENERATOR MATCHES "Make|Ninja")
execute_process(COMMAND ${CMAKE_COMMAND}
-DOUTPUT=${hdr}
-DSTAMP_SCRIPT=${RP6502_STAMP_SCRIPT}
"-DSTAMP_VERSION=${RP6502_VERSION_VALUE}"
"-DSTAMP_CI=${RP6502_CI_VALUE}"
"-DSTAMP_GIT=${RP6502_GIT_VALUE}"
-P ${RP6502_GEN_VERSION_SCRIPT})
return()
endif()
set(_deps ${_src} ${RP6502_GEN_VERSION_SCRIPT} ${RP6502_STAMP_SCRIPT})
set(_impl)
set(_all_tgts ${tgt} ${ARGN})
if(CMAKE_GENERATOR MATCHES "Make")
# The custom target below has no include directories of its own, so
# its IMPLICIT_DEPENDS scanner uses the directory's. tgt's include
# directories are added to the directory for that scanner to find the
# headers the sources include.
get_target_property(_inc_dirs ${tgt} INCLUDE_DIRECTORIES)
if(_inc_dirs)
include_directories(${_inc_dirs})
endif()
foreach(_t IN LISTS _all_tgts)
get_target_property(_srcs ${_t} SOURCES)
get_target_property(_tdir ${_t} SOURCE_DIR)
foreach(_s IN LISTS _srcs)
if(NOT IS_ABSOLUTE "${_s}")
set(_s "${_tdir}/${_s}")
endif()
if(NOT "${_s}" STREQUAL "${_src}")
list(APPEND _deps "${_s}")
list(APPEND _impl C "${_s}")
endif()
endforeach()
endforeach()
if(_impl)
set(_impl IMPLICIT_DEPENDS ${_impl})
endif()
else()
string(REPLACE "." "\\." _esc "${_src_name}${CMAKE_C_OUTPUT_EXTENSION}")
foreach(_t IN LISTS _all_tgts)
list(APPEND _deps $<FILTER:$<TARGET_OBJECTS:${_t}>,EXCLUDE,${_esc}$>)
endforeach()
endif()
add_custom_command(
OUTPUT ${stamp}
BYPRODUCTS ${hdr}
DEPENDS ${_deps}
${_impl}
COMMAND ${CMAKE_COMMAND}
-DOUTPUT=${hdr}
-DSTAMP_SCRIPT=${RP6502_STAMP_SCRIPT}
"-DSTAMP_VERSION=${RP6502_VERSION_VALUE}"
"-DSTAMP_CI=${RP6502_CI_VALUE}"
"-DSTAMP_GIT=${RP6502_GIT_VALUE}"
-P ${RP6502_GEN_VERSION_SCRIPT}
COMMAND ${CMAKE_COMMAND} -E touch ${stamp}
VERBATIM
)
# CMake's Ninja generator lists a byproduct as an output of its command, so
# the object is ordered after the header. The Makefile generator writes no
# rule for a byproduct, so an object that depends on the header fails with
# "No rule to make target" when the target is built by name before the
# custom target below has run. The stamp is a real output under both
# generators, so under Make the object depends on the stamp instead.
if(CMAKE_GENERATOR MATCHES "Make")
set_property(SOURCE ${_src} APPEND PROPERTY OBJECT_DEPENDS ${stamp})
else()
set_property(SOURCE ${_src} APPEND PROPERTY OBJECT_DEPENDS ${hdr})
endif()
add_custom_target(${tgt}_version_header ALL DEPENDS ${stamp})
endfunction()