Skip to content

Commit 2b4e049

Browse files
joa-quimclaude
andcommitted
Share the ~200MB VTK/Qt/TBB runtime cache across Pkg.updates; add update!()
A plain `Pkg.add`-installed package lives in a content-hashed folder that gets a BRAND NEW hash on every single Pkg.update, even for a one-line .jl change. deps/build.jl was extracting the full runtime zip into that ephemeral folder, so every update silently re-downloaded and re-extracted the whole ~200MB VTK/Qt/TBB bundle again. Fixed: the runtime now lives at ~/.julia/gmtvtk_runtime/, keyed off the shared Julia depot rather than the per-version package folder, so it's fetched once ever. src/libgmtvtk.jl looks there too (falling back to it only when there's no local deps/build/gmtvtk.dll, so an active dev build via deps/build.bat always wins first). Verified both files resolve to the identical path and the extraction layout is correct. Add src/selfupdate.jl (InteractiveGMT.update!()): for a `] dev`-installed checkout (fixed directory, never moves across updates -- unlike Pkg.add), pulls the latest source in place via Julia's BUNDLED LibGit2 (no system git.exe required) and rebuilds. Pkg.update() deliberately skips dev'd packages, so this fills that gap. Verified fetch+merge! against this actual repo. Adds LibGit2 + Pkg (both stdlibs) to Project.toml deps -- needed by selfupdate.jl, same reasoning as the earlier Downloads fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 595d288 commit 2b4e049

5 files changed

Lines changed: 100 additions & 22 deletions

File tree

Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ version = "0.1.0"
77
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
88
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
99
GMT = "5752ebe1-31b9-557e-87aa-f909b540aa54"
10+
LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433"
11+
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
1012
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
1113

1214
[compat]
1315
Downloads = "1"
1416
GMT = "1.40"
17+
LibGit2 = "1"
18+
Pkg = "1"
1519
julia = "1.10"
1620

1721
[extras]

deps/build.jl

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,32 @@
44
#
55
# * FULL runtime zip (gmtvtk.dll + bundled VTK/Qt/TBB + Qt plugins) — changes rarely,
66
# only when the VTK/Qt/TBB module set changes. Pinned by deps/RUNTIME_VERSION (a git
7-
# tag, e.g. "runtime-0.1"). Downloaded ONCE, on first install (no marker in deps/build/).
7+
# tag, e.g. "runtime-0.1"). Downloaded ONCE EVER (see SHARED_ROOT below).
88
#
99
# * DLL-ONLY zip (just gmtvtk.dll) — can change daily as the C++ side is edited. Lives at
1010
# a FIXED, reused release tag (DLL_TAG below); its one asset gets overwritten in place
1111
# (`gh release upload dll-latest gmtvtk-win64.zip --clobber`) — no new tag per day.
1212
# Re-downloaded on every `Pkg.build("InteractiveGMT")`.
13-
13+
#
14+
# A regular `Pkg.add`-installed (non-dev) package lives in a content-hashed folder
15+
# (~/.julia/packages/InteractiveGMT/<hash>/) that gets a BRAND NEW <hash> on every single
16+
# Pkg.update, even for a one-line .jl change unrelated to the C++ side. If the ~200 MB
17+
# VTK/Qt/TBB runtime were extracted INTO that folder (as an earlier version of this file did),
18+
# every update would silently re-download and re-extract the entire runtime again --
19+
# unacceptable. Fix: extract the runtime into SHARED_ROOT, a location keyed off the Julia
20+
# DEPOT itself (~/.julia), not off this ephemeral package folder -- the same physical spot
21+
# survives every Pkg.update, `Pkg.add` or `Pkg.develop` alike, so the runtime is fetched once,
22+
# ever, no matter how many times the package updates. src/libgmtvtk.jl looks in this same
23+
# SHARED_ROOT (falling back to it only when there's no LOCAL deps/build/gmtvtk.dll -- i.e. a
24+
# developer's own `deps/build.bat` build always wins first).
1425
using Downloads
1526

1627
const REPO = "GenericMappingTools/InteractiveGMT"
1728
const DLL_TAG = "dll-latest" # fixed tag; its one asset is re-uploaded in place, never retagged
1829

19-
const DEPS_DIR = @__DIR__
20-
const PKG_ROOT = normpath(joinpath(DEPS_DIR, "..")) # zip paths are relative to here (deps/build/..., src/..., data/...)
21-
const MARKER = joinpath(DEPS_DIR, "build", ".full_runtime_installed")
30+
const DEPS_DIR = @__DIR__
31+
const SHARED_ROOT = joinpath(first(Base.DEPOT_PATH), "gmtvtk_runtime") # survives every Pkg.update; zip paths (deps/build/...) are relative to here
32+
const MARKER = joinpath(SHARED_ROOT, "deps", "build", ".full_runtime_installed")
2233

