Skip to content

Commit 2b7f2b9

Browse files
Add Emscripten/WebAssembly build and worker
Add Emscripten (wasm) build support: CI matrix & steps to install emsdk, configure and build with emcmake/emcc, and package wasm artifacts. Update CMakeLists to set emscripten-specific compile/link options, include pre-js, copy the worker stub, and use a static library variant for EMSCRIPTEN. Add README documentation and examples for building and using lunar.js/lunar.wasm and the browser Worker, and ignore build_wasm in .gitignore. Add two runtime helpers: src/emscripten/lunar_pre.js (node FS mounting and path rewriting) and src/emscripten/lunar_worker.js (Worker interface to run the CLI in browsers).
1 parent 76b569f commit 2b7f2b9

File tree

6 files changed

+420
-7
lines changed

6 files changed

+420
-7
lines changed

.github/workflows/Release.yml

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ jobs:
4646
kind: native
4747
cmake_args: '-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"'
4848

49+
- os: ubuntu-latest
50+
name: wasm-emscripten
51+
kind: emscripten
52+
emsdk_version: 5.0.3
53+
4954
runs-on: ${{ matrix.os }}
5055

5156
steps:
@@ -60,6 +65,34 @@ jobs:
6065
if: matrix.kind == 'native'
6166
run: cmake --build build --config Release -j
6267

68+
- name: Setup Emscripten
69+
if: matrix.kind == 'emscripten'
70+
shell: bash
71+
run: |
72+
set -euxo pipefail
73+
git clone https://github.com/emscripten-core/emsdk.git "$RUNNER_TEMP/emsdk"
74+
"$RUNNER_TEMP/emsdk/emsdk" install ${{ matrix.emsdk_version }}
75+
"$RUNNER_TEMP/emsdk/emsdk" activate ${{ matrix.emsdk_version }}
76+
77+
- name: Configure (emscripten)
78+
if: matrix.kind == 'emscripten'
79+
shell: bash
80+
run: |
81+
set -euxo pipefail
82+
source "$RUNNER_TEMP/emsdk/emsdk_env.sh"
83+
emcmake cmake -S . -B build \
84+
-DCMAKE_BUILD_TYPE=Release \
85+
-DLUNAR_ENABLE_SERIES_FALLBACK=ON \
86+
-DLUNAR_BUILD_TESTS=OFF
87+
88+
- name: Build (emscripten)
89+
if: matrix.kind == 'emscripten'
90+
shell: bash
91+
run: |
92+
set -euxo pipefail
93+
source "$RUNNER_TEMP/emsdk/emsdk_env.sh"
94+
cmake --build build -j
95+
6396
6497
- name: Setup Zig (musl)
6598
if: matrix.kind == 'musl'
@@ -115,16 +148,24 @@ jobs:
115148
mkdir -p package
116149
cp README.md package/
117150
cp LICENSE package/
118-
cp -R vsop87a package/
119-
cp -R elpmpp02 package/
120151
121-
if [ "${{ runner.os }}" = "Windows" ]; then
152+
if [ "${{ matrix.kind }}" = "emscripten" ]; then
153+
cp build/lunar.js package/
154+
cp build/lunar.wasm package/
155+
cp build/lunar_worker.js package/
156+
cd package
157+
tar -czvf ../lunar-${{ matrix.name }}.tar.gz *
158+
elif [ "${{ runner.os }}" = "Windows" ]; then
159+
cp -R vsop87a package/
160+
cp -R elpmpp02 package/
122161
cp build/Release/lunar.exe package/
123162
cp build/Release/lunar.dll package/ || true
124163
cp build/Release/lunar.lib package/ || true
125164
cd package
126165
7z a ../lunar-${{ matrix.name }}.zip *
127166
else
167+
cp -R vsop87a package/
168+
cp -R elpmpp02 package/
128169
cp build/lunar package/
129170
cp build/liblunar.so package/ 2>/dev/null || true
130171
cp build/liblunar.dylib package/ 2>/dev/null || true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Thumbs.db
1010
# CMake out-of-source builds
1111
/build/
1212
/build-*/
13+
/build_wasm/
1314
/cmake-build-*/
1415
/out/
1516
/package/

