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
This commit is contained in:
Matthias Donaubauer 2022-06-20 10:45:50 +02:00 committed by GitHub
parent 2365c4a603
commit efadf67a12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 250 additions and 3 deletions

2
.bazelrc Normal file
View File

@ -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""

View File

@ -1,6 +1,7 @@
licenses(["notice"]) licenses(["notice"])
load("//:config/generate_export_header.bzl", "generate_export_header") 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 header to provide ABI export symbols
generate_export_header( generate_export_header(
@ -9,6 +10,23 @@ generate_export_header(
static_define = "BENCHMARK_STATIC_DEFINE", 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( config_setting(
name = "qnx", name = "qnx",
constraint_values = ["@platforms//os:qnx"], constraint_values = ["@platforms//os:qnx"],
@ -39,6 +57,7 @@ cc_library(
hdrs = [ hdrs = [
"include/benchmark/benchmark.h", "include/benchmark/benchmark.h",
"include/benchmark/export.h", # From generate_export_header "include/benchmark/export.h", # From generate_export_header
"include/benchmark/version.h", # From generate_version_header
], ],
linkopts = select({ linkopts = select({
":windows": ["-DEFAULTLIB:shlwapi.lib"], ":windows": ["-DEFAULTLIB:shlwapi.lib"],

View File

@ -322,6 +322,9 @@ if (BENCHMARK_ENABLE_LIBPFM)
find_package(PFM) find_package(PFM)
endif() 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 # Set up directories
include_directories(${PROJECT_SOURCE_DIR}/include) include_directories(${PROJECT_SOURCE_DIR}/include)

View File

@ -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"]),
},
)

99
config/get_git_version.py Normal file
View File

@ -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()

View File

@ -8,8 +8,9 @@
* `git log $(git describe --abbrev=0 --tags)..HEAD` gives you the list of * `git log $(git describe --abbrev=0 --tags)..HEAD` gives you the list of
commits between the last annotated tag and HEAD commits between the last annotated tag and HEAD
* Pick the most interesting. * Pick the most interesting.
* Create one last commit that updates the version saved in `CMakeLists.txt` and the * Create one last commit that updates the version saved in `CMakeLists.txt`, the
`__version__` variable in `bindings/python/google_benchmark/__init__.py`to the release `__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 version you're creating. (This version will be used if benchmark is installed from the
archive you'll be creating in the next step.) archive you'll be creating in the next step.)
@ -17,6 +18,10 @@
project (benchmark VERSION 1.6.0 LANGUAGES CXX) project (benchmark VERSION 1.6.0 LANGUAGES CXX)
``` ```
```
build --workspace_status_command "python workspace_status.py --default_version "1.6.1""
```
```python ```python
# bindings/python/google_benchmark/__init__.py # bindings/python/google_benchmark/__init__.py

6
include/version.h.in Normal file
View File

@ -0,0 +1,6 @@
#ifndef VERSION_H
#define VERSION_H
// clang-format off
#define LIBBENCHMARK_VERSION "@VERSION@"
// clang-format on
#endif // VERSION_H

View File

@ -23,6 +23,7 @@
#include <vector> #include <vector>
#include "benchmark/benchmark.h" #include "benchmark/benchmark.h"
#include "benchmark/version.h"
#include "complexity.h" #include "complexity.h"
#include "string_util.h" #include "string_util.h"
#include "timers.h" #include "timers.h"
@ -124,6 +125,9 @@ bool JSONReporter::ReportContext(const Context& context) {
std::string walltime_value = LocalDateTimeString(); std::string walltime_value = LocalDateTimeString();
out << indent << FormatKV("date", walltime_value) << ",\n"; 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"; out << indent << FormatKV("host_name", context.sys_info.name) << ",\n";
if (Context::executable_name) { if (Context::executable_name) {

View File

@ -20,6 +20,7 @@
#include <vector> #include <vector>
#include "benchmark/benchmark.h" #include "benchmark/benchmark.h"
#include "benchmark/version.h"
#include "check.h" #include "check.h"
#include "string_util.h" #include "string_util.h"
#include "timers.h" #include "timers.h"
@ -43,7 +44,7 @@ void BenchmarkReporter::PrintBasicContext(std::ostream *out,
if (context.executable_name) if (context.executable_name)
Out << "Running " << context.executable_name << "\n"; Out << "Running " << context.executable_name << "\n";
Out << "libbenchmark version: " << LIBBENCHMARK_VERSION << "\n";
const CPUInfo &info = context.cpu_info; const CPUInfo &info = context.cpu_info;
Out << "Run on (" << info.num_cpus << " X " Out << "Run on (" << info.num_cpus << " X "
<< (info.cycles_per_second / 1000000.0) << " MHz CPU " << (info.cycles_per_second / 1000000.0) << " MHz CPU "

View File

@ -18,12 +18,14 @@ static int AddContextCases() {
{ {
{"^%int-%int-%intT%int:%int:%int[-+]%int:%int$", MR_Default}, {"^%int-%int-%intT%int:%int:%int[-+]%int:%int$", MR_Default},
{"Running .*(/|\\\\)reporter_output_test(\\.exe)?$", MR_Next}, {"Running .*(/|\\\\)reporter_output_test(\\.exe)?$", MR_Next},
{"libbenchmark version: %int\\.%int\\.%int", MR_Next},
{"Run on \\(%int X %float MHz CPU s?\\)", MR_Next}, {"Run on \\(%int X %float MHz CPU s?\\)", MR_Next},
}); });
AddCases(TC_JSONOut, AddCases(TC_JSONOut,
{{"^\\{", MR_Default}, {{"^\\{", MR_Default},
{"\"context\":", MR_Next}, {"\"context\":", MR_Next},
{"\"date\": \"", MR_Next}, {"\"date\": \"", MR_Next},
{"\"libbenchmark version\":", MR_Next},
{"\"host_name\":", MR_Next}, {"\"host_name\":", MR_Next},
{"\"executable\": \".*(/|\\\\)reporter_output_test(\\.exe)?\",", {"\"executable\": \".*(/|\\\\)reporter_output_test(\\.exe)?\",",
MR_Next}, MR_Next},

View File

@ -1,6 +1,7 @@
{ {
"context": { "context": {
"date": "2016-08-02 17:44:46", "date": "2016-08-02 17:44:46",
"libbenchmark_version": "1.6.1",
"num_cpus": 4, "num_cpus": 4,
"mhz_per_cpu": 4228, "mhz_per_cpu": 4228,
"cpu_scaling_enabled": false, "cpu_scaling_enabled": false,

View File

@ -1,6 +1,7 @@
{ {
"context": { "context": {
"date": "2016-08-02 17:44:46", "date": "2016-08-02 17:44:46",
"libbenchmark_version": "1.6.1",
"num_cpus": 4, "num_cpus": 4,
"mhz_per_cpu": 4228, "mhz_per_cpu": 4228,
"cpu_scaling_enabled": false, "cpu_scaling_enabled": false,

View File

@ -1,6 +1,7 @@
{ {
"context": { "context": {
"date": "2016-08-02 17:44:46", "date": "2016-08-02 17:44:46",
"libbenchmark_version": "1.6.1",
"num_cpus": 4, "num_cpus": 4,
"mhz_per_cpu": 4228, "mhz_per_cpu": 4228,
"cpu_scaling_enabled": false, "cpu_scaling_enabled": false,

View File

@ -1,6 +1,7 @@
{ {
"context": { "context": {
"date": "2016-08-02 17:44:46", "date": "2016-08-02 17:44:46",
"libbenchmark_version": "1.6.1",
"num_cpus": 4, "num_cpus": 4,
"mhz_per_cpu": 4228, "mhz_per_cpu": 4228,
"cpu_scaling_enabled": false, "cpu_scaling_enabled": false,

View File

@ -1,6 +1,7 @@
{ {
"context": { "context": {
"date": "2016-08-02 17:44:46", "date": "2016-08-02 17:44:46",
"libbenchmark_version": "1.6.1",
"num_cpus": 4, "num_cpus": 4,
"mhz_per_cpu": 4228, "mhz_per_cpu": 4228,
"cpu_scaling_enabled": false, "cpu_scaling_enabled": false,

View File

@ -1,6 +1,7 @@
{ {
"context": { "context": {
"date": "2016-08-02 17:44:46", "date": "2016-08-02 17:44:46",
"libbenchmark_version": "1.6.1",
"num_cpus": 4, "num_cpus": 4,
"mhz_per_cpu": 4228, "mhz_per_cpu": 4228,
"cpu_scaling_enabled": false, "cpu_scaling_enabled": false,

View File

@ -1,6 +1,7 @@
{ {
"context": { "context": {
"date": "2016-08-02 17:44:46", "date": "2016-08-02 17:44:46",
"libbenchmark_version": "1.6.1",
"num_cpus": 4, "num_cpus": 4,
"mhz_per_cpu": 4228, "mhz_per_cpu": 4228,
"cpu_scaling_enabled": false, "cpu_scaling_enabled": false,

69
workspace_status.py Normal file
View File

@ -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()