You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			648 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			648 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
import os
 | 
						|
import sys
 | 
						|
from configparser import ConfigParser
 | 
						|
from itertools import product
 | 
						|
from typing import cast
 | 
						|
 | 
						|
import jaraco.path
 | 
						|
import pytest
 | 
						|
from path import Path
 | 
						|
 | 
						|
import setuptools  # noqa: F401 # force distutils.core to be patched
 | 
						|
from setuptools.command.sdist import sdist
 | 
						|
from setuptools.discovery import find_package_path, find_parent_package
 | 
						|
from setuptools.dist import Distribution
 | 
						|
from setuptools.errors import PackageDiscoveryError
 | 
						|
 | 
						|
from .contexts import quiet
 | 
						|
from .integration.helpers import get_sdist_members, get_wheel_members, run
 | 
						|
from .textwrap import DALS
 | 
						|
 | 
						|
import distutils.core
 | 
						|
 | 
						|
 | 
						|
class TestFindParentPackage:
 | 
						|
    def test_single_package(self, tmp_path):
 | 
						|
        # find_parent_package should find a non-namespace parent package
 | 
						|
        (tmp_path / "src/namespace/pkg/nested").mkdir(exist_ok=True, parents=True)
 | 
						|
        (tmp_path / "src/namespace/pkg/nested/__init__.py").touch()
 | 
						|
        (tmp_path / "src/namespace/pkg/__init__.py").touch()
 | 
						|
        packages = ["namespace", "namespace.pkg", "namespace.pkg.nested"]
 | 
						|
        assert find_parent_package(packages, {"": "src"}, tmp_path) == "namespace.pkg"
 | 
						|
 | 
						|
    def test_multiple_toplevel(self, tmp_path):
 | 
						|
        # find_parent_package should return null if the given list of packages does not
 | 
						|
        # have a single parent package
 | 
						|
        multiple = ["pkg", "pkg1", "pkg2"]
 | 
						|
        for name in multiple:
 | 
						|
            (tmp_path / f"src/{name}").mkdir(exist_ok=True, parents=True)
 | 
						|
            (tmp_path / f"src/{name}/__init__.py").touch()
 | 
						|
        assert find_parent_package(multiple, {"": "src"}, tmp_path) is None
 | 
						|
 | 
						|
 | 
						|
