diff --git a/.github/actions/setup-and-test-shard/action.yml b/.github/actions/setup-and-test-shard/action.yml new file mode 100644 index 000000000..2c882b169 --- /dev/null +++ b/.github/actions/setup-and-test-shard/action.yml @@ -0,0 +1,914 @@ +name: Set up and test CI shard +description: Restore focused caches, install shard tools, and run a pytest marker shard. + +inputs: + shard-name: + required: true + description: Shard display name. + shard-tools: + required: true + description: Space-separated tool groups required by this shard. + shard-markers: + required: true + description: Pytest marker expression for this shard. + +runs: + using: composite + steps: + - name: Prepare cache directories + shell: bash + run: | + echo "UV_CACHE_DIR=$RUNNER_TEMP/uv-cache" >> "$GITHUB_ENV" + echo "PNPM_HOME=$RUNNER_TEMP/pnpm-home" >> "$GITHUB_ENV" + echo "PNPM_STORE_DIR=$RUNNER_TEMP/pnpm-store" >> "$GITHUB_ENV" + echo "ELM_HOME=$RUNNER_TEMP/elm-home" >> "$GITHUB_ENV" + echo "R_LIBS_USER=$RUNNER_TEMP/r-library" >> "$GITHUB_ENV" + echo "JULIA_DEPOT_PATH=$RUNNER_TEMP/julia" >> "$GITHUB_ENV" + mkdir -p \ + "$RUNNER_TEMP/uv-cache" \ + "$HOME/.serena/language_servers/static" \ + "$HOME/bin" \ + "$HOME/perl5" \ + "$HOME/.cache/go-build" \ + "$HOME/go/bin" \ + "$HOME/go/pkg/mod" \ + "$HOME/.cache/dune" \ + "$HOME/.cabal" \ + "$HOME/.ghcup" \ + "$RUNNER_TEMP/pnpm-home" \ + "$RUNNER_TEMP/pnpm-store" \ + "$RUNNER_TEMP/elm-home" \ + "$RUNNER_TEMP/r-library" \ + "$RUNNER_TEMP/julia" + echo "$HOME/bin" >> "$GITHUB_PATH" + echo "$RUNNER_TEMP/pnpm-home" >> "$GITHUB_PATH" + echo "$HOME/.local/share/swiftly/bin" >> "$GITHUB_PATH" + echo "$HOME/.swiftly/bin" >> "$GITHUB_PATH" + echo "$HOME/.elan/bin" >> "$GITHUB_PATH" + echo "$HOME/.nix-profile/bin" >> "$GITHUB_PATH" + echo "$HOME/perl5/bin" >> "$GITHUB_PATH" + echo "PERL5LIB=$HOME/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}" >> "$GITHUB_ENV" + echo "PERL_LOCAL_LIB_ROOT=$HOME/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}" >> "$GITHUB_ENV" + echo "PERL_MB_OPT=--install_base \"$HOME/perl5\"" >> "$GITHUB_ENV" + echo "PERL_MM_OPT=INSTALL_BASE=$HOME/perl5" >> "$GITHUB_ENV" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install uv + uses: astral-sh/setup-uv@v8.1.0 + env: + UV_CACHE_DIR: ${{ runner.temp }}/uv-cache + with: + enable-cache: true + cache-dependency-glob: uv.lock + + - name: Restore Python cache + uses: actions/cache@v4 + with: + path: | + .venv + ${{ runner.temp }}/uv-cache + key: python-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ hashFiles('uv.lock') }}-v3 + restore-keys: | + python-${{ runner.os }}-${{ env.PYTHON_VERSION }}- + python-${{ runner.os }}- + + - name: Install Python environment + env: + UV_CACHE_DIR: ${{ runner.temp }}/uv-cache + shell: bash + run: | + rm -rf .uv-cache + uv sync --extra dev --locked + rm -rf .uv-cache + + - name: Set up Node.js + if: contains(inputs.shard-tools, 'node') || contains(inputs.shard-tools, 'web') + uses: actions/setup-node@v4 + with: + node-version: "20.x" + + - name: Set up pnpm + if: contains(inputs.shard-tools, 'node') || contains(inputs.shard-tools, 'web') + uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + + - name: Restore pnpm cache + if: contains(inputs.shard-tools, 'node') || contains(inputs.shard-tools, 'web') + uses: actions/cache@v4 + with: + path: | + ${{ runner.temp }}/pnpm-home + ${{ runner.temp }}/pnpm-store + ${{ runner.temp }}/elm-home + key: node-pnpm-${{ runner.os }}-${{ inputs.shard-name }}-${{ hashFiles('.github/workflows/pytest.yml', '.github/actions/setup-and-test-shard/action.yml') }} + restore-keys: | + node-pnpm-${{ runner.os }}-${{ inputs.shard-name }}- + node-pnpm-${{ runner.os }}- + + - name: Configure pnpm + if: contains(inputs.shard-tools, 'node') || contains(inputs.shard-tools, 'web') + shell: bash + run: pnpm config set store-dir "$PNPM_STORE_DIR" + + - name: Restore Go cache + if: contains(inputs.shard-tools, 'go') + uses: actions/cache@v4 + with: + path: | + ~/go/bin + ~/go/pkg/mod + ~/.cache/go-build + ~/Library/Caches/go-build + ~/AppData/Local/go-build + key: go-${{ runner.os }}-gopls-latest-v2 + restore-keys: | + go-${{ runner.os }}- + + - name: Set up Go + if: contains(inputs.shard-tools, 'go') + uses: actions/setup-go@v5 + with: + go-version: ">=1.17.0" + cache: false + + - name: Restore static language-server cache + if: contains(inputs.shard-tools, 'compiled') || contains(inputs.shard-tools, 'niche') + uses: actions/cache@v4 + with: + path: | + ~/.serena/language_servers + ~/bin + key: static-ls-${{ runner.os }}-${{ inputs.shard-name }}-lua-${{ env.LUA_LS_VERSION }}-zls-${{ env.ZLS_VERSION }}-haxe-${{ env.HAXE_VERSION }}-verible-${{ env.VERIBLE_VERSION }}-regal-${{ env.REGAL_VERSION }}-v4 + restore-keys: | + static-ls-${{ runner.os }}-${{ inputs.shard-name }}- + static-ls-${{ runner.os }}- + + - name: Restore OCaml cache + if: contains(inputs.shard-tools, 'ocaml') + uses: actions/cache@v4 + with: + path: | + ~/.opam + ~/.cache/dune + key: ocaml-${{ runner.os }}-${{ hashFiles('.github/workflows/pytest.yml', '.github/actions/setup-and-test-shard/action.yml') }} + restore-keys: | + ocaml-${{ runner.os }}- + + - name: Restore R and Julia cache + if: contains(inputs.shard-tools, 'data') + uses: actions/cache@v4 + with: + path: | + ${{ runner.temp }}/r-library + ${{ runner.temp }}/julia + key: r-julia-${{ runner.os }}-r-${{ env.R_VERSION }}-julia-${{ env.JULIA_VERSION }}-v2 + restore-keys: | + r-julia-${{ runner.os }}- + + - name: Restore Haskell cache + if: contains(inputs.shard-tools, 'haskell') && runner.os != 'Windows' + uses: actions/cache@v4 + with: + path: | + ~/.ghcup + ~/.cabal + test/resources/repos/haskell/test_repo/dist-newstyle + key: haskell-${{ runner.os }}-ghc-9.12.2-cabal-3.10.3.0-hls-2.11.0.0-v2 + restore-keys: | + haskell-${{ runner.os }}- + + - name: Restore compiled tool cache + if: contains(inputs.shard-tools, 'compiled') + uses: actions/cache@v4 + with: + path: | + ~/.swiftly + ~/.local/share/swiftly + ~/fpc + ~/fpcsrc + ~/bin + ~/.cache/zig + key: compiled-${{ runner.os }}-swift-${{ env.SWIFT_VERSION }}-zig-${{ env.ZIG_VERSION }}-fpc-${{ env.FPC_VERSION }}-verible-${{ env.VERIBLE_VERSION }}-v2 + restore-keys: | + compiled-${{ runner.os }}- + + - name: Restore Nix and Lean cache + if: contains(inputs.shard-tools, 'nix') || contains(inputs.shard-tools, 'lean') + uses: actions/cache@v4 + with: + path: | + ~/.nix-profile + ~/.cache/nix + ~/.elan + ~/.lake + test/resources/repos/lean4/test_repo/.lake + key: nix-lean-${{ runner.os }}-${{ inputs.shard-name }}-${{ hashFiles('flake.lock', 'test/resources/repos/lean4/test_repo/lake-manifest.json') }}-v2 + restore-keys: | + nix-lean-${{ runner.os }}-${{ inputs.shard-name }}- + nix-lean-${{ runner.os }}- + + - name: Restore JVM, .NET, and Ruby cache + if: contains(inputs.shard-tools, 'java') || contains(inputs.shard-tools, 'dotnet') || contains(inputs.shard-tools, 'ruby') + uses: actions/cache@v4 + with: + path: | + ~/.gem + ~/.nuget/packages + ~/.m2 + ~/.gradle + ~/.cache/coursier + key: ruby-dotnet-java-${{ runner.os }}-${{ inputs.shard-name }}-ruby-${{ env.RUBY_VERSION }}-java-17-dotnet-10-v2 + restore-keys: | + ruby-dotnet-java-${{ runner.os }}-${{ inputs.shard-name }}- + ruby-dotnet-java-${{ runner.os }}- + + - name: Set up Java + if: contains(inputs.shard-tools, 'java') + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + + - name: Set up .NET SDK + if: contains(inputs.shard-tools, 'dotnet') + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + + - name: Set up Ruby + if: contains(inputs.shard-tools, 'ruby') + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ env.RUBY_VERSION }} + + - name: Set up Elixir + if: contains(inputs.shard-tools, 'elixir') && runner.os != 'Windows' + uses: erlef/setup-beam@v1 + with: + elixir-version: "1.19.3" + otp-version: "28" + + - name: Set up Clojure + if: contains(inputs.shard-tools, 'clojure') + uses: DeLaGuardo/setup-clojure@13.4 + with: + cli: latest + + - name: Set up OCaml + if: contains(inputs.shard-tools, 'ocaml') + uses: ocaml/setup-ocaml@v3 + with: + ocaml-compiler: ${{ runner.os == 'Windows' && '4.14' || '5.3.x' }} + dune-cache: true + opam-repositories: | + ${{ runner.os == 'Windows' && 'opam-repository-mingw: https://github.com/ocaml-opam/opam-repository-mingw.git#sunset' || '' }} + default: https://github.com/ocaml/opam-repository.git + + - name: Set up R + if: contains(inputs.shard-tools, 'data') + uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ env.R_VERSION }} + use-public-rspm: true + + - name: Set up Julia + if: contains(inputs.shard-tools, 'data') + uses: julia-actions/setup-julia@v2 + with: + version: ${{ env.JULIA_VERSION }} + + - name: Set up Haskell toolchain + if: contains(inputs.shard-tools, 'haskell') && runner.os != 'Windows' + uses: haskell/ghcup-setup@v1 + with: + ghc: "9.12.2" + cabal: "3.10.3.0" + hls: "2.11.0.0" + + - name: Set up Zig + if: contains(inputs.shard-tools, 'compiled') + uses: goto-bus-stop/setup-zig@v2 + with: + version: ${{ env.ZIG_VERSION }} + + - name: Set up Terraform + if: contains(inputs.shard-tools, 'data') + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: "1.5.0" + terraform_wrapper: false + + - name: Set up Haxe + if: contains(inputs.shard-tools, 'web') && runner.os != 'Linux' + uses: krdlab/setup-haxe@v2 + with: + haxe-version: ${{ env.HAXE_VERSION }} + + - name: Set up Nix + if: contains(inputs.shard-tools, 'nix') && runner.os != 'Windows' + uses: cachix/install-nix-action@v30 + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: Build Lean 4 test project + if: contains(inputs.shard-tools, 'lean') + uses: leanprover/lean-action@v1 + with: + lake-package-directory: test/resources/repos/lean4/test_repo + + - name: Install guarded shard tools + shell: bash + run: | + set -euo pipefail + + has_group() { + [[ " ${{ inputs.shard-tools }} " == *" $1 "* ]] + } + + ensure_windows_action_paths() { + if [[ "$RUNNER_OS" != "Windows" ]]; then + return + fi + + local ci_bin ci_bin_win + ci_bin="${RUNNER_TEMP:-$HOME}/ci-bin" + ci_bin=$(cygpath -u "$ci_bin" 2>/dev/null || printf '%s' "$ci_bin") + ci_bin_win=$(cygpath -w "$ci_bin" 2>/dev/null || printf '%s' "$ci_bin") + mkdir -p "$ci_bin" + + if has_group lean; then + local lean_bin + lean_bin=$(find /c/Users/runneradmin/.elan /c/Users/runneradmin -iname "lean.exe" -type f 2>/dev/null | head -1 || true) + if [[ -z "$lean_bin" ]]; then + echo "Could not locate lean.exe after leanprover/lean-action" >&2 + return 1 + fi + lean_bin=$(dirname "$lean_bin") + for tool in elan lake lean; do + if [[ -x "$lean_bin/$tool.exe" ]]; then + printf '#!/usr/bin/env bash\nexec "%s" "$@"\n' "$lean_bin/$tool.exe" > "$ci_bin/$tool" + printf '@echo off\r\n"%s" %%*\r\n' "$(cygpath -w "$lean_bin/$tool.exe")" > "$ci_bin/$tool.cmd" + chmod +x "$ci_bin/$tool" + fi + done + export PATH="$ci_bin:$lean_bin:$PATH" + echo "$ci_bin_win" >> "$GITHUB_PATH" + echo "$(cygpath -w "$lean_bin")" >> "$GITHUB_PATH" + fi + + if has_group ocaml; then + local opam_exe opam_dir opam_windows_path + opam_windows_path=$(powershell.exe -NoLogo -NoProfile -Command "(Get-Command opam.exe -ErrorAction SilentlyContinue).Source" | tr -d '\r' | head -1 || true) + if [[ -n "$opam_windows_path" ]]; then + opam_exe=$(cygpath -u "$opam_windows_path") + else + opam_exe=$(find /c/hostedtoolcache /c/Users/runneradmin -iname "opam.exe" -type f 2>/dev/null | head -1 || true) + fi + if [[ -z "$opam_exe" ]]; then + echo "Could not locate opam.exe after ocaml/setup-ocaml" >&2 + return 1 + fi + opam_dir=$(dirname "$opam_exe") + printf '#!/usr/bin/env bash\nexec "%s" "$@"\n' "$opam_exe" > "$ci_bin/opam" + printf '@echo off\r\n"%s" %%*\r\n' "$(cygpath -w "$opam_exe")" > "$ci_bin/opam.cmd" + chmod +x "$ci_bin/opam" + export PATH="$ci_bin:$opam_dir:$PATH" + echo "$ci_bin_win" >> "$GITHUB_PATH" + echo "$(cygpath -w "$opam_dir")" >> "$GITHUB_PATH" + fi + } + + download_with_retry() { + local url="$1" + local output="$2" + for attempt in 1 2 3 4 5; do + if curl -fL --retry 3 --retry-delay 5 --retry-all-errors -o "$output" "$url"; then + return 0 + fi + echo "Download failed for $url on attempt $attempt" + sleep $((attempt * 10)) + done + return 1 + } + + install_gopls() { + if ! command -v gopls >/dev/null 2>&1; then + go install golang.org/x/tools/gopls@latest + fi + gopls version + } + + install_ruby_lsp() { + if ! command -v ruby-lsp >/dev/null 2>&1; then + gem install ruby-lsp + fi + ruby-lsp --version || true + } + + install_ccls() { + if command -v ccls >/dev/null 2>&1; then + ccls --version 2>&1 | head -1 || true + return + fi + + if [[ "$RUNNER_OS" == "Linux" ]]; then + sudo apt-get update + sudo apt-get install -y ccls + elif [[ "$RUNNER_OS" == "macOS" ]]; then + brew install ccls + elif [[ "$RUNNER_OS" == "Windows" ]]; then + choco install ccls -y + fi + command -v ccls >/dev/null + } + + install_swift() { + if [[ "$RUNNER_OS" == "Windows" ]]; then + return + fi + if command -v sourcekit-lsp >/dev/null 2>&1; then + sourcekit-lsp --help >/dev/null || true + return + fi + + if [[ "$RUNNER_OS" == "macOS" ]]; then + curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg + installer -pkg swiftly.pkg -target CurrentUserHomeDirectory + ~/.swiftly/bin/swiftly init --quiet-shell-followup + . "${SWIFTLY_HOME_DIR:-$HOME/.swiftly}/env.sh" + else + sudo apt-get update + sudo apt-get -y install libcurl4-openssl-dev + curl -O "https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz" + tar zxf "swiftly-$(uname -m).tar.gz" + ./swiftly init --quiet-shell-followup + . "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh" + fi + + swiftly install --use "$SWIFT_VERSION" + swiftly use "$SWIFT_VERSION" + sourcekit-lsp --help >/dev/null || true + } + + install_ocaml_packages() { + # Ensure this shell sees the same opam switch/environment as setup-ocaml. + eval "$(opam env 2>/dev/null || true)" + + if opam exec -- which ocamllsp >/dev/null 2>&1; then + return + fi + + if [[ "$RUNNER_OS" == "Windows" ]]; then + opam exec -- opam install -y dune ocaml-lsp-server + else + opam exec -- opam install -y dune 'ocaml-lsp-server>=1.23.0' + fi + } + + verify_ocaml_lsp() { + if ! has_group ocaml; then + return + fi + + eval "$(opam env 2>/dev/null || true)" + opam exec -- which ocamllsp >/dev/null + opam exec -- ocamllsp --version >/dev/null + } + + verify_lsp_paths() { + if has_group go; then + command -v gopls >/dev/null + gopls version >/dev/null 2>&1 || true + fi + + if has_group ruby; then + command -v ruby-lsp >/dev/null + ruby-lsp --version >/dev/null 2>&1 || true + fi + + if has_group compiled; then + command -v ccls >/dev/null + command -v zls >/dev/null + command -v verible-verilog-ls >/dev/null + if [[ "$RUNNER_OS" != "Windows" ]]; then + command -v sourcekit-lsp >/dev/null + fi + fi + + if has_group data; then + Rscript -e "library(languageserver)" >/dev/null + julia -e "using LanguageServer" >/dev/null + fi + + if has_group niche; then + command -v lua-language-server >/dev/null + if [[ "$RUNNER_OS" != "Windows" ]]; then + perl -MPerl::LanguageServer -e 1 >/dev/null + command -v regal >/dev/null + else + command -v regal.exe >/dev/null + fi + fi + + if has_group nix && [[ "$RUNNER_OS" != "Windows" ]]; then + command -v nixd >/dev/null + fi + } + + install_r_language_server() { + if [[ "$RUNNER_OS" == "Linux" ]]; then + sudo apt-get update + sudo apt-get install -y libuv1-dev pkg-config + fi + + Rscript - <<'RSCRIPT' + dir.create(Sys.getenv("R_LIBS_USER"), recursive = TRUE, showWarnings = FALSE) + repos <- getOption("repos") + if (identical(repos["CRAN"], "@CRAN@")) repos["CRAN"] <- "https://cloud.r-project.org" + options(repos = repos) + options(pkgType = if (.Platform$pkgType == "both") "binary" else .Platform$pkgType) + pkgs <- c("fs", "pkgload", "roxygen2", "languageserver") + missing <- pkgs[!vapply(pkgs, requireNamespace, logical(1), quietly = TRUE)] + if (length(missing) > 0) { + install.packages( + missing, + lib = Sys.getenv("R_LIBS_USER"), + dependencies = TRUE, + Ncpus = max(1L, parallel::detectCores() - 1L) + ) + } + library(languageserver) + RSCRIPT + } + + install_julia_language_server() { + julia -e 'using Pkg; try using LanguageServer catch; Pkg.add("LanguageServer"); using LanguageServer end' + } + + build_haskell_repo() { + if [[ "$RUNNER_OS" == "Windows" ]]; then + return + fi + ghc --version + cabal --version + if command -v haskell-language-server-wrapper >/dev/null 2>&1; then + haskell-language-server-wrapper --version || true + elif command -v haskell-language-server >/dev/null 2>&1; then + haskell-language-server --version || true + fi + cd test/resources/repos/haskell/test_repo + cabal update + cabal build --only-dependencies + cabal build + } + + install_zls() { + if command -v zls >/dev/null 2>&1; then + zls --version || true + return + fi + + if [[ "$RUNNER_OS" == "Linux" ]]; then + download_with_retry "https://github.com/zigtools/zls/releases/download/${ZLS_VERSION}/zls-x86_64-linux.tar.xz" zls-x86_64-linux.tar.xz + tar -xf zls-x86_64-linux.tar.xz + mv zls "$HOME/bin/" + rm zls-x86_64-linux.tar.xz + elif [[ "$RUNNER_OS" == "macOS" ]]; then + download_with_retry "https://github.com/zigtools/zls/releases/download/${ZLS_VERSION}/zls-x86_64-macos.tar.xz" zls-x86_64-macos.tar.xz + tar -xf zls-x86_64-macos.tar.xz + mv zls "$HOME/bin/" + rm zls-x86_64-macos.tar.xz + elif [[ "$RUNNER_OS" == "Windows" ]]; then + curl -L -o zls.zip "https://github.com/zigtools/zls/releases/download/${ZLS_VERSION}/zls-x86_64-windows.zip" + unzip -o zls.zip + mv zls.exe "$HOME/bin/" + rm zls.zip + fi + } + + install_verible() { + if command -v verible-verilog-ls >/dev/null 2>&1; then + return + fi + + if [[ "$RUNNER_OS" == "Linux" ]]; then + download_with_retry "https://github.com/chipsalliance/verible/releases/download/${VERIBLE_VERSION}/verible-${VERIBLE_VERSION}-linux-static-x86_64.tar.gz" "verible-${VERIBLE_VERSION}-linux-static-x86_64.tar.gz" + tar -xzf "verible-${VERIBLE_VERSION}-linux-static-x86_64.tar.gz" + mv "verible-${VERIBLE_VERSION}/bin/verible-verilog-ls" "$HOME/bin/" + rm -rf "verible-${VERIBLE_VERSION}" "verible-${VERIBLE_VERSION}-linux-static-x86_64.tar.gz" + elif [[ "$RUNNER_OS" == "macOS" ]]; then + download_with_retry "https://github.com/chipsalliance/verible/releases/download/${VERIBLE_VERSION}/verible-${VERIBLE_VERSION}-macOS.tar.gz" "verible-${VERIBLE_VERSION}-macOS.tar.gz" + tar -xzf "verible-${VERIBLE_VERSION}-macOS.tar.gz" + mv "verible-${VERIBLE_VERSION}-macOS/bin/verible-verilog-ls" "$HOME/bin/" + rm -rf "verible-${VERIBLE_VERSION}-macOS" "verible-${VERIBLE_VERSION}-macOS.tar.gz" + elif [[ "$RUNNER_OS" == "Windows" ]]; then + curl -L -o verible.zip "https://github.com/chipsalliance/verible/releases/download/${VERIBLE_VERSION}/verible-${VERIBLE_VERSION}-win64.zip" + unzip -o verible.zip + mv "verible-${VERIBLE_VERSION}-win64/verible-verilog-ls.exe" "$HOME/bin/" + rm -rf "verible-${VERIBLE_VERSION}-win64" verible.zip + fi + command -v verible-verilog-ls >/dev/null + } + + install_lua_language_server() { + if command -v lua-language-server >/dev/null 2>&1; then + return + fi + + local lua_ls_dir="$HOME/.serena/language_servers/lua" + mkdir -p "$lua_ls_dir" + if [[ "$RUNNER_OS" == "Linux" ]]; then + local arch="x64" + [[ "$(uname -m)" != "x86_64" ]] && arch="arm64" + download_with_retry "https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-linux-${arch}.tar.gz" "lua-language-server-${LUA_LS_VERSION}-linux-${arch}.tar.gz" + tar -xzf "lua-language-server-${LUA_LS_VERSION}-linux-${arch}.tar.gz" -C "$lua_ls_dir" + rm "lua-language-server-${LUA_LS_VERSION}-linux-${arch}.tar.gz" + elif [[ "$RUNNER_OS" == "macOS" ]]; then + local arch="x64" + [[ "$(uname -m)" != "x86_64" ]] && arch="arm64" + download_with_retry "https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-darwin-${arch}.tar.gz" "lua-language-server-${LUA_LS_VERSION}-darwin-${arch}.tar.gz" + tar -xzf "lua-language-server-${LUA_LS_VERSION}-darwin-${arch}.tar.gz" -C "$lua_ls_dir" + rm "lua-language-server-${LUA_LS_VERSION}-darwin-${arch}.tar.gz" + elif [[ "$RUNNER_OS" == "Windows" ]]; then + curl -L -o lua-ls.zip "https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-win32-x64.zip" + unzip -o lua-ls.zip -d "$lua_ls_dir" + rm lua-ls.zip + fi + + chmod +x "$lua_ls_dir/bin/lua-language-server" 2>/dev/null || true + if [[ "$RUNNER_OS" == "Windows" ]]; then + cat > "$HOME/bin/lua-language-server" <<'EOF' + #!/usr/bin/env bash + cd "${HOME}/.serena/language_servers/lua/bin" + exec ./lua-language-server.exe "$@" + EOF + chmod +x "$HOME/bin/lua-language-server" + else + cat > "$HOME/bin/lua-language-server" <<'EOF' + #!/usr/bin/env bash + cd "${HOME}/.serena/language_servers/lua/bin" + exec ./lua-language-server "$@" + EOF + chmod +x "$HOME/bin/lua-language-server" + fi + } + + install_perl_language_server() { + if [[ "$RUNNER_OS" == "Windows" ]]; then + return + fi + if perl -MPerl::LanguageServer -e 1 >/dev/null 2>&1; then + return + fi + + if [[ "$RUNNER_OS" == "Linux" ]]; then + sudo apt-get update + sudo apt-get install -y cpanminus build-essential libanyevent-perl libio-aio-perl + elif [[ "$RUNNER_OS" == "macOS" ]]; then + brew install cpanminus + fi + PERL_MM_USE_DEFAULT=1 cpanm --notest --force Perl::LanguageServer + } + + install_ansible_tools() { + uv run python -c 'import ansible, ansiblelint' >/dev/null 2>&1 || uv run pip install ansible-core ansible-lint + } + + install_haxe() { + if command -v haxe >/dev/null 2>&1 && haxe -version | grep -q "^${HAXE_VERSION}$"; then + haxe -version + return + fi + + if [[ "$RUNNER_OS" != "Linux" ]]; then + command -v haxe >/dev/null + haxe -version + return + fi + + local haxe_root="$HOME/.serena/language_servers/static/haxe-${HAXE_VERSION}" + local neko_root="$HOME/.serena/language_servers/static/neko-2.4.0" + local haxe_archive="$RUNNER_TEMP/haxe-${HAXE_VERSION}-linux64.tar.gz" + local neko_archive="$RUNNER_TEMP/neko-2.4.0-linux64.tar.gz" + mkdir -p "$haxe_root" "$neko_root" + + if [[ ! -x "$haxe_root/haxe" || -z "$(find "$neko_root" -type f -name neko -perm -u+x -print -quit)" || -z "$(find "$neko_root" -type f -name 'libneko.so*' -print -quit)" ]]; then + rm -rf "$haxe_root" "$neko_root" + mkdir -p "$haxe_root" "$neko_root" + download_with_retry "https://github.com/HaxeFoundation/neko/releases/download/v2-4-0/neko-2.4.0-linux64.tar.gz" "$neko_archive" + download_with_retry "https://github.com/HaxeFoundation/haxe/releases/download/${HAXE_VERSION}/haxe-${HAXE_VERSION}-linux64.tar.gz" "$haxe_archive" + tar -xzf "$neko_archive" -C "$neko_root" --strip-components=1 + tar -xzf "$haxe_archive" -C "$haxe_root" --strip-components=1 + mkdir -p "$haxe_root/lib" + fi + + local neko_bin_dir + local neko_lib_dir + local neko_lib_path + neko_bin_dir=$(dirname "$(find "$neko_root" -type f -name neko -perm -u+x -print -quit)") + neko_lib_dir=$(dirname "$(find "$neko_root" -type f -name 'libneko.so*' -print -quit)") + if [[ -z "$neko_bin_dir" || -z "$neko_lib_dir" ]]; then + echo "Could not locate Neko runtime under $neko_root" + find "$neko_root" -maxdepth 3 -type f | sort + return 1 + fi + + neko_lib_path="$neko_lib_dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + export PATH="$haxe_root:$neko_bin_dir:$PATH" + export LD_LIBRARY_PATH="$neko_lib_path" + echo "$haxe_root" >> "$GITHUB_PATH" + echo "$neko_bin_dir" >> "$GITHUB_PATH" + echo "LD_LIBRARY_PATH=$neko_lib_path" >> "$GITHUB_ENV" + haxelib setup "$haxe_root/lib" + haxe -version + } + + install_elm() { + if command -v elm >/dev/null 2>&1 && elm --version | grep -q '^0\.19\.1$'; then + echo "Elm compiler already installed" + else + local asset output + if [[ "$RUNNER_OS" == "Linux" ]]; then + asset="binary-for-linux-64-bit.gz" + output="$HOME/bin/elm" + elif [[ "$RUNNER_OS" == "macOS" ]]; then + if [[ "$(uname -m)" == "arm64" ]]; then + asset="binary-for-mac-64-bit-ARM.gz" + else + asset="binary-for-mac-64-bit.gz" + fi + output="$HOME/bin/elm" + elif [[ "$RUNNER_OS" == "Windows" ]]; then + asset="binary-for-windows-64-bit.gz" + output="$HOME/bin/elm.exe" + fi + + download_with_retry "https://github.com/elm/compiler/releases/download/${ELM_VERSION}/${asset}" "$RUNNER_TEMP/$asset" + python -c 'import gzip, pathlib, sys; pathlib.Path(sys.argv[2]).write_bytes(gzip.open(sys.argv[1], "rb").read())' "$RUNNER_TEMP/$asset" "$output" + chmod +x "$output" 2>/dev/null || true + fi + elm --version + (cd test/resources/repos/elm/test_repo && elm make Main.elm --output="$RUNNER_TEMP/elm-test.js") + } + + install_nixd() { + if [[ "$RUNNER_OS" == "Windows" ]]; then + return + fi + if ! command -v nixd >/dev/null 2>&1; then + nix profile install github:nix-community/nixd + fi + command -v nixd >/dev/null + nix build --no-link + } + + install_regal() { + if [[ "$RUNNER_OS" == "Windows" ]]; then + rm -f "$HOME/bin/regal" + if command -v regal.exe >/dev/null 2>&1; then + return + fi + elif command -v regal >/dev/null 2>&1; then + return + fi + + if [[ "$RUNNER_OS" == "Linux" ]]; then + local arch="x86_64" + [[ "$(uname -m)" != "x86_64" ]] && arch="arm64" + curl -L -o "$HOME/bin/regal" "https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Linux_${arch}" + elif [[ "$RUNNER_OS" == "macOS" ]]; then + local arch="x86_64" + [[ "$(uname -m)" != "x86_64" ]] && arch="arm64" + curl -L -o "$HOME/bin/regal" "https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Darwin_${arch}" + elif [[ "$RUNNER_OS" == "Windows" ]]; then + curl -L -o "$HOME/bin/regal.exe" "https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Windows_x86_64.exe" + chmod +x "$HOME/bin/regal.exe" 2>/dev/null || true + fi + chmod +x "$HOME/bin/regal" 2>/dev/null || true + } + + install_fpc() { + if [[ "$RUNNER_OS" == "Linux" ]]; then + if ! command -v fpc >/dev/null 2>&1; then + sudo apt-get update + sudo apt-get install -y fpc fpc-source + fi + local fpcdir + fpcdir=$(ls -d /usr/share/fpcsrc/*/ 2>/dev/null | head -1 | sed 's:/$::') + [[ -z "$fpcdir" ]] && fpcdir="/usr/share/fpcsrc" + export PP="/usr/bin/fpc" + export FPCDIR="$fpcdir" + echo "PP=/usr/bin/fpc" >> "$GITHUB_ENV" + echo "FPCDIR=$fpcdir" >> "$GITHUB_ENV" + elif [[ "$RUNNER_OS" == "macOS" ]]; then + if ! command -v fpc >/dev/null 2>&1; then + brew install fpc + fi + if [[ ! -d "$HOME/fpcsrc/fpc-${FPC_VERSION}" ]]; then + curl -L -o fpc-source.tar.gz "https://sourceforge.net/projects/freepascal/files/Source/${FPC_VERSION}/fpc-${FPC_VERSION}.source.tar.gz/download" + mkdir -p "$HOME/fpcsrc" + tar -xzf fpc-source.tar.gz -C "$HOME/fpcsrc" + rm fpc-source.tar.gz + fi + local fpcdir="$HOME/fpcsrc/fpc-${FPC_VERSION}" + [[ -d "$HOME/fpcsrc/fpc-${FPC_VERSION}/fpc-${FPC_VERSION}/rtl" ]] && fpcdir="$HOME/fpcsrc/fpc-${FPC_VERSION}/fpc-${FPC_VERSION}" + export PP + PP="$(command -v fpc)" + export FPCDIR="$fpcdir" + echo "PP=$PP" >> "$GITHUB_ENV" + echo "FPCDIR=$fpcdir" >> "$GITHUB_ENV" + elif [[ "$RUNNER_OS" == "Windows" ]]; then + if [[ ! -d "$HOME/fpc" ]]; then + curl -L -o fpc-ootb.zip "https://github.com/fredvs/freepascal-ootb/releases/download/${FPC_VERSION}/fpc-ootb-322-x86_64-win64.zip" + mkdir -p "$HOME/fpc" + unzip -q fpc-ootb.zip -d "$HOME/fpc" + rm fpc-ootb.zip + fi + if [[ ! -d "$HOME/fpcsrc/fpc-${FPC_VERSION}" ]]; then + curl -L -o fpc-source.zip "https://sourceforge.net/projects/freepascal/files/Source/${FPC_VERSION}/fpc-${FPC_VERSION}.source.zip/download" + mkdir -p "$HOME/fpcsrc" + unzip -q fpc-source.zip -d "$HOME/fpcsrc" + rm fpc-source.zip + fi + local fpc_exe + fpc_exe=$(find "$HOME/fpc" -name "fpc-ootb-64.exe" -type f 2>/dev/null | head -1) + export PP="$fpc_exe" + export FPCDIR="$HOME/fpcsrc/fpc-${FPC_VERSION}" + echo "PP=$fpc_exe" >> "$GITHUB_ENV" + echo "FPCDIR=$HOME/fpcsrc/fpc-${FPC_VERSION}" >> "$GITHUB_ENV" + echo "$(dirname "$fpc_exe")" >> "$GITHUB_PATH" + fi + } + + verify_fpc() { + if ! has_group compiled; then + return + fi + local test_pas test_out + if [[ "$RUNNER_OS" == "Windows" ]]; then + test_pas="$TEMP/fpc_test.pas" + test_out="$TEMP/fpc_test" + else + test_pas="/tmp/fpc_test.pas" + test_out="/tmp/fpc_test" + fi + echo "program fpc_test; begin writeln('FPC works'); end." > "$test_pas" + "$PP" "$test_pas" -o"$test_out" >/dev/null + [[ -f "$test_out" || -f "${test_out}.exe" ]] + [[ -d "$FPCDIR" ]] + } + + ensure_windows_action_paths + + if has_group go; then install_gopls; fi + if has_group ruby; then install_ruby_lsp; fi + if has_group compiled; then install_ccls; install_swift; install_zls; install_verible; install_fpc; fi + if has_group ocaml; then install_ocaml_packages; fi + if has_group data; then install_r_language_server; install_julia_language_server; fi + if has_group haskell; then build_haskell_repo; fi + if has_group niche; then install_lua_language_server; install_perl_language_server; install_ansible_tools; install_regal; fi + if has_group web; then install_haxe; install_elm; fi + if has_group nix; then install_nixd; fi + + verify_ocaml_lsp + verify_lsp_paths + verify_fpc + rm -rf .uv-cache + + - name: Report free disk space + if: runner.os == 'Linux' + shell: bash + run: df -h + + - name: Check formatting + if: inputs.shard-name == 'lint-type' + env: + UV_CACHE_DIR: ${{ runner.temp }}/uv-cache + shell: bash + run: | + rm -rf .uv-cache + uv run poe lint + + - name: Type-check with mypy + if: inputs.shard-name == 'lint-type' + env: + UV_CACHE_DIR: ${{ runner.temp }}/uv-cache + shell: bash + run: | + rm -rf .uv-cache + uv run poe type-check + + - name: Test with pytest + if: inputs.shard-name != 'lint-type' + env: + UV_CACHE_DIR: ${{ runner.temp }}/uv-cache + shell: bash + run: | + rm -rf .uv-cache + uv run poe test -q --tb=short -m "${{ inputs.shard-markers }}" diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 605301518..638e92034 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -7,575 +7,162 @@ on: - main permissions: - contents: read + contents: read + packages: read concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + PYTHON_VERSION: "3.11" + LUA_LS_VERSION: "3.15.0" + VERIBLE_VERSION: "v0.0-4051-g9fdb4057" + REGAL_VERSION: "0.39.0" + FPC_VERSION: "3.2.2" + SWIFT_VERSION: "6.1.2" + ZIG_VERSION: "0.14.1" + ZLS_VERSION: "0.14.0" + ELM_VERSION: "0.19.1" + HAXE_VERSION: "4.3.7" + RUBY_VERSION: "3.4" + R_VERSION: "4.4.2" + JULIA_VERSION: "1.10" + jobs: - cpu: - name: Tests on ${{ matrix.os }} - runs-on: ${{ matrix.os }} + tests-linux: + name: ${{ matrix.shard.name }} tests-linux ubuntu-latest + runs-on: ubuntu-latest + timeout-minutes: 60 strategy: fail-fast: false + max-parallel: 7 matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.11"] + shard: + - name: lint-type + tools: "python" + markers: "" + - name: core + tools: "python node go ruby nix" + markers: "python or go or bash or markdown or yaml or json or toml or ruby or snapshot" + - name: web + tools: "python node web" + markers: "typescript or vue or svelte or php or solidity or haxe or elm or hlsl" + - name: jvm-dotnet + tools: "python java dotnet clojure" + markers: "java or kotlin or groovy or scala or clojure or csharp or fsharp" + - name: compiled + tools: "python compiled haskell swift lean" + markers: "rust or cpp or zig or swift or haskell or pascal or systemverilog or lean4" + - name: data + tools: "python data ocaml nix" + markers: "r or julia or ocaml or fortran or terraform or nix" + - name: niche + tools: "python node elixir niche" + markers: "elixir or lua or luau or perl or rego or ansible or dart or powershell or crystal or erlang or al or matlab or msl" steps: - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Free disk space - if: runner.os == 'Linux' run: | df -h sudo rm -rf /usr/local/lib/android sudo rm -rf /usr/share/dotnet sudo rm -rf /opt/ghc - sudo rm -rf /opt/hostedtoolcache sudo apt-get clean sudo apt-get autoremove -y docker system prune -af || true df -h - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: "${{ matrix.python-version }}" - - uses: actions/setup-go@v5 - with: - go-version: ">=1.17.0" - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - - name: Ensure cached directory exist before calling cache-related actions - shell: bash - run: | - mkdir -p $HOME/.serena/language_servers/static - mkdir -p $HOME/.cache/go-build - mkdir -p $HOME/go/bin - - name: Install uv - shell: bash - run: curl -LsSf https://astral.sh/uv/install.sh | sh - - name: Cache uv virtualenv - id: cache-uv - uses: actions/cache@v3 - with: - path: .venv - key: uv-venv-${{ runner.os }}-${{ matrix.python-version }}-lock-${{ hashFiles('uv.lock') }} - - name: Create virtual environment - shell: bash - run: | - if [ ! -d ".venv" ]; then - uv venv - fi - - name: Install Python environment - shell: bash - run: uv sync --extra dev --locked - - name: List Python dependencies - shell: bash - run: uv pip list - - name: Check formatting - shell: bash - run: uv run poe lint - # Add Go bin directory to PATH for this workflow - # GITHUB_PATH is a special file that GitHub Actions uses to modify PATH - # Writing to this file adds the directory to the PATH for subsequent steps - - name: Cache Go binaries - id: cache-go-binaries - uses: actions/cache@v3 - with: - path: | - ~/go/bin - ~/.cache/go-build - key: go-binaries-${{ runner.os }}-gopls-latest - - name: Install gopls - if: steps.cache-go-binaries.outputs.cache-hit != 'true' - shell: bash - run: go install golang.org/x/tools/gopls@latest - - name: Set up Elixir - if: runner.os != 'Windows' - uses: erlef/setup-beam@v1 - with: - elixir-version: "1.19.3" - otp-version: "28" -# Erlang currently not tested in CI, random hangings on macos, always hangs on ubuntu -# In local tests, erlang seems to work though -# - name: Install Erlang Language Server -# if: runner.os != 'Windows' -# shell: bash -# run: | -# # Install rebar3 if not already available -# which rebar3 || (curl -fsSL https://github.com/erlang/rebar3/releases/download/3.23.0/rebar3 -o /tmp/rebar3 && chmod +x /tmp/rebar3 && sudo mv /tmp/rebar3 /usr/local/bin/rebar3) -# # Clone and build erlang_ls -# git clone https://github.com/erlang-ls/erlang_ls.git /tmp/erlang_ls -# cd /tmp/erlang_ls -# make install PREFIX=/usr/local -# # Ensure erlang_ls is in PATH -# echo "$HOME/.local/bin" >> $GITHUB_PATH - - name: Install clojure tools - uses: DeLaGuardo/setup-clojure@13.4 - with: - cli: latest - - name: Install ccls (C/C++ Language Server) - shell: bash - run: | - if [[ "${{ runner.os }}" == "Linux" ]]; then - sudo apt-get update - sudo apt-get install -y ccls - elif [[ "${{ runner.os }}" == "macOS" ]]; then - brew install ccls - elif [[ "${{ runner.os }}" == "Windows" ]]; then - choco install ccls -y - fi - # Verify installation - if command -v ccls &> /dev/null; then - echo "ccls installed: $(ccls --version 2>&1 | head -1)" - else - echo "ERROR: ccls installation failed" - exit 1 - fi - - name: Setup Java (for JVM based languages) - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '17' - - name: Setup .NET SDK (for F# and C# languages) - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '10.0.x' - - name: List .NET runtimes - shell: bash - run: dotnet --list-runtimes - - name: Install Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: "1.5.0" - terraform_wrapper: false - # - name: Install swift - # if: runner.os != 'Windows' - # uses: swift-actions/setup-swift@v2 - # Installation of swift with the action screws with installation of ruby on macOS for some reason - # We can try again when version 3 of the action is released, where they will also use swiftly - # Until then, we use custom code to install swift. Sourcekit-lsp is installed automatically with swift - - name: Install Swift with swiftly (macOS) - if: runner.os == 'macOS' - run: | - echo "=== Installing swiftly on macOS ===" - curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && \ - installer -pkg swiftly.pkg -target CurrentUserHomeDirectory && \ - ~/.swiftly/bin/swiftly init --quiet-shell-followup && \ - . "${SWIFTLY_HOME_DIR:-$HOME/.swiftly}/env.sh" && \ - hash -r - swiftly install --use 6.1.2 - swiftly use 6.1.2 - echo "~/.swiftly/bin" >> $GITHUB_PATH - echo "Swiftly installed successfully" - # Verify sourcekit-lsp is working before proceeding - echo "=== Verifying sourcekit-lsp installation ===" - which sourcekit-lsp || echo "Warning: sourcekit-lsp not found in PATH" - sourcekit-lsp --help || echo "Warning: sourcekit-lsp not responding" - - name: Install Swift with swiftly (Ubuntu) - if: runner.os == 'Linux' - run: | - echo "=== Installing swiftly on Ubuntu ===" - # Install dependencies BEFORE Swift to avoid exit code 1 - sudo apt-get update - sudo apt-get -y install libcurl4-openssl-dev - curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \ - tar zxf swiftly-$(uname -m).tar.gz && \ - ./swiftly init --quiet-shell-followup && \ - . "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh" && \ - hash -r - swiftly install --use 6.1.2 - swiftly use 6.1.2 - echo "=== Adding Swift toolchain to PATH ===" - echo "$HOME/.local/share/swiftly/bin" >> $GITHUB_PATH - echo "Swiftly installed successfully!" - # Verify sourcekit-lsp is working before proceeding - echo "=== Verifying sourcekit-lsp installation ===" - which sourcekit-lsp || echo "Warning: sourcekit-lsp not found in PATH" - sourcekit-lsp --help || echo "Warning: sourcekit-lsp not responding" - - name: Install Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.4' - - name: Install Ruby language server - shell: bash - run: gem install ruby-lsp - - name: Install OCaml and opam - uses: ocaml/setup-ocaml@v3 - with: - ocaml-compiler: ${{ runner.os == 'Windows' && '4.14' || '5.3.x' }} - dune-cache: true - opam-repositories: | - ${{ runner.os == 'Windows' && 'opam-repository-mingw: https://github.com/ocaml-opam/opam-repository-mingw.git#sunset' || '' }} - default: https://github.com/ocaml/opam-repository.git - - name: Install OCaml packages - shell: bash - run: | - if [ "$RUNNER_OS" = "Windows" ]; then - opam install -y dune ocaml-lsp-server - else - # Require ocaml-lsp-server >= 1.23.0 for cross-file reference support - opam install -y dune 'ocaml-lsp-server>=1.23.0' - fi - - name: Install R - uses: r-lib/actions/setup-r@v2 - with: - r-version: '4.4.2' - use-public-rspm: true - - name: Install R language server - shell: bash - run: | - Rscript -e "install.packages('languageserver', repos='https://cloud.r-project.org')" - - name: Set up Julia - uses: julia-actions/setup-julia@v2 - with: - version: '1.10' - - name: Install Julia LanguageServer - shell: bash - run: julia -e 'using Pkg; Pkg.add("LanguageServer")' - - name: Setup Haskell toolchain - if: runner.os != 'Windows' - uses: haskell/ghcup-setup@v1 - with: - ghc: '9.12.2' - cabal: '3.10.3.0' - hls: '2.11.0.0' - - name: Verify Haskell tools - if: runner.os != 'Windows' - run: | - echo "Verifying installed Haskell tools:" - which ghc && ghc --version - which cabal && cabal --version - # HLS verification - non-blocking in case of version incompatibility - if command -v haskell-language-server-wrapper &>/dev/null; then - echo "Found haskell-language-server-wrapper" - haskell-language-server-wrapper --version || echo "WARNING: HLS wrapper found but version check failed" - elif command -v haskell-language-server &>/dev/null; then - echo "Found haskell-language-server" - haskell-language-server --version || echo "WARNING: HLS found but version check failed" - else - echo "WARNING: HLS not found (may be incompatible with GHC 9.12.2)" - echo "This is not a critical error - tests will use HLS if available at runtime" - fi - shell: bash - - name: Pre-build Haskell test project for HLS - if: runner.os != 'Windows' - run: | - cd test/resources/repos/haskell/test_repo - cabal update - cabal build --only-dependencies - cabal build - echo "Haskell test project built successfully" - shell: bash - - name: Install Zig - uses: goto-bus-stop/setup-zig@v2 - with: - version: 0.14.1 - - name: Install ZLS (Zig Language Server) - shell: bash - run: | - if [[ "${{ runner.os }}" == "Linux" ]]; then - wget https://github.com/zigtools/zls/releases/download/0.14.0/zls-x86_64-linux.tar.xz - tar -xf zls-x86_64-linux.tar.xz - sudo mv zls /usr/local/bin/ - rm zls-x86_64-linux.tar.xz - elif [[ "${{ runner.os }}" == "macOS" ]]; then - wget https://github.com/zigtools/zls/releases/download/0.14.0/zls-x86_64-macos.tar.xz - tar -xf zls-x86_64-macos.tar.xz - sudo mv zls /usr/local/bin/ - rm zls-x86_64-macos.tar.xz - elif [[ "${{ runner.os }}" == "Windows" ]]; then - curl -L -o zls.zip https://github.com/zigtools/zls/releases/download/0.14.0/zls-x86_64-windows.zip - unzip -o zls.zip - mkdir -p "$HOME/bin" - mv zls.exe "$HOME/bin/" - echo "$HOME/bin" >> $GITHUB_PATH - rm zls.zip - fi - - name: Install verible-verilog-ls (SystemVerilog Language Server) - shell: bash - run: | - VERIBLE_VERSION="v0.0-4051-g9fdb4057" - - if [[ "${{ runner.os }}" == "Linux" ]]; then - wget https://github.com/chipsalliance/verible/releases/download/${VERIBLE_VERSION}/verible-${VERIBLE_VERSION}-linux-static-x86_64.tar.gz - tar -xzf verible-${VERIBLE_VERSION}-linux-static-x86_64.tar.gz - sudo mv verible-${VERIBLE_VERSION}/bin/verible-verilog-ls /usr/local/bin/ - rm -rf verible-${VERIBLE_VERSION} verible-${VERIBLE_VERSION}-linux-static-x86_64.tar.gz - elif [[ "${{ runner.os }}" == "macOS" ]]; then - wget https://github.com/chipsalliance/verible/releases/download/${VERIBLE_VERSION}/verible-${VERIBLE_VERSION}-macOS.tar.gz - tar -xzf verible-${VERIBLE_VERSION}-macOS.tar.gz - sudo mv verible-${VERIBLE_VERSION}-macOS/bin/verible-verilog-ls /usr/local/bin/ - rm -rf verible-${VERIBLE_VERSION}-macOS verible-${VERIBLE_VERSION}-macOS.tar.gz - elif [[ "${{ runner.os }}" == "Windows" ]]; then - curl -L -o verible.zip https://github.com/chipsalliance/verible/releases/download/${VERIBLE_VERSION}/verible-${VERIBLE_VERSION}-win64.zip - unzip -o verible.zip - mkdir -p "$HOME/bin" - mv verible-${VERIBLE_VERSION}-win64/verible-verilog-ls.exe "$HOME/bin/" - echo "$HOME/bin" >> $GITHUB_PATH - rm -rf verible-${VERIBLE_VERSION}-win64 verible.zip - fi - # Verify installation - if command -v verible-verilog-ls &> /dev/null; then - echo "verible-verilog-ls installed successfully" - else - echo "WARNING: verible-verilog-ls not found in PATH" - fi - - name: Install Lua Language Server - shell: bash - run: | - LUA_LS_VERSION="3.15.0" - LUA_LS_DIR="$HOME/.serena/language_servers/lua" - mkdir -p "$LUA_LS_DIR" - - if [[ "${{ runner.os }}" == "Linux" ]]; then - if [[ "$(uname -m)" == "x86_64" ]]; then - wget https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-linux-x64.tar.gz - tar -xzf lua-language-server-${LUA_LS_VERSION}-linux-x64.tar.gz -C "$LUA_LS_DIR" - else - wget https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-linux-arm64.tar.gz - tar -xzf lua-language-server-${LUA_LS_VERSION}-linux-arm64.tar.gz -C "$LUA_LS_DIR" - fi - chmod +x "$LUA_LS_DIR/bin/lua-language-server" - # Create wrapper script instead of symlink to ensure supporting files are found - echo '#!/bin/bash' | sudo tee /usr/local/bin/lua-language-server > /dev/null - echo 'cd "${HOME}/.serena/language_servers/lua/bin"' | sudo tee -a /usr/local/bin/lua-language-server > /dev/null - echo 'exec ./lua-language-server "$@"' | sudo tee -a /usr/local/bin/lua-language-server > /dev/null - sudo chmod +x /usr/local/bin/lua-language-server - rm lua-language-server-*.tar.gz - elif [[ "${{ runner.os }}" == "macOS" ]]; then - if [[ "$(uname -m)" == "x86_64" ]]; then - wget https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-darwin-x64.tar.gz - tar -xzf lua-language-server-${LUA_LS_VERSION}-darwin-x64.tar.gz -C "$LUA_LS_DIR" - else - wget https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-darwin-arm64.tar.gz - tar -xzf lua-language-server-${LUA_LS_VERSION}-darwin-arm64.tar.gz -C "$LUA_LS_DIR" - fi - chmod +x "$LUA_LS_DIR/bin/lua-language-server" - # Create wrapper script instead of symlink to ensure supporting files are found - echo '#!/bin/bash' | sudo tee /usr/local/bin/lua-language-server > /dev/null - echo 'cd "${HOME}/.serena/language_servers/lua/bin"' | sudo tee -a /usr/local/bin/lua-language-server > /dev/null - echo 'exec ./lua-language-server "$@"' | sudo tee -a /usr/local/bin/lua-language-server > /dev/null - sudo chmod +x /usr/local/bin/lua-language-server - rm lua-language-server-*.tar.gz - elif [[ "${{ runner.os }}" == "Windows" ]]; then - curl -L -o lua-ls.zip https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-win32-x64.zip - unzip -o lua-ls.zip -d "$LUA_LS_DIR" - # For Windows, we'll add the bin directory directly to PATH - # The lua-language-server.exe can find its supporting files relative to its location - echo "$LUA_LS_DIR/bin" >> $GITHUB_PATH - rm lua-ls.zip - fi - - name: Install Perl::LanguageServer - if: runner.os != 'Windows' - shell: bash - run: | - if [[ "${{ runner.os }}" == "Linux" ]]; then - sudo apt-get update - sudo apt-get install -y cpanminus build-essential libanyevent-perl libio-aio-perl - elif [[ "${{ runner.os }}" == "macOS" ]]; then - brew install cpanminus - fi - PERL_MM_USE_DEFAULT=1 cpanm --notest --force Perl::LanguageServer - # Set up Perl local::lib environment for subsequent steps - echo "PERL5LIB=$HOME/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}" >> $GITHUB_ENV - echo "PERL_LOCAL_LIB_ROOT=$HOME/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}" >> $GITHUB_ENV - echo "PERL_MB_OPT=--install_base \"$HOME/perl5\"" >> $GITHUB_ENV - echo "PERL_MM_OPT=INSTALL_BASE=$HOME/perl5" >> $GITHUB_ENV - echo "$HOME/perl5/bin" >> $GITHUB_PATH - - name: Install ansible-core and ansible-lint (for Ansible language server tests) - shell: bash - run: uv run pip install ansible-core ansible-lint - - name: Install Haxe - # The Haxe LS binary (server.js) is auto-downloaded and runs on Node.js, - # but it delegates to the Haxe compiler for code analysis at runtime. - uses: krdlab/setup-haxe@v2 + - name: Run shard + uses: ./.github/actions/setup-and-test-shard with: - haxe-version: 4.3.7 - - name: Install Elm - shell: bash - run: npm install -g elm@0.19.1-6 - - name: Install Nix - if: runner.os != 'Windows' # Nix doesn't support Windows natively - uses: cachix/install-nix-action@v30 - with: - nix_path: nixpkgs=channel:nixos-unstable - - name: Install nixd (Nix Language Server) - if: runner.os != 'Windows' # Skip on Windows since Nix isn't available - shell: bash - run: | - # Install nixd using nix - nix profile install github:nix-community/nixd + shard-name: ${{ matrix.shard.name }} + shard-tools: ${{ matrix.shard.tools }} + shard-markers: ${{ matrix.shard.markers }} - # Verify nixd is installed and working - if ! command -v nixd &> /dev/null; then - echo "nixd installation failed or not in PATH" - exit 1 - fi - - echo "$HOME/.nix-profile/bin" >> $GITHUB_PATH - - name: Verify Nix package build - if: runner.os != 'Windows' # Nix only supported on Linux/macOS - shell: bash - run: | - # Verify the flake builds successfully - nix build --no-link - - name: Install Regal (Rego Language Server) - shell: bash - run: | - REGAL_VERSION="0.39.0" - - if [[ "${{ runner.os }}" == "Linux" ]]; then - if [[ "$(uname -m)" == "x86_64" ]]; then - curl -L -o regal https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Linux_x86_64 - else - curl -L -o regal https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Linux_arm64 - fi - chmod +x regal - sudo mv regal /usr/local/bin/ - elif [[ "${{ runner.os }}" == "macOS" ]]; then - if [[ "$(uname -m)" == "x86_64" ]]; then - curl -L -o regal https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Darwin_x86_64 - else - curl -L -o regal https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Darwin_arm64 - fi - chmod +x regal - sudo mv regal /usr/local/bin/ - elif [[ "${{ runner.os }}" == "Windows" ]]; then - curl -L -o regal.exe https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Windows_x86_64.exe - mkdir -p "$HOME/bin" - mv regal.exe "$HOME/bin/" - echo "$HOME/bin" >> $GITHUB_PATH - fi - - name: Install Free Pascal Compiler - shell: bash - run: | - if [[ "${{ runner.os }}" == "Linux" ]]; then - sudo apt-get update - sudo apt-get install -y fpc fpc-source - # Set environment variables for pasls - echo "PP=/usr/bin/fpc" >> $GITHUB_ENV - # Find FPC source directory (version may vary) - remove trailing slash - FPCDIR=$(ls -d /usr/share/fpcsrc/*/ 2>/dev/null | head -1 | sed 's:/$::') - if [[ -z "$FPCDIR" ]]; then - FPCDIR="/usr/share/fpcsrc" - fi - echo "FPCDIR=$FPCDIR" >> $GITHUB_ENV - echo "=== FPC source directory structure ===" - ls -la "$FPCDIR" || echo "FPCDIR not found" - ls -la "$FPCDIR/rtl" 2>/dev/null || echo "rtl subdirectory not found" - elif [[ "${{ runner.os }}" == "macOS" ]]; then - brew install fpc - # Download FPC source from SourceForge (fpc-src-laz cask is incompatible with ARM64) - FPC_VERSION="3.2.2" - curl -L -o fpc-source.tar.gz "https://sourceforge.net/projects/freepascal/files/Source/${FPC_VERSION}/fpc-${FPC_VERSION}.source.tar.gz/download" - mkdir -p "$HOME/fpcsrc" - tar -xzf fpc-source.tar.gz -C "$HOME/fpcsrc" - rm fpc-source.tar.gz - # Check extracted directory structure (might be nested) - echo "=== Extracted FPC source structure ===" - ls -la "$HOME/fpcsrc" - # Find the actual FPC source root (contains rtl, packages, etc.) - if [[ -d "$HOME/fpcsrc/fpc-${FPC_VERSION}/rtl" ]]; then - FPCDIR="$HOME/fpcsrc/fpc-${FPC_VERSION}" - elif [[ -d "$HOME/fpcsrc/fpc-${FPC_VERSION}/fpc-${FPC_VERSION}/rtl" ]]; then - FPCDIR="$HOME/fpcsrc/fpc-${FPC_VERSION}/fpc-${FPC_VERSION}" - else - FPCDIR="$HOME/fpcsrc/fpc-${FPC_VERSION}" - fi - echo "PP=$(which fpc)" >> $GITHUB_ENV - echo "FPCDIR=$FPCDIR" >> $GITHUB_ENV - echo "=== FPC source directory ===" - ls -la "$FPCDIR" || echo "FPCDIR not found" - elif [[ "${{ runner.os }}" == "Windows" ]]; then - FPC_VERSION="3.2.2" - # Download freepascal-ootb (includes FPC compiler) - curl -L -o fpc-ootb.zip https://github.com/fredvs/freepascal-ootb/releases/download/${FPC_VERSION}/fpc-ootb-322-x86_64-win64.zip - mkdir -p "$HOME/fpc" - unzip -q fpc-ootb.zip -d "$HOME/fpc" - rm fpc-ootb.zip - # Download FPC source from SourceForge (fpc-ootb only has compiled units, not source) - curl -L -o fpc-source.zip "https://sourceforge.net/projects/freepascal/files/Source/${FPC_VERSION}/fpc-${FPC_VERSION}.source.zip/download" - mkdir -p "$HOME/fpcsrc" - unzip -q fpc-source.zip -d "$HOME/fpcsrc" - rm fpc-source.zip - # Find fpc executable (fpc-ootb uses fpc-ootb.exe as the compiler) - echo "=== FPC directory structure ===" - find "$HOME/fpc" -name "*.exe" -type f 2>/dev/null | head -10 - FPC_EXE=$(find "$HOME/fpc" -name "fpc-ootb-64.exe" -type f 2>/dev/null | head -1) - echo "Found FPC executable: $FPC_EXE" - echo "Found FPC source dir: $HOME/fpcsrc/fpc-${FPC_VERSION}" - # Set environment variables for pasls - echo "PP=$FPC_EXE" >> $GITHUB_ENV - echo "FPCDIR=$HOME/fpcsrc/fpc-${FPC_VERSION}" >> $GITHUB_ENV - # Add FPC bin directory to PATH - FPC_BIN_DIR=$(dirname "$FPC_EXE") - echo "$FPC_BIN_DIR" >> $GITHUB_PATH - fi - - name: Verify FPC installation - shell: bash - run: | - echo "=== Environment variables ===" - echo "PP=$PP" - echo "FPCDIR=$FPCDIR" - - # Create a simple test program - if [[ "${{ runner.os }}" == "Windows" ]]; then - TEST_PAS="$TEMP/fpc_test.pas" - TEST_OUT="$TEMP/fpc_test" - else - TEST_PAS="/tmp/fpc_test.pas" - TEST_OUT="/tmp/fpc_test" - fi - echo "program fpc_test; begin writeln('FPC works'); end." > "$TEST_PAS" - - # Compile using PP (the compiler we actually use in tests) - echo "=== Compiling test program with PP=$PP ===" - if [[ -n "$PP" ]]; then - "$PP" "$TEST_PAS" -o"$TEST_OUT" 2>&1 - else - echo "ERROR: PP environment variable is not set" - exit 1 - fi + tests-windows: + name: ${{ matrix.shard.name }} tests-windows + runs-on: windows-latest + timeout-minutes: 75 + strategy: + fail-fast: false + max-parallel: 7 + matrix: + shard: + - name: lint-type + tools: "python" + markers: "" + - name: core + tools: "python node go ruby nix" + markers: "python or go or bash or markdown or yaml or json or toml or ruby or snapshot" + - name: web + tools: "python node web" + markers: "typescript or vue or svelte or php or solidity or haxe or elm or hlsl" + - name: jvm-dotnet + tools: "python java dotnet clojure" + markers: "java or kotlin or groovy or scala or clojure or csharp or fsharp" + - name: compiled + tools: "python compiled haskell swift lean" + markers: "rust or cpp or zig or swift or haskell or pascal or systemverilog or lean4" + - name: data + tools: "python data ocaml nix" + markers: "r or julia or ocaml or fortran or terraform or nix" + - name: niche + tools: "python node elixir niche" + markers: "elixir or lua or luau or perl or rego or ansible or dart or powershell or crystal or erlang or al or matlab or msl" + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false - # Verify output binary exists - if [[ -f "$TEST_OUT" ]] || [[ -f "${TEST_OUT}.exe" ]]; then - echo "FPC compilation test PASSED" - else - echo "ERROR: FPC compilation failed - no output binary at $TEST_OUT" - exit 1 - fi + - name: Run shard + uses: ./.github/actions/setup-and-test-shard + with: + shard-name: ${{ matrix.shard.name }} + shard-tools: ${{ matrix.shard.tools }} + shard-markers: ${{ matrix.shard.markers }} - # Verify FPCDIR exists (required for pasls) - if [[ -d "$FPCDIR" ]]; then - echo "FPCDIR exists: $FPCDIR" - else - echo "ERROR: FPCDIR does not exist: $FPCDIR" - exit 1 - fi - - name: Build Lean 4 test project - uses: leanprover/lean-action@v1 + tests-macos: + name: ${{ matrix.shard.name }} tests-macos + runs-on: macos-latest + timeout-minutes: 75 + strategy: + fail-fast: false + max-parallel: 7 + matrix: + shard: + - name: lint-type + tools: "python" + markers: "" + - name: core + tools: "python node go ruby nix" + markers: "python or go or bash or markdown or yaml or json or toml or ruby or snapshot" + - name: web + tools: "python node web" + markers: "typescript or vue or svelte or php or solidity or haxe or elm or hlsl" + - name: jvm-dotnet + tools: "python java dotnet clojure" + markers: "java or kotlin or groovy or scala or clojure or csharp or fsharp" + - name: compiled + tools: "python compiled haskell swift lean" + markers: "rust or cpp or zig or swift or haskell or pascal or systemverilog or lean4" + - name: data + tools: "python data ocaml nix" + markers: "r or julia or ocaml or fortran or terraform or nix" + - name: niche + tools: "python node elixir niche" + markers: "elixir or lua or luau or perl or rego or ansible or dart or powershell or crystal or erlang or al or matlab or msl" + steps: + - uses: actions/checkout@v4 with: - lake-package-directory: test/resources/repos/lean4/test_repo - - name: Cache language servers - id: cache-language-servers - uses: actions/cache@v3 + persist-credentials: false + + - name: Run shard + uses: ./.github/actions/setup-and-test-shard with: - path: ~/.serena/language_servers/static - key: language-servers-${{ runner.os }}-v1 - restore-keys: | - language-servers-${{ runner.os }}- - - name: Report free disk space - if: runner.os == 'Linux' - run: | - echo "Free disk space before tests:" - df -h - - name: Test with pytest - shell: bash - run: uv run poe test -q --tb=short - - name: Type-checking with mypy - shell: bash - run: uv run poe type-check + shard-name: ${{ matrix.shard.name }} + shard-tools: ${{ matrix.shard.tools }} + shard-markers: ${{ matrix.shard.markers }} diff --git a/README.md b/README.md index 3a3c181d0..6f8a0bfdf 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Serena incorporates a powerful abstraction layer for the integration of language The underlying language servers are typically open-source projects or at least freely available for use. When using Serena's language server backend, we provide **support for over 40 programming languages**, including -AL, Ansible, Bash, C#, C/C++, Clojure, Crystal, Dart, Elixir, Elm, Erlang, Fortran, F#, GLSL, Go, Groovy, Haskell, Haxe, HLSL, Java, JavaScript, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Solidity, Svelte, Swift, TOML, TypeScript, Vue, WGSL, YAML, and Zig. +AL, Ansible, Bash, C#, C/C++, Clojure, Crystal, Dart, Elixir, Elm, Erlang, Fortran, F#, GLSL, Go, Groovy, Haskell, Haxe, HLSL, Java, JavaScript, JSON, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, Solidity, Svelte, Swift, TOML, TypeScript, Vue, WGSL, YAML, and Zig. ### The Serena JetBrains Plugin diff --git a/src/solidlsp/language_servers/svelte_language_server.py b/src/solidlsp/language_servers/svelte_language_server.py index deead8ef0..817e5a48a 100644 --- a/src/solidlsp/language_servers/svelte_language_server.py +++ b/src/solidlsp/language_servers/svelte_language_server.py @@ -23,7 +23,7 @@ from solidlsp.ls import LSPFileBuffer, SolidLanguageServer from solidlsp.ls_config import FilenameMatcher, Language, LanguageServerConfig from solidlsp.ls_exceptions import SolidLSPException -from solidlsp.ls_types import Location +from solidlsp.ls_types import Location, WorkspaceEdit from solidlsp.ls_utils import PathUtils from solidlsp.lsp_protocol_handler import lsp_types from solidlsp.lsp_protocol_handler.lsp_types import DocumentSymbol, InitializeParams, SymbolInformation @@ -307,19 +307,19 @@ def _send_ts_references_request(self, relative_file_path: str, line: int, column return result @override - def request_references(self, relative_file_path: str, line: int, column: int) -> list[lsp_types.Location]: + def request_references(self, relative_file_path: str, line: int, column: int) -> list[Location]: self._ensure_ls_operational() return self._send_ts_references_request(relative_file_path, line=line, column=column) @override - def request_definition(self, relative_file_path: str, line: int, column: int) -> list[lsp_types.Location]: + def request_definition(self, relative_file_path: str, line: int, column: int) -> list[Location]: self._ensure_ls_operational() assert self._ts_server is not None with self._ts_server.open_file(relative_file_path): return self._ts_server.request_definition(relative_file_path, line, column) @override - def request_rename_symbol_edit(self, relative_file_path: str, line: int, column: int, new_name: str) -> lsp_types.WorkspaceEdit | None: + def request_rename_symbol_edit(self, relative_file_path: str, line: int, column: int, new_name: str) -> WorkspaceEdit | None: self._ensure_ls_operational() assert self._ts_server is not None with self._ts_server.open_file(relative_file_path): @@ -668,7 +668,7 @@ def stop(self, shutdown_timeout: float = 5.0) -> None: super().stop(shutdown_timeout) @override - def _get_preferred_definition(self, definitions: list[lsp_types.Location]) -> lsp_types.Location: + def _get_preferred_definition(self, definitions: list[Location]) -> Location: return prefer_non_node_modules_definition(definitions) @override