mirror of
https://github.com/YunoHost-Apps/django-for-runners_ynh.git
synced 2024-09-03 18:26:16 +02:00
Use pyrun for missing Python 3.11
Download and setup missing Python versions using eGenix PyRun from: https://github.com/eGenix/egenix-pyrun/
This commit is contained in:
parent
d360e3c094
commit
9117c6de27
4 changed files with 177 additions and 110 deletions
31
dev-cli.py
31
dev-cli.py
|
@ -20,22 +20,27 @@ def print_no_pip_error():
|
|||
print('Hint: "apt-get install python3-venv"\n')
|
||||
|
||||
|
||||
try:
|
||||
from ensurepip import version
|
||||
except ModuleNotFoundError as err:
|
||||
print(err)
|
||||
print('-' * 100)
|
||||
print_no_pip_error()
|
||||
raise
|
||||
else:
|
||||
if not version():
|
||||
print_no_pip_error()
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
assert sys.version_info >= (3, 11), f'Python version {sys.version_info} is too old!'
|
||||
|
||||
|
||||
try:
|
||||
import pip # noqa
|
||||
except ModuleNotFoundError:
|
||||
try:
|
||||
from ensurepip import version
|
||||
except ModuleNotFoundError as err:
|
||||
print(err)
|
||||
print('-' * 100)
|
||||
print_no_pip_error()
|
||||
raise
|
||||
else:
|
||||
if not version():
|
||||
print_no_pip_error()
|
||||
sys.exit(-1)
|
||||
else:
|
||||
print(f'pip version: {pip.__version__}')
|
||||
|
||||
|
||||
if sys.platform == 'win32': # wtf
|
||||
# Files under Windows, e.g.: .../.venv/Scripts/python.exe
|
||||
BIN_NAME = 'Scripts'
|
||||
|
|
|
@ -100,7 +100,7 @@ ram.runtime = "50M" # **estimate** minimum ram requirement. e.g. 50M, 400M, 1G,
|
|||
[resources.apt]
|
||||
# https://yunohost.org/en/packaging_apps_resources#apt
|
||||
# This will automatically install/uninstall the following apt packages
|
||||
packages = "build-essential, python3-dev, python3-pip, python3-venv, git, libpq-dev, postgresql, postgresql-contrib, redis-server, checkinstall, libssl-dev, openssl"
|
||||
packages = "python3, git, libpq-dev, postgresql, postgresql-contrib, redis-server, libssl-dev, openssl"
|
||||
|
||||
[resources.database]
|
||||
# https://yunohost.org/en/packaging_apps_resources#database
|
||||
|
|
|
@ -38,107 +38,18 @@ log_file="${log_path}/${app}.log"
|
|||
# HELPERS
|
||||
#=================================================
|
||||
|
||||
|
||||
#==================================================================================
|
||||
# myynh_install_python() Borrowed from:
|
||||
# https://github.com/YunoHost-Apps/homeassistant_ynh/blob/master/scripts/_common.sh
|
||||
# Until we get a newer Python in YunoHost, see:
|
||||
# https://forum.yunohost.org/t/use-newer-python-than-3-9/22568
|
||||
#==================================================================================
|
||||
py_required_major=3.11
|
||||
py_required_version=$(curl -Ls https://www.python.org/ftp/python/ \
|
||||
| grep '>'$py_required_major | cut -d '/' -f 2 \
|
||||
| cut -d '>' -f 2 | sort -rV | head -n 1) #3.11.8
|
||||
|
||||
myynh_install_python() {
|
||||
# Declare an array to define the options of this helper.
|
||||
local legacy_args=u
|
||||
local -A args_array=( [p]=python= )
|
||||
local python
|
||||
# Manage arguments with getopts
|
||||
ynh_handle_getopts_args "$@"
|
||||
|
||||
# Check python version from APT
|
||||
local py_apt_version=$(python3 --version | cut -d ' ' -f 2)
|
||||
|
||||
# Usefull variables
|
||||
local python_major=${python%.*}
|
||||
|
||||
# Check existing built version of python in /usr/local/bin
|
||||
if [ -e "/usr/local/bin/python$python_major" ]
|
||||
then
|
||||
local py_built_version=$(/usr/local/bin/python$python_major --version \
|
||||
| cut -d ' ' -f 2)
|
||||
else
|
||||
local py_built_version=0
|
||||
fi
|
||||
|
||||
# Compare version
|
||||
if $(dpkg --compare-versions $py_apt_version ge $python)
|
||||
then
|
||||
# APT >= Required
|
||||
ynh_print_info --message="Using provided python3..."
|
||||
|
||||
py_app_version="python3"
|
||||
|
||||
else
|
||||
# Either python already built or to build
|
||||
if $(dpkg --compare-versions $py_built_version ge $python)
|
||||
then
|
||||
# Built >= Required
|
||||
ynh_print_info --message="Using already used python3 built version..."
|
||||
|
||||
py_app_version="/usr/local/bin/python${py_built_version%.*}"
|
||||
|
||||
else
|
||||
# APT < Minimal & Actual < Minimal => Build & install Python into /usr/local/bin
|
||||
ynh_print_info --message="Building python (may take a while)..."
|
||||
|
||||
# Store current direcotry
|
||||
local MY_DIR=$(pwd)
|
||||
|
||||
# Create a temp direcotry
|
||||
tmpdir="$(mktemp --directory)"
|
||||
cd "$tmpdir"
|
||||
|
||||
# Download
|
||||
wget --output-document="Python-$python.tar.xz" \
|
||||
"https://www.python.org/ftp/python/$python/Python-$python.tar.xz" 2>&1
|
||||
|
||||
# Extract
|
||||
tar xf "Python-$python.tar.xz"
|
||||
|
||||
# Install
|
||||
cd "Python-$python"
|
||||
./configure --enable-optimizations
|
||||
ynh_exec_warn_less make -j4
|
||||
ynh_exec_warn_less make altinstall
|
||||
|
||||
# Go back to working directory
|
||||
cd "$MY_DIR"
|
||||
|
||||
# Clean
|
||||
ynh_secure_remove "$tmpdir"
|
||||
|
||||
# Set version
|
||||
py_app_version="/usr/local/bin/python$python_major"
|
||||
fi
|
||||
fi
|
||||
# Save python version in settings
|
||||
ynh_app_setting_set --app=$app --key=python --value="$python"
|
||||
}
|
||||
#==================================================================================
|
||||
#==================================================================================
|
||||
|
||||
myynh_setup_python_venv() {
|
||||
# Always recreate everything fresh with current python version
|
||||
ynh_secure_remove "$data_dir/venv"
|
||||
|
||||
myynh_install_python --python="$py_required_version"
|
||||
# Install PyRun: https://github.com/eGenix/egenix-pyrun/
|
||||
python3 setup-pyrun.py --version 3.11 --destination $data_dir
|
||||
|
||||
# Create a virtualenv with python installed by myynh_install_python():
|
||||
# Skip pip because of: https://github.com/YunoHost/issues/issues/1960
|
||||
$py_app_version -m venv --without-pip "$data_dir/venv"
|
||||
# Now PyRun should be installed:
|
||||
"$data_dir/pyrun3.11/bin/python3.11" -V
|
||||
|
||||
# Create a Python Virtualenv
|
||||
"$data_dir/pyrun3.11/bin/python3.11" -m virtualenv "$data_dir/venv"
|
||||
|
||||
chown -c -R "$app:" "$data_dir"
|
||||
|
||||
|
|
151
scripts/setup-pyrun.py
Executable file
151
scripts/setup-pyrun.py
Executable file
|
@ -0,0 +1,151 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Download and setup missing Python versions using eGenix PyRun from:
|
||||
|
||||
https://github.com/eGenix/egenix-pyrun/
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import urllib.request
|
||||
from argparse import ArgumentParser
|
||||
from pathlib import Path
|
||||
from pprint import pprint
|
||||
|
||||
from urllib3.util import parse_url
|
||||
|
||||
|
||||
DEFAULT_VERSION = '3.11'
|
||||
|
||||
|
||||
GET_PIP_URL = 'https://bootstrap.pypa.io/get-pip.py' # https://github.com/eGenix/egenix-pyrun/issues/11
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ReleaseInfo:
|
||||
version: str
|
||||
urls: list[str]
|
||||
|
||||
def get(self, *, version):
|
||||
for url in self.urls:
|
||||
if f'-py{version}_' in url:
|
||||
return url
|
||||
|
||||
|
||||
def get_pyrun_release_info() -> ReleaseInfo:
|
||||
api_url = "https://api.github.com/repos/eGenix/egenix-pyrun/releases"
|
||||
with urllib.request.urlopen(api_url) as response:
|
||||
data = response.read().decode()
|
||||
releases = json.loads(data)
|
||||
|
||||
latest_release = releases[0]
|
||||
# pprint(latest_release)
|
||||
|
||||
print(latest_release['html_url'])
|
||||
print(latest_release['tag_name'])
|
||||
print(latest_release['name'])
|
||||
|
||||
urls = []
|
||||
for asset in latest_release['assets']:
|
||||
urls.append(asset['browser_download_url'])
|
||||
|
||||
return ReleaseInfo(version=latest_release['name'], urls=urls)
|
||||
|
||||
|
||||
def setup_pyrun(*, pyrun_version: str, destination: str):
|
||||
print('_' * 100)
|
||||
dest_path = Path(destination).expanduser()
|
||||
dest_path.mkdir(parents=True, exist_ok=True)
|
||||
print(f'Setup pyrun for Python {pyrun_version} to: {dest_path}')
|
||||
|
||||
release_info = get_pyrun_release_info()
|
||||
pprint(release_info)
|
||||
|
||||
python_bin_name = f'python{pyrun_version}'
|
||||
path = shutil.which(python_bin_name)
|
||||
if path:
|
||||
path = Path(path).resolve()
|
||||
print(f'Found {python_bin_name} at {path}')
|
||||
print(path.name)
|
||||
if path.name.startswith('pyrun'):
|
||||
print(f'{python_bin_name} is pyrun -> update')
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
url = release_info.get(version=pyrun_version)
|
||||
if not url:
|
||||
print(f'No PyRun release found for Python {pyrun_version}')
|
||||
sys.exit(1)
|
||||
|
||||
print(f'Download {url}')
|
||||
|
||||
subprocess.check_call(
|
||||
['wget', '--timestamp', url],
|
||||
cwd=dest_path,
|
||||
)
|
||||
|
||||
filename = Path(parse_url(url).path).name
|
||||
print(f'{filename=}')
|
||||
tgz_path = dest_path / filename
|
||||
assert tgz_path.is_file(), f'{tgz_path=}'
|
||||
|
||||
final_path = dest_path / f'pyrun{pyrun_version}/'
|
||||
print(f'Extract {tgz_path} to {final_path}...')
|
||||
final_path.mkdir(parents=False, exist_ok=True)
|
||||
with tarfile.open(tgz_path, "r:gz") as tgz_file:
|
||||
for member in tgz_file.getmembers():
|
||||
if ".." in member.name or member.name.startswith("/"):
|
||||
raise ValueError(f"Unsafe file path detected: {member.name}")
|
||||
tgz_file.extractall(path=final_path)
|
||||
|
||||
tgz_path.unlink()
|
||||
|
||||
pyrun_bin = final_path / 'bin' / f'pyrun{pyrun_version}'
|
||||
|
||||
print(f'Install pip/virtualenv for Python {pyrun_version}')
|
||||
# FIXME: # https://github.com/eGenix/egenix-pyrun/issues/11
|
||||
subprocess.check_call(
|
||||
['wget', '--timestamp', GET_PIP_URL],
|
||||
cwd=dest_path,
|
||||
)
|
||||
get_pip_path = dest_path / 'get-pip.py'
|
||||
|
||||
subprocess.check_call(
|
||||
[pyrun_bin, get_pip_path],
|
||||
)
|
||||
get_pip_path.unlink()
|
||||
subprocess.check_call(
|
||||
[pyrun_bin, '-m', 'pip', 'install', '-U', 'virtualenv'],
|
||||
)
|
||||
print('\n')
|
||||
|
||||
print(f'Check {pyrun_bin}:')
|
||||
subprocess.check_call([pyrun_bin, '-V'])
|
||||
|
||||
print('\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = ArgumentParser(description='Setup PyRun for a specific Python version.')
|
||||
parser.add_argument(
|
||||
'--version',
|
||||
type=str,
|
||||
default=DEFAULT_VERSION,
|
||||
help=f'Python version to setup with PyRun (default: {DEFAULT_VERSION}),',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--destination',
|
||||
type=str,
|
||||
help=f'Destination path to store PyRun (e.g.: ~/.local/)',
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
setup_pyrun(
|
||||
pyrun_version=args.version,
|
||||
destination=args.destination,
|
||||
)
|
Loading…
Reference in a new issue