class TestDiscoverPackagesAndPyModules:
 | 
						|
    """Make sure discovered values for ``packages`` and ``py_modules`` work
 | 
						|
    similarly to explicit configuration for the simple scenarios.
 | 
						|
    """
 | 
						|
 | 
						|
    OPTIONS = {
 | 
						|
        # Different options according to the circumstance being tested
 | 
						|
        "explicit-src": {"package_dir": {"": "src"}, "packages": ["pkg"]},
 | 
						|
        "variation-lib": {
 | 
						|
            "package_dir": {"": "lib"},  # variation of the source-layout
 | 
						|
        },
 | 
						|
        "explicit-flat": {"packages": ["pkg"]},
 | 
						|
        "explicit-single_module": {"py_modules": ["pkg"]},
 | 
						|
        "explicit-namespace": {"packages": ["ns", "ns.pkg"]},
 | 
						|
        "automatic-src": {},
 | 
						|
        "automatic-flat": {},
 | 
						|
        "automatic-single_module": {},
 | 
						|
        "automatic-namespace": {},
 | 
						|
    }
 | 
						|
    FILES = {
 | 
						|
        "src": ["src/pkg/__init__.py", "src/pkg/main.py"],
 | 
						|
        "lib": ["lib/pkg/__init__.py", "lib/pkg/main.py"],
 | 
						|
        "flat": ["pkg/__init__.py", "pkg/main.py"],
 | 
						|
        "single_module": ["pkg.py"],
 | 
						|
        "namespace": ["ns/pkg/__init__.py"],
 | 
						|
    }
 | 
						|
 | 
						|
    def _get_info(self, circumstance):
 | 
						|
        _, _, layout = circumstance.partition("-")
 | 
						|
        files = self.FILES[layout]
 | 
						|
        options = self.OPTIONS[circumstance]
 | 
						|
        return files, options
 | 
						|
 | 
						|
    @pytest.mark.parametrize("circumstance", OPTIONS.keys())
 | 
						|
    def test_sdist_filelist(self, tmp_path, circumstance):
 | 
						|
        files, options = self._get_info(circumstance)
 | 
						|
        _populate_project_dir(tmp_path, files, options)
 | 
						|
 | 
						|
        _, cmd = _run_sdist_programatically(tmp_path, options)
 | 
						|
 | 
						|
        manifest = [f.replace(os.sep, "/") for f in cmd.filelist.files]
 | 
						|
        for file in files:
 | 
						|
            assert any(f.endswith(file) for f in manifest)
 | 
						|
 | 
						|
    @pytest.mark.parametrize("circumstance", OPTIONS.keys())
 | 
						|
    def test_project(self, tmp_path, circumstance):
 | 
						|
        files, options = self._get_info(circumstance)
 | 
						|
        _populate_project_dir(tmp_path, files, options)
 | 
						|
 | 
						|
        # Simulate a pre-existing `build` directory
 | 
						|
        (tmp_path / "build").mkdir()
 | 
						|
        (tmp_path / "build/lib").mkdir()
 | 
						|
        (tmp_path / "build/bdist.linux-x86_64").mkdir()
 | 
						|
        (tmp_path / "build/bdist.linux-x86_64/file.py").touch()
 | 
						|
        (tmp_path / "build/lib/__init__.py").touch()
 | 
						|
        (tmp_path / "build/lib/file.py").touch()
 | 
						|
        (tmp_path / "dist").mkdir()
 | 
						|
        (tmp_path / "dist/file.py").touch()
 | 
						|
 | 
						|
        _run_build(tmp_path)
 | 
						|
 | 
						|
        sdist_files = get_sdist_members(next(tmp_path.glob("dist/*.tar.gz")))
 | 
						|
        print("~~~~~ sdist_members ~~~~~")
 | 
						|
        print('\n'.join(sdist_files))
 | 
						|
        assert sdist_files >= set(files)
 | 
						|
 | 
						|
        wheel_files = get_wheel_members(next(tmp_path.glob("dist/*.whl")))
 | 
						|
        print("~~~~~ wheel_members ~~~~~")
 | 
						|
        print('\n'.join(wheel_files))
 | 
						|
        orig_files = {f.replace("src/", "").replace("lib/", "") for f in files}
 | 
						|
        assert wheel_files >= orig_files
 | 
						|
 | 
						|
        # Make sure build files are not included by mistake
 | 
						|
        for file in wheel_files:
 | 
						|
            assert "build" not in files
 | 
						|
            assert "dist" not in files
 | 
						|
 | 
						|
    PURPOSEFULLY_EMPY = {
 | 
						|
        "setup.cfg": DALS(
 | 
						|
            """
 | 
						|
            [metadata]
 | 
						|
            name = myproj
 | 
						|
            version = 0.0.0
 | 
						|
 | 
						|
            [options]
 | 
						|
            {param} =
 | 
						|
            """
 | 
						|
        ),
 | 
						|
        "setup.py": DALS(
 | 
						|
            """
 | 
						|
            __import__('setuptools').setup(
 | 
						|
                name="myproj",
 | 
						|
                version="0.0.0",
 | 
						|
                {param}=[]
 | 
						|
            )
 | 
						|
            """
 | 
						|
        ),
 | 
						|
        "pyproject.toml": DALS(
 | 
						|
            """
 | 
						|
            [build-system]
 | 
						|
            requires = []
 | 
						|
            build-backend = 'setuptools.build_meta'
 | 
						|
 | 
						|
            [project]
 | 
						|
            name = "myproj"
 | 
						|
            version = "0.0.0"
 | 
						|
 | 
						|
            [tool.setuptools]
 | 
						|
            {param} = []
 | 
						|
            """
 | 
						|
        ),
 | 
						|
        "template-pyproject.toml": DALS(
 | 
						|
            """
 | 
						|
            [build-system]
 | 
						|
            requires = []
 | 
						|
            build-backend = 'setuptools.build_meta'
 | 
						|
            """
 | 
						|
        ),
 | 
						|
    }
 | 
						|
 | 
						|
    @pytest.mark.parametrize(
 | 
						|
        ("config_file", "param", "circumstance"),
 | 
						|
        product(
 | 
						|
            ["setup.cfg", "setup.py", "pyproject.toml"],
 | 
						|
            ["packages", "py_modules"],
 | 
						|
            FILES.keys(),
 | 
						|
        ),
 | 
						|
    )
 | 
						|
    def test_purposefully_empty(self, tmp_path, config_file, param, circumstance):
 | 
						|
        files = self.FILES[circumstance] + ["mod.py", "other.py", "src/pkg/__init__.py"]
 | 
						|
        _populate_project_dir(tmp_path, files, {})
 | 
						|
 | 
						|
        if config_file == "pyproject.toml":
 | 
						|
            template_param = param.replace("_", "-")
 | 
						|
        else:
 | 
						|
            # Make sure build works with or without setup.cfg
 | 
						|
            pyproject = self.PURPOSEFULLY_EMPY["template-pyproject.toml"]
 | 
						|
            (tmp_path / "pyproject.toml").write_text(pyproject, encoding="utf-8")
 | 
						|
            template_param = param
 | 
						|
 | 
						|
        config = self.PURPOSEFULLY_EMPY[config_file].format(param=template_param)
 | 
						|
        (tmp_path / config_file).write_text(config, encoding="utf-8")
 | 
						|
 | 
						|
        dist = _get_dist(tmp_path, {})
 | 
						|
        # When either parameter package or py_modules is an empty list,
 | 
						|
        # then there should be no discovery
 | 
						|
        assert getattr(dist, param) == []
 | 
						|
        other = {"py_modules": "packages", "packages": "py_modules"}[param]
 | 
						|
        assert getattr(dist, other) is None
 | 
						|
 | 
						|
    @pytest.mark.parametrize(
 | 
						|
        ("extra_files", "pkgs"),
 | 
						|
        [
 | 
						|
            (["venv/bin/simulate_venv"], {"pkg"}),
 | 
						|
            (["pkg-stubs/__init__.pyi"], {"pkg", "pkg-stubs"}),
 | 
						|
            (["other-stubs/__init__.pyi"], {"pkg", "other-stubs"}),
 | 
						|
            (
 | 
						|
                # Type stubs can also be namespaced
 | 
						|
                ["namespace-stubs/pkg/__init__.pyi"],
 | 
						|
                {"pkg", "namespace-stubs", "namespace-stubs.pkg"},
 | 
						|
            ),
 | 
						|
            (
 | 
						|
                # Just the top-level package can have `-stubs`, ignore nested ones
 | 
						|
                ["namespace-stubs/pkg-stubs/__init__.pyi"],
 | 
						|
                {"pkg", "namespace-stubs"},
 | 
						|
            ),
 | 
						|
            (["_hidden/file.py"], {"pkg"}),
 | 
						|
            (["news/finalize.py"], {"pkg"}),
 | 
						|
        ],
 | 
						|
    )
 | 
						|
    def test_flat_layout_with_extra_files(self, tmp_path, extra_files, pkgs):
 | 
						|
        files = self.FILES["flat"] + extra_files
 | 
						|
        _populate_project_dir(tmp_path, files, {})
 | 
						|
        dist = _get_dist(tmp_path, {})
 | 
						|
        assert set(dist.packages) == pkgs
 | 
						|
 | 
						|
    @pytest.mark.parametrize(
 | 
						|
        "extra_files",
 | 
						|
        [
 | 
						|
            ["other/__init__.py"],
 | 
						|
            ["other/finalize.py"],
 | 
						|
        ],
 | 
						|
    )
 | 
						|
    def test_flat_layout_with_dangerous_extra_files(self, tmp_path, extra_files):
 | 
						|
        files = self.FILES["flat"] + extra_files
 | 
						|
        _populate_project_dir(tmp_path, files, {})
 | 
						|
        with pytest.raises(PackageDiscoveryError, match="multiple (packages|modules)"):
 | 
						|
            _get_dist(tmp_path, {})
 | 
						|
 | 
						|
    def test_flat_layout_with_single_module(self, tmp_path):
 | 
						|
        files = self.FILES["single_module"] + ["invalid-module-name.py"]
 | 
						|
        _populate_project_dir(tmp_path, files, {})
 | 
						|
        dist = _get_dist(tmp_path, {})
 | 
						|
        assert set(dist.py_modules) == {"pkg"}
 | 
						|
 | 
						|
    def test_flat_layout_with_multiple_modules(self, tmp_path):
 | 
						|
        files = self.FILES["single_module"] + ["valid_module_name.py"]
 | 
						|
        _populate_project_dir(tmp_path, files, {})
 | 
						|
        with pytest.raises(PackageDiscoveryError, match="multiple (packages|modules)"):
 | 
						|
            _get_dist(tmp_path, {})
 | 
						|
 | 
						|
    def test_py_modules_when_wheel_dir_is_cwd(self, tmp_path):
 | 
						|
        """Regression for issue 3692"""
 | 
						|
        from setuptools import build_meta
 | 
						|
 | 
						|
        pyproject = '[project]\nname = "test"\nversion = "1"'
 | 
						|
        (tmp_path / "pyproject.toml").write_text(DALS(pyproject), encoding="utf-8")
 | 
						|
        (tmp_path / "foo.py").touch()
 | 
						|
        with jaraco.path.DirectoryStack().context(tmp_path):
 | 
						|
            build_meta.build_wheel(".")
 | 
						|
        # Ensure py_modules are found
 | 
						|
        wheel_files = get_wheel_members(next(tmp_path.glob("*.whl")))
 | 
						|
        assert "foo.py" in wheel_files
 | 
						|
 | 
						|
 | 
						|
