diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 245186ccc7..9586b9cf85 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -179,6 +179,15 @@ jobs: if: matrix.platform == 'ubuntu-latest' run: ./build.sh fable-library --python + # PEP 561 requires the marker inside the package directory. CopyStage filters + # by extension, so it is easy to drop again; without it mypy refuses to read + # the core/*.pyi stubs at all. + - name: PEP 561 marker survives staging + if: matrix.platform == 'ubuntu-latest' + run: | + test -f temp/fable-library-py/fable_library/py.typed \ + || { echo "::error::fable_library/py.typed missing from staged package; stubs would be inert for mypy"; exit 1; } + - name: Fable Library - Python (Windows) if: matrix.platform == 'windows-latest' run: .\build.bat fable-library --python diff --git a/src/Fable.Build/FableLibrary/Python.fs b/src/Fable.Build/FableLibrary/Python.fs index e36f528206..d2e1c7c49c 100644 --- a/src/Fable.Build/FableLibrary/Python.fs +++ b/src/Fable.Build/FableLibrary/Python.fs @@ -31,6 +31,12 @@ type BuildFableLibraryPython(?skipCore: bool, ?postFableBuildStage: unit -> unit Directory.GetFiles(this.LibraryDir, "*") |> Shell.copyFiles this.BuildDir Directory.GetFiles(this.SourceDir, "*.py") |> Shell.copyFiles this.OutDir + // PEP 561 marker. It has to sit *inside* the package directory for type + // checkers to read the stubs at all, and the `*.py` filter above drops it -- + // which is why every published wheel so far has shipped `core/*.pyi` that no + // consumer could see. + [ Path.Combine(this.SourceDir, "py.typed") ] |> Shell.copyFiles this.OutDir + // Python extension modules Directory.GetFiles(Path.Combine(this.SourceDir, "core"), "*") |> Shell.copyFiles (Path.Combine(this.OutDir, "core")) diff --git a/src/fable-library-py/py.typed b/src/fable-library-py/py.typed deleted file mode 100644 index 4882510100..0000000000 --- a/src/fable-library-py/py.typed +++ /dev/null @@ -1 +0,0 @@ -# PEP-0561 -- https://www.python.org/dev/peps/pep-0561/ diff --git a/src/fable-library-py/pyproject.toml b/src/fable-library-py/pyproject.toml index 10c5d2f596..ba3499a1e6 100644 --- a/src/fable-library-py/pyproject.toml +++ b/src/fable-library-py/pyproject.toml @@ -23,20 +23,32 @@ dev = [ "pydantic>=2.13.4", ] -[tool.hatch.build.targets.sdist] -include = ["./fable_library"] - -[tool.hatch.build.targets.wheel] -include = ["./fable_library"] - -[tool.hatch.build.targets.wheel.sources] -"./fable_library" = "fable_library" +# Note: there is deliberately no [tool.hatch.*] config here. `build-backend` below +# is maturin, so hatch settings would be dead config that merely *looks* like it +# governs what ships -- which is a good way for a packaging bug to go unnoticed. [tool.maturin] python-source = "." # Look at project root module-name = "fable_library.core._core" # Full module path features = ["pyo3/extension-module"] +# `Fable.Library.Python.fsproj` lives inside the package directory, so `dotnet build` +# drops MSBuild output into `fable_library/obj/`. With `python-source = "."` maturin +# would package that wholesale, and one of those paths contains a comma +# (`.NETCoreApp,Version=v10.0.AssemblyAttributes.fs`) -- RECORD is CSV, so the +# resulting wheel is rejected by `uv pip install` as malformed. `pip` is lenient +# enough to survive it, which makes the failure look intermittent. +# +# None of this shows up in a clean CI checkout, only for contributors who have run +# `dotnet build`. Excluding here fixes it regardless of build order; deleting `obj/` +# in the build script would not, since it regenerates on the next build. +exclude = [ + "fable_library/obj/**/*", + "**/__pycache__/**/*", + "fable_library/*.fs", + "fable_library/*.fsproj", +] + [tool.ty.environment] python-version = "3.12" @@ -56,5 +68,5 @@ testpaths = ["tests", "tests-benchmark"] addopts = "--benchmark-columns=min,max,mean,stddev,iqr" [build-system] -requires = ["hatchling", "maturin>=1.5,<2.0"] +requires = ["maturin>=1.5,<2.0"] build-backend = "maturin"