def update_version(to: str, _version: version, *, develop: bool = False) -> Tuple[bool, str]: """Use pontos-version to update the version. Arguments: to: The version (str) that will be set _version: Version module develop: Wether to set version to develop or not (bool) Returns: executed: True if successfully executed, False else filename: The filename of the project definition """ args = ['--quiet'] args.append('update') args.append(to) if develop: args.append('--develop') executed, filename = _version.main(leave=False, args=args) if not executed: if filename == "": error("No project definition found.") else: error(f"Unable to update version {to} in {filename}") return executed, filename
def calculate_calendar_version() -> str: """find the correct next calendar version by checking latest version and the today's date""" current_version_str: str = get_current_version() current_version = Version(current_version_str) today = datetime.date.today() if (current_version.major < today.year % 100 or current_version.minor < today.month): release_version = Version( f'{str(today.year % 100)}.{str(today.month)}.0') return str(release_version) elif (current_version.major == today.year % 100 and current_version.minor == today.month): if current_version.dev is None: release_version = Version( f'{str(today.year % 100)}.{str(today.month)}.' f'{str(current_version.micro + 1)}') else: release_version = Version( f'{str(today.year % 100)}.{str(today.month)}.' f'{str(current_version.micro)}') return str(release_version) else: error(f"'{str(current_version)}' is higher than " f"'{str(today.year % 100)}.{str(today.month)}'.") sys.exit(1)
def download_assets( assets_url: str, path: Path, requests_module: requests, ) -> List[str]: """Download all .tar.gz and zip assets of a github release""" file_paths = [] if not assets_url: return file_paths if assets_url: assets_response = requests_module.get(assets_url) if assets_response.status_code != 200: error( f"Wrong response status code {assets_response.status_code} for " f" request {assets_url}") out(json.dumps(assets_response.text, indent=4, sort_keys=True)) else: assets_json = assets_response.json() for asset_json in assets_json: asset_url: str = asset_json.get('browser_download_url', '') name: str = asset_json.get('name', '') if name.endswith('.zip') or name.endswith('.tar.gz'): asset_path = download( asset_url, name, path=path, requests_module=requests_module, ) file_paths.append(asset_path) return file_paths
def upload_assets( username: str, token: str, file_paths: List[Path], github_json: Dict, path: Path, requests_module: requests, ) -> bool: """Function to upload assets Arguments: username: The GitHub username to use for the upload token: That username's GitHub token file_paths: List of paths to asset files github_json: The github dictionary, containing relevant information for the upload path: the python pathlib.Path module requests_module: the python request module Returns: True on success, false else """ info(f"Uploading assets: {[str(p) for p in file_paths]}") asset_url = github_json['upload_url'].replace('{?name,label}', '') paths = [path(f'{str(p)}.asc') for p in file_paths] headers = { 'Accept': 'application/vnd.github.v3+json', 'content-type': 'application/octet-stream', } auth = (username, token) for file_path in paths: to_upload = file_path.read_bytes() resp = requests_module.post( f"{asset_url}?name={file_path.name}", headers=headers, auth=auth, data=to_upload, ) if resp.status_code != 201: error(f"Wrong response status {resp.status_code}" f" while uploading {file_path.name}") out(json.dumps(resp.text, indent=4, sort_keys=True)) return False else: ok(f"Uploaded: {file_path.name}") return True
def get_current_version() -> str: """Get the current Version from a pyproject.toml or a CMakeLists.txt file""" available_cmds = [ ('CMakeLists.txt', CMakeVersionCommand), ('pyproject.toml', PontosVersionCommand), ] for file_name, cmd in available_cmds: project_definition_path = Path.cwd() / file_name if project_definition_path.exists(): ok(f"Found {file_name} project definition file.") current_version: str = cmd().get_current_version() return current_version error("No project settings file found") sys.exit(1)
def main( shell_cmd_runner=lambda x: subprocess.run( x, shell=True, check=True, errors="utf-8", # use utf-8 encoding for error output stdout=subprocess.PIPE, stderr=subprocess.PIPE, ), _path: Path = Path, _requests: requests = requests, _version: version = version, _changelog: changelog = changelog, leave: bool = True, args=None, ): username, token, parsed_args = parse(args) term = Terminal() _set_terminal(term) term.bold_info(f'pontos-release => {parsed_args.func.__name__}') with term.indent(): try: if not parsed_args.func( shell_cmd_runner, parsed_args, path=_path, username=username, token=token, changelog_module=_changelog, requests_module=_requests, version_module=_version, ): return sys.exit(1) if leave else False except subprocess.CalledProcessError as e: error(f'Could not run command "{e.cmd}". Error was:\n\n{e.stderr}') sys.exit(1) return sys.exit(0) if leave else True
def sign( shell_cmd_runner: Callable, args: argparse.Namespace, *, path: Path, username: str, token: str, requests_module: requests, **_kwargs, ) -> bool: project: str = (args.project if args.project is not None else get_project_name(shell_cmd_runner)) space: str = args.space git_tag_prefix: str = args.git_tag_prefix release_version: str = (args.release_version if args.release_version is not None else get_current_version()) signing_key: str = args.signing_key headers = {'Accept': 'application/vnd.github.v3+json'} git_version: str = f'{git_tag_prefix}{release_version}' base_url = (f"https://api.github.com/repos/{space}/{project}" f"/releases/tags/{git_version}") response = requests_module.get( base_url, headers=headers, ) if response.status_code != 200: error(f"Wrong response status code {response.status_code} for request " f"{base_url}") out(json.dumps(response.text, indent=4, sort_keys=True)) return False github_json = json.loads(response.text) zip_path = download( github_json['zipball_url'], f"{project}-{release_version}.zip", path=path, requests_module=requests_module, ) tar_path = download( github_json['tarball_url'], f"{project}-{release_version}.tar.gz", path=path, requests_module=requests_module, ) file_paths = [zip_path, tar_path] asset_paths = download_assets( github_json.get('assets_url'), path=path, requests_module=requests_module, ) file_paths.extend(asset_paths) for file_path in file_paths: info(f"Signing {file_path}") shell_cmd_runner( f"gpg --default-key {signing_key} --yes --detach-sign --armor " f"{file_path}") return upload_assets( username, token, file_paths, github_json, path=path, requests_module=requests_module, )
def release( shell_cmd_runner: Callable, args: argparse.Namespace, *, path: Path, version_module: version, username: str, token: str, requests_module: requests, changelog_module: changelog, **_kwargs, ) -> bool: project: str = (args.project if args.project is not None else get_project_name(shell_cmd_runner)) space: str = args.space git_signing_key: str = (args.git_signing_key if args.git_signing_key is not None else find_signing_key(shell_cmd_runner)) git_remote_name: str = (args.git_remote_name if args.git_remote_name is not None else '') git_tag_prefix: str = args.git_tag_prefix release_version: str = (args.release_version if args.release_version is not None else get_current_version()) next_version: str = (args.next_version if args.next_version is not None else get_next_dev_version(release_version)) info("Pushing changes") shell_cmd_runner(f"git push --follow-tags {git_remote_name}") info("Creating release") changelog_text: str = path(RELEASE_TEXT_FILE).read_text() headers = {'Accept': 'application/vnd.github.v3+json'} base_url = f"https://api.github.com/repos/{space}/{project}/releases" git_version = f'{git_tag_prefix}{release_version}' response = requests_module.post( base_url, headers=headers, auth=(username, token), json=build_release_dict( git_version, changelog_text, name=f"{project} {release_version}", ), ) if response.status_code != 201: error(f"Wrong response status code: {response.status_code}") error(json.dumps(response.text, indent=4, sort_keys=True)) return False path(RELEASE_TEXT_FILE).unlink() # set to new version add skeleton change_log_path = path.cwd() / 'CHANGELOG.md' executed, filename = update_version(next_version, version_module, develop=True) if not executed: return False updated = changelog_module.add_skeleton( markdown=change_log_path.read_text(), new_version=release_version, project_name=project, git_tag_prefix=git_tag_prefix, git_space=space, ) change_log_path.write_text(updated) commit_msg = (f'Automatic adjustments after release\n\n' f'* Update to version {next_version}\n' f'* Add empty changelog after {release_version}') commit_files( filename, commit_msg, shell_cmd_runner, git_signing_key=git_signing_key, ) shell_cmd_runner(f"git push --follow-tags {git_remote_name}") return True
def test_error(self): error('foo bar') self.term.error.assert_called_with('foo bar')