Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions api-model/src/buck2/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ use crate::buck2::{status::Status, types::ProjectRelativePath};
/// Parameters required to build a task.
#[derive(Debug, Deserialize, Serialize, ToSchema)]
pub struct TaskBuildRequest {
/// The repository base path
/// The Buck2 project path within the monorepo (for example `/jupiter/callisto`).
pub repo: String,
/// The change list link (URL)
pub cl_link: String,
//TODO: for old database only, delete after updated
pub cl_id: i64,
/// The list of file diff changes
/// The list of changed files, expressed relative to the monorepo root.
///
/// Example values:
/// - `jupiter/callisto/src/access_token.rs`
/// - `common/lib.rs`
pub changes: Vec<Status<ProjectRelativePath>>,
/// Buck2 target path (e.g. //app:server). Optional for backward compatibility.
#[serde(default, alias = "targets_path")]
Expand All @@ -41,6 +45,7 @@ pub struct RetryBuildRequest {
pub build_id: String,
pub cl_link: String,
pub cl_id: i64,
/// The list of changed files, expressed relative to the monorepo root.
pub changes: Vec<Status<ProjectRelativePath>>,
pub targets: Option<Vec<String>>,
}
Expand Down
5 changes: 5 additions & 0 deletions api-model/src/buck2/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ pub enum TaskPhase {
RunningBuild,
}

/// Slash-separated relative path used in Buck2 payloads.
///
/// The exact base directory is defined by the surrounding API. For task/build
/// requests, see the field-level docs to determine whether the path is relative
/// to the monorepo root or some other project root.
#[derive(Clone, Debug, Hash, PartialEq, Eq, Display, Deserialize, Serialize, ToSchema)]
pub struct ProjectRelativePath(String);

Expand Down
2 changes: 2 additions & 0 deletions api-model/src/buck2/ws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ pub enum WSMessage {
// Server -> Worker messages
TaskBuild {
build_id: String,
/// The Buck2 project path within the monorepo (for example `/jupiter/callisto`).
repo: String,
cl_link: String,
/// The list of changed files, expressed relative to the monorepo root.
changes: Vec<Status<ProjectRelativePath>>,
},

Expand Down
2 changes: 2 additions & 0 deletions ceres/src/build_trigger/changes_calculator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ impl ChangesCalculator {
&self,
cl_diff_files: Vec<ClDiffFile>,
) -> Result<Vec<Status<ProjectRelativePath>>, MegaError> {
// Orion task requests carry change paths relative to the monorepo root,
// so we preserve repository-relative diff paths here.
let to_project_relative = |path: &PathBuf| -> Result<ProjectRelativePath, MegaError> {
let rel = path
.to_string_lossy()
Expand Down
74 changes: 73 additions & 1 deletion orion-server/src/service/api_v2_service.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{convert::Infallible, pin::Pin, time::Duration};
use std::{collections::HashSet, convert::Infallible, pin::Pin, time::Duration};

use api_model::{
buck2::{
Expand Down Expand Up @@ -47,6 +47,29 @@ type MessageErrorResponse = (StatusCode, Json<MessageResponse>);
type JsonValueErrorResponse = (StatusCode, Json<Value>);
type LogSseStream = Pin<Box<dyn Stream<Item = Result<Event, Infallible>> + Send>>;

/// Normalize incoming task changes to the repo-root-relative contract.
///
/// The protocol no longer asks workers to guess whether a path was expressed
/// relative to the selected sub-project. We only perform lossless cleanup here:
/// trim accidental leading slashes and drop exact duplicates.
fn normalize_repo_root_changes(
changes: Vec<Status<ProjectRelativePath>>,
) -> Vec<Status<ProjectRelativePath>> {
// Avoid reserving memory directly from request-controlled input length.
let mut normalized = Vec::new();
let mut seen = HashSet::new();

for change in changes {
let normalized_change =
change.into_map(|path| ProjectRelativePath::new(path.as_str().trim_start_matches('/')));
if seen.insert(normalized_change.clone()) {
normalized.push(normalized_change);
}
}

normalized
}

pub async fn task_output(state: &AppState, id: &str) -> Result<Sse<LogSseStream>, StatusCode> {
if !state.scheduler.active_builds.contains_key(id) {
return Err(StatusCode::NOT_FOUND);
Expand Down Expand Up @@ -584,6 +607,10 @@ pub async fn build_retry(
state: &AppState,
req: api_model::buck2::api::RetryBuildRequest,
) -> Response {
let req = api_model::buck2::api::RetryBuildRequest {
changes: normalize_repo_root_changes(req.changes),
..req
};
let old_build_id = match req.build_id.parse::<uuid::Uuid>() {
Ok(uuid) => uuid,
Err(_) => {
Expand Down Expand Up @@ -881,6 +908,10 @@ async fn handle_immediate_task_dispatch_v2(
}

pub async fn task_handler_v2(state: &AppState, req: TaskBuildRequest) -> Response {
let req = TaskBuildRequest {
changes: normalize_repo_root_changes(req.changes),
..req
};
let task_id = Uuid::now_v7();
if let Err(err) =
OrionTasksRepo::insert_task(task_id, &req.cl_link, &req.repo, &req.changes, &state.conn)
Expand Down Expand Up @@ -1020,3 +1051,44 @@ pub async fn get_orion_client_status_by_id(
})?;
Ok(Json(OrionClientStatus::from_worker_status(&worker)))
}

#[cfg(test)]
mod tests {
use api_model::buck2::{status::Status, types::ProjectRelativePath};

use super::normalize_repo_root_changes;

#[test]
fn test_normalize_repo_root_changes_trims_leading_slashes_and_deduplicates() {
let normalized = normalize_repo_root_changes(vec![
Status::Modified(ProjectRelativePath::new("/jupiter/callisto/src/main.rs")),
Status::Modified(ProjectRelativePath::new("jupiter/callisto/src/main.rs")),
Status::Removed(ProjectRelativePath::new("//common/lib.rs")),
Status::Removed(ProjectRelativePath::new("common/lib.rs")),
]);

assert_eq!(
normalized,
vec![
Status::Modified(ProjectRelativePath::new("jupiter/callisto/src/main.rs")),
Status::Removed(ProjectRelativePath::new("common/lib.rs")),
]
);
}

#[test]
fn test_normalize_repo_root_changes_keeps_distinct_status_entries() {
let normalized = normalize_repo_root_changes(vec![
Status::Added(ProjectRelativePath::new("/common/lib.rs")),
Status::Removed(ProjectRelativePath::new("common/lib.rs")),
]);

assert_eq!(
normalized,
vec![
Status::Added(ProjectRelativePath::new("common/lib.rs")),
Status::Removed(ProjectRelativePath::new("common/lib.rs")),
]
);
}
}
Loading
Loading