Skip to content

Commit 9766935

Browse files
committed
tsparser: fall back to junction points on windows
Windows requires Developer Mode to be enabled to use symlinks without admin permissions. This isn't always enabled, so fall back to using junction points if symlink creation fails. This is for example what rustup does already. See rust-lang/rustup#3687 for example.
1 parent 862c8cf commit 9766935

File tree

3 files changed

+73
-13
lines changed

3 files changed

+73
-13
lines changed

Cargo.lock

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

tsparser/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ serde_yaml = "0.9.32"
4141
symlink = "0.1.0"
4242
tracing = "0.1.40"
4343
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
44+
junction = "1.2.0"
4445

4546
[build-dependencies]
4647
prost-build = { version = "0.12.1" }

tsparser/src/builder/package_mgmt.rs

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
44
use anyhow::{Context, Result};
55
use duct::cmd;
66
use serde::Deserialize;
7+
use std::io;
78

89
use crate::builder::compile::CmdSpec;
910

@@ -279,22 +280,20 @@ fn symlink_encore_dev(app_root: &Path, encore_dev_path: &Path) -> Result<()> {
279280
let node_mod_dst = node_modules.join("encore.dev");
280281

281282
// If the node_modules directory exists, symlink the encore.dev package.
282-
if let Ok(meta) = node_mod_dst.symlink_metadata() {
283-
// Is this a symlink?
284-
if meta.is_symlink() {
285-
// If the symlink is already pointing to our desired target, we're done.
286-
if let Ok(target) = std::fs::read_link(&node_mod_dst) {
287-
if target == encore_dev_path {
288-
log::info!("encore.dev symlink already points to the local runtime, skipping.");
289-
return Ok(());
290-
}
283+
if let Ok(target) = read_symlink(&node_mod_dst) {
284+
if let Some(target) = target {
285+
if target == encore_dev_path {
286+
log::info!("encore.dev symlink already points to the local runtime, skipping.");
287+
return Ok(());
291288
}
292289

293290
// It's a symlink pointing elsewhere. Remove it.
294-
symlink::remove_symlink_auto(&node_mod_dst).with_context(|| {
291+
delete_symlink(&node_mod_dst).with_context(|| {
295292
format!("remove existing encore.dev symlink at {:?}", node_mod_dst)
296293
})?;
297-
} else {
294+
}
295+
296+
if node_mod_dst.exists() {
298297
// It's not a symlink. Remove the directory so we can add a symlink.
299298
std::fs::remove_dir_all(&node_mod_dst).with_context(|| {
300299
format!("remove existing encore.dev directory at {:?}", node_mod_dst)
@@ -304,9 +303,58 @@ fn symlink_encore_dev(app_root: &Path, encore_dev_path: &Path) -> Result<()> {
304303

305304
// Create the symlink if the node_modules directory exists.
306305
if node_modules.exists() {
307-
symlink::symlink_dir(encore_dev_path, &node_mod_dst)
306+
create_symlink(encore_dev_path, &node_mod_dst)
308307
.with_context(|| format!("symlink encore.dev directory at {:?}", node_mod_dst))?;
309308
}
310-
311309
Ok(())
312310
}
311+
312+
#[cfg(not(windows))]
313+
fn create_symlink(src: &Path, dst: &Path) -> io::Result<()> {
314+
symlink::symlink_dir(src, dst)
315+
}
316+
317+
#[cfg(windows)]
318+
fn create_symlink(src: &Path, dst: &Path) -> io::Result<()> {
319+
symlink::symlink_dir(src, dst).or_else(|_| junction::create(src, &dst))
320+
}
321+
322+
#[cfg(not(windows))]
323+
fn read_symlink(src: &Path) -> io::Result<Option<PathBuf>> {
324+
if let Ok(meta) = src.symlink_metadata() {
325+
if meta.is_symlink() {
326+
return std::fs::read_link(src).map(Some);
327+
}
328+
}
329+
Ok(None)
330+
}
331+
332+
#[cfg(windows)]
333+
fn read_symlink(src: &Path) -> io::Result<Option<PathBuf>> {
334+
if let Ok(meta) = src.symlink_metadata() {
335+
// Is this a symlink?
336+
if meta.is_symlink() {
337+
return std::fs::read_link(src).map(Some);
338+
}
339+
}
340+
341+
// Check if it's a junction.
342+
if junction::exists(src)? {
343+
return junction::get_target(src).map(Some);
344+
}
345+
346+
Ok(None)
347+
}
348+
349+
#[cfg(not(windows))]
350+
fn delete_symlink(src: &Path) -> io::Result<()> {
351+
symlink::remove_symlink_auto(src)
352+
}
353+
354+
#[cfg(windows)]
355+
fn delete_symlink(src: &Path) -> io::Result<()> {
356+
if let Ok(_) = symlink::remove_symlink_auto(src) {
357+
return Ok(());
358+
}
359+
junction::delete(src)
360+
}

0 commit comments

Comments
 (0)