Skip to content

Commit c2e0a1f

Browse files
authored
Merge pull request #5 from sqliteai/wasm-build
add cloudsync-wasm build
2 parents 3272150 + e8a1cce commit c2e0a1f

File tree

8 files changed

+352
-24
lines changed

8 files changed

+352
-24
lines changed

.github/workflows/main.yml

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ permissions:
1111
jobs:
1212
build:
1313
runs-on: ${{ matrix.os }}
14-
name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'isim' && matrix.name != 'ios' && ' + test' || ''}}
14+
name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'isim' && matrix.name != 'ios' && matrix.name != 'wasm' && ' + test' || ''}}
1515
timeout-minutes: 20
1616
strategy:
1717
fail-fast: false
@@ -43,6 +43,9 @@ jobs:
4343
- os: macos-latest
4444
name: isim
4545
make: PLATFORM=isim
46+
- os: ubuntu-latest
47+
name: wasm
48+
make: PLATFORM=wasm
4649

4750
defaults:
4851
run:
@@ -70,6 +73,10 @@ jobs:
7073
run: make curl/windows/libcurl.a
7174
shell: msys2 {0}
7275

76+
- name: wasm install wabt
77+
if: matrix.name == 'wasm'
78+
run: sudo apt install wabt
79+
7380
- name: build sqlite-sync
7481
run: make extension ${{ matrix.make && matrix.make || ''}}
7582

@@ -137,11 +144,68 @@ jobs:
137144
with:
138145
path: coverage
139146

