Skip to content

Commit 373ac19

Browse files
committed
tools: add dir-version to print a package version from git references
1 parent 6bf8baa commit 373ac19

File tree

3 files changed

+192
-0
lines changed

3 files changed

+192
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ build
2020
*.dylib
2121
*.so
2222
*.a
23+
__pycache__
2324

2425
#ignore editor temporary files
2526
.*.swp

tools/dir-version/DirVersion.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Daemon BSD Source Code
2+
# Copyright (c) 2024-2026, Daemon Developers
3+
# All rights reserved.
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
# * Redistributions of source code must retain the above copyright
8+
# notice, this list of conditions and the following disclaimer.
9+
# * Redistributions in binary form must reproduce the above copyright
10+
# notice, this list of conditions and the following disclaimer in the
11+
# documentation and/or other materials provided with the distribution.
12+
# * Neither the name of the <organization> nor the
13+
# names of its contributors may be used to endorse or promote products
14+
# derived from this software without specific prior written permission.
15+
#
16+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
20+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
27+
import datetime
28+
import os
29+
import subprocess
30+
import sys
31+
import time
32+
33+
class _DirVersion():
34+
git_short_ref_length = 7
35+
is_permissive = False
36+
37+
def __init__(self, source_dir, is_permissive, is_quiet, is_local):
38+
if not os.path.isdir(source_dir):
39+
raise(ValueError, "not a directory")
40+
41+
self.process_stderr = None
42+
43+
if is_quiet:
44+
self.process_stderr = subprocess.DEVNULL
45+
46+
self.is_local = is_local
47+
48+
self.source_dir_realpath = os.path.realpath(source_dir)
49+
50+
self.git_command_list = ["git", "-C", self.source_dir_realpath]
51+
52+
# Test that Git is available and working.
53+
self.runGitCommand(["-v"])
54+
55+
self.is_permissive = is_permissive
56+
57+
def runGitCommand(self, command_list, is_permissive=False):
58+
command_list = self.git_command_list + command_list
59+
60+
process_check = not (self.is_permissive or is_permissive)
61+
62+
process = subprocess.run(command_list,
63+
stdout=subprocess.PIPE, stderr=self.process_stderr, check=process_check, text=True)
64+
65+
return process.stdout.rstrip(), process.returncode
66+
67+
def getDateString(self, timestamp):
68+
return datetime.datetime.utcfromtimestamp(timestamp).strftime('%Y%m%d-%H%M%S')
69+
70+
def isDirtyGit(self):
71+
if self.is_local:
72+
lookup_dir = self.source_dir_realpath
73+
else:
74+
# Git prints the Git repository root directory.
75+
git_show_toplevel_string, git_show_toplevel_returncode = \
76+
self.runGitCommand(["rev-parse", "--show-toplevel"])
77+
78+
lookup_dir = git_show_toplevel_string.splitlines()[0]
79+
80+
# Git returns 1 if there is at least one modified file in the given directory.
81+
git_diff_quiet_string, git_diff_quiet_returncode \
82+
= self.runGitCommand(["diff", "--quiet", lookup_dir], is_permissive=True)
83+
84+
if git_diff_quiet_returncode != 0:
85+
return True
86+
87+
# Git prints the list of untracked files in the given directory.
88+
git_ls_untracked_string, git_ls_untracked_returncode \
89+
= self.runGitCommand(["ls-files", "-z", "--others", "--exclude-standard", lookup_dir])
90+
91+
untracked_file_list = git_ls_untracked_string.split('\0')[:-1]
92+
93+
return len(untracked_file_list) > 0
94+
95+
def getVersionString(self):
96+
# Fallback version string.
97+
tag_string="0"
98+
date_string="-" + self.getDateString(time.time())
99+
ref_string=""
100+
dirt_string="+dirty"
101+
102+
# Git returns 1 if the directory is not a Git repository.
103+
git_last_commit_string, git_last_commit_returncode \
104+
= self.runGitCommand(["rev-parse", "HEAD", "--"])
105+
106+
# Git-based version string.
107+
if git_last_commit_returncode == 0:
108+
# Git prints the current commit reference.
109+
git_last_commit_short_string = git_last_commit_string[:self.git_short_ref_length]
110+
ref_string = "-" + git_last_commit_short_string
111+
112+
# Git prints the current commit date.
113+
git_last_commit_timestamp_string, git_last_commit_timestamp_returncode \
114+
= self.runGitCommand(["log", "-1", "--pretty=format:%ct"])
115+
116+
if git_last_commit_timestamp_returncode == 0:
117+
date_string = "-" + self.getDateString(int(git_last_commit_timestamp_string))
118+
119+
# Git prints the most recent tag or returns 1 if there is not tag at all.
120+
git_closest_tag_string, git_closest_tag_returncode \
121+
= self.runGitCommand(["describe", "--tags", "--abbrev=0", "--match", "v[0-9].*"])
122+
123+
if git_closest_tag_returncode == 0:
124+
git_closest_tag_version_string = git_closest_tag_string[1:]
125+
tag_string = git_closest_tag_version_string
126+
127+
# Git prints a version string that is equal to the most recent tag
128+
# if the most recent tag is on the current commit or returns 1 if
129+
# there is no tag at all.
130+
git_describe_tag_string, git_describe_tag_returncode \
131+
= self.runGitCommand(["describe", "--tags", "--match", "v[0-9].*"])
132+
git_describe_version_string = git_describe_tag_string[1:]
133+
134+
if git_describe_tag_returncode == 0:
135+
if git_closest_tag_version_string == git_describe_version_string:
136+
# Do not write current commit reference and date in version
137+
# string if the tag is on the current commit.
138+
date_string = ""
139+
ref_string = ""
140+
141+
if not self.isDirtyGit():
142+
# Do not write the dirty flag in version string if everything in
143+
# the Git repository is properly committed.
144+
dirt_string = ""
145+
146+
return tag_string + date_string + ref_string + dirt_string
147+
148+
def getVersionString(source_dir, is_permissive=False, is_quiet=False, is_local=False):
149+
return _DirVersion(source_dir, is_permissive, is_quiet, is_local).getVersionString()

