Skip to content

Commit 0eba6df

Browse files
committed
Fix portable Linux build: gcc -static on Ubuntu, no Docker
- Portable build uses gcc -static on native Ubuntu runner - Rename PAGE_SIZE -> CBM_PAGE_SIZE (conflicts with musl limits.h)
1 parent 9662063 commit 0eba6df

File tree

2 files changed

+58
-55
lines changed

2 files changed

+58
-55
lines changed

.github/workflows/_build.yml

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,8 @@ jobs:
136136
path: "*.zip"
137137

138138
build-linux-portable:
139-
# Static musl builds via `docker run alpine` inside Ubuntu runners.
140-
# Can't use `container: alpine` on ARM64 (GitHub Actions JS actions break).
141-
# Checkout happens on Ubuntu host, build runs inside Alpine container.
139+
# Fully static Linux binaries (gcc -static on Ubuntu).
140+
# Runs on any Linux distro without shared library dependencies.
142141
strategy:
143142
fail-fast: false
144143
matrix:
@@ -148,35 +147,39 @@ jobs:
148147
- arch: arm64
149148
runner: ubuntu-24.04-arm
150149
runs-on: ${{ matrix.runner }}
151-
timeout-minutes: 20
150+
timeout-minutes: 15
152151
steps:
153152
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
154153

155-
- name: Build standard binary (Alpine static)
154+
- name: Install deps
155+
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
156+
157+
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
158+
with:
159+
node-version: "22"
160+
161+
- name: Build standard binary (static)
162+
run: scripts/build.sh ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=gcc CXX=g++ STATIC=1
163+
164+
- name: Verify static linking
165+
run: |
166+
file build/c/codebase-memory-mcp
167+
ldd build/c/codebase-memory-mcp 2>&1 | grep -q "not a dynamic executable" || ldd build/c/codebase-memory-mcp 2>&1 | grep -q "statically linked"
168+
169+
- name: Archive standard binary
156170
run: |
157-
docker run --rm -v "$PWD:/src" -w /src alpine:3.21 sh -c "
158-
apk add --no-cache build-base linux-headers zlib-dev zlib-static nodejs npm bash &&
159-
bash scripts/build.sh ${VERSION_FLAG} CC=gcc CXX=g++ STATIC=1
160-
"
161-
file build/c/codebase-memory-mcp | grep -q "statically linked"
162-
echo "=== Verified: statically linked ==="
163171
cp LICENSE install.sh build/c/
164172
tar -czf codebase-memory-mcp-linux-${{ matrix.arch }}-portable.tar.gz \
165173
-C build/c codebase-memory-mcp LICENSE install.sh
166-
env:
167-
VERSION_FLAG: ${{ inputs.version && format('--version {0}', inputs.version) || '' }}
168174
169-
- name: Build UI binary (Alpine static)
175+
- name: Build UI binary (static)
176+
run: scripts/build.sh --with-ui ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=gcc CXX=g++ STATIC=1
177+
178+
- name: Archive UI binary
170179
run: |
171-
docker run --rm -v "$PWD:/src" -w /src alpine:3.21 sh -c "
172-
apk add --no-cache build-base linux-headers zlib-dev zlib-static nodejs npm bash &&
173-
bash scripts/build.sh --with-ui ${VERSION_FLAG} CC=gcc CXX=g++ STATIC=1
174-
"
175180
cp LICENSE install.sh build/c/
176181
tar -czf codebase-memory-mcp-ui-linux-${{ matrix.arch }}-portable.tar.gz \
177182
-C build/c codebase-memory-mcp LICENSE install.sh
178-
env:
179-
VERSION_FLAG: ${{ inputs.version && format('--version {0}', inputs.version) || '' }}
180183
181184
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
182185
with:

internal/cbm/sqlite_writer.c

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
#include <stdint.h>
2626
#include <stdbool.h>
2727

