Skip to content

Commit

Permalink
Add possibility to ask for libbenchmark version number (#1004) (#1403)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Matthdonau committed Jun 20, 2022
1 parent 2365c4a commit efadf67
Show file tree
Hide file tree
Showing 18 changed files with 250 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -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""
19 changes: 19 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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"],
Expand Down Expand Up @@ -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"],
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
30 changes: 30 additions & 0 deletions config/generate_version_header.bzl
Original file line number Diff line number Diff line change
@@ -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 changes: 99 additions & 0 deletions config/get_git_version.py
Original file line number Diff line number Diff line change
@@ -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()
9 changes: 7 additions & 2 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@
* `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.)

```
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

Expand Down
6 changes: 6 additions & 0 deletions include/version.h.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef VERSION_H
#define VERSION_H
// clang-format off
#define LIBBENCHMARK_VERSION "@VERSION@"
// clang-format on
#endif // VERSION_H
4 changes: 4 additions & 0 deletions src/json_reporter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <vector>

#include "benchmark/benchmark.h"
#include "benchmark/version.h"
#include "complexity.h"
#include "string_util.h"
#include "timers.h"
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion src/reporter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <vector>

#include "benchmark/benchmark.h"
#include "benchmark/version.h"
#include "check.h"
#include "string_util.h"
#include "timers.h"
Expand All @@ -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 "
Expand Down
2 changes: 2 additions & 0 deletions test/reporter_output_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
1 change: 1 addition & 0 deletions tools/gbench/Inputs/test1_run1.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
1 change: 1 addition & 0 deletions tools/gbench/Inputs/test1_run2.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
1 change: 1 addition & 0 deletions tools/gbench/Inputs/test2_run.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
1 change: 1 addition & 0 deletions tools/gbench/Inputs/test3_run0.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
1 change: 1 addition & 0 deletions tools/gbench/Inputs/test3_run1.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
1 change: 1 addition & 0 deletions tools/gbench/Inputs/test4_run0.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
1 change: 1 addition & 0 deletions tools/gbench/Inputs/test4_run1.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
69 changes: 69 additions & 0 deletions workspace_status.py
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit efadf67

Please sign in to comment.