diff --git a/CHANGELOG.md b/CHANGELOG.md index dd6fbef48c..22f78f9366 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added `filename:dirname/1`, `filename:basename/1,2`, `filename:extension/1`, `filename:rootname/1,2` and `filename:join/2` - Added `init:get_arguments/0` +- Added `console:print_err/1` to write to standard error - Added Erlang distribution over serial (uart) - Added WASM32 JIT backend for Emscripten platform - Added `network:wifi_scan/0,1` to ESP32 network driver to scan available APs when in sta or sta+ap mode. @@ -54,6 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `header_continuation` / `trailer_header_continuation` response events are no longer emitted ### Fixed +- Route `io:put_chars(standard_error, ...)` and `io:format(standard_error, ...)` to stderr instead + of aliasing them to standard_io (diagnostics no longer pollute an escript's stdout) - Stop using deprecated `term_from_int32` on STM32 platform - Stop using deprecated `term_from_int32` on RP2 platform - Stop using deprecated `term_from_int32` on ESP32 platform diff --git a/UPDATING.md b/UPDATING.md index b32e4a61c6..1395039683 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -42,6 +42,11 @@ were using the `0x210000` offset. * Instantiate with `const module = await AtomVM({ arguments: [...] })` * The old implicit global-script pattern is no longer supported * `AtomVM.worker.js` is no longer emitted; only `AtomVM.mjs` and `AtomVM.wasm` are needed +- `io:put_chars(standard_error, ...)` (and `io:format/3`, `io:fwrite/3` targeting + `standard_error`) now write directly to the OS standard error stream via `console:print_err/1`, + instead of being routed through the process group leader as an alias for `standard_io`. Code or + tests that captured `standard_error` output by swapping the group leader must instead redirect + or intercept the underlying stderr stream, or pass an explicit pid/device. ## v0.6.4 -> v0.6.5 diff --git a/libs/eavmlib/src/console.erl b/libs/eavmlib/src/console.erl index 92e3932af5..a902a68afc 100644 --- a/libs/eavmlib/src/console.erl +++ b/libs/eavmlib/src/console.erl @@ -24,7 +24,7 @@ %%----------------------------------------------------------------------------- -module(console). --export([start/0, puts/1, puts/2, flush/0, flush/1, print/1]). +-export([start/0, puts/1, puts/2, flush/0, flush/1, print/1, print_err/1]). %%----------------------------------------------------------------------------- %% @param Text the string data to write to the console @@ -74,6 +74,17 @@ flush(Console) -> print(_Text) -> erlang:nif_error(undefined). +%%----------------------------------------------------------------------------- +%% @param Text the data to write to the standard error +%% @returns ok if the data was written, or {error, Reason}, if there was +%% an error. +%% @doc Write a string to the console's standard error. +%% @end +%%----------------------------------------------------------------------------- +-spec print_err(Text :: iodata()) -> ok | {error, term()}. +print_err(_Text) -> + erlang:nif_error(undefined). + %% Internal operations %% @private diff --git a/libs/estdlib/src/io.erl b/libs/estdlib/src/io.erl index 868d63472c..2f0417113f 100644 --- a/libs/estdlib/src/io.erl +++ b/libs/estdlib/src/io.erl @@ -257,7 +257,7 @@ put_chars(standard_io, Chars) -> execute_request(Leader, {put_chars, unicode, Chars}) end; put_chars(standard_error, Chars) -> - put_chars(standard_io, Chars). + console:print_err(Chars). %% @private convert_request({requests, Requests}) -> diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index 2e3f70f7eb..381a6969a5 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -257,6 +257,7 @@ static term nif_atomvm_get_start_beam(Context *ctx, int argc, term argv[]); static term nif_atomvm_read_priv(Context *ctx, int argc, term argv[]); static term nif_atomvm_get_creation(Context *ctx, int argc, term argv[]); static term nif_console_print(Context *ctx, int argc, term argv[]); +static term nif_console_print_err(Context *ctx, int argc, term argv[]); static term nif_base64_encode(Context *ctx, int argc, term argv[]); static term nif_base64_decode(Context *ctx, int argc, term argv[]); static term nif_base64_encode_to_string(Context *ctx, int argc, term argv[]); @@ -827,6 +828,10 @@ static const struct Nif console_print_nif = { .base.type = NIFFunctionType, .nif_ptr = nif_console_print }; +static const struct Nif console_print_err_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_console_print_err +}; static const struct Nif base64_encode_nif = { .base.type = NIFFunctionType, .nif_ptr = nif_base64_encode @@ -5635,16 +5640,14 @@ static term nif_atomvm_get_creation(Context *ctx, int argc, term argv[]) return term_make_maybe_boxed_int64(ctx->global->creation, &ctx->heap); } -static term nif_console_print(Context *ctx, int argc, term argv[]) +static term console_print_to(FILE *stream, Context *ctx, term argv[]) { - UNUSED(argc); - term t = argv[0]; if (term_is_binary(t)) { const char *data = term_binary_data(t); - unsigned long n = term_binary_size(t); - fprintf(stdout, "%.*s", (int) n, data); - fflush(stdout); + size_t n = term_binary_size(t); + fwrite(data, 1, n, stream); + fflush(stream); } else { size_t size; switch (interop_iolist_size(t, &size)) { @@ -5655,6 +5658,10 @@ static term nif_console_print(Context *ctx, int argc, term argv[]) case InteropBadArg: RAISE_ERROR(BADARG_ATOM); } + if (size == 0) { + fflush(stream); + return OK_ATOM; + } char *buf = malloc(size); if (IS_NULL_PTR(buf)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -5669,13 +5676,25 @@ static term nif_console_print(Context *ctx, int argc, term argv[]) free(buf); RAISE_ERROR(BADARG_ATOM); } - fprintf(stdout, "%.*s", (int) size, buf); - fflush(stdout); + fwrite(buf, 1, size, stream); + fflush(stream); free(buf); } return OK_ATOM; } +static term nif_console_print(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + return console_print_to(stdout, ctx, argv); +} + +static term nif_console_print_err(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + return console_print_to(stderr, ctx, argv); +} + // clang-format off static const char b64_table[64] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', diff --git a/src/libAtomVM/nifs.gperf b/src/libAtomVM/nifs.gperf index c7ce64cf7e..05dc935330 100644 --- a/src/libAtomVM/nifs.gperf +++ b/src/libAtomVM/nifs.gperf @@ -225,6 +225,7 @@ code_server:type_resolver/2, IF_HAVE_JIT(&code_server_type_resolver_nif) code_server:import_resolver/2, IF_HAVE_JIT(&code_server_import_resolver_nif) code_server:set_native_code/3, IF_HAVE_JIT(&code_server_set_native_code_nif) console:print/1, &console_print_nif +console:print_err/1, &console_print_err_nif base64:encode/1, &base64_encode_nif base64:encode/2, &base64_encode_nif base64:decode/1, &base64_decode_nif