Skip to content

core-cpp 0.5.0

Latest

Choose a tag to compare

@github-actions github-actions released this 26 Sep 09:24
· 3 commits to master since this release

Breaking

  • The WFMO backend is removed: the completion port is Windows' only backend (core-cpp#6).
    BackendKind::Wfmo, WfmoBackend and the readiness socket transport it drove
    (WindowsSocket, WindowsListener) are gone, after the release in which IOCP was the default
    and WFMO a fallback. makeDefaultBackend() on Windows no longer falls back: a completion port
    the kernel refuses to create is handle exhaustion, and it propagates. listen, listenUnix,
    adoptListener, adoptSocket and the dials refuse a loop whose backend lends no completion port
    with NetErrorCode::Unsupported, where they used to hand out a readiness socket; a dial refuses
    before it creates a socket, so nothing reaches the peer. Two defects of
    the removed transport go with it: a closed socket's parked read now answers Cancelled on every
    platform, where WindowsSocket answered BadHandle (core-cpp#46), and the WFMO-only
    destruction gap of core-cpp#50 has nothing left to apply to. connectUnix's socket on Windows is
    now made uninheritable, as every other one is (core-cpp#28).
    • Migration: no consumer names any of these; a program that did replaces
      makeBackend(BackendKind::Wfmo) with makeDefaultBackend(). BackendKind's enumerators after
      Iocp shift down by one, so a value stored or sent as an integer is re-read by name
      (toString). A Windows test double that stood in for the loop's backend and expected a
      socket from the factories needs a completion port, or drives an ISocket of its own.
  • core::platform::EnvironmentProvider is ProcessEnvironment, a core::Environment; the
    working directory has its own seam; Windows reads and writes the environment in UTF-8

    (core-cpp#7). Two seams answered "read HOME", with two doubles a mixed test had to keep
    agreeing. core::Environment is now the one read seam, and ProcessEnvironment -- what a shell
    writes -- derives from it, so code that only reads is handed the same object and
    testing::TestProcessEnvironment is the double for both. set, unset, exportVariable and
    setAndExport return std::expected<void, PlatformError>, where they returned void and dropped
    the error; a name that is empty or holds = or NUL, or a value that holds NUL, is
    PlatformError::InvalidArgument (a new enumerator, last, so no other value moves).
    changeDirectory and currentDirectory move to core::platform::WorkingDirectory
    (nativeWorkingDirectory(), testing::TestWorkingDirectory), and homeDirectory, userName
    and configHome are the free functions of <core/platform/UserPaths.hpp> over a
    core::Environment const& (userName is new there). On Windows, core::LiveEnvironment,
    core::setProcessEnvironmentVariable and unsetProcessEnvironmentVariable go through
    GetEnvironmentVariableW/SetEnvironmentVariableW, converting to and from UTF-8, where the
    code-page API mangled a value such as a user profile path outside the ANSI code page; a name or
    value that is not UTF-8 is refused (std::errc::invalid_argument), and ProcessEnvironment::keys()
    converts UTF-16 names rather than narrowing them a code unit at a time. The read seam's
    interface is unchanged.
    • Migration: <core/platform/EnvironmentProvider.hpp> is <core/platform/ProcessEnvironment.hpp>;
      EnvironmentProvider is ProcessEnvironment, nativeEnvironmentProvider() is
      nativeProcessEnvironment(), and testing::TestEnvironmentProvider is
      testing::TestProcessEnvironment (<core/platform/testing/TestProcessEnvironment.hpp>), which no
      longer takes an initial directory. Handle or discard the std::expected from every set,
      unset, exportVariable and setAndExport (std::ignore = env.set(...) where a failure is
      acceptable). Replace env.changeDirectory(p) and env.currentDirectory() with a
      WorkingDirectory& the object is given -- nativeWorkingDirectory() in a composition root,
      testing::TestWorkingDirectory(initial) with addValidPath in a test. Its
      currentDirectory() returns a std::filesystem::path, where the old member returned a UTF-8
      std::string, so the answer round-trips through changeDirectory() on Windows whatever it
      spells: a caller that wants the text takes core::platform::normalizePath(cwd.currentDirectory()),
      and one that compared with a string literal compares its generic_string(). Replace
      env.homeDirectory(), env.userName() and env.configHome() with
      core::platform::homeDirectory(env), userName(env) and configHome(env). A function that
      only reads can take a core::Environment const& and be handed either double. contour, which
      uses only core::Environment, changes nothing.
  • core::cli::parse() returns std::expected<FlagStore, ParseError> and throws nothing
    (core-cpp#13). It returned std::optional<FlagStore> -- std::nullopt for tokens left over --
    and threw core::cli::ParserError for a value of the wrong type, a missing value or an explicit
    empty one, and std::invalid_argument for a missing required option. Every one of those is now a
    ParseError: a ParseErrorKind (NotEnoughArguments, InvalidValue, EmptyValue,
    UnexpectedToken, MissingRequiredOption), the index of the token at fault and a message.
    ParserError is removed. Numbers are read whole, with std::from_chars (floating point with
    std::strtod): 12abc is refused rather than read as 12, -1 is no longer accepted -- and
    wrapped -- as an unsigned, a leading + or leading whitespace (+5, 5), which std::stoi
    and std::stoul accepted, is refused, and a value out of the type's range is refused rather
    than truncated. parse() is [[nodiscard]]: a call that discards the result no longer compiles
    under -Werror. App::run() and App::reparseParameters() print the error's message; their signatures are
    unchanged.
    • Migration: a call site that tested has_value() or used *parsed and parsed-> compiles
      as it is when its variable is auto; one that names the type spells
      std::expected<core::cli::FlagStore, core::cli::ParseError>, or auto. Replace a try/catch
      around parse() with a test of the result, and report parsed.error().message. tuidu's
      parseCommandLine() (src/tuidu/Cli.cpp) is the one consumer call site: it declares
      std::optional<core::cli::FlagStore> parsed and catches std::exception around the call.
      contour and endo use core::cli::App only, and need no change.

Added

  • core::async::TaskKind and RunTask::kind(): an around-task hook can tell a coroutine
    resumption from a posted callable
    (core-cpp#53). TaskKind::Callable is what post and
    tryPost were given; TaskKind::Resumption is a coroutine arriving through submit or
    trySubmit, as a handle or as ParkedWork, a ResumeOn hop, or a KeyedStrands reroute
    through a retired key strand. A hook -- StrandOptions::aroundTask or a KeyedAroundTask --
    can now scope per-resumption context to coroutine resumptions only, rather than installing it
    around every callable posted to the same strand or key too. Read from what the task already
    holds, so it adds nothing to a task and costs one load where a hook asks. Additive; no signature
    changes.
  • core::net::closeLingering and LingerBounds (<core/net/LingeringClose.hpp>, from
    fastcached at 0708dd54; core-cpp#35): half-close, discard what the peer is still sending until
    it closes or a bound runs out -- the whole drain's time, the bytes discarded, the reads made --
    then close, so a reply written over a request left unread is followed by a FIN rather than
    destroyed by the reset a bare close sends. HttpLimits::linger bounds it for serve, by
    default 250 ms, 64 KiB and four reads -- smaller than fastcached's two seconds, because serve
    handles one connection at a time and a refused request holds its accept loop for up to that
    long. Additive.
  • core-cpp.open-work: every ## Open work entry leads with a core-cpp issue, and that issue is
    open
    (core-cpp#12). scripts/check-open-work.py reads every such section under .agent/ and
    docs/ and the top-level documents, and refuses an entry that does not lead with a core-cpp issue
    link, a link whose text and URL disagree, and a heading with no entries. The ctest runs that
    offline; CI's style job also runs it --online, which refuses an entry whose issue has closed,
    and one whose issue does not exist (404 or 410); it exits 77 (skipped, a warning in CI) rather
    than failing when it could not ask GitHub. It has a self-test.
  • A nightly job answers whether a provenance row's upstream has moved (core-cpp#33).
    core-cpp.upstream-drift needs the upstream checkouts beside core-cpp, so it skips on every CI
    runner and its coverage there was zero. The upstream-drift job of downstream.yml checks out
    contour, endo and fastcached with full history as siblings and runs the checker: drift is listed
    in the job summary, and a row the checkouts prove malformed, or an upstream the checker could not
    read, fails the job.
  • core-cpp.iterator-debug-canary proves the MSVC Debug runtime's iterator checks are live
    (core-cpp#11). In every MSVC-driver Debug build (cl-debug, clangcl-debug) it indexes a
    std::vector out of range and passes only on the runtime's own vector subscript out of range;
    a build where _ITERATOR_DEBUG_LEVEL fell below 2 reads the element instead, and fails.
  • A windows (clang-tidy) CI job analyses the Windows sources (core-cpp#38, core-cpp#44). The
    clang-tidy job's preset is Unix-only, so every windows/ source and every _WIN32 arm of a
    shared header went unanalysed while it was green. The new job runs the pinned clang-tidy over the
    clang-cl tree's compile database through scripts/tidy-database.py, which refuses a result that
    analysed fewer windows/ sources than git tracks, an analyser other than the pin, and a canary it
    did not report. (CXX_CLANG_TIDY is not used there: over clang-cl it hands clang-tidy a command it
    reads with exceptions disabled.) It is one of ci-ok's needs.

Changed

  • core::net and core::tui ask a promise for its stop token through
    core::async::HasStopToken
    (core-cpp#29), and ctest -L hygiene refuses the hand-spelled
    requires { awaiting.promise().stopToken(); } anywhere in the tree, so the concept is the one
    place that states the contract. No behaviour changes: every site assigned the token to a
    StopToken, which is what the concept requires it to convert to.
  • The hygiene scan's namespace-directory rule skips a leading forward-declaration block
    (core-cpp#23). A header under src/core/<dir>/ that opened with
    namespace core::platform { class Wakeup; } was refused, because that was its first named
    namespace, so it had to include the other module's header instead. A block whose body is only
    class/struct/union/enum declarations ending in ; defines nothing and is now skipped,
    and the rule applies to the first namespace after it. core/tui/TerminalInput.hpp forward-declares
    core::platform::Wakeup accordingly and no longer includes core/platform/Wakeup.hpp; a file
    that used Wakeup through that include includes it itself (none of the consumers does).

Fixed

  • serve's refusal of a request it did not read to its end reaches the client (core-cpp#35).
    A 413 or 400 was written over request bytes still unread, and the connection's bare close then
    sent a reset rather than a FIN: the client read the refusal followed by a connection reset, and
    on Windows could lose the refusal itself. The refusal now closes through closeLingering. A
    request read in full and answered closes as before.
  • NativeFileSystem reports a failure on a path the ANSI code page cannot spell (core-cpp#26).
    Its error messages spelled the path with path::string(), which on Windows narrows through the
    code page: such a name was mangled, and MSVC's conversion throws there, so the error path itself
    threw out of readFile, rename, listDirectory and the rest. The messages now spell the path
    in UTF-8, as the paths core-cpp hands back already are. POSIX was unaffected.
  • testing::InMemoryFileSystem answers as the native backend does in two more places
    (core-cpp#27). isExecutableFile, permissions and setPermissions follow a symlink to its
    target, so a dangling link is not executable and has no permissions to set, where the fake used
    to judge the link by bits recorded on the link itself. createDirectory refuses a path that is
    already there, directory or file, with "File exists", where it used to succeed. What the fake
    still does not model is tabled in docs/modules/platform.md.
  • A Windows SystemPipe's sockets are no longer inherited by child processes (core-cpp#28).
    ::socket() and ::accept() hand back inheritable handles there, so a consumer that spawned a
    process -- contour's and endo's shells -- handed the child the loop's wakeup channel, and a child
    that kept it open could hold it alive after the parent closed its end. The sockets are made with
    WSA_FLAG_NO_HANDLE_INHERIT and the accepted one has its inheritance cleared, as POSIX already
    sets FD_CLOEXEC.
  • Alt+Backspace reaches its key binding (core-cpp#21). VtParser read ESC DEL -- how xterm,
    VTE, iTerm and Alacritty send Alt+Backspace -- and ESC BS (Alt+Ctrl+H) as a bare Escape followed
    by a plain Backspace, so a modal took the Escape as cancel and DeleteBigWordBackward could fire
    only under the Kitty keyboard protocol. Both now decode to one KeyCode::Backspace with
    Modifier::Alt. No signature changes.
  • A character outside the BMP typed or pasted into a Windows console arrives as valid UTF-8
    (core-cpp#20). The console delivers U+1F600 as two key events, one per surrogate, and each was
    encoded on its own (CESU-8), which VtParser then decoded to two lone surrogates. The Windows
    input now pairs surrogates across reads (detail::Utf16ToUtf8, which replaces the private
    windows/Win32Utf.hpp), and an unpaired one becomes U+FFFD. In Win32 input mode, where the
    console reports each surrogate as its own CSI ... _ key, VtParser pairs the two keys the same
    way, across reads, and drops a half it cannot pair. VtParser drops what no UTF-8
    decoder may produce, on every platform: an encoded surrogate, an overlong encoding and a value
    above U+10FFFF. No public signature changes.
  • The vendoring tool refuses a DEST that is a symbolic link (core-cpp#25). CMake's EXISTS
    and IS_DIRECTORY resolve through a link, so a link to an empty directory passed every guard, and
    the replacement then renamed the link aside and put a real directory in its place: the consumer's
    link was gone, its target untouched, and the sync reported success. MODE=sync now refuses such a
    DEST by name, before anything is read or written, and says which directory to name instead.
  • A clang-tidy build re-analyses what a changed .clang-tidy or a replaced analyser governs
    (core-cpp#36). Neither was an input of any compile, so editing a rule, or installing another
    clang-tidy at the same path, re-analysed nothing whose object was current: no work to do meant
    "clean under the rules in force when each object was built". With CORE_CPP_CLANG_TIDY on, every
    analysed compile now depends on the analyser binary and on every .clang-tidy from its source's
    directory up to the tree's root, and core-cpp.tidy-inputs (registered only in a clang-tidy Ninja
    build, and run by CI's clang-tidy job) refuses a build whose statements do not.

Full Changelog: v0.4.3...v0.5.0