class TestNoConfig:
 | 
						|
    DEFAULT_VERSION = "0.0.0"  # Default version given by setuptools
 | 
						|
 | 
						|
    EXAMPLES = {
 | 
						|
        "pkg1": ["src/pkg1.py"],
 | 
						|
        "pkg2": ["src/pkg2/__init__.py"],
 | 
						|
        "pkg3": ["src/pkg3/__init__.py", "src/pkg3-stubs/__init__.py"],
 | 
						|
        "pkg4": ["pkg4/__init__.py", "pkg4-stubs/__init__.py"],
 | 
						|
        "ns.nested.pkg1": ["src/ns/nested/pkg1/__init__.py"],
 | 
						|
        "ns.nested.pkg2": ["ns/nested/pkg2/__init__.py"],
 | 
						|
    }
 | 
						|
 | 
						|
    @pytest.mark.parametrize("example", EXAMPLES.keys())
 | 
						|
    def test_discover_name(self, tmp_path, example):
 | 
						|
        _populate_project_dir(tmp_path, self.EXAMPLES[example], {})
 | 
						|
        dist = _get_dist(tmp_path, {})
 | 
						|
        assert dist.get_name() == example
 | 
						|
 | 
						|
    def test_build_with_discovered_name(self, tmp_path):
 | 
						|
        files = ["src/ns/nested/pkg/__init__.py"]
 | 
						|
        _populate_project_dir(tmp_path, files, {})
 | 
						|
        _run_build(tmp_path, "--sdist")
 | 
						|
        # Expected distribution file
 | 
						|
        dist_file = tmp_path / f"dist/ns_nested_pkg-{self.DEFAULT_VERSION}.tar.gz"
 | 
						|
        assert dist_file.is_file()
 | 
						|
 | 
						|
 | 
						|
