Template 0.1.16: how the Tools bridge hooks reach an existing app
Design note for the 0.1.16 migration (.template_sync/migrations/v0_1_15_to_v0_1_16.py). Short version: the hooks reach an existing app through the normal template synchronization pull request, and reach people who already installed it only when they install a newer installer of the app. Nothing in the template can change an installed copy by itself.
1. What "update" means in this template (checked in the code)
There are two different things called "the app":
| Where it lives | How it changes | |
|---|---|---|
| The app repository (created from the template) | GitHub | The weekly workflow sync_template.yml ("Sync with Template Repository") compares .template_sync/manifest.yaml (template_version) of the repository with the template's main, copies the template's .template_sync folder, runs the chain of migrations with .template_sync/sync.py, and opens a pull request ("Sync template to version X"). It needs the secret LABCONSTRICTOR_SYNC_TOKEN; without it the workflow stops with an error and the repository stays on its version. The owner reviews and merges. |
| The installed app | The user's computer (a conda prefix made by the constructor installer) |
Never by the template. post_install runs once while an installer runs. Nothing runs on update: there is no self-update step, no version check in the scripts, and the notebook update check (notebook_latest_versions.yaml) only tells people that a notebook has a newer version. |
So the real path of a template change to a user is: template main (version bump) -> sync pull request in the app repository (migration edits the scripts) -> owner merges -> owner publishes a release of the app (release_constructor.yml builds the installers from the repository) -> the user downloads and runs the new installer.
What the installer does with the scripts (from construct.yaml and the constructor sources):
post_install.sh/.batare run by the installer at the end of every install (pkgs/post_install.*). How a user gets the new installer onto an existing install differs by system (observed in the first run of the LabConstrictor-Tools workflowsmoke_migration.yml, run 38298832113, with NucleiSky):- Linux:
bash installer.sh -u -p <prefix>updates the existing prefix in place and runspost_installagain (works). - macOS: the same
-ustops inpost_installbecause menuinst refuses an existing shortcut ("App already exists at ~/Applications/.app"). This is how the template behaves today, independent of the hooks. Run the app's uninstall.shfirst, then the new installer. - Windows: the silent installer exits with code 2 when the folder already holds an install. Uninstall first (Settings > Apps), then install the new version. So "update" on macOS and Windows means uninstall and install again; user data outside the install folder is not touched (the scripts only remove the install folder).
pre_uninstall.batis copied by the Windows installer to<prefix>\pre_uninstall.batand run by the uninstaller (constructor'spre_uninstallexists on Windows only).pre_uninstall.shanduninstall.share plainextra_filescopied to the prefix on Linux and macOS (constructor has no uninstall hook there).post_installfills theBASE_PATHtoken with the real prefix.
2. What the migration changes
In the app repository, in this order (each file independently):
| File | Change |
|---|---|
app/bash_bat_scripts/post_install.sh |
the hook block (detect <package>_lc_tools, pip install of Tools, labconstrictor_tools register) is inserted before the include_path.py line |
app/bash_bat_scripts/post_install.bat |
the same block for Windows; SETLOCAL EnableExtensions becomes SETLOCAL EnableExtensions DisableDelayedExpansion |
app/bash_bat_scripts/pre_uninstall.sh / .bat |
the unregister call (with a warning if a registry entry stays) is inserted before the menuinst ... remove line |
app/bash_bat_scripts/uninstall.sh |
only if it is exactly the 0.1.15 text: a failed pre_uninstall and a failed rm -rf are reported (exit 1 if the folder cannot be removed) |
.tools/docs/tools_bridge.md, .tools/docs/README.md |
the guide and its index entry |
.tools/schema/labconstrictor-app.schema.json, .tools/python/check_app_manifest.py, .tools/docs/app_manifest.md, .github/workflows/check_app_manifest.yml |
the App Centre manifest check, refreshed as template-managed files (byte copies, as in 0.1.15). The 0.1.15 schema copy was stale and rejected inputs, outputs, limitations, status and distribution.platforms; the copy is now the Site schema. The app's own labconstrictor-app.yaml is never touched |
construct.yaml needs no change: pre_uninstall.sh, uninstall.sh and the post/pre scripts are already listed, and bump_constructor.py adds every file under src/ (including src/<package>_lc_tools/) at release time. The tool module itself (src/<package>_lc_tools/) belongs to the app author and is not created by the migration.
Rules:
- Idempotent. A script that already mentions
labconstrictor_toolsis left unchanged; a second run changes nothing (tested). Apps that carried the hooks before (thebridge-testapps) are therefore not given the block twice (only their Tools source is pinned, see the next point). - Already-present hooks are pinned. Apps whose hooks came from the
bridge-testbranch (NucleiSky pull request 32, VLab4Mic) have the Tools sourcehttps://github.com/CellMigrationLab/LabConstrictor-Tools/archive/refs/heads/main.zip. For such a script the migration does not insert the block again; it rewrites exactly that source to the form the template scripts use (aLC_TOOLS_PINline, inpost_install.shbefore theLC_TOOLS_MODULE=line and inpost_install.batbefore theLC_TOOLS_SPECline, and the.../archive/refs/tags/<pin>.zipURL; the tag is read from the template scripts) and logs[migration 0.1.16] ...post_install.sh: updated: unpinned Tools source ... rewritten to the pinned tag .... Only that exact text is rewritten (the${LC_TOOLS_SPEC:-<url>}default in the.sh, theIF NOT DEFINED LC_TOOLS_SPEC SETline in the.bat, exactly one occurrence of the URL). Anything else (a fork URL, another branch, another variable, two occurrences) is left unchanged and printed asSKIPPED the pin, with the manual step: replacerefs/heads/main.zipbyrefs/tags/<pin>.zip. A second run changes nothing. Tested with the real lines of VLab4Mic and NucleiSky pull request 32 (tests/fixtures/bridge_test_hooks/). - Package name. The package that carries
<package>_lc_toolsis found, in order, from the singlesrc/<package>_lc_tools, the single package insrc/, the name insetup.py, and finally the[project]name ofpyproject.toml(hyphens become underscores; accepted only ifsrc/<name>/exists). The last step is for apps such as NucleiSky: several packages insrc/and asetup.pyshim without a name. If none decides, thepost_installhooks are not added (a message says so) and the rest is migrated. - Surgical. The block is inserted before a known line, so the app's own edits stay. If the script is missing, was rewritten, or lacks the variables the block uses (
PYTHON_EXE,PROJECT_ROOT,LOG_FILE,BASE_PATH), that file is skipped with a message (SKIPPED: ...) and the rest still runs. The migration never raises for such a file, so the sync pull request is still created. - No tools, no effect. For an app without a
<package>_lc_toolsmodule the hooks are inert:post_installfinds no module and skips the step;pre_uninstallstays silent unless a registry entry exists. The migration says so in its log. - Never deletes. It writes only the files in the table (no
.orig, nothing removed); notebooks,src/, requirements,construct.yamlandlabconstrictor-app.yamlare not touched. Line endings of each file are kept. - Logs. Every step prints one line prefixed
[migration 0.1.16]in the "Run template migrations" step of the workflow. - Version.
template_versiongoes to 0.1.16 (the template's rule: bump when users must act; a sync pull request is only opened when the version is higher). - Pinned to a tag. The inserted block installs Tools from
.../archive/refs/tags/v0.2.0-rc.1.zip(one variable,LC_TOOLS_PIN, at the start of the block inpost_install.shandpost_install.bat), not from the movingrefs/heads/main.zip;LC_TOOLS_SPECstill overrides it exactly as before. Amending the 0.1.16 migration for this was safe: when it was done no app repository had synced to 0.1.16 (NucleiSky, CellTracksColab and GuessTheCondition were at 0.1.15 with an open sync pull request to 0.1.15 and no 0.1.16 branch; VLab4Mic has no.template_sync). Moving the pin later is a one-string change, seetools_bridge.md.
3. What cannot be migrated (honest answer)
- A copy that is already installed cannot be changed by a template version. Its
post_installalready ran; the Linux/macOSpre_uninstall.sh/uninstall.shand the Windowspre_uninstall.batin the prefix are old files. They change only when a newer installer is run over the prefix (-uon Linux) or after an uninstall and a fresh install (macOS, Windows). There is no mechanism in the template, and none is added here, that edits files inside someone's prefix: it would need code that runs on the user's computer without the user installing anything. - An app only gets tools if a release contains
src/<package>_lc_tools/. The tool module is a file of the installer (extra_files); an installed app whose installer did not ship it has nothing to register, whatever the scripts say. So "apps pick up the hooks when they update" means: the app owner merges the sync pull request and publishes a new release, and users install it. - Old installs keep uninstalling the old way. Uninstalling an app installed from an old installer does not unregister anything (it never registered). That is harmless. The reverse case is the risk: an app registered by a new installer and then removed by deleting its folder by hand leaves an entry in
~/.labconstrictor/apps(seelabconstrictor-tools doctor). - Repositories that did not receive the sync (no
LABCONSTRICTOR_SYNC_TOKEN, workflow disabled, sync pull request not merged) stay on their version and do not get the hooks; they can copy the files by hand from.tools/docs/tools_bridge.md. - Customised scripts are skipped (see above): those apps need the block added by hand.
- Windows
.bat. The migration and the generated.battext are tested in the template tests on the files only. Running them on Windows is verified only by the smoke workflow in LabConstrictor-Tools, not here.
4. Test plan
- Template tests (
tests/test_template_sync_0_1_16.py): old scripts of 0.1.15 become byte-identical to the 0.1.16 template scripts rendered for the app; second run changes nothing; hooks already present are untouched except for the pin (real bridge-test texts of VLab4Mic and NucleiSky pull request 32, LF and CRLF, second run, customised sources skipped); the package name frompyproject.toml(NucleiSky-like tree); the docs index entry placement; CRLF/LF preserved; customised and missing files skipped without failing; user files untouched; thesync.pyentry point; the migrated bash scripts run against a stub Python (register, failed registration, no tools module, uninstall unregisters and removes only the prefix, failed unregister only warns). - LabConstrictor-Tools workflow
smoke_migration.yml: an app at an old template version is built and installed, migrated by this branch withsync.py, a new installer is built and the install is updated to it (in place on Linux, uninstall then install on macOS and Windows),labconstrictor-tools listanddoctorare checked, then the app is uninstalled, on Linux, macOS and Windows.