1
0
mirror of https://github.com/janet-lang/janet synced 2025-07-05 11:32:54 +00:00

Merge branch 'master' into compile-opt

This commit is contained in:
Calvin Rose 2025-04-11 22:44:28 -05:00
commit 3cbdf26aa2
8 changed files with 247 additions and 44 deletions

View File

@ -2,7 +2,11 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## Unreleased - ??? ## Unreleased - ???
- Add `ev/to-stream`
- Make `ffi/write` append to a buffer instead of insert at 0 by default. - Make `ffi/write` append to a buffer instead of insert at 0 by default.
- Add `os/getpid` to get the current process id.
- Add `:out` option to `os/spawn` to be able to redirect stderr to stdout with pipes.
Add `interrupt?` argument to `ev/deadline` to use VM interruptions.
## 1.38.0 - 2025-03-18 ## 1.38.0 - 2025-03-18
- Add `bundle/replace` - Add `bundle/replace`

View File

@ -165,6 +165,21 @@ make install-jpm-git
Find out more about the available make targets by running `make help`. Find out more about the available make targets by running `make help`.
### Alpine Linux
To build a statically-linked build of Janet, Alpine Linux + MUSL is a good combination. Janet can also
be built inside a docker container or similar in this manner.
```sh
docker run -it --rm alpine /bin/ash
$ apk add make gcc musl-dev git
$ git clone https://github.com/janet-lang/janet.git
$ cd janet
$ make -j10
$ make test
$ make install
```
### 32-bit Haiku ### 32-bit Haiku
32-bit Haiku build instructions are the same as the UNIX-like build instructions, 32-bit Haiku build instructions are the same as the UNIX-like build instructions,

View File

@ -41,34 +41,34 @@ if not exist build\boot mkdir build\boot
@rem Build the bootstrap interpreter @rem Build the bootstrap interpreter
for %%f in (src\core\*.c) do ( for %%f in (src\core\*.c) do (
%JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f %JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f
@if not errorlevel 0 goto :BUILDFAIL @if errorlevel 1 goto :BUILDFAIL
) )
for %%f in (src\boot\*.c) do ( for %%f in (src\boot\*.c) do (
%JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f %JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f
@if not errorlevel 0 goto :BUILDFAIL @if errorlevel 1 goto :BUILDFAIL
) )
%JANET_LINK% /out:build\janet_boot.exe build\boot\*.obj %JANET_LINK% /out:build\janet_boot.exe build\boot\*.obj
@if not errorlevel 0 goto :BUILDFAIL @if errorlevel 1 goto :BUILDFAIL
build\janet_boot . > build\c\janet.c build\janet_boot . > build\c\janet.c
@if not errorlevel 0 goto :BUILDFAIL @if errorlevel 1 goto :BUILDFAIL
@rem Build the sources @rem Build the sources
%JANET_COMPILE% /Fobuild\janet.obj build\c\janet.c %JANET_COMPILE% /Fobuild\janet.obj build\c\janet.c
@if not errorlevel 0 goto :BUILDFAIL @if errorlevel 1 goto :BUILDFAIL
%JANET_COMPILE% /Fobuild\shell.obj src\mainclient\shell.c %JANET_COMPILE% /Fobuild\shell.obj src\mainclient\shell.c
@if not errorlevel 0 goto :BUILDFAIL @if errorlevel 1 goto :BUILDFAIL
@rem Build the resources @rem Build the resources
rc /nologo /fobuild\janet_win.res janet_win.rc rc /nologo /fobuild\janet_win.res janet_win.rc
@if not errorlevel 0 goto :BUILDFAIL @if errorlevel 1 goto :BUILDFAIL
@rem Link everything to main client @rem Link everything to main client
%JANET_LINK% /out:janet.exe build\janet.obj build\shell.obj build\janet_win.res %JANET_LINK% /out:janet.exe build\janet.obj build\shell.obj build\janet_win.res
@if not errorlevel 0 goto :BUILDFAIL @if errorlevel 1 goto :BUILDFAIL
@rem Build static library (libjanet.lib) @rem Build static library (libjanet.lib)
%JANET_LINK_STATIC% /out:build\libjanet.lib build\janet.obj %JANET_LINK_STATIC% /out:build\libjanet.lib build\janet.obj
@if not errorlevel 0 goto :BUILDFAIL @if errorlevel 1 goto :BUILDFAIL
echo === Successfully built janet.exe for Windows === echo === Successfully built janet.exe for Windows ===
echo === Run 'build_win test' to run tests. == echo === Run 'build_win test' to run tests. ==
@ -102,7 +102,7 @@ exit /b 0
:TEST :TEST
for %%f in (test/suite*.janet) do ( for %%f in (test/suite*.janet) do (
janet.exe test\%%f janet.exe test\%%f
@if not errorlevel 0 goto TESTFAIL @if errorlevel 1 goto TESTFAIL
) )
exit /b 0 exit /b 0