class TestWithAttrDirective:
 | 
						|
    @pytest.mark.parametrize(
 | 
						|
        ("folder", "opts"),
 | 
						|
        [
 | 
						|
            ("src", {}),
 | 
						|
            ("lib", {"packages": "find:", "packages.find": {"where": "lib"}}),
 | 
						|
        ],
 | 
						|
    )
 | 
						|
    def test_setupcfg_metadata(self, tmp_path, folder, opts):
 | 
						|
        files = [f"{folder}/pkg/__init__.py", "setup.cfg"]
 | 
						|
        _populate_project_dir(tmp_path, files, opts)
 | 
						|
 | 
						|
        config = (tmp_path / "setup.cfg").read_text(encoding="utf-8")
 | 
						|
        overwrite = {
 | 
						|
            folder: {"pkg": {"__init__.py": "version = 42"}},
 | 
						|
            "setup.cfg": "[metadata]\nversion = attr: pkg.version\n" + config,
 | 
						|
        }
 | 
						|
        jaraco.path.build(overwrite, prefix=tmp_path)
 | 
						|
 | 
						|
        dist = _get_dist(tmp_path, {})
 | 
						|
        assert dist.get_name() == "pkg"
 | 
						|
        assert dist.get_version() == "42"
 | 
						|
        assert dist.package_dir
 | 
						|
        package_path = find_package_path("pkg", dist.package_dir, tmp_path)
 | 
						|
        assert os.path.exists(package_path)
 | 
						|
        assert folder in Path(package_path).parts()
 | 
						|
 | 
						|
        _run_build(tmp_path, "--sdist")
 | 
						|
        dist_file = tmp_path / "dist/pkg-42.tar.gz"
 | 
						|
        assert dist_file.is_file()
 | 
						|
 | 
						|
    def test_pyproject_metadata(self, tmp_path):
 | 
						|
        _populate_project_dir(tmp_path, ["src/pkg/__init__.py"], {})
 | 
						|
 | 
						|
        overwrite = {
 | 
						|
            "src": {"pkg": {"__init__.py": "version = 42"}},
 | 
						|
            "pyproject.toml": (
 | 
						|
                "[project]\nname = 'pkg'\ndynamic = ['version']\n"
 | 
						|
                "[tool.setuptools.dynamic]\nversion = {attr = 'pkg.version'}\n"
 | 
						|
            ),
 | 
						|
        }
 | 
						|
        jaraco.path.build(overwrite, prefix=tmp_path)
 | 
						|
 | 
						|
        dist = _get_dist(tmp_path, {})
 | 
						|
        assert dist.get_version() == "42"
 | 
						|
        assert dist.package_dir == {"": "src"}
 | 
						|
 | 
						|
 | 
						|
