Wheel Target File Selection Errors in Hatchling
Overview
When assisting users with wheel builds in Hatchling, reference this guide to help them understand and resolve file selection errors. The wheel build target in Hatchling requires explicit or implicit file selection configuration. This document covers file selection errors, heuristics, and resolution strategies.
Main Error: No File Selection Options
Error Message (v1.19.0+)
ValueError: At least one file selection option must be defined, see:
https://hatch.pypa.io/latest/config/build/
Root Cause
The wheel builder cannot determine what files to include in the distribution when:
- No explicit file selection options are configured
- Default heuristics fail to identify package structure
- Project structure doesn't match common patterns
File Selection Options
1. Packages Option (Recommended)
[tool.hatch.build.targets.wheel]
packages = ["src/mypackage"] # Explicit package directory
Directory Structures:
# Standard layout
project/
├── pyproject.toml
├── src/
│ └── mypackage/
│ ├── __init__.py
│ └── module.py
# Flat layout
project/
├── pyproject.toml
├── mypackage/
│ ├── __init__.py
│ └── module.py
2. Include Option
[tool.hatch.build.targets.wheel]
include = [
"mypackage",
"data/*.json",
"configs/*.yaml"
]
3. Only-Include Option (v1.4.0+)
[tool.hatch.build.targets.wheel]
only-include = ["src/mypackage"]
# More targeted than packages option
4. Force-Include Option
[tool.hatch.build.targets.wheel]
force-include = {
"external/lib.so" = "mypackage/lib.so",
"../shared/data.json" = "mypackage/data.json"
}
Default Heuristics and When They Fail
Working Default Heuristics
Hatchling attempts to auto-detect in this order:
Single top-level package:
project/ ├── pyproject.toml ├── mypackage/ │ └── __init__.pySrc-layout:
project/ ├── pyproject.toml ├── src/ │ └── mypackage/ │ └── __init__.pySingle module:
project/ ├── pyproject.toml ├── mymodule.py
When Heuristics Fail
Multiple packages without clear selection:
project/ ├── pyproject.toml ├── package1/ │ └── __init__.py ├── package2/ │ └── __init__.pySolution:
[tool.hatch.build.targets.wheel] packages = ["package1", "package2"]Non-standard directory names:
project/ ├── pyproject.toml ├── core_lib/ # Not matching project name │ └── __init__.pySolution:
[tool.hatch.build.targets.wheel] packages = ["core_lib"]Mixed content types:
project/ ├── pyproject.toml ├── lib.py ├── scripts/ ├── data/Solution:
[tool.hatch.build.targets.wheel] include = [ "lib.py", "scripts/*.py", "data/*.json" ]
Advanced Selection Scenarios
Namespace Packages
[tool.hatch.build.targets.wheel]
packages = ["src/company", "src/company/project"]
# Include all namespace levels
Excluding Test Files
[tool.hatch.build.targets.wheel]
packages = ["src/mypackage"]
exclude = [
"**/tests/**",
"**/*_test.py",
"**/test_*.py"
]
Including Non-Python Files
[tool.hatch.build.targets.wheel]
include = [
"mypackage",
"mypackage/**/*.json",
"mypackage/**/*.yaml",
"mypackage/**/*.txt"
]
Build-Time File Generation
Error: Files Generated by Build Hooks
Problem: Build hooks generate files after selection validation
Solution 1: Use artifacts in build hook
# hatch_build.py
class CustomBuildHook(BuildHookInterface):
def initialize(self, version, build_data):
# Generate file
generated_file = Path("src/mypackage/version.py")
generated_file.write_text(f'__version__ = "{version}"')
# Add as artifact
build_data['artifacts'] = ['src/mypackage/version.py']
Solution 2: Force-include with build hook
[tool.hatch.build.targets.wheel]
force-include = {"generated/file.py" = "mypackage/file.py"}
[tool.hatch.build.hooks.custom]
# Hook generates the file before force-include runs
Bypass Selection (Metadata-Only Wheels)
Creating Empty Wheels (v1.20.0+)
[tool.hatch.build.targets.wheel]
bypass-selection = true
# Creates wheel with only metadata, no files
Use Cases:
- Metadata-only packages
- Virtual packages
- Namespace package roots
Debugging File Selection
1. List Files That Will Be Included
#!/usr/bin/env python3
"""Debug file selection for wheel building."""
from pathlib import Path
from hatchling.builders.wheel import WheelBuilder
from hatchling.metadata.core import ProjectMetadata
# Load metadata
metadata = ProjectMetadata(Path.cwd() / "pyproject.toml")
# Create wheel builder
builder = WheelBuilder(str(Path.cwd()), metadata=metadata)
# List files that will be included
for included_file in builder.recurse_included_files():
print(f"Include: {included_file.path}")
2. Validate Selection Configuration
#!/usr/bin/env python3
"""Validate wheel file selection configuration."""
import sys
import tomllib
from pathlib import Path
def validate_wheel_config():
with open("pyproject.toml", "rb") as f:
config = tomllib.load(f)
wheel_config = (
config.get("tool", {})
.get("hatch", {})
.get("build", {})
.get("targets", {})
.get("wheel", {})
)
# Check if any selection option is defined
selection_options = [
"packages",
"include",
"only-include",
"force-include",
"bypass-selection"
]
has_selection = any(
wheel_config.get(opt) for opt in selection_options
)
if not has_selection:
print("ERROR: No file selection options defined!")
print("Add one of:", ", ".join(selection_options))
sys.exit(1)
print("File selection configuration valid ✓")
if __name__ == "__main__":
validate_wheel_config()
Common Solutions by Project Type
Library with Source Layout
[tool.hatch.build.targets.wheel]
packages = ["src/mylibrary"]
Application with Multiple Packages
[tool.hatch.build.targets.wheel]
packages = [
"app",
"app_cli",
"app_web"
]
Data Science Project
[tool.hatch.build.targets.wheel]
include = [
"notebooks/*.ipynb",
"src/**/*.py",
"data/*.csv",
"models/*.pkl"
]
exclude = [
"data/raw/**",
"notebooks/scratch/**"
]
Plugin System
[tool.hatch.build.targets.wheel]
only-include = ["plugins"]
force-include = {
"plugin.yaml" = "plugins/plugin.yaml"
}
Migration from v1.18 to v1.19+
Old Behavior (v1.18)
- Silent fallback to empty wheel
- Implicit file inclusion
- No validation errors
New Behavior (v1.19+)
- Explicit error when no files selected
- Force-include paths must exist
- Clear error messages with documentation links
Migration Checklist
- ✓ Add explicit
packagesconfiguration - ✓ Verify all force-included paths exist
- ✓ Test build with
hatch build -t wheel - ✓ Validate wheel contents:
unzip -l dist/*.whl
Error Resolution Flowchart
Error: "At least one file selection option must be defined"
│
├─> Do you have a standard layout?
│ ├─> Yes: Add packages = ["src/packagename"]
│ └─> No: Continue
│
├─> Do you need specific files only?
│ ├─> Yes: Use only-include = [...]
│ └─> No: Continue
│
├─> Do you need pattern matching?
│ ├─> Yes: Use include = ["pattern/**/*.py"]
│ └─> No: Continue
│
├─> Do you need external files?
│ ├─> Yes: Use force-include = {...}
│ └─> No: Continue
│
└─> Metadata-only package?
└─> Yes: Set bypass-selection = true
Best Practices
- Always use explicit selection for reproducible builds
- Prefer
packagesfor standard Python packages - Use
only-includefor precise control - Validate configuration before publishing
- Test wheel contents after building
Version History
- v1.4.0: Added
only-includeoption - v1.19.0: Added file selection validation error
- v1.19.1: Improved error messages
- v1.20.0: Added
bypass-selectionoption