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
12 changes: 10 additions & 2 deletions crates/fbuild-cli/src/cli/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1066,9 +1066,17 @@ pub fn resolve_project_dir(
subcommand_dir: Option<String>,
top_level_dir: &Option<String>,
) -> String {
subcommand_dir
let raw = subcommand_dir
.or_else(|| top_level_dir.clone())
.unwrap_or_else(|| ".".to_string())
.unwrap_or_else(|| ".".to_string());
// Send the daemon an absolute path. A relative one is read against the
// daemon's working directory, and every path derived from it stays
// relative while the compiler runs from the project dir -- which is how
// `fbuild ci/kitchensink build` lost its libraries' include paths
// (FastLED/fbuild#1441). Lexical only: symlinks are not resolved.
std::path::absolute(&raw)
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or(raw)
}

/// Known subcommand names for arg rewriting.
Expand Down
4 changes: 2 additions & 2 deletions crates/fbuild-daemon/src/handlers/operations/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

use super::common::{
OperationGuard, export_artifacts_bundle, resolve_build_dir, resolve_client_path,
resolve_request_project_dir,
};
use crate::context::DaemonContext;
use crate::models::{BuildRequest, OperationResponse};
use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use fbuild_core::channel::{UnboundedReceiver, UnboundedSender, unbounded};
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use tokio::sync::Notify;
Expand Down Expand Up @@ -146,7 +146,7 @@ pub async fn build(
let request_id = req
.request_id
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
let project_dir = PathBuf::from(&req.project_dir);
let project_dir = resolve_request_project_dir(&req.project_dir, req.caller_cwd.as_deref());
let stream = req.stream;

if !project_dir.exists() {
Expand Down
20 changes: 20 additions & 0 deletions crates/fbuild-daemon/src/handlers/operations/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,26 @@ pub(crate) fn parse_deploy_route(
}
}

/// A request's `project_dir` as an absolute path.
///
/// A relative project dir must be read against the *caller's* working
/// directory, not the daemon's. Left relative, every path derived from it --
/// the build dir, downloaded libraries, their `-I` flags -- stays relative,
/// while the compiler runs from the absolute project dir, so those flags point
/// nowhere (FastLED/fbuild#1441: `fbuild ci/kitchensink build` could not find
/// a registry library's headers). Lexical only: symlinks are not resolved.
pub(crate) fn resolve_request_project_dir(raw: &str, caller_cwd: Option<&str>) -> PathBuf {
let path = PathBuf::from(raw);
if path.is_absolute() {
return path;
}
let joined = match caller_cwd {
Some(cwd) => PathBuf::from(cwd).join(path),
None => path,
};
std::path::absolute(&joined).unwrap_or(joined)
}

pub(crate) fn resolve_client_path(
raw: &str,
caller_cwd: Option<&str>,
Expand Down
4 changes: 2 additions & 2 deletions crates/fbuild-daemon/src/handlers/operations/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use super::common::{
DeployRoute, EmulatorKind, OperationGuard, compute_esp32_image_hash, export_artifacts_bundle,
infer_default_emulator_kind, parse_deploy_route, qemu_extra_build_flags, resolve_build_dir,
resolve_client_path, trust_device_hash_enabled,
resolve_client_path, resolve_request_project_dir, trust_device_hash_enabled,
};
use super::deploy_port::{append_warning_to_stderr, choose_deploy_port};
use super::monitor::{MonitorOutcome, run_monitor_loop};
Expand Down Expand Up @@ -73,7 +73,7 @@ pub async fn deploy(
.request_id
.clone()
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
let project_dir = PathBuf::from(&req.project_dir);
let project_dir = resolve_request_project_dir(&req.project_dir, req.caller_cwd.as_deref());

if !project_dir.exists() {
return (
Expand Down
5 changes: 2 additions & 3 deletions crates/fbuild-daemon/src/handlers/operations/install_deps.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
//! `POST /api/install-deps` — fetch toolchains, frameworks, and libraries
//! without building.

use super::common::OperationGuard;
use super::common::{OperationGuard, resolve_request_project_dir};
use crate::context::DaemonContext;
use crate::models::{InstallDepsRequest, OperationResponse};
use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use std::path::PathBuf;
use std::sync::Arc;

/// POST /api/install-deps
Expand All @@ -21,7 +20,7 @@ pub async fn install_deps(
let request_id = req
.request_id
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
let project_dir = PathBuf::from(&req.project_dir);
let project_dir = resolve_request_project_dir(&req.project_dir, req.caller_cwd.as_deref());

if !project_dir.exists() {
return (
Expand Down
41 changes: 41 additions & 0 deletions crates/fbuild-daemon/src/handlers/operations/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,44 @@ mod image_hash_memo_tests {
);
}
}

mod request_project_dir_tests {
//! A request's project dir must come out absolute, read against the
//! caller's working directory (FastLED/fbuild#1441).
use super::super::common::resolve_request_project_dir;
use std::path::Path;

#[test]
fn relative_dir_is_joined_onto_caller_cwd() {
let cwd = std::env::temp_dir().join("caller");
let resolved = resolve_request_project_dir("ci/kitchensink", Some(cwd.to_str().unwrap()));
assert!(resolved.is_absolute());
assert_eq!(resolved, cwd.join("ci").join("kitchensink"));
}

#[test]
fn absolute_dir_is_kept_as_is() {
let abs = std::env::temp_dir().join("proj");
let resolved = resolve_request_project_dir(abs.to_str().unwrap(), Some("/elsewhere"));
assert_eq!(resolved, abs);
}

#[test]
fn relative_dir_without_caller_cwd_is_still_absolute() {
let resolved = resolve_request_project_dir("proj", None);
assert!(resolved.is_absolute());
assert!(resolved.ends_with(Path::new("proj")));
}

#[test]
fn dot_resolves_to_the_caller_cwd_itself() {
let cwd = std::env::temp_dir().join("caller");
let resolved = resolve_request_project_dir(".", Some(cwd.to_str().unwrap()));
assert!(resolved.is_absolute());
// `std::path::absolute` keeps a trailing `.` component lexically on
// some hosts; compare by components that matter.
let components: Vec<_> = resolved.components().collect();
let expected: Vec<_> = cwd.components().collect();
assert_eq!(components, expected);
}
}
Loading