class TestWithCExtension:
 | 
						|
    def _simulate_package_with_extension(self, tmp_path):
 | 
						|
        # This example is based on: https://github.com/nucleic/kiwi/tree/1.4.0
 | 
						|
        files = [
 | 
						|
            "benchmarks/file.py",
 | 
						|
            "docs/Makefile",
 | 
						|
            "docs/requirements.txt",
 | 
						|
            "docs/source/conf.py",
 | 
						|
            "proj/header.h",
 | 
						|
            "proj/file.py",
 | 
						|
            "py/proj.cpp",
 | 
						|
            "py/other.cpp",
 | 
						|
            "py/file.py",
 | 
						|
            "py/py.typed",
 | 
						|
            "py/tests/test_proj.py",
 | 
						|
            "README.rst",
 | 
						|
        ]
 | 
						|
        _populate_project_dir(tmp_path, files, {})
 | 
						|
 | 
						|
        setup_script = """
 | 
						|
            from setuptools import Extension, setup
 | 
						|
 | 
						|
            ext_modules = [
 | 
						|
                Extension(
 | 
						|
                    "proj",
 | 
						|
                    ["py/proj.cpp", "py/other.cpp"],
 | 
						|
                    include_dirs=["."],
 | 
						|
                    language="c++",
 | 
						|
                ),
 | 
						|
            ]
 | 
						|
            setup(ext_modules=ext_modules)
 | 
						|
        """
 | 
						|
        (tmp_path / "setup.py").write_text(DALS(setup_script), encoding="utf-8")
 | 
						|
 | 
						|
    def test_skip_discovery_with_setupcfg_metadata(self, tmp_path):
 | 
						|
        """Ensure that auto-discovery is not triggered when the project is based on
 | 
						|
        C-extensions only, for backward compatibility.
 | 
						|
        """
 | 
						|
        self._simulate_package_with_extension(tmp_path)
 | 
						|
 | 
						|
        pyproject = """
 | 
						|
            [build-system]
 | 
						|
            requires = []
 | 
						|
            build-backend = 'setuptools.build_meta'
 | 
						|
        """
 | 
						|
        (tmp_path / "pyproject.toml").write_text(DALS(pyproject), encoding="utf-8")
 | 
						|
 | 
						|
        setupcfg = """
 | 
						|
            [metadata]
 | 
						|
            name = proj
 | 
						|
            version = 42
 | 
						|
        """
 | 
						|
        (tmp_path / "setup.cfg").write_text(DALS(setupcfg), encoding="utf-8")
 | 
						|
 | 
						|
        dist = _get_dist(tmp_path, {})
 | 
						|
        assert dist.get_name() == "proj"
 | 
						|
        assert dist.get_version() == "42"
 | 
						|
        assert dist.py_modules is None
 | 
						|
        assert dist.packages is None
 | 
						|
        assert len(dist.ext_modules) == 1
 | 
						|
        assert dist.ext_modules[0].name == "proj"
 | 
						|
 | 
						|
    def test_dont_skip_discovery_with_pyproject_metadata(self, tmp_path):
 | 
						|
        """When opting-in to pyproject.toml metadata, auto-discovery will be active if
 | 
						|
        the package lists C-extensions, but does not configure py-modules or packages.
 | 
						|
 | 
						|
        This way we ensure users with complex package layouts that would lead to the
 | 
						|
        discovery of multiple top-level modules/packages see errors and are forced to
 | 
						|
        explicitly set ``packages`` or ``py-modules``.
 | 
						|
        """
 | 
						|
        self._simulate_package_with_extension(tmp_path)
 | 
						|
 | 
						|
        pyproject = """
 | 
						|
            [project]
 | 
						|
            name = 'proj'
 | 
						|
            version = '42'
 | 
						|
        """
 | 
						|
        (tmp_path / "pyproject.toml").write_text(DALS(pyproject), encoding="utf-8")
 | 
						|
        with pytest.raises(PackageDiscoveryError, match="multiple (packages|modules)"):
 | 
						|
            _get_dist(tmp_path, {})
 | 
						|
 | 
						|
 | 
						|
