feat(dictionary): add Hachidori backend support

- Add backend selection, setup gating, Anki integration, and external host support
- Add launcher flags, documentation, packaging, and focused tests
- Open on-demand overlay modals on the first attempt
This commit is contained in:
2026-09-22 00:21:19 -07:00
parent 1508863dbb
commit d9fdc7ef6d
446 changed files with 109060 additions and 244 deletions
+102
View File
@@ -0,0 +1,102 @@
cmake_minimum_required(VERSION 3.31)
project(hachidori LANGUAGES C CXX)
option(HACHIDORI_PTHREADS "Build a pthread runtime" ON)
# With pthreads, WasmFS + direct OPFS is the primary runtime. Turning this off
# keeps the threads but stores through the classic FS + IDBFS, for hosts such as
# Electron that expose shared memory and workers but refuse OPFS access handles
# to chrome-extension:// origins.
option(HACHIDORI_WASMFS "Use WasmFS + direct OPFS with pthreads" ON)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# -fwasm-exceptions is an ABI choice: every translation unit that appears in the
# final link must agree, including the engine's and its vendored C deps. Putting
# it in CMAKE_{C,CXX}_FLAGS rather than on our target is what makes that true for
# the add_subdirectory'd trees.
# Every supported host (Chrome >= 128, Electron, Node) has wasm SIMD, and
# link-time optimisation lets the engine, zstd, glaze, and libdeflate inline
# across each other (measured: native lookups 15-30% faster, imports 4-8%;
# output files identical). xxHash would route wasm SIMD through the SIMDe
# arm_neon.h shim, which drags C++ headers inside its extern "C" block; keep
# xxHash scalar and let the compiler vectorise everything else.
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fwasm-exceptions -O3 -msimd128 -flto")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fwasm-exceptions -O3 -msimd128 -flto")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D'XXH_HAS_INCLUDE(x)=0'")
if(HACHIDORI_PTHREADS)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif()
# The WasmFS runtime reads dictionary files through sync access handles (see
# hoshidicts src/memory/memory.cpp); the classic-FS runtimes must not.
if(HACHIDORI_PTHREADS AND HACHIDORI_WASMFS)
set(HOSHIDICTS_WASMFS ON CACHE BOOL "" FORCE)
else()
set(HOSHIDICTS_WASMFS OFF CACHE BOOL "" FORCE)
endif()
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../third_party/hoshidicts hoshidicts)
add_executable(hoshidicts_wasm bindings.cpp)
if(HACHIDORI_PTHREADS AND NOT HACHIDORI_WASMFS)
set_target_properties(hoshidicts_wasm PROPERTIES OUTPUT_NAME hoshidicts-threaded-idbfs SUFFIX ".mjs")
elseif(HACHIDORI_PTHREADS)
set_target_properties(hoshidicts_wasm PROPERTIES OUTPUT_NAME hoshidicts-threaded SUFFIX ".mjs")
else()
set_target_properties(hoshidicts_wasm PROPERTIES OUTPUT_NAME hoshidicts SUFFIX ".mjs")
endif()
# The engine links glaze PRIVATE, so its include dirs do not propagate here.
target_link_libraries(hoshidicts_wasm PRIVATE hoshidicts glaze::glaze)
# src/ is not in the engine's public include set. hdw_import reaches into it for
# zip/zip.hpp so it can read the archive's declared title before handing the
# archive to an importer that turns that title into a directory it may delete.
target_include_directories(hoshidicts_wasm PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/hoshidicts/src)
if(HACHIDORI_PTHREADS AND HACHIDORI_WASMFS)
target_compile_definitions(hoshidicts_wasm PRIVATE HACHIDORI_OPFS=1)
endif()
target_link_options(hoshidicts_wasm PRIVATE
-O3
-fwasm-exceptions
-flto
-sMODULARIZE=1
-sEXPORT_ES6=1
-sEXPORT_NAME=createHoshidicts
-sENVIRONMENT=web,worker,node
-sALLOW_MEMORY_GROWTH=1
-sMAXIMUM_MEMORY=4GB
-sSTACK_SIZE=8MB
-sFORCE_FILESYSTEM=1
-sEXPORTED_FUNCTIONS=_malloc,_free,_hdw_init_storage,_hdw_import,_hdw_reset,_hdw_add_dict,_hdw_remove_dict,_hdw_set_dict_order,_hdw_frequencies,_hdw_lookup,_hdw_lookup_dictionary,_hdw_kanji,_hdw_styles,_hdw_media,_hdw_media_data,_hdw_last_error
)
if(HACHIDORI_PTHREADS)
target_link_options(hoshidicts_wasm PRIVATE
-pthread
"SHELL:-sPTHREAD_POOL_SIZE=Math.max(2,Math.min(globalThis.navigator?.hardwareConcurrency||1,8))+1"
-sPTHREAD_POOL_SIZE_STRICT=2
-sPTHREAD_POOL_DELAY_LOAD=0
-sALLOW_BLOCKING_ON_MAIN_THREAD=0
)
endif()
if(HACHIDORI_PTHREADS AND HACHIDORI_WASMFS)
target_link_options(hoshidicts_wasm PRIVATE
-sWASMFS=1
-sEXPORTED_RUNTIME_METHODS=ccall,cwrap,FS,UTF8ToString,stringToNewUTF8,HEAPU8,lengthBytesUTF8
)
else()
target_link_options(hoshidicts_wasm PRIVATE
-lidbfs.js
-sEXPORTED_RUNTIME_METHODS=ccall,cwrap,FS,IDBFS,UTF8ToString,stringToNewUTF8,HEAPU8,lengthBytesUTF8
)
endif()
+47
View File
@@ -0,0 +1,47 @@
cmake_minimum_required(VERSION 3.31)
project(hachidori_avif LANGUAGES C)
include(FetchContent)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(AVIF_CODEC_AOM LOCAL CACHE STRING "" FORCE)
set(AVIF_CODEC_AOM_DECODE OFF CACHE BOOL "" FORCE)
set(AVIF_CODEC_AOM_ENCODE ON CACHE BOOL "" FORCE)
set(AVIF_LIBYUV OFF CACHE BOOL "" FORCE)
set(AVIF_LIBSHARPYUV OFF CACHE BOOL "" FORCE)
set(AVIF_JPEG OFF CACHE BOOL "" FORCE)
set(AVIF_ZLIBPNG OFF CACHE BOOL "" FORCE)
set(AVIF_BUILD_APPS OFF CACHE BOOL "" FORCE)
set(AVIF_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(AOM_TARGET_CPU generic CACHE STRING "" FORCE)
set(CONFIG_RUNTIME_CPU_DETECT 0 CACHE STRING "" FORCE)
FetchContent_Declare(
libavif
GIT_REPOSITORY https://github.com/AOMediaCodec/libavif.git
GIT_TAG 1aadfad932c98c069a1204261b1856f81f3bc199
GIT_SHALLOW FALSE
)
FetchContent_MakeAvailable(libavif)
add_executable(hachidori_avif encoder.c)
set_target_properties(hachidori_avif PROPERTIES OUTPUT_NAME avif-encoder SUFFIX ".mjs")
target_compile_features(hachidori_avif PRIVATE c_std_11)
target_compile_options(hachidori_avif PRIVATE -Oz)
target_link_libraries(hachidori_avif PRIVATE avif)
target_link_options(hachidori_avif PRIVATE
-Oz
-sMODULARIZE=1
-sEXPORT_ES6=1
-sEXPORT_NAME=createAvifEncoderModule
-sENVIRONMENT=worker,node
-sALLOW_MEMORY_GROWTH=1
-sINITIAL_MEMORY=32MB
-sMAXIMUM_MEMORY=256MB
-sSTACK_SIZE=1MB
-sFILESYSTEM=0
-sMALLOC=emmalloc
-sEXPORTED_FUNCTIONS=_malloc,_free,_hda_create,_hda_add_rgba,_hda_finish,_hda_output,_hda_output_size,_hda_last_error,_hda_destroy
-sEXPORTED_RUNTIME_METHODS=HEAPU8,UTF8ToString
)
+81
View File
@@ -0,0 +1,81 @@
The AVIF encoder binary links libavif v1.3.0 and libaom v3.12.1. It also
includes libavif's bundled libyuv conversion sources.
libavif
Copyright 2019 Joe Drago. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
libaom
Copyright (c) 2016, Alliance for Open Media. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
libyuv
Copyright 2011 The LibYuv Project Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Google nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
+21
View File
@@ -0,0 +1,21 @@
#!/bin/sh
# Build the pinned libavif/libaom sequence encoder used by the capture worker.
set -e
AVIF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
WASM_DIR=$(CDPATH= cd -- "$AVIF_DIR/.." && pwd)
REPO_ROOT=$(CDPATH= cd -- "$WASM_DIR/.." && pwd)
AVIF_BUILD_DIR="${AVIF_BUILD_DIR:-$AVIF_DIR/build}"
VENDOR_DIR="$REPO_ROOT/extension/vendor"
# shellcheck disable=SC1091
. "$WASM_DIR/env.sh"
emcmake cmake -S "$AVIF_DIR" -B "$AVIF_BUILD_DIR" -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel
cmake --build "$AVIF_BUILD_DIR" --parallel "$(nproc 2>/dev/null || printf '%s\n' 4)"
mkdir -p "$VENDOR_DIR"
cp "$AVIF_BUILD_DIR/avif-encoder.mjs" "$VENDOR_DIR/avif-encoder.mjs"
cp "$AVIF_BUILD_DIR/avif-encoder.wasm" "$VENDOR_DIR/avif-encoder.wasm"
chmod 644 "$VENDOR_DIR/avif-encoder.mjs" "$VENDOR_DIR/avif-encoder.wasm"
ls -l "$VENDOR_DIR/avif-encoder.mjs" "$VENDOR_DIR/avif-encoder.wasm"
+149
View File
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Small C ABI around libavif's sequence encoder. The caller supplies one RGBA
// frame at a time, so decoded frame memory never accumulates in WebAssembly.
#include <emscripten/emscripten.h>
#include <avif/avif.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum {
HDA_MAX_WIDTH = 640,
HDA_MAX_HEIGHT = 360,
HDA_MAX_FRAMES = 120,
HDA_MAX_OUTPUT_BYTES = 4 * 1024 * 1024
};
typedef struct {
avifEncoder *encoder;
avifRWData output;
uint32_t width;
uint32_t height;
uint32_t frame_count;
int finished;
char error[256];
} HdaEncoder;
static char global_error[256];
static int fail(HdaEncoder *encoder, const char *message) {
char *target = encoder ? encoder->error : global_error;
snprintf(target, 256, "%s", message ? message : "unknown AVIF encoder failure");
return 0;
}
static int avif_fail(HdaEncoder *encoder, const char *operation, avifResult result) {
char message[256];
snprintf(message, sizeof(message), "%s: %s", operation, avifResultToString(result));
return fail(encoder, message);
}
EMSCRIPTEN_KEEPALIVE
HdaEncoder *hda_create(uint32_t width, uint32_t height, uint32_t timescale, int quality, int speed) {
global_error[0] = '\0';
if (width == 0 || height == 0 || width > HDA_MAX_WIDTH || height > HDA_MAX_HEIGHT
|| (width & 1U) != 0 || (height & 1U) != 0) {
fail(NULL, "AVIF dimensions must be even and no larger than 640 by 360");
return NULL;
}
if (timescale == 0 || quality < AVIF_QUALITY_WORST || quality > AVIF_QUALITY_BEST
|| speed < AVIF_SPEED_SLOWEST || speed > AVIF_SPEED_FASTEST) {
fail(NULL, "AVIF encoder settings are invalid");
return NULL;
}
HdaEncoder *state = calloc(1, sizeof(HdaEncoder));
if (!state) {
fail(NULL, "could not allocate AVIF encoder state");
return NULL;
}
state->encoder = avifEncoderCreate();
if (!state->encoder) {
free(state);
fail(NULL, "could not create AVIF encoder");
return NULL;
}
state->width = width;
state->height = height;
state->encoder->codecChoice = AVIF_CODEC_CHOICE_AOM;
state->encoder->maxThreads = 1;
state->encoder->speed = speed;
state->encoder->quality = quality;
state->encoder->qualityAlpha = quality;
state->encoder->timescale = timescale;
state->encoder->repetitionCount = AVIF_REPETITION_COUNT_INFINITE;
state->encoder->keyframeInterval = 0;
return state;
}
EMSCRIPTEN_KEEPALIVE
int hda_add_rgba(HdaEncoder *state, const uint8_t *rgba, size_t byte_length, uint32_t duration) {
if (!state || !state->encoder || state->finished) return fail(state, "AVIF encoder is not writable");
if (!rgba || byte_length != (size_t)state->width * state->height * 4U) {
return fail(state, "RGBA frame size does not match the AVIF canvas");
}
if (duration == 0 || state->frame_count >= HDA_MAX_FRAMES) {
return fail(state, "AVIF frame count or duration exceeds its limit");
}
avifImage *image = avifImageCreate(state->width, state->height, 8, AVIF_PIXEL_FORMAT_YUV420);
if (!image) return fail(state, "could not allocate AVIF image");
image->yuvRange = AVIF_RANGE_FULL;
image->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709;
image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT709;
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, image);
rgb.format = AVIF_RGB_FORMAT_RGBA;
rgb.ignoreAlpha = AVIF_TRUE;
rgb.pixels = (uint8_t *)rgba;
rgb.rowBytes = state->width * 4U;
avifResult result = avifImageRGBToYUV(image, &rgb);
if (result == AVIF_RESULT_OK) {
result = avifEncoderAddImage(state->encoder, image, duration, AVIF_ADD_IMAGE_FLAG_NONE);
}
avifImageDestroy(image);
if (result != AVIF_RESULT_OK) return avif_fail(state, "could not add AVIF frame", result);
state->frame_count += 1;
return 1;
}
EMSCRIPTEN_KEEPALIVE
int hda_finish(HdaEncoder *state) {
if (!state || !state->encoder || state->finished || state->frame_count == 0) {
return fail(state, "AVIF encoder has no finishable sequence");
}
avifResult result = avifEncoderFinish(state->encoder, &state->output);
if (result != AVIF_RESULT_OK) return avif_fail(state, "could not finish AVIF sequence", result);
if (state->output.size == 0 || state->output.size > HDA_MAX_OUTPUT_BYTES) {
avifRWDataFree(&state->output);
return fail(state, "Animated AVIF exceeds the 4 MiB output limit");
}
state->finished = 1;
return 1;
}
EMSCRIPTEN_KEEPALIVE
const uint8_t *hda_output(const HdaEncoder *state) {
return state && state->finished ? state->output.data : NULL;
}
EMSCRIPTEN_KEEPALIVE
size_t hda_output_size(const HdaEncoder *state) {
return state && state->finished ? state->output.size : 0;
}
EMSCRIPTEN_KEEPALIVE
const char *hda_last_error(const HdaEncoder *state) {
return state ? state->error : global_error;
}
EMSCRIPTEN_KEEPALIVE
void hda_destroy(HdaEncoder *state) {
if (!state) return;
avifRWDataFree(&state->output);
avifEncoderDestroy(state->encoder);
free(state);
}
+1231
View File
File diff suppressed because it is too large Load Diff
+52
View File
@@ -0,0 +1,52 @@
#!/bin/sh
# Builds the threaded OPFS runtime, the threaded IDBFS runtime for hosts without
# OPFS access handles, and the single-thread IDBFS fallback.
set -e
WASM_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
REPO_ROOT=$(CDPATH= cd -- "$WASM_DIR/.." && pwd)
THREADED_BUILD_DIR="${BUILD_DIR:-$WASM_DIR/build}"
THREADED_IDBFS_BUILD_DIR="${THREADED_IDBFS_BUILD_DIR:-${THREADED_BUILD_DIR}-idbfs}"
FALLBACK_BUILD_DIR="${FALLBACK_BUILD_DIR:-${THREADED_BUILD_DIR}-fallback}"
VENDOR_DIR="$REPO_ROOT/extension/vendor"
# shellcheck disable=SC1091
. "$WASM_DIR/env.sh"
build_runtime() {
build_dir=$1
pthreads=$2
wasmfs=$3
emcmake cmake -S "$WASM_DIR" -B "$build_dir" \
-DCMAKE_BUILD_TYPE=Release \
-DHACHIDORI_PTHREADS="$pthreads" \
-DHACHIDORI_WASMFS="$wasmfs"
cmake --build "$build_dir" --parallel "$(nproc 2>/dev/null || printf '%s\n' 4)"
}
build_runtime "$THREADED_BUILD_DIR" ON ON
build_runtime "$THREADED_IDBFS_BUILD_DIR" ON OFF
build_runtime "$FALLBACK_BUILD_DIR" OFF ON
mkdir -p "$VENDOR_DIR"
cp "$THREADED_BUILD_DIR/hoshidicts-threaded.mjs" "$VENDOR_DIR/hoshidicts-threaded.mjs"
cp "$THREADED_BUILD_DIR/hoshidicts-threaded.wasm" "$VENDOR_DIR/hoshidicts-threaded.wasm"
cp "$THREADED_IDBFS_BUILD_DIR/hoshidicts-threaded-idbfs.mjs" "$VENDOR_DIR/hoshidicts-threaded-idbfs.mjs"
cp "$THREADED_IDBFS_BUILD_DIR/hoshidicts-threaded-idbfs.wasm" "$VENDOR_DIR/hoshidicts-threaded-idbfs.wasm"
cp "$FALLBACK_BUILD_DIR/hoshidicts.mjs" "$VENDOR_DIR/hoshidicts.mjs"
cp "$FALLBACK_BUILD_DIR/hoshidicts.wasm" "$VENDOR_DIR/hoshidicts.wasm"
chmod 644 \
"$VENDOR_DIR/hoshidicts-threaded.mjs" \
"$VENDOR_DIR/hoshidicts-threaded.wasm" \
"$VENDOR_DIR/hoshidicts-threaded-idbfs.mjs" \
"$VENDOR_DIR/hoshidicts-threaded-idbfs.wasm" \
"$VENDOR_DIR/hoshidicts.mjs" \
"$VENDOR_DIR/hoshidicts.wasm"
ls -l \
"$VENDOR_DIR/hoshidicts-threaded.mjs" \
"$VENDOR_DIR/hoshidicts-threaded.wasm" \
"$VENDOR_DIR/hoshidicts-threaded-idbfs.mjs" \
"$VENDOR_DIR/hoshidicts-threaded-idbfs.wasm" \
"$VENDOR_DIR/hoshidicts.mjs" \
"$VENDOR_DIR/hoshidicts.wasm"
Vendored Executable
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh
# Shared Emscripten environment for every build entry point in this repo.
#
# emsdk's launchers run `python3` from PATH and require >= 3.10, but this host's
# /usr/bin/python3 is 3.9. EMSHIM puts a newer python3 first on PATH.
EMSDK_DIR="${EMSDK_DIR:-$HOME/emsdk}"
EMSHIM="${EMSHIM:-$HOME/.local/emshim}"
if [ ! -x "$EMSHIM/python3" ]; then
mkdir -p "$EMSHIM"
for v in 3.13 3.12 3.11 3.10; do
if [ -x "$HOME/.local/bin/python$v" ]; then
ln -sf "$HOME/.local/bin/python$v" "$EMSHIM/python3"
break
fi
done
fi
PATH="$EMSHIM:$PATH"
export PATH
export EMSDK_PYTHON="$EMSHIM/python3"
# shellcheck disable=SC1091
. "$EMSDK_DIR/emsdk_env.sh" >/dev/null 2>&1
PATH="$EMSHIM:$PATH"
export PATH