Context
Follow-up to #262 / #264. After the -trimpath flag win, the shim is at 5.70 MB. Symbol analysis shows ~70% of that is dead weight pulled in transitively from blank-imports of the runtime providers:
| Package |
Symbols in shim |
Used by shim? |
klauspost/compress |
235 |
No |
bodgit/sevenzip |
173 |
No |
net/http |
114 |
No |
ulikunitz/xz, pierrec/lz4, andybalholm/brotli, compress/*, archive/zip, crypto/tls, afero, briandowns/spinner, schollz/progressbar |
~370 combined |
No |
Plus 1.3 MB of embedded JSON manifests (internal/manifest/data/*.json) |
— |
No |
PR #75 narrowed the call site with the ShimProvider interface, but the linker still keeps Install/Uninstall/ListAvailable reachable because the registered concrete types (*node.Provider etc.) carry those methods. The fix has to happen at the source level: make the heavy methods physically uncompiled in the shim build.
Target
Push the shim toward ~3.0–3.5 MB (a measured floor for a stub doing only the shim's hot path is 2.3 MB).
Approach
Use Go build tags to split each runtime provider into a "shim-relevant" half and a "full" half, then build the shim with -tags shim.
Per src/runtimes/<name>/:
- Keep in
provider.go: Name, DisplayName, Shims, ExecutablePath, IsInstalled, InstallPath, ShouldReshimAfter, GetEnvironment, the Provider struct, NewProvider, and init(). These need only os, filepath, runtime, internal/config, internal/constants.
- Move to a new
provider_full.go with //go:build !shim: Install, Uninstall, ListInstalled, ListAvailable, DetectInstalled, GlobalPackages, InstallGlobalPackages, ManualPackageInstallCommand, plus all install helpers (downloadAndExtract, getDownloadURL, createShims, etc.) and the global/local/current version setters that need only internal/config. Heavy imports (internal/download, internal/manifest) live only in this file.
internal/runtime/registry.go:
- Build-tag the storage type. Under
//go:build shim the registry stores ShimProvider; otherwise it stores Provider. Register accepts ShimProvider in shim builds and Provider in main builds (since Provider embeds ShimProvider, no caller change needed in main builds).
internal/shim/manager.go:
Rehash and RuntimeShims reach into the full Provider interface via runtime.Get. The shim binary doesn't call them. Move both to a //go:build !shim file so they're not compiled in.
Build configuration:
- Add
-tags shim to the build-shim task in rnr.yaml and to the corresponding steps in .github/workflows/build.yml and .github/workflows/release.yml.
Acceptance criteria
Risk
Mostly mechanical. Watch points:
- Any code outside
cmd/shim that calls full-Provider methods needs to live in !shim files so the shim build compiles.
- Test files in the runtime packages may exercise full-
Provider methods; they should not need a build tag because tests aren't run with -tags shim.
- The
internal/migration packages register full-Provider types into their own registry; need to verify they don't get pulled into the shim transitively.
Out of scope
Context
Follow-up to #262 / #264. After the
-trimpathflag win, the shim is at 5.70 MB. Symbol analysis shows ~70% of that is dead weight pulled in transitively from blank-imports of the runtime providers:klauspost/compressbodgit/sevenzipnet/httpulikunitz/xz,pierrec/lz4,andybalholm/brotli,compress/*,archive/zip,crypto/tls,afero,briandowns/spinner,schollz/progressbarinternal/manifest/data/*.json)PR #75 narrowed the call site with the
ShimProviderinterface, but the linker still keepsInstall/Uninstall/ListAvailablereachable because the registered concrete types (*node.Provideretc.) carry those methods. The fix has to happen at the source level: make the heavy methods physically uncompiled in the shim build.Target
Push the shim toward ~3.0–3.5 MB (a measured floor for a stub doing only the shim's hot path is 2.3 MB).
Approach
Use Go build tags to split each runtime provider into a "shim-relevant" half and a "full" half, then build the shim with
-tags shim.Per
src/runtimes/<name>/:provider.go:Name,DisplayName,Shims,ExecutablePath,IsInstalled,InstallPath,ShouldReshimAfter,GetEnvironment, theProviderstruct,NewProvider, andinit(). These need onlyos,filepath,runtime,internal/config,internal/constants.provider_full.gowith//go:build !shim:Install,Uninstall,ListInstalled,ListAvailable,DetectInstalled,GlobalPackages,InstallGlobalPackages,ManualPackageInstallCommand, plus all install helpers (downloadAndExtract,getDownloadURL,createShims, etc.) and the global/local/current version setters that need onlyinternal/config. Heavy imports (internal/download,internal/manifest) live only in this file.internal/runtime/registry.go://go:build shimthe registry storesShimProvider; otherwise it storesProvider.RegisteracceptsShimProviderin shim builds andProviderin main builds (sinceProviderembedsShimProvider, no caller change needed in main builds).internal/shim/manager.go:RehashandRuntimeShimsreach into the fullProviderinterface viaruntime.Get. The shim binary doesn't call them. Move both to a//go:build !shimfile so they're not compiled in.Build configuration:
-tags shimto thebuild-shimtask inrnr.yamland to the corresponding steps in.github/workflows/build.ymland.github/workflows/release.yml.Acceptance criteria
go build -tags shim ./src/cmd/shimproduces a working shim binary in the 3.0–3.5 MB range on Windows amd64go build ./src(the main CLI) is unchanged in behavior and sizepython --version,node --version, etc. against a configured runtime, plus the "no version configured" fallback path)go tool nm) confirmsklauspost/compress,sevenzip,net/http, etc. are no longer present in the shim binaryRisk
Mostly mechanical. Watch points:
cmd/shimthat calls full-Providermethods needs to live in!shimfiles so the shim build compiles.Providermethods; they should not need a build tag because tests aren't run with-tags shim.internal/migrationpackages register full-Providertypes into their own registry; need to verify they don't get pulled into the shim transitively.Out of scope
internal/ui's color libraries) — separate effort if needed.