class TestWithPackageData:
 | 
						|
    def _simulate_package_with_data_files(self, tmp_path, src_root):
 | 
						|
        files = [
 | 
						|
            f"{src_root}/proj/__init__.py",
 | 
						|
            f"{src_root}/proj/file1.txt",
 | 
						|
            f"{src_root}/proj/nested/file2.txt",
 | 
						|
        ]
 | 
						|
        _populate_project_dir(tmp_path, files, {})
 | 
						|
 | 
						|
        manifest = """
 | 
						|
            global-include *.py *.txt
 | 
						|
        """
 | 
						|
        (tmp_path / "MANIFEST.in").write_text(DALS(manifest), encoding="utf-8")
 | 
						|
 | 
						|
    EXAMPLE_SETUPCFG = """
 | 
						|
    [metadata]
 | 
						|
    name = proj
 | 
						|
    version = 42
 | 
						|
 | 
						|
    [options]
 | 
						|
    include_package_data = True
 | 
						|
    """
 | 
						|
    EXAMPLE_PYPROJECT = """
 | 
						|
    [project]
 | 
						|
    name = "proj"
 | 
						|
    version = "42"
 | 
						|
    """
 | 
						|
 | 
						|
    PYPROJECT_PACKAGE_DIR = """
 | 
						|
    [tool.setuptools]
 | 
						|
    package-dir = {"" = "src"}
 | 
						|
    """
 | 
						|
 | 
						|
    @pytest.mark.parametrize(
 | 
						|
        ("src_root", "files"),
 | 
						|
        [
 | 
						|
            (".", {"setup.cfg": DALS(EXAMPLE_SETUPCFG)}),
 | 
						|
            (".", {"pyproject.toml": DALS(EXAMPLE_PYPROJECT)}),
 | 
						|
            ("src", {"setup.cfg": DALS(EXAMPLE_SETUPCFG)}),
 | 
						|
            ("src", {"pyproject.toml": DALS(EXAMPLE_PYPROJECT)}),
 | 
						|
            (
 | 
						|
                "src",
 | 
						|
                {
 | 
						|
                    "setup.cfg": DALS(EXAMPLE_SETUPCFG)
 | 
						|
                    + DALS(
 | 
						|
                        """
 | 
						|
                        packages = find:
 | 
						|
                        package_dir =
 | 
						|
                            =src
 | 
						|
 | 
						|
                        [options.packages.find]
 | 
						|
                        where = src
 | 
						|
                        """
 | 
						|
                    )
 | 
						|
                },
 | 
						|
            ),
 | 
						|
            (
 | 
						|
                "src",
 | 
						|
                {
 | 
						|
                    "pyproject.toml": DALS(EXAMPLE_PYPROJECT)
 | 
						|
                    + DALS(
 | 
						|
                        """
 | 
						|
                        [tool.setuptools]
 | 
						|
                        package-dir = {"" = "src"}
 | 
						|
                        """
 | 
						|
                    )
 | 
						|
                },
 | 
						|
            ),
 | 
						|
        ],
 | 
						|
    )
 | 
						|
    def test_include_package_data(self, tmp_path, src_root, files):
 | 
						|
        """
 | 
						|
        Make sure auto-discovery does not affect package include_package_data.
 | 
						|
        See issue #3196.
 | 
						|
        """
 | 
						|
        jaraco.path.build(files, prefix=str(tmp_path))
 | 
						|
        self._simulate_package_with_data_files(tmp_path, src_root)
 | 
						|
 | 
						|
        expected = {
 | 
						|
            os.path.normpath(f"{src_root}/proj/file1.txt").replace(os.sep, "/"),
 | 
						|
            os.path.normpath(f"{src_root}/proj/nested/file2.txt").replace(os.sep, "/"),
 | 
						|
        }
 | 
						|
 | 
						|
        _run_build(tmp_path)
 | 
						|
 | 
						|
        sdist_files = get_sdist_members(next(tmp_path.glob("dist/*.tar.gz")))
 | 
						|
        print("~~~~~ sdist_members ~~~~~")
 | 
						|
        print('\n'.join(sdist_files))
 | 
						|
        assert sdist_files >= expected
 | 
						|
 | 
						|
        wheel_files = get_wheel_members(next(tmp_path.glob("dist/*.whl")))
 | 
						|
        print("~~~~~ wheel_members ~~~~~")
 | 
						|
        print('\n'.join(wheel_files))
 | 
						|
        orig_files = {f.replace("src/", "").replace("lib/", "") for f in expected}
 | 
						|
        assert wheel_files >= orig_files
 | 
						|
 | 
						|
 | 
						|
