build: refactorize update_build_version (#5079)

This commit prepares the next change to stop parsing the
CHANGES file to determine version, but to use tags instead.
This commit is contained in:
Nathan Gauër 2023-01-27 15:36:09 +01:00 committed by GitHub
parent 96c5dac559
commit 64ba112ffb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -35,9 +35,13 @@ import os
import os.path
import re
import subprocess
import logging
import sys
import time
# Format of the output generated by this script. Example:
# "v2023.1", "SPIRV-Tools v2023.1 0fc5526f2b01a0cc89192c10cf8bef77f1007a62, 2023-01-18T14:51:49"
OUTPUT_FORMAT = '"{version_tag}", "SPIRV-Tools {version_tag} {description}"\n'
def mkdir_p(directory):
"""Make the directory, and all its ancestors as required. Any of the
@ -55,7 +59,6 @@ def mkdir_p(directory):
else:
raise
def command_output(cmd, directory):
"""Runs a command in a directory and returns its standard output stream.
@ -63,23 +66,29 @@ def command_output(cmd, directory):
Raises a RuntimeError if the command fails to launch or otherwise fails.
"""
p = subprocess.Popen(cmd,
cwd=directory,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(stdout, _) = p.communicate()
if p.returncode != 0:
raise RuntimeError('Failed to run %s in %s' % (cmd, directory))
return stdout
try:
p = subprocess.Popen(cmd,
cwd=directory,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(stdout, stderr) = p.communicate()
if p.returncode != 0:
logging.error('Failed to run "{}" in "{}": {}'.format(cmd, directory, stderr.decode()))
except Exception as e:
logging.error('Failed to run "{}" in "{}": {}'.format(cmd, directory, str(e)))
return False, None
return p.returncode == 0, stdout
def deduce_software_version(changes_file):
"""Returns a software version number parsed from the given CHANGES file.
"""Returns a tuple (success, software version number) parsed from the
given CHANGES file.
The CHANGES file describes most recent versions first.
Success is set to True if the software version could be deduced.
Software version is undefined if success if False.
Function expects the CHANGES file to describes most recent versions first.
"""
# Match the first well-formed version-and-date line.
# Match the first well-formed version-and-date line
# Allow trailing whitespace in the checked-out source code has
# unexpected carriage returns on a linefeed-only system such as
# Linux.
@ -88,60 +97,69 @@ def deduce_software_version(changes_file):
for line in f.readlines():
match = pattern.match(line)
if match:
return match.group(1)
raise Exception('No version number found in {}'.format(changes_file))
return True, match.group(1)
return False, None
def describe(directory):
def describe(repo_path):
"""Returns a string describing the current Git HEAD version as descriptively
as possible.
Runs 'git describe', or alternately 'git rev-parse HEAD', in directory. If
successful, returns the output; otherwise returns 'unknown hash, <date>'."""
try:
# decode() is needed here for Python3 compatibility. In Python2,
# str and bytes are the same type, but not in Python3.
# Popen.communicate() returns a bytes instance, which needs to be
# decoded into text data first in Python3. And this decode() won't
# hurt Python2.
return command_output(['git', 'describe'], directory).rstrip().decode()
except:
try:
return command_output(
['git', 'rev-parse', 'HEAD'], directory).rstrip().decode()
except:
# This is the fallback case where git gives us no information,
# e.g. because the source tree might not be in a git tree.
# In this case, usually use a timestamp. However, to ensure
# reproducible builds, allow the builder to override the wall
# clock time with environment variable SOURCE_DATE_EPOCH
# containing a (presumably) fixed timestamp.
timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
formatted = datetime.datetime.utcfromtimestamp(timestamp).isoformat()
return 'unknown hash, {}'.format(formatted)
success, output = command_output(['git', 'describe'], repo_path)
if not success:
output = command_output(['git', 'rev-parse', 'HEAD'], repo_path)
if success:
# decode() is needed here for Python3 compatibility. In Python2,
# str and bytes are the same type, but not in Python3.
# Popen.communicate() returns a bytes instance, which needs to be
# decoded into text data first in Python3. And this decode() won't
# hurt Python2.
return output.rstrip().decode()
# This is the fallback case where git gives us no information,
# e.g. because the source tree might not be in a git tree.
# In this case, usually use a timestamp. However, to ensure
# reproducible builds, allow the builder to override the wall
# clock time with environment variable SOURCE_DATE_EPOCH
# containing a (presumably) fixed timestamp.
timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
iso_date = datetime.datetime.utcfromtimestamp(timestamp).isoformat()
return "unknown hash, {}".format(iso_date)
def main():
FORMAT = '%(asctime)s %(message)s'
logging.basicConfig(format="[%(asctime)s][%(levelname)-8s] %(message)s", datefmt="%H:%M:%S")
if len(sys.argv) != 3:
print('usage: {} <changes-files> <output-file>'.format(sys.argv[0]))
logging.error("usage: {} <repo-path> <output-file>".format(sys.argv[0]))
sys.exit(1)
output_file = sys.argv[2]
mkdir_p(os.path.dirname(output_file))
changes_file_path = os.path.realpath(sys.argv[1])
output_file_path = sys.argv[2]
software_version = deduce_software_version(sys.argv[1])
directory = os.path.dirname(sys.argv[1])
new_content = '"{}", "SPIRV-Tools {} {}"\n'.format(
software_version, software_version,
describe(directory).replace('"', '\\"'))
success, version = deduce_software_version(changes_file_path)
if not success:
logging.error("Could not deduce latest release version from {}.".format(changes_file_path))
sys.exit(1)
if os.path.isfile(output_file):
with open(output_file, 'r') as f:
if new_content == f.read():
return
repo_path = os.path.dirname(changes_file_path)
description = describe(repo_path)
content = OUTPUT_FORMAT.format(version_tag=version, description=description)
with open(output_file, 'w') as f:
f.write(new_content)
# Escape file content.
content.replace('"', '\\"')
if os.path.isfile(output_file_path):
with open(output_file_path, 'r') as f:
if content == f.read():
return
mkdir_p(os.path.dirname(output_file_path))
with open(output_file_path, 'w') as f:
f.write(content)
if __name__ == '__main__':
main()