diff --git a/packages/a2aprotocol/pyproject.toml b/packages/a2aprotocol/pyproject.toml index f4fe233b7..5281cb85a 100644 --- a/packages/a2aprotocol/pyproject.toml +++ b/packages/a2aprotocol/pyproject.toml @@ -25,11 +25,13 @@ external = true [build-system] -requires = ["hatchling", "hatch-nbgv"] +requires = ["hatchling", "hatch-teams-build"] build-backend = "hatchling.build" [tool.hatch.version] -source = "nbgv-fallback" +source = "teams-build" + +[tool.hatch.metadata.hooks.teams-build] [tool.hatch.build.targets.wheel] packages = ["src/microsoft_teams", "src/microsoft"] diff --git a/packages/ai/pyproject.toml b/packages/ai/pyproject.toml index efac8f9e5..ab70727a1 100644 --- a/packages/ai/pyproject.toml +++ b/packages/ai/pyproject.toml @@ -13,11 +13,13 @@ dependencies = [ ] [build-system] -requires = ["hatchling", "hatch-nbgv"] +requires = ["hatchling", "hatch-teams-build"] build-backend = "hatchling.build" [tool.hatch.version] -source = "nbgv-fallback" +source = "teams-build" + +[tool.hatch.metadata.hooks.teams-build] [tool.hatch.build.targets.wheel] packages = ["src/microsoft_teams", "src/microsoft"] diff --git a/packages/api/pyproject.toml b/packages/api/pyproject.toml index 778cc532c..f7adce5db 100644 --- a/packages/api/pyproject.toml +++ b/packages/api/pyproject.toml @@ -25,11 +25,13 @@ dependencies = [ Homepage = "https://github.com/microsoft/teams.py/tree/main/packages/api/src/microsoft/teams/api" [build-system] -requires = ["hatchling", "hatch-nbgv"] +requires = ["hatchling", "hatch-teams-build"] build-backend = "hatchling.build" [tool.hatch.version] -source = "nbgv-fallback" +source = "teams-build" + +[tool.hatch.metadata.hooks.teams-build] [dependency-groups] dev = [ diff --git a/packages/apps/pyproject.toml b/packages/apps/pyproject.toml index 431adb040..033439b3f 100644 --- a/packages/apps/pyproject.toml +++ b/packages/apps/pyproject.toml @@ -35,11 +35,13 @@ test = [ ] [build-system] -requires = ["hatchling", "hatch-nbgv"] +requires = ["hatchling", "hatch-teams-build"] build-backend = "hatchling.build" [tool.hatch.version] -source = "nbgv-fallback" +source = "teams-build" + +[tool.hatch.metadata.hooks.teams-build] [tool.hatch.build.targets.wheel] packages = ["src/microsoft_teams", "src/microsoft"] diff --git a/packages/botbuilder/pyproject.toml b/packages/botbuilder/pyproject.toml index f0f6c49f2..62c5bf9f3 100644 --- a/packages/botbuilder/pyproject.toml +++ b/packages/botbuilder/pyproject.toml @@ -21,11 +21,13 @@ Homepage = "https://github.com/microsoft/teams.py/tree/main/packages/botbuilder/ [build-system] -requires = ["hatchling", "hatch-nbgv"] +requires = ["hatchling", "hatch-teams-build"] build-backend = "hatchling.build" [tool.hatch.version] -source = "nbgv-fallback" +source = "teams-build" + +[tool.hatch.metadata.hooks.teams-build] [tool.hatch.build.targets.wheel] packages = ["src/microsoft_teams", "src/microsoft"] diff --git a/packages/cards/pyproject.toml b/packages/cards/pyproject.toml index e38bfd4a0..fb023b243 100644 --- a/packages/cards/pyproject.toml +++ b/packages/cards/pyproject.toml @@ -21,11 +21,11 @@ dev = [ Homepage = "https://github.com/microsoft/teams.py/tree/main/packages/cards/src/microsoft/teams/cards" [build-system] -requires = ["hatchling", "hatch-nbgv"] +requires = ["hatchling", "hatch-teams-build"] build-backend = "hatchling.build" [tool.hatch.version] -source = "nbgv-fallback" +source = "teams-build" [tool.hatch.build.targets.wheel] packages = ["src/microsoft_teams", "src/microsoft"] diff --git a/packages/common/pyproject.toml b/packages/common/pyproject.toml index aedee67b9..839eb3ee8 100644 --- a/packages/common/pyproject.toml +++ b/packages/common/pyproject.toml @@ -27,11 +27,11 @@ Homepage = "https://github.com/microsoft/teams.py/tree/main/packages/common/src/ [build-system] -requires = ["hatchling", "hatch-nbgv"] +requires = ["hatchling", "hatch-teams-build"] build-backend = "hatchling.build" [tool.hatch.version] -source = "nbgv-fallback" +source = "teams-build" [tool.hatch.build.targets.wheel] packages = ["src/microsoft_teams", "src/microsoft"] diff --git a/packages/devtools/pyproject.toml b/packages/devtools/pyproject.toml index d5fe8d399..121f973db 100644 --- a/packages/devtools/pyproject.toml +++ b/packages/devtools/pyproject.toml @@ -25,11 +25,13 @@ Homepage = "https://github.com/microsoft/teams.py/tree/main/packages/devtools/sr [build-system] -requires = ["hatchling", "hatch-nbgv"] +requires = ["hatchling", "hatch-teams-build"] build-backend = "hatchling.build" [tool.hatch.version] -source = "nbgv-fallback" +source = "teams-build" + +[tool.hatch.metadata.hooks.teams-build] [tool.hatch.build.targets.wheel] packages = ["src/microsoft_teams", "src/microsoft"] diff --git a/packages/graph/pyproject.toml b/packages/graph/pyproject.toml index 14477b766..f67d702cb 100644 --- a/packages/graph/pyproject.toml +++ b/packages/graph/pyproject.toml @@ -26,11 +26,13 @@ testpaths = ["tests"] python_paths = ["src"] [build-system] -requires = ["hatchling", "hatch-nbgv"] +requires = ["hatchling", "hatch-teams-build"] build-backend = "hatchling.build" [tool.hatch.version] -source = "nbgv-fallback" +source = "teams-build" + +[tool.hatch.metadata.hooks.teams-build] [tool.hatch.build.targets.wheel] packages = ["src/microsoft_teams", "src/microsoft"] diff --git a/packages/mcpplugin/pyproject.toml b/packages/mcpplugin/pyproject.toml index 8bd48b032..1e07579c5 100644 --- a/packages/mcpplugin/pyproject.toml +++ b/packages/mcpplugin/pyproject.toml @@ -20,11 +20,13 @@ dependencies = [ external = true [build-system] -requires = ["hatchling", "hatch-nbgv"] +requires = ["hatchling", "hatch-teams-build"] build-backend = "hatchling.build" [tool.hatch.version] -source = "nbgv-fallback" +source = "teams-build" + +[tool.hatch.metadata.hooks.teams-build] [tool.hatch.build.targets.wheel] packages = ["src/microsoft_teams", "src/microsoft"] diff --git a/packages/openai/pyproject.toml b/packages/openai/pyproject.toml index a0dee43a3..064a2974a 100644 --- a/packages/openai/pyproject.toml +++ b/packages/openai/pyproject.toml @@ -16,11 +16,13 @@ dependencies = [ ] [build-system] -requires = ["hatchling", "hatch-nbgv"] +requires = ["hatchling", "hatch-teams-build"] build-backend = "hatchling.build" [tool.hatch.version] -source = "nbgv-fallback" +source = "teams-build" + +[tool.hatch.metadata.hooks.teams-build] [tool.hatch.build.targets.wheel] packages = ["src/microsoft_teams", "src/microsoft"] diff --git a/pyproject.toml b/pyproject.toml index 3b4d4f0e1..fb8ddc0f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ "microsoft-teams-mcpplugin" = { workspace = true } "microsoft-teams-a2a" = { workspace = true } "microsoft-teams-botbuilder" = { workspace = true } -"hatch-nbgv" = { path = "tools/hatch-nbgv" } +"hatch-teams-build" = { path = "tools/hatch-teams-build" } [tool.uv.workspace] members = ["packages/*", "examples/*"] @@ -31,7 +31,7 @@ dev = [ "pytest-cov>=6.0.0", ] release = [ - "hatch-nbgv", + "hatch-teams-build", ] [tool.ruff] @@ -55,6 +55,7 @@ select = ["E", "F", "W", "B", "Q", "I", "ASYNC"] [tool.pytest.ini_options] asyncio_mode = "auto" +testpaths = ["packages"] [tool.poe.tasks] fmt = "ruff format" diff --git a/templates/package/cookiecutter.json b/templates/package/cookiecutter.json index bf1400e79..7a37a194f 100644 --- a/templates/package/cookiecutter.json +++ b/templates/package/cookiecutter.json @@ -1,7 +1,6 @@ { "pypi_package_name": "subpackage-name", "project_short_description": "project description", - "version": "0.0.1-alpha.1", "core": true, "__prompts__": { "core": "Core package? (I.e. not an external integration?)" diff --git a/templates/package/{{cookiecutter.pypi_package_name}}/pyproject.toml b/templates/package/{{cookiecutter.pypi_package_name}}/pyproject.toml index eb00f7fd5..95172b4ae 100644 --- a/templates/package/{{cookiecutter.pypi_package_name}}/pyproject.toml +++ b/templates/package/{{cookiecutter.pypi_package_name}}/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "{{cookiecutter.pypi_package_name}}" -version = "{{cookiecutter.version}}" +dynamic = ["version"] description = "{{cookiecutter.project_short_description}}" authors = [{ name = "Microsoft", email = "TeamsAISDKFeedback@microsoft.com" }] readme = "README.md" @@ -15,9 +15,14 @@ external = true {% endif %} [build-system] -requires = ["hatchling"] +requires = ["hatchling", "hatch-teams-build"] build-backend = "hatchling.build" +[tool.hatch.version] +source = "teams-build" + +[tool.hatch.metadata.hooks.teams-build] + [tool.hatch.build.targets.wheel] packages = ["src/microsoft_teams"] diff --git a/tools/hatch-nbgv/src/hatch_nbgv/__init__.py b/tools/hatch-nbgv/src/hatch_nbgv/__init__.py deleted file mode 100644 index 488333b3e..000000000 --- a/tools/hatch-nbgv/src/hatch_nbgv/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the MIT License. -""" - -from hatch_nbgv.version_source import NbgvVersionSource - -__all__ = ["NbgvVersionSource"] diff --git a/tools/hatch-nbgv/src/hatch_nbgv/hooks.py b/tools/hatch-nbgv/src/hatch_nbgv/hooks.py deleted file mode 100644 index 5da547375..000000000 --- a/tools/hatch-nbgv/src/hatch_nbgv/hooks.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the MIT License. -""" - -from hatchling.plugin import hookimpl - -from hatch_nbgv.version_source import NbgvVersionSource - - -@hookimpl -def hatch_register_version_source(): - return NbgvVersionSource diff --git a/tools/hatch-teams-build/README.md b/tools/hatch-teams-build/README.md new file mode 100644 index 000000000..002a6988f --- /dev/null +++ b/tools/hatch-teams-build/README.md @@ -0,0 +1,46 @@ +# hatch-teams-build + +Hatchling plugin for the Teams Python SDK. Provides two build-time hooks: + +## Version Source (`teams-build`) + +Wraps [nbgv-python](https://pypi.org/project/nbgv-python/) to resolve package versions from git history via [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning). Falls back to `0.0.0` when the `nbgv` CLI is not installed, so local development works without .NET SDK. Set `NBGV_REQUIRED=1` to make the missing CLI a hard error (used in CI). + +```toml +[tool.hatch.version] +source = "teams-build" +``` + +## Metadata Hook (`teams-build`) + +Rewrites bare `microsoft-teams-*` dependencies to include `>=` at build time. This ensures published wheels enforce compatible sibling package versions. + +```toml +[tool.hatch.metadata.hooks.teams-build] +``` + +**Example:** if the current nbgv version is `2.0.0a49`, a dependency listed as `microsoft-teams-common` becomes `microsoft-teams-common>=2.0.0a49` in the built wheel metadata. + +When nbgv is unavailable (fallback `0.0.0`), the hook skips rewriting entirely — dependencies stay bare, which is correct for local development where UV workspace resolution handles everything. + +## Usage + +Each package pyproject.toml needs: + +```toml +[build-system] +requires = ["hatchling", "hatch-teams-build"] +build-backend = "hatchling.build" + +[tool.hatch.version] +source = "teams-build" + +[tool.hatch.metadata.hooks.teams-build] +``` + +## Verification + +```bash +uv build packages/apps --sdist +# Inspect PKG-INFO — workspace deps should show >= +``` diff --git a/tools/hatch-nbgv/pyproject.toml b/tools/hatch-teams-build/pyproject.toml similarity index 62% rename from tools/hatch-nbgv/pyproject.toml rename to tools/hatch-teams-build/pyproject.toml index a396d1ef8..0eb1dfb16 100644 --- a/tools/hatch-nbgv/pyproject.toml +++ b/tools/hatch-teams-build/pyproject.toml @@ -1,7 +1,7 @@ [project] -name = "hatch-nbgv" +name = "hatch-teams-build" version = "0.1.0" -description = "Hatchling version source plugin wrapping nbgv-python with graceful fallback" +description = "Hatchling plugin for Teams SDK: nbgv version source with graceful fallback and workspace dependency versioning" authors = [{ name = "Microsoft", email = "TeamsAISDKFeedback@microsoft.com" }] requires-python = ">=3.12" license = "MIT" @@ -12,10 +12,10 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project.entry-points.hatch] -hatch-nbgv = "hatch_nbgv.hooks" +hatch-teams-build = "hatch_teams_build.hooks" [tool.hatch.build.targets.wheel] -packages = ["src/hatch_nbgv"] +packages = ["src/hatch_teams_build"] [tool.hatch.build.targets.sdist] include = ["src"] diff --git a/tools/hatch-teams-build/src/hatch_teams_build/__init__.py b/tools/hatch-teams-build/src/hatch_teams_build/__init__.py new file mode 100644 index 000000000..c021c201b --- /dev/null +++ b/tools/hatch-teams-build/src/hatch_teams_build/__init__.py @@ -0,0 +1,9 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +from hatch_teams_build.metadata_hook import TeamsBuildMetadataHook +from hatch_teams_build.version_source import TeamsBuildVersionSource + +__all__ = ["TeamsBuildMetadataHook", "TeamsBuildVersionSource"] diff --git a/tools/hatch-teams-build/src/hatch_teams_build/hooks.py b/tools/hatch-teams-build/src/hatch_teams_build/hooks.py new file mode 100644 index 000000000..b025832c8 --- /dev/null +++ b/tools/hatch-teams-build/src/hatch_teams_build/hooks.py @@ -0,0 +1,19 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +from hatchling.plugin import hookimpl + +from hatch_teams_build.metadata_hook import TeamsBuildMetadataHook +from hatch_teams_build.version_source import TeamsBuildVersionSource + + +@hookimpl +def hatch_register_version_source(): + return TeamsBuildVersionSource + + +@hookimpl +def hatch_register_metadata_hook(): + return TeamsBuildMetadataHook diff --git a/tools/hatch-teams-build/src/hatch_teams_build/metadata_hook.py b/tools/hatch-teams-build/src/hatch_teams_build/metadata_hook.py new file mode 100644 index 000000000..ae6ebc822 --- /dev/null +++ b/tools/hatch-teams-build/src/hatch_teams_build/metadata_hook.py @@ -0,0 +1,64 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +import logging +import os +import re +from typing import Any + +from hatchling.metadata.plugin.interface import MetadataHookInterface + +from hatch_teams_build.version_source import FALLBACK_VERSION, TeamsBuildVersionSource + +logger = logging.getLogger(__name__) + +_WORKSPACE_PREFIX = "microsoft-teams-" + +# Matches a bare package name with no version specifier (e.g. "microsoft-teams-common") +# but not one that already has a constraint (e.g. "microsoft-teams-common>=1.0") +_BARE_DEP_RE = re.compile(r"^([a-zA-Z0-9_-]+)$") + + +def _strip_local(version: str) -> str: + """Strip PEP 440 local segment (e.g. +gf235eb85) since it's not allowed in dependency specifiers.""" + return version.split("+")[0] + + +def _add_version_constraint(dep: str, version: str) -> str: + """Add >={version} to a bare microsoft-teams-* dependency.""" + match = _BARE_DEP_RE.match(dep.strip()) + if match and match.group(1).startswith(_WORKSPACE_PREFIX): + return f"{match.group(1)}>={version}" + return dep + + +class TeamsBuildMetadataHook(MetadataHookInterface): + PLUGIN_NAME = "teams-build" + + def update(self, metadata: dict[str, Any]) -> None: + source = TeamsBuildVersionSource(self.root, self.config) + version = _strip_local(source.get_version_data()["version"]) + + fallback = _strip_local(FALLBACK_VERSION) + if version == fallback: + if os.environ.get("NBGV_REQUIRED"): + raise RuntimeError( + f"teams-build metadata hook: nbgv resolved to {fallback} but NBGV_REQUIRED is set. " + "Workspace dependency versions would be incorrect. " + "Ensure nbgv CLI is installed and available on PATH." + ) + logger.warning( + "teams-build metadata hook: skipping dep rewrite (nbgv unavailable, version is %s)", fallback + ) + return + + logger.debug("teams-build metadata hook: rewriting workspace deps with >=%s", version) + + if "dependencies" in metadata: + metadata["dependencies"] = [_add_version_constraint(dep, version) for dep in metadata["dependencies"]] + + if "optional-dependencies" in metadata: + for extra, deps in metadata["optional-dependencies"].items(): + metadata["optional-dependencies"][extra] = [_add_version_constraint(dep, version) for dep in deps] diff --git a/tools/hatch-nbgv/src/hatch_nbgv/version_source.py b/tools/hatch-teams-build/src/hatch_teams_build/version_source.py similarity index 87% rename from tools/hatch-nbgv/src/hatch_nbgv/version_source.py rename to tools/hatch-teams-build/src/hatch_teams_build/version_source.py index 4c363e816..d7f3623a5 100644 --- a/tools/hatch-nbgv/src/hatch_nbgv/version_source.py +++ b/tools/hatch-teams-build/src/hatch_teams_build/version_source.py @@ -14,11 +14,11 @@ logger = logging.getLogger(__name__) -_FALLBACK_VERSION = "0.0.0" +FALLBACK_VERSION = "0.0.0" -class NbgvVersionSource(VersionSourceInterface): - PLUGIN_NAME = "nbgv-fallback" +class TeamsBuildVersionSource(VersionSourceInterface): + PLUGIN_NAME = "teams-build" def get_version_data(self) -> dict[str, Any]: try: @@ -34,8 +34,8 @@ def get_version_data(self) -> dict[str, Any]: UserWarning, stacklevel=1, ) - logger.warning("nbgv not found, falling back to version %s", _FALLBACK_VERSION) - return {"version": _FALLBACK_VERSION} + logger.warning("nbgv not found, falling back to version %s", FALLBACK_VERSION) + return {"version": FALLBACK_VERSION} def set_version(self, version: str, version_data: dict[str, Any]) -> None: raise NotImplementedError("Version is managed by nbgv via version.json — not settable.") diff --git a/uv.lock b/uv.lock index 3770ac76e..16551009e 100644 --- a/uv.lock +++ b/uv.lock @@ -50,7 +50,7 @@ dev = [ { name = "pytest-cov", specifier = ">=6.0.0" }, { name = "ruff", specifier = ">=0.11.13" }, ] -release = [{ name = "hatch-nbgv", directory = "tools/hatch-nbgv" }] +release = [{ name = "hatch-teams-build", directory = "tools/hatch-teams-build" }] [[package]] name = "a2a" @@ -1168,9 +1168,9 @@ wheels = [ ] [[package]] -name = "hatch-nbgv" +name = "hatch-teams-build" version = "0.1.0" -source = { directory = "tools/hatch-nbgv" } +source = { directory = "tools/hatch-teams-build" } dependencies = [ { name = "hatchling" }, { name = "nbgv-python" },