Files
Actions/releaser/releaser.py
Boyd Kane c11c7295f6 Sort globbed files for releases
This change simply sorts the list of files after the glob has been resolved. This is useful because these files are often destined for a release, and having to scroll through hundreds of files which aren't sorted is *painful*.

I'm happy to convert this into an option that can be specified in the yaml if you'd prefer?
2022-07-16 13:56:14 +02:00

191 lines
8.5 KiB
Python
Executable File

#!/usr/bin/env python3
# ==================================================================================================================== #
# Authors: #
# Patrick Lehmann #
# Unai Martinez-Corral #
# #
# ==================================================================================================================== #
# Copyright 2020-2022 The pyTooling Authors #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# #
# SPDX-License-Identifier: Apache-2.0 #
# ==================================================================================================================== #
import re
from sys import argv as sys_argv, stdout, exit as sys_exit
from os import environ, getenv
from glob import glob
from pathlib import Path
from github import Github, GithubException
from subprocess import check_call
paramTag = getenv("INPUT_TAG", "tip")
paramFiles = getenv("INPUT_FILES", None).split()
paramRM = getenv("INPUT_RM", "false") == "true"
paramSnapshots = getenv("INPUT_SNAPSHOTS", "true").lower() == "true"
paramToken = (
environ["GITHUB_TOKEN"]
if "GITHUB_TOKEN" in environ
else environ["INPUT_TOKEN"]
if "INPUT_TOKEN" in environ
else None
)
paramRepo = getenv("GITHUB_REPOSITORY", None)
paramRef = getenv("GITHUB_REF", None)
paramSHA = getenv("GITHUB_SHA", None)
def GetListOfArtifacts(argv, files):
print("· Get list of artifacts to be uploaded")
args = files if files is not None else []
if len(argv) > 1:
args += argv[1:]
if len(args) == 1 and args[0].lower() == "none":
print("! Skipping 'files' because it's set to 'none")
return []
elif len(args) == 0:
stdout.flush()
raise (Exception("Glob patterns need to be provided as positional arguments or through envvar 'INPUT_FILES'!"))
else:
flist = []
for item in args:
print(f" glob({item!s}):")
for fname in [fname for fname in glob(item, recursive=True) if not Path(fname).is_dir()]:
if Path(fname).stat().st_size == 0:
print(f" - ! Skipping empty file {fname!s}")
continue
print(f" - {fname!s}")
flist.append(fname)
if len(flist) < 1:
stdout.flush()
raise (Exception("Empty list of files to upload/update!"))
return sorted(flist)
def GetGitHubAPIHandler(token):
print("· Get GitHub API handler (authenticate)")
if token is not None:
return Github(token)
raise (Exception("Need credentials to authenticate! Please, provide 'GITHUB_TOKEN' or 'INPUT_TOKEN'"))
def CheckRefSemVer(gh_ref, tag, snapshots):
print("· Check SemVer compliance of the reference/tag")
env_tag = None
if gh_ref[0:10] == "refs/tags/":
env_tag = gh_ref[10:]
if env_tag != tag:
rexp = r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
semver = re.search(rexp, env_tag)
if semver == None and env_tag[0] == "v":
semver = re.search(rexp, env_tag[1:])
tag = env_tag
if semver == None:
print(f"! Could not get semver from {gh_ref!s}")
print(f"! Treat tag '{tag!s}' as a release")
return (tag, env_tag, False)
else:
if semver.group("prerelease") is None:
# is a regular semver compilant tag
return (tag, env_tag, False)
elif snapshots:
# is semver compilant prerelease tag, thus a snapshot (we skip it)
print("! Skipping snapshot prerelease")
sys_exit()
return (tag, env_tag, True)
def GetRepositoryHandler(gh, repo):
print("· Get Repository handler")
if repo is None:
stdout.flush()
raise (Exception("Repository name not defined! Please set 'GITHUB_REPOSITORY"))
return gh.get_repo(repo)
def GetOrCreateRelease(gh_repo, tag, sha, is_prerelease):
print("· Get Release handler")
gh_tag = None
try:
gh_tag = gh_repo.get_git_ref(f"tags/{tag!s}")
except Exception:
stdout.flush()
if gh_tag:
try:
return (gh_repo.get_release(tag), False)
except Exception:
return (gh_repo.create_git_release(tag, tag, "", draft=True, prerelease=is_prerelease), True)
else:
err_msg = f"Tag/release '{tag!s}' does not exist and could not create it!"
if sha is None:
raise (Exception(err_msg))
try:
return (
gh_repo.create_git_tag_and_release(
tag, "", tag, "", sha, "commit", draft=True, prerelease=is_prerelease
),
True,
)
except Exception:
raise (Exception(err_msg))
def UpdateReference(gh_release, tag, sha, is_prerelease, is_draft):
print("· Update Release reference (force-push tag)")
if is_draft:
# Unfortunately, it seems not possible to update fields 'created_at' or 'published_at'.
print(" > Update (pre-)release")
gh_release.update_release(
gh_release.title,
"" if gh_release.body is None else gh_release.body,
draft=False,
prerelease=is_prerelease,
tag_name=gh_release.tag_name,
target_commitish=gh_release.target_commitish,
)
if sha is not None:
print(f" > Force-push '{tag!s}' to {sha!s}")
gh_repo.get_git_ref(f"tags/{tag!s}").edit(sha)
files = GetListOfArtifacts(sys_argv, paramFiles)
stdout.flush()
[tag, env_tag, is_prerelease] = CheckRefSemVer(paramRef, paramTag, paramSnapshots)
stdout.flush()
gh_repo = GetRepositoryHandler(GetGitHubAPIHandler(paramToken), paramRepo)
stdout.flush()
[gh_release, is_draft] = GetOrCreateRelease(gh_repo, tag, paramSHA, is_prerelease)
stdout.flush()
if paramRM:
print("· RM set. All previous assets are being cleared...")
for asset in gh_release.get_assets():
print(f" - {asset.name}")
asset.delete_asset()
stdout.flush()
print("· Cleanup and/or upload artifacts")
env = environ.copy()
env["GITHUB_TOKEN"] = paramToken
cmd = ["gh", "release", "upload", "--repo", paramRepo, "--clobber", tag] + files
print(f" > {' '.join(cmd)}")
check_call(cmd, env=env)
stdout.flush()
UpdateReference(gh_release, tag, paramSHA if env_tag is None else None, is_prerelease, is_draft)