Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix behaviour when discovering files for global hashing #6738

Merged
merged 3 commits into from
Dec 7, 2023
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
132 changes: 89 additions & 43 deletions crates/turborepo-lib/src/run/global_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
use globwalk::WalkType;
use thiserror::Error;
use tracing::debug;
use turbopath::{AbsoluteSystemPath, RelativeUnixPathBuf};
use turbopath::{AbsoluteSystemPath, AbsoluteSystemPathBuf, RelativeUnixPathBuf};
use turborepo_env::{get_global_hashable_env_vars, DetailedMap, EnvironmentVariableMap};
use turborepo_lockfiles::Lockfile;
use turborepo_repository::package_manager::{self, PackageManager};
Expand Down Expand Up @@ -68,47 +68,8 @@ pub fn get_global_hash_inputs<'a, L: ?Sized + Lockfile>(
global_hashable_env_vars.all.names()
);

let mut global_deps = HashSet::new();

if !global_file_dependencies.is_empty() {
let exclusions = match package_manager.get_workspace_globs(root_path) {
Ok(globs) => globs.raw_exclusions,
// If we hit a missing workspaces error, we could be in single package mode
// so we should just use the default globs
Err(package_manager::Error::Workspace(_)) => {
package_manager.get_default_exclusions().collect()
}
Err(err) => {
debug!("no workspace globs found");
return Err(err.into());
}
};

// This is a bit of a hack to ensure that we don't crash
// when given an absolute path on Windows. We don't support
// absolute paths, but the ':' from the drive letter will also
// fail to compile to a glob. We already know we aren't going to
// get anything good, and we've already logged a warning, but we
// can modify the glob so it compiles. This is similar to the old
// behavior, which tacked it on to the end of the base path unmodified,
// and then would produce no files.
#[cfg(windows)]
let windows_global_file_dependencies: Vec<String> = global_file_dependencies
.iter()
.map(|s| s.replace(":", ""))
.collect();
let files = globwalk::globwalk(
root_path,
#[cfg(not(windows))]
global_file_dependencies,
#[cfg(windows)]
windows_global_file_dependencies.as_slice(),
&exclusions,
WalkType::All,
)?;

global_deps.extend(files);
}
let mut global_deps =
collect_global_deps(package_manager, root_path, global_file_dependencies)?;

if lockfile.is_none() {
global_deps.insert(root_path.join_component("package.json"));
Expand Down Expand Up @@ -158,6 +119,52 @@ pub fn get_global_hash_inputs<'a, L: ?Sized + Lockfile>(
})
}

fn collect_global_deps(
package_manager: &PackageManager,
root_path: &AbsoluteSystemPath,
global_file_dependencies: &[String],
) -> Result<HashSet<AbsoluteSystemPathBuf>, Error> {
if global_file_dependencies.is_empty() {
return Ok(HashSet::new());
}
let exclusions = match package_manager.get_workspace_globs(root_path) {
Ok(globs) => globs.raw_exclusions,
// If we hit a missing workspaces error, we could be in single package mode
// so we should just use the default globs
Err(package_manager::Error::Workspace(_)) => {
package_manager.get_default_exclusions().collect()
}
Err(err) => {
debug!("no workspace globs found");
return Err(err.into());
}
};

// This is a bit of a hack to ensure that we don't crash
// when given an absolute path on Windows. We don't support
// absolute paths, but the ':' from the drive letter will also
// fail to compile to a glob. We already know we aren't going to
// get anything good, and we've already logged a warning, but we
// can modify the glob so it compiles. This is similar to the old
// behavior, which tacked it on to the end of the base path unmodified,
// and then would produce no files.
#[cfg(windows)]
let windows_global_file_dependencies: Vec<String> = global_file_dependencies
.iter()
.map(|s| s.replace(":", ""))
.collect();

Ok(globwalk::globwalk(
root_path,
#[cfg(not(windows))]
global_file_dependencies,
#[cfg(windows)]
windows_global_file_dependencies.as_slice(),
&exclusions,
WalkType::Files,
)?)
}

