summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Würth <wuerth.peter@freenet.de>2022-01-27 15:20:15 +0100
committerBrad King <brad.king@kitware.com>2022-01-28 06:23:57 -0500
commitc050d6a01e14500903619589e69a973f1215d109 (patch)
treed78eb4dec319610788d4518d2fe3225e889b9b7c
parent44939f01e761568ab9fe511d2ddf81a48c7c54b9 (diff)
downloadcmake-c050d6a01e14500903619589e69a973f1215d109.tar.gz
string(TIMESTAMP): add %f specifier for microseconds
The %f specified extends the string(TIMESTAMP) and file(TIMESTAMP) commands to output the timestamp with a microsecond resolution. This convention is offered by python's datetime module. Before, the precision was limited to seconds. The implementation is done by extending existing cmTimestamp methods with a `microseconds` parameter. This parameter is optional in order to be backwards compatible. The timestamps are now received in a cross-platform manner using libuv, since the standard C functions like time() don't allow for sub-second precision. This requires libuv 1.28 or higher. We already require higher than that on Windows, so update the required version for other platforms. Implements: #19335
-rw-r--r--CMakeLists.txt2
-rw-r--r--Help/command/string.rst3
-rw-r--r--Help/release/dev/timestamp-microseconds.rst5
-rw-r--r--Source/cmTimestamp.cxx60
-rw-r--r--Source/cmTimestamp.h8
-rw-r--r--Tests/RunCMake/string/Timestamp-stderr.txt2
-rw-r--r--Tests/RunCMake/string/Timestamp.cmake2
7 files changed, 68 insertions, 14 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 428b040f41..b2ab30e152 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -655,7 +655,7 @@ macro (CMAKE_BUILD_UTILITIES)
if(WIN32)
find_package(LibUV 1.38.0)
else()
- find_package(LibUV 1.10.0)
+ find_package(LibUV 1.28.0)
endif()
if(NOT LIBUV_FOUND)
message(FATAL_ERROR
diff --git a/Help/command/string.rst b/Help/command/string.rst
index 29ad082a90..9b707eb83d 100644
--- a/Help/command/string.rst
+++ b/Help/command/string.rst
@@ -490,6 +490,9 @@ specifiers:
``%S``
The second of the current minute. 60 represents a leap second. (00-60)
+``%f``
+ The microsecond of the current second (000000-999999).
+
``%U``
The week number of the current year (00-53).
diff --git a/Help/release/dev/timestamp-microseconds.rst b/Help/release/dev/timestamp-microseconds.rst
new file mode 100644
index 0000000000..0c95eb4193
--- /dev/null
+++ b/Help/release/dev/timestamp-microseconds.rst
@@ -0,0 +1,5 @@
+timestamp-microseconds
+----------------------
+
+* The :command:`string(TIMESTAMP)` and :command:`file(TIMESTAMP)` commands now
+ support the ``%f`` specifier for microseconds.
diff --git a/Source/cmTimestamp.cxx b/Source/cmTimestamp.cxx
index 382657766b..e2b6c20466 100644
--- a/Source/cmTimestamp.cxx
+++ b/Source/cmTimestamp.cxx
@@ -17,18 +17,27 @@
#include <cstdlib>
#include <cstring>
#include <sstream>
+#include <utility>
#ifdef __MINGW32__
# include <libloaderapi.h>
#endif
+#include <cm3p/uv.h>
+
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
std::string cmTimestamp::CurrentTime(const std::string& formatString,
bool utcFlag) const
{
- time_t currentTimeT = time(nullptr);
+ // get current time with microsecond resolution
+ uv_timeval64_t timeval;
+ uv_gettimeofday(&timeval);
+ auto currentTimeT = static_cast<time_t>(timeval.tv_sec);
+ auto microseconds = static_cast<uint32_t>(timeval.tv_usec);
+
+ // check for override via SOURCE_DATE_EPOCH for reproducible builds
std::string source_date_epoch;
cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch);
if (!source_date_epoch.empty()) {
@@ -38,12 +47,15 @@ std::string cmTimestamp::CurrentTime(const std::string& formatString,
cmSystemTools::Error("Cannot parse SOURCE_DATE_EPOCH as integer");
exit(27);
}
+ // SOURCE_DATE_EPOCH has only a resolution in the seconds range
+ microseconds = 0;
}
if (currentTimeT == time_t(-1)) {
return std::string();
}
- return this->CreateTimestampFromTimeT(currentTimeT, formatString, utcFlag);
+ return this->CreateTimestampFromTimeT(currentTimeT, microseconds,
+ formatString, utcFlag);
}
std::string cmTimestamp::FileModificationTime(const char* path,
@@ -57,11 +69,32 @@ std::string cmTimestamp::FileModificationTime(const char* path,
return std::string();
}
- time_t mtime = cmsys::SystemTools::ModifiedTime(real_path);
- return this->CreateTimestampFromTimeT(mtime, formatString, utcFlag);
+ // use libuv's implementation of stat(2) to get the file information
+ time_t mtime = 0;
+ uint32_t microseconds = 0;
+ uv_fs_t req;
+ if (uv_fs_stat(nullptr, &req, real_path.c_str(), nullptr) == 0) {
+ mtime = static_cast<time_t>(req.statbuf.st_mtim.tv_sec);
+ // tv_nsec has nanosecond resolution, but we truncate it to microsecond
+ // resolution in order to be consistent with cmTimestamp::CurrentTime()
+ microseconds = static_cast<uint32_t>(req.statbuf.st_mtim.tv_nsec / 1000);
+ }
+ uv_fs_req_cleanup(&req);
+
+ return this->CreateTimestampFromTimeT(mtime, microseconds, formatString,
+ utcFlag);
+}
+
+std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
+ std::string formatString,
+ bool utcFlag) const
+{
+ return this->CreateTimestampFromTimeT(timeT, 0, std::move(formatString),
+ utcFlag);
}
std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
+ const uint32_t microseconds,
std::string formatString,
bool utcFlag) const
{
@@ -95,7 +128,8 @@ std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
: static_cast<char>(0);
if (c1 == '%' && c2 != 0) {
- result += this->AddTimestampComponent(c2, timeStruct, timeT);
+ result +=
+ this->AddTimestampComponent(c2, timeStruct, timeT, microseconds);
++i;
} else {
result += c1;
@@ -144,9 +178,9 @@ time_t cmTimestamp::CreateUtcTimeTFromTm(struct tm& tm) const
#endif
}
-std::string cmTimestamp::AddTimestampComponent(char flag,
- struct tm& timeStruct,
- const time_t timeT) const
+std::string cmTimestamp::AddTimestampComponent(
+ char flag, struct tm& timeStruct, const time_t timeT,
+ const uint32_t microseconds) const
{
std::string formatString = cmStrCat('%', flag);
@@ -180,13 +214,19 @@ std::string cmTimestamp::AddTimestampComponent(char flag,
const time_t unixEpoch = this->CreateUtcTimeTFromTm(tmUnixEpoch);
if (unixEpoch == -1) {
cmSystemTools::Error(
- "Error generating UNIX epoch in "
- "STRING(TIMESTAMP ...). Please, file a bug report against CMake");
+ "Error generating UNIX epoch in string(TIMESTAMP ...) or "
+ "file(TIMESTAMP ...). Please, file a bug report against CMake");
return std::string();
}
return std::to_string(static_cast<long int>(difftime(timeT, unixEpoch)));
}
+ case 'f': // microseconds
+ {
+ // clip number to 6 digits and pad with leading zeros
+ std::string microsecs = std::to_string(microseconds % 1000000);
+ return std::string(6 - microsecs.length(), '0') + microsecs;
+ }
default: {
return formatString;
}
diff --git a/Source/cmTimestamp.h b/Source/cmTimestamp.h
index 0e2c20006e..ada5006d45 100644
--- a/Source/cmTimestamp.h
+++ b/Source/cmTimestamp.h
@@ -4,6 +4,7 @@
#include "cmConfigure.h" // IWYU pragma: keep
+#include <cstdint>
#include <ctime>
#include <string>
@@ -23,9 +24,14 @@ public:
std::string CreateTimestampFromTimeT(time_t timeT, std::string formatString,
bool utcFlag) const;
+ std::string CreateTimestampFromTimeT(time_t timeT, uint32_t microseconds,
+ std::string formatString,
+ bool utcFlag) const;
+
private:
time_t CreateUtcTimeTFromTm(struct tm& timeStruct) const;
std::string AddTimestampComponent(char flag, struct tm& timeStruct,
- time_t timeT) const;
+ time_t timeT,
+ uint32_t microseconds = 0) const;
};
diff --git a/Tests/RunCMake/string/Timestamp-stderr.txt b/Tests/RunCMake/string/Timestamp-stderr.txt
index d54777bd71..f162f52992 100644
--- a/Tests/RunCMake/string/Timestamp-stderr.txt
+++ b/Tests/RunCMake/string/Timestamp-stderr.txt
@@ -1 +1 @@
-RESULT=2005-08-07 23:19:49 Sunday=Sun August=Aug 05 day=219 wd=0 week=32 w_iso=31 %I=11 epoch=1123456789
+RESULT=2005-08-07 23:19:49.000000 Sunday=Sun August=Aug 05 day=219 wd=0 week=32 w_iso=31 %I=11 epoch=1123456789
diff --git a/Tests/RunCMake/string/Timestamp.cmake b/Tests/RunCMake/string/Timestamp.cmake
index 7fd6d72b0d..531a237bdc 100644
--- a/Tests/RunCMake/string/Timestamp.cmake
+++ b/Tests/RunCMake/string/Timestamp.cmake
@@ -1,3 +1,3 @@
set(ENV{SOURCE_DATE_EPOCH} "1123456789")
-string(TIMESTAMP RESULT "%Y-%m-%d %H:%M:%S %A=%a %B=%b %y day=%j wd=%w week=%U w_iso=%V %%I=%I epoch=%s" UTC)
+string(TIMESTAMP RESULT "%Y-%m-%d %H:%M:%S.%f %A=%a %B=%b %y day=%j wd=%w week=%U w_iso=%V %%I=%I epoch=%s" UTC)
message("RESULT=${RESULT}")