3 # Copyright (c) 2016 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Updates an output file with version info unless the new content is the same
18 # as the existing content.
20 # Args: <spirv-tools_dir> <output-file>
22 # The output file will contain a line of text consisting of two C source syntax
23 # string literals separated by a comma:
24 # - The software version deduced from the CHANGES file in the given directory.
25 # - A longer string with the project name, the software version number, and
26 # git commit information for the directory. The commit information
27 # is the output of "git describe" if that succeeds, or "git rev-parse HEAD"
28 # if that succeeds, or otherwise a message containing the phrase
30 # The string contents are escaped as necessary.
32 from __future__ import print_function
44 def mkdir_p(directory):
45 """Make the directory, and all its ancestors as required. Any of the
46 directories are allowed to already exist."""
49 # We're being asked to make the current directory.
53 os.makedirs(directory)
55 if e.errno == errno.EEXIST and os.path.isdir(directory):
61 def command_output(cmd, directory):
62 """Runs a command in a directory and returns its standard output stream.
64 Captures the standard error stream.
66 Raises a RuntimeError if the command fails to launch or otherwise fails.
68 p = subprocess.Popen(cmd,
70 stdout=subprocess.PIPE,
71 stderr=subprocess.PIPE)
72 (stdout, _) = p.communicate()
74 raise RuntimeError('Failed to run %s in %s' % (cmd, directory))
78 def deduce_software_version(directory):
79 """Returns a software version number parsed from the CHANGES file
80 in the given directory.
82 The CHANGES file describes most recent versions first.
85 # Match the first well-formed version-and-date line.
86 # Allow trailing whitespace in the checked-out source code has
87 # unexpected carriage returns on a linefeed-only system such as
89 pattern = re.compile(r'^(v\d+\.\d+(-dev)?) \d\d\d\d-\d\d-\d\d\s*$')
90 changes_file = os.path.join(directory, 'CHANGES')
91 with open(changes_file, mode='rU') as f:
92 for line in f.readlines():
93 match = pattern.match(line)
96 raise Exception('No version number found in {}'.format(changes_file))
99 def describe(directory):
100 """Returns a string describing the current Git HEAD version as descriptively
103 Runs 'git describe', or alternately 'git rev-parse HEAD', in directory. If
104 successful, returns the output; otherwise returns 'unknown hash, <date>'."""
106 # decode() is needed here for Python3 compatibility. In Python2,
107 # str and bytes are the same type, but not in Python3.
108 # Popen.communicate() returns a bytes instance, which needs to be
109 # decoded into text data first in Python3. And this decode() won't
111 return command_output(['git', 'describe'], directory).rstrip().decode()
114 return command_output(
115 ['git', 'rev-parse', 'HEAD'], directory).rstrip().decode()
117 # This is the fallback case where git gives us no information,
118 # e.g. because the source tree might not be in a git tree.
119 # In this case, usually use a timestamp. However, to ensure
120 # reproducible builds, allow the builder to override the wall
121 # clock time with enviornment variable SOURCE_DATE_EPOCH
122 # containing a (presumably) fixed timestamp.
123 timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
124 formatted = datetime.date.fromtimestamp(timestamp).isoformat()
125 return 'unknown hash, {}'.format(formatted)
129 if len(sys.argv) != 3:
130 print('usage: {} <spirv-tools-dir> <output-file>'.format(sys.argv[0]))
133 output_file = sys.argv[2]
134 mkdir_p(os.path.dirname(output_file))
136 software_version = deduce_software_version(sys.argv[1])
137 new_content = '"{}", "SPIRV-Tools {} {}"\n'.format(
138 software_version, software_version,
139 describe(sys.argv[1]).replace('"', '\\"'))
141 if os.path.isfile(output_file):
142 with open(output_file, 'r') as f:
143 if new_content == f.read():
146 with open(output_file, 'w') as f:
149 if __name__ == '__main__':