Coverage for tsfpga/tools/version_number_handler.py: 0%

62 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-10 20:51 +0000

1# -------------------------------------------------------------------------------------------------- 

2# Copyright (c) Lukas Vik. All rights reserved. 

3# 

4# This file is part of the tsfpga project, a project platform for modern FPGA development. 

5# https://tsfpga.com 

6# https://github.com/tsfpga/tsfpga 

7# -------------------------------------------------------------------------------------------------- 

8# A set of tools for versions and releases. Should be reusable between projects. 

9# -------------------------------------------------------------------------------------------------- 

10 

11# Standard libraries 

12import json 

13import re 

14import sys 

15from pathlib import Path 

16from urllib.request import urlopen 

17 

18# Third party libraries 

19from git.repo import Repo 

20from packaging.version import Version, parse 

21 

22# First party libraries 

23from tsfpga.system_utils import create_file, read_file 

24 

25# Contents of unreleased.rst when it is empty 

26UNRELEASED_EMPTY = "Nothing here yet.\n" 

27 

28 

29class VersionNumberHandler: 

30 

31 """ 

32 Class for handling the version number in __init__.py. 

33 """ 

34 

35 _version_regexp = re.compile(r"\n__version__ = \"(\S+?)\"\n") 

36 

37 def __init__(self, repo: Repo, version_file_path: Path) -> None: 

38 """ 

39 Arguments: 

40 repo: The git repository to work with. 

41 version_file_path: The ``__init__.py`` file that shall be modified. 

42 """ 

43 self._repo = repo 

44 self._file_path = version_file_path 

45 

46 def update(self, new_version: str) -> None: 

47 """ 

48 Set a new version number. 

49 

50 Arguments: 

51 new_version: New version number as a string, e.g. "2.3.1". 

52 """ 

53 old_version = self._get_current_version() 

54 self._verify_that_newer_version_number_is_greater_than_older( 

55 older_version=old_version, newer_version=new_version 

56 ) 

57 

58 self._set_new_version(new_version) 

59 

60 def bump_to_prelease(self) -> None: 

61 """ 

62 Bump to next version number. E.g. go from 8.0.2 to 8.0.3-dev. 

63 """ 

64 current_version = self._get_current_version() 

65 (current_major, current_minor, current_patch) = current_version.release 

66 

67 new_version = f"{current_major}.{current_minor}.{current_patch + 1}-dev" 

68 self._verify_that_newer_version_number_is_greater_than_older( 

69 older_version=current_version, newer_version=new_version 

70 ) 

71 

72 self._set_new_version(new_version) 

73 

74 @staticmethod 

75 def _verify_that_newer_version_number_is_greater_than_older( 

76 older_version: Version, newer_version: str 

77 ) -> None: 

78 if older_version >= parse(newer_version): 

79 sys.exit(f"New version {newer_version} is not greater than old version {older_version}") 

80 

81 def _get_current_version(self) -> Version: 

82 data = read_file(self._file_path) 

83 

84 match = self._version_regexp.search(data) 

85 if match is None: 

86 raise RuntimeError(f"Could not find version value in {self._file_path}") 

87 

88 version = match.group(1) 

89 return parse(version) 

90 

91 def _set_new_version(self, new_version: str) -> None: 

92 data = read_file(self._file_path) 

93 

94 updated_data = self._version_regexp.sub(f'\n__version__ = "{new_version}"\n', data) 

95 create_file(self._file_path, updated_data) 

96 

97 # Add file so that it gets included in upcoming commit 

98 self._repo.index.add(str(self._file_path.resolve())) 

99 

100 

101def verify_new_version_number( 

102 repo: Repo, pypi_project_name: str, new_version: str, unreleased_notes_file: Path 

103) -> str: 

104 """ 

105 Verify that the new version number is sane for this project. Will check git log and PyPI 

106 release history. 

107 

108 Arguments: 

109 repo: The git repository to work with. 

110 pypi_project_name: The name of this project on PyPI. 

111 version: New version. 

112 unreleased_notes_file: Path to the "unreleased.rst" release notes file. 

113 Must not be empty. 

114 

115 Return: 

116 The name of the git tag that corresponds to the new version number. 

117 """ 

118 if repo.is_dirty(): 

119 sys.exit("Must make release from clean repo") 

120 

121 if read_file(unreleased_notes_file) in ["", UNRELEASED_EMPTY]: 

122 sys.exit(f"The unreleased notes file {unreleased_notes_file} should not be empty") 

123 

124 with urlopen(f"https://pypi.python.org/pypi/{pypi_project_name}/json") as file_handle: 

125 json_data = json.load(file_handle) 

126 if new_version in json_data["releases"]: 

127 sys.exit(f"Release {new_version} already exists in PyPI") 

128 

129 git_tag = f"v{new_version}" 

130 if git_tag in repo.tags: 

131 sys.exit(f"Git release tag already exists: {git_tag}") 

132 

133 return git_tag 

134 

135 

136def commit_and_tag_release(repo: Repo, version: str, git_tag: str) -> None: 

137 """ 

138 Make a git commit with a "release" message, and tag it. 

139 

140 Arguments: 

141 repo: The git repository to work with. 

142 version: New version. 

143 git_tag: New git tag. 

144 """ 

145 make_commit(repo=repo, commit_message=f"Release version {version}") 

146 

147 repo.create_tag(git_tag) 

148 if git_tag not in repo.tags: 

149 sys.exit("Git tag failed") 

150 

151 

152def make_commit(repo: Repo, commit_message: str) -> None: 

153 """ 

154 Make a git commit, and check that it worked. 

155 

156 Arguments: 

157 repo (`git.Repo`): The git repository to work with. 

158 commit_message (str): Commit message to use. 

159 """ 

160 repo.index.commit(commit_message) 

161 if repo.is_dirty(): 

162 sys.exit("Git commit failed")