diff --git a/backend/src/hatchling/builders/config.py b/backend/src/hatchling/builders/config.py index fdec72e65..10e68c61a 100644 --- a/backend/src/hatchling/builders/config.py +++ b/backend/src/hatchling/builders/config.py @@ -40,6 +40,7 @@ def __init__(self, builder, root, plugin_name, build_config, target_config): # Common options self.__directory = None + self.__skip_excluded_dirs = None self.__ignore_vcs = None self.__only_packages = None self.__reproducible = None @@ -270,6 +271,26 @@ def directory(self): return self.__directory + @property + def skip_excluded_dirs(self): + if self.__skip_excluded_dirs is None: + if 'skip-excluded-dirs' in self.target_config: + skip_excluded_dirs = self.target_config['skip-excluded-dirs'] + if not isinstance(skip_excluded_dirs, bool): + raise TypeError( + 'Field `tool.hatch.build.targets.{}.skip-excluded-dirs` must be a boolean'.format( + self.plugin_name + ) + ) + else: + skip_excluded_dirs = self.build_config.get('skip-excluded-dirs', False) + if not isinstance(skip_excluded_dirs, bool): + raise TypeError('Field `tool.hatch.build.skip-excluded-dirs` must be a boolean') + + self.__skip_excluded_dirs = skip_excluded_dirs + + return self.__skip_excluded_dirs + @property def ignore_vcs(self): if self.__ignore_vcs is None: diff --git a/backend/src/hatchling/builders/plugin/interface.py b/backend/src/hatchling/builders/plugin/interface.py index c42865c0a..f7dff1e31 100644 --- a/backend/src/hatchling/builders/plugin/interface.py +++ b/backend/src/hatchling/builders/plugin/interface.py @@ -178,14 +178,17 @@ def recurse_project_files(self): if relative_path == '.': relative_path = '' - dirs[:] = sorted( - d - for d in dirs - # The trailing slash is necessary so e.g. `bar/` matches `foo/bar` - if not self.config.path_is_excluded('{}/'.format(os.path.join(relative_path, d))) - ) + if self.config.skip_excluded_dirs: + dirs[:] = sorted( + d + for d in dirs + # The trailing slash is necessary so e.g. `bar/` matches `foo/bar` + if not self.config.path_is_excluded('{}/'.format(os.path.join(relative_path, d))) + ) + else: + dirs.sort() - files = sorted(files) + files.sort() is_package = '__init__.py' in files for f in files: relative_file_path = os.path.join(relative_path, f) diff --git a/docs/config/build.md b/docs/config/build.md index 1ac85da8c..b1f395cc8 100644 --- a/docs/config/build.md +++ b/docs/config/build.md @@ -244,6 +244,27 @@ The [packages](#packages) option itself relies on sources. Defining `#!toml pack sources = ["src"] ``` +### Performance + +All encountered directories are traversed by default. To skip non-[artifact](#artifacts) directories that are excluded, set `skip-excluded-dirs` to `true`: + +=== ":octicons-file-code-16: pyproject.toml" + + ```toml + [tool.hatch.build] + skip-excluded-dirs = true + ``` + +=== ":octicons-file-code-16: hatch.toml" + + ```toml + [build] + skip-excluded-dirs = true + ``` + +!!! warning + This may result in not shipping desired files. For example, if you want to include the file `a/b/c.txt` but your [VCS ignores](#vcs) `a/b`, the file `c.txt` will not be seen because its parent directory will not be entered. In such cases you can use the [`force-include`](#explicit-selection) option. + ## Reproducible builds By default, [build targets](#build-targets) will build in a reproducible manner provided that they support that behavior. To disable this, set `reproducible` to `false`: diff --git a/docs/history.md b/docs/history.md index ad1119bd5..01b012a0a 100644 --- a/docs/history.md +++ b/docs/history.md @@ -44,6 +44,7 @@ This is the first stable release of Hatch v1, a complete rewrite. Enjoy! ***Added:*** +- Add `skip-excluded-dirs` build option - Allow build data to add additional project dependencies for `wheel` and `sdist` build targets - Add `force_include_editable` build data for the `wheel` build target - Update project metadata to reflect the adoption by PyPA @@ -51,6 +52,7 @@ This is the first stable release of Hatch v1, a complete rewrite. Enjoy! ***Fixed:*** - Properly use underscores for the name of `force_include` build data +- No longer greedily skip excluded directories by default ### [0.24.0](https://github.com/pypa/hatch/releases/tag/hatchling-v0.24.0) - 2022-04-28 ### {: #hatchling-v0.24.0 } diff --git a/hatch.toml b/hatch.toml index 795eabb5c..c147b395d 100644 --- a/hatch.toml +++ b/hatch.toml @@ -15,6 +15,12 @@ post-install-commands = [ full = "pytest -n auto --reruns 5 --reruns-delay 1 -r aR --cov-report=term-missing --cov-config=pyproject.toml --cov=src/hatch --cov=backend/src/hatchling --cov=tests {args:tests}" dev = "pytest -p no:randomly --no-cov {args:tests}" +[envs.test.overrides] +env.HERMETIC_TESTS.type = [ + { value = "container", if = ["true"] }, + "virtual", +] + [[envs.test.matrix]] python = ["37", "38", "39", "310"] diff --git a/tests/backend/builders/test_config.py b/tests/backend/builders/test_config.py index 8bea37374..ab25cd332 100644 --- a/tests/backend/builders/test_config.py +++ b/tests/backend/builders/test_config.py @@ -61,6 +61,56 @@ def test_absolute_path(self, isolation): assert builder.config.directory == absolute_path +class TestSkipExcludedDirs: + def test_default(self, isolation): + builder = BuilderInterface(str(isolation)) + + assert builder.config.skip_excluded_dirs is builder.config.skip_excluded_dirs is False + + def test_target(self, isolation): + config = {'tool': {'hatch': {'build': {'targets': {'foo': {'skip-excluded-dirs': True}}}}}} + builder = BuilderInterface(str(isolation), config=config) + builder.PLUGIN_NAME = 'foo' + + assert builder.config.skip_excluded_dirs is True + + def test_target_not_boolean(self, isolation): + config = {'tool': {'hatch': {'build': {'targets': {'foo': {'skip-excluded-dirs': 9000}}}}}} + builder = BuilderInterface(str(isolation), config=config) + builder.PLUGIN_NAME = 'foo' + + with pytest.raises( + TypeError, match='Field `tool.hatch.build.targets.foo.skip-excluded-dirs` must be a boolean' + ): + _ = builder.config.skip_excluded_dirs + + def test_global(self, isolation): + config = {'tool': {'hatch': {'build': {'skip-excluded-dirs': True}}}} + builder = BuilderInterface(str(isolation), config=config) + builder.PLUGIN_NAME = 'foo' + + assert builder.config.skip_excluded_dirs is True + + def test_global_not_boolean(self, isolation): + config = {'tool': {'hatch': {'build': {'skip-excluded-dirs': 9000}}}} + builder = BuilderInterface(str(isolation), config=config) + builder.PLUGIN_NAME = 'foo' + + with pytest.raises(TypeError, match='Field `tool.hatch.build.skip-excluded-dirs` must be a boolean'): + _ = builder.config.skip_excluded_dirs + + def test_target_overrides_global(self, isolation): + config = { + 'tool': { + 'hatch': {'build': {'skip-excluded-dirs': True, 'targets': {'foo': {'skip-excluded-dirs': False}}}} + } + } + builder = BuilderInterface(str(isolation), config=config) + builder.PLUGIN_NAME = 'foo' + + assert builder.config.skip_excluded_dirs is False + + class TestIgnoreVCS: def test_default(self, isolation): builder = BuilderInterface(str(isolation))