//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® import std; import Crafter.Build; namespace fs = std::filesystem; using namespace Crafter; extern "C" Configuration CrafterBuildProject(std::span args) { // TCP, HTTP/1.1 and TLS: what every native build has. constexpr std::array networkInterfaces = { "interfaces/Crafter.Network", "interfaces/Crafter.Network-ClientTCP", "interfaces/Crafter.Network-ListenerTCP", "interfaces/Crafter.Network-HTTP", "interfaces/Crafter.Network-HTTP1", "interfaces/Crafter.Network-Stream", "interfaces/Crafter.Network-TLS", "interfaces/Crafter.Network-ClientHTTP1", "interfaces/Crafter.Network-ListenerHTTP1", }; // HTTP/3, QUIC and WebTransport, over msquic. --no-quic leaves them out. constexpr std::array quicInterfaces = { "interfaces/Crafter.Network-ClientHTTP", "interfaces/Crafter.Network-ListenerHTTP", "interfaces/Crafter.Network-HTTP3", "interfaces/Crafter.Network-ClientQUIC", "interfaces/Crafter.Network-ListenerQUIC", "interfaces/Crafter.Network-WebTransport", }; // --no-quic is this project's own flag, so it stays out of the // dependencies' args: Crafter.Thread would otherwise build a second copy // of itself, hashed apart from the one the consumer imports. std::vector depArgs; for (std::string_view arg : args) { if (arg != "--no-quic") depArgs.emplace_back(arg); } // Crafter.Thread comes from forgejo, or with --local from the checkout // next to this one. --local stays in depArgs, so Crafter.Thread resolves // its own dependencies the same way. const bool useLocal = std::ranges::find(args, std::string_view("--local")) != args.end(); Configuration* thread = useLocal ? LocalProject({ .projectFile = "../Crafter.Thread/project.cpp", .args = depArgs }) : GitProject({ .source = { .url = "https://forgejo.catcrafts.net/Catcrafts/Crafter.Thread.git" }, .args = depArgs, }); Configuration cfg; cfg.path = "./"; cfg.name = "crafter-network"; cfg.outputName = "crafter-network"; cfg.type = ConfigurationType::LibraryStatic; ArgQuery opts = ApplyStandardArgs(cfg, args); cfg.dependencies = { thread }; // Browser path: any wasm32-* target gets the browser network stack // (fetch + WebTransport via JS glue). msquic and the POSIX socket // backends are skipped; the listener / TCP partitions stub to empty // modules via #ifdef CRAFTER_NETWORK_BROWSER in their interface files. // HTTP3 (varint / frame / QPACK codec) is dropped entirely — it threw // exceptions for protocol errors, which the wasm build's -fno-exceptions // forbids, and the browser's fetch() handles HTTP-layer framing itself. bool browser = cfg.target.find("wasm") != std::string::npos; if (browser) { cfg.defines.push_back({"CRAFTER_NETWORK_BROWSER", ""}); std::array browserIfaces = { "interfaces/Crafter.Network", "interfaces/Crafter.Network-ClientTCP", "interfaces/Crafter.Network-ListenerTCP", "interfaces/Crafter.Network-ClientHTTP", "interfaces/Crafter.Network-ListenerHTTP", "interfaces/Crafter.Network-HTTP", "interfaces/Crafter.Network-ClientQUIC", "interfaces/Crafter.Network-ListenerQUIC", "interfaces/Crafter.Network-WebTransport", }; std::array browserImpls = { "implementations/Crafter.Network-ClientHTTP-Browser", "implementations/Crafter.Network-ClientQUIC-Browser", }; cfg.GetInterfacesAndImplementations(browserIfaces, browserImpls); // JS glue shipped alongside the .wasm. The consuming executable's // wasi-browser runtime merges this into the env import object // before instantiation (mirrors Crafter.Graphics/dom-env.js). cfg.files.emplace_back(fs::path("additional/network-env.js")); return cfg; } constexpr std::array networkImplementations = { "implementations/Crafter.Network-ClientTCP", "implementations/Crafter.Network-ListenerTCP", "implementations/Crafter.Network-Stream", "implementations/Crafter.Network-TLS", "implementations/Crafter.Network-ClientHTTP1", "implementations/Crafter.Network-ListenerHTTP1", }; constexpr std::array quicImplementations = { "implementations/Crafter.Network-ClientHTTP", "implementations/Crafter.Network-ListenerHTTP", "implementations/Crafter.Network-ClientQUIC", "implementations/Crafter.Network-ListenerQUIC", "implementations/Crafter.Network-WebTransport", }; // --no-quic: TCP, HTTP/1.1 and TLS only, for consumers that need no more. // msquic takes a recursive clone and a CMake build, so leaving it out is // most of a fresh build. The browser build above has no msquic to leave // out and ignores the flag. const bool quic = !opts.Has("--no-quic"); if (quic) { // msquic — provides the QUIC transport used by ClientQUIC / ListenerQUIC. // Cloned + built via CMake into the per-project external cache; no system // package required. Pinned to a release (v2.6.3): main moves, and its // submodules (quictls / clog / etc.) with it. Crafter.Build checks the // submodules out at the revisions the pinned commit records. We disable // msquic's own tests, tools and perf binaries since we only need the // library. ExternalDependency& msquic = cfg.externalDependencies.emplace_back(); msquic.name = "msquic"; msquic.source.url = "https://github.com/microsoft/msquic.git"; msquic.source.commit = "16ca4d8d785d34fb22285e7c652e477ac16475a5"; msquic.builder = ExternalBuilder::CMake; // TLS from the system's OpenSSL (3.5 or newer, which has the QUIC // API), the same libssl the HTTP/1.1 TLS code links: one OpenSSL in // the process. The quictls fork msquic otherwise builds and bundles is // an older OpenSSL that still reads the system's openssl.cnf, and the // Flatpak runtime's (an engine section for the runtime's own OpenSSL, // config_diagnostics on) makes its initialisation fail: MsQuicOpen2 // returned QUIC_STATUS_TLS_ERROR (0x7e) there. msquic.options = { "-DQUIC_TLS_LIB=openssl", "-DQUIC_USE_EXTERNAL_OPENSSL=ON", "-DQUIC_BUILD_TEST=OFF", "-DQUIC_BUILD_TOOLS=OFF", "-DQUIC_BUILD_PERF=OFF", "-DQUIC_BUILD_SHARED=ON", }; msquic.includeDirs = { "src/inc" }; // msquic's CMakeLists overrides CMAKE_LIBRARY_OUTPUT_DIRECTORY with // QUIC_OUTPUT_DIR (defaults to bin/$), so libmsquic.so lands in // a subdir of the cmake build dir rather than at its root. Point the // linker at the actual output location. msquic.libDirs = { "bin/Release" }; msquic.libs = { "msquic" }; } else { cfg.defines.push_back({"CRAFTER_NETWORK_NO_QUIC", ""}); } // libssl/libcrypto — the TLS transport behind :TLS, i.e. https:// on the // HTTP/1.1 client and listener. A system package rather than a built // external: OpenSSL 3 is on every platform we target, and building it here // would mean shipping a second TLS stack alongside the one msquic already // links (quictls, which keeps its symbols to itself inside libmsquic.so). cfg.linkFlags.push_back("-lssl"); cfg.linkFlags.push_back("-lcrypto"); std::vector ifaces(networkInterfaces.begin(), networkInterfaces.end()); std::vector impls(networkImplementations.begin(), networkImplementations.end()); if (quic) { ifaces.insert(ifaces.end(), quicInterfaces.begin(), quicInterfaces.end()); impls.insert(impls.end(), quicImplementations.begin(), quicImplementations.end()); } cfg.GetInterfacesAndImplementations(ifaces, impls); // Linux-only: the POSIX socket backends, and msquic unless --no-quic. // The browser path above returns early, so wasm builds skip these. Each // test links the local crafter-network static lib via // .Dependencies({ &cfg }). Run the suite with --no-quic as well. Any // Linux triple: the toolchain's own is x86_64-unknown-linux-gnu. if (cfg.target.find("linux") != std::string::npos) { cfg.AddTest("ShouldConfigureTCP").Dependencies({ &cfg }); cfg.AddTest("ShouldRecieveTCP").Dependencies({ &cfg }); cfg.AddTest("ShouldInteropCurlHTTP1").Dependencies({ &cfg }); cfg.AddTest("ShouldInteropCurlHTTPS1").Dependencies({ &cfg }); cfg.AddTest("ShouldParseHTTP1").Dependencies({ &cfg }); cfg.AddTest("ShouldRequireClientCertificateHTTPS1").Dependencies({ &cfg }); cfg.AddTest("ShouldSendRecieveHTTP1").Dependencies({ &cfg }); cfg.AddTest("ShouldSendRecieveHTTPS1").Dependencies({ &cfg }); cfg.AddTest("ShouldSendRecieveKeepaliveHTTP1").Dependencies({ &cfg }); cfg.AddTest("ShouldSendRecieveLargeHTTP1").Dependencies({ &cfg }); cfg.AddTest("ShouldSurviveAbuseHTTP1").Dependencies({ &cfg }); if (quic) { cfg.AddTest("ShouldEchoWebTransport").Dependencies({ &cfg }); cfg.AddTest("ShouldFallbackUnknownRoutes").Dependencies({ &cfg }); cfg.AddTest("ShouldHonourQUICSettings").Dependencies({ &cfg }); cfg.AddTest("ShouldNotDropEarlyStreams").Dependencies({ &cfg }); cfg.AddTest("ShouldSend").Dependencies({ &cfg }); cfg.AddTest("ShouldSendRecieveHTTP").Dependencies({ &cfg }); cfg.AddTest("ShouldSendRecieveKeepaliveHTTP").Dependencies({ &cfg }); cfg.AddTest("ShouldSendRecieveLargeHTTP").Dependencies({ &cfg }); cfg.AddTest("ShouldSendRecieveQUICDatagram").Dependencies({ &cfg }); cfg.AddTest("ShouldSendRecieveQUICStream").Dependencies({ &cfg }); cfg.AddTest("ShouldReportClosedQUICStream").Dependencies({ &cfg }); cfg.AddTest("ShouldSurviveConnectionChurn").Dependencies({ &cfg }); } else { // Only a --no-quic build can show that msquic stayed out. cfg.AddTest("ShouldBuildWithoutQUIC").Dependencies({ &cfg }); } } return cfg; }