diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 12a48d959..de2da6f2a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -148,9 +148,6 @@ jobs: with: dotnet-version: "10.0.x" - - name: Setup dotnet tools - run: dotnet tool restore - - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: diff --git a/.github/workflows/python-type-checking.yml b/.github/workflows/python-type-checking.yml new file mode 100644 index 000000000..fdb3cb049 --- /dev/null +++ b/.github/workflows/python-type-checking.yml @@ -0,0 +1,106 @@ +name: Python Type Checking + +on: + pull_request: + branches: [main] + paths: + - 'src/Fable.Transforms/Python/**' + - 'src/fable-library-py/**' + - 'tests/Python/**' + - 'pyrightconfig.ci.json' + - 'pyproject.toml' + +permissions: + contents: read + pull-requests: write + +jobs: + pyright: + runs-on: ubuntu-latest + env: + UV_LINK_MODE: copy + + steps: + - uses: actions/checkout@v5 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: "10.0.x" + + - name: Setup dotnet tools + run: dotnet tool restore + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install uv + run: | + pipx install uv + pipx install maturin + + - name: Build Python (compile only) + run: ./build.sh test python --compile-only + + - name: Run Pyright (standard mode) + id: pyright + continue-on-error: true + run: | + if uv run pyright --project pyrightconfig.ci.json temp/tests/Python/ temp/fable-library-py/; then + echo "result=:white_check_mark: All non-excluded files pass" >> $GITHUB_OUTPUT + echo "passed=true" >> $GITHUB_OUTPUT + else + echo "result=:x: Type errors found in non-excluded files" >> $GITHUB_OUTPUT + echo "passed=false" >> $GITHUB_OUTPUT + fi + + - name: Get excluded files + id: excluded + run: | + COUNT=$(grep -c '"temp/' pyrightconfig.ci.json || echo "0") + echo "count=$COUNT" >> $GITHUB_OUTPUT + FILES=$(grep '"temp/' pyrightconfig.ci.json | sed 's/.*"temp/temp/' | sed 's/".*//' | sort) + echo "files<> $GITHUB_OUTPUT + echo "$FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Find existing comment + uses: peter-evans/find-comment@v3 + id: find-comment + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Python Type Checking + + - name: Create or update PR comment + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + edit-mode: replace + body: | + ## Python Type Checking (Pyright) Results + + ${{ steps.pyright.outputs.result }} + + | Metric | Value | + | -------------- | ----------------------------------- | + | Excluded files | ${{ steps.excluded.outputs.count }} | + +
+ Files excluded from type checking + + These files have known type errors and are excluded from CI. Remove from `pyrightconfig.ci.json` as errors are fixed. + + ``` + ${{ steps.excluded.outputs.files }} + ``` + +
+ + - name: Fail if type errors + if: false # Temporarily disabled + # if: steps.pyright.outputs.passed == 'false' + run: exit 1 diff --git a/pyrightconfig.ci.json b/pyrightconfig.ci.json new file mode 100644 index 000000000..6d9720c74 --- /dev/null +++ b/pyrightconfig.ci.json @@ -0,0 +1,13 @@ +{ + "extends": "./pyrightconfig.json", + "exclude": [ + "**/.venv/**", + "**/node_modules/**", + "temp/fable-library-py/fable_library/list.py", + "temp/tests/Python/test_applicative.py", + "temp/tests/Python/test_map.py", + "temp/tests/Python/test_misc.py", + "temp/tests/Python/test_type.py", + "temp/tests/Python/fable_modules/thoth_json_python/encode.py" + ] +} diff --git a/src/Fable.Build/FableLibrary/Python.fs b/src/Fable.Build/FableLibrary/Python.fs index ca483b2c3..0d079cc6b 100644 --- a/src/Fable.Build/FableLibrary/Python.fs +++ b/src/Fable.Build/FableLibrary/Python.fs @@ -6,7 +6,7 @@ open Build.Utils open SimpleExec open BlackFox.CommandLine -type BuildFableLibraryPython() = +type BuildFableLibraryPython(?skipCore: bool) = inherit BuildFableLibrary( language = "python", @@ -16,6 +16,8 @@ type BuildFableLibraryPython() = outDir = Path.Combine("temp", "fable-library-py", "fable_library") ) + let skipCore = defaultArg skipCore false + override this.CopyStage() = // Copy all Python/F# files to the build directory Directory.GetFiles(this.LibraryDir, "*") |> Shell.copyFiles this.BuildDir @@ -33,7 +35,11 @@ type BuildFableLibraryPython() = override this.PostFableBuildStage() = // Install the python dependencies at the root of the project Command.Run("uv", "sync", this.BuildDir) // Maturin needs a local virtual environment - Command.Run("uv", "run maturin develop --release", this.BuildDir) + + if skipCore then + printfn "Skipping fable-library-core (Rust) build" + else + Command.Run("uv", "run maturin develop --release", this.BuildDir) // Fix issues with Fable .fsproj not supporting links, so we need to copy the // files ourself to the output directory @@ -44,7 +50,7 @@ type BuildFableLibraryPython() = Shell.deleteDir (this.BuildDir "fable_library/fable-library-ts") - // Run Ruff linter checking import sorting and fix any issues - Command.Run("uv", $"run ruff check --select I --fix {this.BuildDir}") + // Run Ruff linter checking import sorting, removing unused imports, and fix any issues + Command.Run("uv", $"run ruff check --select I,F401 --fix {this.BuildDir}") // Run Ruff formatter on all generated files Command.Run("uv", $"run ruff format {this.BuildDir}") diff --git a/src/Fable.Build/Main.fs b/src/Fable.Build/Main.fs index e79d8c107..a8d79873c 100644 --- a/src/Fable.Build/Main.fs +++ b/src/Fable.Build/Main.fs @@ -63,7 +63,8 @@ Available commands: --threaded Compile and run the tests with the threaded runtime Options for Python: - --typing Run type checking with Pyright and show the summary + --type-check Run type checking on the generated code with Pyright + --format Format the code generated code with Ruff formatter standalone Compile standalone + worker version of Fable running on top of of Node.js diff --git a/src/Fable.Build/Quicktest/Python.fs b/src/Fable.Build/Quicktest/Python.fs index dad00b8a5..8234fc182 100644 --- a/src/Fable.Build/Quicktest/Python.fs +++ b/src/Fable.Build/Quicktest/Python.fs @@ -10,10 +10,12 @@ open Build.Utils let private fableLibraryBuildDir = Path.Resolve("temp", "fable-library-py") let handle (args: string list) = + let skipFableLibraryCore = args |> List.contains "--skip-fable-library-core" + // Install local fable-library as editable package for testing // This ensures quicktest uses the locally built version, not PyPI if not (args |> List.contains "--skip-fable-library") then - BuildFableLibraryPython().Run(false) + BuildFableLibraryPython(skipCore = skipFableLibraryCore).Run(false) // Install fable-library in editable mode Command.Run("uv", $"pip install -e {fableLibraryBuildDir}") diff --git a/src/Fable.Build/Test/Python.fs b/src/Fable.Build/Test/Python.fs index 32b2bef5b..6a82842c1 100644 --- a/src/Fable.Build/Test/Python.fs +++ b/src/Fable.Build/Test/Python.fs @@ -12,12 +12,13 @@ let private fableLibraryBuildDir = Path.Resolve("temp", "fable-library-py") let handle (args: string list) = let skipFableLibrary = args |> List.contains "--skip-fable-library" + let skipFableLibraryCore = args |> List.contains "--skip-fable-library-core" let isWatch = args |> List.contains "--watch" let noDotnet = args |> List.contains "--no-dotnet" - let runTyping = args |> List.contains "--typing" + let runTyping = args |> List.contains "--type-check" let runFormat = args |> List.contains "--format" - BuildFableLibraryPython().Run(skipFableLibrary) + BuildFableLibraryPython(skipCore = skipFableLibraryCore).Run(skipFableLibrary) Directory.clean buildDir @@ -40,7 +41,7 @@ let handle (args: string list) = if isWatch then let ruffCmd = if runFormat then - $"uv run ruff check --select I --fix {buildDir} && uv run ruff format {buildDir} && " + $"uv run ruff check --select I,F401 --fix {buildDir} && uv run ruff format {buildDir} && " else "" @@ -71,8 +72,8 @@ let handle (args: string list) = Command.Fable(fableArgs, workingDirectory = buildDir) if runFormat then - // Run Ruff linter checking import sorting and fix any issues - Command.Run("uv", $"run ruff check --select I --fix {buildDir}") + // Run Ruff linter checking import sorting and fix any issues, and remove unused imports + Command.Run("uv", $"run ruff check --select I,F401 --fix {buildDir}") // Run Ruff formatter on all generated files Command.Run("uv", $"run ruff format {buildDir}") @@ -104,4 +105,4 @@ let handle (args: string list) = printfn "Pyright summary: %s" summaryLine else - printfn "Skipping type checking (use --typing to enable)" + printfn "Skipping type checking (use --type-check to enable)"