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

bug: Node version can't be used when symlink is not present #300

Closed
jameschensmith opened this issue Oct 27, 2020 · 14 comments · Fixed by #308
Closed

bug: Node version can't be used when symlink is not present #300

jameschensmith opened this issue Oct 27, 2020 · 14 comments · Fixed by #308

Comments

@jameschensmith
Copy link
Contributor

When the symlink for the current version (~/.local/share/fnm/current on my machine, but I believe it's the value of FNM_MULTISHELL_PATH if I'm not mistaken) is missing, the CLI throws the following error:

$ fnm use <Node Version>
Using Node <Node Version>
error: Can't delete the symlink: No such file or directory (os error 2)

The culprit appears to be this line. One way to get around this is to create a dummy symlink, which will then be deleted and replaced properly, but this is just a hack for now 😋 The symlink strategy could probably be improved to handle these situations in a more failsafe manner (i.e. just create it if it doesn't exist, perhaps). This is the implementation from the line noted earlier. Since different OS's call different functions, I've linked the documented errors which can occur for the unix implementation as well as the windows implementation.

@Schniz
Copy link
Owner

Schniz commented Oct 27, 2020

We can ignore the error of removing the symlink, but I wonder how ~/.local/share/fnm/current is the value you have for the FNM_MULTISHELL_PATH 🤔
Are you setting it manually?

@Schniz
Copy link
Owner

Schniz commented Oct 27, 2020

We always generate a "random" directory and create the symlink

fnm/src/commands/env.rs

Lines 24 to 31 in a340671

fn generate_symlink_path(root: &std::path::Path) -> std::path::PathBuf {
let temp_dir_name = format!(
"fnm_multishell_{}_{}",
std::process::id(),
chrono::Utc::now().timestamp_millis(),
);
root.join(temp_dir_name)
}

symlink_dir(config.default_version_dir(), &temp_dir).expect("Can't create symlink!");

If you're setting the FNM_MULTISHELL_PATH env var yourself, you're mimicking the previous version where you wouldn't be able to have multiple Node versions across different shells. Maybe this is something we should support, but not do it as default: fnm env --no-multi-shell or something similar?

@jameschensmith
Copy link
Contributor Author

We can ignore the error of removing the symlink, but I wonder how ~/.local/share/fnm/current is the value you have for the FNM_MULTISHELL_PATH 🤔
Are you setting it manually?

I am setting it manually. At first I was experiencing errors with environment variables not being set, but I had realized FNM_MULTISHELL_PATH didn't have a default, so I supplied that myself.

If you're setting the FNM_MULTISHELL_PATH env var yourself, you're mimicking the previous version where you wouldn't be able to have multiple Node versions across different shells. Maybe this is something we should support, but not do it as default: fnm env --no-multi-shell or something similar?

Ah, I think I'm understanding what FNM_MULTISHELL_PATH is truly used for. From the CLI documentation, it essentially sounded like the same as before (i.e. it should just be linked to current):

--multishell-path <multishell-path>
    Where the current node version link is stored

If this is the use-case for it (i.e. random directory), then perhaps we can also provide clearer documentation for it's use-case. In addition, I have my dotfiles committed to a repository, and so committing this random directory would appear to not be flexible between my different machines 😕 I hope that you understand what I'm trying to solve. Having FNM_MULTISHELL_PATH set to a specific path in my configuration files allows me to switch machines and have the same configuration. Perhaps if this should be random, maybe don't allow the user to set this variable and handle this logic in the tool? Open to more thoughts. Appreciate your input! 😊

@jameschensmith
Copy link
Contributor Author

Yeah, just checking my /var/... directory for fnm_multishell_*. I had 80 symlinks 😅 It's probably because I kept running fnm env and checking the env variable names 🙈 Maybe there's a better way to address what you're trying to accomplish? Is the goal to just have a different node version for each shell, or multiple node versions per shell? If I could guess, the goal is probably one "current" version for each shell. In this case, instead of generating a random directory name each time fnm env is called, why not have an ID for the folder name, based off of shell? For example the current version for a user's fish shell could be fnm_multishell_fish or simply fnm_current_fish (something along those lines 😅 ). I don't have the full picture of this feature, so perhaps it would be incredibly beneficial to document this feature to track the history/changes of it.

@Schniz
Copy link
Owner

Schniz commented Oct 27, 2020

then perhaps we can also provide clearer documentation for it's use-case

I agree. I should probably mention that this will be filled automatically after you evaluate fnm env output to your shell 😄 It generates all the environment variables you need. Check out what's fnm env output is for Zsh:

$ fnm env

export PATH="/var/folders/cs/pr19qm154hxcg8_zvbrjwy91l6v056/T/fnm_multishell_65759_1603815370573/bin":$PATH
export FNM_MULTISHELL_PATH="/var/folders/cs/pr19qm154hxcg8_zvbrjwy91l6v056/T/fnm_multishell_65759_1603815370573"
export FNM_DIR="/Users/galsc/.fnm"
export FNM_LOGLEVEL="info"
export FNM_NODE_DIST_MIRROR="https://nodejs.org/dist"

And for fish:

$ fnm env

set -gx PATH "/var/folders/cs/pr19qm154hxcg8_zvbrjwy91l6v056/T/fnm_multishell_65816_1603815420793/bin" $PATH;
set -gx FNM_MULTISHELL_PATH "/var/folders/cs/pr19qm154hxcg8_zvbrjwy91l6v056/T/fnm_multishell_65816_1603815420793";
set -gx FNM_DIR "/Users/galsc/.fnm";
set -gx FNM_LOGLEVEL "info";
set -gx FNM_NODE_DIST_MIRROR "https://nodejs.org/dist";

You don't need to commit anything to your dotfiles. These random symlinks are being generated automatically for you. You figured out why fnm generates a random symlink by yourself already: this way we can support having multiple Node versions across different shell instances. So in one tab, you can use Node 14, and in another you can use Node 10.

Since fnm is not a Shell function (unlike nvm which is a Bash script), we can't modify anything in your environment. We can only touch the file system. This is the simplest way I thought of to do this.

Since symlinks are practically free and being stored in your temp directory, they will be cleaned up automatically if needed and it would take a lot of fnm env calls to tickle your storage 😄


I already mentioned it, but maybe I need to have a series of blog posts about how fnm works, maybe someone will read it and tell me there's a better way, and we'll make this tool better 😁

@ljharb
Copy link

ljharb commented Oct 27, 2020

Why not have one canonical symlink per node version? That way every shell using node 15.0.1, for example, is using the same one, but a shell could also use a different version.

@Schniz
Copy link
Owner

Schniz commented Oct 27, 2020

Why not have one canonical symlink per node version?

if you use the same Node version, /tmp/fnm_multishell_* will be linked to the same exact location, so they "share" the same version. Since fnm is a binary it can't alter environment variables but I can change symlinks (which live on the file system).

When running fnm env I create a symlink that fnm can change whenever the user runs fnm use. Here's a short of symlinks and their target — you can see that most of them target the same Node version:

$ exa -d1 /var/folders/cs/pr19qm154hxcg8_zvbrjwy91l6v056/T/fnm_multishell* | tail -n 5
/var/folders/cs/pr19qm154hxcg8_zvbrjwy91l6v056/T/fnm_multishell_85069_1603642390944 -> /tmp/fnm_test/node-versions/v14.14.0/installation
/var/folders/cs/pr19qm154hxcg8_zvbrjwy91l6v056/T/fnm_multishell_90229_1603651585006 -> /Users/galsc/.fnm/node-versions/v12.19.0/installation
/var/folders/cs/pr19qm154hxcg8_zvbrjwy91l6v056/T/fnm_multishell_90921_1603651840461 -> /Users/galsc/.fnm/node-versions/v12.19.0/installation
/var/folders/cs/pr19qm154hxcg8_zvbrjwy91l6v056/T/fnm_multishell_94908_1603653847585 -> /Users/galsc/.fnm/node-versions/v14.9.0/installation
/var/folders/cs/pr19qm154hxcg8_zvbrjwy91l6v056/T/fnm_multishell_97806_1603658165995 -> /Users/galsc/.fnm/node-versions/v12.19.0/installation

@jameschensmith
Copy link
Contributor Author

I can see how this can work per shell environment, i.e. fish & bash having two different versions (former sourced in ~/.config/fish/<...>; latter sourced in ~/.bash_profile, or equivalent file), but how does this solve using two different node versions using the same shell but different tabs/instances? Or, is that not the use-case for this feature?

@Schniz
Copy link
Owner

Schniz commented Oct 27, 2020

It is. Every time you run fnm env you get a different symlink path that is also in your PATH. So every shell session has its own symlink

@jameschensmith
Copy link
Contributor Author

jameschensmith commented Oct 27, 2020

🤔 So, this wouldn't go to a file. Rather, the use-case would be to evaluate it in each shell session (i.e. eval (fnm env), or something similar based on shell)? If they were written to a config file, I would think that file would just continue to be overridden. Is my understanding correct?

@Schniz
Copy link
Owner

Schniz commented Oct 27, 2020

@Schniz
Copy link
Owner

Schniz commented Oct 27, 2020

On every shell session you should run eval "$(fnm env)" and then you get a symlink for that session. So setting it up in the shell profile is the best way I know 😸

@jameschensmith
Copy link
Contributor Author

jameschensmith commented Oct 27, 2020

D'oh! For some reason, I interpreted the configuration script differently. I apologize. I also brought this issue a bit off-topic. I would say that the central point which could close this ticket is providing a better failsafe option when doing a symlink in case the symlink doesn't exist for any reason. Would that be a good scope for this issue?

@ljharb
Copy link

ljharb commented Oct 27, 2020

@Schniz ah, i see - you need a symlink per shell based on fnm's design. thanks for explaining.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants