From efadf67a124528389b7faa5ed75a358528dbae22 Mon Sep 17 00:00:00 2001 From: Matthias Donaubauer <59538007+Matthdonau@users.noreply.github.com> Date: Mon, 20 Jun 2022 10:45:50 +0200 Subject: [PATCH] Add possibility to ask for libbenchmark version number (#1004) (#1403) * Add possibility to ask for libbenchmark version number (#1004) Add a header which holds the current major, minor, and patch number of the library. The header is auto generated by CMake. * Do not generate unused functions (#1004) * Add support for version number in bazel (#1004) * Fix clang format #1004 * Fix more clang format problems (#1004) * Use git version feature of cmake to determine current lib version * Rename version_config header to version * Bake git version into bazel build * Use same input config header as in cmake for version.h * Adapt the releasing.md to include versioning in bazel --- .bazelrc | 2 + BUILD.bazel | 19 ++++++ CMakeLists.txt | 3 + config/generate_version_header.bzl | 30 +++++++++ config/get_git_version.py | 99 +++++++++++++++++++++++++++++ docs/releasing.md | 9 ++- include/version.h.in | 6 ++ src/json_reporter.cc | 4 ++ src/reporter.cc | 3 +- test/reporter_output_test.cc | 2 + tools/gbench/Inputs/test1_run1.json | 1 + tools/gbench/Inputs/test1_run2.json | 1 + tools/gbench/Inputs/test2_run.json | 1 + tools/gbench/Inputs/test3_run0.json | 1 + tools/gbench/Inputs/test3_run1.json | 1 + tools/gbench/Inputs/test4_run0.json | 1 + tools/gbench/Inputs/test4_run1.json | 1 + workspace_status.py | 69 ++++++++++++++++++++ 18 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 .bazelrc create mode 100644 config/generate_version_header.bzl create mode 100644 config/get_git_version.py create mode 100644 include/version.h.in create mode 100644 workspace_status.py diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 00000000..acfaffce --- /dev/null +++ b/.bazelrc @@ -0,0 +1,2 @@ +# Call workspace_status.py when building benchmark to determine current version of the lib +build --workspace_status_command "python workspace_status.py --default_version "1.6.1"" diff --git a/BUILD.bazel b/BUILD.bazel index 872adb0f..3ea28530 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,6 +1,7 @@ licenses(["notice"]) load("//:config/generate_export_header.bzl", "generate_export_header") +load("//:config/generate_version_header.bzl", "generate_version_header") # Generate header to provide ABI export symbols generate_export_header( @@ -9,6 +10,23 @@ generate_export_header( static_define = "BENCHMARK_STATIC_DEFINE", ) +# Get the git version variables +py_binary( + name = "get_git_version", + srcs = ["config/get_git_version.py"], + python_version = "PY3" +) + +# Generate version header +generate_version_header( + name = "generate_version_header", + git_version_name = "GIT_VERSION", + git_is_dirty_name = "GIT_IS_DIRTY", + default_version = "DEFAULT_VERSION", + header = "include/benchmark/version.h", + src = "include/version.h.in" +) + config_setting( name = "qnx", constraint_values = ["@platforms//os:qnx"], @@ -39,6 +57,7 @@ cc_library( hdrs = [ "include/benchmark/benchmark.h", "include/benchmark/export.h", # From generate_export_header + "include/benchmark/version.h", # From generate_version_header ], linkopts = select({ ":windows": ["-DEFAULTLIB:shlwapi.lib"], diff --git a/CMakeLists.txt b/CMakeLists.txt index 6880c1f3..ac6f82e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -322,6 +322,9 @@ if (BENCHMARK_ENABLE_LIBPFM) find_package(PFM) endif() +# Generate config file (currently only used for version num but may be expanded if needed.) +configure_file(${PROJECT_SOURCE_DIR}/include/version.h.in ${CMAKE_BINARY_DIR}/include/benchmark/version.h) + # Set up directories include_directories(${PROJECT_SOURCE_DIR}/include) diff --git a/config/generate_version_header.bzl b/config/generate_version_header.bzl new file mode 100644 index 00000000..95a97643 --- /dev/null +++ b/config/generate_version_header.bzl @@ -0,0 +1,30 @@ +def _generate_version_header_impl(ctx): + + args = ["--header", ctx.outputs.header.path] + ["--header_input", ctx.file.src.path]\ + + ["--volatile_file", ctx.version_file.path, \ + "--version_variable_name", ctx.attr.git_version_name, "--is_dirty_name",\ + ctx.attr.git_is_dirty_name, "--default_version", ctx.attr.default_version] + + ctx.actions.run( + inputs = [ctx.version_file, ctx.info_file, ctx.file.src], + outputs = [ctx.outputs.header], + arguments = args, + executable = ctx.executable._get_git_version_tool, + ) + +generate_version_header = rule( + implementation = _generate_version_header_impl, + attrs = { + "_get_git_version_tool": attr.label( + executable = True, + cfg = "host", + allow_files = True, + default = Label("//:get_git_version"), + ), + "git_version_name": attr.string(mandatory = True), + "git_is_dirty_name": attr.string(mandatory = True), + "default_version": attr.string(mandatory = True), + "header": attr.output(mandatory = True), + "src" : attr.label(allow_single_file = [".in"]), + }, +) diff --git a/config/get_git_version.py b/config/get_git_version.py new file mode 100644 index 00000000..f4ad0a9e --- /dev/null +++ b/config/get_git_version.py @@ -0,0 +1,99 @@ +# Before actually starting the build, workspace_status.py should have written +# the current git repository status as well as if the repository is dirty +# to volatile-status.txt. +# This script takes these information and generates the version.h which later is +# used by the library to report its version. +import argparse +import sys +import os +import re + + +def normalize_version(git_version, git_is_dirty): + if '-' in git_version: + cleaned = re.search('[0-9]+\.[0-9]+\.[0-9]\-[0-9]+', git_version) + cleaned_string = cleaned.group(0).replace("-", ".") + elif 'v' in git_version: + cleaned_string = git_version.replace("v", "") + else: + cleaned_string = git_version + + # In case the repository is in a dirty state (uncommited changes) + # we do tell the user during build by writing to stdout. + # That is the way it is done in the CMake Build as well. + # Maybe think about adding the -dirty also for the version header. + if git_is_dirty == "TRUE": + git_version_dirty = git_version+"-dirty" + print("git version: " + git_version_dirty + + " normalized to " + cleaned_string) + + return cleaned_string + + +def main(): + parser = argparse.ArgumentParser(description='Generate version header') + parser.add_argument('--header', + required=True, + help='output header file') + parser.add_argument('--header_input', + required=True, + help='input header file') + parser.add_argument('--volatile_file', + required=True, + help='file containing the git version variables') + parser.add_argument('--version_variable_name', + required=True, + help='variablename of the hash') + parser.add_argument('--is_dirty_name', + required=True, + help='variablename of the boolean communicating if the workspace has no local changes') + parser.add_argument('--default_version', + required=True, + help='variablename for version which should be used in case git was not executable.') + + args = parser.parse_args() + + # Read volatile-status.txt file + git_version = "" + is_dirty = "" + try: + with open(args.volatile_file, "r") as f: + for entry in f.read().split("\n"): + if entry: + key_value = entry.split(' ', 1) + key = key_value[0].strip() + if key == args.version_variable_name: + git_version = key_value[1].strip() + if key == args.is_dirty_name: + is_dirty = key_value[1].strip() + except: + # In case volatile-status cannot be read, exit with an error + sys.exit("Cannot open volatile-status.txt") + + if git_version == "" or is_dirty == "": + sys.exit("No usable entry in volatile-status.txt") + + git_version = normalize_version(git_version, is_dirty) + + # In case we werent able to determine the current version + # use the default set version + if git_version == "0.0.0": + git_version = args.default_version + + # Notify the user about the version used. + print("Version: " + git_version) + + # Write the actual version.h + texttosearch = "@VERSION@" + + with open(args.header_input, "r") as f: + with open(args.header, "w") as w: + for line in f: + if texttosearch in line: + w.write(line.replace(texttosearch, git_version)) + else: + w.write(line) + + +if __name__ == "__main__": + main() diff --git a/docs/releasing.md b/docs/releasing.md index 6d3b6138..4b931a3d 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -8,8 +8,9 @@ * `git log $(git describe --abbrev=0 --tags)..HEAD` gives you the list of commits between the last annotated tag and HEAD * Pick the most interesting. -* Create one last commit that updates the version saved in `CMakeLists.txt` and the - `__version__` variable in `bindings/python/google_benchmark/__init__.py`to the release +* Create one last commit that updates the version saved in `CMakeLists.txt`, the + `__version__` variable in `bindings/python/google_benchmark/__init__.py` + and the default version in `.bazelrc` to the release version you're creating. (This version will be used if benchmark is installed from the archive you'll be creating in the next step.) @@ -17,6 +18,10 @@ project (benchmark VERSION 1.6.0 LANGUAGES CXX) ``` +``` +build --workspace_status_command "python workspace_status.py --default_version "1.6.1"" +``` + ```python # bindings/python/google_benchmark/__init__.py diff --git a/include/version.h.in b/include/version.h.in new file mode 100644 index 00000000..b97a0cd5 --- /dev/null +++ b/include/version.h.in @@ -0,0 +1,6 @@ +#ifndef VERSION_H +#define VERSION_H +// clang-format off +#define LIBBENCHMARK_VERSION "@VERSION@" +// clang-format on +#endif // VERSION_H \ No newline at end of file diff --git a/src/json_reporter.cc b/src/json_reporter.cc index e9999e18..fce5ba43 100644 --- a/src/json_reporter.cc +++ b/src/json_reporter.cc @@ -23,6 +23,7 @@ #include #include "benchmark/benchmark.h" +#include "benchmark/version.h" #include "complexity.h" #include "string_util.h" #include "timers.h" @@ -124,6 +125,9 @@ bool JSONReporter::ReportContext(const Context& context) { std::string walltime_value = LocalDateTimeString(); out << indent << FormatKV("date", walltime_value) << ",\n"; + out << indent << FormatKV("libbenchmark version", LIBBENCHMARK_VERSION) + << ",\n"; + out << indent << FormatKV("host_name", context.sys_info.name) << ",\n"; if (Context::executable_name) { diff --git a/src/reporter.cc b/src/reporter.cc index 1d2df17b..d2718d2f 100644 --- a/src/reporter.cc +++ b/src/reporter.cc @@ -20,6 +20,7 @@ #include #include "benchmark/benchmark.h" +#include "benchmark/version.h" #include "check.h" #include "string_util.h" #include "timers.h" @@ -43,7 +44,7 @@ void BenchmarkReporter::PrintBasicContext(std::ostream *out, if (context.executable_name) Out << "Running " << context.executable_name << "\n"; - + Out << "libbenchmark version: " << LIBBENCHMARK_VERSION << "\n"; const CPUInfo &info = context.cpu_info; Out << "Run on (" << info.num_cpus << " X " << (info.cycles_per_second / 1000000.0) << " MHz CPU " diff --git a/test/reporter_output_test.cc b/test/reporter_output_test.cc index 65bb14a1..34dc3c2e 100644 --- a/test/reporter_output_test.cc +++ b/test/reporter_output_test.cc @@ -18,12 +18,14 @@ static int AddContextCases() { { {"^%int-%int-%intT%int:%int:%int[-+]%int:%int$", MR_Default}, {"Running .*(/|\\\\)reporter_output_test(\\.exe)?$", MR_Next}, + {"libbenchmark version: %int\\.%int\\.%int", MR_Next}, {"Run on \\(%int X %float MHz CPU s?\\)", MR_Next}, }); AddCases(TC_JSONOut, {{"^\\{", MR_Default}, {"\"context\":", MR_Next}, {"\"date\": \"", MR_Next}, + {"\"libbenchmark version\":", MR_Next}, {"\"host_name\":", MR_Next}, {"\"executable\": \".*(/|\\\\)reporter_output_test(\\.exe)?\",", MR_Next}, diff --git a/tools/gbench/Inputs/test1_run1.json b/tools/gbench/Inputs/test1_run1.json index 9daed0bc..f6840516 100644 --- a/tools/gbench/Inputs/test1_run1.json +++ b/tools/gbench/Inputs/test1_run1.json @@ -1,6 +1,7 @@ { "context": { "date": "2016-08-02 17:44:46", + "libbenchmark_version": "1.6.1", "num_cpus": 4, "mhz_per_cpu": 4228, "cpu_scaling_enabled": false, diff --git a/tools/gbench/Inputs/test1_run2.json b/tools/gbench/Inputs/test1_run2.json index dc52970a..219aa725 100644 --- a/tools/gbench/Inputs/test1_run2.json +++ b/tools/gbench/Inputs/test1_run2.json @@ -1,6 +1,7 @@ { "context": { "date": "2016-08-02 17:44:46", + "libbenchmark_version": "1.6.1", "num_cpus": 4, "mhz_per_cpu": 4228, "cpu_scaling_enabled": false, diff --git a/tools/gbench/Inputs/test2_run.json b/tools/gbench/Inputs/test2_run.json index 15bc6980..2a02926d 100644 --- a/tools/gbench/Inputs/test2_run.json +++ b/tools/gbench/Inputs/test2_run.json @@ -1,6 +1,7 @@ { "context": { "date": "2016-08-02 17:44:46", + "libbenchmark_version": "1.6.1", "num_cpus": 4, "mhz_per_cpu": 4228, "cpu_scaling_enabled": false, diff --git a/tools/gbench/Inputs/test3_run0.json b/tools/gbench/Inputs/test3_run0.json index 49f8b061..a8ca6750 100644 --- a/tools/gbench/Inputs/test3_run0.json +++ b/tools/gbench/Inputs/test3_run0.json @@ -1,6 +1,7 @@ { "context": { "date": "2016-08-02 17:44:46", + "libbenchmark_version": "1.6.1", "num_cpus": 4, "mhz_per_cpu": 4228, "cpu_scaling_enabled": false, diff --git a/tools/gbench/Inputs/test3_run1.json b/tools/gbench/Inputs/test3_run1.json index acc5ba17..5f2bf9fd 100644 --- a/tools/gbench/Inputs/test3_run1.json +++ b/tools/gbench/Inputs/test3_run1.json @@ -1,6 +1,7 @@ { "context": { "date": "2016-08-02 17:44:46", + "libbenchmark_version": "1.6.1", "num_cpus": 4, "mhz_per_cpu": 4228, "cpu_scaling_enabled": false, diff --git a/tools/gbench/Inputs/test4_run0.json b/tools/gbench/Inputs/test4_run0.json index 54cf1275..f8d55f58 100644 --- a/tools/gbench/Inputs/test4_run0.json +++ b/tools/gbench/Inputs/test4_run0.json @@ -1,6 +1,7 @@ { "context": { "date": "2016-08-02 17:44:46", + "libbenchmark_version": "1.6.1", "num_cpus": 4, "mhz_per_cpu": 4228, "cpu_scaling_enabled": false, diff --git a/tools/gbench/Inputs/test4_run1.json b/tools/gbench/Inputs/test4_run1.json index 25d56050..f8287e49 100644 --- a/tools/gbench/Inputs/test4_run1.json +++ b/tools/gbench/Inputs/test4_run1.json @@ -1,6 +1,7 @@ { "context": { "date": "2016-08-02 17:44:46", + "libbenchmark_version": "1.6.1", "num_cpus": 4, "mhz_per_cpu": 4228, "cpu_scaling_enabled": false, diff --git a/workspace_status.py b/workspace_status.py new file mode 100644 index 00000000..67c58cee --- /dev/null +++ b/workspace_status.py @@ -0,0 +1,69 @@ +# Get the current repository git status. +# This means get the current version tag and if the repository is dirty. +import subprocess +import sys +import argparse + + +def main(): + parser = argparse.ArgumentParser(description='') + parser.add_argument('--default_version', + required=True, + help='default version in case git can not be called') + args = parser.parse_args() + + # Get the current status of the repository by calling out to git. + # In case there is no git executable use the default version. + git_version = get_version(".") + git_is_dirty = get_git_dirty(".") + + # Write to volatile-status.txt. + # This is a bazel thing and the recommended way of + # getting version control status into bazel build according + # to bazels documentation. + print("GIT_VERSION {}".format(git_version)) + print("GIT_IS_DIRTY {}".format(git_is_dirty)) + print("DEFAULT_VERSION {}".format(args.default_version)) + + +def get_version(path): + try: + p = subprocess.Popen(["git", "describe", "--tags", "--match", + "v[0-9]*.[0-9]*.[0-9]*", "--abbrev=8"], cwd=path, stdout=subprocess.PIPE) + (out, err) = p.communicate() + + if p.returncode != 0: + return "v0.0.0" + return out.decode() + + except: + return "0.0.0" + + +def get_git_dirty(path): + try: + p = subprocess.Popen( + ["git", "update-index", "-q", "--refresh"], cwd=path, stdout=subprocess.PIPE) + (out, err) = p.communicate() + if p.returncode != 0: + return "TRUE" + + p = subprocess.Popen(["git", "diff-index", "--name-only", + "HEAD", "--"], cwd=path, stdout=subprocess.PIPE) + (out, err) = p.communicate() + if p.returncode != 0: + return "TRUE" + + if out.decode() != "": + return "TRUE" + else: + return "FALSE" + + except: + # Be pessimistic. In case git is not available + # assume the repository to be dirty. + return "TRUE" + + +if __name__ == "__main__": + main()