Skip to content

Commit 6482b5a

Browse files
committed
fix: avoid touching staged BMI cache outputs
1 parent 4e0cc03 commit 6482b5a

2 files changed

Lines changed: 85 additions & 2 deletions

File tree

src/bmi_cache.cppm

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ bool is_cached(const CacheKey& key);
5555
std::expected<DepArtifacts, std::string>
5656
read_manifest(const CacheKey& key);
5757

58-
// Copy cached files into projectTarget/{gcm.cache,obj}, bumping mtime so
59-
// ninja sees them as up-to-date relative to the (untouched) source files.
58+
// Copy missing cached files into projectTarget/{gcm.cache,obj}. Existing
59+
// project outputs are left untouched: GCC BMIs may differ byte-for-byte between
60+
// equivalent builds, and overwriting them would dirty downstream modules.
6061
std::expected<DepArtifacts, std::string>
6162
stage_into(const CacheKey& key,
6263
const std::filesystem::path& projectTargetDir);
@@ -150,6 +151,11 @@ stage_into(const CacheKey& key,
150151
for (auto& g : arts->gcmFiles) {
151152
auto from = key.gcmDir() / g;
152153
auto to = projectGcm / g;
154+
if (std::filesystem::exists(to, ec)) {
155+
ec.clear();
156+
continue;
157+
}
158+
ec.clear();
153159
if (!copy_one(from, to, ec)) {
154160
return std::unexpected(std::format(
155161
"stage gcm '{}': {}", g, ec.message()));
@@ -159,6 +165,11 @@ stage_into(const CacheKey& key,
159165
for (auto& o : arts->objFiles) {
160166
auto from = key.objDir() / o;
161167
auto to = projectObj / o;
168+
if (std::filesystem::exists(to, ec)) {
169+
ec.clear();
170+
continue;
171+
}
172+
ec.clear();
162173
if (!copy_one(from, to, ec)) {
163174
return std::unexpected(std::format(
164175
"stage obj '{}': {}", o, ec.message()));

tests/unit/test_bmi_cache.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,78 @@ TEST(BmiCache, PopulateThenStageRoundTrip) {
9797
EXPECT_EQ(body, "OBJ-A");
9898
}
9999

100+
TEST(BmiCache, StageIntoDoesNotTouchIdenticalOutputs) {
101+
Tmp t;
102+
auto home = t.path / "home";
103+
auto project = t.path / "proj" / "target";
104+
std::filesystem::create_directories(project / "gcm.cache");
105+
std::filesystem::create_directories(project / "obj");
106+
107+
writeFile(project / "gcm.cache" / "mcpplibs.cmdline.gcm", "GCM-A");
108+
writeFile(project / "obj" / "cmdline.m.o", "OBJ-A");
109+
110+
DepArtifacts arts {
111+
.gcmFiles = { "mcpplibs.cmdline.gcm" },
112+
.objFiles = { "cmdline.m.o" },
113+
};
114+
115+
auto k = makeKey(home);
116+
ASSERT_TRUE(populate_from(k, project, arts));
117+
118+
auto staged = stage_into(k, project);
119+
ASSERT_TRUE(staged) << staged.error();
120+
auto gcmTime = std::filesystem::last_write_time(project / "gcm.cache" / "mcpplibs.cmdline.gcm");
121+
auto objTime = std::filesystem::last_write_time(project / "obj" / "cmdline.m.o");
122+
123+
auto stagedAgain = stage_into(k, project);
124+
ASSERT_TRUE(stagedAgain) << stagedAgain.error();
125+
EXPECT_EQ(std::filesystem::last_write_time(project / "gcm.cache" / "mcpplibs.cmdline.gcm"), gcmTime);
126+
EXPECT_EQ(std::filesystem::last_write_time(project / "obj" / "cmdline.m.o"), objTime);
127+
}
128+
129+
TEST(BmiCache, StageIntoDoesNotOverwriteExistingOutputs) {
130+
Tmp t;
131+
auto home = t.path / "home";
132+
auto cacheProject = t.path / "cache-proj" / "target";
133+
auto project = t.path / "proj" / "target";
134+
std::filesystem::create_directories(cacheProject / "gcm.cache");
135+
std::filesystem::create_directories(cacheProject / "obj");
136+
std::filesystem::create_directories(project / "gcm.cache");
137+
std::filesystem::create_directories(project / "obj");
138+
139+
writeFile(cacheProject / "gcm.cache" / "mcpplibs.cmdline.gcm", "CACHE-GCM");
140+
writeFile(cacheProject / "obj" / "cmdline.m.o", "CACHE-OBJ");
141+
142+
DepArtifacts arts {
143+
.gcmFiles = { "mcpplibs.cmdline.gcm" },
144+
.objFiles = { "cmdline.m.o" },
145+
};
146+
147+
auto k = makeKey(home);
148+
ASSERT_TRUE(populate_from(k, cacheProject, arts));
149+
150+
writeFile(project / "gcm.cache" / "mcpplibs.cmdline.gcm", "PROJECT-GCM");
151+
writeFile(project / "obj" / "cmdline.m.o", "PROJECT-OBJ");
152+
auto gcmTime = std::filesystem::last_write_time(project / "gcm.cache" / "mcpplibs.cmdline.gcm");
153+
auto objTime = std::filesystem::last_write_time(project / "obj" / "cmdline.m.o");
154+
155+
auto staged = stage_into(k, project);
156+
ASSERT_TRUE(staged) << staged.error();
157+
158+
{
159+
std::ifstream is(project / "gcm.cache" / "mcpplibs.cmdline.gcm");
160+
std::string body((std::istreambuf_iterator<char>(is)), {});
161+
EXPECT_EQ(body, "PROJECT-GCM");
162+
}
163+
{
164+
std::ifstream is(project / "obj" / "cmdline.m.o");
165+
std::string body((std::istreambuf_iterator<char>(is)), {});
166+
EXPECT_EQ(body, "PROJECT-OBJ");
167+
}
168+
EXPECT_EQ(std::filesystem::last_write_time(project / "gcm.cache" / "mcpplibs.cmdline.gcm"), gcmTime);
169+
EXPECT_EQ(std::filesystem::last_write_time(project / "obj" / "cmdline.m.o"), objTime);
170+
}
171+
100172
TEST(BmiCache, IsCachedFalseWhenSentinelExistsButFileMissing) {
101173
Tmp t;
102174
auto home = t.path / "home";

0 commit comments

Comments
 (0)