Repository URL to install this package:
|
Version:
3.9.0 ▾
|
flit
/
sdist.py
|
|---|
from collections import defaultdict
import io
import logging
import os
from pathlib import Path
from posixpath import join as pjoin
from pprint import pformat
import tarfile
from flit_core.sdist import SdistBuilder as SdistBuilderCore
from flit_core.common import Module, VCSError
from flit.vcs import identify_vcs
log = logging.getLogger(__name__)
# Our generated setup.py deliberately loads distutils, not setuptools, to
# discourage running it directly and getting a setuptools mess. Tools like pip
# handle this correctly - loading setuptools anyway but avoiding its issues.
SETUP = """\
#!/usr/bin/env python
# setup.py generated by flit for tools that don't yet use PEP 517
from distutils.core import setup
{before}
setup(name={name!r},
version={version!r},
description={description!r},
author={author!r},
author_email={author_email!r},
url={url!r},
{extra}
)
"""
def namespace_packages(module: Module):
"""Get parent package names"""
name_parts = []
for part in module.namespace_package_name.split('.'):
name_parts.append(part)
yield '.'.join(name_parts)
def auto_packages(module: Module):
"""Discover subpackages and package_data"""
pkgdir = os.path.normpath(str(module.path))
pkg_name = module.name
packages = []
if module.in_namespace_package:
packages.extend(namespace_packages(module))
packages.append(pkg_name)
pkg_data = defaultdict(list)
# Undocumented distutils feature: the empty string matches all package names
pkg_data[''].append('*')
subpkg_paths = set()
def find_nearest_pkg(rel_path):
parts = rel_path.split(os.sep)
for i in reversed(range(1, len(parts))):
ancestor = '/'.join(parts[:i])
if ancestor in subpkg_paths:
pkg = '.'.join([pkg_name] + parts[:i])
return pkg, '/'.join(parts[i:])
# Relative to the top-level package
return pkg_name, rel_path
for path, dirnames, filenames in os.walk(pkgdir, topdown=True):
if os.path.basename(path) == '__pycache__':
continue
from_top_level = os.path.relpath(path, pkgdir)
if from_top_level == '.':
continue
is_subpkg = '__init__.py' in filenames
if is_subpkg:
subpkg_paths.add(from_top_level)
parts = from_top_level.split(os.sep)
packages.append('.'.join([pkg_name] + parts))
else:
pkg, from_nearest_pkg = find_nearest_pkg(from_top_level)
pkg_data[pkg].append(pjoin(from_nearest_pkg, '*'))
# Sort values in pkg_data
pkg_data = {k: sorted(v) for (k, v) in pkg_data.items()}
return sorted(packages), pkg_data
def include_path(p):
return not (p.startswith('dist' + os.sep)
or (os.sep+'__pycache__' in p)
or p.endswith('.pyc'))
def _parse_req(requires_dist):
"""Parse "Foo (v); python_version == '2.x'" from Requires-Dist
Returns pip-style appropriate for requirements.txt.
"""
if ';' in requires_dist:
name_version, env_mark = requires_dist.split(';', 1)
env_mark = env_mark.strip()
else:
name_version, env_mark = requires_dist, None
if '(' in name_version:
# turn 'name (X)' and 'name (<X.Y)'
# into 'name == X' and 'name < X.Y'
name, version = name_version.split('(', 1)
name = name.strip()
version = version.replace(')', '').strip()
if not any(c in version for c in '=<>'):
version = '==' + version
name_version = name + version
return name_version, env_mark
def convert_requires(reqs_by_extra):
"""Regroup requirements by (extra, env_mark)"""
grouping = defaultdict(list)
for extra, reqs in reqs_by_extra.items():
for req in reqs:
name_version, env_mark = _parse_req(req)
grouping[(extra, env_mark)].append(name_version)
install_reqs = grouping.pop(('.none', None), [])
extra_reqs = {}
for (extra, env_mark), reqs in grouping.items():
if extra == '.none':
extra = ''
if env_mark is None:
extra_reqs[extra] = reqs
else:
extra_reqs[extra + ':' + env_mark] = reqs
return install_reqs, extra_reqs
class SdistBuilder(SdistBuilderCore):
"""Build a complete sdist
This extends the minimal sdist-building in flit_core:
- Include any files tracked in version control, such as docs sources and
tests.
- Add a generated setup.py for compatibility with tools which don't yet know
about PEP 517.
"""
use_vcs = True
@classmethod
def from_ini_path(cls, ini_path: Path, use_vcs=True):
inst = super().from_ini_path(ini_path)
inst.use_vcs = use_vcs
return inst
def select_files(self):
if not self.use_vcs:
return super().select_files()
vcs_mod = identify_vcs(self.cfgdir)
if vcs_mod is not None:
untracked_deleted = vcs_mod.list_untracked_deleted_files(self.cfgdir)
if any(include_path(p) and not self.excludes.match_file(p)
for p in untracked_deleted):
raise VCSError(
"Untracked or deleted files in the source directory. "
"Commit, undo or ignore these files in your VCS.",
self.cfgdir)
files = [os.path.normpath(p)
for p in vcs_mod.list_tracked_files(self.cfgdir)]
files = sorted(filter(include_path, files))
log.info("Found %d files tracked in %s", len(files), vcs_mod.name)
else:
files = super().select_files()
return files
def add_setup_py(self, files_to_add, target_tarfile):
if 'setup.py' in files_to_add:
log.warning(
"Using setup.py from repository, not generating setup.py")
else:
setup_py = self.make_setup_py()
log.info("Writing generated setup.py")
ti = tarfile.TarInfo(pjoin(self.dir_name, 'setup.py'))
ti.size = len(setup_py)
target_tarfile.addfile(ti, io.BytesIO(setup_py))
def make_setup_py(self):
before, extra = [], []
if self.module.is_package:
packages, package_data = auto_packages(self.module)
before.append("packages = \\\n%s\n" % pformat(sorted(packages)))
before.append("package_data = \\\n%s\n" % pformat(package_data))
extra.append("packages=packages,")
extra.append("package_data=package_data,")
else:
extra.append("py_modules={!r},".format([self.module.name]))
if self.module.in_namespace_package:
packages = list(namespace_packages(self.module))
before.append("packages = \\\n%s\n" % pformat(packages))
extra.append("packages=packages,")
if self.module.prefix:
package_dir = pformat({'': self.module.prefix})
before.append("package_dir = \\\n%s\n" % package_dir)
extra.append("package_dir=package_dir,")
install_reqs, extra_reqs = convert_requires(self.reqs_by_extra)
if install_reqs:
before.append("install_requires = \\\n%s\n" % pformat(install_reqs))
extra.append("install_requires=install_requires,")
if extra_reqs:
before.append("extras_require = \\\n%s\n" % pformat(extra_reqs))
extra.append("extras_require=extras_require,")
entrypoints = self.prep_entry_points()
if entrypoints:
before.append("entry_points = \\\n%s\n" % pformat(entrypoints))
extra.append("entry_points=entry_points,")
if self.metadata.requires_python:
extra.append('python_requires=%r,' % self.metadata.requires_python)
return SETUP.format(
before='\n'.join(before),
name=self.metadata.name,
version=self.metadata.version,
description=self.metadata.summary,
author=self.metadata.author,
author_email=self.metadata.author_email,
url=self.metadata.home_page,
extra='\n '.join(extra),
).encode('utf-8')