From 849c61b571305f659c651208c28c7bcbe47801bc Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 16 May 2025 09:47:23 +0200 Subject: [PATCH 001/103] fix(minor): sqlitecloud to sqliteai --- README.md | 2 +- src/cloudsync.c | 6 +++--- src/vtab.c | 2 +- test/{sqlitecloud.css => sqliteai.css} | 2 +- test/{unittest.c => unit.c} | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename test/{sqlitecloud.css => sqliteai.css} (96%) rename test/{unittest.c => unit.c} (99%) diff --git a/README.md b/README.md index f63e256..b3af60a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ To use SQLiteSync, you need SQLite version 3.x or later. 1. Clone the repository: ```bash - git clone https://github.com/sqlitecloud/sqlitesync.git + git clone https://github.com/sqliteai/sqlite-sync.git cd sqlitesync ``` diff --git a/src/cloudsync.c b/src/cloudsync.c index 95b9bda..72b2963 100644 --- a/src/cloudsync.c +++ b/src/cloudsync.c @@ -635,7 +635,7 @@ int table_add_stmts (sqlite3 *db, cloudsync_table_context *table, int ncols) { if (rc != SQLITE_OK) goto cleanup; // precompile the update rows from meta when pk changes - // see https://github.com/sqlitecloud/cloudsync/blob/main/docs/PriKey.md for more details + // see https://github.com/sqliteai/sqlite-sync/blob/main/docs/PriKey.md for more details sql = cloudsync_memory_mprintf("UPDATE OR REPLACE \"%w_cloudsync\" SET pk=?, db_version=?, col_version=1, seq=cloudsync_seq(), site_id=0 WHERE (pk=? AND col_name!='%s');", table->name, CLOUDSYNC_TOMBSTONE_VALUE); if (!sql) {rc = SQLITE_NOMEM; goto cleanup;} DEBUG_SQL("meta_update_move_stmt: %s", sql); @@ -1763,7 +1763,7 @@ int local_update_move_meta (sqlite3 *db, cloudsync_table_context *table, const c * from OLD.pk to NEW.pk gets a distinct `seq` to maintain proper versioning and ordering of changes. */ - // see https://github.com/sqlitecloud/cloudsync/blob/main/docs/PriKey.md for more details + // see https://github.com/sqliteai/sqlite-sync/blob/main/docs/PriKey.md for more details // pk2 is the old pk sqlite3_stmt *vm = table->meta_update_move_stmt; @@ -2437,7 +2437,7 @@ void cloudsync_update (sqlite3_context *context, int argc, sqlite3_value **argv) // move non-sentinel metadata entries from OLD primary key to NEW primary key // handles the case where some metadata is retained across primary key change - // see https://github.com/sqlitecloud/cloudsync/blob/main/docs/PriKey.md for more details + // see https://github.com/sqliteai/sqlite-sync/blob/main/docs/PriKey.md for more details rc = local_update_move_meta(db, table, pk, pklen, oldpk, oldpklen, db_version); if (rc != SQLITE_OK) goto cleanup; diff --git a/src/vtab.c b/src/vtab.c index f0fe1a3..b38635b 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -462,7 +462,7 @@ int cloudsync_changesvtab_rowid (sqlite3_vtab_cursor *cursor, sqlite3_int64 *row sqlite3_int64 seq = sqlite3_column_int64(c->vm, COL_SEQ_INDEX); sqlite3_int64 db_version = sqlite3_column_int64(c->vm, COL_DBVERSION_INDEX); - // for an explanation see https://github.com/sqlitecloud/cloudsync/blob/main/docs/RowID.md + // for an explanation see https://github.com/sqliteai/sqlite-sync/blob/main/docs/RowID.md *rowid = (db_version << 30) | seq; return SQLITE_OK; } diff --git a/test/sqlitecloud.css b/test/sqliteai.css similarity index 96% rename from test/sqlitecloud.css rename to test/sqliteai.css index d6aaf1b..13e5a6b 100644 --- a/test/sqlitecloud.css +++ b/test/sqliteai.css @@ -1,4 +1,4 @@ -/* sqlitecloud.css - Custom styling for genhtml output */ +/* sqliteai.css - Custom styling for genhtml output */ /* General layout adjustments */ body { diff --git a/test/unittest.c b/test/unit.c similarity index 99% rename from test/unittest.c rename to test/unit.c index 0de8697..c6e4d13 100644 --- a/test/unittest.c +++ b/test/unit.c @@ -1473,7 +1473,7 @@ bool do_test_compare (sqlite3 *db, bool print_result) { bool do_test_rowid (int ntest, bool print_result) { for (int i=0; i Date: Fri, 16 May 2025 09:47:35 +0200 Subject: [PATCH 002/103] update gitignore --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index e43b0f9..2361461 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,9 @@ .DS_Store +*.xcworkspacedata +*.xcuserstate +*.xcbkptlist +*.plist +/build +/dist +/coverage +*.sqlite \ No newline at end of file From f1e70e972e74162ed57a7c4e2f5f4712f2335626 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 16 May 2025 09:47:49 +0200 Subject: [PATCH 003/103] makefile refactor --- Makefile | 240 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 134 insertions(+), 106 deletions(-) diff --git a/Makefile b/Makefile index c62f0b3..c2b73fa 100644 --- a/Makefile +++ b/Makefile @@ -1,132 +1,160 @@ -# Makefile +# Makefile for SQLite Sync Extension +# Supports compilation for Linux, macOS, Windows, Android and iOS -# Compiler and flags -CC = gcc -NO_COVERAGE_FLAGS = -Wall -Wextra -Wno-unused-parameter -I$(SRC_DIR) -I$(TEST_DIR) -I$(SQLITE_DIR) -I$(CURL_DIR) -CFLAGS = $(NO_COVERAGE_FLAGS) -DCLOUDSYNC_OMIT_NETWORK=1 -DCLOUDSYNC_OMIT_PRINT_RESULT=1 -fprofile-arcs -ftest-coverage -EXTENSION_FLAGS = $(NO_COVERAGE_FLAGS) -O3 -fPIC - -ifeq ($(shell uname -s),Darwin) -CONFIG_DARWIN=y -else ifeq ($(OS),Windows_NT) -CONFIG_WINDOWS=y +# Set default platform if not specified +ifeq ($(OS),Windows_NT) + PLATFORM := windows else -CONFIG_LINUX=y -endif - -ifdef CONFIG_DARWIN -LOADABLE_EXTENSION=dylib -endif - -ifdef CONFIG_LINUX -LOADABLE_EXTENSION=so -CFLAGS += -lm -endif - -ifdef CONFIG_WINDOWS -LOADABLE_EXTENSION=dll + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + PLATFORM := macos + else + PLATFORM := linux + endif endif +# Compiler and flags +CC = gcc +CFLAGS = -Wall -Wextra -Wno-unused-parameter -I$(SRC_DIR) -I$(SQLITE_DIR) -I$(CURL_DIR) +TEST_FLAGS = $(CFLAGS) -DSQLITE_CORE -DCLOUDSYNC_UNITTEST -DCLOUDSYNC_OMIT_NETWORK -DCLOUDSYNC_OMIT_PRINT_RESULT -fprofile-arcs -ftest-coverage +EXTENSION_FLAGS = $(CFLAGS) -O3 -fPIC LDFLAGS = -lcurl +COVERAGE = false -# setting the -lgcov flag on macOS leads to a linker error -ifeq ($(shell uname), Linux) - LDFLAGS += -lgcov - DYLIB_LDFLAGS = -else - LDFLAGS += - DYLIB_LDFLAGS = -dynamiclib -endif - -# Directories and files +# Directories SRC_DIR = src +DIST_DIR = dist TEST_DIR = test SQLITE_DIR = sqlite -OBJ_DIR = obj -CURL_DIR = network/curl/macos +VPATH = $(SRC_DIR):$(SQLITE_DIR):$(TEST_DIR) +BUILD_RELEASE = build/release +BUILD_TEST = build/test +BUILD_DIRS = $(BUILD_TEST) $(BUILD_RELEASE) +CURL_DIR = network/curl/$(PLATFORM) COV_DIR = coverage -CUSTOM_CSS = $(TEST_DIR)/sqlitecloud.css -TARGET_PREFIX=dist - -TARGET_NAME=cloudsync -TARGET_LOADABLE=$(TARGET_PREFIX)/$(TARGET_NAME).$(LOADABLE_EXTENSION) -TARGET_STATIC=$(TARGET_PREFIX)/libsqlite_$(TARGET_NAME)0.a -TARGET_STATIC_H=$(TARGET_PREFIX)/sqlite-$(TARGET_NAME).h -TARGET_CLI=$(TARGET_PREFIX)/sqlite3 +CUSTOM_CSS = $(TEST_DIR)/sqliteai.css # Files and objects -LIB_HEADERS = $(wildcard $(SRC_DIR)/*.h) $(wildcard $(SQLITE_DIR)/*.h) -SRC_FILES = $(filter-out $(SQLITE_DIR)/sqlite3.c $(TEST_DIR)/unittest.c $(SRC_DIR)/lz4.c, $(wildcard $(SRC_DIR)/*.c) $(wildcard $(TEST_DIR)/*.c) $(wildcard $(SQLITE_DIR)/*.c)) -LIB_OBJ_FILES = $(patsubst %.c, $(TARGET_PREFIX)/$(OBJ_DIR)/%.o, $(notdir $(SRC_FILES))) -LIB_SQLITE_OBJ = $(TARGET_PREFIX)/$(OBJ_DIR)/sqlite3.o +ifeq ($(PLATFORM),windows) + TEST_TARGET := $(DIST_DIR)/test.exe +else + TEST_TARGET := $(DIST_DIR)/test +endif +SRC_FILES = $(wildcard $(SRC_DIR)/*.c) +TEST_FILES = $(SRC_FILES) $(wildcard $(TEST_DIR)/*.c) $(wildcard $(SQLITE_DIR)/*.c) +RELEASE_OBJ = $(patsubst %.c, $(BUILD_RELEASE)/%.o, $(notdir $(SRC_FILES))) +TEST_OBJ = $(patsubst %.c, $(BUILD_TEST)/%.o, $(notdir $(TEST_FILES))) +COV_FILES = $(filter-out $(SRC_DIR)/lz4.c $(SRC_DIR)/network.c, $(SRC_FILES)) + +# Platform-specific settings +ifeq ($(PLATFORM),windows) + TARGET := $(DIST_DIR)/cloudsync.dll + LDFLAGS += -shared + # Create .def file for Windows + DEF_FILE := $(BUILD_RELEASE)/cloudsync.def +else ifeq ($(PLATFORM),macos) + TARGET := $(DIST_DIR)/cloudsync.dylib + LDFLAGS += -dynamiclib -undefined dynamic_lookup + # macOS-specific flags + CFLAGS += -arch x86_64 -arch arm64 +else ifeq ($(PLATFORM),android) + # Use Android NDK's Clang compiler, the user should set the CC + # example CC=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android26-clang + ifeq ($(filter %-clang,$(CC)),) + $(error "CC must be set to the Android NDK's Clang compiler") + endif + TARGET := $(DIST_DIR)/cloudsync.so + LDFLAGS += -shared -lm + # Android-specific flags + CFLAGS += -D__ANDROID__ +else ifeq ($(PLATFORM),ios) + TARGET := $(DIST_DIR)/cloudsync.dylib + SDK := -isysroot $(shell xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=11.0 + LDFLAGS += -dynamiclib $(SDK) + # iOS-specific flags + CFLAGS += -arch arm64 $(SDK) +else ifeq ($(PLATFORM),isim) + TARGET := $(DIST_DIR)/cloudsync.dylib + SDK := -isysroot $(shell xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=11.0 + LDFLAGS += -dynamiclib $(SDK) + # iphonesimulator-specific flags + CFLAGS += -arch x86_64 -arch arm64 $(SDK) +else # linux + TARGET := $(DIST_DIR)/cloudsync.so + LDFLAGS += -shared + CFLAGS += -lm + TEST_FLAGS += -lgcov +endif + +# Windows .def file generation +$(DEF_FILE): +ifeq ($(PLATFORM),windows) + @echo "LIBRARY js.dll" > $@ + @echo "EXPORTS" >> $@ + @echo " sqlite3_js_init" >> $@ + @echo " sqlitejs_version" >> $@ + @echo " quickjs_version" >> $@ +endif -SRC_FILES = $(filter-out $(SQLITE_DIR)/sqlite3.c $(TEST_DIR)/unittest.c $(SRC_DIR)/lz4.c, $(wildcard $(SRC_DIR)/*.c) $(wildcard $(TEST_DIR)/*.c) $(wildcard $(SQLITE_DIR)/*.c)) -OBJ_FILES = $(patsubst %.c, $(OBJ_DIR)/%.o, $(notdir $(SRC_FILES))) -LZ4_OBJ = $(OBJ_DIR)/lz4.o -SQLITE_OBJ = $(OBJ_DIR)/sqlite3.o -UNIT_TEST_OBJ = $(OBJ_DIR)/unittest.o -HEADERS = $(wildcard $(SRC_DIR)/*.h) $(wildcard $(TEST_DIR)/*.h) $(wildcard $(SQLITE_DIR)/*.h) +# Make sure the build and dist directories exist +$(shell mkdir -p $(BUILD_DIRS) $(DIST_DIR)) # Default target -extension: $(TARGET_LOADABLE) -all: $(TARGET_LOADABLE) +extension: $(TARGET) +all: $(TARGET) # Loadable library -$(TARGET_LOADABLE): $(LIB_OBJ_FILES) $(LIB_SQLITE_OBJ) $(LZ4_OBJ) $(TARGET_PREFIX) - $(CC) $(LIB_OBJ_FILES) $(LZ4_OBJ) $(LIB_SQLITE_OBJ) -o $@ $(LDFLAGS) $(DYLIB_LDFLAGS) - -# Object files for the lib (with coverage flags) -$(TARGET_PREFIX)/$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(LIB_HEADERS) | $(TARGET_PREFIX)/$(OBJ_DIR) - $(CC) $(EXTENSION_FLAGS) -c $< -o $@ -$(LIB_SQLITE_OBJ): $(SQLITE_DIR)/sqlite3.c $(LIB_HEADERS) | $(TARGET_PREFIX)/$(OBJ_DIR) - $(CC) $(EXTENSION_FLAGS) -c $< -o $@ - -# Object files (with coverage flags) -$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(HEADERS) | $(OBJ_DIR) - $(CC) $(CFLAGS) -c $< -o $@ - -$(OBJ_DIR)/%.o: $(TEST_DIR)/%.c $(HEADERS) | $(OBJ_DIR) - $(CC) $(CFLAGS) -c $< -o $@ - -$(OBJ_DIR)/%.o: $(SQLITE_DIR)/%.c $(HEADERS) | $(OBJ_DIR) - $(CC) $(CFLAGS) -c $< -o $@ - -# Compile lz4.c without coverage flags -$(LZ4_OBJ): $(SRC_DIR)/lz4.c $(HEADERS) | $(OBJ_DIR) - $(CC) $(NO_COVERAGE_FLAGS) -c $< -o $@ - -# Compile sqlite3.c without coverage flags -$(SQLITE_OBJ): $(SQLITE_DIR)/sqlite3.c $(HEADERS) | $(OBJ_DIR) - $(CC) $(NO_COVERAGE_FLAGS) -DSQLITE_CORE=1 -c $< -o $@ - -# Compile unittest.c without coverage flags -$(UNIT_TEST_OBJ): $(TEST_DIR)/unittest.c $(HEADERS) | $(OBJ_DIR) - $(CC) $(CFLAGS) -c $< -o $@ - -# Create object directory if not exists -$(OBJ_DIR): - mkdir -p $(OBJ_DIR) -$(TARGET_PREFIX)/$(OBJ_DIR): - mkdir -p $(TARGET_PREFIX)/$(OBJ_DIR) +$(TARGET): $(RELEASE_OBJ) $(DEF_FILE) + $(CC) $(LDFLAGS) $? -o $@ +ifeq ($(PLATFORM),windows) + # Generate import library for Windows + dlltool -D $@ -d $(DEF_FILE) -l $(DIST_DIR)/js.lib +endif -$(TARGET_PREFIX): - mkdir -p $(TARGET_PREFIX) +# Test executable +$(TEST_TARGET): $(TEST_OBJ) + $(CC) $(TEST_FLAGS) $? -o $@ -# Build unit test executable -UNIT_TEST = unittest -$(UNIT_TEST): CFLAGS += -DSQLITE_CORE=1 -DCLOUDSYNC_UNITTEST=1 -$(UNIT_TEST): $(OBJ_FILES) $(SQLITE_OBJ) $(LZ4_OBJ) $(UNIT_TEST_OBJ) - $(CC) $(CFLAGS) $(OBJ_FILES) $(LZ4_OBJ) $(SQLITE_OBJ) $(UNIT_TEST_OBJ) -o $@ $(LDFLAGS) +# Object files +$(BUILD_RELEASE)/%.o: %.c + $(CC) $(EXTENSION_FLAGS) -c $< -o $@ +$(BUILD_TEST)/sqlite3.o: $(SQLITE_DIR)/sqlite3.c + $(CC) $(CFLAGS) -DSQLITE_CORE=1 -c $< -o $@ +$(BUILD_TEST)/%.o: %.c + $(CC) $(TEST_FLAGS) -c $< -o $@ # Run code coverage (--css-file $(CUSTOM_CSS)) -coverage: $(UNIT_TEST) - ./$(UNIT_TEST) +test: $(TARGET) $(TEST_TARGET) + sqlite3 ":memory:" -cmd ".bail on" ".load ./$<" "SELECT cloudsync_version();" + ./$(TEST_TARGET) +ifneq ($(COVERAGE),false) mkdir -p $(COV_DIR) - lcov --capture --directory . --output-file $(COV_DIR)/coverage.info + lcov --capture --directory . --output-file $(COV_DIR)/coverage.info $(subst src, --include src,${COV_FILES}) genhtml $(COV_DIR)/coverage.info --output-directory $(COV_DIR) +endif # Clean up generated files clean: - rm -rf $(OBJ_DIR) $(UNIT_TEST) $(COV_DIR) *.gcda *.gcno *.gcov dist/* - -.PHONY: all clean coverage extension $(DEPS) + rm -rf $(BUILD_DIRS) $(DIST_DIR)/* $(COV_DIR) *.gcda *.gcno *.gcov + +# Help message +help: + @echo "SQLite Sync Extension Makefile" + @echo "Usage:" + @echo " make [PLATFORM=platform] [target]" + @echo "" + @echo "Platforms:" + @echo " linux (default on Linux)" + @echo " macos (default on macOS)" + @echo " windows (default on Windows)" + @echo " android (needs CC to be set to Android NDK's Clang compiler)" + @echo " ios (only on macOS)" + @echo " isim (only on macOS)" + @echo "" + @echo "Targets:" + @echo " all - Build the extension (default)" + @echo " clean - Remove built files" + @echo " test [COVERAGE=true] - Test the extension with optional coverage output" + @echo " help - Display this help message" + +.PHONY: all clean test extension help From f66c38ec97b9c2739258744fcfca8e1671b4af89 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 16 May 2025 09:53:18 +0200 Subject: [PATCH 004/103] update build workflow, with test and release of sqlite-sync --- .github/workflows/main.yml | 130 ++++++++++++++++++++++++++++++++++--- 1 file changed, 121 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 24c8b80..0156ce7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,13 +1,14 @@ -name: build sqlitesync +name: build, test and release sqlite-sync on: push: - branches: - - main + +permissions: + contents: write jobs: build: runs-on: ${{ matrix.os }} - name: build for ${{ matrix.name }}-${{ matrix.arch }} + name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'isim' && matrix.name != 'ios' && ' + test' || ''}} timeout-minutes: 20 strategy: fail-fast: false @@ -34,7 +35,7 @@ jobs: configure: --with-schannel name: windows - os: ubuntu-latest - arch: arm64 + arch: arm64-v8a configure: --host aarch64-linux-android26 --with-openssl=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr @@ -47,6 +48,9 @@ jobs: RANLIB=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib STRIP=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip name: android + make: + PLATFORM=android + CC=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android26-clang - os: ubuntu-latest arch: x86_64 configure: @@ -61,6 +65,10 @@ jobs: RANLIB=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib STRIP=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip name: android + make: + PLATFORM=android + CC=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android26-clang + sqlite-amalgamation-zip: https://sqlite.org/2025/sqlite-amalgamation-3490100.zip - os: macos-latest arch: arm64 configure: @@ -68,6 +76,7 @@ jobs: --with-secure-transport CFLAGS="-arch arm64 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=11.0" name: ios + make: PLATFORM=ios - os: macos-latest arch: arm64 configure: @@ -75,6 +84,7 @@ jobs: --with-secure-transport CFLAGS="-arch arm64 -isysroot $(xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=11.0" name: isim + make: PLATFORM=isim - os: macos-latest arch: x86_64 configure: @@ -82,6 +92,7 @@ jobs: --with-secure-transport CFLAGS="-arch x86_64 -isysroot $(xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=11.0" name: isim + make: PLATFORM=isim defaults: run: @@ -208,12 +219,113 @@ jobs: mkdir -p network/curl/${{ matrix.name }} mv $folder/lib/.libs/libcurl.a network/curl/${{ matrix.name }}/lib${{matrix.arch}}.a + - name: build sqlite-sync + run: make ${{ matrix.make && matrix.make || ''}} + + - name: windows install sqlite3 + if: matrix.os == 'windows-latest' + run: choco install sqlite -y + + - name: macos install sqlite3 without SQLITE_OMIT_LOAD_EXTENSION + if: matrix.name == 'macos' + run: brew link sqlite --force + + - name: android setup test environment + if: matrix.name == 'android' && matrix.arch != 'arm64-v8a' + run: | + + echo "::group::enable kvm group perms" + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + echo "::endgroup::" + + echo "::group::download and build sqlite3 without SQLITE_OMIT_LOAD_EXTENSION" + curl -O ${{ matrix.sqlite-amalgamation-zip }} + unzip sqlite-amalgamation-*.zip + export ${{ matrix.make }} + $CC sqlite-amalgamation-*/shell.c sqlite-amalgamation-*/sqlite3.c -o sqlite3 -ldl + rm -rf sqlite-amalgamation-*.zip sqlite-amalgamation-* + echo "::endgroup::" + + echo "::group::prepare the test script" + make test CC=$CC PLATFORM=$PLATFORM || echo "It should fail. Running remaining commands in the emulator" + cat > commands.sh << EOF + mv -f /data/local/tmp/sqlite3 /system/xbin + cd /data/local/tmp + $(make test CC=$CC PLATFORM=$PLATFORM -n) + EOF + echo "::endgroup::" + + - name: android test sqlite-sync + if: matrix.name == 'android' && matrix.arch != 'arm64-v8a' + uses: reactivecircus/android-emulator-runner@v2.34.0 + with: + api-level: 26 + arch: ${{ matrix.arch }} + script: | + adb root + adb remount + adb push ${{ github.workspace }}/. /data/local/tmp/ + adb shell "sh /data/local/tmp/commands.sh" + + - name: test sqlite-sync + if: matrix.name == 'linux' || matrix.name == 'macos' || matrix.name == 'windows' + run: make test + - uses: actions/upload-artifact@v4.6.2 with: - name: libcurl-${{ matrix.name }}-${{ matrix.arch }} - path: network/curl/${{ matrix.name }}/lib${{matrix.arch}}.a + name: cloudsync-${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} + path: dist/cloudsync.* if-no-files-found: error - - name: build sqlitesync + release: + runs-on: ubuntu-latest + name: release + needs: build + if: github.ref == 'refs/heads/main' + + env: + GH_TOKEN: ${{ github.token }} + + steps: + + - uses: actions/checkout@v4.2.2 + + - uses: actions/download-artifact@v4.2.1 + with: + path: artifacts + + - name: release tag version from cloudsync.h + id: tag + run: | + FILE="src/cloudsync.h" + VERSION=$(grep -oP '#define CLOUDSYNC_VERSION\s+"\K[^"]+' "$FILE") + if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "version=$VERSION" >> $GITHUB_OUTPUT + exit 0 + fi + echo "❌ CLOUDSYNC_VERSION not found in cloudsync.h" + exit 1 + + - name: zip artifacts run: | - echo "TODO: build sqlitesync" + for folder in "artifacts"/*; do + if [ -d "$folder" ]; then + name=$(basename "$folder") + zip -jq "${name}-${{ steps.tag.outputs.version }}.zip" "$folder"/* + tar -cJf "${name}-${{ steps.tag.outputs.version }}.tar.xz" -C "$folder" . + tar -czf "${name}-${{ steps.tag.outputs.version }}.tar.gz" -C "$folder" . + fi + done + + - uses: softprops/action-gh-release@v2.2.1 + with: + generate_release_notes: true + tag_name: ${{ steps.tag.outputs.version }} + files: | + cloudsync-*-${{ steps.tag.outputs.version }}.zip + cloudsync-*-${{ steps.tag.outputs.version }}.tar.xz + cloudsync-*-${{ steps.tag.outputs.version }}.tar.gz + make_latest: true + From c9fa42bcaf0527477672475f224934a30e27485e Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 16 May 2025 10:29:22 +0200 Subject: [PATCH 005/103] fix(windows): choco not in msys2 path --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0156ce7..02e2d5d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -225,6 +225,7 @@ jobs: - name: windows install sqlite3 if: matrix.os == 'windows-latest' run: choco install sqlite -y + shell: bash - name: macos install sqlite3 without SQLITE_OMIT_LOAD_EXTENSION if: matrix.name == 'macos' From b7c35cdf89a6ce83c80764f5c42271fd01fc7a4c Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 16 May 2025 13:49:27 +0000 Subject: [PATCH 006/103] fix(linux): missing uuid --- .github/workflows/main.yml | 4 ++++ README.md | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 02e2d5d..5775754 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -219,6 +219,10 @@ jobs: mkdir -p network/curl/${{ matrix.name }} mv $folder/lib/.libs/libcurl.a network/curl/${{ matrix.name }}/lib${{matrix.arch}}.a + - name: linux install uuid-dev + if: matrix.name == 'linux' + run: sudo apt-get install -y uuid-dev + - name: build sqlite-sync run: make ${{ matrix.make && matrix.make || ''}} diff --git a/README.md b/README.md index b3af60a..10b64a4 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,21 @@ To use SQLiteSync, you need SQLite version 3.x or later. ### Build from Source -1. Clone the repository: +1. Install dependencies: + + ```bash + #linux + sudo apt-get install -y uuid-dev + ``` + +2. Clone the repository: ```bash git clone https://github.com/sqliteai/sqlite-sync.git cd sqlitesync ``` -2. Build the extension: +3. Build the extension: ```bash make From 2c8ae2da754b48b0c45b338a14303936117aa035 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 16 May 2025 14:14:43 +0000 Subject: [PATCH 007/103] fix(linux): curl linking after other objects --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c2b73fa..0f3be15 100644 --- a/Makefile +++ b/Makefile @@ -105,7 +105,7 @@ all: $(TARGET) # Loadable library $(TARGET): $(RELEASE_OBJ) $(DEF_FILE) - $(CC) $(LDFLAGS) $? -o $@ + $(CC) $? -o $@ $(LDFLAGS) ifeq ($(PLATFORM),windows) # Generate import library for Windows dlltool -D $@ -d $(DEF_FILE) -l $(DIST_DIR)/js.lib From a66125c130dbfb6d7b423d55e6c05dcd1c0158e8 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 16 May 2025 14:16:38 +0000 Subject: [PATCH 008/103] new curl.h include path --- src/network.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network.c b/src/network.c index f665d86..d8fa10b 100644 --- a/src/network.c +++ b/src/network.c @@ -11,7 +11,7 @@ #include "network.h" #include "dbutils.h" #include "utils.h" -#include "curl/curl.h" +#include "network/curl/curl.h" #define CLOUDSYNC_ENDPOINT_PREFIX "v1/cloudsync" #define CLOUDSYNC_ENDPOINT_UPLOAD "upload" From 98f0a525f003235952a3061c34430ea06044dd8b Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 16 May 2025 14:22:47 +0000 Subject: [PATCH 009/103] curl library name from libARCH.a to libcurl.a --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5775754..554017f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -217,7 +217,8 @@ jobs: cd .. mkdir -p network/curl/${{ matrix.name }} - mv $folder/lib/.libs/libcurl.a network/curl/${{ matrix.name }}/lib${{matrix.arch}}.a + ls -lah $folder/lib/.libs/ + mv $folder/lib/.libs/libcurl.a network/curl/${{ matrix.name }} - name: linux install uuid-dev if: matrix.name == 'linux' From 9c7363ce6e993e5729d7ca760cdd1802ee649602 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Fri, 16 May 2025 15:28:15 +0000 Subject: [PATCH 010/103] fix(linux): link curl static lib --- .github/workflows/main.yml | 1 - Makefile | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 554017f..e8763fc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -217,7 +217,6 @@ jobs: cd .. mkdir -p network/curl/${{ matrix.name }} - ls -lah $folder/lib/.libs/ mv $folder/lib/.libs/libcurl.a network/curl/${{ matrix.name }} - name: linux install uuid-dev diff --git a/Makefile b/Makefile index 0f3be15..a776fdd 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ CC = gcc CFLAGS = -Wall -Wextra -Wno-unused-parameter -I$(SRC_DIR) -I$(SQLITE_DIR) -I$(CURL_DIR) TEST_FLAGS = $(CFLAGS) -DSQLITE_CORE -DCLOUDSYNC_UNITTEST -DCLOUDSYNC_OMIT_NETWORK -DCLOUDSYNC_OMIT_PRINT_RESULT -fprofile-arcs -ftest-coverage EXTENSION_FLAGS = $(CFLAGS) -O3 -fPIC -LDFLAGS = -lcurl +LDFLAGS = -L/$(CURL_DIR)/$(PLATFORM) -lcurl COVERAGE = false # Directories @@ -30,7 +30,7 @@ VPATH = $(SRC_DIR):$(SQLITE_DIR):$(TEST_DIR) BUILD_RELEASE = build/release BUILD_TEST = build/test BUILD_DIRS = $(BUILD_TEST) $(BUILD_RELEASE) -CURL_DIR = network/curl/$(PLATFORM) +CURL_DIR = network/curl COV_DIR = coverage CUSTOM_CSS = $(TEST_DIR)/sqliteai.css From 8a029e2a224d826d3e3dfc8dd5adab2a83c1bc12 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 19 May 2025 10:32:12 +0200 Subject: [PATCH 011/103] feat(apple): add fatlib curl --- .github/workflows/main.yml | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e8763fc..dfc8951 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,12 +23,9 @@ jobs: configure: --with-openssl name: linux - os: macos-latest - arch: arm64 - configure: --with-secure-transport - name: macos - - os: macos-13 - arch: x86_64 - configure: --with-secure-transport + configure: + --with-secure-transport + CFLAGS="-arch x86_64 -arch arm64" name: macos - os: windows-latest arch: x86_64 @@ -78,19 +75,10 @@ jobs: name: ios make: PLATFORM=ios - os: macos-latest - arch: arm64 configure: --host=arm64-apple-darwin --with-secure-transport - CFLAGS="-arch arm64 -isysroot $(xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=11.0" - name: isim - make: PLATFORM=isim - - os: macos-latest - arch: x86_64 - configure: - --host=x86_64-apple-darwin - --with-secure-transport - CFLAGS="-arch x86_64 -isysroot $(xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=11.0" + CFLAGS="-arch x86_64 -arch arm64 -isysroot $(xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=11.0" name: isim make: PLATFORM=isim @@ -332,5 +320,4 @@ jobs: cloudsync-*-${{ steps.tag.outputs.version }}.zip cloudsync-*-${{ steps.tag.outputs.version }}.tar.xz cloudsync-*-${{ steps.tag.outputs.version }}.tar.gz - make_latest: true - + make_latest: true \ No newline at end of file From 0cf75d955b0e9884ab97348c6a580ca5a81fbed9 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 19 May 2025 10:37:49 +0200 Subject: [PATCH 012/103] gitignore add static libs --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2361461..7a54bd1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ /build /dist /coverage -*.sqlite \ No newline at end of file +*.sqlite +*.a \ No newline at end of file From 61cdbbd932e2dfa52f18c02ef1463cd9bbc9b697 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 19 May 2025 10:47:57 +0200 Subject: [PATCH 013/103] fix(curl): include and link path --- Makefile | 4 ++-- src/network.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index a776fdd..dd92561 100644 --- a/Makefile +++ b/Makefile @@ -15,10 +15,10 @@ endif # Compiler and flags CC = gcc -CFLAGS = -Wall -Wextra -Wno-unused-parameter -I$(SRC_DIR) -I$(SQLITE_DIR) -I$(CURL_DIR) +CFLAGS = -Wall -Wextra -Wno-unused-parameter -I$(SRC_DIR) -I$(SQLITE_DIR) -I$(CURL_DIR)/include TEST_FLAGS = $(CFLAGS) -DSQLITE_CORE -DCLOUDSYNC_UNITTEST -DCLOUDSYNC_OMIT_NETWORK -DCLOUDSYNC_OMIT_PRINT_RESULT -fprofile-arcs -ftest-coverage EXTENSION_FLAGS = $(CFLAGS) -O3 -fPIC -LDFLAGS = -L/$(CURL_DIR)/$(PLATFORM) -lcurl +LDFLAGS = -L./$(CURL_DIR)/$(PLATFORM) -lcurl COVERAGE = false # Directories diff --git a/src/network.c b/src/network.c index d8fa10b..f665d86 100644 --- a/src/network.c +++ b/src/network.c @@ -11,7 +11,7 @@ #include "network.h" #include "dbutils.h" #include "utils.h" -#include "network/curl/curl.h" +#include "curl/curl.h" #define CLOUDSYNC_ENDPOINT_PREFIX "v1/cloudsync" #define CLOUDSYNC_ENDPOINT_UPLOAD "upload" From 0aaf1010d1ce567785804fa361f0f138fa0e4d55 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 19 May 2025 11:37:11 +0200 Subject: [PATCH 014/103] always upload artifacts to debug them --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dfc8951..4c3f7ce 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -267,6 +267,7 @@ jobs: run: make test - uses: actions/upload-artifact@v4.6.2 + if: always() with: name: cloudsync-${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} path: dist/cloudsync.* From e906c1a43286711a8268c8c14c19bc515646f400 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 19 May 2025 11:57:27 +0200 Subject: [PATCH 015/103] fix(apple): missing multiarch args in linker flags --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index dd92561..af6780f 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ ifeq ($(PLATFORM),windows) DEF_FILE := $(BUILD_RELEASE)/cloudsync.def else ifeq ($(PLATFORM),macos) TARGET := $(DIST_DIR)/cloudsync.dylib - LDFLAGS += -dynamiclib -undefined dynamic_lookup + LDFLAGS += -arch x86_64 -arch arm64 -dynamiclib -undefined dynamic_lookup # macOS-specific flags CFLAGS += -arch x86_64 -arch arm64 else ifeq ($(PLATFORM),android) @@ -76,7 +76,7 @@ else ifeq ($(PLATFORM),ios) else ifeq ($(PLATFORM),isim) TARGET := $(DIST_DIR)/cloudsync.dylib SDK := -isysroot $(shell xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=11.0 - LDFLAGS += -dynamiclib $(SDK) + LDFLAGS += -arch x86_64 -arch arm64 -dynamiclib $(SDK) # iphonesimulator-specific flags CFLAGS += -arch x86_64 -arch arm64 $(SDK) else # linux From f127f3c59b0ce2ad13b1ec6922fa0a1adace4368 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Mon, 19 May 2025 12:28:22 +0200 Subject: [PATCH 016/103] rm macos specific libcurl.a --- network/curl/macos/libcurl.a | Bin 546960 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 network/curl/macos/libcurl.a diff --git a/network/curl/macos/libcurl.a b/network/curl/macos/libcurl.a deleted file mode 100644 index f8a9990071dfa13cd063ff8b5aec59aa937610fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 546960 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