Skip to content

Commit 3ed7041

Browse files
authored
Merge branch 'main' into fix/basic-readme-markdown
2 parents bd6457b + 83774bc commit 3ed7041

File tree

43 files changed

+2412
-358
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2412
-358
lines changed

Cargo.lock

Lines changed: 498 additions & 96 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ dunce = "1.0.3"
124124
either = "1.9.0"
125125
futures = "0.3.31"
126126
git2 = { version = "0.20.4", default-features = false }
127+
gix-index = { version = "0.47.0", default-features = false }
127128
hex = "0.4.3"
128129
httpmock = { version = "0.8.0", default-features = false }
129130
indicatif = "0.18.3"

crates/turborepo-api-client/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ impl APIClient {
605605
/// Builds a shared HTTP client with optional connect timeout. This is
606606
/// the single TLS initialization point — all consumers should share the
607607
/// resulting client.
608+
#[tracing::instrument(skip_all)]
608609
pub fn build_http_client(connect_timeout: Option<Duration>) -> Result<reqwest::Client> {
609610
let mut builder = reqwest::Client::builder();
610611
if let Some(dur) = connect_timeout {

crates/turborepo-auth/src/auth/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,12 @@ mod tests {
119119
// Mock the turborepo_dirs functions for testing
120120
fn create_mock_vercel_config_dir() -> AbsoluteSystemPathBuf {
121121
let tmp_dir = tempdir().expect("Failed to create temp dir");
122-
AbsoluteSystemPathBuf::try_from(tmp_dir.into_path()).expect("Failed to create path")
122+
AbsoluteSystemPathBuf::try_from(tmp_dir.keep()).expect("Failed to create path")
123123
}
124124

125125
fn create_mock_turbo_config_dir() -> AbsoluteSystemPathBuf {
126126
let tmp_dir = tempdir().expect("Failed to create temp dir");
127-
AbsoluteSystemPathBuf::try_from(tmp_dir.into_path()).expect("Failed to create path")
127+
AbsoluteSystemPathBuf::try_from(tmp_dir.keep()).expect("Failed to create path")
128128
}
129129

130130
fn setup_auth_file(

crates/turborepo-engine/src/builder.rs

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -256,13 +256,19 @@ impl<'a, L: TurboJsonLoader> EngineBuilder<'a, L> {
256256

257257
let mut visited = HashSet::new();
258258
let mut engine: Engine<Building, TaskDefinition> = Engine::default();
259+
let mut turbo_json_chain_cache: HashMap<PackageName, Vec<&TurboJson>> = HashMap::new();
259260

260261
while let Some(task_id) = traversal_queue.pop_front() {
261262
{
262263
let (task_id, span) = task_id.clone().split();
263264
engine.add_task_location(task_id.into_owned(), span);
264265
}
265266

267+
// Skip before doing expensive work if we've already processed this task.
268+
if visited.contains(task_id.as_inner()) {
269+
continue;
270+
}
271+
266272
// For root tasks, verify they are either explicitly enabled OR (when using
267273
// add_all_tasks mode like devtools) have a definition in root turbo.json.
268274
// Tasks defined without the //# prefix (like "transit") in root turbo.json
@@ -323,17 +329,13 @@ impl<'a, L: TurboJsonLoader> EngineBuilder<'a, L> {
323329
)));
324330
}
325331

326-
let task_definition = self.task_definition(
332+
let task_definition = self.task_definition_cached(
327333
turbo_json_loader,
328334
&task_id,
329335
&task_id.as_non_workspace_task_name(),
336+
&mut turbo_json_chain_cache,
330337
)?;
331338

332-
// Skip this iteration of the loop if we've already seen this taskID
333-
if visited.contains(task_id.as_inner()) {
334-
continue;
335-
}
336-
337339
visited.insert(task_id.as_inner().clone());
338340

339341
// Note that the Go code has a whole if/else statement for putting stuff into
@@ -576,19 +578,51 @@ impl<'a, L: TurboJsonLoader> EngineBuilder<'a, L> {
576578
Ok(TaskDefinitionResult::not_found())
577579
}
578580

579-
fn task_definition(
581+
/// Resolves the merged `TaskDefinition` for a task, caching the turbo.json
582+
/// chain per package. The chain only depends on the package name (not the
583+
/// task), so multiple tasks in the same package share the cached chain.
584+
fn task_definition_cached<'b>(
580585
&self,
581-
turbo_json_loader: &L,
586+
turbo_json_loader: &'b L,
582587
task_id: &Spanned<TaskId>,
583588
task_name: &TaskName,
589+
chain_cache: &mut HashMap<PackageName, Vec<&'b TurboJson>>,
584590
) -> Result<TaskDefinition, BuilderError> {
585591
let processed_task_definition = ProcessedTaskDefinition::from_iter(
586-
self.task_definition_chain(turbo_json_loader, task_id, task_name)?,
592+
self.task_definition_chain_cached(turbo_json_loader, task_id, task_name, chain_cache)?,
587593
);
588594
let path_to_root = self.path_to_root(task_id.as_inner())?;
589595
TaskDefinition::from_processed(processed_task_definition, &path_to_root)
590596
}
591597

598+
/// Like `task_definition_chain` but caches the turbo.json chain per
599+
/// package.
600+
fn task_definition_chain_cached<'b>(
601+
&self,
602+
turbo_json_loader: &'b L,
603+
task_id: &Spanned<TaskId>,
604+
task_name: &TaskName,
605+
chain_cache: &mut HashMap<PackageName, Vec<&'b TurboJson>>,
606+
) -> Result<Vec<ProcessedTaskDefinition>, BuilderError> {
607+
let package_name = PackageName::from(task_id.package());
608+
let turbo_json_chain = match chain_cache.get(&package_name) {
609+
Some(cached) => cached.clone(),
610+
None => {
611+
let chain = self.turbo_json_chain(turbo_json_loader, &package_name)?;
612+
chain_cache.insert(package_name, chain.clone());
613+
chain
614+
}
615+
};
616+
617+
Self::resolve_task_definitions_from_chain(
618+
turbo_json_chain,
619+
task_id,
620+
task_name,
621+
self.is_single,
622+
self.should_validate_engine,
623+
)
624+
}
625+
592626
pub fn task_definition_chain(
593627
&self,
594628
turbo_json_loader: &L,
@@ -597,6 +631,25 @@ impl<'a, L: TurboJsonLoader> EngineBuilder<'a, L> {
597631
) -> Result<Vec<ProcessedTaskDefinition>, BuilderError> {
598632
let package_name = PackageName::from(task_id.package());
599633
let turbo_json_chain = self.turbo_json_chain(turbo_json_loader, &package_name)?;
634+
Self::resolve_task_definitions_from_chain(
635+
turbo_json_chain,
636+
task_id,
637+
task_name,
638+
self.is_single,
639+
self.should_validate_engine,
640+
)
641+
}
642+
643+
/// Given a resolved turbo.json chain for a package, extract the task
644+
/// definitions for a specific task by walking the chain and handling
645+
/// `extends: false`.
646+
fn resolve_task_definitions_from_chain(
647+
turbo_json_chain: Vec<&TurboJson>,
648+
task_id: &Spanned<TaskId>,
649+
task_name: &TaskName,
650+
is_single: bool,
651+
should_validate_engine: bool,
652+
) -> Result<Vec<ProcessedTaskDefinition>, BuilderError> {
600653
let mut task_definitions = Vec::new();
601654

602655
// Find the first package in the chain (iterating in reverse from leaf to root)
@@ -645,7 +698,7 @@ impl<'a, L: TurboJsonLoader> EngineBuilder<'a, L> {
645698
task_definitions.push(root_definition)
646699
}
647700

648-
if self.is_single {
701+
if is_single {
649702
return match task_definitions.is_empty() {
650703
true => {
651704
let (span, text) = task_id.span_and_text("turbo.json");
@@ -667,7 +720,7 @@ impl<'a, L: TurboJsonLoader> EngineBuilder<'a, L> {
667720
}
668721
}
669722

670-
if task_definitions.is_empty() && self.should_validate_engine {
723+
if task_definitions.is_empty() && should_validate_engine {
671724
let (span, text) = task_id.span_and_text("turbo.json");
672725
return Err(BuilderError::MissingPackageTask(Box::new(
673726
MissingPackageTaskError {

crates/turborepo-engine/src/execute.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ impl<T: TaskDefinitionInfo + Clone + Send + Sync + 'static> Engine<Built, T> {
6363
// finish even once a task sends back the stop signal. This is suboptimal
6464
// since it would mean the visitor would need to also track if
6565
// it is cancelled :)
66+
#[tracing::instrument(skip_all)]
6667
pub async fn execute(
6768
self: Arc<Self>,
6869
options: ExecutionOptions,

crates/turborepo-globwalk/src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,24 @@ fn needs_path_cleaning(s: &str) -> bool {
440440
false
441441
}
442442

443+
/// Returns true if the pattern contains glob metacharacters (*, ?, [, {).
444+
/// Literal file paths return false.
445+
pub fn is_glob_pattern(pattern: &str) -> bool {
446+
// Check for unescaped glob metacharacters
447+
let mut chars = pattern.chars().peekable();
448+
while let Some(c) = chars.next() {
449+
if c == '\\' {
450+
// Skip escaped character
451+
chars.next();
452+
continue;
453+
}
454+
if matches!(c, '*' | '?' | '[' | '{') {
455+
return true;
456+
}
457+
}
458+
false
459+
}
460+
443461
pub fn globwalk_with_settings(
444462
base_path: &AbsoluteSystemPath,
445463
include: &[ValidatedGlob],

crates/turborepo-hash/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub struct TaskHashable<'a> {
6060
pub global_hash: &'a str,
6161
pub task_dependency_hashes: Vec<String>,
6262
pub hash_of_files: &'a str,
63-
pub external_deps_hash: Option<String>,
63+
pub external_deps_hash: Option<&'a str>,
6464

6565
// task
6666
pub package_dir: Option<turbopath::RelativeUnixPathBuf>,
@@ -320,7 +320,7 @@ impl From<TaskHashable<'_>> for Builder<HeapAllocator> {
320320

321321
builder.set_hash_of_files(task_hashable.hash_of_files);
322322
if let Some(external_deps_hash) = task_hashable.external_deps_hash {
323-
builder.set_external_deps_hash(&external_deps_hash);
323+
builder.set_external_deps_hash(external_deps_hash);
324324
}
325325

326326
builder.set_task(task_hashable.task);
@@ -512,7 +512,7 @@ mod test {
512512
task_dependency_hashes: vec!["task_dependency_hash".to_string()],
513513
package_dir: Some(turbopath::RelativeUnixPathBuf::new("package_dir").unwrap()),
514514
hash_of_files: "hash_of_files",
515-
external_deps_hash: Some("external_deps_hash".to_string()),
515+
external_deps_hash: Some("external_deps_hash"),
516516
task: "task",
517517
outputs: TaskOutputs {
518518
inclusions: vec!["inclusions".to_string()],

crates/turborepo-lib/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ turborepo-profile-md = { workspace = true }
127127
turborepo-repository = { path = "../turborepo-repository" }
128128
turborepo-run-cache = { path = "../turborepo-run-cache" }
129129
turborepo-run-summary = { workspace = true }
130-
turborepo-scm = { workspace = true, features = ["git2"] }
130+
turborepo-scm = { workspace = true, features = ["git2", "gix"] }
131131
turborepo-scope = { path = "../turborepo-scope" }
132132
turborepo-shim = { workspace = true }
133133
turborepo-signals = { workspace = true }

crates/turborepo-lib/src/run/builder.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,10 @@ impl RunBuilder {
357357
)?;
358358

359359
let env_at_execution_start = EnvironmentVariableMap::infer();
360+
// Pre-warm the turbo.json cache: read and parse all package turbo.json
361+
// files in parallel before the engine builder needs them sequentially.
362+
turbo_json_loader.preload_all();
363+
360364
let mut engine = self.build_engine(
361365
&pkg_dep_graph,
362366
&root_turbo_json,

0 commit comments

Comments
 (0)