CMakeLists.txt

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,28 @@ option(LUNAR_ENABLE_DIMENSION_TYPES "Enable compile-time tagged physical vectors
1212
option(LUNAR_ENABLE_SERIES_FALLBACK "Enable VSOP87A/ELPMPP02 fallback when BSP is unavailable" ON)
1313
option(LUNAR_BUILD_TESTS "Build GoogleTest-based test targets" ${BUILD_TESTING})
1414

15+
if(EMSCRIPTEN)
16+
set(LUNAR_EMSCRIPTEN_COMPILE_OPTIONS
17+
-fexceptions
18+
)
19+
set(LUNAR_EMSCRIPTEN_LINK_OPTIONS
20+
-fexceptions
21+
-lnodefs.js
22+
-lworkerfs.js
23+
"SHELL:-sALLOW_MEMORY_GROWTH=1"
24+
"SHELL:-sFORCE_FILESYSTEM=1"
25+
"SHELL:-sEXIT_RUNTIME=1"
26+
"SHELL:-sENVIRONMENT=node,web,worker"
27+
"SHELL:-sEXPORTED_RUNTIME_METHODS=['FS','callMain']"
28+
"SHELL:--pre-js ${CMAKE_CURRENT_SOURCE_DIR}/src/emscripten/lunar_pre.js"
29+
)
30+
configure_file(
31+
${CMAKE_CURRENT_SOURCE_DIR}/src/emscripten/lunar_worker.js
32+
${CMAKE_CURRENT_BINARY_DIR}/lunar_worker.js
33+
COPYONLY
34+
)
35+
endif()
36+
1537
set(LUNAR_SERIES_SOURCES
1638
vsop87a/vsop87a.cpp
1739
vsop87a/vsop87a_ear.cpp
@@ -100,6 +122,11 @@ target_compile_definitions(lunar_core PUBLIC
100122
LUNAR_ENABLE_DIMENSION_TYPES=$<BOOL:${LUNAR_ENABLE_DIMENSION_TYPES}>
101123
LUNAR_ENABLE_SERIES_FALLBACK=$<BOOL:${LUNAR_ENABLE_SERIES_FALLBACK}>
102124
)
125+
if(EMSCRIPTEN)
126+
target_compile_options(lunar_core PUBLIC
127+
${LUNAR_EMSCRIPTEN_COMPILE_OPTIONS}
128+
)
129+
endif()
103130
if(MSVC)
104131
target_compile_options(lunar_core PRIVATE /utf-8)
105132
endif()
@@ -115,8 +142,19 @@ target_include_directories(lunar PRIVATE
115142
if(MSVC)
116143
target_compile_options(lunar PRIVATE /utf-8)
117144
endif()
145+
if(EMSCRIPTEN)
146+
target_link_options(lunar PRIVATE
147+
${LUNAR_EMSCRIPTEN_LINK_OPTIONS}
148+
)
149+
endif()
150+
151+
if(EMSCRIPTEN)
152+
set(LUNAR_DLL_LIBRARY_TYPE STATIC)
153+
else()
154+
set(LUNAR_DLL_LIBRARY_TYPE SHARED)
155+
endif()
118156

119-
add_library(lunar_dll SHARED
157+
add_library(lunar_dll ${LUNAR_DLL_LIBRARY_TYPE}
120158
src/c_api.cpp
121159
)
122160
target_link_libraries(lunar_dll PRIVATE lunar_core)

README.md

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,78 @@ cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
4949
cmake --build build -j
5050
```
5151

52-
### 2.4 产物
52+
### 2.4 WebAssembly(Emscripten)
53+
54+
```powershell
55+
git clone https://github.com/emscripten-core/emsdk.git D:\tools\emsdk
56+
D:\tools\emsdk\emsdk install latest
57+
D:\tools\emsdk\emsdk activate latest
58+
cmd /c "call D:\tools\emsdk\emsdk_env.bat && D:\tools\emsdk\upstream\emscripten\emcmake.bat cmake -S . -B build_wasm -G Ninja -DLUNAR_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release"
59+
cmd /c "call D:\tools\emsdk\emsdk_env.bat && cmake --build build_wasm --parallel"
60+
```
61+
62+
说明:
63+
64+
- 产物为 `build_wasm/lunar.js``build_wasm/lunar.wasm`
65+
- 兼容包装还会生成 `build_wasm/lunar_worker.js`
66+
- 保留现有 CLI 入口;Node 下可直接执行 `node build_wasm/lunar.js --version`
67+
- wasm 构建默认启用异常支持、文件系统支持、`FS`/`callMain` 导出与内存增长
68+
- Node 下会自动把当前工作目录挂载到 wasm 虚拟文件系统,当前目录内相对 `.bsp` 路径可直接使用
69+
- Windows 下会自动兼容 `D:\path\to\file.bsp` 这类盘符绝对路径
70+
- 浏览器 Worker 下可直接使用 `lunar_worker.js`,按 CLI 的 `argv` 方式调用
71+
- Worker 支持两种输入:`files` 写入内存文件,或 `mounts` 通过 `WORKERFS` 挂载 `File`/`Blob`
72+
- `lunar_worker.js` 保持 CLI 语义:单次执行后自动退出;下一条命令请新建 Worker
73+
- 若不需要 BSP,可继续使用 `@series`
74+
75+
浏览器 Worker 示例:
76+
77+
```js
78+
const worker=new Worker('./lunar_worker.js');
79+
80+
worker.onmessage=({data})=>{
81+
if(data.type==='ready'){
82+
worker.postMessage({
83+
argv:[
84+
'day','2025-06-01',
85+
'--bsp','/ephem/'+spkFile.name,
86+
'--format','txt'
87+
],
88+
mounts:[
89+
{path:'/ephem',files:[spkFile]}
90+
]
91+
});
92+
return;
93+
}
94+
if(data.type==='result'){
95+
console.log(data.exit_code);
96+
console.log(data.stdout);
97+
console.error(data.stderr);
98+
}
99+
};
100+
```
101+
102+
浏览器 Worker 也可先写入内存文件:
103+
104+
```js
105+
worker.postMessage({
106+
argv:[
107+
'day','2025-06-01',
108+
'--bsp','/ephem/de442s.bsp',
109+
'--format','txt'
110+
],
111+
files:[
112+
{path:'/ephem/de442s.bsp',data:spkArrayBuffer}
113+
]
114+
});
115+
```
116+
117+
### 2.5 产物
53118

54119
- 可执行程序:`lunar`
55120
- 动态库:`lunar_dll`(输出名为 `lunar`,对应平台扩展名)
56121
- 测试程序:`lunar_tests`(启用 `LUNAR_BUILD_TESTS` 时)
57122

58-
### 2.5 可选构建开关
123+
### 2.6 可选构建开关
59124

60125
- `-DLUNAR_ENABLE_SERIES_FALLBACK=ON|OFF`
61126
- `ON`(默认):未找到 BSP 时自动切换到内置 VSOP87A + ELPMPP02
@@ -64,7 +129,7 @@ cmake --build build -j
64129
- `ON`(默认):构建 gtest/ctest 测试目标
65130
- `OFF`:不构建测试目标
66131

67-
### 2.6 运行测试
132+
### 2.7 运行测试
68133

69134
```bash
70135
cmake -S . -B build

src/emscripten/lunar_pre.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
Module['preRun']=Module['preRun']||[];
2+
Module['preRun'].push(function(){
3+
if(typeof ENVIRONMENT_IS_NODE==='undefined'||!ENVIRONMENT_IS_NODE){
4+
return;
5+
}
6+
if(typeof NODEFS==='undefined'){
7+
return;
8+
}
9+
10+
var node_fs=require('node:fs');
11+
12+
function ensure_dir(path){
13+
var info=FS.analyzePath(path);
14+
if(info.exists){
15+
return;
16+
}
17+
FS.mkdir(path);
18+
}
19+
20+
function mount_dir(path,root){
21+
ensure_dir(path);
22+
try{
23+
FS.mount(NODEFS,{root:root},path);
24+
}catch(ex){
25+
var info=FS.analyzePath(path);
26+
if(!info.exists||!info.object||!info.object.mounted){
27+
throw ex;
28+
}
29+
}
30+
}
31+
32+
function rewrite_host_path(text){
33+
if(typeof text!=='string'){
34+
return text;
35+
}
36+
if(/^[A-Za-z]:[\\/]/.test(text)){
37+
var drive=text.charAt(0).toUpperCase();
38+
var tail=text.slice(2).replace(/\\/g,'/');
39+
if(!tail.startsWith('/')){
40+
tail='/'+tail;
41+
}
42+
return '/'+drive+tail;
43+
}
44+
return text;
45+
}
46+
47+
mount_dir('/working',process.cwd());
48+
FS.chdir('/working');
49+
50+
for(var code=65;code<=90;++code){
51+
var drive=String.fromCharCode(code);
52+
var root=drive+':\\';
53+
if(!node_fs.existsSync(root)){
54+
continue;
55+
}
56+
mount_dir('/'+drive,root);
57+
}
58+
59+
if(Array.isArray(arguments_)){
60+
for(var i=0;i<arguments_.length;++i){
61+
arguments_[i]=rewrite_host_path(arguments_[i]);
62+
}
63+
}
64+
});

0 commit comments

Comments
 (0)