diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml new file mode 100644 index 00000000000..0e5a9495ff8 --- /dev/null +++ b/.github/workflows/releases.yml @@ -0,0 +1,37 @@ +# This is a basic workflow to help you get started with Actions + +name: Update Releases + +# Controls when the action will run. +on: + # Triggers the workflow on push events but only for the master branch + push: + branches: [ master ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + update-tables: + # The type of runner that the job will run on + runs-on: ubuntu-20.04 + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + # Runs a set of commands using the runners shell + - name: Generate Translation Status Tables + run: | + sudo apt-get update -y >/dev/null 2>&1 + sudo apt-get install -y python3 gettext >/dev/null 2>&1 + cd .applet-versions + python3 release_generator.py + git config user.name "NikoKrause" + git config user.email "8415124+NikoKrause@users.noreply.github.com" + git add . + git commit --quiet -m "Update releases (GitHub Actions)" + git push -f origin master:releases diff --git a/.releases/release_generator.py b/.releases/release_generator.py new file mode 100644 index 00000000000..6536ff42f74 --- /dev/null +++ b/.releases/release_generator.py @@ -0,0 +1,32 @@ +from typing import Dict +from utils.version_generator import generate_applet_version_map, Applet +import os +from pathlib import Path +import json +from itertools import zip_longest + +REPO_FOLDER = os.path.realpath(os.path.abspath(os.path.join( + os.path.normpath(os.path.join(os.getcwd(), *([".."] * 1)))))) + +releasesPath = Path("releases.json") +if __name__ == "__main__": + version_map = generate_applet_version_map() + + old_version_map: Dict[str, Applet] = {} + if (releasesPath.exists()): + with open(releasesPath, "r") as f: + old_version_map = json.load(f) + + # compare + for [id, applet] in version_map.items(): + old_applet = old_version_map.get(applet["id"], None) + old_applet_versions = old_applet["versions"] if old_applet is not None else [] + + for new, old in zip_longest(applet["versions"], old_applet_versions, fillvalue=None): + # Should not care about the order of the versions, they should be sorted already + if old is None: + # New version compared to the old file + pass + + with open(releasesPath, "w") as f: + json.dump(version_map, f, indent=4) \ No newline at end of file diff --git a/.releases/utils/__init__.py b/.releases/utils/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.releases/utils/version_generator.py b/.releases/utils/version_generator.py new file mode 100644 index 00000000000..2a7b0fcdb69 --- /dev/null +++ b/.releases/utils/version_generator.py @@ -0,0 +1,135 @@ +import os +from pathlib import Path +from typing import Dict, List, Optional, Tuple, TypedDict +import subprocess +import json +from multiprocessing import Pool + +class Version(TypedDict): + version: str + commit: str + date: str + url: str + applet_checksum: str + message: str + +class Metadata(TypedDict): + uuid: str + name: str + description: str + version: Optional[str] + +class Applet(TypedDict): + id: str + path: str + versions: List[Version] + +REPO_FOLDER = Path(subprocess.check_output([ + "git", + "rev-parse", + "--show-toplevel" +], encoding="utf-8").strip()) + +def get_current_metadata(path: Path) -> Metadata: + with open(path, "r") as f: + return json.load(f) + +def get_applet_hash(applet_folder: Path, hash: str) -> Optional[str]: + '''Gets hash of the applet "dist" ({name}/files/{name}) folder''' + try: + output = subprocess.check_output([ + "git", + "rev-parse", + f"{hash}:{str(applet_folder)}", + + ], cwd=REPO_FOLDER, encoding="utf-8") + except subprocess.CalledProcessError as e: + return None + return output.strip() + +def obtain_versions(applet: Applet) -> List[Version]: + versions: List[Version] = [] + metadataPath = Path(applet['path']).joinpath(f'metadata.json') + + # Get changelog for applet + output = subprocess.check_output([ + "git", + "log", + "--pretty=format:%h;%aI;%s%d", + "--", + str(applet['path']) + ], cwd=REPO_FOLDER, encoding="utf-8") + + previousVersion: Optional[str] = None + currentRevision: int = 1 + for line in reversed(output.splitlines()): + if line is None: + continue + + # Get information on commit + [commit, timestamp, message] = line.split(";", 2) + hash = get_applet_hash(Path(applet["path"]), commit) + if (hash is None): + # No files in applet dist folder, skip + continue + + try: + metadata: Metadata = json.loads(subprocess.check_output([ + "git", + "show", + f"{commit}:{str(metadataPath)}" + ], cwd=REPO_FOLDER)) + except subprocess.CalledProcessError: + # No metadata.json file in commit, skip + continue + + # Normalize version, can't be null and we add revision that we increment when version was not changed + version = str(metadata["version"]) if metadata.get("version") is not None else "0.0.0" + if (version == previousVersion): + currentRevision += 1 + else: + currentRevision = 1 + + versions.append({ + "version": f"{version}-{str(currentRevision)}", + "commit": commit, + "message": message, + "date": timestamp, + "applet_checksum": hash, + "url": f"https://github.com/linuxmint/cinnamon-spices-applets/archive/{commit}.zip" + }) + previousVersion = version + + return versions + +def get_applet_info(metadata: Metadata, item: Path) -> Applet: + applet: Applet = { + "id": metadata["uuid"], + "path": str(item.joinpath("files/" + item.name).relative_to(REPO_FOLDER)), + "versions": [] + } + + applet["versions"] = obtain_versions(applet) + return applet + +def generate_applet_version_map() -> Dict[str, Applet]: + applets: Dict[str, Applet] = {} + pool = Pool() + items: List[Tuple[Metadata, Path]] = [] + for item in sorted(REPO_FOLDER.iterdir()): + if not item.is_dir(): + continue + metadataFile = item.joinpath(f"files/{item.name}/metadata.json") + if (not metadataFile.exists() and not metadataFile.is_file()): + continue + + metadata = get_current_metadata(metadataFile) + items.append((metadata, item)) + + results = pool.starmap(get_applet_info, items) + for result in results: + applets[result["id"]] = result + return applets + +if __name__ == "__main__": + print(json.dumps(generate_applet_version_map(), indent=4))