tools/dir-version/dir-version

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#! /usr/bin/env python3
2+
3+
# Daemon BSD Source Code
4+
# Copyright (c) 2024-2026, Daemon Developers
5+
# All rights reserved.
6+
#
7+
# Redistribution and use in source and binary forms, with or without
8+
# modification, are permitted provided that the following conditions are met:
9+
# * Redistributions of source code must retain the above copyright
10+
# notice, this list of conditions and the following disclaimer.
11+
# * Redistributions in binary form must reproduce the above copyright
12+
# notice, this list of conditions and the following disclaimer in the
13+
# documentation and/or other materials provided with the distribution.
14+
# * Neither the name of the <organization> nor the
15+
# names of its contributors may be used to endorse or promote products
16+
# derived from this software without specific prior written permission.
17+
#
18+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
22+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28+
29+
import DirVersion
30+
31+
import argparse
32+
33+
parser = argparse.ArgumentParser(description="Print repository version string")
34+
35+
parser.add_argument("-p", "--permissive", dest="is_permissive", help="ignore Git errors", action="store_true")
36+
parser.add_argument("-q", "--quiet", dest="is_quiet", help="silence Git errors", action="store_true")
37+
parser.add_argument("-w", "--local", dest="is_local", help="look for dirt in given directory only, not in whole repository", action="store_true")
38+
parser.add_argument(dest="source_dir", nargs="?", metavar="DIRNAME", default=".", help="repository path")
39+
40+
args = parser.parse_args()
41+
42+
print(DirVersion.getVersionString(args.source_dir, is_permissive=args.is_permissive, is_quiet=args.is_quiet, is_local=args.is_local))

0 commit comments

Comments
 (0)