Revert "drsuapi_dissect_element_DsGetNCChangesCtr6TS_ctr6 dissect_krb5_PAC_NDRHEADERBLOB"
[wireshark-sm.git] / tools / make-version.py
blob11137805a6f1fe6028a46497cf74f42b34a46b94
1 #!/usr/bin/env python3
3 # Copyright 2022 by Moshe Kaplan
4 # Based on make-version.pl by Jörg Mayer
6 # Wireshark - Network traffic analyzer
7 # By Gerald Combs <gerald@wireshark.org>
8 # Copyright 1998 Gerald Combs
10 # SPDX-License-Identifier: GPL-2.0-or-later
12 # See below for usage.
14 # If run with the "-r" or "--set-release" argument the VERSION macro in
15 # CMakeLists.txt will have the version_extra template appended to the
16 # version number. vcs_version.h will _not_ be generated if either argument is
17 # present.
19 # make-version.py is called during the build to update vcs_version.h in the build
20 # directory. To set a fixed version, use something like:
22 # cmake -DVCSVERSION_OVERRIDE="Git v3.1.0 packaged as 3.1.0-1"
25 # XXX - We're pretty dumb about the "{vcsinfo}" substitution, and about having
26 # spaces in the package format.
28 import argparse
29 import os
30 import os.path
31 import re
32 import shlex
33 import shutil
34 import sys
35 import subprocess
37 GIT_ABBREV_LENGTH = 12
39 # `git archive` will use an 'export-subst' entry in .gitattributes to replace
40 # the $Format strings with `git log --pretty=format:` placeholders.
41 # The output will look something like the following:
42 # GIT_EXPORT_SUBST_H = '51315cf37cdf6c0add1b1c99cb7941aac4489a6f'
43 # GIT_EXPORT_SUBST_D = 'HEAD -> master, upstream/master, upstream/HEAD'
44 # If the text "$Format" is still present, it means that
45 # git archive did not replace the $Format string, which
46 # means that this not a git archive.
47 GIT_EXPORT_SUBST_H = '$Format:%H$'
48 GIT_EXPORT_SUBST_D = '$Format:%D$'
49 IS_GIT_ARCHIVE = not GIT_EXPORT_SUBST_H.startswith('$Format')
52 def update_cmakelists_txt(src_dir, set_version, repo_data):
53 if not set_version and repo_data['package_string'] == "":
54 return
56 cmake_filepath = os.path.join(src_dir, "CMakeLists.txt")
58 with open(cmake_filepath, encoding='utf-8') as fh:
59 cmake_contents = fh.read()
61 MAJOR_PATTERN = r"^set *\( *PROJECT_MAJOR_VERSION *\d+ *\)$"
62 MINOR_PATTERN = r"^set *\( *PROJECT_MINOR_VERSION *\d+ *\)$"
63 PATCH_PATTERN = r"^set *\( *PROJECT_PATCH_VERSION *\d+ *\)$"
64 VERSION_EXTENSION_PATTERN = r"^set *\( *PROJECT_VERSION_EXTENSION .*?$"
66 new_cmake_contents = cmake_contents
67 new_cmake_contents = re.sub(MAJOR_PATTERN,
68 f"set(PROJECT_MAJOR_VERSION {repo_data['version_major']})",
69 new_cmake_contents,
70 flags=re.MULTILINE)
71 new_cmake_contents = re.sub(MINOR_PATTERN,
72 f"set(PROJECT_MINOR_VERSION {repo_data['version_minor']})",
73 new_cmake_contents,
74 flags=re.MULTILINE)
75 new_cmake_contents = re.sub(PATCH_PATTERN,
76 f"set(PROJECT_PATCH_VERSION {repo_data['version_patch']})",
77 new_cmake_contents,
78 flags=re.MULTILINE)
79 new_cmake_contents = re.sub(VERSION_EXTENSION_PATTERN,
80 f"set(PROJECT_VERSION_EXTENSION \"{repo_data['package_string']}\")",
81 new_cmake_contents,
82 flags=re.MULTILINE)
84 with open(cmake_filepath, mode='w', encoding='utf-8') as fh:
85 fh.write(new_cmake_contents)
86 print(cmake_filepath + " has been updated.")
89 def update_debian_changelog(src_dir, repo_data):
90 # Read packaging/debian/changelog, then write back out an updated version.
92 deb_changelog_filepath = os.path.join(src_dir, "packaging", "debian", "changelog")
93 with open(deb_changelog_filepath, encoding='utf-8') as fh:
94 changelog_contents = fh.read()
96 CHANGELOG_PATTERN = r"^.*"
97 text_replacement = f"wireshark ({repo_data['version_major']}.{repo_data['version_minor']}.{repo_data['version_patch']}{repo_data['package_string']}) UNRELEASED; urgency=low"
98 # Note: Only need to replace the first line, so we don't use re.MULTILINE or re.DOTALL
99 new_changelog_contents = re.sub(CHANGELOG_PATTERN, text_replacement, changelog_contents)
100 with open(deb_changelog_filepath, mode='w', encoding='utf-8') as fh:
101 fh.write(new_changelog_contents)
102 print(deb_changelog_filepath + " has been updated.")
105 def create_version_file(version_f, repo_data):
106 'Write the version to the specified file handle'
108 version_f.write(f"{repo_data['version_major']}.{repo_data['version_minor']}.{repo_data['version_patch']}{repo_data['package_string']}\n")
109 print(version_f.name + " has been created.")
112 def update_attributes_asciidoc(src_dir, repo_data):
113 # Read doc/attributes.adoc, then write it back out with an updated
114 # wireshark-version replacement line.
115 asiidoc_filepath = os.path.join(src_dir, "doc", "attributes.adoc")
116 with open(asiidoc_filepath, encoding='utf-8') as fh:
117 asciidoc_contents = fh.read()
119 # Sample line (without quotes): ":wireshark-version: 2.3.1"
120 ASCIIDOC_PATTERN = r"^:wireshark-version:.*$"
121 text_replacement = f":wireshark-version: {repo_data['version_major']}.{repo_data['version_minor']}.{repo_data['version_patch']}"
123 new_asciidoc_contents = re.sub(ASCIIDOC_PATTERN, text_replacement, asciidoc_contents, flags=re.MULTILINE)
125 with open(asiidoc_filepath, mode='w', encoding='utf-8') as fh:
126 fh.write(new_asciidoc_contents)
127 print(asiidoc_filepath + " has been updated.")
130 def update_docinfo_asciidoc(src_dir, repo_data):
131 doc_paths = []
132 doc_paths += [os.path.join(src_dir, 'doc', 'wsdg_src', 'developer-guide-docinfo.xml')]
133 doc_paths += [os.path.join(src_dir, 'doc', 'wsug_src', 'user-guide-docinfo.xml')]
135 for doc_path in doc_paths:
136 with open(doc_path, encoding='utf-8') as fh:
137 doc_contents = fh.read()
139 # Sample line (without quotes): "<subtitle>For Wireshark 1.2</subtitle>"
140 DOC_PATTERN = r"^<subtitle>For Wireshark \d+.\d+<\/subtitle>$"
141 text_replacement = f"<subtitle>For Wireshark {repo_data['version_major']}.{repo_data['version_minor']}</subtitle>"
143 new_doc_contents = re.sub(DOC_PATTERN, text_replacement, doc_contents, flags=re.MULTILINE)
145 with open(doc_path, mode='w', encoding='utf-8') as fh:
146 fh.write(new_doc_contents)
147 print(doc_path + " has been updated.")
150 def update_cmake_lib_releases(src_dir, repo_data):
151 # Read CMakeLists.txt for each library, then write back out an updated version.
152 dir_paths = []
153 dir_paths += [os.path.join(src_dir, 'epan')]
154 dir_paths += [os.path.join(src_dir, 'wiretap')]
156 for dir_path in dir_paths:
157 cmakelists_filepath = os.path.join(dir_path, "CMakeLists.txt")
158 with open(cmakelists_filepath, encoding='utf-8') as fh:
159 cmakelists_contents = fh.read()
161 # Sample line (without quotes; note leading tab: " VERSION "0.0.0" SOVERSION 0")
162 VERSION_PATTERN = r'^(\s*VERSION\s+"\d+\.\d+\.)\d+'
163 replacement_text = f"\\g<1>{repo_data['version_patch']}"
164 new_cmakelists_contents = re.sub(VERSION_PATTERN,
165 replacement_text,
166 cmakelists_contents,
167 flags=re.MULTILINE)
169 with open(cmakelists_filepath, mode='w', encoding='utf-8') as fh:
170 fh.write(new_cmakelists_contents)
171 print(cmakelists_filepath + " has been updated.")
174 # Update distributed files that contain any version information
175 def update_versioned_files(src_dir, set_version, repo_data):
176 update_cmakelists_txt(src_dir, set_version, repo_data)
177 update_debian_changelog(src_dir, repo_data)
178 if set_version:
179 update_attributes_asciidoc(src_dir, repo_data)
180 update_docinfo_asciidoc(src_dir, repo_data)
181 update_cmake_lib_releases(src_dir, repo_data)
184 def generate_version_h(repo_data):
185 # Generate new contents of version.h from repository data
187 num_commits_line = '#define VCS_NUM_COMMITS "0"\n'
189 commit_id_line = '/* #undef VCS_COMMIT_ID */\n'
191 if not repo_data.get('enable_vcsversion'):
192 return '/* #undef VCS_VERSION */\n' + num_commits_line + commit_id_line
194 if repo_data.get('num_commits'):
195 num_commits_line = f'#define VCS_NUM_COMMITS "{int(repo_data["num_commits"])}"\n'
197 if repo_data.get('commit_id'):
198 commit_id_line = f'#define VCS_COMMIT_ID "{repo_data["commit_id"]}"'
200 if repo_data.get('git_description'):
201 # Do not bother adding the git branch, the git describe output
202 # normally contains the base tag and commit ID which is more
203 # than sufficient to determine the actual source tree.
204 return f'#define VCS_VERSION "{repo_data["git_description"]}"\n' + num_commits_line + commit_id_line
206 if repo_data.get('last_change') and repo_data.get('num_commits'):
207 version_string = f"v{repo_data['version_major']}.{repo_data['version_minor']}.{repo_data['version_patch']}"
208 vcs_line = f'#define VCS_VERSION "{version_string}-Git-{repo_data["num_commits"]}"\n'
209 return vcs_line + num_commits_line + commit_id_line
211 if repo_data.get('commit_id'):
212 vcs_line = f'#define VCS_VERSION "Git commit {repo_data["commit_id"]}"\n'
213 return vcs_line + num_commits_line + commit_id_line
215 vcs_line = '#define VCS_VERSION "Git Rev Unknown from unknown"\n'
217 return vcs_line + num_commits_line + commit_id_line
220 def print_VCS_REVISION(version_file, repo_data, set_vcs):
221 # Write the version control system's version to $version_file.
222 # Don't change the file if it is not needed.
224 # XXX - We might want to add VCS_VERSION to CMakeLists.txt so that it can
225 # generate vcs_version.h independently.
227 new_version_h = generate_version_h(repo_data)
229 needs_update = True
230 if os.path.exists(version_file):
231 with open(version_file, encoding='utf-8') as fh:
232 current_version_h = fh.read()
233 if current_version_h == new_version_h:
234 needs_update = False
236 if not set_vcs:
237 return
239 if needs_update:
240 with open(version_file, mode='w', encoding='utf-8') as fh:
241 fh.write(new_version_h)
242 print(version_file + " has been updated.")
243 elif not repo_data['enable_vcsversion']:
244 print(version_file + " disabled.")
245 else:
246 print(version_file + " unchanged.")
247 return
250 def get_version(cmakelists_file_data):
251 # Reads major, minor, and patch
252 # Sample data:
253 # set(PROJECT_MAJOR_VERSION 3)
254 # set(PROJECT_MINOR_VERSION 7)
255 # set(PROJECT_PATCH_VERSION 2)
257 MAJOR_PATTERN = r"^set *\( *PROJECT_MAJOR_VERSION *(\d+) *\)$"
258 MINOR_PATTERN = r"^set *\( *PROJECT_MINOR_VERSION *(\d+) *\)$"
259 PATCH_PATTERN = r"^set *\( *PROJECT_PATCH_VERSION *(\d+) *\)$"
261 major_match = re.search(MAJOR_PATTERN, cmakelists_file_data, re.MULTILINE)
262 minor_match = re.search(MINOR_PATTERN, cmakelists_file_data, re.MULTILINE)
263 patch_match = re.search(PATCH_PATTERN, cmakelists_file_data, re.MULTILINE)
265 if not major_match:
266 raise Exception("Couldn't get major version")
267 if not minor_match:
268 raise Exception("Couldn't get minor version")
269 if not patch_match:
270 raise Exception("Couldn't get patch version")
272 major_version = major_match.groups()[0]
273 minor_version = minor_match.groups()[0]
274 patch_version = patch_match.groups()[0]
275 return major_version, minor_version, patch_version
278 def read_git_archive(tagged_version_extra, untagged_version_extra):
279 # Reads key data from the git repo.
280 # For git archives, this does not need to access the source directory because
281 # `git archive` will use an 'export-subst' entry in .gitattributes to replace
282 # the value for GIT_EXPORT_SUBST_H in the script.
283 # Returns a dictionary with key values from the repository
285 is_tagged = False
286 for git_ref in GIT_EXPORT_SUBST_D.split(r', '):
287 match = re.match(r'^tag: (v[1-9].+)', git_ref)
288 if match:
289 is_tagged = True
290 vcs_tag = match.groups()[0]
292 if is_tagged:
293 print(f"We are on tag {vcs_tag}.")
294 package_string = tagged_version_extra
295 else:
296 print("We are not tagged.")
297 package_string = untagged_version_extra
299 # Always 0 commits for a git archive
300 num_commits = 0
302 # Assume a full commit hash, abbreviate it.
303 commit_id = GIT_EXPORT_SUBST_H[:GIT_ABBREV_LENGTH]
304 package_string = package_string.replace("{vcsinfo}", str(num_commits) + "-" + commit_id)
306 repo_data = {}
307 repo_data['commit_id'] = commit_id
308 repo_data['enable_vcsversion'] = True
309 repo_data['info_source'] = "git archive"
310 repo_data['is_tagged'] = is_tagged
311 repo_data['num_commits'] = num_commits
312 repo_data['package_string'] = package_string
313 return repo_data
316 def read_git_repo(src_dir, tagged_version_extra, untagged_version_extra):
317 # Reads metadata from the git repo for generating the version string
318 # Returns the data in a dict
320 IS_GIT_INSTALLED = shutil.which('git') != ''
321 if not IS_GIT_INSTALLED:
322 print("Git unavailable. Git revision will be missing from version string.", file=sys.stderr)
323 return {}
325 GIT_DIR = os.path.join(src_dir, '.git')
326 # Check whether to include VCS version information in vcs_version.h
327 enable_vcsversion = True
328 git_get_commondir_cmd = shlex.split(f'git --git-dir="{GIT_DIR}" rev-parse --git-common-dir')
329 git_commondir = subprocess.check_output(git_get_commondir_cmd, universal_newlines=True).strip()
330 if git_commondir and os.path.exists(f"{git_commondir}{os.sep}wireshark-disable-versioning"):
331 print("Header versioning disabled using git override.")
332 enable_vcsversion = False
334 git_last_changetime_cmd = shlex.split(f'git --git-dir="{GIT_DIR}" log -1 --pretty=format:%at')
335 git_last_changetime = subprocess.check_output(git_last_changetime_cmd, universal_newlines=True).strip()
337 # Commits since last annotated tag.
338 # Output could be something like: v3.7.2rc0-64-g84d83a8292cb
339 # Or g84d83a8292cb
340 git_last_annotated_cmd = shlex.split(f'git --git-dir="{GIT_DIR}" describe --abbrev={GIT_ABBREV_LENGTH} --long --always --match "v[1-9]*"')
341 git_last_annotated = subprocess.check_output(git_last_annotated_cmd, universal_newlines=True).strip()
342 parts = git_last_annotated.split('-')
343 git_description = git_last_annotated
344 if len(parts) > 1:
345 num_commits = int(parts[1])
346 else:
347 num_commits = 0
348 commit_id = parts[-1]
350 release_candidate = ''
351 RC_PATTERN = r'^v\d+\.\d+\.\d+(rc\d+)$'
352 match = re.match(RC_PATTERN, parts[0])
353 if match:
354 release_candidate = match.groups()[0]
356 # This command is expected to fail if the version is not tagged
357 try:
358 git_vcs_tag_cmd = shlex.split(f'git --git-dir="{GIT_DIR}" describe --exact-match --match "v[1-9]*"')
359 git_vcs_tag = subprocess.check_output(git_vcs_tag_cmd, stderr=subprocess.DEVNULL, universal_newlines=True).strip()
360 is_tagged = True
361 except subprocess.CalledProcessError:
362 is_tagged = False
364 git_timestamp = ""
365 if num_commits == 0:
366 # Get the timestamp; format is similar to: 2022-06-27 23:09:20 -0400
367 # Note: This doesn't appear to be used, only checked for command success
368 git_timestamp_cmd = shlex.split(f'git --git-dir="{GIT_DIR}" log --format="%ad" -n 1 --date=iso')
369 git_timestamp = subprocess.check_output(git_timestamp_cmd, universal_newlines=True).strip()
371 if is_tagged:
372 print(f"We are on tag {git_vcs_tag}.")
373 package_string = tagged_version_extra
374 else:
375 print("We are not tagged.")
376 package_string = untagged_version_extra
378 package_string = release_candidate + package_string.replace("{vcsinfo}", str(num_commits) + "-" + commit_id)
380 repo_data = {}
381 repo_data['commit_id'] = commit_id
382 repo_data['enable_vcsversion'] = enable_vcsversion
383 repo_data['git_timestamp'] = git_timestamp
384 repo_data['git_description'] = git_description
385 repo_data['info_source'] = "Command line (git)"
386 repo_data['is_tagged'] = is_tagged
387 repo_data['last_change'] = git_last_changetime
388 repo_data['num_commits'] = num_commits
389 repo_data['package_string'] = package_string
390 return repo_data
393 def parse_versionstring(version_arg):
394 version_parts = version_arg.split('.')
395 if len(version_parts) != 3:
396 msg = "Version must have three numbers of the form x.y.z. You entered: " + version_arg
397 raise argparse.ArgumentTypeError(msg)
398 for i, version_type in enumerate(('Major', 'Minor', 'Patch')):
399 try:
400 int(version_parts[i])
401 except ValueError:
402 msg = f"{version_type} version must be a number! {version_type} version was '{version_parts[i]}'"
403 raise argparse.ArgumentTypeError(msg)
404 return version_parts
407 def read_repo_info(src_dir, tagged_version_extra, untagged_version_extra):
408 if IS_GIT_ARCHIVE:
409 repo_data = read_git_archive(tagged_version_extra, untagged_version_extra)
410 elif os.path.exists(src_dir + os.sep + '.git') and not os.path.exists(os.path.join(src_dir, '.git', 'svn')):
411 repo_data = read_git_repo(src_dir, tagged_version_extra, untagged_version_extra)
412 else:
413 raise Exception(src_dir + " does not appear to be a git repo or git archive!")
415 cmake_path = os.path.join(src_dir, "CMakeLists.txt")
416 with open(cmake_path, encoding='utf-8') as fh:
417 version_major, version_minor, version_patch = get_version(fh.read())
418 repo_data['version_major'] = version_major
419 repo_data['version_minor'] = version_minor
420 repo_data['version_patch'] = version_patch
422 return repo_data
425 # CMakeLists.txt calls this with no arguments to create vcs_version.h
426 # AppVeyor calls this with --set-release --untagged-version-extra=-{vcsinfo}-AppVeyor --tagged-version-extra=-AppVeyor
427 # .gitlab-ci calls this with --set-release
428 # Release checklist requires --set-version
429 def main():
430 parser = argparse.ArgumentParser(description='Wireshark file and package versions')
431 action_group = parser.add_mutually_exclusive_group()
432 action_group.add_argument('--set-version', '-v', metavar='<x.y.z>', type=parse_versionstring, help='Set the major, minor, and patch versions in the top-level CMakeLists.txt, doc/attributes.adoc, packaging/debian/changelog, and the CMakeLists.txt for all libraries to the provided version number')
433 action_group.add_argument('--set-release', '-r', action='store_true', help='Set the extra release information in the top-level CMakeLists.txt based on either default or command-line specified options.')
434 setrel_group = parser.add_argument_group()
435 setrel_group.add_argument('--tagged-version-extra', '-t', default="", help="Extra version information format to use when a tag is found. No format \
436 (an empty string) is used by default.")
437 setrel_group.add_argument('--untagged-version-extra', '-u', default='-{vcsinfo}', help='Extra version information format to use when no tag is found. The format "-{vcsinfo}" (the number of commits and commit ID) is used by default.')
438 parser.add_argument('--version-file', '-f', metavar='<file>', type=argparse.FileType('w'), help='path to version file')
439 parser.add_argument("src_dir", metavar='src_dir', nargs=1, help="path to source code")
440 args = parser.parse_args()
442 if args.version_file and not args.set_release:
443 sys.stderr.write('Error: --version-file must be used with --set-release.\n')
444 sys.exit(1)
446 src_dir = args.src_dir[0]
448 if args.set_version:
449 repo_data = {}
450 repo_data['version_major'] = args.set_version[0]
451 repo_data['version_minor'] = args.set_version[1]
452 repo_data['version_patch'] = args.set_version[2]
453 repo_data['package_string'] = ''
454 else:
455 repo_data = read_repo_info(src_dir, args.tagged_version_extra, args.untagged_version_extra)
457 set_vcs = not (args.set_release or args.set_version)
458 VERSION_FILE = 'vcs_version.h'
459 print_VCS_REVISION(VERSION_FILE, repo_data, set_vcs)
461 if args.set_release or args.set_version:
462 update_versioned_files(src_dir, args.set_version, repo_data)
464 if args.version_file:
465 create_version_file(args.version_file, repo_data)
469 if __name__ == "__main__":
470 main()