def test_compatible_with_numpy_configuration(tmp_path):
 | 
						|
    files = [
 | 
						|
        "dir1/__init__.py",
 | 
						|
        "dir2/__init__.py",
 | 
						|
        "file.py",
 | 
						|
    ]
 | 
						|
    _populate_project_dir(tmp_path, files, {})
 | 
						|
    dist = Distribution({})
 | 
						|
    dist.configuration = object()
 | 
						|
    dist.set_defaults()
 | 
						|
    assert dist.py_modules is None
 | 
						|
    assert dist.packages is None
 | 
						|
 | 
						|
 | 
						|
def test_name_discovery_doesnt_break_cli(tmpdir_cwd):
 | 
						|
    jaraco.path.build({"pkg.py": ""})
 | 
						|
    dist = Distribution({})
 | 
						|
    dist.script_args = ["--name"]
 | 
						|
    dist.set_defaults()
 | 
						|
    dist.parse_command_line()  # <-- no exception should be raised here.
 | 
						|
    assert dist.get_name() == "pkg"
 | 
						|
 | 
						|
 | 
						|
def test_preserve_explicit_name_with_dynamic_version(tmpdir_cwd, monkeypatch):
 | 
						|
    """According to #3545 it seems that ``name`` discovery is running,
 | 
						|
    even when the project already explicitly sets it.
 | 
						|
    This seems to be related to parsing of dynamic versions (via ``attr`` directive),
 | 
						|
    which requires the auto-discovery of ``package_dir``.
 | 
						|
    """
 | 
						|
    files = {
 | 
						|
        "src": {
 | 
						|
            "pkg": {"__init__.py": "__version__ = 42\n"},
 | 
						|
        },
 | 
						|
        "pyproject.toml": DALS(
 | 
						|
            """
 | 
						|
            [project]
 | 
						|
            name = "myproj"  # purposefully different from package name
 | 
						|
            dynamic = ["version"]
 | 
						|
            [tool.setuptools.dynamic]
 | 
						|
            version = {"attr" = "pkg.__version__"}
 | 
						|
            """
 | 
						|
        ),
 | 
						|
    }
 | 
						|
    jaraco.path.build(files)
 | 
						|
    dist = Distribution({})
 | 
						|
    orig_analyse_name = dist.set_defaults.analyse_name
 | 
						|
 | 
						|
    def spy_analyse_name():
 | 
						|
        # We can check if name discovery was triggered by ensuring the original
 | 
						|
        # name remains instead of the package name.
 | 
						|
        orig_analyse_name()
 | 
						|
        assert dist.get_name() == "myproj"
 | 
						|
 | 
						|
    monkeypatch.setattr(dist.set_defaults, "analyse_name", spy_analyse_name)
 | 
						|
    dist.parse_config_files()
 | 
						|
    assert dist.get_version() == "42"
 | 
						|
    assert set(dist.packages) == {"pkg"}
 | 
						|
 | 
						|
 | 
						|
