#!/usr/bin/env python3 # ==================================================================================================================== # # Authors: # # Patrick Lehmann # # Unai Martinez-Corral # # # # ==================================================================================================================== # # Copyright 2020-2025 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"^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?: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[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() if len(files) > 0: print("· Upload assets") 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() else: print("! Skipping uploading assets because the file list is empty.") UpdateReference(gh_release, tag, paramSHA if env_tag is None else None, is_prerelease, is_draft)