impl<'a> GlobalHashableInputs<'a> {
pub fn calculate_global_hash_from_inputs(&mut self) -> String {
match self.env_mode {
Expand Down Expand Up @@ -206,7 +213,7 @@ mod tests {
use turborepo_repository::package_manager::PackageManager;

use super::get_global_hash_inputs;
use crate::cli::EnvMode;
use crate::{cli::EnvMode, run::global_hash::collect_global_deps};

#[test]
fn test_absolute_path() {
Expand Down Expand Up @@ -244,4 +251,43 @@ mod tests {
);
assert!(result.is_ok());
}

/// get_global_hash_inputs should not yield any folders when walking since
/// turbo does not consider changes to folders when evaluating hashes,
/// only to files
#[test]
fn test_collect_only_yields_files() {
let tmp = tempfile::tempdir().unwrap();

// add some files
// - package.json
// - src/index.js
// - src/index.test.js
// - empty-folder/

let root = AbsoluteSystemPathBuf::try_from(tmp.path()).unwrap();
let src = root.join_component("src");

root.join_component("package.json")
.create_with_contents("{}")
.unwrap();
root.join_component("empty-folder")
.create_dir_all()
.unwrap();

src.create_dir_all().unwrap();
src.join_component("index.js")
.create_with_contents("console.log('hello world');")
.unwrap();
src.join_component("index.test.js")
.create_with_contents("")
.unwrap();

let global_file_dependencies = vec!["**".to_string()];
let results =
collect_global_deps(&PackageManager::Berry, &root, &global_file_dependencies).unwrap();

// should not yield the root folder itself, src, or empty-folder
assert_eq!(results.len(), 3, "{:?}", results);
}
}
3 changes: 3 additions & 0 deletions turborepo-tests/integration/fixtures/global_deps/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
.turbo
.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "my-app",
"scripts": {
"build": "echo building",
"maybefails": "exit 4"
},
"dependencies": {
"util": "*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
other file, not a global dependency
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global dep! all tasks depend on this content!
10 changes: 10 additions & 0 deletions turborepo-tests/integration/fixtures/global_deps/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "monorepo",
"scripts": {
"something": "turbo run build"
},
"workspaces": [
"apps/**",
"packages/**"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "another",
"scripts": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "util",
"scripts": {
"build": "echo building",
"maybefails": "echo didnotfail"
}
}
21 changes: 21 additions & 0 deletions turborepo-tests/integration/fixtures/global_deps/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["global_deps/**"],
"globalEnv": ["SOME_ENV_VAR"],
"pipeline": {
"build": {
"env": ["NODE_ENV"],
"outputs": []
},
// this comment verifies that turbo can read .json files with comments
"my-app#build": {
"outputs": ["banana.txt", "apple.json"],
"dotEnv": [".env.local"]
},

"something": {},
"//#something": {},

"maybefails": {}
}
}
26 changes: 26 additions & 0 deletions turborepo-tests/integration/tests/global-deps.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Setup
$ . ${TESTDIR}/../../helpers/setup_integration_test.sh global_deps

Run a build
$ ${TURBO} build -F my-app --output-logs=hash-only
\xe2\x80\xa2 Packages in scope: my-app (esc)
\xe2\x80\xa2 Running build in 1 packages (esc)
\xe2\x80\xa2 Remote caching disabled (esc)
my-app:build: cache miss, executing 81165ceb4ed0e31f

Tasks: 1 successful, 1 total
Cached: 0 cached, 1 total
Time:\s*[\.0-9]+m?s (re)


$ echo "new text" > global_deps/foo.txt
$ ${TURBO} build -F my-app --output-logs=hash-only
\xe2\x80\xa2 Packages in scope: my-app (esc)
\xe2\x80\xa2 Running build in 1 packages (esc)
\xe2\x80\xa2 Remote caching disabled (esc)
my-app:build: cache miss, executing 7bc88ba3e84628fd

Tasks: 1 successful, 1 total
Cached: 0 cached, 1 total
Time:\s*[\.0-9]+m?s (re)

Loading