From 5772c9f0127422200aa8d86f305cb2ab6b250e82 Mon Sep 17 00:00:00 2001 From: Robert Nix Date: Wed, 1 Jun 2016 11:32:20 -0500 Subject: [PATCH 01/75] Windows build Includes LuaJIT patches to make Windows ffi semantics match the way Howl expects. --- lib/howl/init.lua | 2 + ...01-luajit-ffi-win32-walk-all-modules.patch | 102 ++++++++++++++++++ src/002-luajit-win32-build-static.patch | 15 +++ src/Makefile | 20 +++- src/process_helpers.c | 43 ++++++++ 5 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 src/001-luajit-ffi-win32-walk-all-modules.patch create mode 100644 src/002-luajit-win32-build-static.patch diff --git a/lib/howl/init.lua b/lib/howl/init.lua index 3df46dca9..7f88c5777 100644 --- a/lib/howl/init.lua +++ b/lib/howl/init.lua @@ -19,6 +19,8 @@ Where options can be any of: ]=] local path_separator = jit.os == 'Windows' and '\\' or '/' +local path_prefix = jit.os == 'Windows' and '\\\\.\\' or '' +app_root = path_prefix .. app_root local function parse_args(argv) local options = { diff --git a/src/001-luajit-ffi-win32-walk-all-modules.patch b/src/001-luajit-ffi-win32-walk-all-modules.patch new file mode 100644 index 000000000..3d105bb30 --- /dev/null +++ b/src/001-luajit-ffi-win32-walk-all-modules.patch @@ -0,0 +1,102 @@ +--- LuaJIT-2.1.0-beta1-orig/src/lj_clib.c 2015-08-25 16:35:00.000000000 -0500 ++++ LuaJIT-2.1.0-beta1/src/lj_clib.c 2016-06-01 08:56:37.805150100 -0500 +@@ -146,6 +146,7 @@ + + #define WIN32_LEAN_AND_MEAN + #include ++#include + + #ifndef GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS + #define GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS 4 +@@ -231,33 +232,69 @@ + } + } + ++typedef struct LdrListEntry { ++ struct LdrListEntry *Flink; ++ struct LdrListEntry *Blink; ++} LdrListEntry; ++ ++typedef struct LdrDataTableEntry { ++ LdrListEntry InLoadOrderLinks; ++ LdrListEntry InMemoryOrderLinks; ++ LdrListEntry InInitializationOrderLinks; ++ void *ModuleBase; ++} LdrDataTableEntry; ++ ++typedef struct LdrData { ++ unsigned int Length; ++ int Initialized; ++ void *SsHandle; ++ LdrListEntry InLoadOrderModuleList; ++ LdrListEntry InMemoryOrderModuleList; ++ LdrListEntry InInitializationOrderModuleList; ++ void *EntryInProgress; ++} LdrData; ++ ++typedef struct Win32Peb { ++ void *Reserved[3]; ++ LdrData *Ldr; ++} Win32Peb; ++ ++/* These definitions depend on MingW's shims for the MSVC instrinsics. */ ++/* Can copy those shims from intrin.h if a non-mingw gcc is encountered. */ ++#if _WIN64 ++ ++static Win32Peb *clib_win32_getpeb() { ++ return (Win32Peb *)__readgsqword(0x60); ++} ++ ++#else ++ ++static Win32Peb *clib_win32_getpeb() { ++ return (Win32Peb *)__readfsdword(0x30); ++} ++ ++#endif ++ + static void *clib_getsym(CLibrary *cl, const char *name) + { + void *p = NULL; + if (cl->handle == CLIB_DEFHANDLE) { /* Search default libraries. */ +- MSize i; +- for (i = 0; i < CLIB_HANDLE_MAX; i++) { +- HINSTANCE h = (HINSTANCE)clib_def_handle[i]; +- if (!(void *)h) { /* Resolve default library handles (once). */ +- switch (i) { +- case CLIB_HANDLE_EXE: GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, NULL, &h); break; +- case CLIB_HANDLE_DLL: +- GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS|GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, +- (const char *)clib_def_handle, &h); +- break; +- case CLIB_HANDLE_CRT: +- GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS|GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, +- (const char *)&_fmode, &h); +- break; +- case CLIB_HANDLE_KERNEL32: h = LoadLibraryExA("kernel32.dll", NULL, 0); break; +- case CLIB_HANDLE_USER32: h = LoadLibraryExA("user32.dll", NULL, 0); break; +- case CLIB_HANDLE_GDI32: h = LoadLibraryExA("gdi32.dll", NULL, 0); break; +- } +- if (!h) continue; +- clib_def_handle[i] = (void *)h; ++ LdrListEntry *begin, *end; ++ LdrDataTableEntry *curr; ++ Win32Peb *peb = clib_win32_getpeb(); ++ end = &peb->Ldr->InLoadOrderModuleList; ++ begin = end->Flink; ++ while (begin != end) { ++ curr = (LdrDataTableEntry *)((char *)begin); ++ /* From what I've seen, this is unnecessary since ModuleBase == HMODULE */ ++ HINSTANCE h; ++ GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS|GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, ++ (const char *)curr->ModuleBase, &h); ++ if (h) { ++ p = (void *)GetProcAddress(h, name); ++ if (p) break; + } +- p = (void *)GetProcAddress(h, name); +- if (p) break; ++ begin = begin->Flink; + } + } else { + p = (void *)GetProcAddress((HINSTANCE)cl->handle, name); diff --git a/src/002-luajit-win32-build-static.patch b/src/002-luajit-win32-build-static.patch new file mode 100644 index 000000000..f83f6731b --- /dev/null +++ b/src/002-luajit-win32-build-static.patch @@ -0,0 +1,15 @@ +--- LuaJIT-2.1.0-beta1-orig/src/Makefile 2015-08-25 16:35:00.000000000 -0500 ++++ LuaJIT-2.1.0-beta1/src/Makefile 2016-05-31 18:50:20.426644400 -0500 +@@ -69,10 +69,10 @@ + # as dynamic mode. + # + # Mixed mode creates a static + dynamic library and a statically linked luajit. +-BUILDMODE= mixed ++#BUILDMODE= mixed + # + # Static mode creates a static library and a statically linked luajit. +-#BUILDMODE= static ++BUILDMODE= static + # + # Dynamic mode creates a dynamic library and a dynamically linked luajit. + # Note: this executable will only run when the library is installed! diff --git a/src/Makefile b/src/Makefile index 239ca611d..808af2062 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,9 +1,17 @@ PREFIX ?= /usr/local UNAME_S := $(shell uname -s) +CWD := $(shell pwd) +ifneq (, $(findstring _NT-,$(UNAME_S))) + WIN32 = 1 +endif GTK = gtk+-3.0 GTK_CFLAGS = $(shell pkg-config --cflags $(GTK)) -GTK_LIBS = $(shell pkg-config --libs $(GTK) gmodule-2.0 gio-unix-2.0) +GTK_LIBNAMES = $(GTK) gmodule-2.0 gio-unix-2.0 +ifdef WIN32 + GTK_LIBNAMES = $(GTK) gmodule-2.0 +endif +GTK_LIBS = $(shell pkg-config --libs $(GTK_LIBNAMES)) LUAJIT_VER = LuaJIT-2.1.0-beta1 LUAJIT_CHECKSUM = 5a5bf71666e77cf6e7a1ae851127b834 @@ -12,6 +20,11 @@ LUAJIT_SRC_DIR = $(realpath $(LUAJIT)/src) LUAJIT_CFLAGS = -I$(LUAJIT_SRC_DIR) LUAJIT_ARCHIVE = $(LUAJIT)/src/libluajit.a LUAJIT_URL = http://nordman.org/mirror/luajit/$(LUAJIT_VER).tar.gz +ifdef WIN32 + LUAJIT_PATCHES = \ + 001-luajit-ffi-win32-walk-all-modules.patch \ + 002-luajit-win32-build-static.patch +endif LPEG_VER = lpeg-0.10.2 LPEG_CHECKSUM = 1402433f02e37ddadff04a3d4118b026 @@ -30,6 +43,10 @@ ifeq ($(UNAME_S),Darwin) else LD_FLAGS = -Wl,-E endif +ifdef WIN32 + LIBS = -lm ${GTK_LIBS} -lstdc++ + LD_FLAGS = -Wl,--export-all +endif OBJECTS = main.o process_helpers.o DEP_OBJECTS = $(LPEG_OBJECT) @@ -50,6 +67,7 @@ $(LPEG_OBJECT): $(LPEG) $(LUAJIT) $(LUAJIT): @tools/download $(LUAJIT_URL) $(LUAJIT_CHECKSUM) tar xzf {file} -C deps @perl -piorig -e 's/LUA_IDSIZE\s*\d+/LUA_IDSIZE 120/' $(LUAJIT)/src/luaconf.h + cd ${LUAJIT} && for p in $(LUAJIT_PATCHES); do patch -Np1 -i $(CWD)/$$p; done $(LUAJIT_ARCHIVE): $(LUAJIT) cd ${LUAJIT} && $(MAKE) XCFLAGS="-DLUAJIT_ENABLE_LUA52COMPAT" diff --git a/src/process_helpers.c b/src/process_helpers.c index 7902cbbb5..e01365017 100644 --- a/src/process_helpers.c +++ b/src/process_helpers.c @@ -2,7 +2,50 @@ /* License: MIT (see LICENSE.md at the top-level directory of the distribution) */ #include +#ifndef _WIN32 #include +#else +#define WIFEXITED(w) (((w) & 0xff) == 0) +#define WIFSIGNALED(w) (((w) & 0x7f) > 0 && (((w) & 0x7f) < 0x7f)) +#define WEXITSTATUS(w) (((w) >> 8) & 0xff) +#define WTERMSIG(w) ((w) & 0x7f) + +#define SIGHUP 1 /* hangup */ +#define SIGINT 2 /* interrupt */ +#define SIGQUIT 3 /* quit */ +#define SIGILL 4 /* illegal instruction (not reset when caught) */ +#define SIGTRAP 5 /* trace trap (not reset when caught) */ +#define SIGABRT 6 /* used by abort */ +#define SIGIOT SIGABRT /* synonym for SIGABRT on most systems */ +#define SIGEMT 7 /* EMT instruction */ +#define SIGFPE 8 /* floating point exception */ +#define SIGKILL 9 /* kill (cannot be caught or ignored) */ +#define SIGBUS 10 /* bus error */ +#define SIGSEGV 11 /* segmentation violation */ +#define SIGSYS 12 /* bad argument to system call */ +#define SIGPIPE 13 /* write on a pipe with no one to read it */ +#define SIGALRM 14 /* alarm clock */ +#define SIGTERM 15 /* software termination signal from kill */ +#define SIGURG 16 /* urgent condition on IO channel */ +#define SIGSTOP 17 /* sendable stop signal not from tty */ +#define SIGTSTP 18 /* stop signal from tty */ +#define SIGCONT 19 /* continue a stopped process */ +#define SIGCHLD 20 /* to parent on child stop or exit */ +#define SIGCLD 20 /* System V name for SIGCHLD */ +#define SIGTTIN 21 /* to readers pgrp upon background tty read */ +#define SIGTTOU 22 /* like TTIN for output if (tp->t_local<OSTOP) */ +#define SIGIO 23 /* input/output possible signal */ +#define SIGPOLL SIGIO /* System V name for SIGIO */ +#define SIGXCPU 24 /* exceeded CPU time limit */ +#define SIGXFSZ 25 /* exceeded file size limit */ +#define SIGVTALRM 26 /* virtual time alarm */ +#define SIGPROF 27 /* profiling time alarm */ +#define SIGWINCH 28 /* window changed */ +#define SIGLOST 29 /* resource lost (eg, record-lock lost) */ +#define SIGPWR SIGLOST /* power failure */ +#define SIGUSR1 30 /* user defined signal 1 */ +#define SIGUSR2 31 /* user defined signal 2 */ +#endif #include int process_exited_normally(int status) { return WIFEXITED(status); } From f6b8528c878321959674733b839128f9aab81fc9 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Tue, 23 Aug 2016 20:08:59 -0500 Subject: [PATCH 02/75] Windows process execution --- lib/howl/cdefs/init.moon | 5 +++-- lib/howl/cdefs/windows.moon | 13 +++++++++++++ lib/howl/io/input_stream.moon | 9 +++++++-- lib/howl/io/output_stream.moon | 8 ++++++-- lib/howl/io/process.moon | 3 ++- lib/ljglibs/cdefs/gio.moon | 8 ++++++++ lib/ljglibs/cdefs/glib.moon | 6 +++++- lib/ljglibs/gio/win32input_stream.moon | 22 ++++++++++++++++++++++ lib/ljglibs/gio/win32output_stream.moon | 22 ++++++++++++++++++++++ lib/ljglibs/glib/spawn.moon | 11 +++++++++-- src/main.c | 4 ++++ src/process_helpers.c | 8 ++------ 12 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 lib/howl/cdefs/windows.moon create mode 100644 lib/ljglibs/gio/win32input_stream.moon create mode 100644 lib/ljglibs/gio/win32output_stream.moon diff --git a/lib/howl/cdefs/init.moon b/lib/howl/cdefs/init.moon index f16dad85d..44dcf100a 100644 --- a/lib/howl/cdefs/init.moon +++ b/lib/howl/cdefs/init.moon @@ -15,8 +15,6 @@ int kill(pid_t pid, int sig); /* process helpers */ int process_exited_normally(int status); int process_exit_status(int status); -int process_was_signalled(int status); -int process_get_term_sig(int status); int sig_HUP; int sig_INT; @@ -51,6 +49,9 @@ int sig_PWR; int sig_SYS; ]] +if ffi.os == 'Windows' + require 'howl.cdefs.windows' + return { const_char_p: ffi.typeof 'const char *' char_p: ffi.typeof 'char *' diff --git a/lib/howl/cdefs/windows.moon b/lib/howl/cdefs/windows.moon new file mode 100644 index 000000000..d8f6f4d20 --- /dev/null +++ b/lib/howl/cdefs/windows.moon @@ -0,0 +1,13 @@ +-- Copyright 2016 The Howl Developers +-- License: MIT (see LICENSE.md at the top-level directory of the distribution) + +ffi = require 'ffi' + +ffi.cdef [[ + typedef void* HANDLE; + typedef int DWORD; + + DWORD GetProcessId(HANDLE process); + HANDLE _get_osfhandle(int fd); + BOOL TerminateProcess(HANDLE process, unsigned int exitcode); +]] diff --git a/lib/howl/io/input_stream.moon b/lib/howl/io/input_stream.moon index e96eba362..70f8156c8 100644 --- a/lib/howl/io/input_stream.moon +++ b/lib/howl/io/input_stream.moon @@ -3,13 +3,18 @@ dispatch = howl.dispatch glib = require 'ljglibs.glib' -{:UnixInputStream} = require 'ljglibs.gio' +{:Win32InputStream, :UnixInputStream} = require 'ljglibs.gio' {:PropertyObject} = howl.util.moon append = table.insert +ffi = require 'ffi' class InputStream extends PropertyObject new: (@stream, @priority = glib.PRIORITY_LOW) => - @stream = UnixInputStream(@stream) if type(@stream) == 'number' + if type(@stream) == 'number' + if ffi.os == 'Windows' + @stream = Win32InputStream ffi.C._get_osfhandle @stream + else + @stream = UnixInputStream @stream super! @property is_closed: get: => @stream.is_closed diff --git a/lib/howl/io/output_stream.moon b/lib/howl/io/output_stream.moon index 8b349dd3d..c9bbb817c 100644 --- a/lib/howl/io/output_stream.moon +++ b/lib/howl/io/output_stream.moon @@ -2,12 +2,16 @@ -- License: MIT (see LICENSE.md at the top-level directory of the distribution) dispatch = howl.dispatch -{:UnixOutputStream} = require 'ljglibs.gio' +{:Win32OutputStream, :UnixOutputStream} = require 'ljglibs.gio' {:PropertyObject} = howl.util.moon +ffi = require 'ffi' class OutputStream extends PropertyObject new: (fd) => - @stream = UnixOutputStream fd + if ffi.os == 'Windows' + @stream = Win32OutputStream ffi.C._get_osfhandle @stream + else + @stream = UnixOutputStream @stream super! @property is_closed: get: => @stream.is_closed diff --git a/lib/howl/io/process.moon b/lib/howl/io/process.moon index 410477617..da51ec5d5 100644 --- a/lib/howl/io/process.moon +++ b/lib/howl/io/process.moon @@ -108,6 +108,7 @@ class Process @argv, @command_line = get_command opts.cmd, opts.shell error 'opts.cmd missing or invalid', 2 unless @argv @_process = launch @argv, opts + @true_pid = @_process.true_pid @pid = @_process.pid @working_directory = File opts.working_directory or get_current_dir! @stdin = OutputStream(@_process.stdin_pipe) if @_process.stdin_pipe @@ -118,7 +119,7 @@ class Process @@running[@pid] = @ @_exit_handle = callbacks.register child_exited, "process-watch-#{@pid}", @ - C.g_child_watch_add ffi_cast('GPid', @pid), child_watch_callback, callbacks.cast_arg(@_exit_handle.id) + C.g_child_watch_add ffi_cast('GPid', @true_pid), child_watch_callback, callbacks.cast_arg(@_exit_handle.id) wait: => return if @exited diff --git a/lib/ljglibs/cdefs/gio.moon b/lib/ljglibs/cdefs/gio.moon index d0ff423f7..8e5fbaaf4 100644 --- a/lib/ljglibs/cdefs/gio.moon +++ b/lib/ljglibs/cdefs/gio.moon @@ -111,6 +111,14 @@ ffi.cdef [[ typedef struct {} GUnixOutputStream; GUnixOutputStream * g_unix_output_stream_new (gint fd, gboolean close_fd); + /* GWin32InputStream */ + typedef struct {} GWin32InputStream; + GWin32InputStream * g_win32_input_stream_new (void* handle, gboolean close_handle); + + /* GWin32OutputStream */ + typedef struct {} GWin32OutputStream; + GWin32OutputStream * g_win32_output_stream_new (void* handle, gboolean close_handle); + /* GFile and friends */ typedef struct {} GFileInputStream; typedef struct {} GFileOutputStream; diff --git a/lib/ljglibs/cdefs/glib.moon b/lib/ljglibs/cdefs/glib.moon index 523f81da8..e1834ad18 100644 --- a/lib/ljglibs/cdefs/glib.moon +++ b/lib/ljglibs/cdefs/glib.moon @@ -3,6 +3,11 @@ ffi = require 'ffi' +if ffi.os == 'Windows' + ffi.cdef 'typedef void* GPid;' +else + ffi.cdef 'typedef int GPid;' + ffi.cdef [[ typedef char gchar; typedef long glong; @@ -28,7 +33,6 @@ ffi.cdef [[ typedef double gdouble; typedef float gfloat; typedef const void * gconstpointer; - typedef int GPid; /* version definitions */ extern const guint glib_major_version; diff --git a/lib/ljglibs/gio/win32input_stream.moon b/lib/ljglibs/gio/win32input_stream.moon new file mode 100644 index 000000000..166f803fc --- /dev/null +++ b/lib/ljglibs/gio/win32input_stream.moon @@ -0,0 +1,22 @@ +-- Copyright 2014-2015 The Howl Developers +-- License: MIT (see LICENSE.md at the top-level directory of the distribution) + +require 'ljglibs.gio.input_stream' +ffi = require 'ffi' +core = require 'ljglibs.core' +{:ref_ptr} = require 'ljglibs.gobject' + +C = ffi.C + +release = (p) -> + C.g_input_stream_close, p, nil + C.g_object_unref(p) + +core.define 'GWin32InputStream < GInputStream', { + properties: { + handle: 'void*' + close_handle: 'gboolean' + } + +}, (t, handle, close_handle = true) -> + ffi.gc C.g_win32_input_stream_new(handle, close_handle), release diff --git a/lib/ljglibs/gio/win32output_stream.moon b/lib/ljglibs/gio/win32output_stream.moon new file mode 100644 index 000000000..56b796d5a --- /dev/null +++ b/lib/ljglibs/gio/win32output_stream.moon @@ -0,0 +1,22 @@ +-- Copyright 2016 The Howl Developers +-- License: MIT (see LICENSE.md at the top-level directory of the distribution) + +require 'ljglibs.gio.output_stream' +ffi = require 'ffi' +core = require 'ljglibs.core' +{:ref_ptr} = require 'ljglibs.gobject' + +C = ffi.C + +release = (p) -> + C.g_output_stream_close, p, nil + C.g_object_unref(p) + +core.define 'GWin32OutputStream < GOutputStream', { + properties: { + handle: 'void*' + close_handle: 'gboolean' + } + +}, (t, handle, close_handle = true) -> + ffi.gc C.g_win32_output_stream_new(handle, close_handle), release diff --git a/lib/ljglibs/glib/spawn.moon b/lib/ljglibs/glib/spawn.moon index b86e00a0c..12b590b2b 100644 --- a/lib/ljglibs/glib/spawn.moon +++ b/lib/ljglibs/glib/spawn.moon @@ -34,6 +34,8 @@ spawn = { argv = char_p_arr opts.argv pid = ffi_new 'GPid[1]' spawn_flags = core.parse_flags('G_SPAWN_', opts.flags) + if ffi.os == 'Windows' + spawn_flags = bit.bor spawn_flags, flags['DO_NOT_REAP_CHILD'] envp = get_envp opts.env stdin = opts.write_stdin and ffi_new('gint[1]') or nil @@ -47,13 +49,18 @@ spawn = { pid, stdin, stdout, stderr - pid = tonumber pid[0] + true_pid = pid[0] + pid = if ffi.os == 'Windows' + C.GetProcessId true_pid + else + tonumber true_pid caller_will_reap = bit.band(flags['DO_NOT_REAP_CHILD'], spawn_flags) != 0 destructor = caller_will_reap and nil or ffi_gc ffi_new('struct {}'), -> - C.g_spawn_close_pid pid + C.g_spawn_close_pid true_pid { + :true_pid :pid flags: spawn_flags stdin_pipe: stdin and stdin[0] or nil diff --git a/src/main.c b/src/main.c index aa701552c..4b1a2ec89 100644 --- a/src/main.c +++ b/src/main.c @@ -2,12 +2,15 @@ /* License: MIT (see LICENSE.md at the top-level directory of the distribution) */ #include "main.h" +#include #include #include #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) +lua_State* globalL; + static void lua_run(int argc, char *argv[], const gchar *app_root, lua_State *L) { gchar *start_script; @@ -93,6 +96,7 @@ int main(int argc, char *argv[]) } gchar *app_root = get_app_root(argv[0]); lua_State *L = open_lua_state(app_root); + globalL = L; lua_run(argc, argv, app_root, L); lua_close(L); g_free(app_root); diff --git a/src/process_helpers.c b/src/process_helpers.c index e01365017..32b2d0b9b 100644 --- a/src/process_helpers.c +++ b/src/process_helpers.c @@ -5,10 +5,8 @@ #ifndef _WIN32 #include #else -#define WIFEXITED(w) (((w) & 0xff) == 0) -#define WIFSIGNALED(w) (((w) & 0x7f) > 0 && (((w) & 0x7f) < 0x7f)) -#define WEXITSTATUS(w) (((w) >> 8) & 0xff) -#define WTERMSIG(w) ((w) & 0x7f) +#define WIFEXITED(w) ((w) != 3) +#define WEXITSTATUS(w) (w) #define SIGHUP 1 /* hangup */ #define SIGINT 2 /* interrupt */ @@ -50,8 +48,6 @@ int process_exited_normally(int status) { return WIFEXITED(status); } int process_exit_status(int status) { return WEXITSTATUS(status); } -int process_was_signalled(int status) { return WIFSIGNALED(status); } -int process_get_term_sig(int status) { return WTERMSIG(status); } int sig_HUP = SIGHUP; int sig_INT = SIGINT; From a67a388edc9c0ae62ae3328ef29bfd09a1584fa9 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 11:28:01 -0500 Subject: [PATCH 03/75] Make process termination work --- lib/howl/cdefs/windows.moon | 1 + lib/howl/io/process.moon | 44 ++++++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/lib/howl/cdefs/windows.moon b/lib/howl/cdefs/windows.moon index d8f6f4d20..cb69c0440 100644 --- a/lib/howl/cdefs/windows.moon +++ b/lib/howl/cdefs/windows.moon @@ -6,6 +6,7 @@ ffi = require 'ffi' ffi.cdef [[ typedef void* HANDLE; typedef int DWORD; + typedef int BOOL; DWORD GetProcessId(HANDLE process); HANDLE _get_osfhandle(int fd); diff --git a/lib/howl/io/process.moon b/lib/howl/io/process.moon index da51ec5d5..a480f2702 100644 --- a/lib/howl/io/process.moon +++ b/lib/howl/io/process.moon @@ -8,7 +8,7 @@ callbacks = require 'ljglibs.callbacks' dispatch = howl.dispatch {:File, :InputStream, :OutputStream} = howl.io -C, ffi_cast = ffi.C, ffi.cast +C, ffi_cast, ffi_os = ffi.C, ffi.cast, ffi.os append = table.insert child_watch_callback = ffi.cast 'GChildWatchFunc', callbacks.void3 @@ -22,6 +22,10 @@ for s in *{ } signals[s] = tonumber C["sig_#{s}"] +win_signals = {} +win_signals[signals['KILL']] = 1 +win_signals[signals['INT']] = 1 + jit.off true, true signal_name = (signal) -> @@ -72,12 +76,12 @@ pump_stream = (stream, handler, parking) -> local read_handler read_handler = (status, ret, err_code) -> if not status - dispatch.resume_with_error parking, "#{ret} (#{err_code})" + dispatch.resume_with_error, parking, "#{ret} (#{err_code})" else handler ret if ret == nil stream\close! - dispatch.resume parking + pcall dispatch.resume, parking else stream\read_async nil, read_handler @@ -114,6 +118,8 @@ class Process @stdin = OutputStream(@_process.stdin_pipe) if @_process.stdin_pipe @stdout = InputStream(@_process.stdout_pipe) if @_process.stdout_pipe @stderr = InputStream(@_process.stderr_pipe, PRIORITY_LOW - 10) if @_process.stderr_pipe + @stdout_done = nil + @stderr_done = nil @exited = false @@running[@pid] = @ @@ -128,7 +134,14 @@ class Process send_signal: (signal) => signal = signals[signal] if type(signal) == 'string' - C.kill(@pid, signal) + if ffi_os == 'Windows' + error "Signal #{signal} is not supported on Windows" unless win_signals[signal] + -- On Bash, when a process exits due to a signal, it's exit code is + -- 128+{signal code}. Since killing a process like that doesn't + -- necessarily work on Windows, this emulates that exit code. + C.TerminateProcess(@true_pid, 128+signal) + else + C.kill(@pid, signal) pump: (on_stdout, on_stderr) => if on_stdout and not @stdout @@ -148,13 +161,15 @@ class Process stderr = {} on_stderr = (err) -> stderr[#stderr + 1] = err - stdout_done = on_stdout and dispatch.park "process-wait-stdout-#{@pid}" - stderr_done = on_stderr and dispatch.park "process-wait-stderr-#{@pid}" - pump_stream(@stderr, on_stderr, stderr_done, true) if on_stderr - pump_stream(@stdout, on_stdout, stdout_done) if on_stdout + @stdout_done = on_stdout and dispatch.park "process-wait-stdout-#{@pid}" + @stderr_done = on_stderr and dispatch.park "process-wait-stderr-#{@pid}" + pump_stream(@stderr, on_stderr, @stderr_done, true) if on_stderr + pump_stream(@stdout, on_stdout, @stdout_done) if on_stdout - dispatch.wait(stdout_done) if on_stdout - dispatch.wait(stderr_done) if on_stderr + dispatch.wait(@stdout_done) if on_stdout + @stdout_done = nil + dispatch.wait(@stderr_done) if on_stderr + @stderr_done = nil @wait! stdout = stdout and table.concat(stdout) @@ -186,3 +201,12 @@ class Process if @_exit dispatch.resume(@_exit) @_exit = nil + + -- On Windows, read_async may never call the callback, so the dispatchers + -- need to be resumed here instead. + if @stdout_done + pcall dispatch.resume, @stdout_done + @stdout_done = nil + if @stderr_done + pcall dispatch.resume, @stderr_done + @stderr_done = nil From 6f61ce1e4afbb163240febfcfd290568bf4dc991 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 11:29:50 -0500 Subject: [PATCH 04/75] Remove aux.moon (for now, at least) --- lib/howl/aux.moon | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 lib/howl/aux.moon diff --git a/lib/howl/aux.moon b/lib/howl/aux.moon deleted file mode 100644 index d83b665e6..000000000 --- a/lib/howl/aux.moon +++ /dev/null @@ -1,9 +0,0 @@ -util = howl.util - -setmetatable {}, { - __index: (key) => - tb = debug.traceback '', 2 - first = tb\match 'stack traceback:\n%s*([^\n]+)' - log.error "`howl.aux` is deprecated, please update your code to use `howl.util` instead\n (#{first})\n" - util[key] -} From dfc31cbec3a270a241feba6ac28f4c60d3289eff Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 11:30:00 -0500 Subject: [PATCH 05/75] Update theme fonts to have Windows-friendly versions --- bundles/howl-themes/solarized_light/solarized_light.moon | 2 +- bundles/howl-themes/steinom/steinom.moon | 2 +- bundles/howl-themes/tomorrow_night_blue/tm_night_blue.moon | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bundles/howl-themes/solarized_light/solarized_light.moon b/bundles/howl-themes/solarized_light/solarized_light.moon index 9c7cc4d6e..ee225f041 100644 --- a/bundles/howl-themes/solarized_light/solarized_light.moon +++ b/bundles/howl-themes/solarized_light/solarized_light.moon @@ -263,7 +263,7 @@ return { font: bold: true size: 'large' - family: 'Purisa,Latin Modern Sans' + family: 'Purisa,Comic Sans,Latin Modern Sans,Lucida Sans Unicode' definition: color: yellow function: color: blue diff --git a/bundles/howl-themes/steinom/steinom.moon b/bundles/howl-themes/steinom/steinom.moon index 70723435f..b054ac963 100644 --- a/bundles/howl-themes/steinom/steinom.moon +++ b/bundles/howl-themes/steinom/steinom.moon @@ -247,7 +247,7 @@ return { font: bold: true size: 'large' - family: 'Purisa,Latin Modern Sans' + family: 'Purisa,Comic Sans,Latin Modern Sans,Lucida Sans Unicode' definition: color: yellow diff --git a/bundles/howl-themes/tomorrow_night_blue/tm_night_blue.moon b/bundles/howl-themes/tomorrow_night_blue/tm_night_blue.moon index 4dde9709b..d715d1c7f 100644 --- a/bundles/howl-themes/tomorrow_night_blue/tm_night_blue.moon +++ b/bundles/howl-themes/tomorrow_night_blue/tm_night_blue.moon @@ -246,7 +246,7 @@ return { font: bold: true size: 'large' - family: 'Purisa,Latin Modern Sans' + family: 'Purisa,Comic Sans,Latin Modern Sans,Lucida Sans Unicode' definition: color: yellow From eda9cdd14f43cbda636cffcae9f9c76e2085f0f0 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 11:30:15 -0500 Subject: [PATCH 06/75] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6fa0df40e..3f56ef30c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ src/deps/*/* src/*.o src/howl +src/howl.exe *.bc *.bak site/build/ From 4a4a8422070d52a5598b4ec948a4c8b1b68da22b Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 13:12:00 -0500 Subject: [PATCH 07/75] Make icons work --- lib/howl/cdefs/windows.moon | 5 +++++ lib/howl/init.lua | 19 +++++++++++++++++-- lib/howl/ui/icons/font_awesome.moon | 10 +++++++++- src/Makefile | 2 +- src/main.c | 5 +++++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/lib/howl/cdefs/windows.moon b/lib/howl/cdefs/windows.moon index cb69c0440..723e1ab18 100644 --- a/lib/howl/cdefs/windows.moon +++ b/lib/howl/cdefs/windows.moon @@ -5,10 +5,15 @@ ffi = require 'ffi' ffi.cdef [[ typedef void* HANDLE; + typedef void* PVOID; typedef int DWORD; typedef int BOOL; + typedef const char* LPCTSTR; DWORD GetProcessId(HANDLE process); HANDLE _get_osfhandle(int fd); BOOL TerminateProcess(HANDLE process, unsigned int exitcode); + int AddFontResourceExA(LPCTSTR lpszFilename, DWORD fl, PVOID pdv); + + int fr_private; ]] diff --git a/lib/howl/init.lua b/lib/howl/init.lua index 7f88c5777..3b82bb5ac 100644 --- a/lib/howl/init.lua +++ b/lib/howl/init.lua @@ -164,12 +164,27 @@ local function main(args) set_package_path('lib', 'lib/ext', 'lib/ext/moonscript') require 'howl.moonscript_support' table.insert(package.loaders, 2, bytecode_loader()) - require 'howl.cdefs.fontconfig' - ffi.C.FcConfigAppFontAddDir(nil, table.concat({app_root, 'fonts'}, path_separator)) require 'ljglibs.cdefs.glib' howl = auto_module('howl') require('howl.globals') + + local font_dir = table.concat({app_root, 'fonts'}, path_separator) + if ffi.os == 'Windows' then + require 'howl.cdefs.windows' + local fonts = howl.io.File(font_dir).children + for _, font in ipairs(fonts) do + local loaded = ffi.C.AddFontResourceExA(font.path, ffi.C.fr_private, nil) + if loaded == 0 then + print('failed to load font ' .. font.path) + io:flush() + end + end + else + require 'howl.cdefs.fontconfig' + ffi.C.FcConfigAppFontAddDir(nil, font_dir) + end + _G.log = require('howl.log') local args = parse_args(argv) diff --git a/lib/howl/ui/icons/font_awesome.moon b/lib/howl/ui/icons/font_awesome.moon index 9d464c71a..318e94546 100644 --- a/lib/howl/ui/icons/font_awesome.moon +++ b/lib/howl/ui/icons/font_awesome.moon @@ -3,6 +3,8 @@ -- Font Awesome version 4.4.0 +ffi = require 'ffi' + icon_text = { '500px': '\239\137\174', 'adjust': '\239\129\130', @@ -680,9 +682,15 @@ icon_text = { 'youtube-square': '\239\133\166', } +-- On Windows, when the font is loaded, it gets labeled as "FontAwesome". +family = if ffi.os == 'Windows' + 'FontAwesome' +else + 'Font Awesome' + for name, text in pairs icon_text howl.ui.icon.define 'font-awesome-'..name, font: - family: 'Font Awesome' + :family size: 'small' :text diff --git a/src/Makefile b/src/Makefile index 808af2062..3ced541bb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -44,7 +44,7 @@ else LD_FLAGS = -Wl,-E endif ifdef WIN32 - LIBS = -lm ${GTK_LIBS} -lstdc++ + LIBS = -lm ${GTK_LIBS} -lstdc++ -lgdi32 LD_FLAGS = -Wl,--export-all endif OBJECTS = main.o process_helpers.o diff --git a/src/main.c b/src/main.c index 4b1a2ec89..cb077afab 100644 --- a/src/main.c +++ b/src/main.c @@ -9,6 +9,11 @@ #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) +#ifdef _WIN32 +#include +DWORD fr_private = FR_PRIVATE; +#endif + lua_State* globalL; static void lua_run(int argc, char *argv[], const gchar *app_root, lua_State *L) From 26dc5c3e224e89843e25ad99c2fbdb8a1c12087f Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 13:12:26 -0500 Subject: [PATCH 08/75] Clean up some debugging aids --- src/main.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main.c b/src/main.c index cb077afab..ee0e00946 100644 --- a/src/main.c +++ b/src/main.c @@ -14,8 +14,6 @@ DWORD fr_private = FR_PRIVATE; #endif -lua_State* globalL; - static void lua_run(int argc, char *argv[], const gchar *app_root, lua_State *L) { gchar *start_script; @@ -101,7 +99,6 @@ int main(int argc, char *argv[]) } gchar *app_root = get_app_root(argv[0]); lua_State *L = open_lua_state(app_root); - globalL = L; lua_run(argc, argv, app_root, L); lua_close(L); g_free(app_root); From 588dd69a5211f4693e054dc541427d059343878c Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 13:43:16 -0500 Subject: [PATCH 09/75] On Windows, printing a message will leave it in the buffer until the program exits --- lib/howl/log.moon | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/howl/log.moon b/lib/howl/log.moon index de59bb722..893034ebe 100644 --- a/lib/howl/log.moon +++ b/lib/howl/log.moon @@ -37,6 +37,7 @@ dispatch = (level, message) -> command_line\notify essentials, level if command_line.showing elseif not _G.howl.app.args.spec _G.print message + _G.io.flush! while #entries > config.max_log_entries and #entries > 0 table.remove entries, 1 From 0e91553ba80f99bd5789276869afe26b9aae823e Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 15:35:21 -0500 Subject: [PATCH 10/75] Tweak Windows default fonts --- lib/howl/ui/theme.moon | 2 +- spec/ui/theme_spec.moon | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/howl/ui/theme.moon b/lib/howl/ui/theme.moon index 37d8ce4c9..391158d87 100644 --- a/lib/howl/ui/theme.moon +++ b/lib/howl/ui/theme.moon @@ -180,7 +180,7 @@ with config .define name: 'font' description: 'The main font used within the application' - default: 'Liberation Mono, Monaco' + default: 'Liberation Mono, Monaco, Courier New' type_of: 'string' scope: 'global' diff --git a/spec/ui/theme_spec.moon b/spec/ui/theme_spec.moon index f93e0fe34..9df0044db 100644 --- a/spec/ui/theme_spec.moon +++ b/spec/ui/theme_spec.moon @@ -2,9 +2,14 @@ import config, signal from howl import theme from howl.ui import File from howl.io +ffi = require 'ffi' serpent = require 'serpent' -font = name: 'Liberation Mono', size: 11, bold: true +font_name = if ffi.os == 'Windows' + 'Courier New' +else + 'Liberation Mono' +font = name: font_name, size: 11, bold: true spec_theme = { window: From 4222bd98a5a9f827526fc257d959b2f43f3d26c7 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 16:03:37 -0500 Subject: [PATCH 11/75] Fix tmpfile on Windows --- lib/howl/cdefs/windows.moon | 4 +++- lib/howl/io/file.moon | 20 ++++++++++++++++++-- src/Makefile | 2 +- src/main.c | 1 + 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/howl/cdefs/windows.moon b/lib/howl/cdefs/windows.moon index 723e1ab18..63a65465c 100644 --- a/lib/howl/cdefs/windows.moon +++ b/lib/howl/cdefs/windows.moon @@ -8,12 +8,14 @@ ffi.cdef [[ typedef void* PVOID; typedef int DWORD; typedef int BOOL; + typedef char* LPTSTR; typedef const char* LPCTSTR; DWORD GetProcessId(HANDLE process); HANDLE _get_osfhandle(int fd); BOOL TerminateProcess(HANDLE process, unsigned int exitcode); int AddFontResourceExA(LPCTSTR lpszFilename, DWORD fl, PVOID pdv); + DWORD GetTempPathA(DWORD buflen, LPTSTR buf); - int fr_private; + int fr_private, max_path; ]] diff --git a/lib/howl/io/file.moon b/lib/howl/io/file.moon index 7c9fd6b7e..031b1b9d4 100644 --- a/lib/howl/io/file.moon +++ b/lib/howl/io/file.moon @@ -6,6 +6,7 @@ GFileInfo = require 'ljglibs.gio.file_info' glib = require 'ljglibs.glib' import PropertyObject from howl.util.moon append = table.insert +ffi = require 'ffi' file_types = { [tonumber GFileInfo.TYPE_DIRECTORY]: 'directory', @@ -16,15 +17,30 @@ file_types = { [tonumber GFileInfo.TYPE_UNKNOWN]: 'unknown', } +local File + +platform_tmpname = -> + -- os.tmpname is broken on Windows and returns a filename prefixed with \. + -- This causes a lot of "Access denied"-related errors. + filename = assert os.tmpname! + if ffi.os == 'Windows' + -- Remove the prefix \. + filename = filename\sub 2 + buflen = ffi.C.max_path+1 + buf = ffi.new 'char[?]', buflen + if ffi.C.GetTempPathA(buflen, buf) != 0 + filename = File(ffi.string(buf)) / filename + filename + class File extends PropertyObject tmpfile: -> - file = File assert os.tmpname! + file = File platform_tmpname! file.touch if not file.exists file tmpdir: -> - with File os.tmpname! + with File platform_tmpname! \delete! if .exists \mkdir! diff --git a/src/Makefile b/src/Makefile index 3ced541bb..8cfdf1088 100644 --- a/src/Makefile +++ b/src/Makefile @@ -44,7 +44,7 @@ else LD_FLAGS = -Wl,-E endif ifdef WIN32 - LIBS = -lm ${GTK_LIBS} -lstdc++ -lgdi32 + LIBS = -lm ${GTK_LIBS} -lstdc++ -lkernel32 -lgdi32 LD_FLAGS = -Wl,--export-all endif OBJECTS = main.o process_helpers.o diff --git a/src/main.c b/src/main.c index ee0e00946..6da9b4d93 100644 --- a/src/main.c +++ b/src/main.c @@ -12,6 +12,7 @@ #ifdef _WIN32 #include DWORD fr_private = FR_PRIVATE; +int max_path = MAX_PATH; #endif static void lua_run(int argc, char *argv[], const gchar *app_root, lua_State *L) From 5509fe7a3fc761be50201e516dee3ce445e42e4b Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 16:17:14 -0500 Subject: [PATCH 12/75] Fix a LOT of test failures related to howl.io.File and Windows --- spec/io/file_spec.moon | 65 ++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/spec/io/file_spec.moon b/spec/io/file_spec.moon index a2f0cb014..060fc35da 100644 --- a/spec/io/file_spec.moon +++ b/spec/io/file_spec.moon @@ -1,4 +1,10 @@ import File from howl.io +ffi_os = require('ffi').os + +pathsep = if ffi_os == 'Windows' + '\\' +else + '/' describe 'File', -> @@ -38,26 +44,26 @@ describe 'File', -> describe 'new(p, cwd)', -> it 'accepts a string as denothing a path', -> - File '/bin/ls' + File "#{pathsep}bin#{pathsep}ls" it 'accepts other files as well', -> - f = File '/bin/ls' + f = File "#{pathsep}bin#{pathsep}ls" f2 = File f assert.equal f, f2 context 'when is specified', -> it 'resolves a string

relative to ', -> - assert.equal '/bin/ls', File('ls', '/bin').path + assert.equal "#{pathsep}bin#{pathsep}ls", File('ls', '/bin').path it 'resolves an absolute string

as the absolute path', -> - assert.equal '/bin/ls', File('/bin/ls', '/home').path + assert.equal "#{pathsep}bin#{pathsep}ls", File("#{pathsep}bin#{pathsep}ls", '/home').path it 'accepts other Files as ', -> - assert.equal '/bin/ls', File('ls', File('/bin')).path + assert.equal "#{pathsep}bin#{pathsep}ls", File('ls', File('/bin')).path describe '.is_absolute', -> it 'returns true if the given path is absolute', -> - assert.is_true File.is_absolute '/bin/ls' + assert.is_true File.is_absolute "#{pathsep}bin#{pathsep}ls" assert.is_true File.is_absolute 'c:\\\\bin\\ls' it 'returns false if the given path is absolute', -> @@ -73,14 +79,14 @@ describe 'File', -> assert.equal 'base.ext', File('/foo/base.ext').display_name it 'has a trailing separator for directories', -> - assert.equal 'bin/', File('/usr/bin').display_name + assert.equal "bin#{pathsep}", File('/usr/bin').display_name it '.extension returns the extension of the path', -> assert.equal File('/foo/base.ext').extension, 'ext' assert.equal File('/foo/base.ex+').extension, 'ex+' it '.path returns the path of the file', -> - assert.equal '/foo/base.ext', File('/foo/base.ext').path + assert.equal "#{pathsep}foo#{pathsep}base.ext", File('/foo/base.ext').path it '.uri returns an URI representing the path', -> assert.equal File('/foo.txt').uri, 'file:///foo.txt' @@ -110,7 +116,7 @@ describe 'File', -> assert.equal file.contents, 'hello world' it '.parent return the parent of the file', -> - assert.equal File('/bin/ls').parent.path, '/bin' + assert.equal File('/bin/ls').parent.path, "#{pathsep}bin" it '.children returns a table of children', -> with_tmpdir (dir) -> @@ -121,9 +127,14 @@ describe 'File', -> assert.same [v.basename for v in *kids], { 'child1', 'child2' } it '.file_type is a string describing the file type', -> - assert.equal 'directory', File('/bin').file_type - assert.equal 'regular', File('/bin/ls').file_type - assert.equal 'special', File('/dev/null').file_type + if ffi_os == 'Windows' + sysroot = os.getenv 'SYSTEMROOT' + assert.equal 'directory', File(sysroot).file_type + assert.equal 'regular', File("#{sysroot}#{pathsep}explorer.exe").file_type + else + assert.equal 'directory', File('/bin').file_type + assert.equal 'regular', File('/bin/ls').file_type + assert.equal 'special', File('/dev/null').file_type it '.writeable is true if the file represents a entry that can be written to', -> with_tmpdir (dir) -> @@ -202,16 +213,16 @@ describe 'File', -> assert.same { 'first', ' line' }, { file\read 5, '*l' } it 'join() returns a new file representing the specified child', -> - assert.equal File('/bin')\join('ls').path, '/bin/ls' + assert.equal File('/bin')\join('ls').path, "#{pathsep}bin#{pathsep}ls" it 'relative_to_parent() returns a path relative to the specified parent', -> parent = File '/bin' - file = File '/bin/ls' + file = File "#{pathsep}bin#{pathsep}ls" assert.equal 'ls', file\relative_to_parent(parent) it 'is_below(dir) returns true if the file is located beneath

', -> parent = File '/bin' - assert.is_true File('/bin/ls')\is_below parent + assert.is_true File("#{pathsep}bin#{pathsep}ls")\is_below parent assert.is_true File('/bin/sub/ls')\is_below parent assert.is_false File('/usr/bin/ls')\is_below parent @@ -313,10 +324,10 @@ describe 'File', -> normalized = [f\relative_to_parent dir for f in *files] assert.same { 'child1', - 'child1/sandwich.lua', - 'child1/sub_child.txt', - 'child1/sub_dir', - 'child1/sub_dir/deep.lua', + "child1#{pathsep}sandwich.lua", + "child1#{pathsep}sub_child.txt", + "child1#{pathsep}sub_dir", + "child1#{pathsep}sub_dir#{pathsep}deep.lua", 'child2' }, normalized @@ -328,10 +339,10 @@ describe 'File', -> assert.same normalized, { 'child2', 'child1', - 'child1/sandwich.lua', - 'child1/sub_child.txt', - 'child1/sub_dir', - 'child1/sub_dir/deep.lua', + "child1#{pathsep}sandwich.lua", + "child1#{pathsep}sub_child.txt", + "child1#{pathsep}sub_dir", + "child1#{pathsep}sub_dir#{pathsep}deep.lua", } context 'when filter: is passed as an option', -> @@ -345,13 +356,13 @@ describe 'File', -> describe 'meta methods', -> it '/ and .. joins the file with the specified argument', -> file = File('/bin') - assert.equal (file / 'ls').path, '/bin/ls' - assert.equal (file .. 'ls').path, '/bin/ls' + assert.equal (file / 'ls').path, "#{pathsep}bin#{pathsep}ls" + assert.equal (file .. 'ls').path, "#{pathsep}bin#{pathsep}ls" it 'tostring returns the result of File.tostring', -> - file = File '/bin/ls' + file = File "#{pathsep}bin#{pathsep}ls" assert.equal file\tostring!, tostring file it '== returns true if the files point to the same path', -> - assert.equal File('/bin/ls'), File('/bin/ls') + assert.equal File("#{pathsep}bin#{pathsep}ls"), File("#{pathsep}bin#{pathsep}ls") From bce797f28a327e41898ffb950cfad2ccc88fbd6d Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 16:24:40 -0500 Subject: [PATCH 13/75] Fix a minor bug in File.etag --- lib/howl/io/file.moon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/howl/io/file.moon b/lib/howl/io/file.moon index 031b1b9d4..d5fb17c6c 100644 --- a/lib/howl/io/file.moon +++ b/lib/howl/io/file.moon @@ -95,7 +95,7 @@ class File extends PropertyObject @property size: get: => @_info!.size @property exists: get: => @gfile.exists @property readable: get: => @exists and @_info('access')\get_attribute_boolean 'access::can-read' - @property etag: get: => @exists and @_info('etag').etag + @property etag: get: => @exists and @_info('etag').etag or nil @property modified_at: get: => @exists and @_info('time')\get_attribute_uint64 'time::modified' @property short_path: get: => @path\gsub "^#{File.home_dir.path}", '~' From 306976f5b9dbb2a987be19a9445e54c0150bc452 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 16:35:04 -0500 Subject: [PATCH 14/75] More spec fixes --- spec/io/file_spec.moon | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spec/io/file_spec.moon b/spec/io/file_spec.moon index 060fc35da..cafef1e57 100644 --- a/spec/io/file_spec.moon +++ b/spec/io/file_spec.moon @@ -79,7 +79,14 @@ describe 'File', -> assert.equal 'base.ext', File('/foo/base.ext').display_name it 'has a trailing separator for directories', -> - assert.equal "bin#{pathsep}", File('/usr/bin').display_name + local dir, expected + if ffi_os == 'Windows' + dir = File os.getenv 'SYSTEMROOT' + expected = "#{dir.basename}\\" + else + dir = File '/usr/bin' + expected = 'bin/' + assert.equal expected, dir.display_name it '.extension returns the extension of the path', -> assert.equal File('/foo/base.ext').extension, 'ext' From bf1d7f3929610414e74fbb1b1368c54572b04864 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 16:38:24 -0500 Subject: [PATCH 15/75] Some cleanup --- lib/howl/io/process.moon | 4 ++-- lib/howl/ui/icons/font_awesome.moon | 4 +--- spec/io/file_spec.moon | 10 +++------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/howl/io/process.moon b/lib/howl/io/process.moon index a480f2702..3cda460d2 100644 --- a/lib/howl/io/process.moon +++ b/lib/howl/io/process.moon @@ -8,7 +8,7 @@ callbacks = require 'ljglibs.callbacks' dispatch = howl.dispatch {:File, :InputStream, :OutputStream} = howl.io -C, ffi_cast, ffi_os = ffi.C, ffi.cast, ffi.os +C, ffi_cast = ffi.C, ffi.cast append = table.insert child_watch_callback = ffi.cast 'GChildWatchFunc', callbacks.void3 @@ -134,7 +134,7 @@ class Process send_signal: (signal) => signal = signals[signal] if type(signal) == 'string' - if ffi_os == 'Windows' + if jit.os == 'Windows' error "Signal #{signal} is not supported on Windows" unless win_signals[signal] -- On Bash, when a process exits due to a signal, it's exit code is -- 128+{signal code}. Since killing a process like that doesn't diff --git a/lib/howl/ui/icons/font_awesome.moon b/lib/howl/ui/icons/font_awesome.moon index 318e94546..459725a3f 100644 --- a/lib/howl/ui/icons/font_awesome.moon +++ b/lib/howl/ui/icons/font_awesome.moon @@ -3,8 +3,6 @@ -- Font Awesome version 4.4.0 -ffi = require 'ffi' - icon_text = { '500px': '\239\137\174', 'adjust': '\239\129\130', @@ -683,7 +681,7 @@ icon_text = { } -- On Windows, when the font is loaded, it gets labeled as "FontAwesome". -family = if ffi.os == 'Windows' +family = if jit.os == 'Windows' 'FontAwesome' else 'Font Awesome' diff --git a/spec/io/file_spec.moon b/spec/io/file_spec.moon index cafef1e57..4e928bce2 100644 --- a/spec/io/file_spec.moon +++ b/spec/io/file_spec.moon @@ -1,10 +1,6 @@ import File from howl.io -ffi_os = require('ffi').os -pathsep = if ffi_os == 'Windows' - '\\' -else - '/' +pathsep = File.separator describe 'File', -> @@ -80,7 +76,7 @@ describe 'File', -> it 'has a trailing separator for directories', -> local dir, expected - if ffi_os == 'Windows' + if jit.os == 'Windows' dir = File os.getenv 'SYSTEMROOT' expected = "#{dir.basename}\\" else @@ -134,7 +130,7 @@ describe 'File', -> assert.same [v.basename for v in *kids], { 'child1', 'child2' } it '.file_type is a string describing the file type', -> - if ffi_os == 'Windows' + if jit.os == 'Windows' sysroot = os.getenv 'SYSTEMROOT' assert.equal 'directory', File(sysroot).file_type assert.equal 'regular', File("#{sysroot}#{pathsep}explorer.exe").file_type From 87f82a5ef177ea94ee6941558526825b56aedb84 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 16:47:18 -0500 Subject: [PATCH 16/75] I AM AN IDIOT. --- lib/howl/io/file.moon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/howl/io/file.moon b/lib/howl/io/file.moon index d5fb17c6c..5b5f63a86 100644 --- a/lib/howl/io/file.moon +++ b/lib/howl/io/file.moon @@ -36,7 +36,7 @@ class File extends PropertyObject tmpfile: -> file = File platform_tmpname! - file.touch if not file.exists + file\touch! if not file.exists file tmpdir: -> From 8bd1c80748cdc870310b7c9431fd1ad27ffaba05 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 16:48:19 -0500 Subject: [PATCH 17/75] Typo fix --- spec/io/file_spec.moon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/io/file_spec.moon b/spec/io/file_spec.moon index 4e928bce2..aa64f931b 100644 --- a/spec/io/file_spec.moon +++ b/spec/io/file_spec.moon @@ -62,7 +62,7 @@ describe 'File', -> assert.is_true File.is_absolute "#{pathsep}bin#{pathsep}ls" assert.is_true File.is_absolute 'c:\\\\bin\\ls' - it 'returns false if the given path is absolute', -> + it 'returns false if the given path is not absolute', -> assert.is_false File.is_absolute 'bin/ls' assert.is_false File.is_absolute 'bin\\ls' From 8ba523cc969122bbf539442b6bb956336edc1759 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 16:49:08 -0500 Subject: [PATCH 18/75] Test fixes --- spec/io/file_spec.moon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/io/file_spec.moon b/spec/io/file_spec.moon index aa64f931b..e1253b1ff 100644 --- a/spec/io/file_spec.moon +++ b/spec/io/file_spec.moon @@ -100,7 +100,7 @@ describe 'File', -> describe '.short_path', -> it 'returns the path with the home directory replace by "~"', -> file = File(os.getenv('HOME')) / 'foo.txt' - assert.equal '~/foo.txt', file.short_path + assert.equal "~#{pathsep}foo.txt", file.short_path describe 'contents', -> it 'assigning a string writes the string to the file', -> From 9b65e038409ffaaa455de899e6739610b00201b1 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 16:53:06 -0500 Subject: [PATCH 19/75] More test fixes --- spec/io/file_spec.moon | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/io/file_spec.moon b/spec/io/file_spec.moon index e1253b1ff..78a5e8295 100644 --- a/spec/io/file_spec.moon +++ b/spec/io/file_spec.moon @@ -36,7 +36,8 @@ describe 'File', -> describe 'expand_path(path)', -> it 'expands "~" into the full path of the home directory', -> - assert.equals "#{os.getenv('HOME')}/foo.txt", (File.expand_path '~/foo.txt') + assert.equals "#{os.getenv('HOME')}#{pathsep}foo.txt", + File.expand_path("~#{pathsep}foo.txt") describe 'new(p, cwd)', -> it 'accepts a string as denothing a path', -> @@ -59,7 +60,7 @@ describe 'File', -> describe '.is_absolute', -> it 'returns true if the given path is absolute', -> - assert.is_true File.is_absolute "#{pathsep}bin#{pathsep}ls" + assert.is_true File.is_absolute '/bin/ls' assert.is_true File.is_absolute 'c:\\\\bin\\ls' it 'returns false if the given path is not absolute', -> From 05a84ab498f4ea7b350db1be0532d2ca8cebba74 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 17:09:04 -0500 Subject: [PATCH 20/75] Fix several path_spec test failures --- spec/util/paths_spec.moon | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/spec/util/paths_spec.moon b/spec/util/paths_spec.moon index b57206698..dfe3fd614 100644 --- a/spec/util/paths_spec.moon +++ b/spec/util/paths_spec.moon @@ -1,6 +1,8 @@ import File from howl.io import paths from howl.util +pathsep = File.separator + describe 'paths', -> local tmpdir @@ -22,14 +24,15 @@ describe 'paths', -> it 'returns the matched path and unmatched parts of a path', -> assert.same {tmpdir, 'unmatched'}, {paths.get_dir_and_leftover tostring(tmpdir / 'unmatched')} - it 'when given a directory path ending in "/", matches the given directory', -> - assert.same {tmpdir / 'subdir', ''}, {paths.get_dir_and_leftover tostring(tmpdir)..'/subdir/'} + it 'when given a directory path ending in the path separator, matches the given directory', -> + assert.same {tmpdir / 'subdir', ''}, {paths.get_dir_and_leftover tostring(tmpdir).."/subdir/"} it 'when given a directory path not ending in "/", matches the parent directory', -> assert.same {tmpdir, 'subdir'}, {paths.get_dir_and_leftover tostring(tmpdir)..'/subdir'} it 'unmatched part can contain slashes', -> - assert.same {tmpdir, 'unmatched/no/such/file'}, {paths.get_dir_and_leftover tostring(tmpdir / 'unmatched/no/such/file')} + assert.same {tmpdir, "unmatched#{pathsep}no#{pathsep}such#{pathsep}file"}, + {paths.get_dir_and_leftover tostring(tmpdir / 'unmatched/no/such/file')} context 'is given a non absolute path', -> it 'uses the home dir as the base path', -> @@ -43,4 +46,8 @@ describe 'paths', -> (tmpdir / file).contents = 'a' files = paths.subtree_reader tmpdir - assert.same {'a', 'a/x', 'b', 'b/y', 'b/c', 'b/c/z'}, [file\relative_to_parent(tmpdir) for file in *files] + expected = { + 'a', "a#{pathsep}x", 'b', "b#{pathsep}y", "b#{pathsep}c", + "b#{pathsep}c#{pathsep}z" + } + assert.same expected, [file\relative_to_parent(tmpdir) for file in *files] From 0f09ac62c4c157b9dfd9ddb30b65ae66284fee93 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 17:37:29 -0500 Subject: [PATCH 21/75] Fix a bug in File.is_absolute and more paths specs --- lib/howl/io/file.moon | 2 +- spec/util/paths_spec.moon | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/howl/io/file.moon b/lib/howl/io/file.moon index 5b5f63a86..805d27b86 100644 --- a/lib/howl/io/file.moon +++ b/lib/howl/io/file.moon @@ -51,7 +51,7 @@ class File extends PropertyObject error err if not status is_absolute: (path) -> - (path\match('^/') or path\match('^%a:\\\\')) != nil + (path\match('^/') or path\match('^%a:\\')) != nil expand_path: (path) -> res = path\gsub "~#{File.separator}", File.home_dir.path .. File.separator diff --git a/spec/util/paths_spec.moon b/spec/util/paths_spec.moon index dfe3fd614..7b5860aa1 100644 --- a/spec/util/paths_spec.moon +++ b/spec/util/paths_spec.moon @@ -19,16 +19,17 @@ describe 'paths', -> assert.same {File.home_dir, ''}, {paths.get_dir_and_leftover ''} it 'returns the root directory for "/"', -> - assert.same {File.home_dir.root_dir, ''}, {paths.get_dir_and_leftover '/'} + assert.same {File.home_dir.root_dir, ''}, + {paths.get_dir_and_leftover File.home_dir.root_dir.path} it 'returns the matched path and unmatched parts of a path', -> assert.same {tmpdir, 'unmatched'}, {paths.get_dir_and_leftover tostring(tmpdir / 'unmatched')} it 'when given a directory path ending in the path separator, matches the given directory', -> - assert.same {tmpdir / 'subdir', ''}, {paths.get_dir_and_leftover tostring(tmpdir).."/subdir/"} + assert.same {tmpdir / 'subdir', ''}, {paths.get_dir_and_leftover tostring(tmpdir).."#{pathsep}subdir#{pathsep}"} it 'when given a directory path not ending in "/", matches the parent directory', -> - assert.same {tmpdir, 'subdir'}, {paths.get_dir_and_leftover tostring(tmpdir)..'/subdir'} + assert.same {tmpdir, 'subdir'}, {paths.get_dir_and_leftover tostring(tmpdir).."#{pathsep}subdir"} it 'unmatched part can contain slashes', -> assert.same {tmpdir, "unmatched#{pathsep}no#{pathsep}such#{pathsep}file"}, From 20d2c4b3c34c74091f14aeab32e6c9267fbdad33 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 17:58:19 -0500 Subject: [PATCH 22/75] Fix a bug in OutputStream under Windows --- lib/howl/io/output_stream.moon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/howl/io/output_stream.moon b/lib/howl/io/output_stream.moon index c9bbb817c..fe8a89e9a 100644 --- a/lib/howl/io/output_stream.moon +++ b/lib/howl/io/output_stream.moon @@ -9,7 +9,7 @@ ffi = require 'ffi' class OutputStream extends PropertyObject new: (fd) => if ffi.os == 'Windows' - @stream = Win32OutputStream ffi.C._get_osfhandle @stream + @stream = Win32OutputStream ffi.C._get_osfhandle fd else @stream = UnixOutputStream @stream super! From d698bb6f714005c7b29efdaa33d7f29f32f1ca3a Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 18:05:48 -0500 Subject: [PATCH 23/75] Some fixes for process signals --- src/process_helpers.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/process_helpers.c b/src/process_helpers.c index 32b2d0b9b..95e8954c6 100644 --- a/src/process_helpers.c +++ b/src/process_helpers.c @@ -5,8 +5,10 @@ #ifndef _WIN32 #include #else -#define WIFEXITED(w) ((w) != 3) +#define WIFEXITED(w) ((w) < 128) #define WEXITSTATUS(w) (w) +#define WIFSIGNALED(w) ((w) >= 128) +#define WTERMSIG(w) ((w) - 128) #define SIGHUP 1 /* hangup */ #define SIGINT 2 /* interrupt */ @@ -48,6 +50,8 @@ int process_exited_normally(int status) { return WIFEXITED(status); } int process_exit_status(int status) { return WEXITSTATUS(status); } +int process_was_signaled(int status) { return WIFSIGNALED(status); } +int process_get_term_sig(int status) { return WTERMSIG(status); } int sig_HUP = SIGHUP; int sig_INT = SIGINT; From 376aa904f191392a065fc9d8ecff3966d44226ed Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 18:33:26 -0500 Subject: [PATCH 24/75] Process fixes --- lib/howl/cdefs/init.moon | 2 ++ src/process_helpers.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/howl/cdefs/init.moon b/lib/howl/cdefs/init.moon index 44dcf100a..98ca6aa84 100644 --- a/lib/howl/cdefs/init.moon +++ b/lib/howl/cdefs/init.moon @@ -15,6 +15,8 @@ int kill(pid_t pid, int sig); /* process helpers */ int process_exited_normally(int status); int process_exit_status(int status); +int process_was_signalled(int status); +int process_get_term_sig(int status); int sig_HUP; int sig_INT; diff --git a/src/process_helpers.c b/src/process_helpers.c index 95e8954c6..892734081 100644 --- a/src/process_helpers.c +++ b/src/process_helpers.c @@ -50,7 +50,7 @@ int process_exited_normally(int status) { return WIFEXITED(status); } int process_exit_status(int status) { return WEXITSTATUS(status); } -int process_was_signaled(int status) { return WIFSIGNALED(status); } +int process_was_signalled(int status) { return WIFSIGNALED(status); } int process_get_term_sig(int status) { return WTERMSIG(status); } int sig_HUP = SIGHUP; From 0864b601c4f2c98275cc5b335351cadeb5ad9bea Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 24 Aug 2016 18:33:50 -0500 Subject: [PATCH 25/75] Fix the sandboxed loader specs --- lib/howl/util/sandboxed_loader.moon | 2 +- spec/util/sandboxed_loader_spec.moon | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/howl/util/sandboxed_loader.moon b/lib/howl/util/sandboxed_loader.moon index 92338a495..99479aa02 100644 --- a/lib/howl/util/sandboxed_loader.moon +++ b/lib/howl/util/sandboxed_loader.moon @@ -24,7 +24,7 @@ new = (dir, name, sandbox_options = {}) -> "#{name}_file": (rel_path) -> dir / rel_path "#{name}_load": (rel_path, ...) -> - rel_path = rel_path\gsub '%.', File.separator + rel_path = rel_path\gsub '[./]', File.separator error 'Cyclic dependency in ' .. dir / rel_path if loading[rel_path] return loaded[rel_path] if loaded[rel_path] loading[rel_path] = true diff --git a/spec/util/sandboxed_loader_spec.moon b/spec/util/sandboxed_loader_spec.moon index beb005e96..ab1643219 100644 --- a/spec/util/sandboxed_loader_spec.moon +++ b/spec/util/sandboxed_loader_spec.moon @@ -20,13 +20,13 @@ describe 'SandboxedLoader', -> describe '_load(rel_basename)', -> it 'loads relative bytecode, lua and moonscript files', -> - dir\join('aux_lua.lua').contents = '_G.loaded_lua = true' - dir\join('aux_moon.moon').contents = '_G.loaded_moon = true' - dir\join('aux_bc.bc').contents = string.dump loadstring('_G.loaded_bc = true'), false + dir\join('util_lua.lua').contents = '_G.loaded_lua = true' + dir\join('util_moon.moon').contents = '_G.loaded_moon = true' + dir\join('util_bc.bc').contents = string.dump loadstring('_G.loaded_bc = true'), false loader -> - foo_load 'aux_lua' - foo_load 'aux_moon' - foo_load 'aux_bc' + foo_load 'util_lua' + foo_load 'util_moon' + foo_load 'util_bc' assert.is_true _G.loaded_lua assert.is_true _G.loaded_moon @@ -43,13 +43,13 @@ describe 'SandboxedLoader', -> assert.equal 'lua', loader -> foo_load 'two' it 'only loads each file once', -> - dir\join('aux.lua').contents = [[ + dir\join('util.lua').contents = [[ _G.load_count = _G.load_count or 0 _G.load_count = _G.load_count + 1 return _G.load_count ]] - assert.equals 1, loader -> foo_load 'aux' - assert.equals 1, loader -> foo_load 'aux' + assert.equals 1, loader -> foo_load 'util' + assert.equals 1, loader -> foo_load 'util' context '(loading files from sub directories)', -> it 'supports both slashes and dots in the path', -> @@ -74,10 +74,10 @@ describe 'SandboxedLoader', -> assert.equals 1, loader -> foo_load 'down.sub' it 'signals an error upon cyclic dependencies', -> - dir\join('aux.lua').contents = 'foo_load("aux2")' - dir\join('aux2.lua').contents = 'foo_load("aux")' - assert.raises 'Cyclic dependency', -> loader -> foo_load 'aux' + dir\join('util.lua').contents = 'foo_load("util2")' + dir\join('util2.lua').contents = 'foo_load("util")' + assert.raises 'Cyclic dependency', -> loader -> foo_load 'util' it 'allows passing parameters to the loaded file', -> - dir\join('aux.lua').contents = 'return ...' - assert.equal 123, loader -> foo_load 'aux', 123 + dir\join('util.lua').contents = 'return ...' + assert.equal 123, loader -> foo_load 'util', 123 From 5a68550520c82de40604b0832bd6dcb071618ff0 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 25 Aug 2016 10:51:59 -0500 Subject: [PATCH 26/75] Fix project_spec under Windows --- spec/project_spec.moon | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/spec/project_spec.moon b/spec/project_spec.moon index 51356eddb..ccbed3828 100644 --- a/spec/project_spec.moon +++ b/spec/project_spec.moon @@ -1,5 +1,10 @@ import Project, VC from howl import File from howl.io +ffi = require 'ffi' + +if ffi.os == 'Windows' + require 'howl.cdefs.windows' + ffi.cdef [[ BOOL SetFileAttributesA(LPCTSTR file, DWORD attrs); ]] describe 'Project', -> before_each -> @@ -100,4 +105,12 @@ describe 'Project', -> hidden\touch! backup = dir / 'config~' backup\touch! - assert.same { regular.path }, [f.path for f in *Project(dir)\files!] + + expected = { regular.path } + if ffi.os == 'Windows' + FILE_ATTRIBUTE_HIDDEN = 2 + ffi.C.SetFileAttributesA(hidden.path, FILE_ATTRIBUTE_HIDDEN) + -- glib on Windows has no notion of a "backup file" + table.insert expected, 1, backup.path + + assert.same expected, [f.path for f in *Project(dir)\files!] From 7f59fb2207b75a2789c41e3c43e778eec93570d5 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 25 Aug 2016 10:59:48 -0500 Subject: [PATCH 27/75] Fix buffer_spec on Windows --- spec/buffer_spec.moon | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/buffer_spec.moon b/spec/buffer_spec.moon index e5edbb8ec..57146e2c3 100644 --- a/spec/buffer_spec.moon +++ b/spec/buffer_spec.moon @@ -1,6 +1,8 @@ import Buffer, config, signal from howl import File from howl.io import with_tmpfile from File +ffi = require 'ffi' +ffi.cdef [[ void g_usleep(unsigned long ms); ]] describe 'Buffer', -> buffer = (text) -> @@ -44,11 +46,13 @@ describe 'Buffer', -> it 'is updated whenever the buffer is changed in some way', -> b = buffer 'time' cur = b.last_changed + ffi.C.g_usleep 2000 b\insert 'foo', 1 assert.is_true b.last_changed > cur cur = b.last_changed + ffi.C.g_usleep 2000 b\delete 1, 3 assert.is_true b.last_changed > cur From b7b06385dbfc8e11a613de3d48fdba8d41e6c0f8 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 25 Aug 2016 11:20:25 -0500 Subject: [PATCH 28/75] Fixes to file selector interactions and specs --- lib/howl/interactions/file_selection.moon | 8 ++++---- spec/interactions/file_selection_spec.moon | 13 +++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/howl/interactions/file_selection.moon b/lib/howl/interactions/file_selection.moon index fb5dbf359..44d9a8c07 100644 --- a/lib/howl/interactions/file_selection.moon +++ b/lib/howl/interactions/file_selection.moon @@ -47,11 +47,11 @@ class FileSelector path = @command_line\pop_spillover! if path.is_empty - path = tostring(parent) .. '/' + path = tostring(parent) .. File.separator else - trailing = path\ends_with('/') and '/' or '' + trailing = path\ends_with(File.separator) and File.separator or '' path = tostring parent / path - if not path\ends_with '/' + if not path\ends_with File.separator path ..= trailing directory, unmatched = get_dir_and_leftover path @@ -93,7 +93,7 @@ class FileSelector on_update: (text) => return if @submitting - path = @directory.path .. '/' .. text + path = @directory.path .. File.separator .. text directory, text = get_dir_and_leftover path if directory != @directory diff --git a/spec/interactions/file_selection_spec.moon b/spec/interactions/file_selection_spec.moon index b109b63de..b715e5141 100644 --- a/spec/interactions/file_selection_spec.moon +++ b/spec/interactions/file_selection_spec.moon @@ -6,6 +6,7 @@ import File from howl.io import Window from howl.ui require 'howl.ui.icons.font_awesome' require 'howl.interactions.file_selection' +pathsep = File.separator describe 'file_selection', -> local tmpdir, command_line @@ -38,7 +39,7 @@ describe 'file_selection', -> local prompt within_activity interact.select_file, -> prompt = command_line.prompt - assert.same '~/', prompt + assert.same "~#{pathsep}", prompt context 'when a buffer associated with a file is open', -> local buf @@ -48,7 +49,7 @@ describe 'file_selection', -> local prompt within_activity interact.select_file, -> prompt = command_line.prompt - assert.same tostring(tmpdir)..'/', prompt + assert.same tostring(tmpdir)..pathsep, prompt it 'typing a path opens the closest parent', -> prompts = {} @@ -105,7 +106,7 @@ describe 'file_selection', -> within_activity interact.select_file, -> prompt = command_line.prompt text = command_line.text - assert.same '~/', prompt + assert.same "~#{pathsep}", prompt assert.same 'matchthis', text context 'when spillover is an absolute path', -> @@ -115,7 +116,7 @@ describe 'file_selection', -> within_activity interact.select_file, -> prompt = command_line.prompt text = command_line.text - assert.same tostring(tmpdir)..'/', prompt + assert.same tostring(tmpdir)..pathsep, prompt assert.same 'matchthis', text context 'when spillover is a directory path that exists', -> @@ -128,7 +129,7 @@ describe 'file_selection', -> within_activity interact.select_file, -> prompt = command_line.prompt text = command_line.text - assert.same tostring(tmpdir / 'subdir')..'/', prompt + assert.same tostring(tmpdir / 'subdir')..pathsep, prompt assert.same '', text it 'opens the parent when specified without any trailing "/"', -> @@ -137,7 +138,7 @@ describe 'file_selection', -> within_activity interact.select_file, -> prompt = command_line.prompt text = command_line.text - assert.same tostring(tmpdir)..'/', prompt + assert.same tostring(tmpdir)..pathsep, prompt assert.same 'subdir', text context 'when config.hidden_file_extensions is set', -> From 7f12ca168a7dee5a3208e8b9dbb3551518427a4d Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 25 Aug 2016 11:27:10 -0500 Subject: [PATCH 29/75] Make file opening a bit more natural under Windows --- lib/howl/interactions/file_selection.moon | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/howl/interactions/file_selection.moon b/lib/howl/interactions/file_selection.moon index 44d9a8c07..c5698b1f8 100644 --- a/lib/howl/interactions/file_selection.moon +++ b/lib/howl/interactions/file_selection.moon @@ -93,6 +93,8 @@ class FileSelector on_update: (text) => return if @submitting + -- This allows one to use / as a directory separator, even on Windows. + text = text\gsub '/', File.separator path = @directory.path .. File.separator .. text directory, text = get_dir_and_leftover path From 443639bfe414dd36bd9fffff4fe30405fd6c667f Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 25 Aug 2016 12:09:29 -0500 Subject: [PATCH 30/75] More file selection on Windows --- lib/howl/interactions/file_selection.moon | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/howl/interactions/file_selection.moon b/lib/howl/interactions/file_selection.moon index c5698b1f8..5bf5088ce 100644 --- a/lib/howl/interactions/file_selection.moon +++ b/lib/howl/interactions/file_selection.moon @@ -45,6 +45,8 @@ class FileSelector parent or= File.home_dir path = @command_line\pop_spillover! + -- Make / work on Windows. + path = path\gsub '/', File.separator if path.is_empty path = tostring(parent) .. File.separator From f4fcd0ab2fd0f8de8bc0d232c88e7865868ee05f Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 25 Aug 2016 12:13:14 -0500 Subject: [PATCH 31/75] Windows specs --- spec/interactions/file_selection_spec.moon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/interactions/file_selection_spec.moon b/spec/interactions/file_selection_spec.moon index b715e5141..b83de2908 100644 --- a/spec/interactions/file_selection_spec.moon +++ b/spec/interactions/file_selection_spec.moon @@ -79,7 +79,7 @@ describe 'file_selection', -> within_activity interact.select_file, -> command_line\write '/' prompt = command_line.prompt - assert.same '/', prompt + assert.same pathsep, prompt it 'shows files matching entered text in the current directory', -> files = { 'ab1', 'ab2', 'bc1' } From 017566de61fe31c351ce86d93f6118a86b2734f5 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 25 Aug 2016 12:18:47 -0500 Subject: [PATCH 32/75] Simply tempfile code The file_selection specs expect the tempfile to be a subpath of the current directory. --- lib/howl/cdefs/windows.moon | 3 +-- lib/howl/io/file.moon | 9 ++------- src/main.c | 1 - 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/howl/cdefs/windows.moon b/lib/howl/cdefs/windows.moon index 63a65465c..91238b566 100644 --- a/lib/howl/cdefs/windows.moon +++ b/lib/howl/cdefs/windows.moon @@ -15,7 +15,6 @@ ffi.cdef [[ HANDLE _get_osfhandle(int fd); BOOL TerminateProcess(HANDLE process, unsigned int exitcode); int AddFontResourceExA(LPCTSTR lpszFilename, DWORD fl, PVOID pdv); - DWORD GetTempPathA(DWORD buflen, LPTSTR buf); - int fr_private, max_path; + int fr_private; ]] diff --git a/lib/howl/io/file.moon b/lib/howl/io/file.moon index 805d27b86..2e520f317 100644 --- a/lib/howl/io/file.moon +++ b/lib/howl/io/file.moon @@ -23,13 +23,8 @@ platform_tmpname = -> -- os.tmpname is broken on Windows and returns a filename prefixed with \. -- This causes a lot of "Access denied"-related errors. filename = assert os.tmpname! - if ffi.os == 'Windows' - -- Remove the prefix \. - filename = filename\sub 2 - buflen = ffi.C.max_path+1 - buf = ffi.new 'char[?]', buflen - if ffi.C.GetTempPathA(buflen, buf) != 0 - filename = File(ffi.string(buf)) / filename + -- Remove the prefix \. + filename = filename\sub 2 if ffi.os == 'Windows' filename class File extends PropertyObject diff --git a/src/main.c b/src/main.c index 6da9b4d93..ee0e00946 100644 --- a/src/main.c +++ b/src/main.c @@ -12,7 +12,6 @@ #ifdef _WIN32 #include DWORD fr_private = FR_PRIVATE; -int max_path = MAX_PATH; #endif static void lua_run(int argc, char *argv[], const gchar *app_root, lua_State *L) From b8d269baaf73452a2377df39cd02ed1c16853742 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 25 Aug 2016 12:47:37 -0500 Subject: [PATCH 33/75] More file selection fixes --- spec/interactions/file_selection_spec.moon | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/spec/interactions/file_selection_spec.moon b/spec/interactions/file_selection_spec.moon index b83de2908..88120ca78 100644 --- a/spec/interactions/file_selection_spec.moon +++ b/spec/interactions/file_selection_spec.moon @@ -49,7 +49,7 @@ describe 'file_selection', -> local prompt within_activity interact.select_file, -> prompt = command_line.prompt - assert.same tostring(tmpdir)..pathsep, prompt + assert.same tostring(tmpdir)..pathsep, File.expand_path prompt it 'typing a path opens the closest parent', -> prompts = {} @@ -116,7 +116,7 @@ describe 'file_selection', -> within_activity interact.select_file, -> prompt = command_line.prompt text = command_line.text - assert.same tostring(tmpdir)..pathsep, prompt + assert.same tostring(tmpdir)..pathsep, File.expand_path prompt assert.same 'matchthis', text context 'when spillover is a directory path that exists', -> @@ -129,7 +129,7 @@ describe 'file_selection', -> within_activity interact.select_file, -> prompt = command_line.prompt text = command_line.text - assert.same tostring(tmpdir / 'subdir')..pathsep, prompt + assert.same tostring(tmpdir / 'subdir')..pathsep, File.expand_path prompt assert.same '', text it 'opens the parent when specified without any trailing "/"', -> @@ -138,7 +138,7 @@ describe 'file_selection', -> within_activity interact.select_file, -> prompt = command_line.prompt text = command_line.text - assert.same tostring(tmpdir)..pathsep, prompt + assert.same tostring(tmpdir)..pathsep, File.expand_path prompt assert.same 'subdir', text context 'when config.hidden_file_extensions is set', -> @@ -171,10 +171,11 @@ describe 'file_selection', -> context 'in subtree mode', -> it 'shows files and directories in the subtree', -> - files = { 'ab1', 'ab2/', 'ab2/xy', 'ef/', 'ef/gh/', 'ef/gh/ab4'} + files = { 'ab1', "ab2#{pathsep}", "ab2#{pathsep}xy", "ef#{pathsep}", + "ef#{pathsep}gh#{pathsep}", "ef#{pathsep}gh#{pathsep}ab4" } for name in *files f = tmpdir / name - if name\ends_with '/' + if name\ends_with pathsep f\mkdir! else f.contents = 'a' @@ -188,7 +189,9 @@ describe 'file_selection', -> items2 = get_ui_list_widget_column(2) assert.same files, items - assert.same {'ab1', 'ab2/', 'ab2/xy', 'ef/gh/ab4'}, items2 + expected = {'ab1', "ab2#{pathsep}", "ab2#{pathsep}xy", + "ef#{pathsep}gh#{pathsep}ab4"} + assert.same expected, items2 describe 'interact.select_directory', -> From 7eb58c361a025d3a34b4d92f9f67b4dc7cfb167a Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 25 Aug 2016 14:08:44 -0500 Subject: [PATCH 34/75] Finish fixing the file_selection specs --- lib/howl/interactions/file_selection.moon | 5 ++++- spec/interactions/file_selection_spec.moon | 16 ++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/howl/interactions/file_selection.moon b/lib/howl/interactions/file_selection.moon index 5bf5088ce..2e3f5996a 100644 --- a/lib/howl/interactions/file_selection.moon +++ b/lib/howl/interactions/file_selection.moon @@ -97,7 +97,10 @@ class FileSelector -- This allows one to use / as a directory separator, even on Windows. text = text\gsub '/', File.separator - path = @directory.path .. File.separator .. text + path = if File.is_absolute text + text + else + @directory.path .. File.separator .. text directory, text = get_dir_and_leftover path if directory != @directory diff --git a/spec/interactions/file_selection_spec.moon b/spec/interactions/file_selection_spec.moon index 88120ca78..6f56dc8a1 100644 --- a/spec/interactions/file_selection_spec.moon +++ b/spec/interactions/file_selection_spec.moon @@ -55,24 +55,24 @@ describe 'file_selection', -> prompts = {} within_activity interact.select_file, -> command_line\write tostring(tmpdir) - table.insert prompts, command_line.prompt - assert.same {tostring(tmpdir.parent) .. '/'}, prompts + table.insert prompts, File.expand_path command_line.prompt + assert.same {tostring(tmpdir.parent) .. pathsep}, prompts it 'typing "/" after a directory name opens the directory', -> local prompt within_activity interact.select_file, -> command_line\write tostring(tmpdir) .. '/' prompt = command_line.prompt - assert.same tostring(tmpdir) .. '/', prompt + assert.same tostring(tmpdir) .. pathsep, File.expand_path prompt it 'typing "../" switches to the parent of the current directory', -> prompts = {} within_activity interact.select_file, -> command_line\write tostring(tmpdir) .. '/' - table.insert prompts, command_line.prompt - command_line\write tostring(tmpdir) .. '../' - table.insert prompts, command_line.prompt - assert.same {tostring(tmpdir) .. '/', tostring(tmpdir.parent) .. '/'}, prompts + table.insert prompts, File.expand_path command_line.prompt + command_line\write tostring(tmpdir) .. '/../' + table.insert prompts, File.expand_path command_line.prompt + assert.same {tostring(tmpdir) .. pathsep, tostring(tmpdir.parent) .. pathsep}, prompts it 'typing "/" without any preceeding text changes to home directory', -> local prompt @@ -211,4 +211,4 @@ describe 'file_selection', -> command_line\write tostring(tmpdir) .. '/' items = get_ui_list_widget_column(2) - assert.same { './', 'dir1/', 'dir2/' }, items + assert.same { ".#{pathsep}", "dir1#{pathsep}", "dir2#{pathsep}" }, items From 173ad8c7145424dc63cb6266c20cae9571e73012 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 25 Aug 2016 14:19:04 -0500 Subject: [PATCH 35/75] Hidden file reorg & make bundle_spec pass on Windows --- lib/howl/cdefs/windows.moon | 1 + spec/bundle_spec.moon | 1 + spec/interactions/buffer_selection_spec.moon | 4 +++- spec/project_spec.moon | 7 +------ spec/support/spec_helper.moon | 4 ++++ 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/howl/cdefs/windows.moon b/lib/howl/cdefs/windows.moon index 91238b566..76da6222d 100644 --- a/lib/howl/cdefs/windows.moon +++ b/lib/howl/cdefs/windows.moon @@ -15,6 +15,7 @@ ffi.cdef [[ HANDLE _get_osfhandle(int fd); BOOL TerminateProcess(HANDLE process, unsigned int exitcode); int AddFontResourceExA(LPCTSTR lpszFilename, DWORD fl, PVOID pdv); + BOOL SetFileAttributesA(LPCTSTR file, DWORD attrs); int fr_private; ]] diff --git a/spec/bundle_spec.moon b/spec/bundle_spec.moon index 68d65db2a..0d84964fa 100644 --- a/spec/bundle_spec.moon +++ b/spec/bundle_spec.moon @@ -110,6 +110,7 @@ describe 'bundle', -> bundle.dirs = {dir} b_dir = dir / '.hidden' b_dir\mkdir! + make_hidden b_dir.path b_dir\join('init.lua').contents = bundle_init name: 'hidden' bundle.load_all! diff --git a/spec/interactions/buffer_selection_spec.moon b/spec/interactions/buffer_selection_spec.moon index 2641f4fcd..42bcde32d 100644 --- a/spec/interactions/buffer_selection_spec.moon +++ b/spec/interactions/buffer_selection_spec.moon @@ -114,4 +114,6 @@ describe 'buffer_selection', -> local buflist within_activity (-> interact.select_buffer :editor), -> buflist = get_ui_list_widget_column 2 - assert.same {'file1 [project1]', 'file1 [project2]', 'path1/file2', 'path2/file2'}, buflist + assert.same { 'file1 [project1]', 'file1 [project2]', + "path1#{File.separator}file2", + "path2#{File.separator}file2" }, buflist diff --git a/spec/project_spec.moon b/spec/project_spec.moon index ccbed3828..c220a92ab 100644 --- a/spec/project_spec.moon +++ b/spec/project_spec.moon @@ -2,10 +2,6 @@ import Project, VC from howl import File from howl.io ffi = require 'ffi' -if ffi.os == 'Windows' - require 'howl.cdefs.windows' - ffi.cdef [[ BOOL SetFileAttributesA(LPCTSTR file, DWORD attrs); ]] - describe 'Project', -> before_each -> Project.roots = {} @@ -108,8 +104,7 @@ describe 'Project', -> expected = { regular.path } if ffi.os == 'Windows' - FILE_ATTRIBUTE_HIDDEN = 2 - ffi.C.SetFileAttributesA(hidden.path, FILE_ATTRIBUTE_HIDDEN) + make_hidden hidden.path -- glib on Windows has no notion of a "backup file" table.insert expected, 1, backup.path diff --git a/spec/support/spec_helper.moon b/spec/support/spec_helper.moon index f0933ed2e..a7d3cf98c 100644 --- a/spec/support/spec_helper.moon +++ b/spec/support/spec_helper.moon @@ -159,3 +159,7 @@ export assert_memory_stays_within = (units, iterations, f) -> baseline, avg_used, diff, percentual error err +if jit.os == 'Windows' + require 'howl.cdefs.windows' + FILE_ATTRIBUTE_HIDDEN = 2 + export make_hidden = (path) -> C.SetFileAttributesA(path, FILE_ATTRIBUTE_HIDDEN) From 38796e1d8a5322276732eb714b46a35413c14db6 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 25 Aug 2016 14:36:56 -0500 Subject: [PATCH 36/75] Un-break stuff --- spec/support/spec_helper.moon | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/support/spec_helper.moon b/spec/support/spec_helper.moon index a7d3cf98c..9f50c0793 100644 --- a/spec/support/spec_helper.moon +++ b/spec/support/spec_helper.moon @@ -163,3 +163,5 @@ if jit.os == 'Windows' require 'howl.cdefs.windows' FILE_ATTRIBUTE_HIDDEN = 2 export make_hidden = (path) -> C.SetFileAttributesA(path, FILE_ATTRIBUTE_HIDDEN) +else + export make_hidden = (path) -> nil From e0617cb93501df0449dcd2c6abb978b062d57168 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 25 Aug 2016 14:42:23 -0500 Subject: [PATCH 37/75] Shut up, linter --- lint_config.moon | 1 + 1 file changed, 1 insertion(+) diff --git a/lint_config.moon b/lint_config.moon index 295c10fc3..446b48e05 100644 --- a/lint_config.moon +++ b/lint_config.moon @@ -182,6 +182,7 @@ 'howl_async', 'howl_main_ctx' 'it', + 'make_hidden', 'moon', 'pump_mainloop', 'set_howl_loop', From 9cd8d6c8b60feff1d30b10d47e7f07dee517ae78 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 26 Aug 2016 10:53:08 -0500 Subject: [PATCH 38/75] Update Makefile for MSYS2 --- src/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Makefile b/src/Makefile index 8cfdf1088..27f3d01bb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -44,6 +44,8 @@ else LD_FLAGS = -Wl,-E endif ifdef WIN32 + # MSYS sets CC to cc, even though it doesn't actually exist. + CC=gcc LIBS = -lm ${GTK_LIBS} -lstdc++ -lkernel32 -lgdi32 LD_FLAGS = -Wl,--export-all endif From 843a404806c829ebfac1babe70d59e7c3fa7dfe3 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 26 Aug 2016 12:42:30 -0500 Subject: [PATCH 39/75] More process fixes --- lib/howl/commands/app_commands.moon | 2 +- lib/howl/io/process.moon | 18 +++++++++++-- spec/io/process_spec.moon | 42 ++++++++++++++++++++++------- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/lib/howl/commands/app_commands.moon b/lib/howl/commands/app_commands.moon index 82136d89e..6e39389a0 100644 --- a/lib/howl/commands/app_commands.moon +++ b/lib/howl/commands/app_commands.moon @@ -379,7 +379,7 @@ command.register ----------------------------------------------------------------------- launch_cmd = (working_directory, cmd) -> - shell = howl.sys.env.SHELL or '/bin/sh' + shell = howl.sys.env.SHELL p = Process { :cmd, :shell, diff --git a/lib/howl/io/process.moon b/lib/howl/io/process.moon index 3cda460d2..2c1a79440 100644 --- a/lib/howl/io/process.moon +++ b/lib/howl/io/process.moon @@ -40,11 +40,25 @@ shell_quote = (s) -> else s -get_command = (v, shell = '/bin/sh') -> +default_shell = if jit.os == 'Windows' + if howl.sys.env.MSYSCON + -- Running under MSYS2. + "#{howl.sys.env.WD}sh.exe" + else + "#{howl.sys.env.SYSTEMROOT}/System32/cmd.exe" +else + '/bin/sh' + +get_command = (v, shell = default_shell) -> t = type v if t == 'string' - return { shell, '-c', v }, v + arg = if shell\find 'cmd' + -- Likely cmd.exe. + '/C' + else + '-c' + return { shell, arg, v }, v elseif t != 'table' return nil diff --git a/spec/io/process_spec.moon b/spec/io/process_spec.moon index 1325c59aa..3c4496623 100644 --- a/spec/io/process_spec.moon +++ b/spec/io/process_spec.moon @@ -2,6 +2,24 @@ Process = howl.io.Process File = howl.io.File glib = require 'ljglibs.glib' +sh, echo = if jit.os == 'Windows' + if not howl.sys.env.MSYSCON + error 'These specs must be run under MSYS!' + "#{howl.sys.env.WD}sh.exe", "#{howl.sys.env.WD}echo.exe" +else + '/bin/sh', '/bin/echo' + +fix_paths = (path) -> + if jit.os == 'Windows' + -- On MSYS, often the shell will output paths like: + -- /usr/bin/sh + -- but the specs use (and therefore expect) stuff like: + -- C:/msys64/usr/bin/sh.exe + -- This fixes up those paths to make the latter like the former. + path\gsub('\\', '/')\gsub('.*msys[^/]*/', '/')\gsub('%.exe$', '') + else + path + describe 'Process', -> run = (...) -> @@ -26,16 +44,16 @@ describe 'Process', -> assert.same {'echo', 'foo'}, p.argv p = Process cmd: 'echo "foo bar"' - assert.same { '/bin/sh', '-c', 'echo "foo bar"'}, p.argv + assert.same { sh, '-c', 'echo "foo bar"'}, p.argv it 'allows specifying a different shell', -> - p = Process cmd: 'foo', shell: '/bin/echo' - assert.same { '/bin/echo', '-c', 'foo'}, p.argv + p = Process cmd: 'foo', shell: echo + assert.same { echo, '-c', 'foo'}, p.argv describe 'Process.execute(cmd, opts)', -> it 'executes the specified command and return ', (done) -> howl_async -> - out, err, p = Process.execute {'sh', '-c', 'cat; echo foo >&2'}, stdin: 'reverb' + out, err, p = Process.execute {sh, '-c', 'cat; echo foo >&2'}, stdin: 'reverb' assert.equal 'reverb', out assert.equal 'foo\n', err assert.equal 'Process', typeof(p) @@ -45,12 +63,13 @@ describe 'Process', -> howl_async -> status, out = pcall Process.execute, 'echo $0' assert.is_true status - assert.equal '/bin/sh\n', out + expected = fix_paths sh + assert.equal "#{expected}\n", out done! it "allows specifying a different shell", (done) -> howl_async -> - status, out, _, process = pcall Process.execute, 'blargh', shell: '/bin/echo' + status, out, _, process = pcall Process.execute, 'blargh', shell: echo assert.is_true status assert.match out, 'blargh' assert.equal 'blargh', process.command_line @@ -215,14 +234,19 @@ describe 'Process', -> describe '.working_directory', -> context 'when provided during launch', -> + bindir = if jit.os == 'Windows' + howl.sys.env.SYSTEMROOT + else + '/bin' + it 'is the same directory', -> - cwd = File '/bin' + cwd = File bindir p = Process(cmd: 'true', working_directory: cwd) assert.equal cwd, p.working_directory it 'is always a File instance', -> - p = Process(cmd: 'true', working_directory: '/bin') - assert.equal 'File', typeof p.working_directory + p = Process(cmd: 'true', working_directory: bindir) + assert.equal 'File', typeof p.working_directory context 'when not provided', -> it 'is the current working directory', -> From ccc8bb889dd5c270d8a841a19d6b70654c908cf6 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Tue, 30 Aug 2016 13:43:18 -0500 Subject: [PATCH 40/75] More spec fixes --- lib/ljglibs/spec/glib/spawn_spec.moon | 31 ++++++++++++++++++--------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/lib/ljglibs/spec/glib/spawn_spec.moon b/lib/ljglibs/spec/glib/spawn_spec.moon index b8062a158..dd986b3fa 100644 --- a/lib/ljglibs/spec/glib/spawn_spec.moon +++ b/lib/ljglibs/spec/glib/spawn_spec.moon @@ -1,5 +1,12 @@ {:spawn} = require 'ljglibs.glib' -{:UnixInputStream, :UnixOutputStream} = require 'ljglibs.gio' +{:UnixInputStream, :UnixOutputStream, :Win32InputStream, :Win32OutputStream} = require 'ljglibs.gio' +ffi = require 'ffi' + +InputStream, OutputStream = if jit.os == 'Windows' + forward = (cls) -> (pipe) -> cls ffi.C._get_osfhandle pipe + forward(Win32InputStream), forward(Win32OutputStream) +else + UnixInputStream, UnixOutputStream describe 'spawn', -> @@ -24,7 +31,7 @@ describe 'spawn', -> flags: { 'SEARCH_PATH' } } assert.is_not_nil p.stdout_pipe - stdout = UnixInputStream p.stdout_pipe + stdout = InputStream p.stdout_pipe assert.equals 'foo\n', stdout\read_all! context '(when opts.write_stdin is given)', -> @@ -36,10 +43,10 @@ describe 'spawn', -> flags: { 'SEARCH_PATH' } } assert.is_not_nil p.stdin_pipe - stdin = UnixOutputStream p.stdin_pipe + stdin = OutputStream p.stdin_pipe stdin\write_all 'give it back!' stdin\close! - input_stream = UnixInputStream p.stdout_pipe + input_stream = InputStream p.stdout_pipe assert.equals 'give it back!', input_stream\read_all! context '(when opts.read_stderr is given)', -> @@ -50,7 +57,7 @@ describe 'spawn', -> flags: { 'SEARCH_PATH', 'STDOUT_TO_DEV_NULL' } } assert.is_not_nil p.stderr_pipe - stderr = UnixInputStream p.stderr_pipe + stderr = InputStream p.stderr_pipe assert.equals 'foo\n', stderr\read_all! context 'when .env is set', -> @@ -61,16 +68,20 @@ describe 'spawn', -> flags: { 'SEARCH_PATH' }, env: { MY_SOLE_VAR: 'alone' } } - stdout = UnixInputStream p.stdout_pipe - assert.equals 'MY_SOLE_VAR=alone\n', stdout\read_all! + stdout = InputStream p.stdout_pipe + assert.equals 'alone\n', stdout\read_all! context 'when .working_dir is set', -> it 'spawns the process with the value as the working directory', -> + working_directory, expected_directory = if jit.os == 'Windows' + 'C:\\', '/c' + else + '/etc', '/etc' p = spawn.async_with_pipes { argv: { 'pwd' }, read_stdout: true, flags: { 'SEARCH_PATH' }, - working_directory: '/etc' + :working_directory } - stdout = UnixInputStream p.stdout_pipe - assert.equals '/etc\n', stdout\read_all! + stdout = InputStream p.stdout_pipe + assert.equals "#{expected_directory}\n", stdout\read_all! From 7c628a0477b1a75ebac6e176bbfbf816a9762e7c Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Tue, 30 Aug 2016 15:42:03 -0500 Subject: [PATCH 41/75] Fix scrollbar on an empty file --- lib/aullar/view.moon | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/aullar/view.moon b/lib/aullar/view.moon index 9641af05c..51c01172c 100644 --- a/lib/aullar/view.moon +++ b/lib/aullar/view.moon @@ -282,9 +282,14 @@ View = { if opts.vertical page_size = @lines_showing - 1 + upper = @buffer.nr_lines + if page_size == 0 + -- GtkRange docs mention that page_size is normally >0 for GtkScrollbar + upper = 2 + page_size = 1 adjustment = @vertical_scrollbar.adjustment if adjustment - adjustment\configure @first_visible_line, 1, @buffer.nr_lines, 1, page_size, page_size + adjustment\configure @first_visible_line, 1, upper, 1, page_size, page_size if opts.horizontal max_width = 0 From 11fe38d083619171ff1957cf8e548091d413115c Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 28 Sep 2016 15:34:30 -0500 Subject: [PATCH 42/75] Process spec fixes --- spec/io/process_spec.moon | 34 ++++++++++++++++++---------------- spec/support/spec_helper.moon | 8 ++++++-- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/spec/io/process_spec.moon b/spec/io/process_spec.moon index 3c4496623..86cd893fc 100644 --- a/spec/io/process_spec.moon +++ b/spec/io/process_spec.moon @@ -84,7 +84,7 @@ describe 'Process', -> it 'opts.env sets the process environment', (done) -> howl_async -> - out = Process.execute {'env'}, env: { foo: 'bar' } + out = Process.execute 'env', env: { foo: 'bar' } assert.equal 'foo=bar', out.stripped done! @@ -138,20 +138,21 @@ describe 'Process', -> context 'when handlers are not specified', -> it 'collects and returns and output', -> - p = Process cmd: 'echo foo', read_stdout: true - stdout, stderr = p\pump! - assert.equals 'foo\n', stdout - assert.is_nil stderr + howl_async -> + p = Process cmd: 'echo foo', read_stdout: true + stdout, stderr = p\pump! + assert.equals 'foo\n', stdout + assert.is_nil stderr - p = Process cmd: 'echo err >&2', read_stderr: true - stdout, stderr = p\pump! - assert.equals 'err\n', stderr - assert.is_nil stdout + p = Process cmd: 'echo err >&2', read_stderr: true + stdout, stderr = p\pump! + assert.equals 'err\n', stderr + assert.is_nil stdout - p = Process cmd: 'echo out; echo err >&2', read_stdout: true, read_stderr: true - stdout, stderr = p\pump! - assert.equals 'out\n', stdout - assert.equals 'err\n', stderr + p = Process cmd: 'echo out; echo err >&2', read_stdout: true, read_stderr: true + stdout, stderr = p\pump! + assert.equals 'out\n', stdout + assert.equals 'err\n', stderr describe 'wait()', -> it 'waits until the process is finished', (done) -> @@ -207,9 +208,10 @@ describe 'Process', -> describe '.exit_status', -> it 'is nil for a running process', -> - p = Process cmd: { 'sh', '-c', "sleep 1; true" } - assert.is_nil p.exit_status - p\wait! + howl_async -> + p = Process cmd: { 'sh', '-c', "sleep 1; true" } + assert.is_nil p.exit_status + p\wait! it 'is nil for a signalled process', (done) -> howl_async -> diff --git a/spec/support/spec_helper.moon b/spec/support/spec_helper.moon index 9f50c0793..696b2746e 100644 --- a/spec/support/spec_helper.moon +++ b/spec/support/spec_helper.moon @@ -92,8 +92,12 @@ howl_loop = setmetatable { export set_howl_loop = -> _G.setloop howl_loop export howl_async = (f) -> - _G.setloop howl_loop - co = coroutine.create busted.async(f) + local co + if jit.os == 'Windows' + co = coroutine.create f + else + set_howl_loop! + co = coroutine.create busted.async(f) status, err = coroutine.resume co error err unless status From 9e1f47202e90f71cdee3c9514554cac8a3b2c521 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 28 Sep 2016 15:37:49 -0500 Subject: [PATCH 43/75] More spec fixes --- spec/io/process_spec.moon | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/io/process_spec.moon b/spec/io/process_spec.moon index 86cd893fc..b2fa46746 100644 --- a/spec/io/process_spec.moon +++ b/spec/io/process_spec.moon @@ -37,7 +37,11 @@ describe 'Process', -> assert.equal 'Process', typeof Process cmd: 'true' it 'raises an error for an unknown command', -> - assert.raises 'howlblargh', -> Process cmd: {'howlblargh'} + errstring = if jit.os == 'Windows' + 'No such file or directory' + else + 'howlblargh' + assert.raises errstring, -> Process cmd: {'howlblargh'} it 'sets .argv to the parsed command line', -> p = Process cmd: {'echo', 'foo'} From cc28ed6bf10dd49c1e3e0834f26a020451db5e88 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 28 Sep 2016 18:01:18 -0500 Subject: [PATCH 44/75] PROCESS. SPECS. WORK. --- spec/io/process_spec.moon | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/spec/io/process_spec.moon b/spec/io/process_spec.moon index b2fa46746..b830b01e8 100644 --- a/spec/io/process_spec.moon +++ b/spec/io/process_spec.moon @@ -26,15 +26,24 @@ describe 'Process', -> with Process cmd: ... \wait! + procs = {} + collected_process = (...) -> + proc = Process ... + table.insert procs, proc + proc + collect = -> + for proc in *procs + proc\wait! + describe 'Process(opts)', -> it 'raises an error if opts.cmd is missing or invalid', -> assert.raises 'cmd', -> Process {} assert.raises 'cmd', -> Process cmd: 2 - assert.not_error -> Process cmd: 'id' - assert.not_error -> Process cmd: {'echo', 'foo'} + assert.not_error -> collected_process cmd: 'id' + assert.not_error -> collected_process cmd: {'echo', 'foo'} it 'returns a process object', -> - assert.equal 'Process', typeof Process cmd: 'true' + assert.equal 'Process', typeof collected_process cmd: 'true' it 'raises an error for an unknown command', -> errstring = if jit.os == 'Windows' @@ -44,14 +53,14 @@ describe 'Process', -> assert.raises errstring, -> Process cmd: {'howlblargh'} it 'sets .argv to the parsed command line', -> - p = Process cmd: {'echo', 'foo'} + p = collected_process cmd: {'echo', 'foo'} assert.same {'echo', 'foo'}, p.argv - p = Process cmd: 'echo "foo bar"' + p = collected_process cmd: 'echo "foo bar"' assert.same { sh, '-c', 'echo "foo bar"'}, p.argv it 'allows specifying a different shell', -> - p = Process cmd: 'foo', shell: echo + p = collected_process cmd: 'foo', shell: echo assert.same { echo, '-c', 'foo'}, p.argv describe 'Process.execute(cmd, opts)', -> @@ -247,16 +256,16 @@ describe 'Process', -> it 'is the same directory', -> cwd = File bindir - p = Process(cmd: 'true', working_directory: cwd) + p = collected_process(cmd: 'true', working_directory: cwd) assert.equal cwd, p.working_directory it 'is always a File instance', -> - p = Process(cmd: 'true', working_directory: bindir) + p = collected_process(cmd: 'true', working_directory: bindir) assert.equal 'File', typeof p.working_directory context 'when not provided', -> it 'is the current working directory', -> - p = Process(cmd: 'true') + p = collected_process(cmd: 'true') assert.equal File(glib.get_current_dir!), p.working_directory describe '.successful', -> @@ -281,7 +290,7 @@ describe 'Process', -> describe '.stdout', -> it 'allows reading process output', (done) -> howl_async -> - p = Process cmd: {'echo', 'one\ntwo'}, read_stdout: true + p = collected_process cmd: {'echo', 'one\ntwo'}, read_stdout: true assert.equals 'one\ntwo\n', p.stdout\read! assert.is_nil p.stdout\read! done! @@ -289,7 +298,7 @@ describe 'Process', -> describe '.stderr', -> it 'allows reading process error output', (done) -> howl_async -> - p = Process cmd: {'sh', '-c', 'echo foo >&2'}, read_stderr: true + p = collected_process cmd: {'sh', '-c', 'echo foo >&2'}, read_stderr: true assert.equals 'foo\n', p.stderr\read! done! @@ -336,6 +345,7 @@ describe 'Process', -> describe 'Process.running', -> it 'is a table of currently running processes, keyed by pid', (done) -> howl_async -> + collect! assert.same {}, Process.running p = Process cmd: {'cat'}, write_stdin: true assert.same {[p.pid]: p}, Process.running From 5e32023e1c8b178f965321fa765713be8d6715ab Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 28 Sep 2016 18:59:28 -0500 Subject: [PATCH 45/75] Fix glib specs --- lib/ljglibs/spec/glib/spawn_spec.moon | 10 +- spec/io/process_spec.moon | 132 ++++++++++++++------------ spec/support/spec_helper.moon | 8 +- 3 files changed, 80 insertions(+), 70 deletions(-) diff --git a/lib/ljglibs/spec/glib/spawn_spec.moon b/lib/ljglibs/spec/glib/spawn_spec.moon index dd986b3fa..5288f4e1b 100644 --- a/lib/ljglibs/spec/glib/spawn_spec.moon +++ b/lib/ljglibs/spec/glib/spawn_spec.moon @@ -2,11 +2,13 @@ {:UnixInputStream, :UnixOutputStream, :Win32InputStream, :Win32OutputStream} = require 'ljglibs.gio' ffi = require 'ffi' -InputStream, OutputStream = if jit.os == 'Windows' +InputStream, OutputStream, shell = if jit.os == 'Windows' forward = (cls) -> (pipe) -> cls ffi.C._get_osfhandle pipe - forward(Win32InputStream), forward(Win32OutputStream) + if not os.getenv 'MSYSCON' + error 'These tests only work on MSYS2' + forward(Win32InputStream), forward(Win32OutputStream), "#{os.getenv 'WD'}/sh.exe" else - UnixInputStream, UnixOutputStream + UnixInputStream, UnixOutputStream, 'sh' describe 'spawn', -> @@ -63,7 +65,7 @@ describe 'spawn', -> context 'when .env is set', -> it 'spawns the process with the set values as the environment', -> p = spawn.async_with_pipes { - argv: { 'env' }, + argv: { shell, '-c', 'echo $MY_SOLE_VAR' }, read_stdout: true, flags: { 'SEARCH_PATH' }, env: { MY_SOLE_VAR: 'alone' } diff --git a/spec/io/process_spec.moon b/spec/io/process_spec.moon index b830b01e8..c5893bf31 100644 --- a/spec/io/process_spec.moon +++ b/spec/io/process_spec.moon @@ -20,6 +20,18 @@ fix_paths = (path) -> else path +proc_async = (f) -> + local co + if jit.os == 'Windows' + co = coroutine.create f + else + set_howl_loop! + co = coroutine.create busted.async(f) + status, err = coroutine.resume co + error err unless status + +be_done = (done) -> done! unless jit.os == 'Windows' + describe 'Process', -> run = (...) -> @@ -65,79 +77,79 @@ describe 'Process', -> describe 'Process.execute(cmd, opts)', -> it 'executes the specified command and return ', (done) -> - howl_async -> + proc_async -> out, err, p = Process.execute {sh, '-c', 'cat; echo foo >&2'}, stdin: 'reverb' assert.equal 'reverb', out assert.equal 'foo\n', err assert.equal 'Process', typeof(p) - done! + be_done done it "executes string commands using /bin/sh by default", (done) -> - howl_async -> + proc_async -> status, out = pcall Process.execute, 'echo $0' assert.is_true status expected = fix_paths sh assert.equal "#{expected}\n", out - done! + be_done done it "allows specifying a different shell", (done) -> - howl_async -> + proc_async -> status, out, _, process = pcall Process.execute, 'blargh', shell: echo assert.is_true status assert.match out, 'blargh' assert.equal 'blargh', process.command_line - done! + be_done done it 'opts.working_directory sets the working working directory', (done) -> - howl_async -> + proc_async -> with_tmpdir (dir) -> out = Process.execute 'pwd', working_directory: dir assert.equal dir.path, out.stripped - done! + be_done done it 'opts.env sets the process environment', (done) -> - howl_async -> + proc_async -> out = Process.execute 'env', env: { foo: 'bar' } assert.equal 'foo=bar', out.stripped - done! + be_done done it 'works with large process outputs', (done) -> - howl_async -> + proc_async -> File.with_tmpfile (f) -> file_contents = string.rep "xxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyy zzzzzzzzzzzzzzzzzzz\n", 5000 f.contents = file_contents status, out = pcall Process.execute, "cat #{f.path}" assert.is_true status assert.equal file_contents, out - done! + be_done done describe 'pump(on_stdout, on_stderr)', -> context 'when the handler is provided', -> it 'invokes the handler for any stdout output before returning', (done) -> - howl_async -> + proc_async -> on_stdout = spy.new -> nil p = Process cmd: 'echo foo', read_stdout: true p\pump on_stdout assert.is_true p.exited assert.spy(on_stdout).was_called_with 'foo\n' assert.spy(on_stdout).was_called_with nil - done! + be_done done context 'when the handler is provided', -> it 'invokes the handler for any stderr output before returning', (done) -> - howl_async -> + proc_async -> on_stderr = spy.new -> nil p = Process cmd: 'echo err >&2', read_stderr: true p\pump nil, on_stderr assert.is_true p.exited assert.spy(on_stderr).was_called_with 'err\n' assert.spy(on_stderr).was_called_with nil - done! + be_done done context 'when both handlers are provided', -> it 'invokes both handlers for any output before returning', (done) -> - howl_async -> + proc_async -> on_stdout = spy.new -> nil on_stderr = spy.new -> nil p = Process cmd: 'echo out; echo err >&2', read_stdout: true, read_stderr: true @@ -147,11 +159,11 @@ describe 'Process', -> assert.spy(on_stdout).was_called_with nil assert.spy(on_stderr).was_called_with 'err\n' assert.spy(on_stderr).was_called_with nil - done! + be_done done context 'when handlers are not specified', -> it 'collects and returns and output', -> - howl_async -> + proc_async -> p = Process cmd: 'echo foo', read_stdout: true stdout, stderr = p\pump! assert.equals 'foo\n', stdout @@ -170,72 +182,72 @@ describe 'Process', -> describe 'wait()', -> it 'waits until the process is finished', (done) -> settimeout 2 - howl_async -> + proc_async -> File.with_tmpfile (file) -> file\delete! p = Process cmd: { 'sh', '-c', "sleep 1; touch '#{file.path}'" } p\wait! assert.is_true file.exists - done! + be_done done context 'signal handling', -> describe 'send_signal(signal) and .signalled', -> it 'sends the specified signal to the process', (done) -> - howl_async -> + proc_async -> p = Process cmd: 'cat', write_stdin: true p\send_signal 9 p\wait! assert.is_true p.signalled - done! + be_done done it '.signalled is false for a non-signaled process', (done) -> - howl_async -> + proc_async -> p = Process cmd: 'id' p\wait! assert.is_false p.signalled - done! + be_done done it '.signal holds the signal used for terminating the process', (done) -> - howl_async -> + proc_async -> p = Process cmd: 'cat', write_stdin: true p\send_signal 9 p\wait! assert.equals 9, p.signal - done! + be_done done it '.signal_name holds the name of the signal used for terminating the process', (done) -> - howl_async -> + proc_async -> p = Process cmd: 'cat', write_stdin: true p\send_signal 9 p\wait! assert.equals 'KILL', p.signal_name - done! + be_done done it 'signals can be referred to by name as well', (done) -> - howl_async -> + proc_async -> p = Process cmd: 'cat', write_stdin: true p\send_signal 'KILL' p\wait! assert.equals 9, p.signal - done! + be_done done describe '.exit_status', -> it 'is nil for a running process', -> - howl_async -> + proc_async -> p = Process cmd: { 'sh', '-c', "sleep 1; true" } assert.is_nil p.exit_status p\wait! it 'is nil for a signalled process', (done) -> - howl_async -> + proc_async -> p = Process cmd: 'cat', write_stdin: true p\send_signal 9 p\wait! assert.is_nil p.exit_status - done! + be_done done it 'is set to the exit status for a normally exited process', (done) -> - howl_async -> + proc_async -> p = run 'echo foo' assert.equals 0, p.exit_status @@ -245,7 +257,7 @@ describe 'Process', -> p = run {'sh', '-c', 'exit 2' } assert.equals 2, p.exit_status - done! + be_done done describe '.working_directory', -> context 'when provided during launch', -> @@ -270,41 +282,41 @@ describe 'Process', -> describe '.successful', -> it 'is true if the process exited cleanly with a zero exit code', (done) -> - howl_async -> + proc_async -> assert.is_true run('id').successful - done! + be_done done it 'is false if the process exited with a non-zero exit code', (done) -> - howl_async -> + proc_async -> assert.is_false run('false').successful - done! + be_done done it 'is false if the process exited due to a signal', (done) -> - howl_async -> + proc_async -> p = Process cmd: 'cat', write_stdin: true p\send_signal 9 p\wait! assert.is_false p.successful - done! + be_done done describe '.stdout', -> it 'allows reading process output', (done) -> - howl_async -> + proc_async -> p = collected_process cmd: {'echo', 'one\ntwo'}, read_stdout: true assert.equals 'one\ntwo\n', p.stdout\read! assert.is_nil p.stdout\read! - done! + be_done done describe '.stderr', -> it 'allows reading process error output', (done) -> - howl_async -> + proc_async -> p = collected_process cmd: {'sh', '-c', 'echo foo >&2'}, read_stderr: true assert.equals 'foo\n', p.stderr\read! - done! + be_done done describe '.stdin', -> it 'allows writing to the process input', (done) -> - howl_async -> + proc_async -> p = Process cmd: {'cat'}, write_stdin: true, read_stdout: true with p.stdin \write 'round-trip' @@ -312,39 +324,39 @@ describe 'Process', -> assert.equals 'round-trip', p.stdout\read! p\wait! - done! + be_done done describe '.command_line', -> context 'when the command is specified as a string', -> it 'is the same', (done) -> - howl_async -> + proc_async -> assert.equal 'echo command "bar"', run('echo command "bar"').command_line - done! + be_done done context 'when the command is specified as a table', -> it 'is a created shell command line', (done) -> - howl_async -> + proc_async -> assert.equal "echo command 'bar zed'", run({'echo', 'command', 'bar zed'}).command_line - done! + be_done done describe '.exit_status_string', -> it 'provides the exit code for a normally terminated process', (done) -> - howl_async -> + proc_async -> assert.equals 'exited normally with code 0', run('id').exit_status_string assert.equals 'exited normally with code 1', run('exit 1').exit_status_string - done! + be_done done it 'provides the signal name for a killed process', (done) -> - howl_async -> + proc_async -> p = Process cmd: {'cat'}, write_stdin: true, read_stdout: true p\send_signal 'KILL' p\wait! assert.equals 'killed by signal 9 (KILL)', p.exit_status_string - done! + be_done done describe 'Process.running', -> it 'is a table of currently running processes, keyed by pid', (done) -> - howl_async -> + proc_async -> collect! assert.same {}, Process.running p = Process cmd: {'cat'}, write_stdin: true @@ -352,12 +364,12 @@ describe 'Process', -> p.stdin\close! p\wait! assert.same {}, Process.running - done! + be_done done context 'resource management', -> it 'processes are collected correctly', (done) -> - howl_async -> + proc_async -> p = Process cmd: {'echo', 'one\ntwo'}, read_stdout: true assert.equals 'one\ntwo\n', p.stdout\read! p\wait! @@ -367,4 +379,4 @@ describe 'Process', -> p = nil collect_memory! assert.is_nil list[1] - done! + be_done done diff --git a/spec/support/spec_helper.moon b/spec/support/spec_helper.moon index 696b2746e..dc1be0216 100644 --- a/spec/support/spec_helper.moon +++ b/spec/support/spec_helper.moon @@ -92,12 +92,8 @@ howl_loop = setmetatable { export set_howl_loop = -> _G.setloop howl_loop export howl_async = (f) -> - local co - if jit.os == 'Windows' - co = coroutine.create f - else - set_howl_loop! - co = coroutine.create busted.async(f) + set_howl_loop! + co = coroutine.create busted.async(f) status, err = coroutine.resume co error err unless status From 4d1437d6bf561c990c204e0177870ef26d4c5925 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 28 Sep 2016 19:32:58 -0500 Subject: [PATCH 46/75] Fix the definition of g_object_new --- lib/ljglibs/cdefs/gobject.moon | 4 +++- lib/ljglibs/gobject/object.moon | 6 +++--- lib/ljglibs/spec/gobject/object_spec.moon | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/ljglibs/cdefs/gobject.moon b/lib/ljglibs/cdefs/gobject.moon index 82732b28c..0fa15c2e4 100644 --- a/lib/ljglibs/cdefs/gobject.moon +++ b/lib/ljglibs/cdefs/gobject.moon @@ -27,7 +27,9 @@ ffi.cdef [[ /* GObject */ typedef struct {} GObject; - GObject g_object_new (GType object_type); + GObject g_object_new (GType object_type, + const gchar* first_property_name, + ...); gpointer g_object_ref (gpointer object); gpointer g_object_ref_sink (gpointer object); gboolean g_object_is_floating (gpointer object); diff --git a/lib/ljglibs/gobject/object.moon b/lib/ljglibs/gobject/object.moon index 5e457ad28..13e1bda80 100644 --- a/lib/ljglibs/gobject/object.moon +++ b/lib/ljglibs/gobject/object.moon @@ -7,9 +7,9 @@ C, ffi_cast, ffi_new = ffi.C, ffi.cast, ffi.new lua_value = types.lua_value core.define 'GObject', { - new: (type) -> + new: (type, ...) -> error 'Undefined gtype passed in', 2 if type == 0 or type == nil - C.g_object_new type + C.g_object_new type, ... ref: (o) -> return nil if o == nil @@ -42,5 +42,5 @@ core.define 'GObject', { set_typed: (k, type, v) => C.g_object_set @, k, ffi_cast(type, v), nil -}, (spec, type) -> spec.new type +}, (spec, type, ...) -> spec.new type, ... diff --git a/lib/ljglibs/spec/gobject/object_spec.moon b/lib/ljglibs/spec/gobject/object_spec.moon index 390bbf60c..f7cb0e0db 100644 --- a/lib/ljglibs/spec/gobject/object_spec.moon +++ b/lib/ljglibs/spec/gobject/object_spec.moon @@ -9,12 +9,12 @@ describe 'Object', -> context '(constructing)', -> it 'can be created using an existing gtype', -> type = Type.from_name 'GtkEventBox' - o = Object type + o = Object type, nil assert.is_not_nil o it 'raises an error if type is nil', -> type = Type.from_name 'GtkButton2' - assert.raises 'undefined', -> Object type + assert.raises 'undefined', -> Object type, nil describe 'get_typed(k, type)', -> it 'returns a property value converted according to ', -> From 40be07baffbf09bfa1a77fe34cd3781ee12252c7 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 28 Sep 2016 19:41:06 -0500 Subject: [PATCH 47/75] gio test fixes --- lib/ljglibs/spec/gio/file_spec.moon | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/ljglibs/spec/gio/file_spec.moon b/lib/ljglibs/spec/gio/file_spec.moon index 6c41719ee..619816f5b 100644 --- a/lib/ljglibs/spec/gio/file_spec.moon +++ b/lib/ljglibs/spec/gio/file_spec.moon @@ -1,10 +1,23 @@ glib = require 'ljglibs.glib' GFile = require 'ljglibs.gio.file' +sep = if jit.os == 'Windows' + '\\' +else + '/' +ls = "#{sep}bin#{sep}ls" +demo_file = if jit.os == 'Windows' + error 'These tests must be run under MSYS2' unless os.getenv 'MSYSCON' + "#{os.getenv 'WD'}ls.exe" +else + '/bin/ls' + describe 'GFile', -> with_tmpfile = (contents, f) -> p = os.tmpname! + if jit.os == 'Windows' + p = p\sub 2 fh = io.open p, 'wb' fh\write contents fh\close! @@ -15,28 +28,28 @@ describe 'GFile', -> if glib.check_version 2, 36, 0 describe 'new_for_commandline_arg_and_cwd(path, cwd)', -> it 'resolves a relative from ', -> - assert.equals '/bin/ls', GFile.new_for_commandline_arg_and_cwd('ls', '/bin').path + assert.equals ls, GFile.new_for_commandline_arg_and_cwd('ls', '/bin').path it 'resolves an absolute as is', -> - assert.equals '/bin/touch', GFile.new_for_commandline_arg_and_cwd('/bin/touch', '/home').path + assert.equals ls, GFile.new_for_commandline_arg_and_cwd('/bin/ls', '/home').path it '.path contains the path', -> - assert.equals '/bin/ls', GFile('/bin/ls').path + assert.equals ls, GFile('/bin/ls').path it '.uri contains an URI representing the path', -> assert.equal 'file:///foo.txt', GFile('/foo.txt').uri it '.exists returns true if the path exists', -> - assert.is_true GFile('/bin/ls').exists + assert.is_true GFile(demo_file).exists assert.is_false GFile('/pleasedontputadirectorylikethisinyourroot').exists it '.parent return the parent of the file', -> - assert.equal '/bin', GFile('/bin/ls').parent.path + assert.equal "#{sep}bin", GFile('/bin/ls').parent.path describe 'get_child(name)', -> it 'returns a new file for the given child', -> parent = GFile '/bin' - assert.equals '/bin/ls', parent\get_child('ls').path + assert.equals ls, parent\get_child('ls').path describe 'has_parent([file])', -> it 'returns true if the file has a parent', -> @@ -61,7 +74,7 @@ describe 'GFile', -> context 'for an existing file', -> it 'returns an info object', -> - f = GFile '/bin/ls' + f = GFile demo_file info = f\query_info '*', GFile.QUERY_INFO_NONE assert.is_false info.is_hidden assert.is_false info.is_symlink @@ -78,4 +91,4 @@ describe 'GFile', -> it 'tostring returns the path as a string', -> collectgarbage! file = GFile '/bin/ls' - assert.equal '/bin/ls', tostring file + assert.equal ls, tostring file From 6dff5cadf82295ef882b340e8410e3517e19b518 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 28 Sep 2016 19:53:12 -0500 Subject: [PATCH 48/75] More spec fixes --- lib/ljglibs/spec/gio/file_spec.moon | 1 + lib/ljglibs/spec/gio/input_stream_spec.moon | 2 + lib/ljglibs/spec/gio/output_stream_spec.moon | 2 + lib/ljglibs/spec/gio/subprocess_spec.moon | 46 +++++++++++--------- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/lib/ljglibs/spec/gio/file_spec.moon b/lib/ljglibs/spec/gio/file_spec.moon index 619816f5b..4342c4d90 100644 --- a/lib/ljglibs/spec/gio/file_spec.moon +++ b/lib/ljglibs/spec/gio/file_spec.moon @@ -1,5 +1,6 @@ glib = require 'ljglibs.glib' GFile = require 'ljglibs.gio.file' +File = require 'howl.io.file' sep = if jit.os == 'Windows' '\\' diff --git a/lib/ljglibs/spec/gio/input_stream_spec.moon b/lib/ljglibs/spec/gio/input_stream_spec.moon index 98d38fa05..d523c499d 100644 --- a/lib/ljglibs/spec/gio/input_stream_spec.moon +++ b/lib/ljglibs/spec/gio/input_stream_spec.moon @@ -6,6 +6,8 @@ glib = require 'ljglibs.glib' with_tmpfile = (contents, f) -> p = os.tmpname! + if jit.os == 'Windows' + p = p\sub 2 fh = io.open p, 'wb' fh\write contents fh\close! diff --git a/lib/ljglibs/spec/gio/output_stream_spec.moon b/lib/ljglibs/spec/gio/output_stream_spec.moon index 9ba34a388..fb761f760 100644 --- a/lib/ljglibs/spec/gio/output_stream_spec.moon +++ b/lib/ljglibs/spec/gio/output_stream_spec.moon @@ -5,6 +5,8 @@ with_tmpfile = (f) -> p = os.tmpname! + if jit.os == 'Windows' + p = p\sub 2 status, err = pcall f, p os.remove p error err unless status diff --git a/lib/ljglibs/spec/gio/subprocess_spec.moon b/lib/ljglibs/spec/gio/subprocess_spec.moon index 1e4f2a321..826968dae 100644 --- a/lib/ljglibs/spec/gio/subprocess_spec.moon +++ b/lib/ljglibs/spec/gio/subprocess_spec.moon @@ -19,7 +19,11 @@ describe 'Subprocess', -> describe 'creation', -> it 'raises an error for an unknown command', -> - assert.raises 'howlblargh', -> Subprocess nil, 'howlblargh', 'urk' + errstring = if jit.os == 'Windows' + 'No such file or directory' + else + 'howlblargh' + assert.raises errstring, -> Subprocess nil, 'howlblargh', 'urk' it 'returns a Subprocess for a valid command', -> assert.not_nil Subprocess(Subprocess.FLAGS_STDOUT_SILENCE, 'id') @@ -51,29 +55,30 @@ describe 'Subprocess', -> assert.equal 0, run('id').exit_status assert.not_equal 0, run('false').exit_status - context 'signal handling', -> - describe 'send_signal(signal) and .if_signaled', -> - it 'sends the specified signal to the process', -> - process = Subprocess(Subprocess.FLAGS_STDIN_PIPE, 'cat') - process\send_signal 9 - process\wait! - assert.is_true process.if_signaled + if jit.os != 'Windows' + context 'signal handling', -> + describe 'send_signal(signal) and .if_signaled', -> + it 'sends the specified signal to the process', -> + process = Subprocess(Subprocess.FLAGS_STDIN_PIPE, 'cat') + process\send_signal 9 + process\wait! + assert.is_true process.if_signaled - it '.if_signaled returns false for a non-signaled process', -> - assert.is_false run('id').if_signaled + it '.if_signaled returns false for a non-signaled process', -> + assert.is_false run('id').if_signaled - it '.term_sig holds the signal used for terminating the process', -> - process = Subprocess(Subprocess.FLAGS_STDIN_PIPE, 'cat') - process\send_signal 9 - process\wait! - assert.equal 9, process.term_sig + it '.term_sig holds the signal used for terminating the process', -> + process = Subprocess(Subprocess.FLAGS_STDIN_PIPE, 'cat') + process\send_signal 9 + process\wait! + assert.equal 9, process.term_sig describe 'force_exit()', -> it 'tries to terminate the process in some way', -> process = Subprocess(Subprocess.FLAGS_STDIN_PIPE, 'cat') process\force_exit! process\wait! - assert.is_true process.if_signaled + assert.is_true process.if_exited describe 'wait_check()', -> it 'waits until the process is finished and returns true for a succesful termination', -> @@ -83,10 +88,11 @@ describe 'Subprocess', -> process = Subprocess(Subprocess.FLAGS_STDOUT_SILENCE, 'false') assert.raises 'exited', -> process\wait_check! - it 'raises an error if the process was killed by a signal', -> - process = Subprocess(Subprocess.FLAGS_STDIN_PIPE, 'cat') - process\send_signal 9 - assert.raises 'killed', -> process\wait_check! + if jit.os != 'Windows' + it 'raises an error if the process was killed by a signal', -> + process = Subprocess(Subprocess.FLAGS_STDIN_PIPE, 'cat') + process\send_signal 9 + assert.raises 'killed', -> process\wait_check! describe '.stdout_pipe', -> it 'allows reading process output', -> From c7260e054d5158fc3cb390f8f33c68284d8560f7 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 28 Sep 2016 20:06:55 -0500 Subject: [PATCH 49/75] More spec fixes --- bundles/git/spec/git_spec.moon | 34 +++++++++-------- spec/io/process_spec.moon | 70 ++++++++++++++-------------------- spec/support/spec_helper.moon | 12 ++++++ 3 files changed, 60 insertions(+), 56 deletions(-) diff --git a/bundles/git/spec/git_spec.moon b/bundles/git/spec/git_spec.moon index 272a4ef1d..7836ed563 100644 --- a/bundles/git/spec/git_spec.moon +++ b/bundles/git/spec/git_spec.moon @@ -2,6 +2,11 @@ import bundle, config, VC from howl import File from howl.io import Spy from howl.spec +echo = if jit.os == 'Windows' + "#{howl.sys.env.WD}echo.exe" +else + '/bin/echo' + describe 'Git bundle', -> setup -> bundle.load_by_name 'git' teardown -> bundle.unload 'git' @@ -27,8 +32,7 @@ describe 'Git bundle', -> assert.equal instance.root, dir it 'returns nil if no git root was found', -> - File.with_tmpfile (file) -> - assert.is_nil git_vc.find file + assert.is_nil git_vc.find howl.io.File echo describe 'A Git instance', -> root = nil @@ -52,7 +56,7 @@ describe 'Git bundle', -> assert.same list1, list2 it 'returns a list of git files, including untracked', (done) -> - howl_async -> + proc_async -> assert_same_files git\files!, {} file = root / 'new.lua' file\touch! @@ -67,7 +71,7 @@ describe 'Git bundle', -> file2 = root / 'another.lua' file2\touch! assert_same_files git\files!, { file2, file } - done! + proc_done done describe 'diff([file])', -> local file @@ -79,37 +83,37 @@ describe 'Git bundle', -> os.execute "cd #{root} && git commit -q -m 'rev1' #{file}" it 'returns nil if has not changed', (done) -> - howl_async -> + proc_async -> assert.is_nil git\diff file - done! + proc_done done it 'returns a string containing the diff if has changed', (done) -> - howl_async -> + proc_async -> file.contents ..= 'line 2\n' diff = git\diff file assert.includes diff, file.basename assert.includes diff, '+line 2' - done! + proc_done done it 'returns a diff for the entire directory if file is not specified', (done) -> - howl_async -> + proc_async -> file.contents ..= 'line 2\n' diff = git\diff! assert.includes diff, file.basename assert.includes diff, '+line 2' - done! + proc_done done describe 'run(...)', -> it 'runs git in the root dir with the given arguments and returns the output', (done) -> - howl_async -> + proc_async -> assert.includes git\run('config', '--local', '-l'), "Howl Spec" - done! + proc_done done it 'uses the executable in variable `git_path` if specified', (done) -> - howl_async -> - config.git_path = '/bin/echo' + proc_async -> + config.git_path = echo status, out = pcall git.run, git, 'using echo' config.git_path = nil assert status, out assert.includes out, "using echo" - done! + proc_done done diff --git a/spec/io/process_spec.moon b/spec/io/process_spec.moon index c5893bf31..9cc0fd9f5 100644 --- a/spec/io/process_spec.moon +++ b/spec/io/process_spec.moon @@ -20,18 +20,6 @@ fix_paths = (path) -> else path -proc_async = (f) -> - local co - if jit.os == 'Windows' - co = coroutine.create f - else - set_howl_loop! - co = coroutine.create busted.async(f) - status, err = coroutine.resume co - error err unless status - -be_done = (done) -> done! unless jit.os == 'Windows' - describe 'Process', -> run = (...) -> @@ -82,7 +70,7 @@ describe 'Process', -> assert.equal 'reverb', out assert.equal 'foo\n', err assert.equal 'Process', typeof(p) - be_done done + proc_done done it "executes string commands using /bin/sh by default", (done) -> proc_async -> @@ -90,7 +78,7 @@ describe 'Process', -> assert.is_true status expected = fix_paths sh assert.equal "#{expected}\n", out - be_done done + proc_done done it "allows specifying a different shell", (done) -> proc_async -> @@ -98,20 +86,20 @@ describe 'Process', -> assert.is_true status assert.match out, 'blargh' assert.equal 'blargh', process.command_line - be_done done + proc_done done it 'opts.working_directory sets the working working directory', (done) -> proc_async -> with_tmpdir (dir) -> out = Process.execute 'pwd', working_directory: dir assert.equal dir.path, out.stripped - be_done done + proc_done done it 'opts.env sets the process environment', (done) -> proc_async -> out = Process.execute 'env', env: { foo: 'bar' } assert.equal 'foo=bar', out.stripped - be_done done + proc_done done it 'works with large process outputs', (done) -> proc_async -> @@ -121,7 +109,7 @@ describe 'Process', -> status, out = pcall Process.execute, "cat #{f.path}" assert.is_true status assert.equal file_contents, out - be_done done + proc_done done describe 'pump(on_stdout, on_stderr)', -> @@ -134,7 +122,7 @@ describe 'Process', -> assert.is_true p.exited assert.spy(on_stdout).was_called_with 'foo\n' assert.spy(on_stdout).was_called_with nil - be_done done + proc_done done context 'when the handler is provided', -> it 'invokes the handler for any stderr output before returning', (done) -> @@ -145,7 +133,7 @@ describe 'Process', -> assert.is_true p.exited assert.spy(on_stderr).was_called_with 'err\n' assert.spy(on_stderr).was_called_with nil - be_done done + proc_done done context 'when both handlers are provided', -> it 'invokes both handlers for any output before returning', (done) -> @@ -159,7 +147,7 @@ describe 'Process', -> assert.spy(on_stdout).was_called_with nil assert.spy(on_stderr).was_called_with 'err\n' assert.spy(on_stderr).was_called_with nil - be_done done + proc_done done context 'when handlers are not specified', -> it 'collects and returns and output', -> @@ -188,7 +176,7 @@ describe 'Process', -> p = Process cmd: { 'sh', '-c', "sleep 1; touch '#{file.path}'" } p\wait! assert.is_true file.exists - be_done done + proc_done done context 'signal handling', -> describe 'send_signal(signal) and .signalled', -> @@ -198,14 +186,14 @@ describe 'Process', -> p\send_signal 9 p\wait! assert.is_true p.signalled - be_done done + proc_done done it '.signalled is false for a non-signaled process', (done) -> proc_async -> p = Process cmd: 'id' p\wait! assert.is_false p.signalled - be_done done + proc_done done it '.signal holds the signal used for terminating the process', (done) -> proc_async -> @@ -213,7 +201,7 @@ describe 'Process', -> p\send_signal 9 p\wait! assert.equals 9, p.signal - be_done done + proc_done done it '.signal_name holds the name of the signal used for terminating the process', (done) -> proc_async -> @@ -221,7 +209,7 @@ describe 'Process', -> p\send_signal 9 p\wait! assert.equals 'KILL', p.signal_name - be_done done + proc_done done it 'signals can be referred to by name as well', (done) -> proc_async -> @@ -229,7 +217,7 @@ describe 'Process', -> p\send_signal 'KILL' p\wait! assert.equals 9, p.signal - be_done done + proc_done done describe '.exit_status', -> it 'is nil for a running process', -> @@ -244,7 +232,7 @@ describe 'Process', -> p\send_signal 9 p\wait! assert.is_nil p.exit_status - be_done done + proc_done done it 'is set to the exit status for a normally exited process', (done) -> proc_async -> @@ -257,7 +245,7 @@ describe 'Process', -> p = run {'sh', '-c', 'exit 2' } assert.equals 2, p.exit_status - be_done done + proc_done done describe '.working_directory', -> context 'when provided during launch', -> @@ -284,12 +272,12 @@ describe 'Process', -> it 'is true if the process exited cleanly with a zero exit code', (done) -> proc_async -> assert.is_true run('id').successful - be_done done + proc_done done it 'is false if the process exited with a non-zero exit code', (done) -> proc_async -> assert.is_false run('false').successful - be_done done + proc_done done it 'is false if the process exited due to a signal', (done) -> proc_async -> @@ -297,7 +285,7 @@ describe 'Process', -> p\send_signal 9 p\wait! assert.is_false p.successful - be_done done + proc_done done describe '.stdout', -> it 'allows reading process output', (done) -> @@ -305,14 +293,14 @@ describe 'Process', -> p = collected_process cmd: {'echo', 'one\ntwo'}, read_stdout: true assert.equals 'one\ntwo\n', p.stdout\read! assert.is_nil p.stdout\read! - be_done done + proc_done done describe '.stderr', -> it 'allows reading process error output', (done) -> proc_async -> p = collected_process cmd: {'sh', '-c', 'echo foo >&2'}, read_stderr: true assert.equals 'foo\n', p.stderr\read! - be_done done + proc_done done describe '.stdin', -> it 'allows writing to the process input', (done) -> @@ -324,27 +312,27 @@ describe 'Process', -> assert.equals 'round-trip', p.stdout\read! p\wait! - be_done done + proc_done done describe '.command_line', -> context 'when the command is specified as a string', -> it 'is the same', (done) -> proc_async -> assert.equal 'echo command "bar"', run('echo command "bar"').command_line - be_done done + proc_done done context 'when the command is specified as a table', -> it 'is a created shell command line', (done) -> proc_async -> assert.equal "echo command 'bar zed'", run({'echo', 'command', 'bar zed'}).command_line - be_done done + proc_done done describe '.exit_status_string', -> it 'provides the exit code for a normally terminated process', (done) -> proc_async -> assert.equals 'exited normally with code 0', run('id').exit_status_string assert.equals 'exited normally with code 1', run('exit 1').exit_status_string - be_done done + proc_done done it 'provides the signal name for a killed process', (done) -> proc_async -> @@ -352,7 +340,7 @@ describe 'Process', -> p\send_signal 'KILL' p\wait! assert.equals 'killed by signal 9 (KILL)', p.exit_status_string - be_done done + proc_done done describe 'Process.running', -> it 'is a table of currently running processes, keyed by pid', (done) -> @@ -364,7 +352,7 @@ describe 'Process', -> p.stdin\close! p\wait! assert.same {}, Process.running - be_done done + proc_done done context 'resource management', -> @@ -379,4 +367,4 @@ describe 'Process', -> p = nil collect_memory! assert.is_nil list[1] - be_done done + proc_done done diff --git a/spec/support/spec_helper.moon b/spec/support/spec_helper.moon index dc1be0216..05bc657be 100644 --- a/spec/support/spec_helper.moon +++ b/spec/support/spec_helper.moon @@ -97,6 +97,18 @@ export howl_async = (f) -> status, err = coroutine.resume co error err unless status +export proc_async = (f) -> + local co + if jit.os == 'Windows' + co = coroutine.create f + else + set_howl_loop! + co = coroutine.create busted.async(f) + status, err = coroutine.resume co + error err unless status + +export proc_done = (done) -> done! unless jit.os == 'Windows' + export pump_mainloop = -> jit.off true, true count = 0 From 941a91f9dda60388b533c8802b9c75b1705089cb Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 28 Sep 2016 20:08:11 -0500 Subject: [PATCH 50/75] Cleanup --- lib/ljglibs/spec/gio/file_spec.moon | 1 - lib/ljglibs/spec/glib/spawn_spec.moon | 2 -- spec/io/process_spec.moon | 2 -- spec/support/spec_helper.moon | 3 +++ 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/ljglibs/spec/gio/file_spec.moon b/lib/ljglibs/spec/gio/file_spec.moon index 4342c4d90..4363d1e49 100644 --- a/lib/ljglibs/spec/gio/file_spec.moon +++ b/lib/ljglibs/spec/gio/file_spec.moon @@ -8,7 +8,6 @@ else '/' ls = "#{sep}bin#{sep}ls" demo_file = if jit.os == 'Windows' - error 'These tests must be run under MSYS2' unless os.getenv 'MSYSCON' "#{os.getenv 'WD'}ls.exe" else '/bin/ls' diff --git a/lib/ljglibs/spec/glib/spawn_spec.moon b/lib/ljglibs/spec/glib/spawn_spec.moon index 5288f4e1b..cacf1e8ea 100644 --- a/lib/ljglibs/spec/glib/spawn_spec.moon +++ b/lib/ljglibs/spec/glib/spawn_spec.moon @@ -4,8 +4,6 @@ ffi = require 'ffi' InputStream, OutputStream, shell = if jit.os == 'Windows' forward = (cls) -> (pipe) -> cls ffi.C._get_osfhandle pipe - if not os.getenv 'MSYSCON' - error 'These tests only work on MSYS2' forward(Win32InputStream), forward(Win32OutputStream), "#{os.getenv 'WD'}/sh.exe" else UnixInputStream, UnixOutputStream, 'sh' diff --git a/spec/io/process_spec.moon b/spec/io/process_spec.moon index 9cc0fd9f5..1ada7314f 100644 --- a/spec/io/process_spec.moon +++ b/spec/io/process_spec.moon @@ -3,8 +3,6 @@ File = howl.io.File glib = require 'ljglibs.glib' sh, echo = if jit.os == 'Windows' - if not howl.sys.env.MSYSCON - error 'These specs must be run under MSYS!' "#{howl.sys.env.WD}sh.exe", "#{howl.sys.env.WD}echo.exe" else '/bin/sh', '/bin/echo' diff --git a/spec/support/spec_helper.moon b/spec/support/spec_helper.moon index 05bc657be..6c0f49613 100644 --- a/spec/support/spec_helper.moon +++ b/spec/support/spec_helper.moon @@ -7,6 +7,9 @@ import theme from howl.ui import dispatch, signal, config from howl _G.Spy = require 'howl.spec.spy' +if not howl.sys.env.MSYSCON + error 'These specs must be run under MSYS2!' + -- additional aliases export context = describe From 98946933ce6dcd6f3bd606d076ddd52874ae2768 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 28 Sep 2016 20:15:45 -0500 Subject: [PATCH 51/75] Linter --- lib/ljglibs/spec/gio/file_spec.moon | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ljglibs/spec/gio/file_spec.moon b/lib/ljglibs/spec/gio/file_spec.moon index 4363d1e49..7d265a28e 100644 --- a/lib/ljglibs/spec/gio/file_spec.moon +++ b/lib/ljglibs/spec/gio/file_spec.moon @@ -1,6 +1,5 @@ glib = require 'ljglibs.glib' GFile = require 'ljglibs.gio.file' -File = require 'howl.io.file' sep = if jit.os == 'Windows' '\\' From fd7f5776411073ecad136037839ff7fed330d8ec Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 28 Sep 2016 20:17:54 -0500 Subject: [PATCH 52/75] Mrgrgr... --- lint_config.moon | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lint_config.moon b/lint_config.moon index 446b48e05..09d3bfc10 100644 --- a/lint_config.moon +++ b/lint_config.moon @@ -184,6 +184,8 @@ 'it', 'make_hidden', 'moon', + 'proc_async', + 'proc_done', 'pump_mainloop', 'set_howl_loop', 'settimeout', From 285242eb86f629a8e0691cf166ed1902b73e6f27 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 28 Sep 2016 20:19:35 -0500 Subject: [PATCH 53/75] *facepalm* --- spec/support/spec_helper.moon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/spec_helper.moon b/spec/support/spec_helper.moon index 6c0f49613..ba3a92b06 100644 --- a/spec/support/spec_helper.moon +++ b/spec/support/spec_helper.moon @@ -7,7 +7,7 @@ import theme from howl.ui import dispatch, signal, config from howl _G.Spy = require 'howl.spec.spy' -if not howl.sys.env.MSYSCON +if jit.os == 'Windows' and not howl.sys.env.MSYSCON error 'These specs must be run under MSYS2!' -- additional aliases From 734e3487437a243de2448d86693eab709fbc0059 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 29 Sep 2016 11:08:38 -0500 Subject: [PATCH 54/75] Linux fixes --- lib/howl/io/output_stream.moon | 2 +- lib/ljglibs/spec/gio/subprocess_spec.moon | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/howl/io/output_stream.moon b/lib/howl/io/output_stream.moon index fe8a89e9a..60a8495ef 100644 --- a/lib/howl/io/output_stream.moon +++ b/lib/howl/io/output_stream.moon @@ -11,7 +11,7 @@ class OutputStream extends PropertyObject if ffi.os == 'Windows' @stream = Win32OutputStream ffi.C._get_osfhandle fd else - @stream = UnixOutputStream @stream + @stream = UnixOutputStream fd super! @property is_closed: get: => @stream.is_closed diff --git a/lib/ljglibs/spec/gio/subprocess_spec.moon b/lib/ljglibs/spec/gio/subprocess_spec.moon index 826968dae..bf7a0dc82 100644 --- a/lib/ljglibs/spec/gio/subprocess_spec.moon +++ b/lib/ljglibs/spec/gio/subprocess_spec.moon @@ -73,12 +73,13 @@ describe 'Subprocess', -> process\wait! assert.equal 9, process.term_sig - describe 'force_exit()', -> - it 'tries to terminate the process in some way', -> - process = Subprocess(Subprocess.FLAGS_STDIN_PIPE, 'cat') - process\force_exit! - process\wait! - assert.is_true process.if_exited + if jit.os != 'Windows' + describe 'force_exit()', -> + it 'tries to terminate the process in some way', -> + process = Subprocess(Subprocess.FLAGS_STDIN_PIPE, 'cat') + process\force_exit! + process\wait! + assert.is_true process.if_signaled describe 'wait_check()', -> it 'waits until the process is finished and returns true for a succesful termination', -> From 5647ae09092ffb296ecc9fbb1fe0116c19e2da8f Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 29 Sep 2016 11:25:28 -0500 Subject: [PATCH 55/75] Fixes --- lib/howl/io/process.moon | 2 +- lib/ljglibs/glib/spawn.moon | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/howl/io/process.moon b/lib/howl/io/process.moon index 2c1a79440..551fc4c55 100644 --- a/lib/howl/io/process.moon +++ b/lib/howl/io/process.moon @@ -90,7 +90,7 @@ pump_stream = (stream, handler, parking) -> local read_handler read_handler = (status, ret, err_code) -> if not status - dispatch.resume_with_error, parking, "#{ret} (#{err_code})" + dispatch.resume_with_error parking, "#{ret} (#{err_code})" else handler ret if ret == nil diff --git a/lib/ljglibs/glib/spawn.moon b/lib/ljglibs/glib/spawn.moon index 12b590b2b..260b50496 100644 --- a/lib/ljglibs/glib/spawn.moon +++ b/lib/ljglibs/glib/spawn.moon @@ -50,10 +50,12 @@ spawn = { stdin, stdout, stderr true_pid = pid[0] - pid = if ffi.os == 'Windows' - C.GetProcessId true_pid + local pid + if ffi.os == 'Windows' + pid = C.GetProcessId true_pid else - tonumber true_pid + true_pid = tonumber true_pid + pid = true_pid caller_will_reap = bit.band(flags['DO_NOT_REAP_CHILD'], spawn_flags) != 0 destructor = caller_will_reap and nil or ffi_gc ffi_new('struct {}'), -> From 277b25205860ff42d727a5c70d389ebfe3f22e4c Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 29 Sep 2016 11:32:43 -0500 Subject: [PATCH 56/75] Fixes --- lib/howl/io/process.moon | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/howl/io/process.moon b/lib/howl/io/process.moon index 551fc4c55..fa7a72fa6 100644 --- a/lib/howl/io/process.moon +++ b/lib/howl/io/process.moon @@ -218,9 +218,10 @@ class Process -- On Windows, read_async may never call the callback, so the dispatchers -- need to be resumed here instead. - if @stdout_done - pcall dispatch.resume, @stdout_done - @stdout_done = nil - if @stderr_done - pcall dispatch.resume, @stderr_done - @stderr_done = nil + if jit.os == 'Windows' + if @stdout_done + pcall dispatch.resume, @stdout_done + @stdout_done = nil + if @stderr_done + pcall dispatch.resume, @stderr_done + @stderr_done = nil From 41fccbe26709764c9eaf435f7135e1b3cccf0e71 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 29 Sep 2016 11:39:56 -0500 Subject: [PATCH 57/75] More spec fixes --- spec/io/process_spec.moon | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/spec/io/process_spec.moon b/spec/io/process_spec.moon index 1ada7314f..946dadf7d 100644 --- a/spec/io/process_spec.moon +++ b/spec/io/process_spec.moon @@ -33,6 +33,11 @@ describe 'Process', -> for proc in *procs proc\wait! + win_async = if jit.os == 'Windows' + proc_async + else + (f) -> f! + describe 'Process(opts)', -> it 'raises an error if opts.cmd is missing or invalid', -> assert.raises 'cmd', -> Process {} @@ -95,7 +100,11 @@ describe 'Process', -> it 'opts.env sets the process environment', (done) -> proc_async -> - out = Process.execute 'env', env: { foo: 'bar' } + cmd = if jit.os == 'Windows' + 'env' + else + {'env'} + out = Process.execute cmd, env: { foo: 'bar' } assert.equal 'foo=bar', out.stripped proc_done done @@ -148,7 +157,7 @@ describe 'Process', -> proc_done done context 'when handlers are not specified', -> - it 'collects and returns and output', -> + it 'collects and returns and output', (done) -> proc_async -> p = Process cmd: 'echo foo', read_stdout: true stdout, stderr = p\pump! @@ -165,6 +174,8 @@ describe 'Process', -> assert.equals 'out\n', stdout assert.equals 'err\n', stderr + proc_done done + describe 'wait()', -> it 'waits until the process is finished', (done) -> settimeout 2 @@ -219,7 +230,7 @@ describe 'Process', -> describe '.exit_status', -> it 'is nil for a running process', -> - proc_async -> + win_async -> p = Process cmd: { 'sh', '-c', "sleep 1; true" } assert.is_nil p.exit_status p\wait! From f48895d2142871e69a383b1e4e88abf926139bde Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 29 Sep 2016 11:41:24 -0500 Subject: [PATCH 58/75] More spec fixes --- spec/io/process_spec.moon | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/spec/io/process_spec.moon b/spec/io/process_spec.moon index 946dadf7d..688e4856e 100644 --- a/spec/io/process_spec.moon +++ b/spec/io/process_spec.moon @@ -33,11 +33,6 @@ describe 'Process', -> for proc in *procs proc\wait! - win_async = if jit.os == 'Windows' - proc_async - else - (f) -> f! - describe 'Process(opts)', -> it 'raises an error if opts.cmd is missing or invalid', -> assert.raises 'cmd', -> Process {} @@ -229,11 +224,13 @@ describe 'Process', -> proc_done done describe '.exit_status', -> - it 'is nil for a running process', -> - win_async -> + it 'is nil for a running process', (done) -> + settimeout 2 + proc_async -> p = Process cmd: { 'sh', '-c', "sleep 1; true" } assert.is_nil p.exit_status p\wait! + proc_done done it 'is nil for a signalled process', (done) -> proc_async -> From e1b48c93bd5c583b9f55f454d3631c63291e58b3 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 29 Sep 2016 12:06:22 -0500 Subject: [PATCH 59/75] Docs --- README.md | 2 ++ site/source/getit.md | 23 ++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7514f24f0..7933f33a9 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ how to install Howl from source. based system). - `C compiler`: Howl has a very small C core itself, and it embeds dependencies written in C. +- If you're using Windows, you *need* to build Howl inside of +[MSYS2](https://msys2.github.io/)'s MinGW32 shell. ### Build && install diff --git a/site/source/getit.md b/site/source/getit.md index 9dee129a9..dc71a9d24 100644 --- a/site/source/getit.md +++ b/site/source/getit.md @@ -5,9 +5,9 @@ title: Installation # Installing Howl Howl is developed on Linux, but it builds on other \*NIX platforms as well such -as FreeBSD (with other \*BSDs presumably requiring only little work). It should -be possible to port to OSX or Windows, should any brave soul be willing to put in -the work. +as FreeBSD (with other \*BSDs presumably requiring only little work), along with +Windows. It should be possible to port to OSX, should any brave soul be willing to +put in the work. You can install Howl by building it from source, either from a release or by cloning the repository from Github. If you're on ArchLinux you can install the @@ -47,6 +47,17 @@ dependencies). - `C compiler`: Howl has a very small C core itself, and it embedds a few dependencies built in C. +####Windows dependencies + +On Windows, you need to build Howl under [MSYS2](https://msys2.github.io/). To +install all the dependencies, you can open up the MSYS2 shell and run: + +```shell +pacman -S make tar git wget patch # utilities +pacman -S mingw32/mingw-w64-i686-gcc mingw32/mingw-w64-i686-pkg-config # toolchain +pacman -S mingw32/mingw-w64-i686-gtk3 # dependencies +``` + ### Building Download and unpack a Howl release, or get the source from @@ -86,6 +97,12 @@ make PREFIX=~/.local make PREFIX=~/.local install ``` +**If you are using Windows, make sure you run the build commands inside MSYS2's +MinGW32 shell, NOT the standard MSYS2 shell or the MinGW64 shell.** Using the +wrong shell may result in very bizarre build errors. If you find you began the +build in the wrong shell, make sure you run `make clean`, otherwise the build +may still fail. + *NB: If you install to a non-standard location, your desktop environment might not pick up on the fact that Howl is installed, and the application icon will look ugly as the result.* From 44c5029caace753172a5288f2a172c5ead4cda18 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 30 Sep 2016 11:22:43 -0500 Subject: [PATCH 60/75] Cleanup --- lib/howl/io/file.moon | 2 -- lib/howl/io/process.moon | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/howl/io/file.moon b/lib/howl/io/file.moon index 2e520f317..37d92bb2f 100644 --- a/lib/howl/io/file.moon +++ b/lib/howl/io/file.moon @@ -17,8 +17,6 @@ file_types = { [tonumber GFileInfo.TYPE_UNKNOWN]: 'unknown', } -local File - platform_tmpname = -> -- os.tmpname is broken on Windows and returns a filename prefixed with \. -- This causes a lot of "Access denied"-related errors. diff --git a/lib/howl/io/process.moon b/lib/howl/io/process.moon index fa7a72fa6..239248718 100644 --- a/lib/howl/io/process.moon +++ b/lib/howl/io/process.moon @@ -22,9 +22,7 @@ for s in *{ } signals[s] = tonumber C["sig_#{s}"] -win_signals = {} -win_signals[signals['KILL']] = 1 -win_signals[signals['INT']] = 1 +win_signals = win_signals = {signals[s], true for s in *{'KILL', 'INT'}} jit.off true, true @@ -53,7 +51,7 @@ get_command = (v, shell = default_shell) -> t = type v if t == 'string' - arg = if shell\find 'cmd' + arg = if shell\find 'cmd.exe' -- Likely cmd.exe. '/C' else @@ -95,7 +93,7 @@ pump_stream = (stream, handler, parking) -> handler ret if ret == nil stream\close! - pcall dispatch.resume, parking + dispatch.resume parking else stream\read_async nil, read_handler From b4a12a7eaebfa56404254a4cad809912d756d255 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 30 Sep 2016 11:25:53 -0500 Subject: [PATCH 61/75] Null termination --- lib/ljglibs/gobject/object.moon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ljglibs/gobject/object.moon b/lib/ljglibs/gobject/object.moon index 13e1bda80..6174be5d4 100644 --- a/lib/ljglibs/gobject/object.moon +++ b/lib/ljglibs/gobject/object.moon @@ -9,7 +9,7 @@ lua_value = types.lua_value core.define 'GObject', { new: (type, ...) -> error 'Undefined gtype passed in', 2 if type == 0 or type == nil - C.g_object_new type, ... + C.g_object_new type, ..., nil ref: (o) -> return nil if o == nil From d9f072d05087be533d53122cd3f69aaadb04c836 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 30 Sep 2016 11:27:05 -0500 Subject: [PATCH 62/75] Move g_usleep to cdefs --- lib/ljglibs/cdefs/glib.moon | 2 ++ spec/buffer_spec.moon | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ljglibs/cdefs/glib.moon b/lib/ljglibs/cdefs/glib.moon index e1834ad18..3dad29ab2 100644 --- a/lib/ljglibs/cdefs/glib.moon +++ b/lib/ljglibs/cdefs/glib.moon @@ -289,4 +289,6 @@ ffi.cdef [[ gint64 g_get_real_time (void); + void g_usleep(unsigned long ms); + ]] diff --git a/spec/buffer_spec.moon b/spec/buffer_spec.moon index 57146e2c3..c8926b11b 100644 --- a/spec/buffer_spec.moon +++ b/spec/buffer_spec.moon @@ -1,8 +1,6 @@ import Buffer, config, signal from howl import File from howl.io import with_tmpfile from File -ffi = require 'ffi' -ffi.cdef [[ void g_usleep(unsigned long ms); ]] describe 'Buffer', -> buffer = (text) -> From 537e96783b6eb48cd37bb5915767d3d21705fbce Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 30 Sep 2016 11:44:30 -0500 Subject: [PATCH 63/75] Fixes & more cleanup --- lib/howl/io/process.moon | 26 +++++++------------------- spec/buffer_spec.moon | 1 + 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/lib/howl/io/process.moon b/lib/howl/io/process.moon index 239248718..2905f07e5 100644 --- a/lib/howl/io/process.moon +++ b/lib/howl/io/process.moon @@ -22,7 +22,7 @@ for s in *{ } signals[s] = tonumber C["sig_#{s}"] -win_signals = win_signals = {signals[s], true for s in *{'KILL', 'INT'}} +win_signals = {signals[s], true for s in *{'KILL', 'INT'}} jit.off true, true @@ -173,15 +173,13 @@ class Process stderr = {} on_stderr = (err) -> stderr[#stderr + 1] = err - @stdout_done = on_stdout and dispatch.park "process-wait-stdout-#{@pid}" - @stderr_done = on_stderr and dispatch.park "process-wait-stderr-#{@pid}" - pump_stream(@stderr, on_stderr, @stderr_done, true) if on_stderr - pump_stream(@stdout, on_stdout, @stdout_done) if on_stdout + stdout_done = on_stdout and dispatch.park "process-wait-stdout-#{@pid}" + stderr_done = on_stderr and dispatch.park "process-wait-stderr-#{@pid}" + pump_stream(@stderr, on_stderr, stderr_done, true) if on_stderr + pump_stream(@stdout, on_stdout, stdout_done) if on_stdout - dispatch.wait(@stdout_done) if on_stdout - @stdout_done = nil - dispatch.wait(@stderr_done) if on_stderr - @stderr_done = nil + dispatch.wait(stdout_done) if on_stdout + dispatch.wait(stderr_done) if on_stderr @wait! stdout = stdout and table.concat(stdout) @@ -213,13 +211,3 @@ class Process if @_exit dispatch.resume(@_exit) @_exit = nil - - -- On Windows, read_async may never call the callback, so the dispatchers - -- need to be resumed here instead. - if jit.os == 'Windows' - if @stdout_done - pcall dispatch.resume, @stdout_done - @stdout_done = nil - if @stderr_done - pcall dispatch.resume, @stderr_done - @stderr_done = nil diff --git a/spec/buffer_spec.moon b/spec/buffer_spec.moon index c8926b11b..9747ebe80 100644 --- a/spec/buffer_spec.moon +++ b/spec/buffer_spec.moon @@ -1,6 +1,7 @@ import Buffer, config, signal from howl import File from howl.io import with_tmpfile from File +ffi = require 'ffi' describe 'Buffer', -> buffer = (text) -> From e800c94f76470a9a5f502c3bc8427b627429f58d Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 30 Sep 2016 13:41:18 -0500 Subject: [PATCH 64/75] Fix path to cmd.exe --- lib/howl/io/process.moon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/howl/io/process.moon b/lib/howl/io/process.moon index 2905f07e5..518510b9f 100644 --- a/lib/howl/io/process.moon +++ b/lib/howl/io/process.moon @@ -43,7 +43,7 @@ default_shell = if jit.os == 'Windows' -- Running under MSYS2. "#{howl.sys.env.WD}sh.exe" else - "#{howl.sys.env.SYSTEMROOT}/System32/cmd.exe" + "#{howl.sys.env.SYSTEMROOT}\\System32\\cmd.exe" else '/bin/sh' From 67afdb60cb79079c0dba4746117d126a109cfdf1 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 30 Sep 2016 13:46:19 -0500 Subject: [PATCH 65/75] Write font loading failure to stderr --- lib/howl/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/howl/init.lua b/lib/howl/init.lua index 3b82bb5ac..06446f986 100644 --- a/lib/howl/init.lua +++ b/lib/howl/init.lua @@ -176,8 +176,8 @@ local function main(args) for _, font in ipairs(fonts) do local loaded = ffi.C.AddFontResourceExA(font.path, ffi.C.fr_private, nil) if loaded == 0 then - print('failed to load font ' .. font.path) - io:flush() + io.stderr:write('failed to load font ', font.path, '\n') + io.stderr:flush() end end else From ff375948538570c209786875e7a75507536f43a2 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Sat, 5 Nov 2016 18:51:27 -0500 Subject: [PATCH 66/75] Cleanup --- lib/howl/io/process.moon | 2 -- lib/ljglibs/gio/win32input_stream.moon | 1 - 2 files changed, 3 deletions(-) diff --git a/lib/howl/io/process.moon b/lib/howl/io/process.moon index 518510b9f..6b4e24f6c 100644 --- a/lib/howl/io/process.moon +++ b/lib/howl/io/process.moon @@ -130,8 +130,6 @@ class Process @stdin = OutputStream(@_process.stdin_pipe) if @_process.stdin_pipe @stdout = InputStream(@_process.stdout_pipe) if @_process.stdout_pipe @stderr = InputStream(@_process.stderr_pipe, PRIORITY_LOW - 10) if @_process.stderr_pipe - @stdout_done = nil - @stderr_done = nil @exited = false @@running[@pid] = @ diff --git a/lib/ljglibs/gio/win32input_stream.moon b/lib/ljglibs/gio/win32input_stream.moon index 166f803fc..eb022a230 100644 --- a/lib/ljglibs/gio/win32input_stream.moon +++ b/lib/ljglibs/gio/win32input_stream.moon @@ -4,7 +4,6 @@ require 'ljglibs.gio.input_stream' ffi = require 'ffi' core = require 'ljglibs.core' -{:ref_ptr} = require 'ljglibs.gobject' C = ffi.C From 2536cd75aaa864a33fb071d7855cab6b6535a598 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Sat, 5 Nov 2016 19:30:01 -0500 Subject: [PATCH 67/75] Move some platform-specific code to howl.sys.platform --- lib/howl/init.lua | 15 +--------- lib/howl/io/file.moon | 13 ++------- lib/howl/io/input_stream.moon | 6 ++-- lib/howl/io/output_stream.moon | 6 ++-- lib/howl/io/process.moon | 23 ++------------- lib/howl/sys.moon | 52 ++++++++++++++++++++++++++++++++++ 6 files changed, 63 insertions(+), 52 deletions(-) diff --git a/lib/howl/init.lua b/lib/howl/init.lua index 06446f986..e17806a93 100644 --- a/lib/howl/init.lua +++ b/lib/howl/init.lua @@ -170,20 +170,7 @@ local function main(args) require('howl.globals') local font_dir = table.concat({app_root, 'fonts'}, path_separator) - if ffi.os == 'Windows' then - require 'howl.cdefs.windows' - local fonts = howl.io.File(font_dir).children - for _, font in ipairs(fonts) do - local loaded = ffi.C.AddFontResourceExA(font.path, ffi.C.fr_private, nil) - if loaded == 0 then - io.stderr:write('failed to load font ', font.path, '\n') - io.stderr:flush() - end - end - else - require 'howl.cdefs.fontconfig' - ffi.C.FcConfigAppFontAddDir(nil, font_dir) - end + require('howl.sys').platform.load_font_dir(font_dir) _G.log = require('howl.log') local args = parse_args(argv) diff --git a/lib/howl/io/file.moon b/lib/howl/io/file.moon index 37d92bb2f..df84547fc 100644 --- a/lib/howl/io/file.moon +++ b/lib/howl/io/file.moon @@ -5,6 +5,7 @@ GFile = require 'ljglibs.gio.file' GFileInfo = require 'ljglibs.gio.file_info' glib = require 'ljglibs.glib' import PropertyObject from howl.util.moon +import platform from howl.sys append = table.insert ffi = require 'ffi' @@ -17,23 +18,15 @@ file_types = { [tonumber GFileInfo.TYPE_UNKNOWN]: 'unknown', } -platform_tmpname = -> - -- os.tmpname is broken on Windows and returns a filename prefixed with \. - -- This causes a lot of "Access denied"-related errors. - filename = assert os.tmpname! - -- Remove the prefix \. - filename = filename\sub 2 if ffi.os == 'Windows' - filename - class File extends PropertyObject tmpfile: -> - file = File platform_tmpname! + file = File platform.tmpname! file\touch! if not file.exists file tmpdir: -> - with File platform_tmpname! + with File platform.tmpname! \delete! if .exists \mkdir! diff --git a/lib/howl/io/input_stream.moon b/lib/howl/io/input_stream.moon index 70f8156c8..0ea26da2a 100644 --- a/lib/howl/io/input_stream.moon +++ b/lib/howl/io/input_stream.moon @@ -5,16 +5,14 @@ dispatch = howl.dispatch glib = require 'ljglibs.glib' {:Win32InputStream, :UnixInputStream} = require 'ljglibs.gio' {:PropertyObject} = howl.util.moon +{:platform} = howl.sys append = table.insert ffi = require 'ffi' class InputStream extends PropertyObject new: (@stream, @priority = glib.PRIORITY_LOW) => if type(@stream) == 'number' - if ffi.os == 'Windows' - @stream = Win32InputStream ffi.C._get_osfhandle @stream - else - @stream = UnixInputStream @stream + @stream = platform.fd_to_stream Win32InputStream, UnixInputStream, @stream super! @property is_closed: get: => @stream.is_closed diff --git a/lib/howl/io/output_stream.moon b/lib/howl/io/output_stream.moon index 60a8495ef..f8c79dd22 100644 --- a/lib/howl/io/output_stream.moon +++ b/lib/howl/io/output_stream.moon @@ -4,14 +4,12 @@ dispatch = howl.dispatch {:Win32OutputStream, :UnixOutputStream} = require 'ljglibs.gio' {:PropertyObject} = howl.util.moon +{:platform} = howl.sys ffi = require 'ffi' class OutputStream extends PropertyObject new: (fd) => - if ffi.os == 'Windows' - @stream = Win32OutputStream ffi.C._get_osfhandle fd - else - @stream = UnixOutputStream fd + @stream = platform.fd_to_stream Win32OutputStream, UnixOutputStream, fd super! @property is_closed: get: => @stream.is_closed diff --git a/lib/howl/io/process.moon b/lib/howl/io/process.moon index 6b4e24f6c..bb5d9effd 100644 --- a/lib/howl/io/process.moon +++ b/lib/howl/io/process.moon @@ -7,6 +7,7 @@ jit = require 'jit' callbacks = require 'ljglibs.callbacks' dispatch = howl.dispatch {:File, :InputStream, :OutputStream} = howl.io +{:platform} = howl.sys C, ffi_cast = ffi.C, ffi.cast append = table.insert @@ -22,8 +23,6 @@ for s in *{ } signals[s] = tonumber C["sig_#{s}"] -win_signals = {signals[s], true for s in *{'KILL', 'INT'}} - jit.off true, true signal_name = (signal) -> @@ -38,16 +37,7 @@ shell_quote = (s) -> else s -default_shell = if jit.os == 'Windows' - if howl.sys.env.MSYSCON - -- Running under MSYS2. - "#{howl.sys.env.WD}sh.exe" - else - "#{howl.sys.env.SYSTEMROOT}\\System32\\cmd.exe" -else - '/bin/sh' - -get_command = (v, shell = default_shell) -> +get_command = (v, shell = platform.default_shell!) -> t = type v if t == 'string' @@ -144,14 +134,7 @@ class Process send_signal: (signal) => signal = signals[signal] if type(signal) == 'string' - if jit.os == 'Windows' - error "Signal #{signal} is not supported on Windows" unless win_signals[signal] - -- On Bash, when a process exits due to a signal, it's exit code is - -- 128+{signal code}. Since killing a process like that doesn't - -- necessarily work on Windows, this emulates that exit code. - C.TerminateProcess(@true_pid, 128+signal) - else - C.kill(@pid, signal) + platform.send_signal @true_pid, signal pump: (on_stdout, on_stderr) => if on_stdout and not @stdout diff --git a/lib/howl/sys.moon b/lib/howl/sys.moon index 00bb4099e..85219f9b9 100644 --- a/lib/howl/sys.moon +++ b/lib/howl/sys.moon @@ -2,6 +2,7 @@ -- License: MIT (see LICENSE.md at the top-level directory of the distribution) glib = require 'ljglibs.glib' +C = require('ffi').C env = setmetatable {}, { __index: (variable) => glib.getenv variable @@ -27,10 +28,61 @@ find_executable = (name) -> time = -> glib.get_real_time! / 1000000 +platform = {} + +platform.load_font_dir = (font_dir) -> + if jit.os == 'Windows' + require 'howl.cdefs.windows' + fonts = howl.io.File(font_dir).children + for font in *fonts + loaded = C.AddFontResourceExA font.path, C.fr_private, nil + if loaded == 0 + io.stderr\write "failed to load font #{font.path}\n" + io.stderr\flush! + else + require 'howl.cdefs.fontconfig' + C.FcConfigAppFontAddDir nil, font_dir + +platform.tmpname = -> + -- os.tmpname is broken on Windows and returns a filename prefixed with \. + -- This causes a lot of "Access denied"-related errors. + filename = assert os.tmpname! + -- Remove the prefix \. + filename = filename\sub 2 if jit.os == 'Windows' + filename + +platform.fd_to_stream = (win_type, unix_type, fd) -> + if jit.os == 'Windows' + win_type C._get_osfhandle fd + else + unix_type fd + +platform.default_shell = -> + if jit.os == 'Windows' + if howl.sys.env.MSYSCON + -- Running under MSYS2. + "#{env.WD}sh.exe" + else + "#{env.SYSTEMROOT}\\System32\\cmd.exe" + else + '/bin/sh' + +win_signals = {[tonumber C.sig_KILL]: true, [tonumber C.sig_INT]: true} +platform.send_signal = (pid, signal) -> + if jit.os == 'Windows' + error "Signal #{signal} is not supported on Windows" unless win_signals[signal] + -- On Bash, when a process exits due to a signal, it's exit code is + -- 128+{signal code}. Since killing a process like that doesn't + -- necessarily work on Windows, this emulates that exit code. + C.TerminateProcess(pid, 128+signal) + else + C.kill(pid, signal) + { :env, :find_executable :time, + :platform info: { os: jit.os\lower! } From 2dba2b26e3dddb4b19bb8c96e6c454248e7f2c22 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Sat, 5 Nov 2016 20:01:31 -0500 Subject: [PATCH 68/75] Linter fixes --- lib/howl/io/file.moon | 1 - lib/howl/io/input_stream.moon | 1 - lib/howl/io/output_stream.moon | 1 - 3 files changed, 3 deletions(-) diff --git a/lib/howl/io/file.moon b/lib/howl/io/file.moon index df84547fc..d74d49c14 100644 --- a/lib/howl/io/file.moon +++ b/lib/howl/io/file.moon @@ -7,7 +7,6 @@ glib = require 'ljglibs.glib' import PropertyObject from howl.util.moon import platform from howl.sys append = table.insert -ffi = require 'ffi' file_types = { [tonumber GFileInfo.TYPE_DIRECTORY]: 'directory', diff --git a/lib/howl/io/input_stream.moon b/lib/howl/io/input_stream.moon index 0ea26da2a..36d7c1773 100644 --- a/lib/howl/io/input_stream.moon +++ b/lib/howl/io/input_stream.moon @@ -7,7 +7,6 @@ glib = require 'ljglibs.glib' {:PropertyObject} = howl.util.moon {:platform} = howl.sys append = table.insert -ffi = require 'ffi' class InputStream extends PropertyObject new: (@stream, @priority = glib.PRIORITY_LOW) => diff --git a/lib/howl/io/output_stream.moon b/lib/howl/io/output_stream.moon index f8c79dd22..3ce9f2250 100644 --- a/lib/howl/io/output_stream.moon +++ b/lib/howl/io/output_stream.moon @@ -5,7 +5,6 @@ dispatch = howl.dispatch {:Win32OutputStream, :UnixOutputStream} = require 'ljglibs.gio' {:PropertyObject} = howl.util.moon {:platform} = howl.sys -ffi = require 'ffi' class OutputStream extends PropertyObject new: (fd) => From 6b4ae52aceaa77ae0e0e7883d051311149293e89 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Sat, 5 Nov 2016 20:34:41 -0500 Subject: [PATCH 69/75] Oops --- lib/ljglibs/gio/win32output_stream.moon | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ljglibs/gio/win32output_stream.moon b/lib/ljglibs/gio/win32output_stream.moon index 56b796d5a..a75f5d90e 100644 --- a/lib/ljglibs/gio/win32output_stream.moon +++ b/lib/ljglibs/gio/win32output_stream.moon @@ -4,7 +4,6 @@ require 'ljglibs.gio.output_stream' ffi = require 'ffi' core = require 'ljglibs.core' -{:ref_ptr} = require 'ljglibs.gobject' C = ffi.C From 81186cbcff5b7de000c5e2ea2ae8487341a3cf02 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 28 Apr 2017 14:40:27 -0500 Subject: [PATCH 70/75] boring stuffz --- bundles/git/spec/git_spec.moon | 24 +++---- lib/ljglibs/glib/spawn.moon | 34 +++++++-- lint_config.moon | 2 - spec/io/process_spec.moon | 124 ++++++++++++++++----------------- spec/support/spec_helper.moon | 12 ---- 5 files changed, 102 insertions(+), 94 deletions(-) diff --git a/bundles/git/spec/git_spec.moon b/bundles/git/spec/git_spec.moon index 7836ed563..49ebf730f 100644 --- a/bundles/git/spec/git_spec.moon +++ b/bundles/git/spec/git_spec.moon @@ -56,7 +56,7 @@ describe 'Git bundle', -> assert.same list1, list2 it 'returns a list of git files, including untracked', (done) -> - proc_async -> + howl_async -> assert_same_files git\files!, {} file = root / 'new.lua' file\touch! @@ -71,7 +71,7 @@ describe 'Git bundle', -> file2 = root / 'another.lua' file2\touch! assert_same_files git\files!, { file2, file } - proc_done done + done! describe 'diff([file])', -> local file @@ -83,37 +83,37 @@ describe 'Git bundle', -> os.execute "cd #{root} && git commit -q -m 'rev1' #{file}" it 'returns nil if has not changed', (done) -> - proc_async -> + howl_async -> assert.is_nil git\diff file - proc_done done + done! it 'returns a string containing the diff if has changed', (done) -> - proc_async -> + howl_async -> file.contents ..= 'line 2\n' diff = git\diff file assert.includes diff, file.basename assert.includes diff, '+line 2' - proc_done done + done! it 'returns a diff for the entire directory if file is not specified', (done) -> - proc_async -> + howl_async -> file.contents ..= 'line 2\n' diff = git\diff! assert.includes diff, file.basename assert.includes diff, '+line 2' - proc_done done + done! describe 'run(...)', -> it 'runs git in the root dir with the given arguments and returns the output', (done) -> - proc_async -> + howl_async -> assert.includes git\run('config', '--local', '-l'), "Howl Spec" - proc_done done + done! it 'uses the executable in variable `git_path` if specified', (done) -> - proc_async -> + howl_async -> config.git_path = echo status, out = pcall git.run, git, 'using echo' config.git_path = nil assert status, out assert.includes out, "using echo" - proc_done done + done! diff --git a/lib/ljglibs/glib/spawn.moon b/lib/ljglibs/glib/spawn.moon index 260b50496..d1ed09eec 100644 --- a/lib/ljglibs/glib/spawn.moon +++ b/lib/ljglibs/glib/spawn.moon @@ -42,12 +42,34 @@ spawn = { stdout = opts.read_stdout and ffi_new('gint[1]') or nil stderr = opts.read_stderr and ffi_new('gint[1]') or nil - catch_error C.g_spawn_async_with_pipes, - opts.working_directory, - argv, envp, spawn_flags, - nil, nil, - pid, - stdin, stdout, stderr + -- XXX: This is a nasty hack!! + -- On Windows, for reasons unknown, g_spawn_async_with_pipes will randomly + -- fail with an EOF error (?). In order to work around that, on Windows, + -- the spawn will be attempted three times first. + + limit = if jit.os == 'Windows' + 3 + else + 1 + + for i=1,limit + status, err = pcall catch_error, C.g_spawn_async_with_pipes, + opts.working_directory, + argv, envp, spawn_flags, + nil, nil, + pid, + stdin, stdout, stderr + + if status + break + if jit.os == 'Windows' and err\match 'Failed to read from child pipe %(EOF%)' + _G.print i, limit + _G.io.flush! + if i < limit + -- Try sleeping for 1/10th second. + C.g_usleep 100000 + continue + error err true_pid = pid[0] local pid diff --git a/lint_config.moon b/lint_config.moon index 09d3bfc10..446b48e05 100644 --- a/lint_config.moon +++ b/lint_config.moon @@ -184,8 +184,6 @@ 'it', 'make_hidden', 'moon', - 'proc_async', - 'proc_done', 'pump_mainloop', 'set_howl_loop', 'settimeout', diff --git a/spec/io/process_spec.moon b/spec/io/process_spec.moon index 688e4856e..49cd39a47 100644 --- a/spec/io/process_spec.moon +++ b/spec/io/process_spec.moon @@ -63,83 +63,83 @@ describe 'Process', -> describe 'Process.execute(cmd, opts)', -> it 'executes the specified command and return ', (done) -> - proc_async -> + howl_async -> out, err, p = Process.execute {sh, '-c', 'cat; echo foo >&2'}, stdin: 'reverb' assert.equal 'reverb', out assert.equal 'foo\n', err assert.equal 'Process', typeof(p) - proc_done done + done! it "executes string commands using /bin/sh by default", (done) -> - proc_async -> + howl_async -> status, out = pcall Process.execute, 'echo $0' assert.is_true status expected = fix_paths sh assert.equal "#{expected}\n", out - proc_done done + done! it "allows specifying a different shell", (done) -> - proc_async -> + howl_async -> status, out, _, process = pcall Process.execute, 'blargh', shell: echo assert.is_true status assert.match out, 'blargh' assert.equal 'blargh', process.command_line - proc_done done + done! it 'opts.working_directory sets the working working directory', (done) -> - proc_async -> + howl_async -> with_tmpdir (dir) -> out = Process.execute 'pwd', working_directory: dir assert.equal dir.path, out.stripped - proc_done done + done! it 'opts.env sets the process environment', (done) -> - proc_async -> + howl_async -> cmd = if jit.os == 'Windows' 'env' else {'env'} out = Process.execute cmd, env: { foo: 'bar' } assert.equal 'foo=bar', out.stripped - proc_done done + done! it 'works with large process outputs', (done) -> - proc_async -> + howl_async -> File.with_tmpfile (f) -> file_contents = string.rep "xxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyy zzzzzzzzzzzzzzzzzzz\n", 5000 f.contents = file_contents status, out = pcall Process.execute, "cat #{f.path}" assert.is_true status assert.equal file_contents, out - proc_done done + done! describe 'pump(on_stdout, on_stderr)', -> context 'when the handler is provided', -> it 'invokes the handler for any stdout output before returning', (done) -> - proc_async -> + howl_async -> on_stdout = spy.new -> nil p = Process cmd: 'echo foo', read_stdout: true p\pump on_stdout assert.is_true p.exited assert.spy(on_stdout).was_called_with 'foo\n' assert.spy(on_stdout).was_called_with nil - proc_done done + done! context 'when the handler is provided', -> it 'invokes the handler for any stderr output before returning', (done) -> - proc_async -> + howl_async -> on_stderr = spy.new -> nil p = Process cmd: 'echo err >&2', read_stderr: true p\pump nil, on_stderr assert.is_true p.exited assert.spy(on_stderr).was_called_with 'err\n' assert.spy(on_stderr).was_called_with nil - proc_done done + done! context 'when both handlers are provided', -> it 'invokes both handlers for any output before returning', (done) -> - proc_async -> + howl_async -> on_stdout = spy.new -> nil on_stderr = spy.new -> nil p = Process cmd: 'echo out; echo err >&2', read_stdout: true, read_stderr: true @@ -149,11 +149,11 @@ describe 'Process', -> assert.spy(on_stdout).was_called_with nil assert.spy(on_stderr).was_called_with 'err\n' assert.spy(on_stderr).was_called_with nil - proc_done done + done! context 'when handlers are not specified', -> it 'collects and returns and output', (done) -> - proc_async -> + howl_async -> p = Process cmd: 'echo foo', read_stdout: true stdout, stderr = p\pump! assert.equals 'foo\n', stdout @@ -169,79 +169,79 @@ describe 'Process', -> assert.equals 'out\n', stdout assert.equals 'err\n', stderr - proc_done done + done! describe 'wait()', -> it 'waits until the process is finished', (done) -> settimeout 2 - proc_async -> + howl_async -> File.with_tmpfile (file) -> file\delete! p = Process cmd: { 'sh', '-c', "sleep 1; touch '#{file.path}'" } p\wait! assert.is_true file.exists - proc_done done + done! context 'signal handling', -> describe 'send_signal(signal) and .signalled', -> it 'sends the specified signal to the process', (done) -> - proc_async -> + howl_async -> p = Process cmd: 'cat', write_stdin: true p\send_signal 9 p\wait! assert.is_true p.signalled - proc_done done + done! it '.signalled is false for a non-signaled process', (done) -> - proc_async -> + howl_async -> p = Process cmd: 'id' p\wait! assert.is_false p.signalled - proc_done done + done! it '.signal holds the signal used for terminating the process', (done) -> - proc_async -> + howl_async -> p = Process cmd: 'cat', write_stdin: true p\send_signal 9 p\wait! assert.equals 9, p.signal - proc_done done + done! it '.signal_name holds the name of the signal used for terminating the process', (done) -> - proc_async -> + howl_async -> p = Process cmd: 'cat', write_stdin: true p\send_signal 9 p\wait! assert.equals 'KILL', p.signal_name - proc_done done + done! it 'signals can be referred to by name as well', (done) -> - proc_async -> + howl_async -> p = Process cmd: 'cat', write_stdin: true p\send_signal 'KILL' p\wait! assert.equals 9, p.signal - proc_done done + done! describe '.exit_status', -> it 'is nil for a running process', (done) -> settimeout 2 - proc_async -> + howl_async -> p = Process cmd: { 'sh', '-c', "sleep 1; true" } assert.is_nil p.exit_status p\wait! - proc_done done + done! it 'is nil for a signalled process', (done) -> - proc_async -> + howl_async -> p = Process cmd: 'cat', write_stdin: true p\send_signal 9 p\wait! assert.is_nil p.exit_status - proc_done done + done! it 'is set to the exit status for a normally exited process', (done) -> - proc_async -> + howl_async -> p = run 'echo foo' assert.equals 0, p.exit_status @@ -251,7 +251,7 @@ describe 'Process', -> p = run {'sh', '-c', 'exit 2' } assert.equals 2, p.exit_status - proc_done done + done! describe '.working_directory', -> context 'when provided during launch', -> @@ -276,41 +276,41 @@ describe 'Process', -> describe '.successful', -> it 'is true if the process exited cleanly with a zero exit code', (done) -> - proc_async -> + howl_async -> assert.is_true run('id').successful - proc_done done + done! it 'is false if the process exited with a non-zero exit code', (done) -> - proc_async -> + howl_async -> assert.is_false run('false').successful - proc_done done + done! it 'is false if the process exited due to a signal', (done) -> - proc_async -> + howl_async -> p = Process cmd: 'cat', write_stdin: true p\send_signal 9 p\wait! assert.is_false p.successful - proc_done done + done! describe '.stdout', -> it 'allows reading process output', (done) -> - proc_async -> + howl_async -> p = collected_process cmd: {'echo', 'one\ntwo'}, read_stdout: true assert.equals 'one\ntwo\n', p.stdout\read! assert.is_nil p.stdout\read! - proc_done done + done! describe '.stderr', -> it 'allows reading process error output', (done) -> - proc_async -> + howl_async -> p = collected_process cmd: {'sh', '-c', 'echo foo >&2'}, read_stderr: true assert.equals 'foo\n', p.stderr\read! - proc_done done + done! describe '.stdin', -> it 'allows writing to the process input', (done) -> - proc_async -> + howl_async -> p = Process cmd: {'cat'}, write_stdin: true, read_stdout: true with p.stdin \write 'round-trip' @@ -318,39 +318,39 @@ describe 'Process', -> assert.equals 'round-trip', p.stdout\read! p\wait! - proc_done done + done! describe '.command_line', -> context 'when the command is specified as a string', -> it 'is the same', (done) -> - proc_async -> + howl_async -> assert.equal 'echo command "bar"', run('echo command "bar"').command_line - proc_done done + done! context 'when the command is specified as a table', -> it 'is a created shell command line', (done) -> - proc_async -> + howl_async -> assert.equal "echo command 'bar zed'", run({'echo', 'command', 'bar zed'}).command_line - proc_done done + done! describe '.exit_status_string', -> it 'provides the exit code for a normally terminated process', (done) -> - proc_async -> + howl_async -> assert.equals 'exited normally with code 0', run('id').exit_status_string assert.equals 'exited normally with code 1', run('exit 1').exit_status_string - proc_done done + done! it 'provides the signal name for a killed process', (done) -> - proc_async -> + howl_async -> p = Process cmd: {'cat'}, write_stdin: true, read_stdout: true p\send_signal 'KILL' p\wait! assert.equals 'killed by signal 9 (KILL)', p.exit_status_string - proc_done done + done! describe 'Process.running', -> it 'is a table of currently running processes, keyed by pid', (done) -> - proc_async -> + howl_async -> collect! assert.same {}, Process.running p = Process cmd: {'cat'}, write_stdin: true @@ -358,12 +358,12 @@ describe 'Process', -> p.stdin\close! p\wait! assert.same {}, Process.running - proc_done done + done! context 'resource management', -> it 'processes are collected correctly', (done) -> - proc_async -> + howl_async -> p = Process cmd: {'echo', 'one\ntwo'}, read_stdout: true assert.equals 'one\ntwo\n', p.stdout\read! p\wait! @@ -373,4 +373,4 @@ describe 'Process', -> p = nil collect_memory! assert.is_nil list[1] - proc_done done + done! diff --git a/spec/support/spec_helper.moon b/spec/support/spec_helper.moon index ba3a92b06..6f4640d20 100644 --- a/spec/support/spec_helper.moon +++ b/spec/support/spec_helper.moon @@ -100,18 +100,6 @@ export howl_async = (f) -> status, err = coroutine.resume co error err unless status -export proc_async = (f) -> - local co - if jit.os == 'Windows' - co = coroutine.create f - else - set_howl_loop! - co = coroutine.create busted.async(f) - status, err = coroutine.resume co - error err unless status - -export proc_done = (done) -> done! unless jit.os == 'Windows' - export pump_mainloop = -> jit.off true, true count = 0 From 67bf2fd215b669d5794c4806c8c26e95078ea51a Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 28 Apr 2017 14:56:26 -0500 Subject: [PATCH 71/75] Add 'make windist' --- src/Makefile | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index 27f3d01bb..54d01d8b7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -13,6 +13,12 @@ ifdef WIN32 endif GTK_LIBS = $(shell pkg-config --libs $(GTK_LIBNAMES)) +ifdef WIN32 + HOWL_EXE=howl.exe +else + HOWL_EXE=howl +endif + LUAJIT_VER = LuaJIT-2.1.0-beta1 LUAJIT_CHECKSUM = 5a5bf71666e77cf6e7a1ae851127b834 LUAJIT = deps/${LUAJIT_VER} @@ -52,9 +58,11 @@ endif OBJECTS = main.o process_helpers.o DEP_OBJECTS = $(LPEG_OBJECT) -all: howl bytecode +.PHONY: all bytecode deps-download deps-purge deps-clean clean install uninstall windist + +all: ${HOWL_EXE} bytecode -howl: ${OBJECTS} main.h $(ARCHIVES) $(DEP_OBJECTS) Makefile +${HOWL_EXE}: ${OBJECTS} main.h $(ARCHIVES) $(DEP_OBJECTS) Makefile ${CC} -o howl ${OBJECTS} $(DEP_OBJECTS) ${ARCHIVES} ${LIBS} ${LD_FLAGS} ${OBJECTS}: %.o : %.c main.h $(LUAJIT) @@ -112,3 +120,18 @@ uninstall: @rm -v $(DESTDIR)$(PREFIX)/share/applications/howl.desktop @rm -v $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/howl.svg @echo All done. + +windist: + rm -rf windist + mkdir -p windist + mkdir -p windist/lib + + $(MAKE) install PREFIX=windist + for dll in `ldd howl.exe | grep mingw32 | cut -d= -f1 | cut -f1`; do \ + cp -v /mingw32/bin/$dll windist/bin; \ + done + rm windist/bin/howl-spec + cp -v /mingw32/bin/librsvg*.dll /mingw32/bin/gspawn-win32-helper* windist/bin + cp -v howl.exe windist/bin + cp -rv /mingw32/lib/gdk-pixbuf-2.0 windist/lib + zip -r windist/howl.zip windist/bin windist/lib windist/share From c1e4c985d4cc5fdf51b43bce1e40676ae51b1d6c Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 28 Apr 2017 15:24:08 -0500 Subject: [PATCH 72/75] Rename ljglibs.aux -> ljglibs.util --- lib/aullar/gutter.moon | 2 +- lib/howl/ui/content_box.moon | 2 +- lib/howl/ui/window.moon | 2 +- lib/ljglibs/{aux => util}/background.moon | 0 src/Makefile | 17 +++++++++++++++-- 5 files changed, 18 insertions(+), 5 deletions(-) rename lib/ljglibs/{aux => util}/background.moon (100%) diff --git a/lib/aullar/gutter.moon b/lib/aullar/gutter.moon index 31b557c05..f9b74e2de 100644 --- a/lib/aullar/gutter.moon +++ b/lib/aullar/gutter.moon @@ -5,7 +5,7 @@ {:define_class} = require 'aullar.util' Pango = require 'ljglibs.pango' {:RGBA} = require 'ljglibs.gdk' -Background = require 'ljglibs.aux.background' +Background = require 'ljglibs.util.background' Layout = Pango.Layout pango_cairo = Pango.cairo diff --git a/lib/howl/ui/content_box.moon b/lib/howl/ui/content_box.moon index 52b114292..b62280e97 100644 --- a/lib/howl/ui/content_box.moon +++ b/lib/howl/ui/content_box.moon @@ -4,7 +4,7 @@ Gdk = require 'ljglibs.gdk' Gtk = require 'ljglibs.gtk' gobject_signal = require 'ljglibs.gobject.signal' -Background = require 'ljglibs.aux.background' +Background = require 'ljglibs.util.background' ffi = require 'ffi' {:signal} = howl diff --git a/lib/howl/ui/window.moon b/lib/howl/ui/window.moon index 3e8f284da..6e74d08f8 100644 --- a/lib/howl/ui/window.moon +++ b/lib/howl/ui/window.moon @@ -5,7 +5,7 @@ Gdk = require 'ljglibs.gdk' Gtk = require 'ljglibs.gtk' ffi = require 'ffi' gobject_signal = require 'ljglibs.gobject.signal' -Background = require 'ljglibs.aux.background' +Background = require 'ljglibs.util.background' import PropertyObject from howl.util.moon {:CommandLine, :Status, :theme} = howl.ui import signal from howl diff --git a/lib/ljglibs/aux/background.moon b/lib/ljglibs/util/background.moon similarity index 100% rename from lib/ljglibs/aux/background.moon rename to lib/ljglibs/util/background.moon diff --git a/src/Makefile b/src/Makefile index 54d01d8b7..fb790995f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -38,6 +38,12 @@ LPEG = deps/$(LPEG_VER) LPEG_OBJECT = $(LPEG)/lpeg.o LPEG_URL = http://nordman.org/mirror/lpeg/$(LPEG_VER).tar.gz +MODPATH_ISS = deps/modpath.iss +MODPATH_ISS_CHECKSUM = a39bb32a15e0f4b90b38933e6ceb6a78 +MODPATH_ISS_URL = https://www.legroom.net/files/software/modpath.iss + +ISCC=iscc + CFLAGS = -Wall -O2 -g $(LUAJIT_CFLAGS) $(GTK_CFLAGS) -DHOWL_PREFIX=$(PREFIX) ARCHIVES = $(LUAJIT_ARCHIVE) LIBS = -lm -ldl ${GTK_LIBS} -lstdc++ @@ -58,7 +64,7 @@ endif OBJECTS = main.o process_helpers.o DEP_OBJECTS = $(LPEG_OBJECT) -.PHONY: all bytecode deps-download deps-purge deps-clean clean install uninstall windist +.PHONY: all bytecode deps-download deps-purge deps-clean clean install uninstall windist wininstall all: ${HOWL_EXE} bytecode @@ -82,10 +88,13 @@ $(LUAJIT): $(LUAJIT_ARCHIVE): $(LUAJIT) cd ${LUAJIT} && $(MAKE) XCFLAGS="-DLUAJIT_ENABLE_LUA52COMPAT" +$(MODPATH_ISS): + @tools/download $(MODPATH_ISS_URL) $(MODPATH_ISS_CHECKSUM) cp {file} $(MODPATH_ISS) + deps-download: $(LUAJIT) $(LPEG) deps-purge: - rm -rf $(LUAJIT) $(LPEG) + rm -rf $(LUAJIT) $(LPEG) $(MODPATH_ISS) deps-clean: @rm $(LPEG_OBJECT) || true @@ -97,6 +106,7 @@ clean: bytecode: howl -@find ../lib ../bundles -name '*.bc' | xargs rm @find ../lib ../bundles -name '*.lua' -o -name '*.moon' | xargs ./howl --compile + @rm -rf windist wininst install: all @echo Installing to $(DESTDIR)$(PREFIX).. @@ -135,3 +145,6 @@ windist: cp -v howl.exe windist/bin cp -rv /mingw32/lib/gdk-pixbuf-2.0 windist/lib zip -r windist/howl.zip windist/bin windist/lib windist/share + +wininst: $(MODPATH_ISS) windist + $(ISCC) wininst.iss From decaa9a925c6f01accfc74e59d65380d3d3afba4 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 28 Apr 2017 17:18:33 -0500 Subject: [PATCH 73/75] Installer stuff --- .gitignore | 6 +++++ src/Makefile | 45 ++++++++++++++++++++++++----------- src/howl.rc | 1 + src/wininst.iss | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ win.md | 5 ++++ 5 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 src/howl.rc create mode 100644 src/wininst.iss create mode 100644 win.md diff --git a/.gitignore b/.gitignore index 3f56ef30c..e5a73f925 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,13 @@ +src/deps/* src/deps/*/* src/*.o src/howl src/howl.exe +src/howl.ico +src/howl.png +src/howl.res +src/windist +src/wininst *.bc *.bak site/build/ diff --git a/src/Makefile b/src/Makefile index fb790995f..af6495323 100644 --- a/src/Makefile +++ b/src/Makefile @@ -43,6 +43,7 @@ MODPATH_ISS_CHECKSUM = a39bb32a15e0f4b90b38933e6ceb6a78 MODPATH_ISS_URL = https://www.legroom.net/files/software/modpath.iss ISCC=iscc +CONVERT=convert CFLAGS = -Wall -O2 -g $(LUAJIT_CFLAGS) $(GTK_CFLAGS) -DHOWL_PREFIX=$(PREFIX) ARCHIVES = $(LUAJIT_ARCHIVE) @@ -58,22 +59,38 @@ endif ifdef WIN32 # MSYS sets CC to cc, even though it doesn't actually exist. CC=gcc + CFLAGS += -mwindows LIBS = -lm ${GTK_LIBS} -lstdc++ -lkernel32 -lgdi32 - LD_FLAGS = -Wl,--export-all + LD_FLAGS = -mwindows -Wl,--export-all + ifdef MAKE_RC + WINRES=howl.res + endif endif OBJECTS = main.o process_helpers.o DEP_OBJECTS = $(LPEG_OBJECT) +ICON=howl.ico + .PHONY: all bytecode deps-download deps-purge deps-clean clean install uninstall windist wininstall all: ${HOWL_EXE} bytecode -${HOWL_EXE}: ${OBJECTS} main.h $(ARCHIVES) $(DEP_OBJECTS) Makefile - ${CC} -o howl ${OBJECTS} $(DEP_OBJECTS) ${ARCHIVES} ${LIBS} ${LD_FLAGS} +${HOWL_EXE}: ${OBJECTS} main.h $(ARCHIVES) $(DEP_OBJECTS) Makefile $(WINRES) + ${CC} -o howl ${OBJECTS} $(DEP_OBJECTS) ${WINRES} ${ARCHIVES} ${LIBS} ${LD_FLAGS} ${OBJECTS}: %.o : %.c main.h $(LUAJIT) ${CC} -c $< ${CFLAGS} +${WINRES}: howl.rc ${ICON} + windres $< -O coff -o $@ + +# Doing the full conversion using ImageMagick gives nasty results. So, RSVG-Convert +# is used first, *then* ImageMagick. + +${ICON}: ../share/icons/hicolor/scalable/apps/howl.svg + RSVG-Convert $< -d 300 -p 300 -o howl.png + ${CONVERT} -background transparent howl.png -define icon:auto-resize=128,64,48,32,16 $@ + $(LPEG): @tools/download $(LPEG_URL) $(LPEG_CHECKSUM) tar xzf {file} -C deps @@ -132,19 +149,19 @@ uninstall: @echo All done. windist: - rm -rf windist - mkdir -p windist - mkdir -p windist/lib + @rm -rf windist + @mkdir -p windist - $(MAKE) install PREFIX=windist - for dll in `ldd howl.exe | grep mingw32 | cut -d= -f1 | cut -f1`; do \ - cp -v /mingw32/bin/$dll windist/bin; \ + @$(MAKE) install PREFIX=windist MAKE_RC=1 + @for dll in `ldd howl.exe | grep mingw32 | cut -d= -f1 | cut -f2`; do \ + cp -v /mingw32/bin/$$dll windist/bin; \ done - rm windist/bin/howl-spec - cp -v /mingw32/bin/librsvg*.dll /mingw32/bin/gspawn-win32-helper* windist/bin - cp -v howl.exe windist/bin - cp -rv /mingw32/lib/gdk-pixbuf-2.0 windist/lib - zip -r windist/howl.zip windist/bin windist/lib windist/share + @rm windist/bin/howl-spec + @cp -v /mingw32/bin/librsvg*.dll /mingw32/bin/gspawn-win32-helper* windist/bin + @cp -v howl.exe windist/bin + @mkdir -p windist/lib + @cp -rv /mingw32/lib/gdk-pixbuf-2.0 windist/lib + @zip -r windist/howl.zip windist/bin windist/lib windist/share wininst: $(MODPATH_ISS) windist $(ISCC) wininst.iss diff --git a/src/howl.rc b/src/howl.rc new file mode 100644 index 000000000..a67d2e9d6 --- /dev/null +++ b/src/howl.rc @@ -0,0 +1 @@ +id ICON "howl.ico" diff --git a/src/wininst.iss b/src/wininst.iss new file mode 100644 index 000000000..97970d62d --- /dev/null +++ b/src/wininst.iss @@ -0,0 +1,62 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define MyAppName "Howl" +#define MyAppVersion "0.4.1" +#define MyAppPublisher "The Howl Developers" +#define MyAppURL "http://howl.io/" +#define MyAppExeName "howl.exe" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. +; Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{F15DBA31-AA25-4F0F-83F4-D13588984724} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={pf}\{#MyAppName} +DisableWelcomePage=no +DisableProgramGroupPage=yes +DefaultGroupName=Howl +LicenseFile=../LICENSE.md +OutputDir=wininst +OutputBaseFilename=howl-setup +Compression=lzma +SolidCompression=yes +SetupIconFile=howl.ico + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +Source: "windist\bin\*"; DestDir: "{app}\bin"; Flags: recursesubdirs ignoreversion +Source: "windist\lib\*"; DestDir: "{app}\lib"; Flags: recursesubdirs ignoreversion +Source: "windist\share\*"; DestDir: "{app}\share"; Flags: recursesubdirs ignoreversion +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{commonprograms}\{#MyAppName}"; Filename: "{app}\bin\{#MyAppExeName}" +Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\bin\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{app}\bin\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent + +[Code] +const + ModPathName = 'modifypath'; + ModPathType = 'system'; + +function ModPathDir(): TArrayOfString; +begin + setArrayLength(Result, 1) + Result[0] := ExpandConstant('{app}\host\bin'); +end; +#include "deps/modpath.iss" diff --git a/win.md b/win.md new file mode 100644 index 000000000..6f7e4cb7a --- /dev/null +++ b/win.md @@ -0,0 +1,5 @@ +#WIP + +mingw32/mingw-w64-i686-imagemagick + +version change: howl.rc, wininst.iss From 21f574243e80e56179213639f142349c38c9c989 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 28 Apr 2017 17:30:11 -0500 Subject: [PATCH 74/75] *facepalm --- src/wininst.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wininst.iss b/src/wininst.iss index 97970d62d..d4373a05a 100644 --- a/src/wininst.iss +++ b/src/wininst.iss @@ -57,6 +57,6 @@ const function ModPathDir(): TArrayOfString; begin setArrayLength(Result, 1) - Result[0] := ExpandConstant('{app}\host\bin'); + Result[0] := ExpandConstant('{app}\bin'); end; #include "deps/modpath.iss" From f94c148385e98e71b8354da2c07788fecab2b906 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Fri, 28 Apr 2017 17:42:25 -0500 Subject: [PATCH 75/75] Windows docs --- site/source/getit.md | 21 +++--------- site/source/getit.windows.md | 65 ++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 site/source/getit.windows.md diff --git a/site/source/getit.md b/site/source/getit.md index dc71a9d24..e2f815a9b 100644 --- a/site/source/getit.md +++ b/site/source/getit.md @@ -27,6 +27,10 @@ _SHA1_: 557fea5af8e6768ea6408ab2d11db63c0ae5fdf4 __Release notes:__ [Howl 0.4 Released](/blog/2016/05/31/howl-0-4-released.html) +## Building or installing Howl on Windows + +See [the Windows-specific documentation](/getit.windows.html). + ## Building Howl from source ### Build requirements @@ -47,17 +51,6 @@ dependencies). - `C compiler`: Howl has a very small C core itself, and it embedds a few dependencies built in C. -####Windows dependencies - -On Windows, you need to build Howl under [MSYS2](https://msys2.github.io/). To -install all the dependencies, you can open up the MSYS2 shell and run: - -```shell -pacman -S make tar git wget patch # utilities -pacman -S mingw32/mingw-w64-i686-gcc mingw32/mingw-w64-i686-pkg-config # toolchain -pacman -S mingw32/mingw-w64-i686-gtk3 # dependencies -``` - ### Building Download and unpack a Howl release, or get the source from @@ -97,12 +90,6 @@ make PREFIX=~/.local make PREFIX=~/.local install ``` -**If you are using Windows, make sure you run the build commands inside MSYS2's -MinGW32 shell, NOT the standard MSYS2 shell or the MinGW64 shell.** Using the -wrong shell may result in very bizarre build errors. If you find you began the -build in the wrong shell, make sure you run `make clean`, otherwise the build -may still fail. - *NB: If you install to a non-standard location, your desktop environment might not pick up on the fact that Howl is installed, and the application icon will look ugly as the result.* diff --git a/site/source/getit.windows.md b/site/source/getit.windows.md new file mode 100644 index 000000000..76c97fec1 --- /dev/null +++ b/site/source/getit.windows.md @@ -0,0 +1,65 @@ +--- +title: Building and installing Howl on Windows +--- + +# Using the binary distributions + +*TODO* + +# Building from source + + +## Setting up the build environment + +First of all, you need [MSYS2](http://www.msys2.org/). Once that's been +installed, open up the MSYS2 shell and run: + +``` +pacman -S make tar git wget patch +pacman -S mingw32/mingw-w64-i686-gcc mingw32/mingw-w64-i686-pkg-config +pacman -S mingw32/mingw-w64-i686-gtk3 mingw32/mingw-w64-i686-imagemagick +``` + +You can omit ImageMagick if desired, but then the Howl binary won't have the +icon files embedded inside. + +## Building + +**Open the MinGW32 shell. If you stay in the normal MSYS Shell, the build +will fail with obscure, downright weird errors.** If you accidentally began +building in the MSYS Shell, make sure you run `make clean` before continuing! + +Run: + +``` +make MAKE_RC=1 +``` + +to build with the icons embedded or plain: + +``` +make +``` + +to build without them. + +## Using Howl outside MSYS2 + +The binary file that's built won't be usable outside MSYS2. To make a version +usable within the rest of Windows, run: + +``` +make windist +``` + +This will a `windist` directory, containing: + +- A copy of Howl with the proper DLLs in place. +- A file `howl.zip`, suitable for portable usage. + +If you want an installer, make sure Inno Setup is installed and in your PATH, +and run: + +``` +make wininst +```