From cdaa96250d9fd0fbf633a12e1115f53f0da465d6 Mon Sep 17 00:00:00 2001 From: Istvan Matejcsok <119620946+matejcsok-ee@users.noreply.github.com> Date: Tue, 23 Jun 2026 21:23:20 +0200 Subject: [PATCH] fix(wasm): KiCad 10 kiplatform/libgit2 stubs, e2e fixtures, asyncify -g hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wasm/kiplatform: add EnableDarkMode, IO::TimestampDir, UI::AllowNetworkFileSystems, UI::CancelPendingScroll, SECRETS::DeleteSecret (new KiCad 10 functions). - wasm/stubs/libgit2_stub.c: ~156 no-op stubs for KiCad 10's restructured git backend + git-backed local history + text_eval VCS. - tests/kicad: bump the demo project dir 9.99 -> 10.0 (KiCad 10 stores projects under /home/kicad/documents/kicad/10.0/) — fixes load-pcb + 3d-viewer e2e. - scripts/common/apply-asyncify.sh: ASYNCIFY_EXTRA_OPTS hook (e.g. -g for symbolizable wasm traces). - kicad submodule -> ef00c2d3 (KiCad 10 link + runtime adaptations). Co-Authored-By: Claude Opus 4.8 (1M context) --- kicad | 2 +- scripts/common/apply-asyncify.sh | 2 +- tests/kicad/3d-viewer.spec.ts | 3 +- tests/kicad/load-pcb-probe.spec.ts | 6 +- tests/kicad/load-pcb.spec.ts | 6 +- wasm/kiplatform/app.cpp | 7 ++ wasm/kiplatform/io.cpp | 35 ++++++ wasm/kiplatform/secrets.cpp | 21 ++++ wasm/kiplatform/ui.cpp | 16 +++ wasm/stubs/libgit2_stub.c | 174 +++++++++++++++++++++++++++++ 10 files changed, 264 insertions(+), 8 deletions(-) diff --git a/kicad b/kicad index 8444324..ef00c2d 160000 --- a/kicad +++ b/kicad @@ -1 +1 @@ -Subproject commit 84443246e34cd17e9ce8a3a0e7598efd0d7cc1c4 +Subproject commit ef00c2d3a3b289579d23d3e2a7235069c16cc81a diff --git a/scripts/common/apply-asyncify.sh b/scripts/common/apply-asyncify.sh index d07604c..44a7f49 100755 --- a/scripts/common/apply-asyncify.sh +++ b/scripts/common/apply-asyncify.sh @@ -112,7 +112,7 @@ echo "This may take several minutes and use significant RAM..." echo " BINARYEN_CORES=${BINARYEN_CORES}" echo " LD_PRELOAD=${WASM_OPT_PRELOAD:-}" -"${PRELOAD_CMD[@]}" "${TIME_CMD[@]}" "${WASM_OPT}" --asyncify \ +"${PRELOAD_CMD[@]}" "${TIME_CMD[@]}" "${WASM_OPT}" --asyncify ${ASYNCIFY_EXTRA_OPTS:-} \ "--pass-arg=asyncify-imports@${ASYNCIFY_IMPORTS}" \ "--pass-arg=asyncify-removelist@${ASYNCIFY_REMOVE_ARG}" \ --pass-arg=asyncify-propagate-addlist \ diff --git a/tests/kicad/3d-viewer.spec.ts b/tests/kicad/3d-viewer.spec.ts index 1e125c9..9ee3399 100644 --- a/tests/kicad/3d-viewer.spec.ts +++ b/tests/kicad/3d-viewer.spec.ts @@ -21,7 +21,8 @@ import { waitForPcbnew } from './utils/pcbnew-ready'; * models — deferred), the viewer shows copper/silk/mask/edge geometry in 3D. */ -const KICAD_VERSION_DIR = '9.99'; +// KiCad 10 stores projects under /home/kicad/documents/kicad/10.0/projects. +const KICAD_VERSION_DIR = '10.0'; const PROJECT_DIR_MEMFS = `/home/kicad/documents/kicad/${KICAD_VERSION_DIR}/projects`; // pic_programmer frames correctly in the default 3D camera (the microwave demo diff --git a/tests/kicad/load-pcb-probe.spec.ts b/tests/kicad/load-pcb-probe.spec.ts index c9694db..c29dc9a 100644 --- a/tests/kicad/load-pcb-probe.spec.ts +++ b/tests/kicad/load-pcb-probe.spec.ts @@ -201,8 +201,8 @@ test.describe('PCB load probe', () => { '/home/kicad', '/home/kicad/documents', '/home/kicad/documents/kicad', - '/home/kicad/documents/kicad/9.99', - '/home/kicad/documents/kicad/9.99/projects', + '/home/kicad/documents/kicad/10.0', + '/home/kicad/documents/kicad/10.0/projects', '/tmp', '/workspace', ]); @@ -238,7 +238,7 @@ test.describe('PCB load probe', () => { // Final check: re-dump MEMFS so we can confirm nothing changed under us await dumpMemfs(page, [ - '/home/kicad/documents/kicad/9.99/projects', + '/home/kicad/documents/kicad/10.0/projects', '/tmp', ]); diff --git a/tests/kicad/load-pcb.spec.ts b/tests/kicad/load-pcb.spec.ts index 64a0bf9..2c62c34 100644 --- a/tests/kicad/load-pcb.spec.ts +++ b/tests/kicad/load-pcb.spec.ts @@ -16,7 +16,7 @@ import { waitForBoardLoaded } from './utils/board-ready'; * if it appears — depends on whether MEMFS already has config). * 2. Inject the demo's .kicad_pcb (+ .kicad_pro for completeness) into * MEMFS at PATHS::GetDefaultUserProjectsPath() — confirmed by - * load-pcb-probe.spec.ts to be /home/kicad/documents/kicad/9.99/projects/. + * load-pcb-probe.spec.ts to be /home/kicad/documents/kicad/10.0/projects/. * 3. Drive File → Open via menu helpers. * 4. Click on the file row in wxFileListCtrl (wasm port doesn't register * listctrl rows in wxElementRegistry, so we click by the filelist's @@ -36,7 +36,9 @@ import { waitForBoardLoaded } from './utils/board-ready'; * `features//rtree-debug-findings.md` for the full trail. */ -const KICAD_VERSION_DIR = '9.99'; +// KiCad 10 stores projects under /home/kicad/documents/kicad/10.0/projects +// (GetMajorMinorVersion() = "10.0"); the old WASM port used "9.99". +const KICAD_VERSION_DIR = '10.0'; const PROJECT_DIR_MEMFS = `/home/kicad/documents/kicad/${KICAD_VERSION_DIR}/projects`; type DemoCfg = { diff --git a/wasm/kiplatform/app.cpp b/wasm/kiplatform/app.cpp index c473f27..9b74241 100644 --- a/wasm/kiplatform/app.cpp +++ b/wasm/kiplatform/app.cpp @@ -83,5 +83,12 @@ void AddDynamicLibrarySearchPath( const wxString& aPath ) // No dynamic library loading in WASM } +void EnableDarkMode( bool aForce ) +{ + // Native platforms use this to theme window chrome (title bars, etc.). + // In the browser the page/host controls theming via CSS, so this is a no-op. + (void) aForce; +} + } // namespace APP } // namespace KIPLATFORM diff --git a/wasm/kiplatform/io.cpp b/wasm/kiplatform/io.cpp index 84b7d54..01450e5 100644 --- a/wasm/kiplatform/io.cpp +++ b/wasm/kiplatform/io.cpp @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include namespace KIPLATFORM { @@ -45,5 +47,38 @@ void LongPathAdjustment( wxFileName& aFilename ) // No-op on non-Windows platforms } +long long TimestampDir( const wxString& aDirPath, const wxString& aFilespec ) +{ + // Mirror the native implementations: accumulate (mtime, size) over files + // matching the spec so callers can detect library-directory changes. The + // Emscripten virtual filesystem supports stat(), so this works for MEMFS- + // backed local libraries (git/network libraries are handled JS-side). + long long timestamp = 0; + + wxDir dir( aDirPath ); + + if( !dir.IsOpened() ) + return timestamp; + + wxString filename; + bool cont = dir.GetFirst( &filename, aFilespec, wxDIR_FILES ); + + while( cont ) + { + wxString fullPath = aDirPath + wxFILE_SEP_PATH + filename; + struct stat entryStat; + + if( stat( fullPath.fn_str(), &entryStat ) == 0 ) + { + timestamp += static_cast( entryStat.st_mtime ) * 1000; + timestamp += entryStat.st_size; + } + + cont = dir.GetNext( &filename ); + } + + return timestamp; +} + } // namespace IO } // namespace KIPLATFORM diff --git a/wasm/kiplatform/secrets.cpp b/wasm/kiplatform/secrets.cpp index 46bb9df..fa0e0ca 100644 --- a/wasm/kiplatform/secrets.cpp +++ b/wasm/kiplatform/secrets.cpp @@ -72,5 +72,26 @@ bool GetSecret( const wxString& aService, const wxString& aKey, wxString& aSecre #endif } +bool DeleteSecret( const wxString& aService, const wxString& aKey ) +{ +#ifdef __EMSCRIPTEN__ + int result = EM_ASM_INT({ + try { + var service = UTF8ToString($0); + var key = UTF8ToString($1); + var storageKey = 'kicad_secret_' + service + '_' + key; + localStorage.removeItem(storageKey); + return 1; + } catch(e) { + console.warn('Failed to delete secret:', e); + return 0; + } + }, aService.utf8_str().data(), aKey.utf8_str().data()); + return result == 1; +#else + return false; +#endif +} + } // namespace SECRETS } // namespace KIPLATFORM diff --git a/wasm/kiplatform/ui.cpp b/wasm/kiplatform/ui.cpp index c45d286..142cc0b 100644 --- a/wasm/kiplatform/ui.cpp +++ b/wasm/kiplatform/ui.cpp @@ -191,5 +191,21 @@ void SetFloatLevel( wxWindow* aWindow ) // No floating window levels in browser } +void AllowNetworkFileSystems( wxDialog* aDialog ) +{ + // Native platforms use this to let file dialogs browse network mounts. + // The browser's file dialogs are virtual (MEMFS / JS bridge), so this is + // a no-op. + (void) aDialog; +} + +void CancelPendingScroll( wxDataViewCtrl* aCtrl ) +{ + // Native (GTK/macOS) platforms cancel an in-flight kinetic/async scroll on + // the data view. The browser wx port has no such pending-scroll state, so + // this is a no-op. + (void) aCtrl; +} + } // namespace UI } // namespace KIPLATFORM diff --git a/wasm/stubs/libgit2_stub.c b/wasm/stubs/libgit2_stub.c index 2688808..589fecc 100644 --- a/wasm/stubs/libgit2_stub.c +++ b/wasm/stubs/libgit2_stub.c @@ -272,3 +272,177 @@ void git_config_entry_free(void *entry) { void git_config_free(void *cfg) { (void)cfg; } + +/* ============================================================================ + * KiCad 10 rebase: additional libgit2 stubs. + * + * KiCad 10 restructured its git backend (git/git_backend.cpp, + * git/libgit_backend.cpp) and added text_eval/text_eval_vcs.cpp (VCS text + * variables), which call libgit2 functions not covered by the stubs above. + * These no-op / failure-returning definitions let the WASM build link; git is + * non-functional in the browser, so callers see "no repository / failure". + * + * Signatures use generic types (pointers -> void*, enums/callbacks -> int) to + * keep the wasm function types compatible with the real callers without + * pulling in git2.h. Generated from the libgit2 1.7.1 headers. + * ============================================================================ */ +int git_annotated_commit_from_ref(void **out, void *repo, const void *ref) { return -1; } +const void * git_annotated_commit_id(const void *commit) { return 0; } +int git_annotated_commit_lookup(void **out, void *repo, const void *id) { return -1; } +int git_branch_create(void **out, void *repo, const char *branch_name, const void *target, int force) { return -1; } +int git_branch_is_head(const void *branch) { return -1; } +int git_branch_iterator_new(void **out, void *repo, int list_flags) { return -1; } +int git_branch_lookup(void **out, void *repo, const char *branch_name, int branch_type) { return -1; } +int git_branch_name(const char **out, const void *ref) { return -1; } +int git_branch_next(void **out, void *out_type, void *iter) { return -1; } +int git_branch_remote_name(void *out, void *repo, const char *refname) { return -1; } +int git_branch_set_upstream(void *branch, const char *branch_name) { return -1; } +int git_branch_upstream(void **out, const void *branch) { return -1; } +void git_buf_dispose(void *buffer) { } +int git_checkout_init_options(void *opts, unsigned int version) { return -1; } +int git_checkout_tree(void *repo, const void *treeish, const void *opts) { return -1; } +int git_clone(void **out, const char *url, const char *local_path, const void *options) { return -1; } +int git_clone_init_options(void *opts, unsigned int version) { return -1; } +int git_commit_amend(void *id, const void *commit_to_amend, const char *update_ref, const void *author, const void *committer, const char *message_encoding, const char *message, const void *tree) { return -1; } +const void * git_commit_author(const void *commit) { return 0; } +const void * git_commit_committer(const void *commit) { return 0; } +const char * git_commit_message(const void *commit) { return 0; } +int git_commit_parent(void **out, const void *commit, unsigned int n) { return -1; } +unsigned int git_commit_parentcount(const void *commit) { return 0; } +long long git_commit_time(const void *commit) { return 0; } +int git_config_get_bool(int *out, const void *cfg, const char *name) { return -1; } +int git_credential_ssh_key_from_agent(void **out, const char *username) { return -1; } +int git_credential_ssh_key_new(void **out, const char *username, const char *publickey, const char *privatekey, const char *passphrase) { return -1; } +int git_credential_username_new(void **out, const char *username) { return -1; } +int git_credential_userpass_plaintext_new(void **out, const char *username, const char *password) { return -1; } +int git_diff_init_options(void *opts, unsigned int version) { return -1; } +void git_error_clear(void) { } +int git_error_set_str(int error_class, const char *string) { return -1; } +int git_fetch_init_options(void *opts, unsigned int version) { return -1; } +int git_index_clear(void *index) { return -1; } +int git_index_find(size_t *at_pos, void *index, const char *path) { return -1; } +int git_index_has_conflicts(const void *index) { return -1; } +int git_index_read_tree(void *index, const void *tree) { return -1; } +int git_index_remove_bypath(void *index, const char *path) { return -1; } +int git_libgit2_version(int *major, int *minor, int *rev) { if(major)*major=1; if(minor)*minor=7; if(rev)*rev=1; return 0; } +int git_merge(void *repo, const void **their_heads, size_t their_heads_len, const void *merge_opts, const void *checkout_opts) { return -1; } +int git_merge_analysis(void *analysis_out, void *preference_out, void *repo, const void **their_heads, size_t their_heads_len) { return -1; } +int git_merge_base(void *out, void *repo, const void *one, const void *two) { return -1; } +const void * git_object_id(const void *obj) { return 0; } +int git_object_type(const void *obj) { return -1; } +int git_oid_cpy(void *out, const void *src) { return -1; } +int git_oid_fromstrn(void *out, const char *str, size_t length) { return -1; } +int git_oid_is_zero(const void *id) { return 1; } +int git_oid_iszero(const void *id) { return 1; } +int git_push_init_options(void *opts, unsigned int version) { return -1; } +int git_rebase_abort(void *rebase) { return -1; } +int git_rebase_commit(void *id, void *rebase, const void *author, const void *committer, const char *message_encoding, const char *message) { return -1; } +int git_rebase_finish(void *rebase, const void *signature) { return -1; } +int git_rebase_init(void **out, void *repo, const void *branch, const void *upstream, const void *onto, const void *opts) { return -1; } +int git_rebase_init_options(void *opts, unsigned int version) { return -1; } +int git_rebase_next(void **operation, void *rebase) { return -1; } +int git_reference_dwim(void **out, void *repo, const char *shorthand) { return -1; } +int git_reference_is_branch(const void *ref) { return -1; } +int git_reference_is_remote(const void *ref) { return -1; } +const char * git_reference_name(const void *ref) { return 0; } +int git_reference_peel(void **out, const void *ref, int type) { return -1; } +int git_reference_resolve(void **out, const void *ref) { return -1; } +int git_reference_set_target(void **out, void *ref, const void *id, const char *log_message) { return -1; } +const char * git_reference_shorthand(const void *ref) { return 0; } +int git_remote_connect(void *remote, int direction, const void *callbacks, const void *proxy_opts, const void *custom_headers) { return -1; } +int git_remote_create_anonymous(void **out, void *repo, const char *url) { return -1; } +int git_remote_create_with_fetchspec(void **out, void *repo, const char *name, const char *url, const char *fetch) { return -1; } +int git_remote_disconnect(void *remote) { return -1; } +int git_remote_fetch(void *remote, const void *refspecs, const void *opts, const char *reflog_message) { return -1; } +void git_remote_free(void *remote) { } +int git_remote_init_callbacks(void *opts, unsigned int version) { return -1; } +int git_remote_list(void *out, void *repo) { return -1; } +int git_remote_lookup(void **out, void *repo, const char *name) { return -1; } +const char * git_remote_name(const void *remote) { return 0; } +int git_remote_push(void *remote, const void *refspecs, const void *opts) { return -1; } +const char * git_remote_pushurl(const void *remote) { return 0; } +int git_remote_set_url(void *repo, const char *remote, const char *url) { return -1; } +const char * git_remote_url(const void *remote) { return 0; } +int git_repository_config(void **out, void *repo) { return -1; } +int git_repository_discover(void *out, const char *start_path, int across_fs, const char *ceiling_dirs) { return -1; } +int git_repository_fetchhead_foreach(void *repo, int callback, void *payload) { return -1; } +int git_repository_head_unborn(void *repo) { return -1; } +int git_repository_init_ext(void **out, const char *repo_path, void *opts) { return -1; } +int git_repository_init_init_options(void *opts, unsigned int version) { return -1; } +const char * git_repository_path(const void *repo) { return 0; } +int git_repository_set_head(void *repo, const char *refname) { return -1; } +int git_repository_state_cleanup(void *repo) { return -1; } +const char * git_repository_workdir(const void *repo) { return 0; } +int git_reset(void *repo, const void *target, int reset_type, const void *checkout_opts) { return -1; } +int git_revparse_single(void **out, void *repo, const char *spec) { return -1; } +void git_revwalk_free(void *walk) { } +int git_revwalk_hide(void *walk, const void *commit_id) { return -1; } +int git_revwalk_new(void **out, void *repo) { return -1; } +int git_revwalk_next(void *out, void *walk) { return -1; } +int git_revwalk_push(void *walk, const void *id) { return -1; } +int git_revwalk_push_glob(void *walk, const char *glob) { return -1; } +int git_revwalk_sorting(void *walk, unsigned int sort_mode) { return -1; } +int git_signature_default(void **out, void *repo) { return -1; } +const void * git_status_byindex(void *statuslist, size_t idx) { return 0; } +int git_status_init_options(void *opts, unsigned int version) { return -1; } +size_t git_status_list_entrycount(void *statuslist) { return 0; } +void git_status_list_free(void *statuslist) { } +int git_status_list_new(void **out, void *repo, const void *opts) { return -1; } +int git_status_options_init(void *opts, unsigned int version) { return -1; } +void git_strarray_dispose(void *array) { } +int git_tag_list_match(void *tag_names, const char *pattern, void *repo) { return -1; } +int git_tag_peel(void **tag_target_out, const void *tag) { return -1; } +int git_tree_entry_bypath(void **out, const void *root, const char *path) { return -1; } +void git_tree_entry_free(void *entry) { } +const void * git_tree_entry_id(const void *entry) { return 0; } +const char * git_tree_entry_name(const void *entry) { return 0; } +int git_tree_entry_type(const void *entry) { return -1; } +int git_tree_walk(const void *tree, int mode, int callback, void *payload) { return -1; } + +/* ---- KiCad 10 local-history (git/history_lock.cpp, git/local_history.cpp) and + * other kicommon git callers: additional no-op libgit2 stubs. Same rationale + * as the block above (git is non-functional in the browser). ---- */ +void git_annotated_commit_free(void *commit) { } +void git_blame_free(void *blame) { } +void git_blob_free(void *blob) { } +int git_blob_lookup(void **blob, void *repo, const void *id) { return -1; } +const void * git_blob_rawcontent(const void *blob) { return 0; } +long long git_blob_rawsize(const void *blob) { return 0; } +void git_branch_iterator_free(void *iter) { } +void git_buf_free(void *buffer) { } +void git_config_iterator_free(void *iter) { } +void git_credential_free(void *cred) { } +void git_describe_result_free(void *result) { } +void git_diff_stats_free(void *stats) { } +void git_filter_list_free(void *filters) { } +int git_ignore_add_rule(void *repo, const char *rules) { return -1; } +int git_index_add_all(void *index, const void *pathspec, unsigned int flags, int callback, void *payload) { return -1; } +void git_index_conflict_iterator_free(void *iterator) { } +void git_index_iterator_free(void *iterator) { } +void git_indexer_free(void *idx) { } +void git_odb_free(void *db) { } +const void * git_odb_object_data(void *object) { return 0; } +void git_odb_object_free(void *object) { } +size_t git_odb_object_size(void *object) { return 0; } +int git_odb_object_type(void *object) { return -1; } +int git_odb_read(void **out, void *db, const void *id) { return -1; } +int git_odb_read_header(size_t *len_out, void *type_out, void *db, const void *id) { return -1; } +int git_odb_write(void *out, void *odb, const void *data, size_t len, int type) { return -1; } +int git_oid_fromstr(void *out, const char *str) { return -1; } +void git_oidarray_free(void *array) { } +void git_packbuilder_free(void *pb) { } +int git_packbuilder_insert_commit(void *pb, const void *id) { return -1; } +int git_packbuilder_new(void **out, void *repo) { return -1; } +int git_packbuilder_set_callbacks(void *pb, int progress_cb, void *progress_cb_payload) { return -1; } +int git_packbuilder_write(void *pb, const char *path, unsigned int mode, int progress_cb, void *progress_cb_payload) { return -1; } +void git_rebase_free(void *rebase) { } +int git_repository_odb(void **out, void *repo) { return -1; } +int git_repository_set_workdir(void *repo, const char *workdir, int update_gitlink) { return -1; } +int git_revwalk_push_head(void *walk) { return -1; } +int git_signature_new(void **out, const char *name, const char *email, long long time, int offset) { return -1; } +int git_status_file(unsigned int *status_flags, void *repo, const char *path) { return -1; } +void git_strarray_free(void *array) { } +int git_tag_list(void *tag_names, void *repo) { return -1; } +const void * git_tree_entry_byindex(const void *tree, size_t idx) { return 0; } +size_t git_tree_entrycount(const void *tree) { return 0; } +const void * git_tree_id(const void *tree) { return 0; }