Skip to content

Commit 41fdd1d

Browse files
committed
cvd file bug
Assisted-by: Jetski:GeminiNext
1 parent df0986d commit 41fdd1d

13 files changed

Lines changed: 480 additions & 63 deletions

File tree

base/cvd/cuttlefish/host/commands/cvd/cli/BUILD.bazel

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ cf_cc_library(
7979
],
8080
)
8181

82+
cf_cc_library(
83+
name = "log_tail",
84+
srcs = ["log_tail.cpp"],
85+
hdrs = ["log_tail.h"],
86+
deps = [
87+
"//cuttlefish/common/libs/fs",
88+
"//cuttlefish/result",
89+
"@abseil-cpp//absl/strings",
90+
"@abseil-cpp//absl/strings:cord",
91+
],
92+
)
93+
8294
cf_cc_test(
8395
name = "log_files_test",
8496
srcs = ["log_files_test.cpp"],
@@ -118,6 +130,7 @@ cf_cc_library(
118130
"//cuttlefish/host/commands/cvd/cli:command_request",
119131
"//cuttlefish/host/commands/cvd/cli:help_format",
120132
"//cuttlefish/host/commands/cvd/cli:types",
133+
"//cuttlefish/host/commands/cvd/cli/commands:bug",
121134
"//cuttlefish/host/commands/cvd/cli/commands:bugreport",
122135
"//cuttlefish/host/commands/cvd/cli/commands:cache",
123136
"//cuttlefish/host/commands/cvd/cli/commands:clear",

base/cvd/cuttlefish/host/commands/cvd/cli/commands/BUILD.bazel

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,32 @@ exports_files([
1414
"load_configs.h",
1515
])
1616

17+
cf_cc_library(
18+
name = "bug",
19+
srcs = ["bug.cpp"],
20+
hdrs = ["bug.h"],
21+
clang_format_enabled = True,
22+
deps = [
23+
"//cuttlefish/common/libs/fs",
24+
"//cuttlefish/common/libs/utils:files",
25+
"//cuttlefish/common/libs/utils:subprocess",
26+
"//cuttlefish/common/libs/utils:subprocess_managed_stdio",
27+
"//cuttlefish/common/libs/utils:users",
28+
":bugreport",
29+
"//cuttlefish/host/commands/cvd/cli:command_request",
30+
"//cuttlefish/host/commands/cvd/cli:log_files",
31+
"//cuttlefish/host/commands/cvd/cli:log_tail",
32+
"//cuttlefish/host/commands/cvd/cli:types",
33+
"//cuttlefish/host/commands/cvd/cli/commands:command_handler",
34+
"//cuttlefish/host/commands/cvd/instances",
35+
"//cuttlefish/host/commands/cvd/version",
36+
"//cuttlefish/result",
37+
"//libbase",
38+
"@abseil-cpp//absl/strings",
39+
"@fmt",
40+
],
41+
)
42+
1743
cf_cc_library(
1844
name = "bugreport",
1945
srcs = ["bugreport.cpp"],
@@ -254,6 +280,7 @@ cf_cc_library(
254280
"//cuttlefish/common/libs/fs",
255281
"//cuttlefish/common/libs/utils:flag_parser",
256282
"//cuttlefish/host/commands/cvd/cli:command_request",
283+
"//cuttlefish/host/commands/cvd/cli:log_tail",
257284
"//cuttlefish/host/commands/cvd/cli:types",
258285
"//cuttlefish/host/commands/cvd/cli:utils",
259286
"//cuttlefish/host/commands/cvd/cli/commands:command_handler",
@@ -265,7 +292,6 @@ cf_cc_library(
265292
"//libbase",
266293
"@abseil-cpp//absl/log:check",
267294
"@abseil-cpp//absl/strings",
268-
"@abseil-cpp//absl/strings:cord",
269295
"@fmt",
270296
],
271297
)
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/*
2+
* Copyright (C) 2026 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "cuttlefish/host/commands/cvd/cli/commands/bug.h"
18+
19+
#include <fcntl.h>
20+
#include <algorithm>
21+
#include <cctype>
22+
#include <cstddef>
23+
#include <iostream>
24+
#include <memory>
25+
#include <optional>
26+
#include <string>
27+
#include <string_view>
28+
#include <utility>
29+
#include <vector>
30+
31+
#include <fmt/format.h>
32+
#include <fmt/ranges.h>
33+
34+
#include "cuttlefish/common/libs/fs/shared_fd.h"
35+
#include "cuttlefish/common/libs/utils/files.h"
36+
#include "cuttlefish/common/libs/utils/subprocess.h"
37+
#include "cuttlefish/common/libs/utils/subprocess_managed_stdio.h"
38+
#include "cuttlefish/common/libs/utils/users.h"
39+
#include "cuttlefish/host/commands/cvd/cli/command_request.h"
40+
#include "cuttlefish/host/commands/cvd/cli/commands/bugreport.h"
41+
#include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h"
42+
#include "cuttlefish/host/commands/cvd/cli/log_files.h"
43+
#include "cuttlefish/host/commands/cvd/cli/log_tail.h"
44+
#include "cuttlefish/host/commands/cvd/cli/types.h"
45+
#include "cuttlefish/host/commands/cvd/instances/instance_database.h"
46+
#include "cuttlefish/host/commands/cvd/instances/local_instance.h"
47+
#include "cuttlefish/host/commands/cvd/version/version.h"
48+
#include "cuttlefish/result/result.h"
49+
50+
namespace cuttlefish {
51+
namespace {
52+
53+
constexpr char kSummaryHelpText[] = "File an issue using go/bugged";
54+
constexpr char kHelpMessage[] = R"(
55+
usage: cvd bug
56+
57+
`cvd bug` will invoke `bugged create` to file an issue against Cuttlefish.
58+
It requires `bugged` to be installed on the system.
59+
See go/bugged for more information.
60+
)";
61+
62+
Result<std::string> ParseBugId(std::string_view stdout_str) {
63+
const std::string_view prefix = "http://b/";
64+
const size_t pos = stdout_str.find(prefix);
65+
CF_EXPECT(pos != std::string_view::npos, "Prefix not found");
66+
67+
const size_t start = pos + prefix.size();
68+
size_t end = start;
69+
while (end < stdout_str.size() && std::isdigit(stdout_str[end])) {
70+
end++;
71+
}
72+
CF_EXPECT(end > start, "No digits found after prefix");
73+
74+
return std::string(stdout_str.substr(start, end - start));
75+
}
76+
77+
Result<LocalInstance> GetLatestLocalInstance(
78+
const InstanceDatabase& instance_db) {
79+
const std::vector<LocalInstanceGroup> groups =
80+
CF_EXPECT(instance_db.InstanceGroups());
81+
CF_EXPECT(!groups.empty(), "No instance groups found.");
82+
83+
const auto latest_group = std::max_element(
84+
groups.begin(), groups.end(),
85+
[](const LocalInstanceGroup& a, const LocalInstanceGroup& b) {
86+
return a.StartTime() < b.StartTime();
87+
});
88+
89+
CF_EXPECT(!latest_group->Instances().empty(),
90+
"Latest instance group has no instances.");
91+
return latest_group->Instances().front();
92+
}
93+
94+
Result<std::string> GenerateIssueText() {
95+
const std::optional<std::string> previous_log_opt =
96+
CF_EXPECT(GetPreviousLogFile());
97+
CF_EXPECT(previous_log_opt.has_value(), "No previous log file found.");
98+
const std::string previous_log = *previous_log_opt;
99+
100+
const SharedFD fd = SharedFD::Open(previous_log, O_RDONLY);
101+
CF_EXPECTF(fd->IsOpen(), "Failed to open log file {}: {}", previous_log,
102+
fd->StrError());
103+
104+
const std::vector<std::string> lines = CF_EXPECTF(
105+
GetLastNLines(fd, 30), "Failed to read log file {}", previous_log);
106+
const std::string log_tail =
107+
fmt::format("```\n{}\n```\n", fmt::join(lines, "\n"));
108+
109+
const std::string username = CF_EXPECT(CurrentUserName());
110+
111+
return fmt::format(
112+
"Cuttlefish bug report\n\n"
113+
"{}\n"
114+
"CVD Version:\n{}\n\n"
115+
"CC+=cloud-android-devs\n"
116+
"COMPONENT=162041\n"
117+
"HOTLIST+=1883485\n"
118+
"PRIORITY=P2\n"
119+
"REPORTER={}\n"
120+
"SEVERITY=S2\n"
121+
"STATUS=NEW\n"
122+
"TYPE=BUG\n",
123+
log_tail, GetVersionIds().ToPrettyString(), username);
124+
}
125+
126+
Result<std::string> GetBuggedBinary() {
127+
CF_EXPECT(FileExists("/usr/bin/gcertstatus"), "Not a Googler desktop.");
128+
const int gcert_status = Execute({"/usr/bin/gcertstatus"});
129+
CF_EXPECT(gcert_status == 0, "Please run gcert.");
130+
131+
if (FileExists("/usr/bin/bugged")) {
132+
return "/usr/bin/bugged";
133+
}
134+
return "/google/bin/releases/bugged/bugged";
135+
}
136+
137+
Result<void> ProduceBugreport(const InstanceDatabase& instance_db,
138+
const cvd_common::Envs& env) {
139+
const LocalInstance latest_instance =
140+
CF_EXPECT(GetLatestLocalInstance(instance_db));
141+
const std::string android_host_out = latest_instance.host_artifacts_path();
142+
const std::string home = latest_instance.home_directory();
143+
const std::string log_dir = CvdUserLogDir();
144+
145+
CF_EXPECT(RunHostBugreportCommand(android_host_out, home, env, {}, log_dir));
146+
return {};
147+
}
148+
149+
Result<std::string> FileIssue(const std::string& bugged_bin,
150+
const std::string& issue_text) {
151+
Command command(bugged_bin);
152+
command.AddParameter("create");
153+
command.AddParameter("--format=MARKDOWN");
154+
155+
std::string stdout_str;
156+
const int exit_code = RunWithManagedStdio(std::move(command), &issue_text,
157+
&stdout_str, nullptr);
158+
CF_EXPECTF(exit_code == 0, "bugged exited with code {}", exit_code);
159+
160+
const std::string bug_id =
161+
CF_EXPECTF(ParseBugId(stdout_str),
162+
"Failed to parse bug ID from bugged output: {}", stdout_str);
163+
return bug_id;
164+
}
165+
166+
Result<void> AttachFile(const std::string& bugged_bin,
167+
const std::string& bug_id,
168+
const std::string& file_path) {
169+
if (FileExists(file_path)) {
170+
Command attach_cmd(bugged_bin);
171+
attach_cmd.AddParameter("attach");
172+
attach_cmd.AddParameter(bug_id);
173+
attach_cmd.AddParameter(file_path);
174+
175+
const int attach_status =
176+
RunWithManagedStdio(std::move(attach_cmd), nullptr, nullptr, nullptr);
177+
if (attach_status != 0) {
178+
std::cerr << "Failed to attach file " << file_path << " to bug " << bug_id
179+
<< std::endl;
180+
}
181+
} else {
182+
std::cerr << "File " << file_path << " does not exist to attach."
183+
<< std::endl;
184+
}
185+
return {};
186+
}
187+
188+
} // namespace
189+
190+
CvdBugCommandHandler::CvdBugCommandHandler(const InstanceDatabase& instance_db)
191+
: instance_db_(instance_db) {}
192+
193+
Result<void> CvdBugCommandHandler::Handle(const CommandRequest& request) {
194+
const std::string bugged_bin = CF_EXPECT(GetBuggedBinary());
195+
const std::string issue_text = CF_EXPECT(GenerateIssueText());
196+
CF_EXPECT(ProduceBugreport(instance_db_, request.Env()));
197+
const std::string bug_id = CF_EXPECT(FileIssue(bugged_bin, issue_text));
198+
std::cout << "Created issue http://b/" << bug_id << std::endl;
199+
200+
const std::string bugreport_zip = CvdUserLogDir() + "/host_bugreport.zip";
201+
CF_EXPECT(AttachFile(bugged_bin, bug_id, bugreport_zip));
202+
203+
const std::optional<std::string> previous_log_opt =
204+
CF_EXPECT(GetPreviousLogFile());
205+
if (previous_log_opt) {
206+
CF_EXPECT(AttachFile(bugged_bin, bug_id, *previous_log_opt));
207+
} else {
208+
std::cerr << "No previous log file found to attach." << std::endl;
209+
}
210+
211+
return {};
212+
}
213+
214+
std::vector<std::string> CvdBugCommandHandler::CmdList() const {
215+
return {"bug"};
216+
}
217+
218+
std::string CvdBugCommandHandler::SummaryHelp() const {
219+
return kSummaryHelpText;
220+
}
221+
222+
Result<std::string> CvdBugCommandHandler::DetailedHelp(const CommandRequest&) {
223+
return kHelpMessage;
224+
}
225+
226+
std::unique_ptr<CvdCommandHandler> NewCvdBugCommandHandler(
227+
const InstanceDatabase& instance_db) {
228+
return std::make_unique<CvdBugCommandHandler>(instance_db);
229+
}
230+
231+
} // namespace cuttlefish
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (C) 2026 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <memory>
20+
#include <string>
21+
#include <vector>
22+
23+
#include "cuttlefish/host/commands/cvd/cli/command_request.h"
24+
#include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h"
25+
#include "cuttlefish/host/commands/cvd/instances/instance_database.h"
26+
#include "cuttlefish/result/result.h"
27+
28+
namespace cuttlefish {
29+
30+
class CvdBugCommandHandler : public CvdCommandHandler {
31+
public:
32+
CvdBugCommandHandler(const InstanceDatabase& instance_db);
33+
34+
Result<void> Handle(const CommandRequest& request) override;
35+
std::vector<std::string> CmdList() const override;
36+
std::string SummaryHelp() const override;
37+
Result<std::string> DetailedHelp(const CommandRequest& request) override;
38+
39+
private:
40+
const InstanceDatabase& instance_db_;
41+
};
42+
43+
std::unique_ptr<CvdCommandHandler> NewCvdBugCommandHandler(
44+
const InstanceDatabase& instance_db);
45+
46+
} // namespace cuttlefish

0 commit comments

Comments
 (0)