2334
function runtime_tag()
2435
f = joinpath(DEPS_DIR, "RUNTIME_VERSION")
@@ -35,13 +46,10 @@ release_url(tag::String, asset::String) =
3546
# "does not look like a tar archive" / "Error exit delayed from previous errors".
3647
const TAR = joinpath(get(ENV, "SystemRoot", "C:\\Windows"), "System32", "tar.exe")
3748

38-
# Both zips also contain Project.toml/data/src (the "full" one, for the standalone
39-
# zip/NSIS user who isn't going through Julia Pkg at all) — but under Pkg those files
40-
# already exist on disk from the git checkout, and Pkg marks them READ-ONLY on Windows.
41-
# Extracting the whole archive then fails with "Can't stat existing object: Permission
42-
# denied" trying to overwrite them. Only deps/build/ (the binaries) is actually needed
43-
# here, so restrict extraction to that subtree — sidesteps the permission error AND
44-
# avoids redundantly re-writing files git already gave us.
49+
# The full zip also contains Project.toml/data/src (for the standalone zip/NSIS user who isn't
50+
# going through Julia Pkg at all) — irrelevant here since Pkg already gave us those via git, and
51+
# SHARED_ROOT only ever needs the binaries. Restrict extraction to deps/build/ so SHARED_ROOT
52+
# doesn't waste disk space on a redundant copy of data/ and src/.
4553
function fetch_and_extract(url::String, dest::String)
4654
isfile(TAR) || error("$TAR not found — need Windows 10 1803+ (bsdtar) to unzip gmtvtk binaries")
4755
zip = joinpath(tempdir(), basename(url))
@@ -58,14 +66,15 @@ end
5866

5967
function main()
6068
if !isfile(MARKER)
61-
# First install: full runtime bundle, pinned to a coarse, rarely-bumped tag.
62-
fetch_and_extract(release_url(runtime_tag(), "iGMT-win64-full.zip"), PKG_ROOT)
69+
# First install EVER on this machine: full runtime bundle, pinned to a coarse,
70+
# rarely-bumped tag. Never repeated after this, even across many future updates.
71+
fetch_and_extract(release_url(runtime_tag(), "iGMT-win64-full.zip"), SHARED_ROOT)
6372
touch(MARKER)
6473
else
65-
# Update: DLL only, always the same rolling tag/asset.
66-
fetch_and_extract(release_url(DLL_TAG, "gmtvtk-win64.zip"), PKG_ROOT)
74+
# Every subsequent build: DLL only (~1 MB), always the same rolling tag/asset.
75+
fetch_and_extract(release_url(DLL_TAG, "gmtvtk-win64.zip"), SHARED_ROOT)
6776
end
68-
@info "InteractiveGMT: gmtvtk binaries installed"
77+
@info "InteractiveGMT: gmtvtk binaries installed" SHARED_ROOT
6978
end
7079

7180
main()

src/InteractiveGMT.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ using PrecompileTools: @setup_workload, @compile_workload
2020

2121
# --- C-API DLL loader (resolved at runtime in __init__; see libgmtvtk.jl) ----------------
2222
include("libgmtvtk.jl")
23+
include("selfupdate.jl") # update!() -- pull + rebuild in place, for a `] dev`-installed checkout
2324

2425
# --- handles, event loop, in-window Julia console ----------------------------------------
2526
include("types.jl")

src/libgmtvtk.jl

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,30 @@ const _PKGROOT = normpath(joinpath(@__DIR__, ".."))
1414
# Libdl without adding it to [deps]: the stdlib module is re-exposed inside Base.
1515
const Libdl = Base.Libc.Libdl
1616

