-
Notifications
You must be signed in to change notification settings - Fork 2.2k
fix: windows symlinking bug #10992
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: windows symlinking bug #10992
Changes from 7 commits
c2c3ce7
d35a2ee
907a38b
41069de
77ef4bb
0dc95ae
2406bfe
054ce70
03f923d
7149bfa
280a803
1f5cafc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,10 +59,121 @@ | |
| // leading to the wrong place. We could separate the Windows | ||
| // implementation, but this workaround works for other platforms as | ||
| // well. | ||
| let canonical_path = | ||
| fs_canonicalize(root_path.as_path().join("node_modules").join("turbo")).ok()?; | ||
| let turbo_path = root_path.as_path().join("node_modules").join("turbo"); | ||
| debug!( | ||
| "generate_linked_path: Attempting to canonicalize: {}", | ||
| turbo_path | ||
| ); | ||
|
|
||
| AbsoluteSystemPathBuf::try_from(canonical_path.parent()?).ok() | ||
| match fs_canonicalize(&turbo_path) { | ||
| Ok(canonical_path) => { | ||
| debug!( | ||
| "generate_linked_path: Canonicalized to: {}", | ||
| canonical_path.display() | ||
| ); | ||
| match canonical_path.parent() { | ||
| Some(parent) => { | ||
| debug!( | ||
| "generate_linked_path: Parent directory: {}", | ||
| parent.display() | ||
| ); | ||
| match AbsoluteSystemPathBuf::try_from(parent) { | ||
| Ok(path) => { | ||
| debug!( | ||
| "generate_linked_path: Successfully created \ | ||
| AbsoluteSystemPathBuf" | ||
| ); | ||
| Some(path) | ||
| } | ||
| Err(e) => { | ||
| debug!( | ||
| "generate_linked_path: Failed to create \ | ||
| AbsoluteSystemPathBuf: {:?}", | ||
| e | ||
| ); | ||
| None | ||
| } | ||
| } | ||
| } | ||
| None => { | ||
| debug!("generate_linked_path: Canonicalized path has no parent"); | ||
| None | ||
| } | ||
| } | ||
| } | ||
| Err(e) => { | ||
| debug!( | ||
| "generate_linked_path: Failed to canonicalize {}: {}", | ||
| turbo_path, e | ||
| ); | ||
|
|
||
| // On Windows, canonicalize can fail with permission errors even when | ||
| // the symlink is valid. Try using read_link instead. | ||
| #[cfg(target_os = "windows")] | ||
| { | ||
| debug!("generate_linked_path: Attempting Windows fallback with read_link"); | ||
| match fs::read_link(&turbo_path) { | ||
| Ok(link_target) => { | ||
| debug!( | ||
| "generate_linked_path: read_link succeeded, target: {}", | ||
| link_target.display() | ||
| ); | ||
|
|
||
| // The link target is relative to the symlink location | ||
| // e.g., ".pnpm/[email protected]/node_modules/turbo" | ||
| // We need to resolve it relative to node_modules directory | ||
| let node_modules = root_path.as_path().join("node_modules"); | ||
| let resolved = node_modules.join(&link_target); | ||
|
Check failure on line 126 in crates/turborepo-lib/src/shim/local_turbo_state.rs
|
||
|
|
||
| debug!( | ||
| "generate_linked_path: Resolved path: {}", | ||
| resolved.display() | ||
|
Check failure on line 130 in crates/turborepo-lib/src/shim/local_turbo_state.rs
|
||
| ); | ||
|
|
||
| // Get the parent directory (should be .pnpm/[email protected]/node_modules) | ||
| match resolved.parent() { | ||
| Some(parent) => { | ||
| debug!( | ||
| "generate_linked_path: Parent directory: {}", | ||
| parent.display() | ||
|
Check failure on line 138 in crates/turborepo-lib/src/shim/local_turbo_state.rs
|
||
| ); | ||
| match AbsoluteSystemPathBuf::try_from(parent) { | ||
|
Check failure on line 140 in crates/turborepo-lib/src/shim/local_turbo_state.rs
|
||
| Ok(path) => { | ||
| debug!( | ||
| "generate_linked_path: Successfully created \ | ||
| AbsoluteSystemPathBuf from read_link fallback" | ||
| ); | ||
| Some(path) | ||
| } | ||
| Err(e) => { | ||
| debug!( | ||
| "generate_linked_path: Failed to create \ | ||
| AbsoluteSystemPathBuf: {:?}", | ||
| e | ||
| ); | ||
| None | ||
| } | ||
| } | ||
| } | ||
| None => { | ||
| debug!("generate_linked_path: Resolved path has no parent"); | ||
| None | ||
| } | ||
| } | ||
| } | ||
| Err(e) => { | ||
| debug!("generate_linked_path: read_link also failed: {}", e); | ||
| None | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(not(target_os = "windows"))] | ||
| { | ||
| None | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // The unplugged directory doesn't have a fixed path. | ||
|
|
@@ -117,6 +228,11 @@ | |
| let platform_package_name = TurboState::platform_package_name(); | ||
| let binary_name = TurboState::binary_name(); | ||
|
|
||
| debug!( | ||
| "Searching for local turbo. Platform: {}, Binary: {}", | ||
| platform_package_name, binary_name | ||
| ); | ||
|
|
||
| let platform_package_json_path_components = [platform_package_name, "package.json"]; | ||
| let platform_package_executable_path_components = | ||
| [platform_package_name, "bin", binary_name]; | ||
|
|
@@ -131,13 +247,20 @@ | |
|
|
||
| // Detecting the package manager is more expensive than just doing an exhaustive | ||
| // search. | ||
| for root in search_functions | ||
| for (idx, root) in search_functions | ||
| .iter() | ||
| .filter_map(|search_function| search_function(root_path)) | ||
| .enumerate() | ||
| { | ||
| debug!("Trying search path #{}: {}", idx + 1, root); | ||
| // Needs borrow because of the loop. | ||
| #[allow(clippy::needless_borrow)] | ||
| let bin_path = root.join_components(&platform_package_executable_path_components); | ||
| debug!("Looking for binary at: {}", bin_path); | ||
|
|
||
| let exists = bin_path.exists(); | ||
| debug!("Binary exists: {}", exists); | ||
|
|
||
| match fs_canonicalize(&bin_path) { | ||
| Ok(bin_path) => { | ||
| let resolved_package_json_path = | ||
|
|
@@ -153,10 +276,14 @@ | |
| version: local_version, | ||
| }); | ||
| } | ||
| Err(_) => debug!("No local turbo binary found at: {}", bin_path), | ||
| Err(e) => debug!( | ||
| "No local turbo binary found at: {} (error: {})", | ||
| bin_path, e | ||
| ), | ||
| } | ||
| } | ||
|
|
||
| debug!("No local turbo found after searching all paths"); | ||
| None | ||
| } | ||
|
|
||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,50 @@ | ||
| Setup | ||
| $ . ${TESTDIR}/../../../helpers/setup.sh | ||
| $ . ${TESTDIR}/setup.sh $(pwd) "linked" | ||
| * (glob) | ||
| $ echo "=== Verifying fixture state after setup ===" | ||
| === Verifying fixture state after setup === | ||
| $ ls -la node_modules/turbo 2>&1 || echo "node_modules/turbo does not exist" | ||
| * (glob) | ||
| $ ls -la node_modules/.pnpm 2>&1 | head -5 | ||
| * (glob) | ||
| $ echo "=== End verification ===" | ||
| === End verification === | ||
|
|
||
| Make sure we use local, but do not pass --skip-infer to old binary | ||
| $ ${TESTDIR}/set_version.sh $(pwd) "1.0.0" | ||
| $ ${TURBO} build --filter foo -vv > out.log 2>&1 | ||
| $ grep --quiet -F "Local turbo version: 1.0.0" out.log | ||
| $ echo "Running turbo with verbose output..." | ||
| Running turbo with verbose output... | ||
| $ ${TURBO} build --filter foo -vv 2>&1 | tee out.log | ||
| * (glob) | ||
| $ echo "=== Full output from out.log ===" | ||
| === Full output from out.log === | ||
| $ cat out.log | ||
| * (glob) | ||
| $ echo "=== Checking for version string ===" | ||
| === Checking for version string === | ||
| $ grep -F "Local turbo version: 1.0.0" out.log || echo "VERSION STRING NOT FOUND" | ||
vercel[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * (glob) | ||
| $ echo "=== Last line of output ===" | ||
| === Last line of output === | ||
| $ cat out.log | tail -n1 | ||
| build --filter foo -vv -- | ||
|
|
||
| Make sure we use local, and DO pass --skip-infer to newer binary | ||
| $ ${TESTDIR}/set_version.sh $(pwd) "1.8.0" | ||
| $ ${TURBO} build --filter foo -vv > out.log 2>&1 | ||
| $ grep --quiet -F "Local turbo version: 1.8.0" out.log | ||
| $ echo "Running turbo with newer version..." | ||
| Running turbo with newer version... | ||
| $ ${TURBO} build --filter foo -vv 2>&1 | tee out.log | ||
| * (glob) | ||
| $ echo "=== Full output from out.log ===" | ||
| === Full output from out.log === | ||
| $ cat out.log | ||
| * (glob) | ||
| $ echo "=== Checking for version string ===" | ||
| === Checking for version string === | ||
| $ grep -F "Local turbo version: 1.8.0" out.log || echo "VERSION STRING NOT FOUND" | ||
| * (glob) | ||
| $ echo "=== Last line of output ===" | ||
| === Last line of output === | ||
| $ cat out.log | tail -n1 | ||
| --skip-infer build --filter foo -vv --single-package -- | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,25 +5,70 @@ SCRIPT_DIR=$(dirname ${BASH_SOURCE[0]}) | |
| TARGET_DIR=$1 | ||
| FIXTURE_DIR=$2 | ||
|
|
||
| echo "=== Setup starting for fixture: $FIXTURE_DIR ===" | ||
| echo "OSTYPE: $OSTYPE" | ||
| echo "TARGET_DIR: $TARGET_DIR" | ||
|
|
||
| cp -a ${SCRIPT_DIR}/../../fixtures/find_turbo/$FIXTURE_DIR/. ${TARGET_DIR}/ | ||
|
|
||
| # Verify what got copied | ||
| echo "Checking .pnpm contents after cp:" | ||
| ls -la ${TARGET_DIR}/node_modules/.pnpm/ 2>&1 || echo ".pnpm directory does not exist" | ||
| if [ -d "${TARGET_DIR}/node_modules/.pnpm/[email protected]" ]; then | ||
| echo "[email protected] directory exists" | ||
| ls -la ${TARGET_DIR}/node_modules/.pnpm/[email protected]/node_modules/ 2>&1 || echo "node_modules subdirectory missing" | ||
| else | ||
| echo "ERROR: [email protected] directory was NOT copied!" | ||
| fi | ||
|
|
||
| # We need to symlink: turbo -> .pnpm/[email protected]/node_modules/turbo | ||
| # where `turbo` is the symlink | ||
| # and `.pnpm/[email protected]/node_modules/turbo` is the path to symlink to | ||
| # Note: using a nested if so it's easy to find the Windows checks in scripts around the codebase. | ||
| if [[ "$OSTYPE" == "msys" ]]; then | ||
| echo "Running on Windows (msys)" | ||
| if [[ $FIXTURE_DIR == "linked" ]]; then | ||
| echo "Setting up linked fixture for Windows..." | ||
|
|
||
| # Check what exists before we start | ||
| echo "Before setup:" | ||
| ls -la node_modules/turbo 2>&1 || echo "node_modules/turbo does not exist" | ||
| ls -la node_modules/.pnpm/[email protected]/node_modules/turbo 2>&1 || echo "pnpm turbo directory does not exist" | ||
|
|
||
| # Delete the existing turbo directory or file, whatever exists there | ||
| echo "Removing existing node_modules/turbo..." | ||
| rm -rf node_modules/turbo | ||
|
|
||
| # Let's enter the node_modules directory | ||
| # echo "entering node_modules directory" | ||
| echo "Entering node_modules directory..." | ||
| pushd node_modules > /dev/null || exit 1 | ||
|
|
||
| # Use pnpx to run symlnk-dir because installing globally doesn't work with pnpm. | ||
| pnpx symlink-dir .pnpm/[email protected]/node_modules/turbo turbo > /dev/null 2>&1 | ||
| echo "Attempting to create symlink with: pnpx symlink-dir .pnpm/[email protected]/node_modules/turbo turbo" | ||
| if pnpx symlink-dir .pnpm/[email protected]/node_modules/turbo turbo; then | ||
| echo "✓ Symlink created successfully" | ||
| else | ||
| EXIT_CODE=$? | ||
| echo "✗ Symlink creation FAILED with exit code: $EXIT_CODE" | ||
| fi | ||
|
|
||
| # Get outta there | ||
| popd > /dev/null || exit 1 | ||
|
|
||
| # Verify what we ended up with | ||
| echo "After setup:" | ||
| ls -la node_modules/turbo 2>&1 || echo "node_modules/turbo still does not exist" | ||
| if [ -L node_modules/turbo ]; then | ||
| LINK_TARGET=$(readlink node_modules/turbo) | ||
| echo "node_modules/turbo is a symlink pointing to: $LINK_TARGET" | ||
| echo "Checking if symlink target exists:" | ||
| ls -la "node_modules/$LINK_TARGET" 2>&1 || echo "ERROR: Symlink target does NOT exist!" | ||
| echo "Checking if we can find turbo-windows-64 from the symlink target:" | ||
| ls -la "node_modules/$LINK_TARGET/../turbo-windows-64" 2>&1 || echo "ERROR: turbo-windows-64 not found relative to symlink target" | ||
| fi | ||
|
|
||
| echo "=== Setup complete ===" | ||
| fi | ||
| else | ||
| echo "Not running on Windows, skipping Windows-specific setup" | ||
| fi | ||
Uh oh!
There was an error while loading. Please reload this page.