Repository: dankamongmen/notcurses (language: python)

Issue:
look at using libdeflate
when using Kitty graphics, compression is responsible for almost half of our compute, and we absolutely need it for networked connections. libdeflate is supposedly a good deal faster? it's a different API, but it is available just about everywhere, so we might still keep a single compression path.

https://github.com/ebiggers/libdeflate#api
look at using libdeflate
when using Kitty graphics, compression is responsible for almost half of our compute, and we absolutely need it for networked connections. libdeflate is supposedly a good deal faster? it's a different API, but it is available just about everywhere, so we might still keep a single compression path.

https://github.com/ebiggers/libdeflate#api
package libdeflate on fedora rawhide
There's no libdeflate on Fedora, at least in Rawhide. There was a rust-libdeflate-sys, but it bundled libdeflate(!), and isn't present anymore anyway (replaced by rust-native rust-libflate-sys). Either we need to package it and get it in Rawhide and anywhere we want to ship 3.0.0, or we need to make libdeflate optional (at least for now).

I'm inclined to package it, ugh how annoying.


Discussion / hints:
http://www.htslib.org/benchmarks/zlib.html
this seems promising and easy enough that i'd really like to try it for 3.0.0.
http://www.htslib.org/benchmarks/zlib.html
this seems promising and easy enough that i'd really like to try it for 3.0.0.


Fix patch:
diff --git a/.drone.yml b/.drone.yml
index cc90a0eeac..2ecd2bf56f 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -5,7 +5,7 @@ name: debian-unstable
 
 steps:
 - name: debian-build
-  image: dankamongmen/unstable_builder:2021-11-06a
+  image: dankamongmen/unstable_builder:2021-11-14a
   commands:
     - export LANG=en_US.UTF-8
     - export TERM=xterm
@@ -21,30 +21,31 @@ steps:
     - LDFLAGS=-L/usr/local/lib CFLAGS=-I/usr/local/include python3 setup.py sdist build install
     - env LD_LIBRARY_PATH=/usr/local/lib ./notcurses-pydemo > /dev/null
     - env LD_LIBRARY_PATH=/usr/local/lib ./ncdirect-pydemo > /dev/null
----
-kind: pipeline
-type: docker
-name: fedora-rawhide
-
-steps:
-- name: fedora-rawhide
-  image: dankamongmen/rawhide:2021-07-15a
-  commands:
-    - export LANG=en_US.UTF-8
-    - export TERM=xterm
-    - mkdir build
-    - cd build
-    - cmake -DCMAKE_BUILD_TYPE=Release -DUSE_MULTIMEDIA=oiio -DUSE_QRCODEGEN=on ..
-    - make -j2
-    - ctest --output-on-failure
-    - make install
-    - ldconfig
-    - cd ../cffi
-    - LDFLAGS=-L/usr/local/lib CFLAGS=-I/usr/local/include python3 setup.py sdist build install
-    - cd ../python
-    - LDFLAGS=-L/usr/local/lib CFLAGS=-I/usr/local/include python3 setup.py sdist build install
-    - env LD_LIBRARY_PATH=/usr/local/lib ./notcurses-pydemo > /dev/null
-    - env LD_LIBRARY_PATH=/usr/local/lib ./ncdirect-pydemo > /dev/null
+# gone until we package libdeflate
+#---
+#kind: pipeline
+#type: docker
+#name: fedora-rawhide
+#
+#steps:
+#- name: fedora-rawhide
+#  image: dankamongmen/rawhide:2021-11-14a
+#  commands:
+#    - export LANG=en_US.UTF-8
+#    - export TERM=xterm
+#    - mkdir build
+#    - cd build
+#    - cmake -DCMAKE_BUILD_TYPE=Release -DUSE_MULTIMEDIA=oiio -DUSE_QRCODEGEN=on ..
+#    - make -j2
+#    - ctest --output-on-failure
+#    - make install
+#    - ldconfig
+#    - cd ../cffi
+#    - LDFLAGS=-L/usr/local/lib CFLAGS=-I/usr/local/include python3 setup.py sdist build install
+#    - cd ../python
+#    - LDFLAGS=-L/usr/local/lib CFLAGS=-I/usr/local/include python3 setup.py sdist build install
+#    - env LD_LIBRARY_PATH=/usr/local/lib ./notcurses-pydemo > /dev/null
+#    - env LD_LIBRARY_PATH=/usr/local/lib ./ncdirect-pydemo > /dev/null
 ---
 kind: pipeline
 type: docker
