tom_analyzer_shared
Caching infrastructure that builds and reuses binary analyzer summaries so Tom's code generators skip rescanning stable dependencies on every run.
Overview
From the module readme.md file:
What it enables
Enables Reflection code generation, d4rt bridge generation, Cached dependency analysis.
Relationships
Standalone — no declared relationships.
tom_analyzer_shared
> Part of the Tom framework by al-the-bear. > © 2024–2026 Peter Nicolai Alexis Kyaw — BSD-3-Clause, see LICENSE.
Shared analyzer-summary caching infrastructure reused by Tom code generators (reflection, d4rt bridges, …).
Every tool that builds on the Dart analyzer package pays the same tax: before it can look at
your code, the analyzer must resolve and analyse all of your dependencies — Flutter,
provider, meta, the SDK itself. For a code generator that runs repeatedly, re-analysing the same stable packages on every run is pure waste.
tom_analyzer_shared removes that waste by building analyzer summaries (.sum
files) for the dependencies whose versions cannot change between runs, caching them in a shared workspace directory, and handing the paths back so the analyzer loads pre-digested type information instead of re-scanning sources.
It was extracted from tom_reflection_generator so that more than one generator could share one cache, and it is consumed today by both the reflection generator and the d4rt bridge generator.
---
Overview
The analyzer can be told, when it constructs an analysis context, "here are some pre-built summaries — trust them instead of reading these packages from source":
AnalysisContextCollectionImpl(
includedPaths: [projectRoot],
sdkSummaryPath: ..., // a prebuilt SDK summary
librarySummaryPaths: ..., // prebuilt package summaries
);
The hard part is producing those summaries correctly: knowing which packages are safe to cache, locating their sources in the pub cache and the SDK, building each summary
after the summaries it depends on, and storing them under a stable, version-keyed name so the next run finds them.
tom_analyzer_shared owns that pipeline:
pubspec.lock ──▶ DependencyResolver ──▶ which deps are cacheable?
│ (hosted + SDK = stable versions)
▼
SummaryGenerator ──▶ build SDK summary, then each package
│ summary in topological order
▼
SummaryCacheManager ──▶ <workspace>/.tom/analyzer-cache/
│ {package}@{version}.sum
▼
SummaryCacheResult { summaryPaths, sdkSummaryPath } ──▶ analyzer
The whole pipeline is wrapped by one function — runSummaryCacheStage — that a CLI tool calls once and then feeds the result straight into its analysis context. The individual stages are public too, for tools that need finer control.
Why only some dependencies are cached
A summary is keyed by {package}@{version}. That key is only trustworthy when the version pins the
content — which is true for hosted (pub.dev) and SDK packages, and false for
path and git dependencies, whose files can change without the version changing. So
tom_analyzer_shared caches hosted and SDK packages and leaves path/git dependencies to be analysed from source every run. This is the single rule encoded in
PackageDependency.isCacheable.
---
Installation
dependencies:
tom_analyzer_shared: ^0.1.0
Or from the command line:
dart pub add tom_analyzer_shared
Then import the single entry point:
import 'package:tom_analyzer_shared/tom_analyzer_shared.dart';
Requires Dart SDK ^3.10.4. It depends on analyzer, path, and yaml. It is a
dart:io package (it reads pubspec.lock, the pub cache, and the SDK), so it runs on desktop, server, and CLI hosts.
---
Features
| API | Kind | Purpose |
|---|---|---|
runSummaryCacheStage |
function | The one-call orchestration entry point used by CLI tools |
SummaryCacheResult |
class | The returned summaryPaths + sdkSummaryPath, ready for the analyzer |
DependencyResolver |
class | Parses pubspec.lock; resolves hosted/SDK source locations |
PackageDependency |
class | One resolved dependency: name, version, source, cacheability |
DependencySet |
class | Dependencies split into cacheable / uncacheable |
DependencyListExtensions |
extension |
hosted
/
sdk
/
paths
/
cacheable
/
findByName
|
SummaryGenerator |
class | Builds the SDK summary and per-package summaries in topological order |
SummaryGenerationResult |
class | Counts of generated / skipped / failed + per-package errors |
SummaryCacheManager |
class | Reads/writes .sum files; cache paths, stats, cleanup |
CacheStats | class | Cache directory size and file count |
---
Quick start
Most callers need exactly one function. Resolve, generate what's missing, and get the paths to hand to the analyzer:
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
import 'package:tom_analyzer_shared/tom_analyzer_shared.dart';
Future<void> analyse(String projectRoot) async {
final result = await runSummaryCacheStage(projectRoot, verbose: true);
final collection = AnalysisContextCollectionImpl(
includedPaths: [projectRoot],
sdkSummaryPath: result?.sdkSummaryPath,
librarySummaryPaths: result?.summaryPaths ?? const [],
);
// … use `collection` to analyse the project's own sources …
}
runSummaryCacheStage resolves dependencies from pubspec.lock, builds any summaries not already in
<projectRoot>/.tom/analyzer-cache/, and returns a SummaryCacheResult. On the first run it generates the cache; on later runs it finds everything present and returns immediately. It returns
null when no dependencies could be resolved or no summaries are available, so the null-aware
result?.… is the intended usage.
---
Example projects
This package is consumed inside code-generator CLIs rather than run standalone, so it ships no
example/ program. The worked sample lives in the samples folder:
| Sample | Demonstrates |
|---|---|
tom_build_base_advanced_analyzer_sample
|
A small nestable, traversal-driven tool that exercises the cacheability rule, dependency resolution and the on-disk summary cache per project, then reports the cold→warm cache-hit payoff — end to end and offline. |
The usage sections below and the consumers named under ecosystem are the inline reference.
---
Usage
The one-call stage
runSummaryCacheStage takes the project root and a handful of optional flags that mirror what a generator CLI exposes:
final result = await runSummaryCacheStage(
projectRoot,
verbose: true, // detail each generated/failed package
rebuildCache: false, // true → clear the cache first
showCacheStatus: false, // true → print status and return null
cacheOnlyPackages: const [], // non-empty → only these packages
log: print, // redirect output (defaults to print)
);
-
rebuildCacheclears<root>/.tom/analyzer-cache/before generating — use it
when a summary may be stale (e.g. after an SDK upgrade). - showCacheStatus prints a per-package CACHED/MISSING report and returns
null without doing heavy work — the implementation behind a --cache-status flag. -
cacheOnlyPackages narrows generation to named packages for targeted rebuilds. - log
redirects the human-readable progress lines to your own sink.
Resolving dependencies
When you need the dependency list itself — to inspect it, filter it, or drive a custom generation — use
DependencyResolver. It parses pubspec.lock and classifies every entry:
final resolver = DependencyResolver();
final deps = await resolver.resolveVersionedDependencies(projectRoot);
for (final dep in deps) {
print('${dep.cacheKey} source=${dep.source} cacheable=${dep.isCacheable}');
}
A PackageDependency carries the name, exact version, source
(hosted, sdk, path, git), and the source-specific extras (hostedUrl,
sdkName, path). Its two derived members are the heart of the package:
-
isCacheable—trueonly forhostedandsdksources. cacheKey—'{name}@{version}', the cache file stem.
const dep = PackageDependency(name: 'provider', version: '6.1.2', source: 'hosted');
print(dep.isCacheable); // true
print(dep.cacheKey); // provider@6.1.2
DependencySet.from(deps) splits a list into cacheable / uncacheable, and the
DependencyListExtensions give you focused views:
final set = DependencySet.from(deps);
print(set.cacheable.length); // hosted + sdk
print(deps.hosted.map((d) => d.name)); // hosted only
print(deps.findByName('flutter')?.cacheKey);
The cache directory
SummaryCacheManager owns <workspace>/.tom/analyzer-cache/ and the file naming. You construct it with the workspace root and ask it for paths or status — it never guesses a layout the analyzer can't find:
final cache = SummaryCacheManager(projectRoot);
print(cache.cacheDirectory); // …/.tom/analyzer-cache
print(cache.getCachePath('provider', '6.1.2')); // …/provider@6.1.2.sum
print(cache.getSdkSummaryPath()); // …/sdk@<dart-version>.sum
final stats = await cache.getStats(); // CacheStats
print('${stats.summaryCount} files, ${stats.totalSizeMB.toStringAsFixed(1)} MB');
It also offers hasSummary, findMissingSummaries, loadSummary,
clearCache, cleanOutdated, and cleanUnusedSummaries for tools that manage the cache lifecycle directly.
Generating summaries directly
SummaryGenerator is the engine runSummaryCacheStage drives. Use it directly when you want to control SDK-vs-package ordering or capture per-package errors:
final generator = SummaryGenerator(
cacheManager: cache,
dependencyResolver: resolver,
);
await generator.generateSdkSummary(); // dart:core / dart:ui first
final cacheable = await resolver.resolveCacheableDependencies(projectRoot);
final result = await generator.generateMissingSummaries(
cacheable,
onProgress: (pkg, current, total) => print(' ($current/$total) $pkg'),
);
print('generated=${result.generated} skipped=${result.skipped} '
'failed=${result.failed} total=${result.total}');
for (final entry in result.errors.entries) {
print(' ${entry.key}: ${entry.value}'); // why a package failed
}
The SDK summary is built first because package summaries resolve dart:core
(and, under Flutter, dart:ui) from it. Package summaries are then built in topological order
so each can reference its dependencies' summaries. A single package failing to summarise is recorded in
errors and does not abort the rest — the analyzer simply falls back to analysing that one from source.
---
Architecture
package:tom_analyzer_shared/tom_analyzer_shared.dart (single entry point)
│
├── runSummaryCacheStage() ── orchestration ──┐
│ returns SummaryCacheResult │
│ ▼
├── DependencyResolver ── pubspec.lock ──▶ List<PackageDependency>
│ resolveVersionedDependencies / resolveCacheableDependencies
│ getHostedPackagePath / getSdkPackagePath / getFlutterSdkPath
│
├── SummaryGenerator ── generateSdkSummary / generateMissingSummaries
│ topological order ──▶ SummaryGenerationResult
│
└── SummaryCacheManager ── <workspace>/.tom/analyzer-cache/
getCachePath / getSdkSummaryPath / getStats / clearCache
| Type | Role |
|---|---|
runSummaryCacheStage |
One-call stage: resolve → generate missing → return paths |
SummaryCacheResult |
summaryPaths + sdkSummaryPath for the analysis context |
DependencyResolver |
Parses pubspec.lock; locates hosted/SDK sources |
PackageDependency |
One resolved dependency (isCacheable, cacheKey) |
DependencySet | Cacheable / uncacheable partition of a dependency list |
SummaryGenerator | Builds SDK + package summaries, topologically ordered |
SummaryGenerationResult |
generated / skipped / failed counts + error map |
SummaryCacheManager |
The .sum cache directory: paths, stats, cleanup |
CacheStats | Cache file count and total size |
The cache is keyed purely by package name and version, so it is safe to share across tools and across runs: two generators pointed at the same workspace reuse each other's summaries, and nothing in the key depends on which tool wrote it.
---
Ecosystem
tom_analyzer_shared is the analyzer-tooling member of the tom_ai/basics
foundation layer. Its consumers are Tom's code generators, which all need a resolved analysis context fast:
tom_reflection_generator— generates reflection metadata; the package
was originally extracted from it. - tom_d4rt_generator — generates d4rt bridge classes for native Dart APIs.
Both declare tom_analyzer_shared as a dependency and call runSummaryCacheStage
so they share one .tom/analyzer-cache/ directory per workspace. Adding a third generator is a matter of the same one-call stage — the cache it shares is already there.
Sibling foundation packages: tom_basics
· tom_basics_console
· tom_basics_network
· tom_build_base.
---
Further documentation
-
../README.md— thetom_ai/basicspackage map. - The
analyzerpackage'sAnalysisContextCollectionImplis the consumer of
this package's output; its sdkSummaryPath / librarySummaryPaths parameters are what
SummaryCacheResult is built to fill.
---
Status
- Version: 0.1.0
- Tests: a
test/summary/suite covering dependency resolution, the cache
manager, summary generation, and an end-to-end integration test (dart test / testkit :test). -
Analysis: clean under package:lints (dart analyze — no issues). -
Platforms: any Dart runtime with dart:io (desktop, server, CLI).
License
BSD 3-Clause License Copyright (c) 2024-2026, Peter Nicolai Alexis Kyaw Find me on LinkedIn under Alexis Kyaw All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.