Skip to content

Commit bc6db31

Browse files
committed
feat(run-environment): add CircleCI provider for GitHub repositories
Support running benchmarks from CircleCI on GitHub-hosted repositories. The build runs on CircleCI while commits and pull requests live on GitHub, so the run reports against the GitHub commit and PR. Unlike GitHub Actions, CircleCI checks out the associated commit itself rather than a synthetic merge ref, so the default git HEAD read already yields CIRCLE_SHA1 and no commit hash override is needed. CircleCI exposes no base branch variable either: baseRef is left empty and CodSpeed resolves the base branch from the pull request. Uploads authenticate with a static CODSPEED_TOKEN and are sent as a single self-contained part, mirroring Buildkite. Refs COD-2995 Refs COD-3262
1 parent 97a789a commit bc6db31

8 files changed

Lines changed: 558 additions & 1 deletion
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use console::style;
2+
use log::*;
3+
use simplelog::SharedLogger;
4+
use std::{env, io::Write};
5+
6+
use crate::{
7+
logger::{GroupEvent, get_announcement_event, get_group_event, get_json_event},
8+
run_environment::logger::should_provider_logger_handle_record,
9+
};
10+
11+
/// A logger that prints logs in the format expected by CircleCI
12+
///
13+
/// CircleCI has no collapsible section markers, so groups are printed as plain
14+
/// headers.
15+
pub struct CircleCILogger {
16+
log_level: LevelFilter,
17+
}
18+
19+
impl CircleCILogger {
20+
pub fn new() -> Self {
21+
// force activation of colors: CircleCI renders ANSI sequences in its UI, but
22+
// the output is not a TTY so colors would be disabled by default.
23+
console::set_colors_enabled(true);
24+
25+
let log_level = env::var("CODSPEED_LOG")
26+
.ok()
27+
.and_then(|log_level| log_level.parse::<log::LevelFilter>().ok())
28+
.unwrap_or(log::LevelFilter::Info);
29+
Self { log_level }
30+
}
31+
}
32+
33+
impl Log for CircleCILogger {
34+
fn enabled(&self, _metadata: &Metadata) -> bool {
35+
true
36+
}
37+
38+
fn log(&self, record: &Record) {
39+
if !should_provider_logger_handle_record(record) {
40+
return;
41+
}
42+
43+
let level = record.level();
44+
let message = record.args();
45+
46+
if let Some(group_event) = get_group_event(record) {
47+
match group_event {
48+
GroupEvent::Start(name) | GroupEvent::StartOpened(name) => {
49+
println!("{}", style(name).cyan().bold());
50+
}
51+
GroupEvent::End => {}
52+
}
53+
return;
54+
}
55+
56+
if get_json_event(record).is_some() {
57+
return;
58+
}
59+
60+
if let Some(announcement) = get_announcement_event(record) {
61+
println!("{}", style(announcement).green());
62+
return;
63+
}
64+
65+
if level > self.log_level {
66+
return;
67+
}
68+
69+
match level {
70+
Level::Error => {
71+
println!("{}", style(message).red());
72+
}
73+
Level::Warn => {
74+
println!("{}", style(message).yellow());
75+
}
76+
Level::Info => {
77+
println!("{message}");
78+
}
79+
Level::Debug => {
80+
println!("{}", style(message).cyan());
81+
}
82+
Level::Trace => {
83+
println!("{}", style(message).magenta());
84+
}
85+
}
86+
}
87+
88+
fn flush(&self) {
89+
std::io::stdout().flush().unwrap();
90+
}
91+
}
92+
93+
impl SharedLogger for CircleCILogger {
94+
fn level(&self) -> LevelFilter {
95+
self.log_level
96+
}
97+
98+
fn config(&self) -> Option<&simplelog::Config> {
99+
None
100+
}
101+
102+
fn as_log(self: Box<Self>) -> Box<dyn Log> {
103+
Box::new(*self)
104+
}
105+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
mod logger;
2+
mod provider;
3+
4+
pub use provider::CircleCIProvider;

0 commit comments

Comments
 (0)