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:
commit
3cbdf26aa2
@ -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`
|
||||||
|
15
README.md
15
README.md
@ -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,
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
@ -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 */
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user