mirror of https://github.com/pypa/hatch.git
278 lines
6.5 KiB
Python
278 lines
6.5 KiB
Python
import glob
|
|
import os
|
|
import platform
|
|
import re
|
|
import shutil
|
|
from base64 import urlsafe_b64encode
|
|
from contextlib import contextmanager
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
from urllib.request import urlopen
|
|
|
|
__platform = platform.system()
|
|
ON_MACOS = os.name == 'mac' or __platform == 'Darwin'
|
|
ON_WINDOWS = NEED_SUBPROCESS_SHELL = os.name == 'nt' or __platform == 'Windows'
|
|
|
|
VENV_FLAGS = {
|
|
'_HATCHING_',
|
|
'VIRTUAL_ENV',
|
|
'CONDA_PREFIX',
|
|
}
|
|
|
|
|
|
def venv_active():
|
|
return bool(VENV_FLAGS & set(os.environ)) and not venv_ignored()
|
|
|
|
|
|
def venv_ignored():
|
|
return os.environ.get('_IGNORE_VENV_') == '1'
|
|
|
|
|
|
def get_random_venv_name():
|
|
# Will be length 4, so 16777216 possibilities.
|
|
return urlsafe_b64encode(os.urandom(3)).decode()
|
|
|
|
|
|
def get_admin_command(): # no cov
|
|
if ON_WINDOWS:
|
|
return [
|
|
'runas', r'/user:{}\{}'.format(
|
|
platform.node() or os.environ.get('USERDOMAIN', ''),
|
|
os.environ.get('_DEFAULT_ADMIN_', 'Administrator')
|
|
)
|
|
]
|
|
# Should we indeed use -H here?
|
|
else:
|
|
admin = os.environ.get('_DEFAULT_ADMIN_', '')
|
|
return ['sudo', '-H'] + (['--user={}'.format(admin)] if admin else [])
|
|
|
|
|
|
def find_project_root(d=None, max_depth=3):
|
|
path = Path(d or os.getcwd())
|
|
root = path.drive + path.root
|
|
path = str(path)
|
|
|
|
while max_depth >= 0:
|
|
project_file = os.path.join(path, 'pyproject.toml')
|
|
setup_file = os.path.join(path, 'setup.py')
|
|
|
|
if os.path.isfile(project_file) or os.path.isfile(setup_file):
|
|
return path
|
|
elif path == root:
|
|
max_depth = -1
|
|
continue
|
|
|
|
max_depth -= 1
|
|
path = os.path.dirname(path)
|
|
else:
|
|
raise Exception(
|
|
'Unable to find project home. Are you sure '
|
|
"you're inside a project directory?"
|
|
)
|
|
|
|
|
|
def is_project(d=None):
|
|
return os.path.isfile(os.path.join(d or os.getcwd(), 'setup.py'))
|
|
|
|
|
|
def is_os_64bit(): # no cov
|
|
# https://stackoverflow.com/a/12578715/5854007
|
|
return platform.machine().endswith('64')
|
|
|
|
|
|
def conda_available(): # no cov
|
|
return not not shutil.which('conda')
|
|
|
|
|
|
def get_requirements_file(d, dev=False):
|
|
d = d or os.getcwd()
|
|
|
|
reqs = os.path.join(d, 'requirements.txt')
|
|
if dev or not os.path.exists(reqs):
|
|
paths = set(glob.iglob(os.path.join(d, '*requirements*.txt')))
|
|
paths.discard(reqs)
|
|
if not paths:
|
|
return
|
|
reqs = sorted(paths)[0]
|
|
|
|
return reqs
|
|
|
|
|
|
def ensure_dir_exists(d):
|
|
if not os.path.exists(d):
|
|
os.makedirs(d)
|
|
|
|
|
|
def create_file(fname):
|
|
ensure_dir_exists(os.path.dirname(os.path.abspath(fname)))
|
|
with open(fname, 'a'):
|
|
os.utime(fname, times=None)
|
|
|
|
|
|
def download_file(url, fname):
|
|
req = urlopen(url)
|
|
with open(fname, 'wb') as f:
|
|
while True:
|
|
chunk = req.read(16384)
|
|
if not chunk:
|
|
break
|
|
f.write(chunk)
|
|
f.flush()
|
|
|
|
|
|
def copy_path(path, d):
|
|
if os.path.isdir(path):
|
|
shutil.copytree(
|
|
path,
|
|
os.path.join(d, basepath(path)),
|
|
copy_function=shutil.copy
|
|
)
|
|
else:
|
|
shutil.copy(path, d)
|
|
|
|
|
|
def remove_path(path):
|
|
try:
|
|
shutil.rmtree(path)
|
|
except (FileNotFoundError, OSError):
|
|
try:
|
|
os.remove(path)
|
|
except (FileNotFoundError, PermissionError):
|
|
pass
|
|
|
|
|
|
def resolve_path(path):
|
|
try:
|
|
path = str(Path(path).resolve())
|
|
# FUTURE: Remove this when we drop 3.5.
|
|
except FileNotFoundError: # no cov
|
|
return ''
|
|
return path if os.path.exists(path) else ''
|
|
|
|
|
|
def basepath(path):
|
|
return os.path.basename(os.path.normpath(path))
|
|
|
|
|
|
def get_current_year():
|
|
return str(datetime.now().year)
|
|
|
|
|
|
def normalize_package_name(package_name):
|
|
return re.sub(r"[-_.]+", "_", package_name).lower()
|
|
|
|
|
|
@contextmanager
|
|
def chdir(d, cwd=None):
|
|
origin = cwd or os.getcwd()
|
|
os.chdir(d)
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
os.chdir(origin)
|
|
|
|
|
|
@contextmanager
|
|
def temp_chdir(cwd=None):
|
|
with TemporaryDirectory() as d:
|
|
origin = cwd or os.getcwd()
|
|
os.chdir(d)
|
|
|
|
try:
|
|
yield resolve_path(d)
|
|
finally:
|
|
os.chdir(origin)
|
|
|
|
|
|
@contextmanager
|
|
def env_vars(evars, ignore=None):
|
|
ignore = ignore or {}
|
|
ignored_evars = {}
|
|
old_evars = {}
|
|
|
|
for ev in evars:
|
|
if ev in os.environ:
|
|
old_evars[ev] = os.environ[ev]
|
|
os.environ[ev] = evars[ev]
|
|
|
|
for ev in ignore:
|
|
if ev in os.environ: # no cov
|
|
ignored_evars[ev] = os.environ[ev]
|
|
os.environ.pop(ev)
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
for ev in evars:
|
|
if ev in old_evars:
|
|
os.environ[ev] = old_evars[ev]
|
|
else:
|
|
os.environ.pop(ev)
|
|
|
|
for ev in ignored_evars:
|
|
os.environ[ev] = ignored_evars[ev]
|
|
|
|
|
|
@contextmanager
|
|
def temp_move_path(path, d):
|
|
if os.path.exists(path):
|
|
dst = shutil.move(path, d)
|
|
|
|
try:
|
|
yield dst
|
|
finally:
|
|
try:
|
|
os.replace(dst, path)
|
|
except OSError: # no cov
|
|
shutil.move(dst, path)
|
|
else:
|
|
try:
|
|
yield
|
|
finally:
|
|
remove_path(path)
|
|
|
|
|
|
def is_setup_managed(setup_file):
|
|
try:
|
|
with open(setup_file) as f:
|
|
contents = f.readlines()
|
|
except FileNotFoundError:
|
|
return False
|
|
|
|
setup_header = re.compile(r'#+\sMaintained by Hatch\s#+')
|
|
if setup_header.match(contents[0]):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def parse_setup(setup_file):
|
|
with open(setup_file) as f:
|
|
contents = f.readlines()
|
|
|
|
user_def_start = re.compile(r'#+\sBEGIN USER OVERRIDES\s#+')
|
|
user_def_end = re.compile(r'#+\sEND USER OVERRIDES\s#+')
|
|
user_defined_snippet = []
|
|
in_user_defined_section = False
|
|
user_defined_snippet_is_valid = False
|
|
place_holder_text = ['# Add your customizations in this section.']
|
|
|
|
for line in contents:
|
|
if user_def_start.match(line):
|
|
in_user_defined_section = True
|
|
continue
|
|
if in_user_defined_section:
|
|
if line.strip() in place_holder_text:
|
|
continue
|
|
if user_def_end.match(line):
|
|
user_defined_snippet_is_valid = True
|
|
break
|
|
user_defined_snippet.append(line)
|
|
|
|
if user_defined_snippet_is_valid:
|
|
return ''.join(user_defined_snippet)
|
|
else:
|
|
raise Exception('User-defined section did not end correctly.')
|