@@ -2102,21 +2102,85 @@ prepare_build(bool print_fingerprint,
21022102// ─── P0: build cache for fast-path rebuilds ─────────────────────────
21032103
21042104constexpr std::string_view kBuildCacheFile = " target/.build_cache" ;
2105+ constexpr int kBuildCacheMaxEntries = 4 ; // P3: LRU capacity
2106+
2107+ // P3: one entry per (target, fingerprint) pair.
2108+ struct BuildCacheEntry {
2109+ std::string targetTriple; // "" for default target
2110+ std::string outputDir;
2111+ std::string ninjaProgram;
2112+ std::string fingerprint; // outputDir basename
2113+ };
2114+
2115+ std::vector<BuildCacheEntry> read_build_cache (const std::filesystem::path& projectRoot) {
2116+ auto path = projectRoot / kBuildCacheFile ;
2117+ std::ifstream f (path);
2118+ if (!f) return {};
2119+
2120+ std::string firstLine;
2121+ if (!std::getline (f, firstLine) || firstLine.empty ()) return {};
2122+
2123+ // Detect legacy format (first line is an absolute path, not "[target=...]").
2124+ if (firstLine[0 ] != ' [' ) {
2125+ // Legacy 4-line format: outputDir, ninjaProgram, target, fingerprint.
2126+ BuildCacheEntry e;
2127+ e.outputDir = firstLine;
2128+ std::getline (f, e.ninjaProgram );
2129+ std::getline (f, e.targetTriple );
2130+ std::getline (f, e.fingerprint );
2131+ if (e.outputDir .empty () || e.ninjaProgram .empty ()) return {};
2132+ return {e};
2133+ }
2134+
2135+ // P3 multi-entry format: sections of [target=<triple>] + 3 lines.
2136+ std::vector<BuildCacheEntry> entries;
2137+ std::string line = firstLine;
2138+ while (true ) {
2139+ // Parse [target=<triple>]
2140+ if (line.size () < 9 || !line.starts_with (" [target=" ) || line.back () != ' ]' )
2141+ break ;
2142+ BuildCacheEntry e;
2143+ e.targetTriple = line.substr (8 , line.size () - 9 );
2144+ if (!std::getline (f, e.outputDir ) || e.outputDir .empty ()) break ;
2145+ if (!std::getline (f, e.ninjaProgram ) || e.ninjaProgram .empty ()) break ;
2146+ std::getline (f, e.fingerprint );
2147+ entries.push_back (std::move (e));
2148+ if (!std::getline (f, line)) break ;
2149+ }
2150+ return entries;
2151+ }
21052152
21062153void write_build_cache (const std::filesystem::path& projectRoot,
21072154 const std::filesystem::path& outputDir,
21082155 const std::string& ninjaProgram,
21092156 const std::string& targetTriple,
21102157 const std::string& fingerprintHex = " " ) {
21112158 auto path = projectRoot / kBuildCacheFile ;
2159+ auto entries = read_build_cache (projectRoot);
2160+
2161+ // Remove existing entry for this target (will be re-added at front).
2162+ std::erase_if (entries, [&](const BuildCacheEntry& e) {
2163+ return e.targetTriple == targetTriple;
2164+ });
2165+
2166+ // Insert at front (MRU).
2167+ BuildCacheEntry newEntry{targetTriple, outputDir.string (), ninjaProgram, fingerprintHex};
2168+ entries.insert (entries.begin (), std::move (newEntry));
2169+
2170+ // Trim to LRU capacity.
2171+ if ((int )entries.size () > kBuildCacheMaxEntries )
2172+ entries.resize (kBuildCacheMaxEntries );
2173+
2174+ // Write P3 format.
21122175 std::error_code ec;
21132176 std::filesystem::create_directories (path.parent_path (), ec);
21142177 std::ofstream f (path, std::ios::trunc);
2115- if (f) {
2116- f << outputDir.string () << ' \n ' ;
2117- f << ninjaProgram << ' \n ' ;
2118- f << targetTriple << ' \n ' ;
2119- f << fingerprintHex << ' \n ' ;
2178+ if (!f) return ;
2179+ for (auto & e : entries) {
2180+ f << " [target=" << e.targetTriple << " ]\n " ;
2181+ f << e.outputDir << ' \n ' ;
2182+ f << e.ninjaProgram << ' \n ' ;
2183+ f << e.fingerprint << ' \n ' ;
21202184 }
21212185}
21222186
@@ -2177,16 +2241,16 @@ int run_build_plan(BuildContext& ctx, bool verbose, bool no_cache,
21772241
21782242 // P1.5: warn if fingerprint changed from last build (explains full rebuild).
21792243 {
2180- auto cachePath = ctx.projectRoot / kBuildCacheFile ;
2181- std::ifstream cf (cachePath);
2182- std::string oldDir;
2183- if ( std::getline (cf, oldDir) && !oldDir. empty ()) {
2184- auto oldFp = std::filesystem::path (oldDir). filename (). string ();
2185- auto newFp = ctx. outputDir . filename (). string ();
2186- if (oldFp != newFp) {
2187- mcpp::ui::warning ( std::format (
2188- " fingerprint changed ({} → {}), full rebuild " ,
2189- oldFp, newFp)) ;
2244+ auto entries = read_build_cache ( ctx.projectRoot ) ;
2245+ for ( auto & e : entries) {
2246+ if (e. targetTriple == targetOverride && !e. fingerprint . empty ()) {
2247+ auto newFp = ctx. outputDir . filename (). string ();
2248+ if (e. fingerprint != newFp) {
2249+ mcpp::ui::warning ( std::format (
2250+ " fingerprint changed ({} → {}), full rebuild " ,
2251+ e. fingerprint , newFp));
2252+ }
2253+ break ;
21902254 }
21912255 }
21922256 }
@@ -2218,35 +2282,27 @@ std::optional<int> try_fast_build(const std::filesystem::path& projectRoot,
22182282 std::string_view currentTarget = " " ) {
22192283 if (no_cache) return std::nullopt ;
22202284
2221- auto cachePath = projectRoot / kBuildCacheFile ;
2222- std::error_code ec;
2223- if (!std::filesystem::exists (cachePath, ec)) return std::nullopt ;
2224-
2225- std::ifstream f (cachePath);
2226- std::string outputDirStr, ninjaProgram, cachedTarget, cachedFingerprint;
2227- if (!std::getline (f, outputDirStr) || outputDirStr.empty ()) return std::nullopt ;
2228- if (!std::getline (f, ninjaProgram) || ninjaProgram.empty ()) return std::nullopt ;
2229- std::getline (f, cachedTarget); // may be empty for old cache files
2230- std::getline (f, cachedFingerprint); // may be empty for pre-0.0.15 caches
2231-
2232- // Reject cache if target triple changed (e.g. previous build used
2233- // --target x86_64-linux-musl but this one is a default build).
2234- if (cachedTarget != currentTarget) return std::nullopt ;
2235-
2236- // P1: verify fingerprint matches the outputDir basename. If someone
2237- // switched mcpp installations (different toolchain binary), the cached
2238- // outputDir points to a stale fingerprint directory. Detect and reject.
2285+ // P3: read multi-entry cache and find entry matching currentTarget.
2286+ auto entries = read_build_cache (projectRoot);
2287+ const BuildCacheEntry* match = nullptr ;
2288+ for (auto & e : entries) {
2289+ if (e.targetTriple == currentTarget) { match = &e; break ; }
2290+ }
2291+ if (!match) return std::nullopt ;
2292+
2293+ auto outputDirStr = match->outputDir ;
2294+ auto ninjaProgram = match->ninjaProgram ;
2295+ auto cachedFingerprint = match->fingerprint ;
2296+
2297+ // P1: verify fingerprint matches the outputDir basename.
22392298 if (!cachedFingerprint.empty ()) {
2240- std::filesystem::path outputDir (outputDirStr);
2241- auto dirBasename = outputDir.filename ().string ();
2299+ auto dirBasename = std::filesystem::path (outputDirStr).filename ().string ();
22422300 if (dirBasename != cachedFingerprint) {
2243- // Cache is inconsistent — invalidate it.
2244- std::error_code ec2;
2245- std::filesystem::remove (cachePath, ec2);
22462301 return std::nullopt ;
22472302 }
22482303 }
22492304
2305+ std::error_code ec;
22502306 std::filesystem::path outputDir (outputDirStr);
22512307
22522308 auto ninjaPath = outputDir / " build.ninja" ;
0 commit comments