147+
- name: wasm npm pack
148+
if: matrix.name == 'wasm'
149+
run: |
150+
TAG=$(git ls-remote --tags https://github.com/sqlite/sqlite-wasm.git | \
151+
awk -v ver=$(make sqlite_version) -F'/' '$NF ~ ver"-build[0-9]+$" {print $NF}' | \
152+
sort -V | \
153+
tail -n1)
154+
git clone --branch "$TAG" --depth 1 https://github.com/sqlite/sqlite-wasm.git sqlite-wasm
155+
rm -rf sqlite-wasm/sqlite-wasm/*
156+
unzip dist/sqlite-wasm.zip -d sqlite-wasm/tmp
157+
mv sqlite-wasm/tmp/sqlite-wasm-*/jswasm sqlite-wasm/sqlite-wasm
158+
159+
cd sqlite-wasm && npm i && npm run fix && npm run publint && npm run check-types && cd ..
160+
161+
PKG=sqlite-wasm/package.json
162+
TMP=sqlite-wasm/package.tmp.json
163+
164+
DESC="SQLite Wasm compiled with the automatically initialized cloudsync extension. Conveniently packaged as an ES Module for effortless integration."
165+
166+
jq \
167+
--arg name "@sqliteai/cloudsync-wasm" \
168+
--arg version "$(make sqlite_version)-cloudsync-$(make version)" \
169+
--arg desc "$DESC" \
170+
--argjson keywords '["offsync","cloudsync","sqliteai"]' \
171+
--arg repo_url "git+https://github.com/sqliteai/sqlite-sync.git" \
172+
--arg author "Gioele Cantoni ([email protected])" \
173+
--arg bugs_url "https://github.com/sqliteai/sqlite-sync/issues" \
174+
--arg homepage "https://github.com/sqliteai/sqlite-sync#readme" \
175+
'
176+
.name = $name
177+
| .version = $version
178+
| .description = $desc
179+
| .keywords += $keywords
180+
| del(.bin)
181+
| .scripts |= with_entries(select(
182+
.key != "build"
183+
and .key != "start"
184+
and .key != "start:node"
185+
and .key != "prepublishOnly"
186+
and .key != "deploy"
187+
))
188+
| .repository.url = $repo_url
189+
| .author = $author
190+
| .bugs.url = $bugs_url
191+
| .homepage = $homepage
192+
| del(.devDependencies.decompress)
193+
| del(.devDependencies["http-server"])
194+
| del(.devDependencies.shx)
195+
' "$PKG" > "$TMP" && mv "$TMP" "$PKG"
196+
197+
sed 's/@sqlite\.org\/sqlite-wasm/@sqliteai\/cloudsync-wasm/g' sqlite-wasm/README.md > sqlite-wasm/README.tmp
198+
echo -e "# sqlite-sync WASM $(make version)\nThis README and the TypeScript types are from the [official SQLite wasm repository](https://github.com/sqlite/sqlite-wasm)\n\n$(cat sqlite-wasm/README.tmp)" > sqlite-wasm/README.md
199+
rm -rf sqlite-wasm/tmp sqlite-wasm/bin sqlite-wasm/demo sqlite-wasm/README.tmp sqlite-wasm/package-lock.json
200+
cd sqlite-wasm && npm pack
201+
140202
- uses: actions/[email protected]
141203
if: always()
142204
with:
143205
name: cloudsync-${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }}
144-
path: dist/cloudsync.*
206+
path: |
207+
dist/cloudsync.*
208+
sqlite-wasm/*.tgz
145209
if-no-files-found: error
146210

147211
release:
@@ -170,8 +234,7 @@ jobs:
170234
- name: release tag version from cloudsync.h
171235
id: tag
172236
run: |
173-
FILE="src/cloudsync.h"
174-
VERSION=$(grep -oP '#define CLOUDSYNC_VERSION\s+"\K[^"]+' "$FILE")
237+
VERSION=$(make version)
175238
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
176239
LATEST=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.name')
177240
if [[ "$VERSION" != "$LATEST" || "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then
@@ -183,6 +246,18 @@ jobs:
183246
fi
184247
echo "❌ CLOUDSYNC_VERSION not found in cloudsync.h"
185248
exit 1
249+
250+
- uses: actions/setup-node@v4
251+
with:
252+
node-version: '20.x'
253+
registry-url: 'https://registry.npmjs.org'
254+
255+
- name: wasm publish to npmjs
256+
if: steps.tag.outputs.version != ''
257+
#use this version when the repo will become public run: npm publish --provenance --access public ./artifacts/cloudsync-wasm/sqlite-wasm/sqliteai-cloudsync-wasm-*-cloudsync-*.tgz
258+
run: npm publish --access public ./artifacts/cloudsync-wasm/sqlite-wasm/sqliteai-cloudsync-wasm-*-cloudsync-*.tgz
259+
env:
260+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
186261

187262
- name: zip artifacts
188263
run: |

Makefile

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ SQLITE3 ?= sqlite3
88
# set curl version to download and build
99
CURL_VERSION ?= 8.12.1
1010

11+
# set sqlite version for WASM static build
12+
SQLITE_VERSION ?= 3.49.2
13+
1114
# Set default platform if not specified
1215
ifeq ($(OS),Windows_NT)
1316
PLATFORM := windows
@@ -47,13 +50,14 @@ CURL_DIR = curl
4750
CURL_SRC = $(CURL_DIR)/src/curl-$(CURL_VERSION)
4851
COV_DIR = coverage
4952
CUSTOM_CSS = $(TEST_DIR)/sqliteai.css
53+
BUILD_WASM = build/wasm
5054

5155
SRC_FILES = $(wildcard $(SRC_DIR)/*.c)
5256
TEST_SRC = $(wildcard $(TEST_DIR)/*.c)
5357
TEST_FILES = $(SRC_FILES) $(TEST_SRC) $(wildcard $(SQLITE_DIR)/*.c)
5458
RELEASE_OBJ = $(patsubst %.c, $(BUILD_RELEASE)/%.o, $(notdir $(SRC_FILES)))
5559
TEST_OBJ = $(patsubst %.c, $(BUILD_TEST)/%.o, $(notdir $(TEST_FILES)))
56-
COV_FILES = $(filter-out $(SRC_DIR)/lz4.c $(SRC_DIR)/network.c, $(SRC_FILES))
60+
COV_FILES = $(filter-out $(SRC_DIR)/lz4.c $(SRC_DIR)/network.c $(SRC_DIR)/wasm.c, $(SRC_FILES))
5761
CURL_LIB = $(CURL_DIR)/$(PLATFORM)/libcurl.a
5862
TEST_TARGET = $(patsubst %.c,$(DIST_DIR)/%$(EXE), $(notdir $(TEST_SRC)))
5963

@@ -110,6 +114,8 @@ else ifeq ($(PLATFORM),isim)
110114
T_LDFLAGS = -framework Security
111115
CFLAGS += -arch x86_64 -arch arm64 $(SDK)
112116
CURL_CONFIG = --host=arm64-apple-darwin --with-secure-transport CFLAGS="-arch x86_64 -arch arm64 -isysroot $$(xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=11.0"
117+
else ifeq ($(PLATFORM),wasm)
118+
TARGET := $(DIST_DIR)/sqlite-wasm.zip
113119
else # linux
114120
TARGET := $(DIST_DIR)/cloudsync.so
115121
LDFLAGS += -shared -lssl -lcrypto
@@ -127,7 +133,7 @@ endif
127133
# Windows .def file generation
128134
$(DEF_FILE):
129135
ifeq ($(PLATFORM),windows)
130-
@echo "LIBRARY js.dll" > $@
136+
@echo "LIBRARY cloudsync.dll" > $@
131137
@echo "EXPORTS" >> $@
132138
@echo " sqlite3_cloudsync_init" >> $@
133139
endif
@@ -139,12 +145,32 @@ $(shell mkdir -p $(BUILD_DIRS) $(DIST_DIR))
139145
extension: $(TARGET)
140146
all: $(TARGET)
141147

148+
ifneq ($(PLATFORM),wasm)
142149
# Loadable library
143150
$(TARGET): $(RELEASE_OBJ) $(DEF_FILE) $(CURL_LIB)
144151
$(CC) $(RELEASE_OBJ) $(DEF_FILE) -o $@ $(LDFLAGS)
145152
ifeq ($(PLATFORM),windows)
146153
# Generate import library for Windows
147-
dlltool -D $@ -d $(DEF_FILE) -l $(DIST_DIR)/js.lib
154+
dlltool -D $@ -d $(DEF_FILE) -l $(DIST_DIR)/cloudsync.lib
155+
endif
156+
else
157+
#WASM build
158+
EMSDK := $(BUILD_WASM)/emsdk
159+
$(EMSDK):
160+
git clone https://github.com/emscripten-core/emsdk.git $(EMSDK)
161+
cd $(EMSDK) && ./emsdk install latest && ./emsdk activate latest
162+
163+
SQLITE_SRC := $(BUILD_WASM)/sqlite
164+
$(SQLITE_SRC): $(EMSDK)
165+
git clone --branch version-$(SQLITE_VERSION) --depth 1 https://github.com/sqlite/sqlite.git $(SQLITE_SRC)
166+
cd $(EMSDK) && . ./emsdk_env.sh && cd ../sqlite && ./configure --enable-all
167+
168+
WASM_FLAGS = emcc.jsflags += -sFETCH -pthread
169+
WASM_MAKEFILE = $(SQLITE_SRC)/ext/wasm/GNUmakefile
170+
$(TARGET): $(SQLITE_SRC) $(SRC_FILES)
171+
@grep '$(WASM_FLAGS)' '$(WASM_MAKEFILE)' >/dev/null 2>&1 || echo '$(WASM_FLAGS)' >> '$(WASM_MAKEFILE)'
172+
cd $(SQLITE_SRC)/ext/wasm && $(MAKE) dist sqlite3_wasm_extra_init.c=../../../../../src/wasm.c
173+
mv $(SQLITE_SRC)/ext/wasm/sqlite-wasm-*.zip $(TARGET)
148174
endif
149175

150176
# Test executable
@@ -266,9 +292,15 @@ endif
266292
mv $(CURL_SRC)/lib/.libs/libcurl.a $(CURL_DIR)/$(PLATFORM)
267293
rm -rf $(CURL_DIR)/src
268294

295+
# Tools
296+
sqlite_version:
297+
@echo $(SQLITE_VERSION)
298+
version:
299+
@echo $(shell sed -n 's/^#define CLOUDSYNC_VERSION[[:space:]]*"\([^"]*\)".*/\1/p' src/cloudsync.h)
300+
269301
# Clean up generated files
270302
clean:
271-
rm -rf $(BUILD_DIRS) $(DIST_DIR)/* $(COV_DIR) *.gcda *.gcno *.gcov $(CURL_DIR)/src *.sqlite
303+
rm -rf $(BUILD_DIRS) $(DIST_DIR)/* $(COV_DIR) *.gcda *.gcno *.gcov $(CURL_DIR)/src *.sqlite $(BUILD_WASM)
272304

273305
# Help message
274306
help:
@@ -283,6 +315,7 @@ help:
283315
@echo " android (needs ARCH to be set to x86_64 or arm64-v8a and ANDROID_NDK to be set)"
284316
@echo " ios (only on macOS)"
285317
@echo " isim (only on macOS)"
318+
@echo " wasm (needs wabt[brew install wabt/sudo apt install wabt])"
286319
@echo ""
287320
@echo "Targets:"
288321
@echo " all - Build the extension (default)"

src/cloudsync.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ char *db_version_build_query (sqlite3 *db) {
360360

361361
// the good news is that the query can be computed in SQLite without the need to do any extra computation from the host language
362362
const char *sql = "WITH table_names AS ("
363-
"SELECT format(\"%w\", name) as tbl_name "
363+
"SELECT format('%w', name) as tbl_name "
364364
"FROM sqlite_master "
365365
"WHERE type='table' "
366366
"AND name LIKE '%_cloudsync'"

src/cloudsync.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
#include "sqlite3.h"
1717
#endif
1818

19-
#define CLOUDSYNC_VERSION "0.8.5"
19+
#define CLOUDSYNC_VERSION "0.8.6"
2020

2121
int sqlite3_cloudsync_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi);
2222

src/dbutils.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ bool dbutils_table_sanity_check (sqlite3 *db, sqlite3_context *context, const ch
419419
// the affinity of a column is determined by the declared type of the column,
420420
// according to the following rules in the order shown:
421421
// 1. If the declared type contains the string "INT" then it is assigned INTEGER affinity.
422-
sql = sqlite3_snprintf((int)blen, buffer, "SELECT count(*) FROM pragma_table_info('%w') WHERE pk=1 AND \"type\" LIKE \"%%INT%%\";", name);
422+
sql = sqlite3_snprintf((int)blen, buffer, "SELECT count(*) FROM pragma_table_info('%w') WHERE pk=1 AND \"type\" LIKE '%%INT%%';", name);
423423
sqlite3_int64 count2 = dbutils_int_select(db, sql);
424424
if (count == count2) {
425425
dbutils_context_result_error(context, "Table %s uses an single-column INTEGER primary key. For CRDT replication, primary keys must be globally unique. Consider using a TEXT primary key with UUIDs or ULID to avoid conflicts across nodes. If you understand the risk and still want to use this INTEGER primary key, set the third argument of the cloudsync_init function to 1 to skip this check.", name);

0 commit comments

Comments
 (0)