From 016409d46d798a9a2b1be937b5be117cb3728901 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Thu, 28 May 2026 15:38:35 -0500 Subject: [PATCH 1/5] Initial Windows support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - adds Windows oriented build using vcpkg - implement or stub missing POSIX functions and includes Co-authored-by: Josef Šimánek --- .gitignore | 5 +- meson.build | 180 ++++++++++-- meson_options.txt | 21 ++ src/args.c | 4 + src/config.c | 4 +- src/infra.c | 9 + src/inputs.c | 57 +++- src/platform/compat/windows/fcntl.h | 21 ++ src/platform/compat/windows/langinfo.h | 13 + src/platform/compat/windows/libgen.h | 9 + src/platform/compat/windows/poll.h | 28 ++ src/platform/compat/windows/signal.h | 75 +++++ src/platform/compat/windows/strings.h | 11 + src/platform/compat/windows/sys/ioctl.h | 19 ++ src/platform/compat/windows/sys/wait.h | 23 ++ src/platform/compat/windows/term.h | 13 + src/platform/compat/windows/termios.h | 30 ++ src/platform/compat/windows/unistd.h | 46 ++++ src/platform/dl_reaper.c | 42 +++ src/platform/platform.h | 84 ++++++ src/platform/windows.c | 348 ++++++++++++++++++++++++ src/pspg.c | 4 +- src/pspg.h | 7 +- src/readline.c | 3 + src/st_panel.h | 2 + src/table.c | 14 +- src/theme_loader.c | 1 + src/unicode.c | 2 + vcpkg.json | 19 ++ 29 files changed, 1053 insertions(+), 41 deletions(-) create mode 100644 meson_options.txt create mode 100644 src/platform/compat/windows/fcntl.h create mode 100644 src/platform/compat/windows/langinfo.h create mode 100644 src/platform/compat/windows/libgen.h create mode 100644 src/platform/compat/windows/poll.h create mode 100644 src/platform/compat/windows/signal.h create mode 100644 src/platform/compat/windows/strings.h create mode 100644 src/platform/compat/windows/sys/ioctl.h create mode 100644 src/platform/compat/windows/sys/wait.h create mode 100644 src/platform/compat/windows/term.h create mode 100644 src/platform/compat/windows/termios.h create mode 100644 src/platform/compat/windows/unistd.h create mode 100644 src/platform/dl_reaper.c create mode 100644 src/platform/platform.h create mode 100644 src/platform/windows.c create mode 100644 vcpkg.json diff --git a/.gitignore b/.gitignore index 5efdfc5a..8a058570 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,7 @@ pspg /config.cache /config.log /config.status -/config.make \ No newline at end of file +/config.make + +/build +/vcpkg_installed diff --git a/meson.build b/meson.build index 5a9296b8..02b8ad3f 100644 --- a/meson.build +++ b/meson.build @@ -1,47 +1,162 @@ -project('pspg', ['c'], version: '5.8.16') +project('pspg', ['c'], version: '5.8.16', meson_version: '>= 1.5.0') build_args = [ '-DPROJECT_NAME=' + meson.project_name(), '-DPROJECT_VERSION=' + meson.project_version(), + '-DCOMPILE_MENU', ] cc = meson.get_compiler('c') +fs = import('fs') +is_windows = host_machine.system() == 'windows' +prefix = get_option('dep_prefix') +shlibs = get_option('shlibs') -panel = cc.find_library('panelw') -curses = dependency('curses') -math = cc.find_library('m') +delayload = false +foreach e : get_option('c_link_args') + if e.to_lower().contains('delayload') + delayload = true + endif +endforeach + +curses_backend = get_option('curses_backend') +if curses_backend == 'auto' + curses_backend = is_windows ? 'pdcurses' : 'curses' +endif + +want_static = shlibs != 'all' +want_full_static = shlibs == 'no' + +message('curses backend: ' + curses_backend) +message('shared library mode: ' + shlibs) +message('delayload: ' + delayload.to_string()) + +project_deps = [] +link_args = [] +project_includes = [ + include_directories('src'), + include_directories('src/platform'), +] + +cmake_prefix = fs.is_absolute(prefix) ? prefix : meson.current_source_dir() / prefix + +if is_windows + project_includes += [ + include_directories('src/platform/compat/windows'), + include_directories(prefix / 'include') # vcpkg_installed/triplet/include + ] + if cc.get_argument_syntax() == 'msvc' + project_deps += dependency('getopt', method: 'cmake', required: true, static: want_static, + modules: 'getopt::getopt_shared', + cmake_args: '-DCMAKE_PREFIX_PATH=' + cmake_prefix) + endif + build_args += [ + '-DGWINSZ_IN_SYS_IOCTL', + '-Dssize_t=ptrdiff_t', + ] +endif -message(curses.name()) +curses = dependency([curses_backend, 'unofficial-pdcurses'], required: not is_windows, static: want_static, + modules: 'unofficial::pdcurses::pdcurses', + cmake_args: '-DCMAKE_PREFIX_PATH=' + cmake_prefix) +if curses.found() + curses_libs = curses.get_variable(default_value: '', cmake: 'PACKAGE_LIBRARIES', pkgconfig : 'libdir') + if curses_libs == '' + message('meson fails to properly parse unofficial::pdcurses::pdcurses imported CMake target of "UNKNOWN" type') + if get_option('buildtype') == 'debug' + curses_lib_dir = cmake_prefix / 'debug' + endif + curses_lib_dir = curses_lib_dir / 'lib' + project_deps += cc.find_library('pdcurses', dirs: curses_lib_dir) + endif +else # msys2 fallback + curses = cc.find_library('pdcurses_wincon') +endif +curses_name = curses.name() +if curses_name.contains('pdcurses') + # unofficial-pdcurses with MSVC, pdcurses_wincon with msys2 + curses_name = 'pdcurses' + build_args += [ + '-DPDCURSES', + '-DPDC_WIDE', + '-DPDC_FORCE_UTF8', + '-DHAVE_NCURSESW', + ] +endif +message(curses_name) conf = configuration_data() +panel = cc.find_library('panel', required: curses_name != 'pdcurses') +math = cc.find_library('m', required: not is_windows) -if curses.name() == 'ncursesw' - message('detect ncursesw') +have_kqueue = cc.has_function('kqueue', prefix : '#include ') +if have_kqueue + build_args += '-DHAVE_KQUEUE' +endif + +if ['ncursesw', 'pdcurses'].contains(curses_name) + message('enabling wide character support') build_args += '-DHAVE_NCURSESW' endif -build_args += '-DCOMPILE_MENU' +curses_inc = prefix / 'include' / curses_name +if curses.type_name() == 'pkgconfig' # won't work with find_library + curses_inc = curses.get_variable(pkgconfig: 'includedir') +endif +if fs.is_dir(curses_inc) + message('Adding curses include directory: ' + curses_inc) + project_includes += include_directories(curses_inc) +endif + +readline = dependency(['readline', 'unofficial-readline-win32'], + static: want_static, required: false, + modules: 'unofficial::readline-win32::readline', + cmake_args: '-DCMAKE_PREFIX_PATH=' + cmake_prefix +) +if readline.found() + readline_inc = readline.get_variable(pkgconfig: 'includedir', default_value: prefix / 'include') / 'readline' + if fs.is_dir(readline_inc) + message('Adding readline include directory: ' + readline_inc) + project_includes += include_directories(readline_inc) + endif +endif check_headers = [ - ['ncursesw/menu.h', '-DHAVE_NCURSESW_MENU_H'], - ['ncurses/menu.h', '-DHAVE_NCURSES_MENU_H'], - ['menu.h', '-DHAVE_MENU_H'], - ['ncursesw/curses.h', '-DHAVE_NCURSESW_CURSES_H'], ['ncursesw.h', '-DHAVE_NCURSESW_H'], - ['ncurses/curses.h', '-DHAVE_NCURSES_CURSES_H'], ['ncurses.h', '-DHAVE_NCURSES_H'], ['curses.h', '-DHAVE_CURSES_H'], - ['ncursesw/panel.h', '-DHAVE_NCURSESW_PANEL_H'], - ['ncurses/panel.h', '-DHAVE_NCURSES_PANEL_H'], - ['panel.h', '-DHAVE_PANEL_H'] + ['panel.h', '-DHAVE_PANEL_H'], + ['readline.h', ['-DHAVE_LIBREADLINE', '-DHAVE_READLINE_H']], + ['history.h', ['-DHAVE_READLINE_HISTORY', '-DHAVE_HISTORY_H']] ] +if not have_kqueue # FreeBSD has both, prefer kqueue + check_headers += [['sys/inotify.h', '-DHAVE_INOTIFY']] +endif + foreach h : check_headers - if cc.has_header(h.get(0)) + if cc.has_header(h.get(0), include_directories: project_includes) build_args += h.get(1) endif endforeach +pg = dependency('libpq', required: false, static: want_full_static) +if not pg.found() + pgroot = get_option('pgroot') + if pgroot != '' + pg = declare_dependency( + dependencies : cc.find_library('pq', dirs: pgroot / 'lib', required: false), + include_directories : include_directories(pgroot / 'include') + ) + else + message('PostgreSQL library not found; libpq support will be disabled') + message('Set the pgroot option to specify a PostgreSQL installation for libpq support') + endif +endif +if pg.found() + build_args += '-DHAVE_POSTGRESQL' +endif + sources = [ 'src/args.c', 'src/bscommands.c', @@ -67,10 +182,37 @@ sources = [ 'src/unicode.c' ] +if is_windows + sources += files('src/platform/windows.c') + if delayload + sources += files('src/platform/dl_reaper.c') + project_deps += [ cc.find_library('delayimp'), cc.find_library('ntdll') ] + endif + + project_deps += cc.find_library('ws2_32') + if curses.name() == 'pdcurses_wincon' + project_deps += cc.find_library('winmm') + endif + if cc.get_argument_syntax() == 'gcc' and not cc.has_function('clock_gettime') + project_deps += cc.find_library('winpthread') + endif +endif + +if want_static and curses_backend == 'ncursesw' + build_args += '-DNCURSES_STATIC' +endif + +if want_static and cc.get_argument_syntax() == 'gcc' + build_args += ['-ffunction-sections', '-fdata-sections'] + link_args += ['-static', '-Wl,--gc-sections'] +endif + project_target = executable( meson.project_name(), sources, - dependencies: [ curses, panel, math ], + dependencies: project_deps + [pg, readline, curses, panel, math], install : true, - c_args : build_args + c_args : build_args, + include_directories: project_includes, + link_args: link_args, ) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 00000000..644a377f --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,21 @@ +option('curses_backend', + type: 'combo', + choices: ['auto', 'curses', 'pdcurses'], + value: 'auto', + description: 'Which curses backend to use') + +option('shlibs', + type: 'combo', + choices: ['all', 'pg', 'no'], + value: 'all', + description: 'Whether to link against dynamic libraries (all, pg, no)') + +option('pgroot', + type: 'string', + value: '', + description: 'Path to external PostgreSQL installation for libpq include and library lookup') + +option('dep_prefix', + type: 'string', + value: '/usr', + description: 'Prefix for curses etc lookup (default: /usr)') diff --git a/src/args.c b/src/args.c index 338f0610..688d5a94 100644 --- a/src/args.c +++ b/src/args.c @@ -302,7 +302,11 @@ print_info(void) #endif +#if defined(__SIZEOF_WCHAR_T__) && defined(__WCHAR_MAX__) fprintf(stdout, "wchar_t width: %d, max: %d\n", __SIZEOF_WCHAR_T__, __WCHAR_MAX__); +#else + fprintf(stdout, "wchar_t width: %zu, max: %u\n", sizeof(wchar_t), WCHAR_MAX); +#endif #if NCURSES_EXT_FUNCS diff --git a/src/config.c b/src/config.c index 9aff3713..d281df22 100644 --- a/src/config.c +++ b/src/config.c @@ -116,10 +116,10 @@ save_config(const char *path, Options *opts) if (chmod(path, 0644) != 0) { - int _errno = errno; + int __errno = errno; fclose(f); - errno = _errno; + errno = __errno; return false; } diff --git a/src/infra.c b/src/infra.c index e8a2e090..d988fb6e 100644 --- a/src/infra.c +++ b/src/infra.c @@ -617,6 +617,15 @@ tilde(char *dest, const char *path) { char *home = getenv("HOME"); +#ifdef _WIN32 + /* + * On Windows, fall back to USERPROFILE if HOME is not set. + * Windows uses %USERPROFILE% instead of Unix $HOME. + */ + if (home == NULL) + home = getenv("USERPROFILE"); +#endif + if (home == NULL) leave("HOME directory is not defined"); diff --git a/src/inputs.c b/src/inputs.c index fdb05e02..dc8ee82a 100644 --- a/src/inputs.c +++ b/src/inputs.c @@ -19,6 +19,22 @@ #include #include #include + +#ifdef _WIN32 + +/* + * Windows doesn't have S_ISFIFO (no FIFOs) and uses _S_IFREG + */ +#ifndef S_ISFIFO +#define S_ISFIFO(m) 0 +#endif + +#ifndef S_ISREG +#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) +#endif + +#endif + #include #include @@ -331,9 +347,31 @@ get_ncurses_event(NCursesEventData *nced, bool *sigint, bool *sigwinch) if (first_event) { first_event = false; + +#ifdef PDCURSES + /* + * On PDCurses/Windows, use short timeout to detect Alt sequences. + * 100ms timeout allows detection of Alt+key combinations. + */ + timeout(100); +#endif goto repeat; } +#ifdef PDCURSES + else + { + /* Second iteration - restore no timeout mode */ + timeout(-1); + } +#endif } +#ifdef PDCURSES + else if (!first_event) + { + /* If we got here on second iteration, restore no timeout */ + timeout(-1); + } +#endif } #if PDCURSES @@ -1131,12 +1169,21 @@ close_data_stream(void) bool open_tty_stream(void) { - -#ifndef __APPLE__ - - f_tty = fopen("/dev/tty", "r+"); - +#ifdef _WIN32 + /* + * Windows console access via CONIN$ (Console Input device). + * This is the Windows equivalent of /dev/tty for reading. + */ + #define ttyname(fd) "CONIN$" +#ifdef _MSC_VER /* cl.exe and ClangCL */ + /* Otherwise, I get this: Redirection is not supported. */ + f_tty = freopen(ttyname(fileno(stdin)), "r+", stdin); +#else /* No problem here with msys2/MinGW */ + f_tty = fopen(ttyname(fileno(stdin)), "r+"); #endif +#elif !defined(__APPLE__) + f_tty = fopen("/dev/tty", "r+"); +#endif /* !_WIN32 && !__APPLE__ */ if (!f_tty) { diff --git a/src/platform/compat/windows/fcntl.h b/src/platform/compat/windows/fcntl.h new file mode 100644 index 00000000..2ea8875d --- /dev/null +++ b/src/platform/compat/windows/fcntl.h @@ -0,0 +1,21 @@ +/* Windows compatibility for fcntl.h */ +#ifndef FCNTL_H_COMPAT +#define FCNTL_H_COMPAT + +#include +#include + +/* File control commands and flags from POSIX.1-2008 (missing in Windows fcntl.h) */ +#define F_GETFL 3 /* fcntl command 3: Get file status flags (O_APPEND, O_NONBLOCK, etc.) */ +#define F_SETFL 4 /* fcntl command 4: Set file status flags */ +#define O_NONBLOCK 04000 /* Octal 04000 (0x800): Non-blocking I/O - from Linux fcntl-linux.h */ + +static inline int fcntl_win32(int fd, int cmd, ...) { + return 0; +} + +#ifndef fcntl + #define fcntl fcntl_win32 +#endif + +#endif diff --git a/src/platform/compat/windows/langinfo.h b/src/platform/compat/windows/langinfo.h new file mode 100644 index 00000000..4d790cd4 --- /dev/null +++ b/src/platform/compat/windows/langinfo.h @@ -0,0 +1,13 @@ +/* Windows compatibility for langinfo.h */ +#ifndef LANGINFO_H_COMPAT +#define LANGINFO_H_COMPAT + +/* Character encoding query (always UTF-8 on Windows) */ +#define CODESET 0 + +static inline char *nl_langinfo(int item) { + (void)item; + return "UTF-8"; +} + +#endif diff --git a/src/platform/compat/windows/libgen.h b/src/platform/compat/windows/libgen.h new file mode 100644 index 00000000..28e5ebca --- /dev/null +++ b/src/platform/compat/windows/libgen.h @@ -0,0 +1,9 @@ +/* Windows compatibility for libgen.h */ +#ifndef LIBGEN_H_COMPAT +#define LIBGEN_H_COMPAT + +/* Path manipulation functions */ +#define basename(x) platform_basename(x) +#define dirname(x) platform_dirname(x) + +#endif diff --git a/src/platform/compat/windows/poll.h b/src/platform/compat/windows/poll.h new file mode 100644 index 00000000..07b1fd6a --- /dev/null +++ b/src/platform/compat/windows/poll.h @@ -0,0 +1,28 @@ +/* Windows compatibility for poll.h */ +#ifndef POLL_H_COMPAT +#define POLL_H_COMPAT + +#include + +#ifndef WSAPoll + #define pollfd pollfd_custom + + struct pollfd_custom { + int fd; + short events; + short revents; + }; + + /* Poll event flags from POSIX.1-2008 (bitmask for events/revents fields) */ + #ifndef POLLIN + #define POLLIN 0x0001 /* Bit 0: Data available for reading */ + #define POLLOUT 0x0004 /* Bit 2: Ready for writing */ + #define POLLERR 0x0008 /* Bit 3: Error condition */ + #endif + + int poll(struct pollfd_custom *fds, unsigned int nfds, int timeout); +#else + #define poll WSAPoll +#endif + +#endif diff --git a/src/platform/compat/windows/signal.h b/src/platform/compat/windows/signal.h new file mode 100644 index 00000000..e734cc47 --- /dev/null +++ b/src/platform/compat/windows/signal.h @@ -0,0 +1,75 @@ +/* Windows compatibility for signal.h */ +#ifndef SIGNAL_H_COMPAT +#define SIGNAL_H_COMPAT + +#if defined(__GNUC__) || defined(__clang__) +# include_next +#else +# include +#endif + +/* Signal handler function pointers (POSIX.1-2008, values from AT&T Unix) */ +#ifndef SIG_IGN + #define SIG_IGN ((void (__cdecl *)(int)) 1) /* Ignore signal - pointer value 1 */ +#endif +#ifndef SIG_DFL + #define SIG_DFL ((void (__cdecl *)(int)) 0) /* Default handler - pointer value 0 (NULL) */ +#endif + +/* Unix signal numbers from POSIX.1-2008 (values from 4.3BSD) */ +#ifndef SIGPIPE + #define SIGPIPE 13 /* Broken pipe - write to pipe with no reader */ +#endif + +/* Common signals (already in Windows signal.h) */ +#ifndef SIGINT + #define SIGINT 2 /* Interrupt from keyboard (Ctrl+C) */ +#endif +#ifndef SIGTERM + #define SIGTERM 15 /* Termination signal - graceful shutdown request */ +#endif +#ifndef SIGSEGV + #define SIGSEGV 11 /* Invalid memory reference - segmentation violation */ +#endif + +#ifndef SIGWINCH + #define SIGWINCH 28 /* Window size change - terminal resized */ +#endif + +struct sigaction { + void (*sa_handler)(int); + int sa_flags; + int sa_mask; +}; + +/* sigaction flags from POSIX.1-2008 (Linux sa_flags bits) */ +#define SA_RESETHAND 0x00000004 /* Bit 2: Reset handler to SIG_DFL after invocation */ + +static inline int sigemptyset(int *set) { + *set = 0; + return 0; +} + +#ifndef __GNUC__ /* MSVC only and not ClangCL */ +typedef void (__CRTDECL *_crt_signal_t)(int); + _ACRTIMP _crt_signal_t __cdecl signal(_In_ int _Signal, _In_opt_ _crt_signal_t _Function); +#endif + +/* + * Windows doesn't have sigaction, emulate with signal(). + */ +static inline int sigaction(int signum, const struct sigaction *act, + struct sigaction *oldact) { + if (act) { + void (*old_handler)(int) = signal(signum, act->sa_handler); + if (oldact) { + oldact->sa_handler = old_handler; + oldact->sa_flags = 0; + oldact->sa_mask = 0; + } + return 0; + } + return -1; +} + +#endif diff --git a/src/platform/compat/windows/strings.h b/src/platform/compat/windows/strings.h new file mode 100644 index 00000000..665e6f07 --- /dev/null +++ b/src/platform/compat/windows/strings.h @@ -0,0 +1,11 @@ +/* Windows compatibility for strings.h */ +#ifndef STRINGS_H_COMPAT +#define STRINGS_H_COMPAT + +#include + +/* Case-insensitive string comparison */ +#define strcasecmp _stricmp +#define strncasecmp _strnicmp + +#endif diff --git a/src/platform/compat/windows/sys/ioctl.h b/src/platform/compat/windows/sys/ioctl.h new file mode 100644 index 00000000..9ca16a9d --- /dev/null +++ b/src/platform/compat/windows/sys/ioctl.h @@ -0,0 +1,19 @@ +/* Windows compatibility for sys/ioctl.h */ +#ifndef SYS_IOCTL_H_COMPAT +#define SYS_IOCTL_H_COMPAT + +/* Terminal ioctl requests from Linux asm-generic/ioctls.h */ +#define TIOCGWINSZ 0x5413 /* Get window size - 'T'<<8 | 0x13 */ + +struct winsize { + unsigned short ws_row; + unsigned short ws_col; + unsigned short ws_xpixel; + unsigned short ws_ypixel; +}; + +static inline int ioctl(int fd, unsigned long request, ...) { + return -1; +} + +#endif diff --git a/src/platform/compat/windows/sys/wait.h b/src/platform/compat/windows/sys/wait.h new file mode 100644 index 00000000..be01c2ca --- /dev/null +++ b/src/platform/compat/windows/sys/wait.h @@ -0,0 +1,23 @@ +/* Windows compatibility for sys/wait.h */ +#ifndef SYS_WAIT_H_COMPAT +#define SYS_WAIT_H_COMPAT + +/* waitpid options from POSIX.1-2008 (bits for 3rd parameter) */ +#define WNOHANG 1 /* Bit 0: Return immediately if no child has exited */ +#define WUNTRACED 2 /* Bit 1: Also return for stopped children (not just terminated) */ + +/* Process exit status macros (POSIX.1-2008) - Windows stubs always report normal exit */ +#define WIFEXITED(status) 1 /* Always true: process exited normally */ +#define WEXITSTATUS(status) (status) /* Return full status as exit code */ +#define WIFSIGNALED(status) 0 /* Always false: not terminated by signal */ +#define WTERMSIG(status) 0 /* Always 0: no terminating signal */ + +#ifndef __GNUC__ +typedef int pid_t; +#endif + +static inline pid_t waitpid(pid_t pid, int *status, int options) { + return -1; +} + +#endif diff --git a/src/platform/compat/windows/term.h b/src/platform/compat/windows/term.h new file mode 100644 index 00000000..367a6c02 --- /dev/null +++ b/src/platform/compat/windows/term.h @@ -0,0 +1,13 @@ +/* Windows compatibility for term.h */ +#ifndef TERM_H_COMPAT +#define TERM_H_COMPAT + +#ifndef NCURSES_CONST + #define NCURSES_CONST const +#endif + +static inline char* tigetstr(NCURSES_CONST char *capname) { + return NULL; +} + +#endif diff --git a/src/platform/compat/windows/termios.h b/src/platform/compat/windows/termios.h new file mode 100644 index 00000000..76c17e1e --- /dev/null +++ b/src/platform/compat/windows/termios.h @@ -0,0 +1,30 @@ +/* Windows compatibility for termios.h */ +#ifndef TERMIOS_H_COMPAT +#define TERMIOS_H_COMPAT + +typedef unsigned int tcflag_t; +typedef unsigned char cc_t; + +struct termios { + tcflag_t c_iflag; + tcflag_t c_oflag; + tcflag_t c_cflag; + tcflag_t c_lflag; + cc_t c_cc[32]; +}; + +/* Terminal control values from POSIX.1-2008 (octal values from 4.3BSD) */ +#define TCSANOW 0 /* Change attributes immediately */ +#define TCSAFLUSH 2 /* Change after flushing output and draining input */ +#define ECHO 0000010 /* Octal 010 (bit 3): Echo input characters */ +#define ICANON 0000002 /* Octal 002 (bit 1): Canonical mode - line-based editing */ + +static inline int tcgetattr(int fd, struct termios *termios_p) { + return 0; +} + +static inline int tcsetattr(int fd, int optional_actions, const struct termios *termios_p) { + return 0; +} + +#endif diff --git a/src/platform/compat/windows/unistd.h b/src/platform/compat/windows/unistd.h new file mode 100644 index 00000000..c1ede46b --- /dev/null +++ b/src/platform/compat/windows/unistd.h @@ -0,0 +1,46 @@ +/* Windows compatibility for unistd.h */ +#ifndef UNISTD_H_COMPAT +#define UNISTD_H_COMPAT + +#include +#include +#include +#include + +#ifndef _SSIZE_T_DEFINED +typedef SSIZE_T ssize_t; +#define _SSIZE_T_DEFINED +#endif + +/* Standard file descriptor numbers (POSIX.1-2008) */ +#ifndef STDIN_FILENO + #define STDIN_FILENO 0 /* Standard input - fd 0 */ + #define STDOUT_FILENO 1 /* Standard output - fd 1 */ + #define STDERR_FILENO 2 /* Standard error - fd 2 */ +#endif + +/* Platform-specific implementations */ +#define strndup platform_strndup +#define usleep platform_usleep + +/* POSIX to Windows CRT mappings */ +#define access _access +#define close _close +#define dup2 _dup2 +#define fileno _fileno +#define isatty _isatty +#define lseek _lseek +#define write _write + +/* Process pipe functions */ +#define popen _popen +#define pclose _pclose + +#ifndef setlinebuf + #define setlinebuf(stream) setvbuf(stream, NULL, _IOLBF, BUFSIZ) +#endif + +int pipe(int pipefd[2]); +int fork(void); + +#endif diff --git a/src/platform/dl_reaper.c b/src/platform/dl_reaper.c new file mode 100644 index 00000000..0abe95b6 --- /dev/null +++ b/src/platform/dl_reaper.c @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2026 Mikhail Titov + * SPDX-License-Identifier: BSD-3-Clause + */ + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +NTSYSAPI NTSTATUS NTAPI NtRaiseHardError( + NTSTATUS ErrorStatus, + ULONG NumberOfParameters, + ULONG UnicodeStringParameterMask, + PULONG_PTR Parameters, + ULONG ValidResponseOptions, + PULONG Response +); + +#define PREFIX L"(optional) " +#define PREFIX_LEN sizeof(PREFIX)/sizeof(wchar_t) - 1 + +wchar_t wDllName[PREFIX_LEN + MAX_PATH] = PREFIX; + +FARPROC WINAPI DelayLoadFailureHook(unsigned dliNotify, PDelayLoadInfo pdli) { + (void)dliNotify; + ULONG response; + UNICODE_STRING usDllName; + + SetErrorMode(0); + MultiByteToWideChar(CP_ACP, 0, pdli->szDll, -1, wDllName + PREFIX_LEN, MAX_PATH); + RtlInitUnicodeString(&usDllName, wDllName); + NtRaiseHardError(STATUS_DLL_NOT_FOUND, 1, 1, (PULONG_PTR)&(PUNICODE_STRING){ &usDllName }, 1, &response); + + TerminateProcess(GetCurrentProcess(), STATUS_DLL_NOT_FOUND); + return 0; +} + +#ifdef _MSC_VER +const +#endif +PfnDliHook __pfnDliFailureHook2 = DelayLoadFailureHook; diff --git a/src/platform/platform.h b/src/platform/platform.h new file mode 100644 index 00000000..1f3eb162 --- /dev/null +++ b/src/platform/platform.h @@ -0,0 +1,84 @@ +/*------------------------------------------------------------------------- + * + * platform.h + * Cross-platform compatibility layer + * + *------------------------------------------------------------------------- + */ + +#ifndef PSPG_PLATFORM_H +#define PSPG_PLATFORM_H + +/* Standard includes that work everywhere */ +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER + #define F_SETFL 4 /* fcntl command 4: Set file status flags */ + #define O_NONBLOCK 04000 /* Octal 04000 (0x800): Non-blocking I/O - from Linux fcntl-linux.h */ + #define fcntl(...) (0) + + #include /* chmod instead of sys/stat.h */ + #include + typedef SSIZE_T ssize_t; + #define PATH_SEPARATOR '\\' + #define PATH_SEPARATOR_STR "\\" + + /* POSIX clock types from POSIX.1-2008 (clock_gettime parameter) */ + #ifndef CLOCK_MONOTONIC + #define CLOCK_MONOTONIC 1 /* Monotonic clock: cannot go backwards, unaffected by time adjustments */ + #endif + + #ifndef CLOCK_REALTIME + #define CLOCK_REALTIME 0 /* Real-time clock: wall-clock time, affected by NTP/manual changes */ + #endif + struct timespec; + int clock_gettime(int clk_id, struct timespec *tp); +#else + #include + #include + #define PATH_SEPARATOR '/' + #define PATH_SEPARATOR_STR "/" +#endif + +char *platform_strndup(const char *s, size_t n); +char *platform_basename(char *path); +char *platform_dirname(char *path); +int platform_usleep(unsigned int usec); + +#ifdef _WIN32 +char *getpass(const char *prompt); +ssize_t getline(char **lineptr, size_t *n, FILE *stream); +#endif + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) +#endif + +#ifndef S_ISREG +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif + +#if defined(__has_attribute) + #if __has_attribute(noreturn) + #define PSPG_NORETURN __attribute__ ((noreturn)) + #else + #define PSPG_NORETURN + #endif +#elif defined(__GNUC__) + #define PSPG_NORETURN __attribute__ ((noreturn)) +#elif defined(_MSC_VER) + #define PSPG_NORETURN __declspec(noreturn) +#else + #define PSPG_NORETURN +#endif + +#ifndef UNUSED + #define UNUSED(expr) do { (void)(expr); } while (0) +#endif + +#endif /* PSPG_PLATFORM_H */ diff --git a/src/platform/windows.c b/src/platform/windows.c new file mode 100644 index 00000000..4e191e9b --- /dev/null +++ b/src/platform/windows.c @@ -0,0 +1,348 @@ +/*------------------------------------------------------------------------- + * + * windows.c + * Windows platform implementation + * + * This file contains Windows implementations of functions that are + * normally provided by POSIX on Unix systems. + * + *------------------------------------------------------------------------- + */ + +#include "platform.h" +#include +#include +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include /* ENOSYS */ + +/* + * Windows implementation for strndup (not available in standard library) + */ +char * +platform_strndup(const char *s, size_t n) +{ + size_t len = strnlen(s, n); + char *result = malloc(len + 1); + + if (result) + { + memcpy(result, s, len); + result[len] = '\0'; + } + + return result; +} + +/* + * Windows implementation for getline (not available in standard library) + */ +ssize_t +getline(char **lineptr, size_t *n, FILE *stream) +{ + size_t pos = 0; + int c; + + if (lineptr == NULL || n == NULL || stream == NULL) + return -1; + + if (*lineptr == NULL) + { + *n = 128; + *lineptr = malloc(*n); + if (*lineptr == NULL) + return -1; + } + + while ((c = fgetc(stream)) != EOF) + { + if (pos + 1 >= *n) + { + size_t new_size = *n * 2; + char *new_ptr = realloc(*lineptr, new_size); + if (new_ptr == NULL) + return -1; + *lineptr = new_ptr; + *n = new_size; + } + + (*lineptr)[pos++] = c; + if (c == '\n') + break; + } + + if (pos == 0) + return -1; + + (*lineptr)[pos] = '\0'; + return pos; +} + +/* + * Windows implementation for basename/dirname from libgen.h + */ +char * +platform_basename(char *path) +{ + char *p; + + if (!path || !*path) + return "."; + + p = path + strlen(path) - 1; + while (p > path && (*p == '\\' || *p == '/')) + *p-- = '\0'; + + p = strrchr(path, '\\'); + if (!p) + p = strrchr(path, '/'); + + return p ? p + 1 : path; +} + +char * +platform_dirname(char *path) +{ + static char dot[] = "."; + char *p; + + if (!path || !*path) + return dot; + + p = path + strlen(path) - 1; + + while (p > path && (*p == '\\' || *p == '/')) + *p-- = '\0'; + + while (p > path && *p != '\\' && *p != '/') + p--; + + if (p == path) + { + if (*p == '\\' || *p == '/') + return path; + return dot; + } + + while (p > path && (*p == '\\' || *p == '/')) + p--; + + p[1] = '\0'; + return path; +} + +int +platform_usleep(unsigned int usec) +{ + HANDLE timer; + LARGE_INTEGER ft; + + ft.QuadPart = -(10 * (LONGLONG)usec); + + timer = CreateWaitableTimer(NULL, TRUE, NULL); + if (timer == NULL) + return -1; + + SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); + + return 0; +} + +/* + * Windows implementation of poll() using select() + * Limited implementation - only handles what pspg needs + */ +#ifndef WSAPoll + +#define pollfd pollfd_custom + +struct pollfd_custom { + int fd; + short events; + short revents; +}; + +#undef POLLIN +#undef POLLOUT +#undef POLLERR + +#define POLLIN 0x0001 +#define POLLOUT 0x0004 +#define POLLERR 0x0008 + +int +poll(struct pollfd_custom *fds, unsigned int nfds, int timeout) +{ + fd_set readfds, writefds, exceptfds; + struct timeval tv, *tvp; + int maxfd = -1; + unsigned int i; + int rc; + +#ifdef _WIN32 + /* + * Special handling for console input on Windows with PDCurses. + * PDCurses uses getch() which handles console reads directly, + * so we just indicate input is available. + */ + if (nfds > 0 && fds[0].fd >= 0) + { + HANDLE hInput = (HANDLE)_get_osfhandle(fds[0].fd); + DWORD fdwMode; + + if (hInput != INVALID_HANDLE_VALUE && GetConsoleMode(hInput, &fdwMode)) + { + if (timeout > 0) + Sleep(timeout); + else if (timeout < 0) + Sleep(100); + + fds[0].revents = POLLIN; + return 1; + } + } +#endif + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); for (i = 0; i < nfds; i++) + { + if (fds[i].fd < 0) + continue; + + if (fds[i].events & POLLIN) + FD_SET(fds[i].fd, &readfds); + if (fds[i].events & POLLOUT) + FD_SET(fds[i].fd, &writefds); + FD_SET(fds[i].fd, &exceptfds); + + if (fds[i].fd > maxfd) + maxfd = fds[i].fd; + + fds[i].revents = 0; + } + + if (timeout < 0) + tvp = NULL; + else + { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + tvp = &tv; + } + + rc = select(maxfd + 1, &readfds, &writefds, &exceptfds, tvp); + + if (rc > 0) + { + for (i = 0; i < nfds; i++) + { + if (fds[i].fd < 0) + continue; + + if (FD_ISSET(fds[i].fd, &readfds)) + fds[i].revents |= POLLIN; + if (FD_ISSET(fds[i].fd, &writefds)) + fds[i].revents |= POLLOUT; + if (FD_ISSET(fds[i].fd, &exceptfds)) + fds[i].revents |= POLLERR; + } + } + + return rc; +} + +#endif /* WSAPoll */ + +/* + * Simplified popen for Windows + * The fork/pipe/exec implementation in infra.c won't work on Windows + */ +int +run_command_with_pipes_win32(const char *command, int *fin, int *fout, int *ferr) +{ + /* TODO: Implement using CreateProcess + CreatePipe */ + *fin = -1; + *fout = -1; + *ferr = -1; + return -1; +} + +/* + * getpass for Windows - read password without echoing. + * Password input with echo disabled using Windows console API. + */ +char * +getpass(const char *prompt) +{ + static char password[128]; + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + DWORD mode, count; + + fputs(prompt, stderr); + fflush(stderr); + + if (hStdin == INVALID_HANDLE_VALUE || !GetConsoleMode(hStdin, &mode)) + return NULL; + + SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT)); + + if (!ReadConsoleA(hStdin, password, sizeof(password) - 1, &count, NULL)) + { + SetConsoleMode(hStdin, mode); + return NULL; + } + + SetConsoleMode(hStdin, mode); + + password[count] = '\0'; + while (count > 0 && (password[count - 1] == '\r' || password[count - 1] == '\n')) + password[--count] = '\0'; + + fputc('\n', stderr); + + return password; +} + +/* + * Stub implementations for Unix-specific functions that are not used on Windows + * but are referenced through compat headers + */ + +int +pipe(int pipefd[2]) +{ + (void)pipefd; + errno = ENOSYS; + return -1; +} + +int +fork(void) +{ + errno = ENOSYS; + return -1; +} + +#ifdef _MSC_VER /* implemented in (win)pthread MinGW library */ +int clock_gettime(int clk_id, struct timespec *tp) +{ + static LARGE_INTEGER frequency = { 0 }; + LARGE_INTEGER counter; + + if (frequency.QuadPart == 0) { + QueryPerformanceFrequency(&frequency); + } + + QueryPerformanceCounter(&counter); + + tp->tv_sec = (time_t)(counter.QuadPart / frequency.QuadPart); + tp->tv_nsec = (long)((counter.QuadPart % frequency.QuadPart) * 1000000000 / frequency.QuadPart); + + return 0; +} +#endif /* _MSC_VER */ diff --git a/src/pspg.c b/src/pspg.c index ce6dbd52..4c20f6c5 100644 --- a/src/pspg.c +++ b/src/pspg.c @@ -51,9 +51,7 @@ #endif #ifdef PDCURSES - #include - #endif #include @@ -5415,7 +5413,7 @@ main(int argc, char *argv[]) if (cursor_row < 0) cursor_row = 0; } - else + else make_beep(); } break; diff --git a/src/pspg.h b/src/pspg.h index bdf31b34..d57161b0 100644 --- a/src/pspg.h +++ b/src/pspg.h @@ -17,6 +17,8 @@ #include #include +#include "platform/platform.h" /* Cross-platform compatibility */ + #include "commands.h" #include "config.h" #include "themes.h" @@ -350,9 +352,6 @@ extern int max_int(int a, int b); #define time_diff(s1, ms1, s2, ms2) ((s1 - s2) * 1000 + ms1 - ms2) -#define UNUSED(expr) do { (void)(expr); } while (0) - - /* from print.c */ extern void window_fill(int window_identifier, int srcy, int srcx, int cursor_row, int vcursor_xmin, int vcursor_xmax, int selected_xmin, int selected_xmax, DataDesc *desc, ScrDesc *scrdesc, Options *opts); @@ -395,7 +394,7 @@ extern bool args_are_consistent(Options *opts, StateData *state); /* from infra.c */ extern void log_row(const char *fmt, ...); -extern void leave(const char *fmt, ...) __attribute__ ((noreturn)); +extern PSPG_NORETURN void leave(const char *fmt, ...); extern void format_error(const char *fmt, ...); extern void *smalloc(int size); diff --git a/src/readline.c b/src/readline.c index 32218a08..12bc5270 100644 --- a/src/readline.c +++ b/src/readline.c @@ -796,8 +796,11 @@ pspg_init_readline(const char *histfile) rl_catch_signals = 0; rl_catch_sigwinch = 0; +#ifndef _MSC_VER + /* readline-win32 does not check for NULL but has safe defaults */ rl_deprep_term_function = NULL; rl_prep_term_function = NULL; +#endif #if RL_READLINE_VERSION > 0x0603 diff --git a/src/st_panel.h b/src/st_panel.h index 7580e5a5..0795db1d 100644 --- a/src/st_panel.h +++ b/src/st_panel.h @@ -4,6 +4,8 @@ #include #elif defined HAVE_PANEL_H #include +#elif defined PDCURSES +#include #else /* fallback */ #include diff --git a/src/table.c b/src/table.c index 9bff2645..81425899 100644 --- a/src/table.c +++ b/src/table.c @@ -397,20 +397,20 @@ is_cmdtag(char *str) static size_t _getline(char **lineptr, size_t *n, FILE *fp, bool is_nonblocking, bool wait_on_data) { - int _errno; + int saved_errno; ssize_t result; if (!is_nonblocking) { result = getline(lineptr, n, fp); - _errno = errno; + saved_errno = errno; if (result < 0) { free(*lineptr); *lineptr = NULL; - errno = _errno; + errno = saved_errno; } return result; @@ -430,7 +430,7 @@ _getline(char **lineptr, size_t *n, FILE *fp, bool is_nonblocking, bool wait_on_ errno = 0; str = fgets(statbuf, STATBUF_SIZE, fp); - _errno = errno; + saved_errno = errno; if (str) { @@ -483,7 +483,7 @@ _getline(char **lineptr, size_t *n, FILE *fp, bool is_nonblocking, bool wait_on_ fetched_chars += len; } - errno = _errno; + errno = saved_errno; } if (errno || feof(fp)) @@ -835,11 +835,11 @@ readfile(Options *opts, DataDesc *desc, StateData *state) if ((tabptr = memchr(line, '\t', read))) { int tabcount = 1; - void *endptr = line + read - 1; + char *endptr = line + read - 1; char *newline, *writeptr, *readptr; int total_dl = 0; - while ((tabptr = memchr(((char *) tabptr) + 1, '\t', endptr - tabptr))) + while ((tabptr = memchr(((char *) tabptr) + 1, '\t', endptr - ((char *) tabptr)))) { tabcount += 1; } diff --git a/src/theme_loader.c b/src/theme_loader.c index ca63191f..dfad657e 100644 --- a/src/theme_loader.c +++ b/src/theme_loader.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include diff --git a/src/unicode.c b/src/unicode.c index 15764fba..7e0b91cf 100644 --- a/src/unicode.c +++ b/src/unicode.c @@ -177,6 +177,7 @@ ucs_wcwidth(wchar_t ucs) return 1; } +#ifndef MSVC_STATIC_POSTGRESQL /* * Map a Unicode code point to UTF-8. utf8string must have 4 bytes of * space allocated. @@ -218,6 +219,7 @@ unicode_to_utf8(wchar_t c, unsigned char *utf8string, int *size) return utf8string; } +#endif /* MSVC_STATIC_POSTGRESQL */ diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000..ea71bdf2 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "name": "pspg", + "version": "5.8.12", + "description": "PostgreSQL pager for Windows", + "homepage": "https://github.com/okbob/pspg", + "dependencies": [ + "readline", + "pdcurses", + "getopt" + ], + "features": { + "vcpkg-pg": { + "description": "Only enable this if you lack a local Postgres installation", + "dependencies": [ "libpq" ] + } + }, + "builtin-baseline": "edffab1bcd2cb5b8c17d6ba34d5651ea0bf82979" +} From aac9f754c3433a68463cb2bf084481d915b1e96e Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 20 May 2026 23:12:44 -0500 Subject: [PATCH 2/5] fix(menu): off-by-one caused PDCurses crash on short terminal --- src/st_menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/st_menu.c b/src/st_menu.c index 135293e3..7ca4a217 100644 --- a/src/st_menu.c +++ b/src/st_menu.c @@ -857,7 +857,7 @@ pulldownmenu_ajust_position(struct ST_MENU *menu, int maxy, int maxx) */ if (rows != menu->rows || cols != menu->cols) { - int new_rows = y + menu->rows <= maxy ? menu->rows : maxy - y + 1; + int new_rows = y + menu->rows <= maxy ? menu->rows : maxy - y; int new_cols = x + menu->cols <= maxx ? menu->cols : maxx - x + 1; if (new_rows != rows || new_cols != cols) From 94ed95506f4f3039638415302e51bde459cfcb10 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Tue, 19 May 2026 00:20:00 -0500 Subject: [PATCH 3/5] Don't use bold checkmark in menu on Windows This messes up formatting --- src/st_menu_styles.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/st_menu_styles.c b/src/st_menu_styles.c index 98697d9a..b60cca74 100644 --- a/src/st_menu_styles.c +++ b/src/st_menu_styles.c @@ -312,7 +312,11 @@ st_menu_load_style_rgb(ST_MENU_CONFIG *config, int style, int start_from_cpn, in if (!config->force8bit && !config->force_ascii_art) { +#ifdef _WIN32 + config->mark_tag = L'\x2713'; +#else config->mark_tag = L'\x2714'; +#endif /* config->switch_tag_n1 = L'\x2680'; config->switch_tag_0 = L'\x2610'; @@ -321,7 +325,11 @@ st_menu_load_style_rgb(ST_MENU_CONFIG *config, int style, int start_from_cpn, in config->switch_tag_n1 = '.'; config->switch_tag_0 = ' '; +#ifdef _WIN32 + config->switch_tag_1 = L'\x2713'; +#else config->switch_tag_1 = L'\x2714'; +#endif config->scroll_up_tag = L'\x25b2'; config->scroll_down_tag = L'\x25bc'; From cf7bc279df531812dc8eeb5473b6eb5d163cc079 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Tue, 2 Jun 2026 14:56:43 -0500 Subject: [PATCH 4/5] Test on GitHub CI w/ Meson --- .github/workflows/freebsd.yml | 29 ++++++++++ .github/workflows/haiku.yml | 31 +++++++++++ .github/workflows/linux.yml | 34 +++++++++--- .github/workflows/macos.yml | 37 +++++++++++++ .github/workflows/windows-static.yml | 82 ++++++++++++++++++++++++++++ .github/workflows/windows.yml | 55 +++++++++++++++++++ 6 files changed, 259 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/freebsd.yml create mode 100644 .github/workflows/haiku.yml create mode 100644 .github/workflows/macos.yml create mode 100644 .github/workflows/windows-static.yml create mode 100644 .github/workflows/windows.yml diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml new file mode 100644 index 00000000..2430cf60 --- /dev/null +++ b/.github/workflows/freebsd.yml @@ -0,0 +1,29 @@ +name: FreeBSD + +on: + push: + branches: + - '**' + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + shell: cpa.sh {0} + steps: + - uses: actions/checkout@v6 + - uses: cross-platform-actions/action@v1.2.0 + with: + operating_system: freebsd + version: '15.0' + memory: 2G + - name: Install dependencies + run: sudo pkg install -y devel/readline devel/ncurses databases/postgresql18-client meson ninja pkgconf + - name: Configure Meson + run: meson setup build + - name: Build + run: meson compile -C build + - name: Test + run: build/pspg --info diff --git a/.github/workflows/haiku.yml b/.github/workflows/haiku.yml new file mode 100644 index 00000000..4a82656d --- /dev/null +++ b/.github/workflows/haiku.yml @@ -0,0 +1,31 @@ +name: Haiku + +on: + push: + branches: + - '**' + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + shell: cpa.sh {0} + steps: + - uses: actions/checkout@v6 + - uses: cross-platform-actions/action@v1.2.0 + with: + operating_system: haiku + version: r1beta5 + memory: 2G + - name: Install dependencies + # newer PG ports miss pg_hmac_create and pg_tolower that somehow get pulled in :( + # also https://github.com/haikuports/haikuports/pull/12892#issuecomment-3248334462 + run: pkgman install -y pkgconfig meson readline ncurses6_devel postgresql11_devel + - name: Configure Meson + run: meson setup build + - name: Build + run: meson compile -C build + - name: Test + run: build/pspg --info diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8504837c..4c173e39 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -1,10 +1,10 @@ -name: build Linux +name: Ubuntu Linux on: push: - branches: [ "master" ] + branches: + - '**' pull_request: - branches: [ "master" ] jobs: build: @@ -12,9 +12,25 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: configure - run: ./configure - - name: make - run: make - + - uses: actions/checkout@v6 + - name: Install dependencies + run: >- + sudo apt-get install -qq -y -o=Dpkg::Use-Pty=0 + gcc + meson-1.7 + ninja-build + libpq-dev + libncurses-dev + libreadline-dev + - name: Configure Meson + run: meson setup build --backend ninja --buildtype release + - name: Build + run: meson compile -C build + - name: Test + run: build/pspg --info + - name: Upload Meson log + uses: actions/upload-artifact@v7 + if: ${{ failure() }} + with: + name: Meson log + path: build/meson-logs/meson-log.txt diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 00000000..43a0aa10 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,37 @@ +name: macOS + +on: + push: + branches: + - '**' + pull_request: + +jobs: + build: + + runs-on: macos-latest + + steps: + - uses: actions/checkout@v6 + - name: Install dependencies + env: + HOMEBREW_NO_AUTO_UPDATE: 1 + run: | + brew install meson + # libpq + # readline + # ncurses + - name: Configure Meson + env: + PKG_CONFIG_PATH: /opt/homebrew/opt/readline/lib/pkgconfig:/opt/homebrew/opt/libpq/lib/pkgconfig + run: meson setup build --backend ninja + - name: Build + run: meson compile -C build + - name: Test + run: build/pspg --info + - name: Upload Meson log + uses: actions/upload-artifact@v7 + if: ${{ failure() }} + with: + name: Meson log + path: build/meson-logs/meson-log.txt diff --git a/.github/workflows/windows-static.yml b/.github/workflows/windows-static.yml new file mode 100644 index 00000000..57f46051 --- /dev/null +++ b/.github/workflows/windows-static.yml @@ -0,0 +1,82 @@ +name: Semi-static on Windows w/ CLANG64 + +on: + release: + types: [published] + push: + branches: + - '**' + pull_request: + +jobs: + build: + runs-on: windows-latest + defaults: + run: + shell: bash + env: + PKG_CONFIG_PATH: C:\msys64\clang64\lib\pkgconfig + MSYSTEM: CLANG64 + MINGW_PREFIX: /clang64 + steps: + - uses: actions/checkout@v6 + - name: Cache downloaded Pacman packages + uses: actions/cache@v5 + with: + path: C:\msys64\var\cache\pacman\pkg + key: pacman-win-static + - name: Add MSYS2 to PATH + shell: pwsh + run: | + echo "C:\msys64\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + echo "C:\msys64\clang64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Install dependencies + run: >- + pacman --noconfirm -S + mingw-w64-clang-x86_64-clang + mingw-w64-clang-x86_64-meson + mingw-w64-clang-x86_64-ninja + mingw-w64-clang-x86_64-pkgconf + mingw-w64-clang-x86_64-postgresql + mingw-w64-clang-x86_64-pdcurses + mingw-w64-clang-x86_64-upx + - name: Configure Meson + run: >- + meson setup build + -Dshlibs=pg -Ddep_prefix=$MINGW_PREFIX + -Dc_link_args="-s -Wl,--delayload=libpq.dll" + --buildtype=minsize -Db_ndebug=true -Ddebug=false + - name: Build + run: meson compile -C build + - name: Test + run: build/pspg.exe --info + - name: Compress binary with UPX + run: upx --best --ultra-brute -o build/pspg_upx.exe build/pspg.exe + - name: Upload artifact + uses: actions/upload-artifact@v7 + with: + name: pspg-windows-binary + path: build/pspg*.exe + - name: Upload Meson log + uses: actions/upload-artifact@v7 + if: ${{ failure() }} + with: + name: Meson log + path: build/meson-logs/meson-log.txt + + upload-release: + if: github.event_name == 'release' + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download built binaries + uses: actions/download-artifact@v6 + with: + name: pspg-windows-binary + path: dist + - name: Upload assets to release + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload "${{ github.event.release.tag_name }}" dist/pspg*.exe --clobber --repo "${{ github.repository }}" diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 00000000..d4f0659b --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,55 @@ +name: MSVC & ClangCL + +on: + push: + branches: + - '**' + pull_request: + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v6 + + - name: Cache Vcpkg + uses: actions/cache@v5 + with: + path: vcpkg_installed + key: vcpkg + + - name: Install Meson + run: | + winget install -e --id mesonbuild.meson --silent --accept-source-agreements --accept-package-agreements + echo "C:\Program Files\Meson" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: MSVC environment + uses: ilammy/msvc-dev-cmd@v1 + + - name: Install Vcpkg dependencies + run: C:\vcpkg\vcpkg.exe install --vcpkg-root=C:\vcpkg + + - name: Configure Meson + env: + CC: clang-cl + # --genvslite vs2022 + run: >- + meson setup build --backend vs + -Dpgroot="C:\Program Files\PostgreSQL\17" -Ddep_prefix=vcpkg_installed/x64-windows + + - name: Build + run: meson compile -C build + + - name: Test + env: + PATH: "${{ github.workspace }}/vcpkg_installed/x64-windows/bin;C:/Program Files/PostgreSQL/17/bin;$env:PATH" + run: | + echo $env:PATH + build\pspg.exe --info + + - name: Upload artifact + uses: actions/upload-artifact@v7 + with: + name: Windows shlibs + path: build/RelWithDebInfo/* From 6dcf8fdc35a4c659b2a276ae075897eb3ff2363d Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 3 Jun 2026 12:07:16 -0500 Subject: [PATCH 5/5] build(meson): add bsd dependency for getpass on Haiku --- meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/meson.build b/meson.build index 02b8ad3f..5bbfcb3f 100644 --- a/meson.build +++ b/meson.build @@ -155,6 +155,7 @@ if not pg.found() endif if pg.found() build_args += '-DHAVE_POSTGRESQL' + project_deps += cc.find_library('bsd', required: host_machine.system() == 'haiku') # getpass endif sources = [