From 2ce84d117792ab3943aeeeef0b648f0dff0bf334 Mon Sep 17 00:00:00 2001 From: "Josh.5" Date: Mon, 10 May 2021 23:49:46 +1200 Subject: [PATCH] Add initial files... --- .gitignore | 7 ++ build.py | 177 +++++++++++++++++++++++++++++++++++++++++++++ sanitize.sh | 88 ++++++++++++++++++++++ template/addon.xml | 23 ++++++ template/icon.png | Bin 0 -> 1611 bytes 5 files changed, 295 insertions(+) create mode 100644 .gitignore create mode 100755 build.py create mode 100755 sanitize.sh create mode 100644 template/addon.xml create mode 100644 template/icon.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f946cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/modules.json +/venv/** +/.idea/** + +/cache/** +/out/** + diff --git a/build.py b/build.py new file mode 100755 index 0000000..e3d7822 --- /dev/null +++ b/build.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +### +# File: build.py +# Project: Kodi-Module-Generator +# File Created: Monday, 10th May 2021 8:52:42 pm +# Author: Josh.5 (jsunnex@gmail.com) +# ----- +# Last Modified: Monday, 10th May 2021 11:34:57 pm +# Modified By: Josh.5 (jsunnex@gmail.com) +### + +# Requirements = `python3 -m pip install pipgrip` + +import json +import os +import shutil +import subprocess + +template_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'template') +out_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'out') +cache_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'cache') + + +def install_module(module_dir, module_name, version): + module_lib_dir = os.path.abspath(os.path.join(module_dir, 'lib')) + if not module_lib_dir: + os.makedirs(module_lib_dir) + + # First install local + cmd = 'python3 -m pip install --user {}=={}'.format(module_name, version) + subprocess.call(cmd, shell=True) + + # Then install to module directory + cmd = 'python3 -m pip install --ignore-installed --no-dependencies --target={} {}=={}'.format(module_lib_dir, + module_name, + version) + subprocess.call(cmd, shell=True) + + +def update_module_data(module_dir, module_name, module_deps_list): + # Read data from module + res = subprocess.check_output('python3 -m pip show {}'.format(module_name), shell=True) + module_version = '' + module_author = '' + module_author_email = '' + module_license = '' + module_summary = '' + module_website = 'https://pypi.org/project/{}/'.format(module_name) + for line in res.splitlines(): + x = str(line.decode("utf-8")) + print(x) + if x.startswith('Version:'): + module_version = x.split(': ')[1].strip() + elif x.startswith('Author:'): + module_author = x.split(': ')[1].strip() + elif x.startswith('Author-email:'): + module_author_email = x.split(': ')[1].strip() + elif x.startswith('License:'): + module_license = x.split(': ')[1].strip() + elif x.startswith('Summary:'): + module_summary = x.split(': ')[1].strip() + + module_xml = os.path.join(module_dir, 'addon.xml') + # Read in the file + with open(module_xml, 'r') as file: + file_data = file.read() + + # Replace the target string with data from pip show + if module_author_email: + module_author = '{} ({})'.format(module_author, module_author_email) + file_data = file_data.replace('PYTHON_MODULE_NAME', module_name) + file_data = file_data.replace('PYTHON_MODULE_VERSION', module_version) + file_data = file_data.replace('PYTHON_MODULE_AUTHOR', module_author) + file_data = file_data.replace('PYTHON_MODULE_LICENSE', module_license) + file_data = file_data.replace('PYTHON_MODULE_SUMMARY', module_summary) + file_data = file_data.replace('PYTHON_MODULE_DESCRIPTION', module_summary) + file_data = file_data.replace('PYTHON_MODULE_WEBSITE', module_website) + + # Append dependencies list + template = ' ' + deps_to_add = "" + for dep in module_deps_list: + line = template.format(dep.get('name'), dep.get('version')) + deps_to_add = "{}\n{}".format(deps_to_add, line) + file_data = file_data.replace('PYTHON_MODULE_DEPENDENCIES', deps_to_add) + + # Write the file out again + with open(module_xml, 'w') as file: + file.write(file_data) + + +def sanitize_module(module_name): + module_dir = os.path.join(out_dir, 'script.module.{}'.format(module_name)) + sanitize_script = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'sanitize.sh') + cmd = '{} {}'.format(sanitize_script, module_dir) + subprocess.call(cmd, shell=True) + + +def build_script_module(module_name, version, module_deps): + module_dir = os.path.join(out_dir, 'script.module.{}'.format(module_name)) + if not os.path.exists(module_dir): + os.makedirs(module_dir) + + if not os.path.exists(os.path.join(module_dir, 'addon.xml')): + shutil.copy(os.path.join(template_dir, 'addon.xml'), os.path.join(module_dir, 'addon.xml')) + + if not os.path.exists(os.path.join(module_dir, 'icon.png')): + shutil.copy(os.path.join(template_dir, 'icon.png'), os.path.join(module_dir, 'icon.png')) + + # Install modules + install_module(module_dir, module_name, version) + + # Sanitize module + sanitize_module(module_name) + + # Write addon.xml + update_module_data(module_dir, module_name, module_deps) + + +def process_module_deps_list(deps_list): + for dep in deps_list: + module_name = dep.get('name') + module_version = dep.get('version') + child_deps = dep.get('dependencies', []) + + # Build module for each item found in depends list + process_module_deps_list(child_deps) + + # Build module + print("Building Kodi Python module for {} v{}".format(module_name, module_version)) + build_script_module(module_name, module_version, child_deps) + + +def create_dep_list(module, version): + deps_cache = os.path.join(cache_dir, 'deps-{}-{}.json'.format(module, version)) + + if not os.path.exists(deps_cache): + print("Fetching Dependencies list for {} v{}".format(module, version)) + cmd = 'pipgrip --tree --json unmanic'.format(module, version) + res = subprocess.check_output(cmd, shell=True) + decoded = str(res.decode("utf-8")) + data = json.loads(decoded) + + with open(deps_cache, 'w') as file: + json.dump(data, file, indent=2) + + with open(deps_cache, 'r') as file: + deps_list = json.load(file) + + return deps_list + + +def run(): + if not os.path.exists(template_dir): + os.makedirs(template_dir) + if not os.path.exists(out_dir): + os.makedirs(out_dir) + if not os.path.exists(cache_dir): + os.makedirs(cache_dir) + + try: + with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'modules.json'), 'r') as file: + modules = json.load(file) + except Exception as e: + print("No modules configured... - {}".format(str(e))) + modules = [] + + for module_data in modules: + original_module = module_data.get('module') + version = module_data.get('version') + deps_list = create_dep_list(original_module, version) + process_module_deps_list(deps_list) + + +if __name__ == '__main__': + run() diff --git a/sanitize.sh b/sanitize.sh new file mode 100755 index 0000000..ac9ac8d --- /dev/null +++ b/sanitize.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +### +# File: sanitize.sh +# Project: Kodi-Module-Generator +# File Created: Monday, 10th May 2021 11:02:32 pm +# Author: Josh.5 (jsunnex@gmail.com) +# ----- +# Last Modified: Monday, 10th May 2021 11:10:23 pm +# Modified By: Josh.5 (jsunnex@gmail.com) +### + + + +addon_root=${@} +lib_dir="${addon_root}/lib" + + +print_step(){ + ten=" " + spaces="${ten}${ten}${ten}${ten}${ten}${ten}${ten}${ten}${ten}" + message=" - ${@}" + message="${message:0:70}${spaces:0:$((70 - ${#message}))}" + echo -ne "${message}" +} +print_sub_step(){ + ten=" " + spaces="${ten}${ten}${ten}${ten}${ten}${ten}${ten}${ten}${ten}" + message=" - ${@}" + message="${message:0:70}${spaces:0:$((70 - ${#message}))}" + echo -ne "${message}" +} +mark_step(){ + RED="\e[31m" + GREEN="\e[32m" + ENDCOLOR="\e[0m" + status_message="" + [[ ${1} == 'failed' ]] && status_message="${RED}[FAILED]${ENDCOLOR}" + [[ ${1} == 'success' ]] && status_message="${GREEN}[SUCCESS]${ENDCOLOR}" + echo -e "${status_message}" +} + + +# Remove everything except the Python source +remove_extensions=( + "ans" + "bz2" + "db" + "dll" + "exe" + "gz" + "mo" + "pyc" + "pyo" + "so" + "xbt" + "xpr" +) +print_step "Cleaning out unnecessary binary files:" +mark_step +for ext in "${remove_extensions[@]}"; do + print_sub_step "Delete all '*.${ext}' files..." + find "${lib_dir}" -type f -iname "*.${ext}" -delete + [[ $? > 0 ]] && mark_step failed && exit 1 + mark_step success +done +print_step "Cleaning out Python cache directories" +find "${lib_dir}" -type d -name "__pycache__" -exec rm -rf {} + +[[ $? > 0 ]] && mark_step failed && exit 1 +mark_step success +print_step "Cleaning out Python 'dist-info' directories" +find "${lib_dir}" -type d -name "*.dist-info" -exec rm -rf {} + +[[ $? > 0 ]] && mark_step failed && exit 1 +mark_step success + +# Remove Python bin +print_step "Removing 'bin' Python directory" +rm -rf "${lib_dir}/bin" +[[ $? > 0 ]] && mark_step failed && exit 1 +mark_step success + +# Ensure all files are not executable +print_step "Ensure all items in the lib directory are not executable" +find "${lib_dir}" -type f -exec chmod a-x {} + +[[ $? > 0 ]] && mark_step failed && exit 1 +mark_step success + +exit 0 diff --git a/template/addon.xml b/template/addon.xml new file mode 100644 index 0000000..8fafb3b --- /dev/null +++ b/template/addon.xml @@ -0,0 +1,23 @@ + + + + PYTHON_MODULE_DEPENDENCIES + + + + PYTHON_MODULE_SUMMARY + PYTHON_MODULE_DESCRIPTION + PYTHON_MODULE_LICENSE + all + PYTHON_MODULE_WEBSITE + PYTHON_MODULE_WEBSITE + + icon.png + + + \ No newline at end of file diff --git a/template/icon.png b/template/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..893dda076db1e43e0c426b6a2249113cafe659d5 GIT binary patch literal 1611 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K588}#g)VJz{w+swSkE=o=N`ey06$*;-(=u~X z6-p`#QWa7wGSe6sDsH`<9$oZQi^ui9q$@w8Cr9VQ1i4V#X6rxq66BvRnOqn8{rp<1 zWjuWcq$~9mJSG0u&V9(Or!~_#=yOa_-;N;9R&6<(dEqxrk8XK=+%^2Kp4>ZkH|>n4 zZ#Goz4Hdk;U0kmC!bdx~Kd%z^x5ydCH*HEwv-$E|MewY2Q~S1QQI z=Eemh^Mpu4@yCaFk8tz^WNh-BsK-~->&{?&V0ZCC+x(lKO^eRl;dGv<;8yXpUG>kJ z&;DLBHtTk4{CX?;>Cx7riN}<-XZ0`%%n~=h%VvLSZ{}m+2u8=dX4ZBajy;}YG3DO1 zX*uVb3cJ4D5fxbz&$NM~SbLHpW8)0ZDMqfzW=cO^FoaCB^nS$5;xW~F(i4@Y&nIqF zp6xB~`4$m&ZqFf$*$hsq-HpBzxgs<*ul1`Qms+JYE5vm5N_O3-%}=J7KF_I?ExLMS z(#llhV(qY4o1Il<#or%oEfAd37*(|K(b6eVmLWbN3quX}DHVNCbhr9)qFuIb%3?E} z`O{`?yR)WJdu`dfONBmP?yRlW-^<6vtbWwP*5Ebf-d56Yf%_lf{-8b4>$@lL%og=3u#cf=TVGUCoG3ijG_LN{%rfmy53q$@|& zN}`T581N>|E!kYH{5|?k)35x($MF-nx$o{*5N~*=yxo7r*RR#cm;p*z-NFxxZIDs!V9v(?5DkB13o7X>g&k*3&0N3a`3LX1DF}oBLcdRil679-l+9D{Xw`p4IQaZg=jLZ+7uC zcGrOI&qCH-b7I;J!Gca%qgD@k*tT_@uLG}_)Usv|0tX!O0s%-mmQh}ko*VDx@1Xy}p zauj4>;5ck>VE^-vPOK|i;;b}n^K=*-7&sUh6&P3=7z7xY29m?D0qEGl;1HGu28IJn h3_xmtLxiPa9c!2n^S|pXE$bM7z|+;wWt~$(697PJMcx1a literal 0 HcmV?d00001