/src/CMake/Source/cmTimestamp.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | |
4 | | #if !defined(_WIN32) && !defined(__sun) && !defined(__OpenBSD__) |
5 | | // POSIX APIs are needed |
6 | | // NOLINTNEXTLINE(bugprone-reserved-identifier) |
7 | | # define _POSIX_C_SOURCE 200809L |
8 | | #endif |
9 | | #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__QNX__) |
10 | | // For isascii |
11 | | // NOLINTNEXTLINE(bugprone-reserved-identifier) |
12 | | # define _XOPEN_SOURCE 700 |
13 | | #endif |
14 | | #if defined(__APPLE__) |
15 | | // Restore Darwin APIs removed by _POSIX_C_SOURCE: |
16 | | // aligned_alloc |
17 | | // timespec_get |
18 | | // NOLINTNEXTLINE(bugprone-reserved-identifier) |
19 | | # define _DARWIN_C_SOURCE |
20 | | #endif |
21 | | |
22 | | #include "cmTimestamp.h" |
23 | | |
24 | | #include <cstdlib> |
25 | | #include <cstring> |
26 | | #include <ctime> |
27 | | #include <sstream> |
28 | | #include <utility> |
29 | | |
30 | | #ifdef __MINGW32__ |
31 | | # include <libloaderapi.h> |
32 | | #endif |
33 | | |
34 | | #include <cm3p/uv.h> |
35 | | |
36 | | #include "cmStringAlgorithms.h" |
37 | | #include "cmSystemTools.h" |
38 | | |
39 | | std::string cmTimestamp::CurrentTime(cm::string_view formatString, |
40 | | bool utcFlag) const |
41 | 0 | { |
42 | | // get current time with microsecond resolution |
43 | 0 | uv_timeval64_t timeval; |
44 | 0 | uv_gettimeofday(&timeval); |
45 | 0 | auto currentTimeT = static_cast<time_t>(timeval.tv_sec); |
46 | 0 | auto microseconds = static_cast<uint32_t>(timeval.tv_usec); |
47 | | |
48 | | // check for override via SOURCE_DATE_EPOCH for reproducible builds |
49 | 0 | std::string source_date_epoch; |
50 | 0 | cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch); |
51 | 0 | if (!source_date_epoch.empty()) { |
52 | 0 | std::istringstream iss(source_date_epoch); |
53 | 0 | iss >> currentTimeT; |
54 | 0 | if (iss.fail() || !iss.eof()) { |
55 | 0 | cmSystemTools::Error("Cannot parse SOURCE_DATE_EPOCH as integer"); |
56 | 0 | exit(27); |
57 | 0 | } |
58 | | // SOURCE_DATE_EPOCH has only a resolution in the seconds range |
59 | 0 | microseconds = 0; |
60 | 0 | } |
61 | 0 | if (currentTimeT == static_cast<time_t>(-1)) { |
62 | 0 | return std::string(); |
63 | 0 | } |
64 | | |
65 | 0 | return this->CreateTimestampFromTimeT(currentTimeT, microseconds, |
66 | 0 | static_cast<std::string>(formatString), |
67 | 0 | utcFlag); |
68 | 0 | } |
69 | | |
70 | | std::string cmTimestamp::FileModificationTime(char const* path, |
71 | | cm::string_view formatString, |
72 | | bool utcFlag) const |
73 | 0 | { |
74 | 0 | std::string real_path = |
75 | 0 | cmSystemTools::GetRealPathResolvingWindowsSubst(path); |
76 | |
|
77 | 0 | if (!cmsys::SystemTools::FileExists(real_path)) { |
78 | 0 | return std::string(); |
79 | 0 | } |
80 | | |
81 | | // use libuv's implementation of stat(2) to get the file information |
82 | 0 | time_t mtime = 0; |
83 | 0 | uint32_t microseconds = 0; |
84 | 0 | uv_fs_t req; |
85 | 0 | if (uv_fs_stat(nullptr, &req, real_path.c_str(), nullptr) == 0) { |
86 | 0 | mtime = static_cast<time_t>(req.statbuf.st_mtim.tv_sec); |
87 | | // tv_nsec has nanosecond resolution, but we truncate it to microsecond |
88 | | // resolution in order to be consistent with cmTimestamp::CurrentTime() |
89 | 0 | microseconds = static_cast<uint32_t>(req.statbuf.st_mtim.tv_nsec / 1000); |
90 | 0 | } |
91 | 0 | uv_fs_req_cleanup(&req); |
92 | |
|
93 | 0 | return this->CreateTimestampFromTimeT( |
94 | 0 | mtime, microseconds, static_cast<std::string>(formatString), utcFlag); |
95 | 0 | } |
96 | | |
97 | | std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT, |
98 | | std::string formatString, |
99 | | bool utcFlag) const |
100 | 0 | { |
101 | 0 | return this->CreateTimestampFromTimeT(timeT, 0, std::move(formatString), |
102 | 0 | utcFlag); |
103 | 0 | } |
104 | | |
105 | | std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT, |
106 | | uint32_t const microseconds, |
107 | | std::string formatString, |
108 | | bool utcFlag) const |
109 | 0 | { |
110 | 0 | if (formatString.empty()) { |
111 | 0 | formatString = "%Y-%m-%dT%H:%M:%S"; |
112 | 0 | if (utcFlag) { |
113 | 0 | formatString += "Z"; |
114 | 0 | } |
115 | 0 | } |
116 | |
|
117 | 0 | struct tm timeStruct; |
118 | 0 | memset(&timeStruct, 0, sizeof(timeStruct)); |
119 | |
|
120 | 0 | struct tm* ptr = nullptr; |
121 | 0 | if (utcFlag) { |
122 | 0 | ptr = gmtime(&timeT); |
123 | 0 | } else { |
124 | 0 | ptr = localtime(&timeT); |
125 | 0 | } |
126 | |
|
127 | 0 | if (!ptr) { |
128 | 0 | return std::string(); |
129 | 0 | } |
130 | | |
131 | 0 | timeStruct = *ptr; |
132 | |
|
133 | 0 | std::string result; |
134 | 0 | for (std::string::size_type i = 0; i < formatString.size(); ++i) { |
135 | 0 | char c1 = formatString[i]; |
136 | 0 | char c2 = (i + 1 < formatString.size()) ? formatString[i + 1] |
137 | 0 | : static_cast<char>(0); |
138 | |
|
139 | 0 | if (c1 == '%' && c2 != 0) { |
140 | 0 | result += this->AddTimestampComponent(c2, timeStruct, timeT, utcFlag, |
141 | 0 | microseconds); |
142 | 0 | ++i; |
143 | 0 | } else { |
144 | 0 | result += c1; |
145 | 0 | } |
146 | 0 | } |
147 | |
|
148 | 0 | return result; |
149 | 0 | } |
150 | | |
151 | | time_t cmTimestamp::CreateUtcTimeTFromTm(struct tm& tm) const |
152 | 0 | { |
153 | | #if defined(_MSC_VER) && _MSC_VER >= 1400 |
154 | | return _mkgmtime(&tm); |
155 | | #else |
156 | | // From Linux timegm() manpage. |
157 | |
|
158 | 0 | std::string tz_old; |
159 | 0 | bool const tz_was_set = cmSystemTools::GetEnv("TZ", tz_old); |
160 | 0 | tz_old = "TZ=" + tz_old; |
161 | | |
162 | | // The standard says that "TZ=" or "TZ=[UNRECOGNIZED_TZ]" means UTC. |
163 | | // It seems that "TZ=" does NOT work, at least under Windows |
164 | | // with neither MSVC nor MinGW, so let's use explicit "TZ=UTC" |
165 | |
|
166 | 0 | cmSystemTools::PutEnv("TZ=UTC"); |
167 | |
|
168 | 0 | tzset(); |
169 | |
|
170 | 0 | time_t result = mktime(&tm); |
171 | |
|
172 | 0 | # ifndef CMAKE_BOOTSTRAP |
173 | 0 | if (tz_was_set) { |
174 | 0 | cmSystemTools::PutEnv(tz_old); |
175 | 0 | } else { |
176 | 0 | cmSystemTools::UnsetEnv("TZ"); |
177 | 0 | } |
178 | | # else |
179 | | // No UnsetEnv during bootstrap. This is good enough for CMake itself. |
180 | | cmSystemTools::PutEnv(tz_old); |
181 | | static_cast<void>(tz_was_set); |
182 | | # endif |
183 | |
|
184 | 0 | tzset(); |
185 | |
|
186 | 0 | return result; |
187 | 0 | #endif |
188 | 0 | } |
189 | | |
190 | | std::string cmTimestamp::AddTimestampComponent( |
191 | | char flag, struct tm& timeStruct, time_t const timeT, bool const utcFlag, |
192 | | uint32_t const microseconds) const |
193 | 0 | { |
194 | 0 | std::string formatString = cmStrCat('%', flag); |
195 | |
|
196 | 0 | switch (flag) { |
197 | 0 | case 'a': |
198 | 0 | case 'A': |
199 | 0 | case 'b': |
200 | 0 | case 'B': |
201 | 0 | case 'd': |
202 | 0 | case 'H': |
203 | 0 | case 'I': |
204 | 0 | case 'j': |
205 | 0 | case 'm': |
206 | 0 | case 'M': |
207 | 0 | case 'S': |
208 | 0 | case 'U': |
209 | 0 | case 'V': |
210 | 0 | case 'w': |
211 | 0 | case 'y': |
212 | 0 | case 'Y': |
213 | 0 | case '%': |
214 | 0 | break; |
215 | 0 | case 'Z': |
216 | 0 | #if defined(__GLIBC__) |
217 | | // 'struct tm' has the time zone, so strftime can honor UTC. |
218 | 0 | static_cast<void>(utcFlag); |
219 | | #else |
220 | | // 'struct tm' may not have the time zone, so strftime may |
221 | | // use local time. Hard-code the UTC result. |
222 | | if (utcFlag) { |
223 | | return std::string("GMT"); |
224 | | } |
225 | | #endif |
226 | 0 | break; |
227 | 0 | case 'z': { |
228 | 0 | #if defined(__GLIBC__) |
229 | | // 'struct tm' has the time zone, so strftime can honor UTC. |
230 | 0 | static_cast<void>(utcFlag); |
231 | | #else |
232 | | // 'struct tm' may not have the time zone, so strftime may |
233 | | // use local time. Hard-code the UTC result. |
234 | | if (utcFlag) { |
235 | | return std::string("+0000"); |
236 | | } |
237 | | #endif |
238 | 0 | #ifndef _AIX |
239 | 0 | break; |
240 | | #else |
241 | | std::string xpg_sus_old; |
242 | | bool const xpg_sus_was_set = |
243 | | cmSystemTools::GetEnv("XPG_SUS_ENV", xpg_sus_old); |
244 | | if (xpg_sus_was_set && xpg_sus_old == "ON") { |
245 | | break; |
246 | | } |
247 | | xpg_sus_old = "XPG_SUS_ENV=" + xpg_sus_old; |
248 | | |
249 | | // On AIX systems, %z requires XPG_SUS_ENV=ON to work as desired. |
250 | | cmSystemTools::PutEnv("XPG_SUS_ENV=ON"); |
251 | | tzset(); |
252 | | |
253 | | char buffer[16]; |
254 | | size_t size = strftime(buffer, sizeof(buffer), "%z", &timeStruct); |
255 | | |
256 | | # ifndef CMAKE_BOOTSTRAP |
257 | | if (xpg_sus_was_set) { |
258 | | cmSystemTools::PutEnv(xpg_sus_old); |
259 | | } else { |
260 | | cmSystemTools::UnsetEnv("XPG_SUS_ENV"); |
261 | | } |
262 | | # else |
263 | | // No UnsetEnv during bootstrap. This is good enough for CMake itself. |
264 | | cmSystemTools::PutEnv(xpg_sus_old); |
265 | | static_cast<void>(xpg_sus_was_set); |
266 | | # endif |
267 | | tzset(); |
268 | | |
269 | | return std::string(buffer, size); |
270 | | #endif |
271 | 0 | } |
272 | 0 | case 's': // Seconds since UNIX epoch (midnight 1-jan-1970) |
273 | 0 | { |
274 | | // Build a time_t for UNIX epoch and subtract from the input "timeT": |
275 | 0 | struct tm tmUnixEpoch; |
276 | 0 | memset(&tmUnixEpoch, 0, sizeof(tmUnixEpoch)); |
277 | 0 | tmUnixEpoch.tm_mday = 1; |
278 | 0 | tmUnixEpoch.tm_year = 1970 - 1900; |
279 | |
|
280 | 0 | time_t const unixEpoch = this->CreateUtcTimeTFromTm(tmUnixEpoch); |
281 | 0 | if (unixEpoch == -1) { |
282 | 0 | cmSystemTools::Error( |
283 | 0 | "Error generating UNIX epoch in string(TIMESTAMP ...) or " |
284 | 0 | "file(TIMESTAMP ...). Please, file a bug report against CMake"); |
285 | 0 | return std::string(); |
286 | 0 | } |
287 | | |
288 | 0 | return std::to_string( |
289 | 0 | static_cast<int64_t>(std::difftime(timeT, unixEpoch))); |
290 | 0 | } |
291 | 0 | case 'f': // microseconds |
292 | 0 | { |
293 | | // clip number to 6 digits and pad with leading zeros |
294 | 0 | std::string microsecs = std::to_string(microseconds % 1000000); |
295 | 0 | return std::string(6 - microsecs.length(), '0') + microsecs; |
296 | 0 | } |
297 | 0 | default: { |
298 | 0 | return formatString; |
299 | 0 | } |
300 | 0 | } |
301 | | |
302 | 0 | char buffer[16]; |
303 | |
|
304 | | #ifdef __MINGW32__ |
305 | | /* See a bug in MinGW: https://sourceforge.net/p/mingw-w64/bugs/793/. A work |
306 | | * around is to try to use strftime() from ucrtbase.dll. */ |
307 | | using T = size_t(__cdecl*)(char*, size_t, char const*, const struct tm*); |
308 | | auto loadUcrtStrftime = []() -> T { |
309 | | auto handle = |
310 | | LoadLibraryExA("ucrtbase.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); |
311 | | if (handle) { |
312 | | # pragma GCC diagnostic push |
313 | | # pragma GCC diagnostic ignored "-Wcast-function-type" |
314 | | return reinterpret_cast<T>(GetProcAddress(handle, "strftime")); |
315 | | # pragma GCC diagnostic pop |
316 | | } |
317 | | return nullptr; |
318 | | }; |
319 | | static T ucrtStrftime = loadUcrtStrftime(); |
320 | | |
321 | | if (ucrtStrftime) { |
322 | | size_t size = |
323 | | ucrtStrftime(buffer, sizeof(buffer), formatString.c_str(), &timeStruct); |
324 | | return std::string(buffer, size); |
325 | | } |
326 | | #endif |
327 | |
|
328 | 0 | size_t size = |
329 | 0 | strftime(buffer, sizeof(buffer), formatString.c_str(), &timeStruct); |
330 | |
|
331 | 0 | return std::string(buffer, size); |
332 | 0 | } |