diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 05f74b6378c6..568828b17bee 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -7,6 +7,7 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: + SPEC_SPLIT_DOTS: 160 CI_LLVM_VERSION: "18.1.1" jobs: @@ -266,13 +267,47 @@ jobs: run: make -f Makefile.win samples x86_64-windows-release: - if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/')) needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm-libs, x86_64-windows-llvm-dlls] uses: ./.github/workflows/win_build_portable.yml with: release: true llvm_version: "18.1.1" + x86_64-windows-test-interpreter: + runs-on: windows-2022 + needs: [x86_64-windows-release] + steps: + - name: Disable CRLF line ending substitution + run: | + git config --global core.autocrlf false + + - name: Download Crystal source + uses: actions/checkout@v4 + + - name: Download Crystal executable + uses: actions/download-artifact@v4 + with: + name: crystal-release + path: build + + - name: Restore LLVM + uses: actions/cache/restore@v4 + with: + path: llvm + key: llvm-libs-${{ env.CI_LLVM_VERSION }}-msvc + fail-on-cache-miss: true + + - name: Set up environment + run: | + Add-Content $env:GITHUB_PATH "$(pwd)\build" + Add-Content $env:GITHUB_ENV "CRYSTAL_SPEC_COMPILER_BIN=$(pwd)\build\crystal.exe" + + - name: Run stdlib specs with interpreter + run: bin\crystal i spec\std_spec.cr + + - name: Run primitives specs with interpreter + run: bin\crystal i spec\primitives_spec.cr + x86_64-windows-installer: if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/')) runs-on: windows-2022 diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml index d2ed6469d264..12ee17da9e68 100644 --- a/.github/workflows/win_build_portable.yml +++ b/.github/workflows/win_build_portable.yml @@ -114,7 +114,7 @@ jobs: - name: Build Crystal run: | bin/crystal.bat env - make -f Makefile.win -B ${{ inputs.release && 'release=1' || '' }} + make -f Makefile.win -B ${{ inputs.release && 'release=1' || '' }} interpreter=1 - name: Download shards release uses: actions/checkout@v4 diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index 451960a8c79f..6bd04ab3e2f2 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -6,6 +6,12 @@ require "http/server" require "http/log" require "log/spec" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending HTTP::Client + {% skip_file %} +{% end %} + private def test_server(host, port, read_time = 0.seconds, content_type = "text/plain", write_response = true, &) server = TCPServer.new(host, port) begin diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index 5e1e5dab76f6..3980084ea414 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -4,6 +4,12 @@ require "http/client" require "../../../support/ssl" require "../../../support/channel" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending HTTP::Server + {% skip_file %} +{% end %} + # TODO: replace with `HTTP::Client.get` once it supports connecting to Unix socket (#2735) private def unix_request(path) UNIXSocket.open(path) do |io| diff --git a/spec/std/http/web_socket_spec.cr b/spec/std/http/web_socket_spec.cr index 75a54e91fb2e..164a1d067df5 100644 --- a/spec/std/http/web_socket_spec.cr +++ b/spec/std/http/web_socket_spec.cr @@ -7,6 +7,12 @@ require "../../support/fibers" require "../../support/ssl" require "../socket/spec_helper.cr" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending HTTP::WebSocket + {% skip_file %} +{% end %} + private def assert_text_packet(packet, size, final = false) assert_packet packet, HTTP::WebSocket::Protocol::Opcode::TEXT, size, final: final end diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 6974a9fe3466..c584ec81a1e8 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -816,23 +816,26 @@ describe IO do io.gets_to_end.should eq("\r\nFoo\nBar") end - it "gets ascii from socket (#9056)" do - server = TCPServer.new "localhost", 0 - sock = TCPSocket.new "localhost", server.local_address.port - begin - sock.set_encoding("ascii") - spawn do - client = server.accept - message = client.gets - client << "#{message}\n" + # TODO: Windows networking in the interpreter requires #12495 + {% unless flag?(:interpreted) || flag?(:win32) %} + it "gets ascii from socket (#9056)" do + server = TCPServer.new "localhost", 0 + sock = TCPSocket.new "localhost", server.local_address.port + begin + sock.set_encoding("ascii") + spawn do + client = server.accept + message = client.gets + client << "#{message}\n" + end + sock << "K\n" + sock.gets.should eq("K") + ensure + server.close + sock.close end - sock << "K\n" - sock.gets.should eq("K") - ensure - server.close - sock.close end - end + {% end %} end describe "encode" do diff --git a/spec/std/oauth2/client_spec.cr b/spec/std/oauth2/client_spec.cr index 3ee66e29ab49..ee445f3426e7 100644 --- a/spec/std/oauth2/client_spec.cr +++ b/spec/std/oauth2/client_spec.cr @@ -3,6 +3,12 @@ require "oauth2" require "http/server" require "../http/spec_helper" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending OAuth2::Client + {% skip_file %} +{% end %} + describe OAuth2::Client do describe "authorization uri" do it "gets with default endpoint" do diff --git a/spec/std/openssl/ssl/server_spec.cr b/spec/std/openssl/ssl/server_spec.cr index 2e0e413a618d..8618ed780a50 100644 --- a/spec/std/openssl/ssl/server_spec.cr +++ b/spec/std/openssl/ssl/server_spec.cr @@ -3,6 +3,12 @@ require "socket" require "../../spec_helper" require "../../../support/ssl" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending OpenSSL::SSL::Server + {% skip_file %} +{% end %} + describe OpenSSL::SSL::Server do it "sync_close" do TCPServer.open(0) do |tcp_server| diff --git a/spec/std/openssl/ssl/socket_spec.cr b/spec/std/openssl/ssl/socket_spec.cr index bbc5b11e4b9b..47374ce28cca 100644 --- a/spec/std/openssl/ssl/socket_spec.cr +++ b/spec/std/openssl/ssl/socket_spec.cr @@ -4,6 +4,12 @@ require "../../spec_helper" require "../../socket/spec_helper" require "../../../support/ssl" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending OpenSSL::SSL::Socket + {% skip_file %} +{% end %} + describe OpenSSL::SSL::Socket do describe OpenSSL::SSL::Socket::Server do it "auto accept client by default" do diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 57f90121c26b..d41ee0bed242 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -55,7 +55,12 @@ private def newline end # interpreted code doesn't receive SIGCHLD for `#wait` to work (#12241) -pending_interpreted describe: Process do +{% if flag?(:interpreted) && !flag?(:win32) %} + pending Process + {% skip_file %} +{% end %} + +describe Process do describe ".new" do it "raises if command doesn't exist" do expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index 2127e196b746..98555937dea3 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -2,6 +2,12 @@ require "./spec_helper" require "../../support/tempfile" require "../../support/win32" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending Socket + {% skip_file %} +{% end %} + describe Socket, tags: "network" do describe ".unix" do it "creates a unix socket" do diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index 68c00ccd2e79..f3d460f92401 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -3,6 +3,12 @@ require "./spec_helper" require "../../support/win32" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending TCPSocket + {% skip_file %} +{% end %} + describe TCPSocket, tags: "network" do describe "#connect" do each_ip_family do |family, address| diff --git a/spec/std/socket/unix_server_spec.cr b/spec/std/socket/unix_server_spec.cr index ca364f08667c..60f0279b4091 100644 --- a/spec/std/socket/unix_server_spec.cr +++ b/spec/std/socket/unix_server_spec.cr @@ -4,6 +4,12 @@ require "../../support/fibers" require "../../support/channel" require "../../support/tempfile" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending UNIXServer + {% skip_file %} +{% end %} + describe UNIXServer do describe ".new" do it "raises when path is too long" do diff --git a/spec/std/socket/unix_socket_spec.cr b/spec/std/socket/unix_socket_spec.cr index 24777bada67f..c51f37193c0e 100644 --- a/spec/std/socket/unix_socket_spec.cr +++ b/spec/std/socket/unix_socket_spec.cr @@ -2,6 +2,12 @@ require "spec" require "socket" require "../../support/tempfile" +# TODO: Windows networking in the interpreter requires #12495 +{% if flag?(:interpreted) && flag?(:win32) %} + pending UNIXSocket + {% skip_file %} +{% end %} + describe UNIXSocket do it "raises when path is too long" do with_tempfile("unix_socket-too_long-#{("a" * 2048)}.sock") do |path| diff --git a/spec/std/uuid_spec.cr b/spec/std/uuid_spec.cr index 48cc3351a3c6..5d7e627031f0 100644 --- a/spec/std/uuid_spec.cr +++ b/spec/std/uuid_spec.cr @@ -1,6 +1,7 @@ require "spec" require "uuid" require "spec/helpers/string" +require "../support/wasm32" describe "UUID" do describe "#==" do diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index 05bf988c9218..772e4c5c232f 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -133,15 +133,25 @@ class Crystal::Loader end def load_file?(path : String | ::Path) : Bool + # API sets shouldn't be linked directly from linker flags, but just in case + if api_set?(path) + return load_dll?(path.to_s) + end + return false unless File.file?(path) # On Windows, each `.lib` import library may reference any number of `.dll` # files, whose base names may not match the library's. Thus it is necessary # to extract this information from the library archive itself. - System::LibraryArchive.imported_dlls(path).each do |dll| - dll_full_path = @dll_search_paths.try &.each do |search_path| - full_path = File.join(search_path, dll) - break full_path if File.file?(full_path) + System::LibraryArchive.imported_dlls(path).all? do |dll| + # API set names do not refer to physical filenames despite ending with + # `.dll`, and therefore should not use a path search: + # https://learn.microsoft.com/en-us/cpp/windows/universal-crt-deployment?view=msvc-170#local-deployment + unless api_set?(dll) + dll_full_path = @dll_search_paths.try &.each do |search_path| + full_path = File.join(search_path, dll) + break full_path if File.file?(full_path) + end end dll = dll_full_path || dll @@ -152,13 +162,16 @@ class Crystal::Loader # # Note that the compiler's directory and PATH are effectively searched # twice when coming from the interpreter - handle = open_library(dll) - return false unless handle - - @handles << handle - @loaded_libraries << (module_filename(handle) || dll) + load_dll?(dll) end + end + + private def load_dll?(dll) + handle = open_library(dll) + return false unless handle + @handles << handle + @loaded_libraries << (module_filename(handle) || dll) true end @@ -190,6 +203,12 @@ class Crystal::Loader @handles.clear end + # Returns whether *dll* names an API set according to: + # https://learn.microsoft.com/en-us/windows/win32/apiindex/windows-apisets#api-set-contract-names + private def api_set?(dll) + dll.to_s.matches?(/^(?:api-|ext-)[a-zA-Z0-9-]*l\d+-\d+-\d+\.dll$/) + end + private def module_filename(handle) Crystal::System.retry_wstr_buffer do |buffer, small_buf| len = LibC.GetModuleFileNameW(handle, buffer, buffer.size) diff --git a/src/kernel.cr b/src/kernel.cr index 8c84a197b78f..3791e0d4c451 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -616,3 +616,16 @@ end Crystal::System::Signal.setup_default_handlers {% end %} {% end %} + +# This is a temporary workaround to ensure there is always something in the IOCP +# event loop being awaited, since both the interrupt loop and the fiber stack +# pool collector are disabled in interpreted code. Without this, asynchronous +# code that bypasses `Crystal::IOCP::OverlappedOperation` does not currently +# work, see https://github.com/crystal-lang/crystal/pull/14949#issuecomment-2328314463 +{% if flag?(:interpreted) && flag?(:win32) %} + spawn(name: "Interpreter idle loop") do + while true + sleep 1.day + end + end +{% end %}