17+
# Where's gmtvtk.dll? Checked in order:
18+
# 1. A LOCAL dev build (deps/build/gmtvtk.dll next to this checkout, from deps/build.bat) --
19+
# always wins first, so a developer actively rebuilding the DLL never picks up a stale
20+
# cached copy.
21+
# 2. SHARED_LIB: the depot-wide runtime cache deps/build.jl fetches into
22+
# (~/.julia/gmtvtk_runtime/deps/build/gmtvtk.dll) -- this is the Pkg.add/Pkg.develop path.
23+
# It's keyed off the Julia DEPOT itself, not off this package's own (possibly
24+
# content-hashed, possibly-different-every-update) folder, so the SAME ~200 MB VTK/Qt/TBB
25+
# runtime is reused across every future update instead of being re-fetched into a new
26+
# folder each time.
27+
const _LOCAL_LIB = joinpath(_PKGROOT, "deps", "build", "gmtvtk.dll")
28+
const _SHARED_LIB = joinpath(first(Base.DEPOT_PATH), "gmtvtk_runtime", "deps", "build", "gmtvtk.dll")
29+
const _LIB = isfile(_LOCAL_LIB) ? _LOCAL_LIB : _SHARED_LIB
30+
const _BIN_DIR = dirname(_LIB)
31+
1732
# Toolchain runtime DLL dirs (this machine). Dependent DLLs (Qt6*, vtk*) resolve from PATH at
1833
# load time. Override any of these via the matching ENV var BEFORE `using InteractiveGMT`.
1934
#
2035
# A GMTVTK_PACKAGE=ON build (see deps/CMakeLists.txt) drops every VTK/Qt/TBB runtime DLL plus the
2136
# Qt platform plugins (platforms/qwindows.dll, via windeployqt) into deps/build/ next to
22-
# gmtvtk.dll itself — that's the NSIS-installed layout, with no VTK/Qt toolchain on the
23-
# destination machine at all. Detect that bundle and point straight at it; otherwise fall back
24-
# to this dev machine's hard-coded toolchain paths (ENV overrides always win either way).
25-
const _LIB = joinpath(_PKGROOT, "deps", "build", "gmtvtk.dll")
26-
const _BIN_DIR = dirname(_LIB)
37+
# gmtvtk.dll itself — that's the NSIS-installed / shared-runtime-cache layout, with no VTK/Qt
38+
# toolchain on the destination machine at all. Detect that bundle and point straight at it;
39+
# otherwise fall back to this dev machine's hard-coded toolchain paths (ENV overrides always
40+
# win either way).
2741
const _BUNDLED = isdir(joinpath(_BIN_DIR, "platforms"))
2842
const _VTK_BIN = get(ENV, "INTERACTIVEGMT_VTK_BIN", _BUNDLED ? _BIN_DIR : raw"C:\programs\compa_libs\VTK-9.6.2\compileds\bin")
2943
const _QT_BIN = get(ENV, "INTERACTIVEGMT_QT_BIN", _BUNDLED ? _BIN_DIR : raw"C:\programs\Qt6\6.11.1\msvc2022_64\bin")

src/selfupdate.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# selfupdate.jl — update!() pulls the latest InteractiveGMT source in place and rebuilds the
2+
# binaries, for a `] dev`-installed checkout ONLY.
3+
#
4+
# Why this exists: `] dev https://github.com/GenericMappingTools/InteractiveGMT` clones ONCE to
5+
# a fixed, permanent directory (~/.julia/dev/InteractiveGMT by default) that never moves again --
6+
# unlike a plain `Pkg.add`, which re-checks-out into a brand NEW content-hashed folder on every
7+
# single `Pkg.update`. A fixed directory means a Desktop shortcut never goes stale. The one thing
8+
# `dev` doesn't give you for free is an update mechanism: Pkg.update() deliberately skips dev'd
9+
# packages (you're expected to manage their git state yourself). update!() is that missing piece
10+
# — using Julia's BUNDLED LibGit2, not a system `git.exe`, so end users never need git installed.
11+
12+
using LibGit2
13+
import Pkg
14+
15+
"""
16+
InteractiveGMT.update!()
17+
18+
Pull the latest InteractiveGMT source in place (fast-forward only) and rebuild the binaries.
19+
Only works for a `] dev`-installed checkout — a plain `Pkg.add` install should use
20+
`Pkg.update("InteractiveGMT")` instead.
21+
"""
22+
function update!()
23+
isdir(joinpath(_PKGROOT, ".git")) || error(
24+
"InteractiveGMT at $_PKGROOT isn't a git checkout -- update! only works for a " *
25+
"`] dev`-installed copy. For a plain `Pkg.add` install, use " *
26+
"Pkg.update(\"InteractiveGMT\") instead.")
27+
28+
repo = LibGit2.GitRepo(_PKGROOT)
29+
try
30+
@info "InteractiveGMT: fetching latest changes..." path=_PKGROOT
31+
LibGit2.fetch(repo)
32+
before = LibGit2.head_oid(repo)
33+
ok = LibGit2.merge!(repo; fastforward=true)
34+
ok || error("InteractiveGMT: local changes or diverged history -- couldn't fast-forward. " *
35+
"This checkout is a normal git repo at $_PKGROOT; resolve manually (e.g. `git status`).")
36+
after = LibGit2.head_oid(repo)
37+
if before == after
38+
@info "InteractiveGMT: already up to date."
39+
return nothing
40+
end
41+
@info "InteractiveGMT: updated." from = string(before)[1:8] to = string(after)[1:8]
42+
finally
43+
close(repo)
44+
end
45+
46+
@info "InteractiveGMT: rebuilding binaries..."
47+
Pkg.build("InteractiveGMT")
48+
@info "InteractiveGMT: update complete. Restart Julia to use the new version."
49+
return nothing
50+
end

0 commit comments

Comments
 (0)