View File

@ -1084,16 +1084,29 @@
(map-aggregator ,maptype ,res (,f x ;call-buffer))))))) (map-aggregator ,maptype ,res (,f x ;call-buffer)))))))
(defn map (defn map
`Map a function over every value in a data structure and ```
return an array of the results.` Map a function `f` over every value in a data structure `ind`
and return an array of results, but only if no `inds` are
provided. Multiple data structures can be handled if each
`inds` is a data structure and `f` is a function of arity
one more than the number of `inds`. The resulting array has
a length that is the shortest of `ind` and each of `inds`.
```
[f ind & inds] [f ind & inds]
(def res @[]) (def res @[])
(map-template :map res f ind inds) (map-template :map res f ind inds)
res) res)
(defn mapcat (defn mapcat
``Map a function over every element in an array or tuple and ```
use `array/concat` to concatenate the results.`` Map a function `f` over every value in a data structure `ind`
and use `array/concat` to concatenate the results, but only if
no `inds` are provided. Multiple data structures can be handled
if each `inds` is a data structure and `f` is a function of
arity one more than the number of `inds`. Note that `f` is only
applied to values at indeces up to the largest index of the
shortest of `ind` and each of `inds`.
```
[f ind & inds] [f ind & inds]
(def res @[]) (def res @[])
(map-template :mapcat res f ind inds) (map-template :mapcat res f ind inds)
@ -1110,18 +1123,30 @@
res) res)
(defn count (defn count
``Count the number of items in `ind` for which `(pred item)` ```
is true.`` Count the number of values in a data structure `ind` for which
applying `pred` yields a truthy value, but only if no `inds` are
provided. Multiple data structures can be handled if each `inds`
is a data structure and `pred` is a function of arity one more
than the number of `inds`. Note that `pred` is only applied to
values at indeces up to the largest index of the shortest of
`ind` and each of `inds`.
```
[pred ind & inds] [pred ind & inds]
(var res 0) (var res 0)
(map-template :count res pred ind inds) (map-template :count res pred ind inds)
res) res)
(defn keep (defn keep
``Given a predicate `pred`, return a new array containing the truthy results ```
of applying `pred` to each element in the indexed collection `ind`. This is Given a predicate `pred`, return a new array containing the
different from `filter` which returns an array of the original elements where truthy results of applying `pred` to each value in the data
the predicate is truthy.`` structure `ind`, but only if no `inds` are provided. Multiple
data structures can be handled if each `inds` is a data
structure and `pred` is a function of arity one more than the
number of `inds`. The resulting array has a length that is no
longer than the shortest of `ind` and each of `inds`.
```
[pred ind & inds] [pred ind & inds]
(def res @[]) (def res @[])
(map-template :keep res pred ind inds) (map-template :keep res pred ind inds)
@ -2203,17 +2228,32 @@
ret) ret)
(defn all (defn all
``Returns true if `(pred item)` is truthy for every item in `ind`. ```
Otherwise, returns the first falsey result encountered. Returns true if applying `pred` to every value in a data
Returns true if `ind` is empty.`` structure `ind` results in only truthy values, but only if no
`inds` are provided. Multiple data structures can be handled
if each `inds` is a data structure and `pred` is a function
of arity one more than the number of `inds`. Returns the first
falsey result encountered. Note that `pred` is only called as
many times as the length of the shortest of `ind` and each of
`inds`. If `ind` or any of `inds` are empty, returns true.
```
[pred ind & inds] [pred ind & inds]
(var res true) (var res true)
(map-template :all res pred ind inds) (map-template :all res pred ind inds)
res) res)
(defn some (defn some
``Returns nil if `(pred item)` is false or nil for every item in `ind`. ```
Otherwise, returns the first truthy result encountered.`` Returns nil if applying `pred` to every value in a data
structure `ind` results in only falsey values, but only if no
`inds` are provided. Multiple data structures can be handled
if each `inds` is a data structure and `pred` is a function
of arity one more than the number of `inds`. Returns the first
truthy result encountered. Note that `pred` is only called as
many times as the length of the shortest of `ind` and each of
`inds`. If `ind` or any of `inds` are empty, returns nil.
```
[pred ind & inds] [pred ind & inds]
(var res nil) (var res nil)
(map-template :some res pred ind inds) (map-template :some res pred ind inds)
@ -4314,6 +4354,9 @@
(when check (when check
(do-hook module bundle-name :check man))) (do-hook module bundle-name :check man)))
(print "installed " bundle-name) (print "installed " bundle-name)
(when (get man :has-bin-script)
(def binpath (string (dyn *syspath*) s "bin"))
(eprintf "executable scripts have been installed to %s" binpath))
bundle-name) bundle-name)
(defn- bundle/pack (defn- bundle/pack
@ -4424,7 +4467,7 @@
(defn bundle/add (defn bundle/add
"Add files and directories during a bundle install relative to `(dyn *syspath*)`. "Add files and directories during a bundle install relative to `(dyn *syspath*)`.
Added paths will be recorded in the bundle manifest such that they are properly tracked Added files and directories will be recorded in the bundle manifest such that they are properly tracked
and removed during an upgrade or uninstall." and removed during an upgrade or uninstall."
[manifest src &opt dest chmod-mode] [manifest src &opt dest chmod-mode]
(default dest src) (default dest src)
@ -4447,6 +4490,7 @@
(default dest (last (string/split s src))) (default dest (last (string/split s src)))
(default chmod-mode 8r755) (default chmod-mode 8r755)
(os/mkdir (string (dyn *syspath*) s "bin")) (os/mkdir (string (dyn *syspath*) s "bin"))
(put manifest :has-bin-script true)
(bundle/add-file manifest src (string "bin" s dest) chmod-mode)) (bundle/add-file manifest src (string "bin" s dest) chmod-mode))
(defn bundle/update-all (defn bundle/update-all

View File

@ -353,21 +353,22 @@ JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod
static void janet_stream_close_impl(JanetStream *stream) { static void janet_stream_close_impl(JanetStream *stream) {
stream->flags |= JANET_STREAM_CLOSED; stream->flags |= JANET_STREAM_CLOSED;
int canclose = !(stream->flags & JANET_STREAM_NOT_CLOSEABLE);
#ifdef JANET_WINDOWS #ifdef JANET_WINDOWS
if (stream->handle != INVALID_HANDLE_VALUE) { if (stream->handle != INVALID_HANDLE_VALUE) {
#ifdef JANET_NET #ifdef JANET_NET
if (stream->flags & JANET_STREAM_SOCKET) { if (stream->flags & JANET_STREAM_SOCKET) {
closesocket((SOCKET) stream->handle); if (canclose) closesocket((SOCKET) stream->handle);
} else } else
#endif #endif
{ {
CloseHandle(stream->handle); if (canclose) CloseHandle(stream->handle);
} }
stream->handle = INVALID_HANDLE_VALUE; stream->handle = INVALID_HANDLE_VALUE;
} }
#else #else
if (stream->handle != -1) { if (stream->handle != -1) {
close(stream->handle); if (canclose) close(stream->handle);
stream->handle = -1; stream->handle = -1;
#ifdef JANET_EV_POLL #ifdef JANET_EV_POLL
uint32_t i = stream->index; uint32_t i = stream->index;
@ -652,6 +653,12 @@ static VOID CALLBACK janet_timeout_stop(ULONG_PTR ptr) {
UNREFERENCED_PARAMETER(ptr); UNREFERENCED_PARAMETER(ptr);
ExitThread(0); ExitThread(0);
} }
#elif JANET_ANDROID
static void janet_timeout_stop(int sig_num) {
if (sig_num == SIGUSR1) {
pthread_exit(0);
}
}
#endif #endif
static void janet_timeout_cb(JanetEVGenericMessage msg) { static void janet_timeout_cb(JanetEVGenericMessage msg) {
@ -673,6 +680,14 @@ static DWORD WINAPI janet_timeout_body(LPVOID ptr) {
} }
#else #else
static void *janet_timeout_body(void *ptr) { static void *janet_timeout_body(void *ptr) {
#ifdef JANET_ANDROID
struct sigaction action;
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = &janet_timeout_stop;
sigaction(SIGUSR1, &action, NULL);
#endif
JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr; JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr;
janet_free(ptr); janet_free(ptr);
struct timespec ts; struct timespec ts;
@ -1489,8 +1504,12 @@ JanetFiber *janet_loop1(void) {
QueueUserAPC(janet_timeout_stop, to.worker, 0); QueueUserAPC(janet_timeout_stop, to.worker, 0);
WaitForSingleObject(to.worker, INFINITE); WaitForSingleObject(to.worker, INFINITE);
CloseHandle(to.worker); CloseHandle(to.worker);
#else
#ifdef JANET_ANDROID
pthread_kill(to.worker, SIGUSR1);
#else #else
pthread_cancel(to.worker); pthread_cancel(to.worker);
#endif
void *res; void *res;
pthread_join(to.worker, &res); pthread_join(to.worker, &res);
#endif #endif
@ -3188,6 +3207,9 @@ JANET_CORE_FN(cfun_ev_deadline,
to.is_error = 0; to.is_error = 0;
to.sched_id = to.fiber->sched_id; to.sched_id = to.fiber->sched_id;
if (use_interrupt) { if (use_interrupt) {
#ifdef JANET_ANDROID
janet_sandbox_assert(JANET_SANDBOX_SIGNAL);
#endif
JanetThreadedTimeout *tto = janet_malloc(sizeof(JanetThreadedTimeout)); JanetThreadedTimeout *tto = janet_malloc(sizeof(JanetThreadedTimeout));
if (NULL == tto) { if (NULL == tto) {
JANET_OUT_OF_MEMORY; JANET_OUT_OF_MEMORY;
@ -3465,6 +3487,39 @@ JANET_CORE_FN(janet_cfun_ev_all_tasks,
return janet_wrap_array(array); return janet_wrap_array(array);
} }
JANET_CORE_FN(janet_cfun_to_stream,
"(ev/to-stream file)",
"Convert a core/file to a core/stream. On POSIX operating systems, this will mark "
"the underlying open file descriptor as non-blocking.") {
janet_fixarity(argc, 1);
int32_t flags = 0;
int32_t stream_flags = 0;
FILE *file = janet_getfile(argv, 0, &flags);
if (flags & JANET_FILE_READ) stream_flags |= JANET_STREAM_READABLE;
if (flags & JANET_FILE_WRITE) stream_flags |= JANET_STREAM_WRITABLE;
if (flags & JANET_FILE_NOT_CLOSEABLE) stream_flags |= JANET_STREAM_NOT_CLOSEABLE;
if (flags & JANET_FILE_CLOSED) janet_panic("file is closed");
#ifdef JANET_WINDOWS
HANDLE handle = (HANDLE) _get_osfhandle(_fileno(file));
HANDLE prochandle = GetCurrentProcess();
HANDLE dupped_handle = INVALID_HANDLE_VALUE;
if (!DuplicateHandle(prochandle, handle, prochandle, &dupped_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
janet_panic("cannot duplicate handle to file");
}
JanetStream *stream = janet_stream(dupped_handle, stream_flags, NULL);
#else
int handle = fileno(file);
int dupped_handle = 0;
int status = 0;
RETRY_EINTR(dupped_handle, dup(handle));
if (status == -1) janet_panic(janet_strerror(errno));
RETRY_EINTR(status, fcntl(dupped_handle, F_SETFL, O_NONBLOCK));
if (status == -1) janet_panic(janet_strerror(errno));
JanetStream *stream = janet_stream(dupped_handle, stream_flags, NULL);
#endif
return janet_wrap_abstract(stream);
}
void janet_lib_ev(JanetTable *env) { void janet_lib_ev(JanetTable *env) {
JanetRegExt ev_cfuns_ext[] = { JanetRegExt ev_cfuns_ext[] = {
JANET_CORE_REG("ev/give", cfun_channel_push), JANET_CORE_REG("ev/give", cfun_channel_push),
@ -3496,6 +3551,7 @@ void janet_lib_ev(JanetTable *env) {
JANET_CORE_REG("ev/release-rlock", janet_cfun_rwlock_read_release), JANET_CORE_REG("ev/release-rlock", janet_cfun_rwlock_read_release),
JANET_CORE_REG("ev/release-wlock", janet_cfun_rwlock_write_release), JANET_CORE_REG("ev/release-wlock", janet_cfun_rwlock_write_release),
JANET_CORE_REG("ev/to-file", janet_cfun_to_file), JANET_CORE_REG("ev/to-file", janet_cfun_to_file),
JANET_CORE_REG("ev/to-stream", janet_cfun_to_stream),
JANET_CORE_REG("ev/all-tasks", janet_cfun_ev_all_tasks), JANET_CORE_REG("ev/all-tasks", janet_cfun_ev_all_tasks),
JANET_REG_END JANET_REG_END
}; };

View File

@ -55,6 +55,7 @@
#include <sys/utime.h> #include <sys/utime.h>
#include <io.h> #include <io.h>
#include <process.h> #include <process.h>
#define JANET_SPAWN_CHDIR
#else #else
#include <spawn.h> #include <spawn.h>
#include <utime.h> #include <utime.h>
@ -73,6 +74,20 @@ extern char **environ;
#endif #endif
#endif #endif
/* Detect availability of posix_spawn_file_actions_addchdir_np. Since
* this doesn't seem to follow any standard, just a common extension, we
* must enumerate supported systems for availability. Define JANET_SPAWN_NO_CHDIR
* to disable this. */
#ifndef JANET_SPAWN_NO_CHDIR
#ifdef __GLIBC__
#define JANET_SPAWN_CHDIR
#elif defined(JANET_APPLE) /* Some older versions may not work here. */
#define JANET_SPAWN_CHDIR
#elif defined(__FreeBSD__) /* Not all BSDs work, for example openBSD doesn't seem to support this */
#define JANET_SPAWN_CHDIR
#endif
#endif
/* Not POSIX, but all Unixes but Solaris have this function. */ /* Not POSIX, but all Unixes but Solaris have this function. */
#if defined(JANET_POSIX) && !defined(__sun) #if defined(JANET_POSIX) && !defined(__sun)
time_t timegm(struct tm *tm); time_t timegm(struct tm *tm);
@ -813,6 +828,19 @@ JANET_CORE_FN(os_proc_close,
#endif #endif
} }
JANET_CORE_FN(os_proc_getpid,
"(os/getpid)",
"Get the process ID of the current process.") {
janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS);
janet_fixarity(argc, 0);
(void) argv;
#ifdef JANET_WINDOWS
return janet_wrap_number((double) _getpid());
#else
return janet_wrap_number((double) getpid());
#endif
}
static void swap_handles(JanetHandle *handles) { static void swap_handles(JanetHandle *handles) {
JanetHandle temp = handles[0]; JanetHandle temp = handles[0];
handles[0] = handles[1]; handles[0] = handles[1];
@ -1137,6 +1165,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
JanetAbstract orig_in = NULL, orig_out = NULL, orig_err = NULL; JanetAbstract orig_in = NULL, orig_out = NULL, orig_err = NULL;
JanetHandle new_in = JANET_HANDLE_NONE, new_out = JANET_HANDLE_NONE, new_err = JANET_HANDLE_NONE; JanetHandle new_in = JANET_HANDLE_NONE, new_out = JANET_HANDLE_NONE, new_err = JANET_HANDLE_NONE;
JanetHandle pipe_in = JANET_HANDLE_NONE, pipe_out = JANET_HANDLE_NONE, pipe_err = JANET_HANDLE_NONE; JanetHandle pipe_in = JANET_HANDLE_NONE, pipe_out = JANET_HANDLE_NONE, pipe_err = JANET_HANDLE_NONE;
int stderr_is_stdout = 0;
int pipe_errflag = 0; /* Track errors setting up pipes */ int pipe_errflag = 0; /* Track errors setting up pipes */
int pipe_owner_flags = (is_spawn && (flags & 0x8)) ? JANET_PROC_ALLOW_ZOMBIE : 0; int pipe_owner_flags = (is_spawn && (flags & 0x8)) ? JANET_PROC_ALLOW_ZOMBIE : 0;
@ -1161,11 +1190,28 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
if (is_spawn && janet_keyeq(maybe_stderr, "pipe")) { if (is_spawn && janet_keyeq(maybe_stderr, "pipe")) {
new_err = make_pipes(&pipe_err, 0, &pipe_errflag); new_err = make_pipes(&pipe_err, 0, &pipe_errflag);
pipe_owner_flags |= JANET_PROC_OWNS_STDERR; pipe_owner_flags |= JANET_PROC_OWNS_STDERR;
} else if (is_spawn && janet_keyeq(maybe_stderr, "out")) {
stderr_is_stdout = 1;
} else if (!janet_checktype(maybe_stderr, JANET_NIL)) { } else if (!janet_checktype(maybe_stderr, JANET_NIL)) {
new_err = janet_getjstream(&maybe_stderr, 0, &orig_err); new_err = janet_getjstream(&maybe_stderr, 0, &orig_err);
} }
} }
/* Optional working directory. Available for both os/execute and os/spawn. */
const char *chdir_path = NULL;
if (argc > 2) {
JanetDictView tab = janet_getdictionary(argv, 2);
Janet workdir = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("cd"));
if (janet_checktype(workdir, JANET_STRING)) {
chdir_path = (const char *) janet_unwrap_string(workdir);
#ifndef JANET_SPAWN_CHDIR
janet_panicf(":cd argument not supported on this system - %s", chdir_path);
#endif
} else if (!janet_checktype(workdir, JANET_NIL)) {
janet_panicf("expected string for :cd argumnet, got %v", workdir);
}
}
/* Clean up if any of the pipes have any issues */ /* Clean up if any of the pipes have any issues */
if (pipe_errflag) { if (pipe_errflag) {
if (pipe_in != JANET_HANDLE_NONE) close_handle(pipe_in); if (pipe_in != JANET_HANDLE_NONE) close_handle(pipe_in);
@ -1180,6 +1226,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
SECURITY_ATTRIBUTES saAttr; SECURITY_ATTRIBUTES saAttr;
PROCESS_INFORMATION processInfo; PROCESS_INFORMATION processInfo;
STARTUPINFO startupInfo; STARTUPINFO startupInfo;
LPCSTR lpCurrentDirectory = NULL;
memset(&saAttr, 0, sizeof(saAttr)); memset(&saAttr, 0, sizeof(saAttr));
memset(&processInfo, 0, sizeof(processInfo)); memset(&processInfo, 0, sizeof(processInfo));
memset(&startupInfo, 0, sizeof(startupInfo)); memset(&startupInfo, 0, sizeof(startupInfo));
@ -1196,6 +1243,10 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} }
const char *path = (const char *) janet_unwrap_string(exargs.items[0]); const char *path = (const char *) janet_unwrap_string(exargs.items[0]);
if (chdir_path != NULL) {
lpCurrentDirectory = chdir_path;
}
/* Do IO redirection */ /* Do IO redirection */
if (pipe_in != JANET_HANDLE_NONE) { if (pipe_in != JANET_HANDLE_NONE) {
@ -1203,7 +1254,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (new_in != JANET_HANDLE_NONE) { } else if (new_in != JANET_HANDLE_NONE) {
startupInfo.hStdInput = new_in; startupInfo.hStdInput = new_in;
} else { } else {
startupInfo.hStdInput = (HANDLE) _get_osfhandle(0); startupInfo.hStdInput = (HANDLE) _get_osfhandle(_fileno(stdin));
} }
if (pipe_out != JANET_HANDLE_NONE) { if (pipe_out != JANET_HANDLE_NONE) {
@ -1211,15 +1262,17 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (new_out != JANET_HANDLE_NONE) { } else if (new_out != JANET_HANDLE_NONE) {
startupInfo.hStdOutput = new_out; startupInfo.hStdOutput = new_out;
} else { } else {
startupInfo.hStdOutput = (HANDLE) _get_osfhandle(1); startupInfo.hStdOutput = (HANDLE) _get_osfhandle(_fileno(stdout));
} }
if (pipe_err != JANET_HANDLE_NONE) { if (pipe_err != JANET_HANDLE_NONE) {
startupInfo.hStdError = pipe_err; startupInfo.hStdError = pipe_err;
} else if (new_err != NULL) { } else if (new_err != NULL) {
startupInfo.hStdError = new_err; startupInfo.hStdError = new_err;
} else if (stderr_is_stdout) {
startupInfo.hStdError = startupInfo.hStdOutput;
} else { } else {
startupInfo.hStdError = (HANDLE) _get_osfhandle(2); startupInfo.hStdError = (HANDLE) _get_osfhandle(_fileno(stderr));
} }
int cp_failed = 0; int cp_failed = 0;
@ -1230,7 +1283,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
TRUE, /* handle inheritance */ TRUE, /* handle inheritance */
0, /* flags */ 0, /* flags */
use_environ ? NULL : envp, /* pass in environment */ use_environ ? NULL : envp, /* pass in environment */
NULL, /* use parents starting directory */ lpCurrentDirectory,
&startupInfo, &startupInfo,
&processInfo)) { &processInfo)) {
cp_failed = 1; cp_failed = 1;
@ -1287,6 +1340,15 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
/* Posix spawn setup */ /* Posix spawn setup */
posix_spawn_file_actions_t actions; posix_spawn_file_actions_t actions;
posix_spawn_file_actions_init(&actions); posix_spawn_file_actions_init(&actions);
#ifdef JANET_SPAWN_CHDIR
if (chdir_path != NULL) {
#ifdef JANET_SPAWN_CHDIR_NO_NP
posix_spawn_file_actions_addchdir(&actions, chdir_path);
#else
posix_spawn_file_actions_addchdir_np(&actions, chdir_path);
#endif
}
#endif
if (pipe_in != JANET_HANDLE_NONE) { if (pipe_in != JANET_HANDLE_NONE) {
posix_spawn_file_actions_adddup2(&actions, pipe_in, 0); posix_spawn_file_actions_adddup2(&actions, pipe_in, 0);
posix_spawn_file_actions_addclose(&actions, pipe_in); posix_spawn_file_actions_addclose(&actions, pipe_in);
@ -1309,6 +1371,8 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (new_err != JANET_HANDLE_NONE && new_err != 2) { } else if (new_err != JANET_HANDLE_NONE && new_err != 2) {
posix_spawn_file_actions_adddup2(&actions, new_err, 2); posix_spawn_file_actions_adddup2(&actions, new_err, 2);
posix_spawn_file_actions_addclose(&actions, new_err); posix_spawn_file_actions_addclose(&actions, new_err);
} else if (stderr_is_stdout) {
posix_spawn_file_actions_adddup2(&actions, 1, 2);
} }
pid_t pid; pid_t pid;
@ -1414,7 +1478,8 @@ JANET_CORE_FN(os_spawn,
"`:pipe` may fail if there are too many active file descriptors. The caller is " "`:pipe` may fail if there are too many active file descriptors. The caller is "
"responsible for closing pipes created by `:pipe` (either individually or using " "responsible for closing pipes created by `:pipe` (either individually or using "
"`os/proc-close`). Similar to `os/execute`, the caller is responsible for ensuring " "`os/proc-close`). Similar to `os/execute`, the caller is responsible for ensuring "
"pipes do not cause the program to block and deadlock.") { "pipes do not cause the program to block and deadlock. As a special case, the stream passed to `:err` "
"can be the keyword `:out` to redirect stderr to stdout in the subprocess.") {
return os_execute_impl(argc, argv, JANET_EXECUTE_SPAWN); return os_execute_impl(argc, argv, JANET_EXECUTE_SPAWN);
} }
@ -2797,6 +2862,7 @@ void janet_lib_os(JanetTable *env) {
JANET_CORE_REG("os/proc-wait", os_proc_wait), JANET_CORE_REG("os/proc-wait", os_proc_wait),
JANET_CORE_REG("os/proc-kill", os_proc_kill), JANET_CORE_REG("os/proc-kill", os_proc_kill),
JANET_CORE_REG("os/proc-close", os_proc_close), JANET_CORE_REG("os/proc-close", os_proc_close),
JANET_CORE_REG("os/getpid", os_proc_getpid),
#endif #endif
/* high resolution timers */ /* high resolution timers */

View File

@ -67,6 +67,11 @@ extern "C" {
#define JANET_LINUX 1 #define JANET_LINUX 1
#endif #endif
/* Check for Android */
#ifdef __ANDROID__
#define JANET_ANDROID 1
#endif
/* Check for Cygwin */ /* Check for Cygwin */
#if defined(__CYGWIN__) #if defined(__CYGWIN__)
#define JANET_CYGWIN 1 #define JANET_CYGWIN 1
@ -591,7 +596,7 @@ typedef void *JanetAbstract;
#define JANET_STREAM_WRITABLE 0x400 #define JANET_STREAM_WRITABLE 0x400
#define JANET_STREAM_ACCEPTABLE 0x800 #define JANET_STREAM_ACCEPTABLE 0x800
#define JANET_STREAM_UDPSERVER 0x1000 #define JANET_STREAM_UDPSERVER 0x1000
#define JANET_STREAM_BUFFERED 0x2000 #define JANET_STREAM_NOT_CLOSEABLE 0x2000
#define JANET_STREAM_TOCLOSE 0x10000 #define JANET_STREAM_TOCLOSE 0x10000
typedef enum { typedef enum {

View File

@ -430,13 +430,7 @@
# Now do our telnet chat # Now do our telnet chat
(def bob (assert (net/connect test-host test-port :stream))) (def bob (assert (net/connect test-host test-port :stream)))
(expect-read bob "Whats your name?\n") (expect-read bob "Whats your name?\n")
(if (= :mingw (os/which)) (net/write bob "bob")
(net/write bob "bob")
(do
(def fbob (ev/to-file bob))
(file/write fbob "bob")
(file/flush fbob)
(:close fbob)))
(expect-read bob "Welcome bob\n") (expect-read bob "Welcome bob\n")
(def alice (assert (net/connect test-host test-port))) (def alice (assert (net/connect test-host test-port)))
(expect-read alice "Whats your name?\n") (expect-read alice "Whats your name?\n")
@ -501,8 +495,10 @@
# soreuseport on unix domain sockets # soreuseport on unix domain sockets
(compwhen (or (= :macos (os/which)) (= :linux (os/which))) (compwhen (or (= :macos (os/which)) (= :linux (os/which)))
(assert-no-error "unix-domain socket reuseaddr" (assert-no-error "unix-domain socket reuseaddr"
(let [s (net/listen :unix "./unix-domain-socket" :stream)] (let [uds-path "./unix-domain-socket"]
(:close s)))) (defer (os/rm uds-path)
(let [s (net/listen :unix uds-path :stream)]
(:close s))))))
# net/accept-loop level triggering # net/accept-loop level triggering
(gccollect) (gccollect)
@ -554,4 +550,21 @@
(ev/deadline 0.01 nil f true) (ev/deadline 0.01 nil f true)
(assert-error "deadline expired" (resume f))) (assert-error "deadline expired" (resume f)))
# Use :err :stdout
(def- subproc-code '(do (eprint "hi") (eflush) (print "there") (flush)))
(defn ev/slurp
[f &opt buf]
(default buf @"")
(if (ev/read f 0x10000 buf)
(ev/slurp f buf)
buf))
(def p (os/spawn [;run janet "-e" (string/format "%j" subproc-code)] :px {:out :pipe :err :out}))
(def [exit-code data]
(ev/gather
(os/proc-wait p)
(ev/slurp (p :out))))
(def data (string/replace-all "\r" "" data))
(assert (zero? exit-code) "subprocess ran")
(assert (= data "hi\nthere\n") "output is correct")
(end-suite) (end-suite)