# 🧩 Build System Integration ```{tip} See [`example/`](https://github.com/basnijholt/unidep/tree/main/example/) for working examples of using `unidep` with different build systems. ``` `unidep` seamlessly integrates with popular Python build systems to simplify dependency management in your projects. ## Local Dependencies in Monorepos Local dependencies are essential for monorepos and multi-package projects, allowing you to: - Share code between packages during development - Maintain separate releases for each package - Test changes across multiple packages simultaneously However, when building wheels for distribution, local paths create non-portable packages that only work on the original system. ## PyPI Alternatives for Local Dependencies UniDep solves this problem by letting you specify both local paths (for development) and PyPI packages (for distribution): ```yaml # requirements.yaml dependencies: - numpy - pandas local_dependencies: # Standard string format for local dependencies - ../shared-lib # Dictionary format with optional PyPI alternative for build-time - local: ../auth-lib pypi: company-auth-lib>=1.0 - local: ../utils pypi: company-utils~=2.0 use: pypi # see [Overriding Nested Vendor Copies](build-system-integration.md#overriding-nested-vendor-copies-with-use) ``` Or in `pyproject.toml`: ```toml [tool.unidep] dependencies = ["numpy", "pandas"] local_dependencies = [ # Standard string format for local dependencies "../shared-lib", # Dictionary format with optional PyPI alternative for build-time {local = "../auth-lib", pypi = "company-auth-lib>=1.0"}, {local = "../utils", pypi = "company-utils~=2.0", use = "pypi"}, ] ``` **How it works:** - **During development** (e.g., `unidep install` or `pip install -e .`): Uses local paths when they exist - **When building wheels**: PyPI alternatives (if specified) are used to create portable packages - The standard string format continues to work as always for local dependencies ```{tip} PyPI alternatives ensure your wheels are portable and can be installed anywhere, not just on the build system. Use the `use` field (see [Overriding Nested Vendor Copies](build-system-integration.md#overriding-nested-vendor-copies-with-use)) to control whether UniDep installs the local path, forces PyPI, or skips the entry entirely. ``` ## Overriding Nested Vendor Copies with `use` **The Problem:** When vendoring dependencies as git submodules, you often encounter conflicts where a submodule bundles its own copy of a dependency you also use, but at a different version. **The Solution:** Use `use: pypi` to force your PyPI package instead of the vendored copy, with automatic propagation to all nested references. ### Example: Override foo's bundled bar with your PyPI build Your project vendors `foo` as a submodule. Foo bundles `bar@1.0`, but you need `bar@2.0`: ``` project/ third_party/ foo/ # git submodule you don't control third_party/ bar/ # foo bundles bar@1.0 ``` **Solution with `use: pypi`:** ```yaml local_dependencies: - ./third_party/foo # Keep foo editable for development # Override: force YOUR PyPI build of bar - local: ./third_party/foo/third_party/bar pypi: my-bar>=2.0 use: pypi # Install from PyPI, skip local path ``` **What happens:** 1. `foo` stays local (editable for development) 2. `my-bar>=2.0` gets installed from PyPI (not foo's bundled v1.0) 3. **Propagates**: Every nested reference to `bar` uses your PyPI package 4. Works with `unidep install`, `unidep conda-lock`, all CLI commands This is the **key difference** from just using `pypi:` as a build-time fallback - `use: pypi` **forces the PyPI package during development** while keeping other local dependencies editable. --- ## All `use` values Tell UniDep what to **use** for each entry in `local_dependencies`: | `use` value | When to use | Installs from | Propagates override? | |------------|-------------|---------------|---------------------| | `local` *(default)* | Normal local development | Local path | - | | `pypi` | **Force PyPI** even when local exists | `pypi:` spec | Yes | | `skip` | Ignore this path entirely | Nothing | Yes | **Common patterns:** ```yaml local_dependencies: # Standard local development (default) - ../shared-lib # Force PyPI to override nested vendor copy - local: ./vendor/foo/nested/bar pypi: my-bar>=2.0 use: pypi # Skip a path without installing anything - local: ./deprecated-module use: skip ``` ```{note} **Precedence:** The `use` flag on the entry itself always wins. When UniDep encounters the same path in nested `local_dependencies`, it uses your override. Setting `UNIDEP_SKIP_LOCAL_DEPS=1` forces any effective `use: local` to behave like `pypi` (if specified) or `skip`, but does **not** override explicit `use: pypi` or `use: skip`. ``` ```{caution} If `use: pypi` is set but no `pypi:` requirement is provided, UniDep exits with a clear error so you can supply the missing spec. ``` ## Build System Behavior **Important differences between build backends:** - **Setuptools**: Builds wheels containing `file://` URLs with absolute paths. These wheels only work on the original system. - **Hatchling**: Rejects `file://` URLs by default, preventing non-portable wheels. To ensure portable wheels, you can use the `UNIDEP_SKIP_LOCAL_DEPS` environment variable: ```bash # Force use of PyPI alternatives even when local paths exist UNIDEP_SKIP_LOCAL_DEPS=1 python -m build # For hatch projects UNIDEP_SKIP_LOCAL_DEPS=1 hatch build # For uv build UNIDEP_SKIP_LOCAL_DEPS=1 uv build ``` ```{note} **When `UNIDEP_SKIP_LOCAL_DEPS=1` is set:** - Any effective `use: local` behaves as `use: pypi` (if a `pypi` spec exists) or `use: skip` - Explicit `use: pypi` and `use: skip` remain unchanged - Dependencies from local packages are still included (from their `requirements.yaml`/`pyproject.toml`) ``` ## Example packages Explore these installable [example](https://github.com/basnijholt/unidep/tree/main/example/) packages to understand how `unidep` integrates with different build tools and configurations: | Project | Build Tool | `pyproject.toml` | `requirements.yaml` | `setup.py` | | ---------------------------------------------------------- | ------------ | ---------------- | ------------------- | ---------- | | [`setup_py_project`](https://github.com/basnijholt/unidep/tree/main/example/setup_py_project) | `setuptools` | ✅ | ✅ | ✅ | | [`setuptools_project`](https://github.com/basnijholt/unidep/tree/main/example/setuptools_project) | `setuptools` | ✅ | ✅ | ❌ | | [`pyproject_toml_project`](https://github.com/basnijholt/unidep/tree/main/example/pyproject_toml_project) | `setuptools` | ✅ | ❌ | ❌ | | [`hatch_project`](https://github.com/basnijholt/unidep/tree/main/example/hatch_project) | `hatch` | ✅ | ✅ | ❌ | | [`hatch2_project`](https://github.com/basnijholt/unidep/tree/main/example/hatch2_project) | `hatch` | ✅ | ❌ | ❌ | ## Setuptools Integration For projects using `setuptools`, configure `unidep` in `pyproject.toml` and either specify dependencies in a `requirements.yaml` file or include them in `pyproject.toml` too. - **Using `pyproject.toml` only**: The `[project.dependencies]` field in `pyproject.toml` gets automatically populated from `requirements.yaml` or from the `[tool.unidep]` section in `pyproject.toml`. - **Using `setup.py`**: The `install_requires` field in `setup.py` automatically reflects dependencies specified in `requirements.yaml` or `pyproject.toml`. - **Wheel metadata**: UniDep also writes `unidep.json` into wheel metadata (`.dist-info`) so `unidep install "pkg==x.y"` can install Conda + pip dependencies directly from the published artifact. **Example `pyproject.toml` Configuration**: ```toml [build-system] build-backend = "setuptools.build_meta" requires = ["setuptools", "unidep"] [project] dynamic = ["dependencies"] ``` ## Hatchling Integration For projects managed with [Hatch](https://hatch.pypa.io/), `unidep` can be configured in `pyproject.toml` to automatically process the dependencies from `requirements.yaml` or from the `[tool.unidep]` section in `pyproject.toml`. For wheel builds, the UniDep build hook stores `unidep.json` in `.dist-info/extra_metadata`. **Example Configuration for Hatch**: ```toml [build-system] requires = ["hatchling", "unidep"] build-backend = "hatchling.build" [project] dynamic = ["dependencies"] # Additional project configurations [tool.hatch.metadata.hooks.unidep] # Enable the unidep plugin [tool.hatch.build.hooks.unidep] # Embed unidep.json in wheel metadata [tool.hatch.metadata] allow-direct-references = true [tool.unidep] # Your dependencies configuration ```