28-
#define PAGE_SIZE 65536
28+
#define CBM_PAGE_SIZE 65536
2929
#define SCHEMA_FORMAT 4
3030
#define FILE_FORMAT 1
3131
#define SQLITE_VERSION 3046000 // 3.46.0
@@ -124,7 +124,7 @@ enum {
124124
#define BTREE_INTERIOR_INDEX 0x02
125125

126126
// SQLite 100-byte database header field offsets.
127-
#define HDR_OFF_PAGE_SIZE 16
127+
#define HDR_OFF_CBM_PAGE_SIZE 16
128128
#define HDR_OFF_WRITE_VERSION 18
129129
#define HDR_OFF_READ_VERSION 19
130130
#define HDR_OFF_RESERVED 20
@@ -405,7 +405,7 @@ typedef struct {
405405
bool is_index; // true for index B-trees
406406

407407
// Current leaf page being built
408-
uint8_t page[PAGE_SIZE];
408+
uint8_t page[CBM_PAGE_SIZE];
409409
int cell_count;
410410
int content_offset; // where cell content starts (grows down from page end)
411411
int ptr_offset; // where cell pointers are written (grows up from header)
@@ -421,11 +421,11 @@ static void pb_init(PageBuilder *pb, FILE *fp, uint32_t start_page, bool is_inde
421421
pb->next_page = start_page;
422422
pb->is_index = is_index;
423423
pb->cell_count = 0;
424-
pb->content_offset = PAGE_SIZE;
424+
pb->content_offset = CBM_PAGE_SIZE;
425425
pb->page1_offset = (start_page == SKIP_ONE) ? SQLITE_HEADER_SIZE : 0;
426426
// Header: flag(1) + freeblock(2) + cell_count(2) + content_start(2) + fragmented(1) = 8
427427
pb->ptr_offset = pb->page1_offset + BTREE_HEADER_SIZE;
428-
memset(pb->page, 0, PAGE_SIZE);
428+
memset(pb->page, 0, CBM_PAGE_SIZE);
429429
pb->leaves = NULL;
430430
pb->leaf_count = 0;
431431
pb->leaf_cap = 0;
@@ -456,9 +456,9 @@ static void pb_flush_leaf(PageBuilder *pb) {
456456

457457
// Write page to file
458458
uint32_t page_num = pb->next_page;
459-
long offset = (long)(page_num - SKIP_ONE) * PAGE_SIZE;
459+
long offset = (long)(page_num - SKIP_ONE) * CBM_PAGE_SIZE;
460460
(void)fseek(pb->fp, offset, SEEK_SET);
461-
(void)fwrite(pb->page, SKIP_ONE, PAGE_SIZE, pb->fp);
461+
(void)fwrite(pb->page, SKIP_ONE, CBM_PAGE_SIZE, pb->fp);
462462

463463
// Record this leaf for interior page building
464464
if (pb->leaf_count >= pb->leaf_cap) {
@@ -481,10 +481,10 @@ static void pb_flush_leaf(PageBuilder *pb) {
481481
// Reset for next page
482482
pb->next_page++;
483483
pb->cell_count = 0;
484-
pb->content_offset = PAGE_SIZE;
484+
pb->content_offset = CBM_PAGE_SIZE;
485485
pb->page1_offset = 0; // only page 1 has the 100-byte header
486486
pb->ptr_offset = BTREE_HEADER_SIZE; // standard B-tree header size for non-page-1
487-
memset(pb->page, 0, PAGE_SIZE);
487+
memset(pb->page, 0, CBM_PAGE_SIZE);
488488
}
489489

490490
// Check if a cell of given size fits in the current page
@@ -554,8 +554,8 @@ static int write_interior_page(PageBuilder *pb, uint8_t *page, int cell_count, i
554554
page[HDR_FRAGBYTES_OFF] = 0;
555555
put_u32(page + HDR_RIGHTCHILD_OFF, right_child_page);
556556

557-
(void)fseek(pb->fp, (long)(pnum - SKIP_ONE) * PAGE_SIZE, SEEK_SET);
558-
(void)fwrite(page, SKIP_ONE, PAGE_SIZE, pb->fp);
557+
(void)fseek(pb->fp, (long)(pnum - SKIP_ONE) * CBM_PAGE_SIZE, SEEK_SET);
558+
(void)fwrite(page, SKIP_ONE, CBM_PAGE_SIZE, pb->fp);
559559

560560
if (parent_count >= *parent_cap) {
561561
int old_pcap = *parent_cap;
@@ -639,10 +639,10 @@ static uint32_t pb_build_interior(PageBuilder *pb, bool is_index) {
639639

640640
int i = 0;
641641
while (i < child_count) {
642-
uint8_t page[PAGE_SIZE];
643-
memset(page, 0, PAGE_SIZE);
642+
uint8_t page[CBM_PAGE_SIZE];
643+
memset(page, 0, CBM_PAGE_SIZE);
644644
int cell_count = 0;
645-
int content_offset = PAGE_SIZE;
645+
int content_offset = CBM_PAGE_SIZE;
646646
int ptr_offset = BTREE_INTERIOR_HDR;
647647

648648
fill_interior_page(page, children, child_count, is_index, &i, &cell_count,
@@ -965,16 +965,16 @@ static uint32_t write_table_btree(FILE *fp, uint32_t *next_page, const uint8_t *
965965
if (count == 0) {
966966
// Empty table: write a single empty leaf page
967967
uint32_t pnum = (*next_page)++;
968-
uint8_t page[PAGE_SIZE];
969-
memset(page, 0, PAGE_SIZE);
968+
uint8_t page[CBM_PAGE_SIZE];
969+
memset(page, 0, CBM_PAGE_SIZE);
970970
int hdr = first_is_page1 ? SQLITE_HEADER_SIZE : 0;
971-
page[hdr] = BTREE_LEAF_TABLE; // leaf table
972-
put_u16(page + hdr + HDR_FREEBLOCK_OFF, 0); // no freeblocks
973-
put_u16(page + hdr + HDR_CELLCOUNT_OFF, 0); // 0 cells
974-
put_u16(page + hdr + HDR_CONTENT_OFF, (uint16_t)PAGE_SIZE); // content at end of page
975-
page[hdr + HDR_FRAGBYTES_OFF] = 0; // 0 fragmented bytes
976-
(void)fseek(fp, (long)(pnum - SKIP_ONE) * PAGE_SIZE, SEEK_SET);
977-
(void)fwrite(page, SKIP_ONE, PAGE_SIZE, fp);
971+
page[hdr] = BTREE_LEAF_TABLE; // leaf table
972+
put_u16(page + hdr + HDR_FREEBLOCK_OFF, 0); // no freeblocks
973+
put_u16(page + hdr + HDR_CELLCOUNT_OFF, 0); // 0 cells
974+
put_u16(page + hdr + HDR_CONTENT_OFF, (uint16_t)CBM_PAGE_SIZE); // content at end of page
975+
page[hdr + HDR_FRAGBYTES_OFF] = 0; // 0 fragmented bytes
976+
(void)fseek(fp, (long)(pnum - SKIP_ONE) * CBM_PAGE_SIZE, SEEK_SET);
977+
(void)fwrite(page, SKIP_ONE, CBM_PAGE_SIZE, fp);
978978
return pnum;
979979
}
980980

@@ -1013,15 +1013,15 @@ static bool pb_promote_and_flush(PageBuilder *pb, uint8_t **cells, int *cell_len
10131013
// Write an empty index leaf page.
10141014
static uint32_t write_empty_index_leaf(FILE *fp, uint32_t *next_page) {
10151015
uint32_t pnum = (*next_page)++;
1016-
uint8_t page[PAGE_SIZE];
1017-
memset(page, 0, PAGE_SIZE);
1016+
uint8_t page[CBM_PAGE_SIZE];
1017+
memset(page, 0, CBM_PAGE_SIZE);
10181018
page[0] = NEWLINE_BYTE;
10191019
put_u16(page + HDR_FREEBLOCK_OFF, 0);
10201020
put_u16(page + HDR_CELLCOUNT_OFF, 0);
1021-
put_u16(page + HDR_CONTENT_OFF, (uint16_t)PAGE_SIZE);
1021+
put_u16(page + HDR_CONTENT_OFF, (uint16_t)CBM_PAGE_SIZE);
10221022
page[HDR_FRAGBYTES_OFF] = 0;
1023-
(void)fseek(fp, (long)(pnum - SKIP_ONE) * PAGE_SIZE, SEEK_SET);
1024-
(void)fwrite(page, SKIP_ONE, PAGE_SIZE, fp);
1023+
(void)fseek(fp, (long)(pnum - SKIP_ONE) * CBM_PAGE_SIZE, SEEK_SET);
1024+
(void)fwrite(page, SKIP_ONE, CBM_PAGE_SIZE, fp);
10251025
return pnum;
10261026
}
10271027

@@ -1775,13 +1775,13 @@ int cbm_write_db(const char *path, const char *project, const char *root_path,
17751775

17761776
// Write master B-tree starting at page 1
17771777
{
1778-
uint8_t page1[PAGE_SIZE];
1779-
memset(page1, 0, PAGE_SIZE);
1778+
uint8_t page1[CBM_PAGE_SIZE];
1779+
memset(page1, 0, CBM_PAGE_SIZE);
17801780

17811781
// B-tree header starts at offset 100 on page 1
17821782
int hdr = SQLITE_HEADER_SIZE;
17831783
page1[hdr] = BTREE_LEAF_TABLE; // leaf table
1784-
int content_off = PAGE_SIZE;
1784+
int content_off = CBM_PAGE_SIZE;
17851785
int ptr_off = hdr + BTREE_HEADER_SIZE;
17861786
int mcell_count = 0;
17871787

@@ -1822,7 +1822,7 @@ int cbm_write_db(const char *path, const char *project, const char *root_path,
18221822
// Write the 100-byte SQLite file header
18231823
memcpy(page1, "SQLite format 3\000", 16);
18241824
/* Page size 65536 is encoded as 1 in the 2-byte header field */
1825-
put_u16(page1 + HDR_OFF_PAGE_SIZE, (uint16_t)SKIP_ONE);
1825+
put_u16(page1 + HDR_OFF_CBM_PAGE_SIZE, (uint16_t)SKIP_ONE);
18261826
page1[HDR_OFF_WRITE_VERSION] = FILE_FORMAT; // file format write version
18271827
page1[HDR_OFF_READ_VERSION] = FILE_FORMAT; // file format read version
18281828
page1[HDR_OFF_RESERVED] = 0; // reserved space per page
@@ -1848,7 +1848,7 @@ int cbm_write_db(const char *path, const char *project, const char *root_path,
18481848
put_u32(page1 + HDR_OFF_FILE_CHANGE, SKIP_ONE);
18491849

18501850
(void)fseek(fp, 0, SEEK_SET);
1851-
(void)fwrite(page1, SKIP_ONE, PAGE_SIZE, fp);
1851+
(void)fwrite(page1, SKIP_ONE, CBM_PAGE_SIZE, fp);
18521852
}
18531853

18541854
for (int i = 0; i < master_count; i++) {
@@ -1858,11 +1858,11 @@ int cbm_write_db(const char *path, const char *project, const char *root_path,
18581858
free(master_lens);
18591859
free(master_rowids);
18601860

1861-
// Ensure file size is exactly next_page * PAGE_SIZE
1861+
// Ensure file size is exactly next_page * CBM_PAGE_SIZE
18621862
// (pad any remaining space)
18631863
(void)fseek(fp, 0, SEEK_END);
18641864
long file_size = ftell(fp);
1865-
long expected_size = (long)(next_page - SKIP_ONE) * PAGE_SIZE;
1865+
long expected_size = (long)(next_page - SKIP_ONE) * CBM_PAGE_SIZE;
18661866
if (file_size < expected_size) {
18671867
// Pad with zeros
18681868
uint8_t zero = 0;

0 commit comments

Comments
 (0)