Skip to content
Snippets Groups Projects
Commit 4288f254 authored by Philipp Hörist's avatar Philipp Hörist
Browse files

other: Initial

parents
No related branches found
Tags omemo_2.5.9
No related merge requests found
Pipeline #12280 passed
{{ range .Versions }}
# release-helper {{ .Tag.Name }} ({{ datetime "02 Jan 2006" .Tag.Date }})
{{ range .CommitGroups -}}
{{ if .Title }}## {{ .Title }}{{end}}
{{ range .Commits -}}
{{ if .Subject }}* {{ .Subject }}{{end}}{{if .Refs}} ({{range .Refs}}#{{.Ref}}{{end}}){{end}}
{{ end }}
{{ end -}}
{{- if .NoteGroups -}}
{{ range .NoteGroups -}}
## {{ .Title }}
{{ range .Notes }}
* {{ .Body }}
{{ end }}
{{ end -}}
{{ end -}}
{{break}}
{{ end -}}
style: github
template: CHANGELOG.tpl.md
info:
title: CHANGELOG
repository_url: project-url
options:
commits:
filters:
Type:
- change
- feat
- fix
- imprv
- perf
commit_groups:
title_maps:
change: Changed
feat: Features
fix: Bug Fixes
imprv: Improvement
perf: Performance
header:
pattern: "^(\\w*)\\:\\s(.*)$"
pattern_maps:
- Type
- Subject
notes:
keywords:
- BREAKING CHANGE
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
__pycache__/
.ruff_cache/
build/
dist/
*.egg-info
.venv/
*.sublime-project
*.sublime-workspace
image: nbxmpp-master:latest
stages:
- test
- build
- deploy
test-ruff:
stage: test
rules:
- changes:
- "**/*.py"
script:
- pip3 install ruff
- ruff .
interruptible: true
test-isort:
stage: test
rules:
- changes:
- "**/*.py"
script:
- pip3 install isort
- isort --check .
interruptible: true
test-black:
stage: test
rules:
- changes:
- "**/*.py"
script:
- pip3 install black
- black --check .
interruptible: true
build-linux:
stage: build
dependencies: []
script:
- pip install build
- python3 -m build
artifacts:
name: "release-helper-$CI_COMMIT_SHA"
expire_in: 1 week
paths:
- dist/release-helper*.tar.gz
- dist/release-helper*.whl
# deploy-pypi:
# stage: deploy
# dependencies:
# - "build-linux"
# rules:
# - if: '$CI_COMMIT_TAG'
# script:
# - python3 .ci/deploy.py
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.212
hooks:
- id: ruff
exclude: ".githooks/"
args: ["--force-exclude"]
- repo: https://github.com/codespell-project/codespell
rev: v2.2.2
hooks:
- id: codespell
pass_filenames: false
additional_dependencies:
- tomli
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.287
hooks:
- id: pyright
pass_filenames: false
stages: [manual]
- repo: https://github.com/pycqa/isort
rev: 5.11.4
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 22.12.0
hooks:
- id: black
language_version: python3.10
LICENSE 0 → 100644
MIT License
Copyright (c) 2023 Philipp Hörist
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
include CHANGELOG.md
# release-helper
A small CLI tool for all tasks around releasing applications
## Installation
$ pip install git+https://dev.gajim.org/gajim/release-helper.git
## Usage
$ release-helper --help
## Runtime Requirements
- [Python](https://www.python.org/) (>=3.10)
## Build Requirements
- [setuptools](https://pypi.org/project/setuptools/) (>=65.0.0)
[build-system]
requires = ["setuptools >= 65.0.0"]
build-backend = "setuptools.build_meta"
[project]
name = "release-helper"
description = "CLI Tool for releasing applications"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{email = "philipp@hoerist.com"},
{name = "Philipp Hörist"}
]
classifiers = [
"Programming Language :: Python :: 3.10",
]
dependencies = []
dynamic = ["version"]
[project.optional-dependencies]
dev = [
"isort",
"black",
"ruff",
]
[project.urls]
repository = "https://dev.gajim.org/gajim/release-helper"
[project.scripts]
release-helper = "release_helper.main:run"
[tool.setuptools.dynamic]
version = {attr = "release_helper.__version__"}
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
"release_helper" = ["py.typed"]
"release_helper.data" = ["**/*"]
[tool.pyright]
pythonVersion = "3.10"
pythonPlatform = "All"
typeCheckingMode = "strict"
reportUnnecessaryTypeIgnoreComment = "error"
reportPropertyTypeMismatch = "error"
reportMissingModuleSource = "none"
[tool.ruff]
line-length = 88
select = [
"A", # flake8-builtins
# "ANN", # flake8-annotations
# "ARG", # flake8-unused-arguments
"B", # flake8-bugbear
"BLE", # flake8-blind-except
"C", # flake8-comprehensions
"C901", # mccabe
# "D", # pydocstyle
"E", # pycodestyle
"EM", # flake8-errmsg
"ERA", # eradicate
"F", # pyflakes
# "FBT", # flake8-boolean-trap
# "I", # flake8-tidy-imports, isort
"ICN", # flake8-import-conventions
"N", # pep8-naming
"PD", # pandas-vet
"PGH", # pygrep-hooks
"PLC", # pylint
"PLE", # pylint
"PLR", # pylint
"PLW", # pylint
"Q", # flake8-quotes
"RUF", # Ruff-specific rules
"RET", # flake8-return
"S", # flake8-bandit
"SIM", # flake8-simplify
"T", # flake8-debugger, flake8-print
"TID", # flake8-tidy-imports
"UP", # pyupgrade
"W", # pycodestyle
"YTT", # flake8-2020
]
ignore = [
"EM101", # Exception must not use a string literal, assign to variable first
"SIM105", # Use `contextlib.suppress(error_perm)` instead of try-except-pass
"BLE001", # Do not catch blind exception: `Exception`
]
exclude = [
".eggs",
".git",
".ruff_cache",
".venv",
"build",
"dist",
"venv",
"*.pyi",
]
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
target-version = "py310"
[tool.ruff.mccabe]
max-complexity = 15
[tool.ruff.flake8-quotes]
inline-quotes = "double"
docstring-quotes = "double"
multiline-quotes = "double"
[tool.isort]
force_alphabetical_sort_within_sections = true
force_single_line = true
group_by_package = true
known_typing = ["typing"]
sections = ["FUTURE", "TYPING", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
skip_gitignore = true
[tool.black]
line-length = 88
target-version = ["py310"]
[tool.codespell]
skip = "*__pycache__*,*.egg-info,.git,*.sublime*"
#!/usr/bin/env python3
import argparse
import re
import subprocess
import sys
from pathlib import Path
REPO_DIR = Path(__file__).resolve().parent.parent
INIT = REPO_DIR / "src" / "release_helper" / "__init__.py"
CHANGELOG = REPO_DIR / "CHANGELOG.md"
VERSION_RX = r"\d+\.\d+\.\d+"
def get_current_version() -> str:
with INIT.open("r") as f:
content = f.read()
match = re.search(VERSION_RX, content)
if match is None:
sys.exit("Unable to find current version")
return match[0]
def bump_init(current_version: str, new_version: str) -> None:
with INIT.open("r", encoding="utf8") as f:
content = f.read()
content = content.replace(current_version, new_version, 1)
with INIT.open("w", encoding="utf8") as f:
f.write(content)
def make_changelog(new_version: str) -> None:
cmd = ["git-chglog", "--next-tag", new_version]
result = subprocess.run(
cmd, cwd=REPO_DIR, text=True, check=True, capture_output=True
)
changes = result.stdout
changes = changes.removeprefix("\n")
current_changelog = CHANGELOG.read_text()
with CHANGELOG.open("w") as f:
f.write(changes + current_changelog)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Bump Version")
parser.add_argument("version", help="The new version, e.g. 1.5.0")
args = parser.parse_args()
current_version = get_current_version()
bump_init(current_version, args.version)
make_changelog(args.version)
__version__ = "0.9.0"
from __future__ import annotations
import logging
import shutil
import subprocess
from datetime import datetime
from datetime import timezone
from pathlib import Path
class DebianBuild:
def __init__(self, tarball: Path, rev: str, pkgprefix: str, pkgsuffix: str) -> None:
self._date = datetime.now(tz=timezone.utc).strftime("%Y%m%d")
self._date_time = datetime.now(tz=timezone.utc).strftime("%a, %d %b %Y %T %z")
self._tarball = tarball
self._rev = rev
self._pkgprefix = pkgprefix
self._app = tarball.name.rsplit("-", maxsplit=1)[0]
self._pkg_name = self._make_package_name(self._app, pkgprefix, pkgsuffix)
self._working_dir = Path.cwd()
self._build_dir = self._working_dir / "debian_build"
self._release_name = f"{self._pkg_name}_{self._date}"
self._tarball_name = f"{self._release_name}.orig.tar.gz"
self._release_dir = self._build_dir / self._release_name
def _make_package_name(self, app: str, pkgprefix: str, pkgsuffix: str) -> str:
if pkgprefix:
app = f"{pkgprefix}-{app}"
if pkgsuffix:
return f"{app}-{pkgsuffix}"
return app
def _print_paths(self):
logging.info(
f"Working Dir: {self._working_dir}\n"
f"Build Dir: {self._build_dir}\n"
f"Release Name: {self._release_name}\n"
f"Release Dir: {self._release_dir}\n"
f"Tarball Name: {self._tarball_name}"
)
def _pre_clean_build_dir(self) -> None:
if self._build_dir.exists():
logging.info("Pre clean build directory")
shutil.rmtree(self._build_dir)
self._build_dir.mkdir()
def _post_clean_build_dir(self) -> None:
logging.info("Post clean build directory")
shutil.rmtree(self._release_dir)
def _prepare_build_files(self) -> None:
"""Copy all files into the build dir and prepare them"""
dst = self._build_dir / f"{self._release_name}.orig.tar.gz"
logging.info("Copy tarball: %s -> %s", self._tarball, dst)
tarball = Path(shutil.copy(self._tarball, dst))
logging.info("Unpacking tarball to %s", self._build_dir)
shutil.unpack_archive(tarball, self._build_dir)
logging.info("Rename dir to: %s", self._release_dir)
folder = list(self._build_dir.glob(f"{self._app}-?.?.?"))[0]
folder.rename(self._release_dir)
src = self._working_dir / "debian"
dst = self._release_dir / "debian"
logging.info("Copy debian files: %s -> %s", src, dst)
shutil.copytree(src, dst)
def _prepare_changelog(self) -> None:
logging.info("Prepare Changelog")
changelog = self._release_dir / "debian" / "changelog"
content = changelog.read_text()
content = content.replace("{DATE}", f"{self._date}-{self._rev}")
content = content.replace("{DATE_TIME}", self._date_time)
changelog.write_text(content)
def build(self) -> None:
self._print_paths()
self._pre_clean_build_dir()
self._prepare_build_files()
self._prepare_changelog()
logging.info("Start package build")
subprocess.run(
["dpkg-buildpackage", "--no-sign"], cwd=self._release_dir, check=True
)
self._post_clean_build_dir()
from __future__ import annotations
import logging
import ssl
from ftplib import error_perm
from ftplib import FTP_TLS
from pathlib import Path
class FTPConnection:
def __init__(self, host: str) -> None:
self._con = FTP_TLS(host, context=ssl.create_default_context())
self._host = host
def login(self, user: str, password: str) -> None:
self._con.login(user=user, passwd=password)
self._con.prot_p()
logging.info("Successfully connected to %s", self._host)
@staticmethod
def _generate_path_tree(directory: str) -> list[str]:
folders = directory.split("/")
tree: list[str] = []
for index, _ in enumerate(folders):
tree.append("/".join(folders[: index + 1]))
return tree
def _create_directory(self, directory: str) -> None:
"""Creates a directory and all parent directories recusively"""
path_tree = self._generate_path_tree(directory)
for path in path_tree:
try:
self._con.mkd(path)
except error_perm:
pass
def upload(self, directory: str, paths: Path) -> None:
files = list(paths.glob("**/*")) if paths.is_dir() else [paths]
if directory:
self._create_directory(directory)
try:
self._con.cwd(directory)
except error_perm:
logging.error("Unable to change directory to %s", directory)
self._quit()
return
logging.info("Changed directory to %s", directory)
for file in files:
logging.info("Upload file %s", file.name)
with file.open("rb") as f:
try:
self._con.storbinary("STOR " + file.name, f)
except Exception as error:
logging.error("Unable to upload file %s: %s", file.name, error)
self._quit()
return
logging.info("Finished")
self._quit()
def _quit(self) -> None:
self._con.quit()
logging.info("Disconnected")
from __future__ import annotations
import logging
from pathlib import Path
import click
from .debian import DebianBuild
from .ftp import FTPConnection
logging.basicConfig(level="INFO", format="%(message)s")
@click.group()
def cli():
pass
@cli.command()
@click.option("--host", required=True)
@click.option("--user", required=True)
@click.option("--password", required=True)
@click.option("--directory", default="")
@click.argument("filenames", type=Path)
def deploy_to_ftp(host: str, user: str, password: str, directory: str, filenames: Path):
"""Deploy"""
directory = directory.removesuffix("/")
directory = directory.removeprefix("/")
ftp = FTPConnection(host)
ftp.login(user, password)
ftp.upload(directory, filenames)
@cli.command()
@click.argument("tarball", type=Path)
@click.argument("rev")
@click.option(
"--pkgprefix",
default="",
help="Prefix for the package name e.g. python3",
)
@click.option(
"--pkgsuffix",
default="",
help="Suffix for the package name e.g. nightly",
)
def build_debian_pkg(tarball: Path, rev: str, pkgprefix: str, pkgsuffix: str) -> None:
"""Build debian package"""
debian = DebianBuild(tarball, rev, pkgprefix, pkgsuffix)
debian.build()
def run():
cli()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment