From d4106c548e2265df88a7a44c1b6bacb2125765a2 Mon Sep 17 00:00:00 2001 From: Spacetown Date: Wed, 22 Feb 2023 20:13:34 +0100 Subject: [PATCH 1/7] Read default from environment variable SOURCE_DATE_EPOCH if defined. In practice SOURCE_DATE_EPOCH specifies the last modification of something, usually the source code, (measured) in seconds since the Unix epoch, which is January 1st 1970, 00:00:00 UTC. --- gcovr/configuration.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gcovr/configuration.py b/gcovr/configuration.py index 7ecb3710f8..53a519b67c 100644 --- a/gcovr/configuration.py +++ b/gcovr/configuration.py @@ -22,6 +22,7 @@ from inspect import isclass from locale import getpreferredencoding from multiprocessing import cpu_count +import time from typing import Iterable, Any, List, Optional, Union, Callable, TextIO, Dict from dataclasses import dataclass import datetime @@ -1160,10 +1161,13 @@ def merge_options_and_set_defaults( "Override current time for reproducible reports. " "Can use `YYYY-MM-DD hh:mm:ss` or epoch notation. " "Used by HTML, Coveralls, and Cobertura reports. " - "Default: current time." + "Default: Environment variable SOURCE_DATE_EPOCH " + "(see https://reproducible-builds.org/docs/source-date-epoch) or current time." ), type=timestamp, - default=datetime.datetime.now(), + default=datetime.datetime.fromtimestamp( + int(os.environ.get("SOURCE_DATE_EPOCH", time.time())) + ), ), GcovrConfigOption( "filter", From b840261c152413686f829aacddc46dda61f4bfdd Mon Sep 17 00:00:00 2001 From: Spacetown Date: Wed, 22 Feb 2023 20:51:24 +0100 Subject: [PATCH 2/7] Use SOURCE_DATE_EPOCH instead of --timestamp in examples. --- doc/examples/example_html.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/examples/example_html.sh b/doc/examples/example_html.sh index 66edd169e7..5096c3a7d6 100755 --- a/doc/examples/example_html.sh +++ b/doc/examples/example_html.sh @@ -6,7 +6,9 @@ set -euo pipefail # "use strict" # this function wraps gcovr to force a specific timestamp. # This can be ignored by end users and isn't really part of the example. gcovr() { - python3 -m gcovr --timestamp="2021-11-08 21:12:28" "$@" + export SOURCE_DATE_EPOCH=3666 + python3 -m gcovr "$@" + unset SOURCE_DATE_EPOCH } From 3f8cee4edef1d5ca14415aec513f81fba9768ac3 Mon Sep 17 00:00:00 2001 From: Spacetown Date: Wed, 22 Feb 2023 20:57:02 +0100 Subject: [PATCH 3/7] Update test data. --- ...ml.details.example.cpp.9597a7a3397b8e3a48116e2a3afb4154.html | 2 +- doc/examples/example_html.details.functions.html | 2 +- doc/examples/example_html.details.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/examples/example_html.details.example.cpp.9597a7a3397b8e3a48116e2a3afb4154.html b/doc/examples/example_html.details.example.cpp.9597a7a3397b8e3a48116e2a3afb4154.html index cdfe47ce46..f7b2d5105b 100644 --- a/doc/examples/example_html.details.example.cpp.9597a7a3397b8e3a48116e2a3afb4154.html +++ b/doc/examples/example_html.details.example.cpp.9597a7a3397b8e3a48116e2a3afb4154.html @@ -27,7 +27,7 @@

GCC Code Coverage Report

Date: - 2021-11-08 21:12:28 + 1970-01-01 01:01:06 diff --git a/doc/examples/example_html.details.functions.html b/doc/examples/example_html.details.functions.html index 85916daa0e..18f40655a6 100644 --- a/doc/examples/example_html.details.functions.html +++ b/doc/examples/example_html.details.functions.html @@ -23,7 +23,7 @@

GCC Code Coverage Report

Date: - 2021-11-08 21:12:28 + 1970-01-01 01:01:06 diff --git a/doc/examples/example_html.details.html b/doc/examples/example_html.details.html index c52214e089..2cf696c3c4 100644 --- a/doc/examples/example_html.details.html +++ b/doc/examples/example_html.details.html @@ -23,7 +23,7 @@

GCC Code Coverage Report

Date: - 2021-11-08 21:12:28 + 1970-01-01 01:01:06 Coverage: From 733c006b792fef4325b46ed8288eb0a6f0625828 Mon Sep 17 00:00:00 2001 From: Spacetown Date: Wed, 22 Feb 2023 21:01:51 +0100 Subject: [PATCH 4/7] Update docs. --- CHANGELOG.rst | 1 + doc/source/guide/timestamps.rst | 13 ++++++++++--- gcovr/configuration.py | 3 ++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 681bc075aa..1002d28e64 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -33,6 +33,7 @@ New features and notable changes: - Allow annotations for never executed branches. (:issue:`711`) - Add function merge mode for same function defined in different lines. (:issue:`700`) - Update link to gcovr documentation in HTML report to point to the documentation of the used version. (:issue:`723`) +- Add environment `SOURCE_DATE_EPOCH `_ to set default for :option:`--timestamp`. (:issue:`729`) Bug fixes and small improvements: diff --git a/doc/source/guide/timestamps.rst b/doc/source/guide/timestamps.rst index 4e5f996c6d..973453e152 100644 --- a/doc/source/guide/timestamps.rst +++ b/doc/source/guide/timestamps.rst @@ -5,16 +5,23 @@ Reproducible Timestamps In some cases, it may be desirable to list a specific timestamp in the report. Timestamps are shown in -the :ref:`html_output`, -:ref:`coveralls_output`, -and the :ref:`cobertura_output`. +the :ref:`html_output`, :ref:`coveralls_output`, and the :ref:`cobertura_output`. This can be achieved via the :option:`--timestamp ` option. This option does not affect the modification times or other filesystem metadata. +The default for the variable is taken from the environment variable +`SOURCE_DATE_EPOCH`_ converted to a human readable time or to the current time is +the environment variable isn't defined. + +.. versionadded:: NEXT + + Respect environment variable `SOURCE_DATE_EPOCH`_ for default of :option:`gcovr --timestamp`. .. versionadded:: 5.1 The :option:`gcovr --timestamp` option. +.. _SOURCE_DATE_EPOCH: https://reproducible-builds.org/docs/source-date-epoch + Timestamp Syntax ---------------- diff --git a/gcovr/configuration.py b/gcovr/configuration.py index 53a519b67c..702dd3f295 100644 --- a/gcovr/configuration.py +++ b/gcovr/configuration.py @@ -1162,7 +1162,8 @@ def merge_options_and_set_defaults( "Can use `YYYY-MM-DD hh:mm:ss` or epoch notation. " "Used by HTML, Coveralls, and Cobertura reports. " "Default: Environment variable SOURCE_DATE_EPOCH " - "(see https://reproducible-builds.org/docs/source-date-epoch) or current time." + "(see https://reproducible-builds.org/docs/source-date-epoch) " + "or current time." ), type=timestamp, default=datetime.datetime.fromtimestamp( From 8b65607f411f89d7d8364805c8a3a5c5b0497657 Mon Sep 17 00:00:00 2001 From: Spacetown Date: Wed, 22 Feb 2023 21:58:52 +0100 Subject: [PATCH 5/7] Manual merge parts from Lukas Co-authored-by: Lukas Atkinson --- ....cpp.9597a7a3397b8e3a48116e2a3afb4154.html | 2 +- .../example_html.details.functions.html | 2 +- doc/examples/example_html.details.html | 2 +- doc/examples/example_html.sh | 4 +- doc/examples/example_timestamps.sh | 4 ++ doc/source/guide/timestamps.rst | 32 ++++++++++--- gcovr/configuration.py | 48 +++++++++++++++++-- 7 files changed, 79 insertions(+), 15 deletions(-) diff --git a/doc/examples/example_html.details.example.cpp.9597a7a3397b8e3a48116e2a3afb4154.html b/doc/examples/example_html.details.example.cpp.9597a7a3397b8e3a48116e2a3afb4154.html index f7b2d5105b..cdfe47ce46 100644 --- a/doc/examples/example_html.details.example.cpp.9597a7a3397b8e3a48116e2a3afb4154.html +++ b/doc/examples/example_html.details.example.cpp.9597a7a3397b8e3a48116e2a3afb4154.html @@ -27,7 +27,7 @@

GCC Code Coverage Report

Date: - 1970-01-01 01:01:06 + 2021-11-08 21:12:28 diff --git a/doc/examples/example_html.details.functions.html b/doc/examples/example_html.details.functions.html index 18f40655a6..85916daa0e 100644 --- a/doc/examples/example_html.details.functions.html +++ b/doc/examples/example_html.details.functions.html @@ -23,7 +23,7 @@

GCC Code Coverage Report

Date: - 1970-01-01 01:01:06 + 2021-11-08 21:12:28 diff --git a/doc/examples/example_html.details.html b/doc/examples/example_html.details.html index 2cf696c3c4..c52214e089 100644 --- a/doc/examples/example_html.details.html +++ b/doc/examples/example_html.details.html @@ -23,7 +23,7 @@

GCC Code Coverage Report

Date: - 1970-01-01 01:01:06 + 2021-11-08 21:12:28 Coverage: diff --git a/doc/examples/example_html.sh b/doc/examples/example_html.sh index 5096c3a7d6..66edd169e7 100755 --- a/doc/examples/example_html.sh +++ b/doc/examples/example_html.sh @@ -6,9 +6,7 @@ set -euo pipefail # "use strict" # this function wraps gcovr to force a specific timestamp. # This can be ignored by end users and isn't really part of the example. gcovr() { - export SOURCE_DATE_EPOCH=3666 - python3 -m gcovr "$@" - unset SOURCE_DATE_EPOCH + python3 -m gcovr --timestamp="2021-11-08 21:12:28" "$@" } diff --git a/doc/examples/example_timestamps.sh b/doc/examples/example_timestamps.sh index fdacbb6f4c..548f324c9e 100755 --- a/doc/examples/example_timestamps.sh +++ b/doc/examples/example_timestamps.sh @@ -6,6 +6,10 @@ set -x # trace all commands gcovr --timestamp 1640606727 #END simple epoch +#BEGIN source date epoch +SOURCE_DATE_EPOCH=1640606727 gcovr +#END source date epoch + #BEGIN simple RFC 3339 gcovr --timestamp '2021-12-27 13:05:27' #END simple RFC 3339 diff --git a/doc/source/guide/timestamps.rst b/doc/source/guide/timestamps.rst index 973453e152..2c9f036e11 100644 --- a/doc/source/guide/timestamps.rst +++ b/doc/source/guide/timestamps.rst @@ -6,11 +6,9 @@ Reproducible Timestamps In some cases, it may be desirable to list a specific timestamp in the report. Timestamps are shown in the :ref:`html_output`, :ref:`coveralls_output`, and the :ref:`cobertura_output`. -This can be achieved via the :option:`--timestamp ` option. +This can be achieved via the :option:`--timestamp ` option +or via :ref:`Using SOURCE_DATE_EPOCH` environment variable. This option does not affect the modification times or other filesystem metadata. -The default for the variable is taken from the environment variable -`SOURCE_DATE_EPOCH`_ converted to a human readable time or to the current time is -the environment variable isn't defined. .. versionadded:: NEXT @@ -20,8 +18,6 @@ the environment variable isn't defined. The :option:`gcovr --timestamp` option. -.. _SOURCE_DATE_EPOCH: https://reproducible-builds.org/docs/source-date-epoch - Timestamp Syntax ---------------- @@ -119,3 +115,27 @@ The supported settings are: ``--date=iso8601-strict``, ``--date=iso-strict-local``, ``--date=iso8601-strict-local`` + +.. _Using SOURCE_DATE_EPOCH: + +Using SOURCE_DATE_EPOCH +----------------------- + +The Reproducible Builds project defines the ``SOURCE_DATE_EPOCH`` variable. +Gcovr will use this variable as a default timestamp +if no explicit :option:`--timestamp ` is set. + +The contents of this variable *must* be an UTC epoch, without any prefix. +No other format is supported. +Example usage: + +.. include:: ../../examples/example_timestamps.sh + :code: bash + :start-after: #BEGIN source date epoch + :end-before: #END source date epoch + +For more information on setting and using this variable, +see the `Reproducible Builds documentation on SOURCE_DATE_EPOCH +`_. + +.. _SOURCE_DATE_EPOCH: https://reproducible-builds.org/docs/source-date-epoch/ diff --git a/gcovr/configuration.py b/gcovr/configuration.py index 702dd3f295..7531692b53 100644 --- a/gcovr/configuration.py +++ b/gcovr/configuration.py @@ -21,6 +21,7 @@ from argparse import ArgumentParser, ArgumentTypeError, SUPPRESS from inspect import isclass from locale import getpreferredencoding +import logging from multiprocessing import cpu_count import time from typing import Iterable, Any, List, Optional, Union, Callable, TextIO, Dict @@ -32,6 +33,8 @@ from .utils import FilterOption, force_unix_separator from .writer.html import CssRenderer +logger = logging.getLogger("gcovr") + def check_percentage(value: str) -> float: r""" @@ -95,6 +98,46 @@ def timestamp(value: str) -> datetime.datetime: raise ArgumentTypeError(f"{ex}: {value!r}") from None +def source_date_epoch() -> Optional[datetime.datetime]: + """ + Load time from SOURCE_DATE_EPOCH, if it exists. + See: + Examples: + >>> monkeypatch = getfixture("monkeypatch") + >>> caplog = getfixture("caplog") + Example: can be empty + >>> with monkeypatch.context() as mp: + ... mp.delenv("SOURCE_DATE_EPOCH", raising=False) + ... print(source_date_epoch()) + None + Example: can contain timestamp + >>> with monkeypatch.context() as mp: + ... mp.setenv("SOURCE_DATE_EPOCH", "1677067226") + ... print(source_date_epoch()) + 2023-02-22 12:00:26 + Example: can contain invalid timestamp + >>> with monkeypatch.context() as mp: + ... mp.setenv("SOURCE_DATE_EPOCH", "not a timestamp") + ... print(source_date_epoch()) + None + >>> for m in caplog.messages: print(m) + ignoring invalid environment variable SOURCE_DATE_EPOCH='not a timestamp' + """ + + ts = os.environ.get("SOURCE_DATE_EPOCH") + + if ts: + try: + return datetime.datetime.fromtimestamp(int(ts), datetime.timezone.utc) + except Exception: + logger.warning( + "Ignoring invalid environment variable SOURCE_DATE_EPOCH=%r", + ts, + ) + + return None + + class OutputOrDefault: """An output path that may be empty. @@ -1166,9 +1209,8 @@ def merge_options_and_set_defaults( "or current time." ), type=timestamp, - default=datetime.datetime.fromtimestamp( - int(os.environ.get("SOURCE_DATE_EPOCH", time.time())) - ), + default=source_date_epoch() + or datetime.datetime.fromtimestamp(time.time(), datetime.timezone.utc), ), GcovrConfigOption( "filter", From 27cec263f1cc35024354bbf4be72a357b6a6aef7 Mon Sep 17 00:00:00 2001 From: Spacetown Date: Wed, 22 Feb 2023 22:36:20 +0100 Subject: [PATCH 6/7] Use UTC format only for SOURCE_DATE_EPOCH. --- gcovr/configuration.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gcovr/configuration.py b/gcovr/configuration.py index 7531692b53..f42dae845c 100644 --- a/gcovr/configuration.py +++ b/gcovr/configuration.py @@ -23,7 +23,6 @@ from locale import getpreferredencoding import logging from multiprocessing import cpu_count -import time from typing import Iterable, Any, List, Optional, Union, Callable, TextIO, Dict from dataclasses import dataclass import datetime @@ -1209,8 +1208,7 @@ def merge_options_and_set_defaults( "or current time." ), type=timestamp, - default=source_date_epoch() - or datetime.datetime.fromtimestamp(time.time(), datetime.timezone.utc), + default=source_date_epoch() or datetime.datetime.now(), ), GcovrConfigOption( "filter", From a0309a9cfb759221985db7dbed01561701613d43 Mon Sep 17 00:00:00 2001 From: Spacetown Date: Wed, 22 Feb 2023 22:45:03 +0100 Subject: [PATCH 7/7] Fix doctest. --- gcovr/configuration.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gcovr/configuration.py b/gcovr/configuration.py index f42dae845c..2a15d4249d 100644 --- a/gcovr/configuration.py +++ b/gcovr/configuration.py @@ -101,26 +101,30 @@ def source_date_epoch() -> Optional[datetime.datetime]: """ Load time from SOURCE_DATE_EPOCH, if it exists. See: + Examples: >>> monkeypatch = getfixture("monkeypatch") >>> caplog = getfixture("caplog") + Example: can be empty >>> with monkeypatch.context() as mp: ... mp.delenv("SOURCE_DATE_EPOCH", raising=False) ... print(source_date_epoch()) None + Example: can contain timestamp >>> with monkeypatch.context() as mp: ... mp.setenv("SOURCE_DATE_EPOCH", "1677067226") ... print(source_date_epoch()) - 2023-02-22 12:00:26 + 2023-02-22 12:00:26+00:00 + Example: can contain invalid timestamp >>> with monkeypatch.context() as mp: ... mp.setenv("SOURCE_DATE_EPOCH", "not a timestamp") ... print(source_date_epoch()) None >>> for m in caplog.messages: print(m) - ignoring invalid environment variable SOURCE_DATE_EPOCH='not a timestamp' + Ignoring invalid environment variable SOURCE_DATE_EPOCH='not a timestamp' """ ts = os.environ.get("SOURCE_DATE_EPOCH")