@@ -52,7 +53,7 @@ name: ubuntu-impish
 
 steps:
 - name: ubuntu-build
-  image: dankamongmen/impish:2021-08-19a
+  image: dankamongmen/impish:2021-11-14b
   commands:
     - export LANG=es_ES.UTF-8
     - export TERM=xterm
@@ -74,7 +75,7 @@ name: alpine-edge
 
 steps:
 - name: alpine-edge
-  image: dankamongmen/edge_builder:2021-11-08a
+  image: dankamongmen/edge_builder:2021-11-14a
   commands:
     - export LANG=en_US.UTF-8
     - export TERM=vt100
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3785dd223d..0c17dfe972 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -137,8 +137,6 @@ endif()
 # feature_summary + set_package_properties to fail in one fell swoop.
 find_package(Threads)
 set_package_properties(Threads PROPERTIES TYPE REQUIRED)
-find_package(ZLIB)
-set_package_properties(ZLIB PROPERTIES TYPE REQUIRED)
 # platform-specific logics
 if(WIN32)
   set(LIBRT_LIBRARIES wsock32 ws2_32 Secur32)
@@ -168,6 +166,14 @@ endif()
 find_library(unistring unistring REQUIRED)
 set_property(GLOBAL APPEND PROPERTY PACKAGES_FOUND libunistring)
 set_package_properties(libunistring PROPERTIES TYPE REQUIRED)
+unset(HAVE_DEFLATE_H CACHE)
+check_include_file("libdeflate.h" HAVE_DEFLATE_H)
+if(NOT "${HAVE_DEFLATE_H}")
+  message(FATAL_ERROR "Couldn't find libdeflate.h")
+endif()
+find_library(libdeflate deflate REQUIRED)
+set_property(GLOBAL APPEND PROPERTY PACKAGES_FOUND libdeflate)
+set_package_properties(libdeflate PROPERTIES TYPE REQUIRED)
 if(${USE_GPM}) # no pkgconfig from gpm
 unset(HAVE_GPM_H CACHE)
 check_include_file("gpm.h" HAVE_GPM_H)