def _populate_project_dir(root, files, options):
 | 
						|
    # NOTE: Currently pypa/build will refuse to build the project if no
 | 
						|
    # `pyproject.toml` or `setup.py` is found. So it is impossible to do
 | 
						|
    # completely "config-less" projects.
 | 
						|
    basic = {
 | 
						|
        "setup.py": "import setuptools\nsetuptools.setup()",
 | 
						|
        "README.md": "# Example Package",
 | 
						|
        "LICENSE": "Copyright (c) 2018",
 | 
						|
    }
 | 
						|
    jaraco.path.build(basic, prefix=root)
 | 
						|
    _write_setupcfg(root, options)
 | 
						|
    paths = (root / f for f in files)
 | 
						|
    for path in paths:
 | 
						|
        path.parent.mkdir(exist_ok=True, parents=True)
 | 
						|
        path.touch()
 | 
						|
 | 
						|
 | 
						|
def _write_setupcfg(root, options):
 | 
						|
    if not options:
 | 
						|
        print("~~~~~ **NO** setup.cfg ~~~~~")
 | 
						|
        return
 | 
						|
    setupcfg = ConfigParser()
 | 
						|
    setupcfg.add_section("options")
 | 
						|
    for key, value in options.items():
 | 
						|
        if key == "packages.find":
 | 
						|
            setupcfg.add_section(f"options.{key}")
 | 
						|
            setupcfg[f"options.{key}"].update(value)
 | 
						|
        elif isinstance(value, list):
 | 
						|
            setupcfg["options"][key] = ", ".join(value)
 | 
						|
        elif isinstance(value, dict):
 | 
						|
            str_value = "\n".join(f"\t{k} = {v}" for k, v in value.items())
 | 
						|
            setupcfg["options"][key] = "\n" + str_value
 | 
						|
        else:
 | 
						|
            setupcfg["options"][key] = str(value)
 | 
						|
    with open(root / "setup.cfg", "w", encoding="utf-8") as f:
 | 
						|
        setupcfg.write(f)
 | 
						|
    print("~~~~~ setup.cfg ~~~~~")
 | 
						|
    print((root / "setup.cfg").read_text(encoding="utf-8"))
 | 
						|
 | 
						|
 | 
						|
def _run_build(path, *flags):
 | 
						|
    cmd = [sys.executable, "-m", "build", "--no-isolation", *flags, str(path)]
 | 
						|
    return run(cmd, env={'DISTUTILS_DEBUG': ''})
 | 
						|
 | 
						|
 | 
						|
def _get_dist(dist_path, attrs):
 | 
						|
    root = "/".join(os.path.split(dist_path))  # POSIX-style
 | 
						|
 | 
						|
    script = dist_path / 'setup.py'
 | 
						|
    if script.exists():
 | 
						|
        with Path(dist_path):
 | 
						|
            dist = cast(
 | 
						|
                Distribution,
 | 
						|
                distutils.core.run_setup("setup.py", {}, stop_after="init"),
 | 
						|
            )
 | 
						|
    else:
 | 
						|
        dist = Distribution(attrs)
 | 
						|
 | 
						|
    dist.src_root = root
 | 
						|
    dist.script_name = "setup.py"
 | 
						|
    with Path(dist_path):
 | 
						|
        dist.parse_config_files()
 | 
						|
 | 
						|
    dist.set_defaults()
 | 
						|
    return dist
 | 
						|
 | 
						|
 | 
						|
def _run_sdist_programatically(dist_path, attrs):
 | 
						|
    dist = _get_dist(dist_path, attrs)
 | 
						|
    cmd = sdist(dist)
 | 
						|
    cmd.ensure_finalized()
 | 
						|
    assert cmd.distribution.packages or cmd.distribution.py_modules
 | 
						|
 | 
						|
    with quiet(), Path(dist_path):
 | 
						|
        cmd.run()
 | 
						|
 | 
						|
    return dist, cmd
 |