120 lines
4.2 KiB
Python
120 lines
4.2 KiB
Python
import os
|
|
import platform
|
|
import subprocess
|
|
import urllib.request
|
|
import zipfile
|
|
import tarfile
|
|
import re
|
|
from typing import List
|
|
try:
|
|
from .Env import Env
|
|
except ImportError:
|
|
from Env import Env
|
|
class StandalonePythonEnv(Env):
|
|
def __init__(self, path: str, version: str):
|
|
super().__init__()
|
|
self.version = version
|
|
self.path = path
|
|
self.python_path = self._get_python_executable_path()
|
|
self.pip_path = self._get_pip_executable_path()
|
|
|
|
def _get_platform_info(self):
|
|
system = platform.system().lower()
|
|
arch = platform.machine().lower()
|
|
if arch in ['x86_64', 'amd64']:
|
|
arch = 'amd64'
|
|
elif arch in ['i386', 'i686', 'x86']:
|
|
arch = 'win32' if system == 'windows' else 'x86'
|
|
return system, arch
|
|
|
|
def _get_python_executable_path(self):
|
|
system, _ = self._get_platform_info()
|
|
if system == 'windows':
|
|
return os.path.join(self.path, 'python.exe')
|
|
return os.path.join(self.path, 'bin', 'python3')
|
|
|
|
def _get_pip_executable_path(self):
|
|
system, _ = self._get_platform_info()
|
|
if system == 'windows':
|
|
return os.path.join(self.path, 'Scripts', 'pip.exe')
|
|
return os.path.join(self.path, 'bin', 'pip')
|
|
|
|
def _download_and_extract(self):
|
|
system, arch = self._get_platform_info()
|
|
base_url = 'https://www.python.org/ftp/python'
|
|
|
|
if system == 'windows':
|
|
url = f'{base_url}/{self.version}/python-{self.version}-embed-{arch}.zip'
|
|
extract_path = self.path
|
|
zip_path = os.path.join(self.path, 'python.zip')
|
|
|
|
os.makedirs(self.path, exist_ok=True)
|
|
urllib.request.urlretrieve(url, zip_path)
|
|
|
|
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
|
zip_ref.extractall(extract_path)
|
|
os.remove(zip_path)
|
|
|
|
elif system == 'linux':
|
|
url = f'{base_url}/{self.version}/Python-{self.version}.tgz'
|
|
tar_path = os.path.join(self.path, 'python.tgz')
|
|
|
|
os.makedirs(self.path, exist_ok=True)
|
|
urllib.request.urlretrieve(url, tar_path)
|
|
|
|
with tarfile.open(tar_path, 'r:gz') as tar_ref:
|
|
if hasattr(tarfile, 'data_filter'):
|
|
tar_ref.extractall(self.path, filter='data')
|
|
else:
|
|
tar_ref.extractall(self.path)
|
|
os.remove(tar_path)
|
|
|
|
# Компиляция Python из исходников
|
|
source_dir = os.path.join(self.path, f'Python-{self.version}')
|
|
subprocess.run([
|
|
'./configure', f'--prefix={self.path}',
|
|
'--enable-optimizations',
|
|
'--with-ensurepip=install'
|
|
], cwd=source_dir, check=True)
|
|
subprocess.run(['make', '-j8'], cwd=source_dir, check=True)
|
|
subprocess.run(['make', 'install'], cwd=source_dir, check=True)
|
|
|
|
|
|
import shutil
|
|
shutil.copy2(os.path.join(self.path, 'bin', 'pip3'), os.path.join(self.path, 'bin', 'pip'))
|
|
|
|
pass
|
|
|
|
else:
|
|
raise NotImplementedError(f"Unsupported platform: {system}")
|
|
|
|
def create(self):
|
|
self._download_and_extract()
|
|
|
|
def install_pkgs(self, pkgs: List[str], repo: str = None, extra :str = None):
|
|
command = [self.pip_path, 'install'] + pkgs
|
|
if repo: command.extend(["--index-url", repo])
|
|
if extra: command.extend(["--extra-index-url", extra])
|
|
subprocess.run(command, check=True)
|
|
|
|
@staticmethod
|
|
def get_available_versions():
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
|
|
url = 'https://www.python.org/downloads/'
|
|
response = requests.get(url)
|
|
soup = BeautifulSoup(response.text, 'html.parser')
|
|
|
|
versions = []
|
|
for release in soup.select('.release-list .release-number'):
|
|
version_text = release.text.strip().split()[-1]
|
|
if re.match(r'^\d+\.\d+\.\d+$', version_text):
|
|
versions.append(version_text)
|
|
|
|
return sorted(versions, key=lambda x: tuple(map(int, x.split('.'))), reverse=True)
|
|
|
|
if __name__ == '__main__':
|
|
env = StandalonePythonEnv(os.path.join('/tmp', 'build/test_standalone'), '3.12.9')
|
|
env.create()
|
|
env.install_pkgs(['requests', 'numpy']) |