354 lines
9.4 KiB
Python
354 lines
9.4 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""The setup script."""
|
|
|
|
from collections import namedtuple
|
|
from distutils.dir_util import copy_tree, mkpath
|
|
import glob
|
|
from html.parser import HTMLParser
|
|
import json
|
|
import os
|
|
import pkg_resources
|
|
from pprint import pformat
|
|
from setuptools import setup, find_packages # type: ignore
|
|
from setuptools.command.develop import develop # type: ignore
|
|
from setuptools.command.install import install # type: ignore
|
|
from setuptools.command.bdist_egg import bdist_egg # type: ignore
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tarfile
|
|
import typing as T
|
|
from urllib.error import HTTPError, URLError
|
|
from urllib.request import urlopen
|
|
from wheel.bdist_wheel import bdist_wheel # type: ignore
|
|
import zipfile
|
|
|
|
|
|
GECKO_RELEASE_PATH = "https://github.com/mozilla/geckodriver"
|
|
PKG_NAME = "bot_z"
|
|
VERSION = "1.0.0"
|
|
AUTHOR = "blallo"
|
|
AUTHOR_EMAIL = "blallo@autistici.org"
|
|
BIN_PATH = "bin/geckodriver"
|
|
ASSET_FILE_PATHS = [
|
|
"assets/*",
|
|
"assets/static/*",
|
|
"assets/static/css/*",
|
|
"assets/static/js/*",
|
|
]
|
|
|
|
with open("README.md") as readme_file:
|
|
readme = readme_file.read()
|
|
|
|
requirements = [
|
|
"Click>=7.0",
|
|
"selenium>=3.141.0",
|
|
"lockfile>=0.12.2",
|
|
"python-daemon>=2.2.3",
|
|
"aiohttp>=3.5.4",
|
|
"aiodns>=2.0.0",
|
|
"cchardet",
|
|
"aiohttp-session==2.7.0",
|
|
"cryptography==2.7",
|
|
"PyYAML==5.1.2",
|
|
"passlib>=1.7.1, <2.0.0",
|
|
"bcrypt>=3.0.0",
|
|
]
|
|
|
|
setup_requirements = [] # type: T.List[str]
|
|
|
|
test_requirements = [] # type: T.List[str]
|
|
|
|
|
|
def _find_javascript_pkgmgr():
|
|
pkgmgr_list = ("yarn", "npm")
|
|
for _pkgmgr in pkgmgr_list:
|
|
if shutil.which(_pkgmgr):
|
|
pkgmgr = _pkgmgr
|
|
break
|
|
|
|
if not pkgmgr:
|
|
raise RuntimeError(
|
|
"Missing javascript package manager. Allowed are: {}".format(pkgmgr_list)
|
|
)
|
|
|
|
return pkgmgr
|
|
|
|
|
|
def _find_built_assets(base_path: str):
|
|
asset_manifest = os.path.join(base_path, "build", "asset-manifest.json")
|
|
with open(asset_manifest) as f:
|
|
manifest = json.load(f)
|
|
return manifest.get("files")
|
|
|
|
|
|
def build_web():
|
|
pkgmgr = _find_javascript_pkgmgr()
|
|
webpath = "./bot.z_web" # TODO: find base path
|
|
script = f"cd {webpath} && {pkgmgr} build"
|
|
print("[BUILD_WEB] running script:\n\t{}".format(script))
|
|
subprocess.check_call(["bash", "-c", script])
|
|
assets = _find_built_assets(webpath)
|
|
print("[BUILD_WEB] built assets: {}".format(pformat(assets)))
|
|
assets_path = pkg_resources.resource_filename("api", "assets")
|
|
if not os.path.exists(assets_path):
|
|
mkpath(assets_path)
|
|
copy_tree(os.path.join(webpath, "build"), assets_path)
|
|
|
|
|
|
class GitTags(HTMLParser):
|
|
tags: T.List[str] = list()
|
|
take_next = 0
|
|
|
|
def handle_starttag(self, tag, attrs):
|
|
dattrs = dict(attrs)
|
|
if "commit-title" in dattrs.get("class", ""):
|
|
self.take_next = 1
|
|
|
|
def handle_data(self, data):
|
|
if self.take_next == 0:
|
|
return
|
|
elif self.take_next == 1:
|
|
self.take_next = 2
|
|
elif self.take_next == 2:
|
|
self.tags.append(data.strip("\n").strip(" ").strip("\n"))
|
|
self.take_next = 0
|
|
|
|
|
|
def retrieve_page(url: str) -> bytes:
|
|
"""
|
|
Auxiliary function to download html body
|
|
from and URI, handling the errors.
|
|
"""
|
|
try:
|
|
with urlopen(url) as conn:
|
|
content = conn.read()
|
|
except HTTPError as e:
|
|
print("Connection error: {!s}".format(e))
|
|
raise
|
|
except URLError as e:
|
|
print("Check the URI: {!s}".format(e))
|
|
raise
|
|
|
|
return content
|
|
|
|
|
|
def find_latest_version(url: str) -> str:
|
|
"""
|
|
Retrieves latest geckodriver tag.
|
|
"""
|
|
tag_page = retrieve_page("{}/tags".format(url))
|
|
gt = GitTags()
|
|
gt.feed(tag_page.decode("utf-8"))
|
|
gt.tags.sort()
|
|
return gt.tags[-1]
|
|
|
|
|
|
def verify_if_superuser() -> bool:
|
|
"""
|
|
Checks if uid or euid is 0.
|
|
"""
|
|
_uid = os.getuid()
|
|
_euid = os.geteuid()
|
|
return _uid == 0 or _euid == 0
|
|
|
|
|
|
def ensure_local_folder() -> None:
|
|
"""
|
|
Create a bin/ folder in the current package installation path.
|
|
"""
|
|
bin_path = pkg_resources.resource_filename(PKG_NAME, BIN_PATH)
|
|
print("[LOCAL_FOLDER] ensuring local folder: {}".format(bin_path))
|
|
pkg_resources.ensure_directory(bin_path)
|
|
|
|
|
|
PLATFORM_MAP = {
|
|
"win32": "win32",
|
|
"win64": "win64",
|
|
"linux32": "linux32",
|
|
"linux64": "linux64",
|
|
"darwin64": "macos",
|
|
"darwin32": "macos", # This is impossible (?)
|
|
"macos": "macos",
|
|
}
|
|
|
|
|
|
def _identify_platform():
|
|
s_platform = sys.platform
|
|
if s_platform == "darwin":
|
|
return "macos"
|
|
is_64bits = sys.maxsize > 2 ** 32
|
|
if "linux" in s_platform:
|
|
plat = "linux"
|
|
elif "win" in s_platform:
|
|
plat = "win"
|
|
if is_64bits:
|
|
platform = "{}64".format(plat)
|
|
else:
|
|
platform = "{}32".format(plat)
|
|
return platform
|
|
|
|
|
|
def assemble_driver_uri(
|
|
version: T.Optional[str] = None, platform: T.Optional[str] = None
|
|
) -> str:
|
|
"""
|
|
Selects the right geckodriver URI.
|
|
"""
|
|
# TODO: use pkg_resources.get_platform()
|
|
if not version:
|
|
version = find_latest_version(GECKO_RELEASE_PATH)
|
|
if platform is None:
|
|
platform = _identify_platform()
|
|
if "win" in platform:
|
|
ext = "zip"
|
|
else:
|
|
ext = "tar.gz"
|
|
return "{base}/releases/download/{vers}/geckodriver-{vers}-{platform}.{ext}".format(
|
|
base=GECKO_RELEASE_PATH, vers=version, platform=PLATFORM_MAP[platform], ext=ext
|
|
)
|
|
|
|
|
|
def download_driver_bin(uri: str, path: str) -> None:
|
|
"""
|
|
Donwloads the geckodriver binary.
|
|
"""
|
|
name = uri.split("/")[-1]
|
|
filepath = os.path.join(path, name)
|
|
print("[DRIVER] downloading '{}' to {}".format(uri, filepath))
|
|
content = retrieve_page(uri)
|
|
try:
|
|
with open(filepath, "wb") as f:
|
|
f.write(content)
|
|
if name.endswith(".zip"):
|
|
with zipfile.ZipFile(filepath, "r") as z:
|
|
z.extractall(path)
|
|
elif name.endswith(".tar.gz"):
|
|
with tarfile.open(filepath, "r") as r:
|
|
r.extractall(path)
|
|
else:
|
|
raise RuntimeError("Unrecognized file extension: %s", name)
|
|
finally:
|
|
os.remove(filepath)
|
|
|
|
|
|
def preinstall(platform: T.Text, build_iface: bool = True) -> None:
|
|
"""
|
|
Performs all the postintallation flow, donwloading in the
|
|
right place the geckodriver binary.
|
|
"""
|
|
target_path = pkg_resources.resource_filename(
|
|
"bot_z", os.path.join("bin", platform)
|
|
)
|
|
if not os.path.exists(target_path):
|
|
mkpath(target_path)
|
|
ensure_local_folder()
|
|
version = os.environ.get("BOTZ_GECKO_VERSION")
|
|
gecko_uri = assemble_driver_uri(version, platform)
|
|
print("[POSTINSTALL] gecko_uri: {}".format(gecko_uri))
|
|
download_driver_bin(gecko_uri, target_path)
|
|
if build_iface:
|
|
build_web()
|
|
|
|
|
|
PLATS = {
|
|
"win32": "win32",
|
|
"win-amd64": "win64",
|
|
"manylinux1-i686": "linux32",
|
|
"manylinux1-x86_64": "linux64",
|
|
"macosx": "macos",
|
|
}
|
|
|
|
|
|
def translate_platform_to_gecko_vers(plat: str) -> str:
|
|
"""
|
|
Map appropriately the platform provided on the command line
|
|
to the one used by PEP 513.
|
|
"""
|
|
if plat is None:
|
|
return None
|
|
|
|
try:
|
|
return PLATS[plat]
|
|
except KeyError:
|
|
print("Allowed platforms are: {!r}".format(list(PLATS.keys())))
|
|
raise
|
|
|
|
|
|
# From: https://stackoverflow.com/a/36902139
|
|
class CustomDevelopCommand(develop):
|
|
"""Custom installation for development mode."""
|
|
|
|
def run(self):
|
|
print("POSTINSTALL")
|
|
platform = _identify_platform()
|
|
preinstall(platform, build_iface=False)
|
|
super().run()
|
|
|
|
|
|
class CustomInstallCommand(install):
|
|
"""Custom installation for installation mode."""
|
|
|
|
def run(self):
|
|
opts = self.distribution.get_cmdline_options()
|
|
platform = None
|
|
if "bdist_wheel" in opts:
|
|
platform = translate_platform_to_gecko_vers(
|
|
opts["bdist_wheel"].get("plat-name")
|
|
)
|
|
preinstall(platform)
|
|
super().run()
|
|
|
|
|
|
# From: https://stackoverflow.com/a/45150383
|
|
class CustomBDistWheel(bdist_wheel):
|
|
"""
|
|
Custom bdist_wheel command to ship the right binary
|
|
of geckodriver.
|
|
"""
|
|
|
|
def finalize_options(self):
|
|
super().finalize_options()
|
|
self.root_is_pure = False
|
|
|
|
def get_tag(self):
|
|
python, abi, plat = super().get_tag()
|
|
python, abi = "py3", "none"
|
|
return python, abi, plat
|
|
|
|
|
|
setup(
|
|
name=PKG_NAME,
|
|
version=VERSION,
|
|
description="A bot to easen the daily routine with zucchetti virtual badge.",
|
|
long_description=readme,
|
|
author=AUTHOR,
|
|
author_email=AUTHOR_EMAIL,
|
|
url="https://git.abbiamoundominio.org/blallo/BotZ",
|
|
packages=find_packages(include=["bot_z", "api"]),
|
|
cmdclass={
|
|
"develop": CustomDevelopCommand,
|
|
"install": CustomInstallCommand,
|
|
"bdist_wheel": CustomBDistWheel,
|
|
},
|
|
entry_points={"console_scripts": ["bot_z=bot_z.cli:cli", "z_app=api.app:cli"]},
|
|
package_data={"bot_z": ["bin/geckodriver"], "api": ASSET_FILE_PATHS},
|
|
install_requires=requirements,
|
|
license="GLWTS Public Licence",
|
|
zip_safe=False,
|
|
keywords="bot_z",
|
|
classifiers=[
|
|
"Development Status :: 2 - Pre-Alpha",
|
|
"Intended Audience :: Developers",
|
|
"License :: GLWTS Public Licence",
|
|
"Natural Language :: English",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.7",
|
|
],
|
|
test_suite="pytest",
|
|
tests_require=test_requirements,
|
|
setup_requires=setup_requirements,
|
|
)
|