@@ -231,7 +237,7 @@ target_include_directories(notcurses-core
     "${CMAKE_REQUIRED_INCLUDES}"
     "${PROJECT_BINARY_DIR}/include"
     "${TERMINFO_INCLUDE_DIRS}"
-    "${ZLIB_INCLUDE_DIRS}"
+    "${libdeflate_INCLUDE_DIRS}"
 )
 target_include_directories(notcurses-core-static
   BEFORE
@@ -241,11 +247,11 @@ target_include_directories(notcurses-core-static
     "${CMAKE_REQUIRED_INCLUDES}"
     "${PROJECT_BINARY_DIR}/include"
     "${TERMINFO_STATIC_INCLUDE_DIRS}"
-    "${ZLIB_STATIC_INCLUDE_DIRS}"
+    "${libdeflate_STATIC_INCLUDE_DIRS}"
 )
 target_link_libraries(notcurses-core
   PRIVATE
-    "${ZLIB_LIBRARIES}"
+    "${libdeflate}"
     "${TERMINFO_LIBRARIES}"
     "${LIBM}"
     "${unistring}"
@@ -256,7 +262,7 @@ target_link_libraries(notcurses-core
 )
 target_link_libraries(notcurses-core-static
   PRIVATE
-    "${ZLIB_STATIC_LIBRARIES}"
+    "${libdeflate_STATIC_LIBRARIES}"
     "${TERMINFO_STATIC_LIBRARIES}"
     "${LIBM}"
     "${unistring}"
@@ -268,12 +274,12 @@ target_link_libraries(notcurses-core-static
 target_link_directories(notcurses-core
   PRIVATE
     "${TERMINFO_LIBRARY_DIRS}"
-    "${ZLIB_LIBRARY_DIRS}"
+    "${libdeflate_LIBRARY_DIRS}"
 )
 target_link_directories(notcurses-core-static
   PRIVATE
     "${TERMINFO_STATIC_LIBRARY_DIRS}"
-    "${ZLIB_STATIC_LIBRARY_DIRS}"
+    "${libdeflate_STATIC_LIBRARY_DIRS}"
 )
 if(${USE_QRCODEGEN})
 target_link_libraries(notcurses-core PRIVATE qrcodegen)
diff --git a/INSTALL.md b/INSTALL.md
index b596db736f..f1ddc43f8e 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -2,11 +2,15 @@
 
 You are generally recommended to use your distro's package manager to install
 Notcurses; it is [available](https://repology.org/project/notcurses/versions)
-prepackaged on many distributions. Otherwise, acquire the current source via
+prepackaged on many distributions. If you wish to build from source, read on.
+
+## Prerequisites for building
+
+Acquire the current source via
 
 `git clone https://github.com/dankamongmen/notcurses.git`
 
-## Prerequisites
+There are no submodules. Dependencies are fairly minimal.
 
 ### APT
 
@@ -32,6 +36,12 @@ If you only intend to build core Notcurses (without multimedia support), you
 can omit `OpenImageIO-devel`. If you're building outside Fedora Core (e.g. with
 RPM Fusion), you might want to use FFmpeg rather than OpenImageIO.
 
+### FreeBSD / DragonFly BSD
+
+Install build dependencies:
+
+`pkg install devel/ncurses multimedia/ffmpeg graphics/qr-code-generator devel/libunistring`
+
 ### Microsoft Windows
 
 Currently, building on Windows requires [MSYS2](https://www.msys2.org/) in its
diff --git a/src/fetch/main.c b/src/fetch/main.c
index 73f0c9962c..143ddceb26 100644
--- a/src/fetch/main.c
+++ b/src/fetch/main.c
@@ -6,14 +6,15 @@
 #include <locale.h>
 #include <strings.h>
 #include <pthread.h>
-#include <langinfo.h>
 #include <semaphore.h>
 #include <sys/param.h>
 #include <sys/types.h>
 #if defined(__linux__) || defined(__gnu_hurd__)
+#include <langinfo.h>
 #include <sys/utsname.h>
 #include <sys/sysinfo.h>
 #elif !defined(__MINGW64__)
+#include <langinfo.h>
 #include <sys/sysctl.h>
 #include <sys/utsname.h>
 #else
diff --git a/src/lib/banner.c b/src/lib/banner.c
index 206f25a9e2..edb3fca0e7 100644
--- a/src/lib/banner.c
+++ b/src/lib/banner.c
@@ -1,4 +1,4 @@
-#include <zlib.h>
+#include <libdeflate.h>
 #include "internal.h"
 
 // only invoked without suppress banners flag. prints various warnings based on
@@ -61,7 +61,7 @@ int init_banner(const notcurses* nc, fbuf* f){
     const char* ncursesver = curses_version();
     const char* ncver = strchr(ncursesver, ' ');
     ncver = ncver ? ncver + 1 : ncursesver;
-    fbuf_printf(f, "%u colors" NL "%s%s%s (%s)" NL "%sterminfo %s zlib %s GPM %s" NL,
+    fbuf_printf(f, "%u colors" NL "%s%s%s (%s)" NL "%sterminfo %s libdeflate %s GPM %s" NL,
                 nc->tcache.caps.colors, clreol,
 #ifdef __clang__
             "", // name is contained in __VERSION__
@@ -82,7 +82,7 @@ int init_banner(const notcurses* nc, fbuf* f){
 #else
 #error "No __BYTE_ORDER__ definition"
 #endif
-            clreol, ncver, zlibVersion(), gpm_version());
+            clreol, ncver, LIBDEFLATE_VERSION_STRING, gpm_version());
     fbuf_puts(f, clreol); // for ncvisual banner
     ncvisual_printbanner(f);
     init_banner_warnings(nc, f, clreol);
diff --git a/src/lib/kitty.c b/src/lib/kitty.c
index ddd8dbcfbd..f1855a1b6b 100644
--- a/src/lib/kitty.c
+++ b/src/lib/kitty.c
@@ -1,4 +1,4 @@
-#include <zlib.h>
+#include <libdeflate.h>
 #include "internal.h"
 #include "base64.h"
 
@@ -559,19 +559,17 @@ int kitty_commit(fbuf* f, sprixel* s, unsigned noscroll){
   return 0;
 }
 
-static inline void*
-zctx_origbuf(z_stream* zctx, int pixy, int pixx){
-  size_t blen = pixx * pixy * 4;
-  unsigned long b = deflateBound(zctx, blen);
-  return (unsigned char *)zctx->next_out - (b - zctx->avail_out);
-}
-
+// chunkify and write the collected buffer in the animated case. this might
+// or might not be compressed (depends on whether compression was useful).
 static int
-encode_and_chunkify(fbuf* f, z_stream* zctx, int pixy, int pixx){
-  unsigned long totw = zctx->total_out;
-  unsigned char* buf = zctx_origbuf(zctx, pixy, pixx);
+encode_and_chunkify(fbuf* f, const unsigned char* buf, size_t blen, unsigned compressed){
   // need to terminate the header, requiring semicolon
-  if(totw > 4096 * 3 / 4){
+  if(compressed){
+    if(fbuf_putn(f, ",o=z", 4) < 0){
+      return -1;
+    }
+  }
+  if(blen > 4096 * 3 / 4){
     if(fbuf_putn(f, ",m=1", 4) < 0){
       return -1;
     }
@@ -582,7 +580,7 @@ encode_and_chunkify(fbuf* f, z_stream* zctx, int pixy, int pixx){
   bool first = true;
   unsigned long i = 0;
   char b64d[4];
-  while(totw - i > 4096 * 3 / 4){
+  while(blen - i > 4096 * 3 / 4){
     if(!first){
       if(fbuf_putn(f, "\x1b_Gm=1;", 7) < 0){
         return -1;
@@ -606,13 +604,13 @@ encode_and_chunkify(fbuf* f, z_stream* zctx, int pixy, int pixx){
       return -1;
     }
   }
-  while(i < totw){
-    if(totw - i < 3){
-      base64final(buf + i, b64d, totw - i);
+  while(i < blen){
+    if(blen - i < 3){
+      base64final(buf + i, b64d, blen - i);
       if(fbuf_putn(f, b64d, 4) < 0){
         return -1;
       }
-      i += totw - i;
+      i += blen - i;
     }else{
       base64x3(buf + i, b64d);
       if(fbuf_putn(f, b64d, 4) < 0){
@@ -628,25 +626,39 @@ encode_and_chunkify(fbuf* f, z_stream* zctx, int pixy, int pixx){
 }
 
 static int
-finalize_deflator(z_stream* zctx, fbuf* f, int dimy, int dimx){
-  assert(0 == zctx->avail_in);
-  int zret = deflate(zctx, Z_FINISH);
-  if(zret != Z_STREAM_END){
-    logerror("Error deflating (%d)\n", zret);
+deflate_buf(void* buf, fbuf* f, int dimy, int dimx){
+  // 2 has been shown to work pretty well for things that are actually going
+  // to compress; results per unit time fall off quickly after 2.
+  struct libdeflate_compressor* cmp = libdeflate_alloc_compressor(6);
+  if(cmp == NULL){
+    logerror("couldn't get libdeflate context\n");
     return -1;
   }
-  if(encode_and_chunkify(f, zctx, dimy, dimx)){
-    return -1;
+  size_t blen = dimx * dimy * 4;
+  // if this allocation fails, just skip compression, no need to bail
+  void* cbuf = malloc(blen);
+  size_t clen;
+  if(cbuf){
+    clen = libdeflate_zlib_compress(cmp, buf, blen, cbuf, blen);
+  }else{
+    clen = 0;
   }
-  return 0;
+  int ret;
+  if(0 == clen){ // wasn't enough room; compressed data is larger than original
+    ret = encode_and_chunkify(f, buf, blen, 0);
+  }else{
+    loginfo("deflated %" PRIuPTR "B to %" PRIuPTR "B\n", blen, clen);
+    ret = encode_and_chunkify(f, cbuf, clen, 1);
+  }
+  free(cbuf);
+  libdeflate_free_compressor(cmp);
+  return ret;
 }
 
-// copy |encodeable| pixels to the deflate state
-static int
-add_to_deflator(z_stream* zctx, const uint32_t* src, int encodeable, bool wipe[static 3]){
-  assert(0 == zctx->avail_in);
-  uint32_t dst[3];
-  zctx->next_in = (void*)dst;
+// copy |encodeable| ([1..3]) pixels from |src| to the buffer |dst|, setting
+// alpha along the way according to |wipe|. 
+static inline int
+add_to_buf(uint32_t *dst, const uint32_t* src, int encodeable, bool wipe[static 3]){
   dst[0] = *src++;
   if(wipe[0] || rgba_trans_p(dst[0], 0)){
     ncpixel_set_a(&dst[0], 0);
@@ -663,52 +675,25 @@ add_to_deflator(z_stream* zctx, const uint32_t* src, int encodeable, bool wipe[s
       }
     }
   }
-  zctx->avail_in = encodeable * 4;
-  int zret = deflate(zctx, Z_NO_FLUSH);
-  if(zret != Z_OK){
-    logerror("Error deflating (%d)\n", zret);
-    return -1;
-  }
   return 0;
 }
 
-// writes to |*animated| based on normalized |level|
-static int
-prep_deflator(ncpixelimpl_e level, z_stream* zctx, int pixy, int pixx,
-              unsigned* animated){
-  memset(zctx, 0, sizeof(*zctx));
-  if(level >= NCPIXEL_KITTY_ANIMATED){
-    int zret;
-    // 2 seems to work well for things that are going to compress up
-    // meaningfully at all, while not taking too much time.
-    if((zret = deflateInit(zctx, 2)) != Z_OK){
-      logerror("Couldn't get a deflate context (%d)\n", zret);
-      return -1;
-    }
-    size_t blen = pixx * pixy * 4;
-    unsigned long b = deflateBound(zctx, blen);
-    unsigned char* buf = malloc(b);
-    if(buf == NULL){
-      deflateEnd(zctx);
+// writes to |*animated| based on normalized |level|. if we're not animated,
+// we won't be using compression.
+static inline int
+prep_animation(ncpixelimpl_e level, uint32_t** buf, int leny, int lenx, unsigned* animated){
+  if(level < NCPIXEL_KITTY_ANIMATED){
+    *animated = false;
+    *buf = NULL;
+  }else{
+    if((*buf = malloc(lenx * leny * sizeof(uint32_t))) == NULL){
       return -1;
     }
-    zctx->avail_out = b;
-    zctx->next_out = buf;
     *animated = true;
-  }else{
-    *animated = false;
   }
   return 0;
 }
 
-static void
-destroy_deflator(unsigned animated, z_stream* zctx, int pixy, int pixx){
-  if(animated){
-    free(zctx_origbuf(zctx, pixy, pixx));
-    deflateEnd(zctx);
-  }
-}
-
 // if we're NCPIXEL_KITTY_SELFREF, and we're blitting a secondary frame, we need
 // carry through the TAM's annihilation entires...but we also need load the
 // frame *without* annihilations, lest we be unable to build it. we thus go
@@ -746,19 +731,20 @@ write_kitty_data(fbuf* f, int linesize, int leny, int lenx, int cols,
     logerror("stride (%d) badly aligned\n", linesize);
     return -1;
   }
-  // we only deflate if we're using animation, since otherwise we need be able
-  // to edit the encoded bitmap in-place for wipes/restores.
-  z_stream zctx;
   unsigned animated;
-  if(prep_deflator(level, &zctx, leny, lenx, &animated)){
+  uint32_t* buf;
+  // we'll be collecting the pixels, modified to reflect alpha nullification
+  // due to preexisting wipes, into a temporary buffer for compression (iff
+  // we're animated). pixels are 32 bits each.
+  if(prep_animation(level, &buf, leny, lenx, &animated)){
     return -1;
   }
+  unsigned bufidx = 0; // an index; the actual offset is bufidx * 4
   bool translucent = bargs->flags & NCVISUAL_OPTION_BLEND;
   sprixel* s = bargs->u.pixel.spx;
-  int sprixelid = s->id;
-  int cdimy = s->cellpxy;
-  int cdimx = s->cellpxx;
-  uint32_t transcolor = bargs->transcolor;
+  const int cdimy = s->cellpxy;
+  const int cdimx = s->cellpxx;
+  const uint32_t transcolor = bargs->transcolor;
   int total = leny * lenx; // total number of pixels (4 * total == bytecount)
   // number of 4KiB chunks we'll need
   int chunks = (total + (RGBA_MAXLEN - 1)) / RGBA_MAXLEN;
@@ -777,10 +763,11 @@ write_kitty_data(fbuf* f, int linesize, int leny, int lenx, int cols,
     if(totalout == 0){
       // older versions of kitty will delete uploaded images when scrolling,
       // alas. see https://github.com/dankamongmen/notcurses/issues/1910 =[.
-      // parse_start isn't used in animation mode, so no worries there.
+      // parse_start isn't used in animation mode, so no worries about the
+      // fact that this doesn't complete the header in that case.
       *parse_start = fbuf_printf(f, "\e_Gf=32,s=%d,v=%d,i=%d,p=1,a=t,%s",
-                                 lenx, leny, sprixelid,
-                                 animated ? "o=z,q=2" : chunks ? "m=1;" : "q=2;");
+                                 lenx, leny, s->id,
+                                 animated ? "q=2" : chunks ? "m=1;" : "q=2;");
       if(*parse_start < 0){
         goto err;
       }
@@ -898,9 +885,10 @@ write_kitty_data(fbuf* f, int linesize, int leny, int lenx, int cols,
       }
       totalout += encodeable;
       if(animated){
-        if(add_to_deflator(&zctx, source, encodeable, wipe)){
+        if(add_to_buf(buf + bufidx, source, encodeable, wipe)){
           goto err;
         }
+        bufidx += encodeable;
       }else{
         // we already took transcolor to alpha 0; there's no need to
         // check it again, so pass 0.
@@ -916,8 +904,10 @@ write_kitty_data(fbuf* f, int linesize, int leny, int lenx, int cols,
       }
     }
   }
+  // we only deflate if we're using animation, since otherwise we need be able
+  // to edit the encoded bitmap in-place for wipes/restores.
   if(animated){
-    if(finalize_deflator(&zctx, f, leny, lenx)){
+    if(deflate_buf(buf, f, leny, lenx)){
       goto err;
     }
     if(selfref_annihilated){
@@ -927,13 +917,13 @@ write_kitty_data(fbuf* f, int linesize, int leny, int lenx, int cols,
     }
   }
   scrub_tam_boundaries(tam, leny, lenx, cdimy, cdimx);
-  destroy_deflator(animated, &zctx, leny, lenx);
+  free(buf);
   return 0;
 
 err:
   logerror("failed blitting kitty graphics\n");
   cleanup_tam(tam, (leny + cdimy - 1) / cdimy, (lenx + cdimx - 1) / cdimx);
-  destroy_deflator(animated, &zctx, leny, lenx);
+  free(buf);
   return -1;
 }
 


Test patch:
diff --git a/.github/workflows/macos_test.yml b/.github/workflows/macos_test.yml
index 83b1a08ba1..95201251ad 100644
--- a/.github/workflows/macos_test.yml
+++ b/.github/workflows/macos_test.yml
@@ -24,6 +24,7 @@ jobs:
             coreutils \
             doctest \
             ffmpeg \
+            libdeflate \
             libunistring \
             ncurses
 
diff --git a/.github/workflows/ubuntu_test.yml b/.github/workflows/ubuntu_test.yml
index 7535d1b980..d3b730b20a 100644
--- a/.github/workflows/ubuntu_test.yml
+++ b/.github/workflows/ubuntu_test.yml
@@ -29,6 +29,7 @@ jobs:
             libavcodec-dev \
             libavformat-dev \
             libavutil-dev \
+            libdeflate-dev \
             libncurses-dev \
             libqrcodegen-dev \
             libswscale-dev \
diff --git a/.github/workflows/windows_test.yml b/.github/workflows/windows_test.yml
index 2bdf5e8120..23bdf0459c 100644
--- a/.github/workflows/windows_test.yml
+++ b/.github/workflows/windows_test.yml
@@ -31,6 +31,7 @@ jobs:
             mingw-w64-ucrt-x86_64-cmake
             mingw-w64-ucrt-x86_64-doctest
             mingw-w64-ucrt-x86_64-ffmpeg
+            mingw-w64-ucrt-x86_64-libdeflate
             mingw-w64-ucrt-x86_64-libunistring
             mingw-w64-ucrt-x86_64-ncurses
             mingw-w64-ucrt-x86_64-toolchain