Skip to content

Commit 6191ee3

Browse files
authored
Merge pull request #2730 from jhasse/restat-with-builddir
"ninja -t restat" doesn't work with "builddir"
2 parents cc60300 + 3fdd179 commit 6191ee3

File tree

3 files changed

+142
-5
lines changed

3 files changed

+142
-5
lines changed

doc/manual.asciidoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ _Available since Ninja 1.11._
397397
398398
`restat`:: updates all recorded file modification timestamps in the `.ninja_log`
399399
file. _Available since Ninja 1.10._
400+
+
401+
The build manifest won't be parsed when running this tool. If you're using a
402+
`builddir` you must pass it via `--builddir=DIR`. _Available since Ninja 1.14._
400403
401404
`rules`:: output the list of all rules. It can be used to know which rule name
402405
to pass to +ninja -t targets rule _name_+ or +ninja -t compdb+. Adding the `-d`

src/ninja.cc

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,20 +1114,25 @@ int NinjaMain::ToolRestat(const Options* options, int argc, char* argv[]) {
11141114

11151115
optind = 1;
11161116
int opt;
1117-
while ((opt = getopt(argc, argv, const_cast<char*>("h"))) != -1) {
1117+
const option kLongOptions[] = { { "builddir", required_argument, nullptr,
1118+
'b' },
1119+
{ "help", no_argument, nullptr, 'h' },
1120+
{ nullptr, 0, nullptr, 0 } };
1121+
while ((opt = getopt_long(argc, argv, const_cast<char*>("h"), kLongOptions,
1122+
nullptr)) != -1) {
11181123
switch (opt) {
1124+
case 'b':
1125+
build_dir_ = optarg;
1126+
break;
11191127
case 'h':
11201128
default:
1121-
printf("usage: ninja -t restat [outputs]\n");
1129+
printf("usage: ninja -t restat [--builddir=DIR] [outputs]\n");
11221130
return 1;
11231131
}
11241132
}
11251133
argv += optind;
11261134
argc -= optind;
11271135

1128-
if (!EnsureBuildDirExists())
1129-
return 1;
1130-
11311136
string log_path = ".ninja_log";
11321137
if (!build_dir_.empty())
11331138
log_path = build_dir_ + "/" + log_path;
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#!/usr/bin/env python3
2+
3+
"""Integration test for 'ninja -t restat' with builddir."""
4+
5+
import os
6+
import subprocess
7+
import tempfile
8+
import time
9+
import unittest
10+
11+
NINJA_PATH = os.path.abspath("./ninja")
12+
13+
14+
class RestatBuildDirTest(unittest.TestCase):
15+
"""Test that 'ninja -t restat' respects builddir."""
16+
17+
def setUp(self):
18+
"""Create a temporary directory for the test."""
19+
self.test_dir = tempfile.mkdtemp(prefix="ninja_restat_test_")
20+
self.original_dir = os.getcwd()
21+
os.chdir(self.test_dir)
22+
23+
def tearDown(self):
24+
"""Clean up the temporary directory."""
25+
os.chdir(self.original_dir)
26+
import shutil
27+
28+
shutil.rmtree(self.test_dir, ignore_errors=True)
29+
30+
def test_restat_with_builddir(self):
31+
"""Test that ninja -t restat updates mtime in builddir/.ninja_log."""
32+
33+
# Create a simple build.ninja file with builddir
34+
build_ninja = """
35+
builddir = build
36+
37+
rule touch
38+
command = touch $out
39+
description = Creating $out
40+
41+
build output.txt: touch
42+
"""
43+
with open("build.ninja", "w") as f:
44+
f.write(build_ninja)
45+
46+
# Run ninja to build the output
47+
result = subprocess.run([NINJA_PATH], capture_output=True, text=True)
48+
self.assertEqual(result.returncode, 0, f"Initial build failed: {result.stderr}")
49+
self.assertTrue(os.path.exists("output.txt"), "output.txt was not created")
50+
self.assertTrue(
51+
os.path.exists("build/.ninja_log"), "build/.ninja_log was not created"
52+
)
53+
54+
# Read the original .ninja_log to get the initial mtime
55+
with open("build/.ninja_log", "r") as f:
56+
log_lines = f.readlines()
57+
58+
# Find the entry for output.txt
59+
output_entry = ""
60+
for line in log_lines:
61+
if "output.txt" in line:
62+
output_entry = line.strip()
63+
break
64+
65+
self.assertNotEqual(
66+
output_entry, "", "output.txt not found in build/.ninja_log"
67+
)
68+
69+
# Parse the log entry: start_time\tend_time\tmtime\toutput\tcommand_hash
70+
parts = output_entry.split("\t")
71+
self.assertEqual(len(parts), 5, f"Unexpected log format: {output_entry}")
72+
original_mtime = int(parts[2])
73+
74+
# Wait a bit to ensure different mtime
75+
time.sleep(0.01)
76+
77+
# Touch the output file to update its mtime using explicit time
78+
current_time = time.time() + 2 # Add 2 seconds to ensure different mtime
79+
os.utime("output.txt", (current_time, current_time))
80+
81+
# Get the new actual file mtime
82+
new_file_mtime = int(
83+
os.path.getmtime("output.txt") * 1000000000
84+
) # Convert to nanoseconds
85+
self.assertGreater(
86+
new_file_mtime,
87+
original_mtime,
88+
f"File mtime should have increased: {new_file_mtime} vs {original_mtime}",
89+
)
90+
91+
# Run ninja -t restat
92+
result = subprocess.run(
93+
[NINJA_PATH, "-t", "restat", "--builddir=build"],
94+
capture_output=True,
95+
text=True,
96+
)
97+
self.assertEqual(
98+
result.returncode, 0, f"ninja -t restat failed: {result.stderr}"
99+
)
100+
101+
# Read the updated .ninja_log
102+
with open("build/.ninja_log", "r") as f:
103+
updated_log_lines = f.readlines()
104+
105+
# Find the updated entry for output.txt
106+
updated_entry = ""
107+
for line in updated_log_lines:
108+
if "output.txt" in line:
109+
updated_entry = line.strip()
110+
break
111+
112+
self.assertNotEqual(
113+
updated_entry, "", "output.txt not found in updated build/.ninja_log"
114+
)
115+
116+
# Parse the updated log entry
117+
updated_parts = updated_entry.split("\t")
118+
self.assertEqual(
119+
len(updated_parts), 5, f"Unexpected updated log format: {updated_entry}"
120+
)
121+
updated_mtime = int(updated_parts[2])
122+
123+
# Verify that the mtime was updated in the log
124+
self.assertGreater(
125+
updated_mtime,
126+
original_mtime,
127+
f"mtime in build/.ninja_log should have been updated. "
128+
f"Original: {original_mtime}, Updated: {updated_mtime}",
129+
)

0 commit comments

Comments
 (0)