Changed . token to _dot

This change allows the dotfiles to work with chezmoi (e.g: on windows)
and improves grepability with neovim/telescope
This commit is contained in:
2024-11-07 13:52:17 +00:00
parent 83b02bd753
commit 896af887ca
2351 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
# Virtual environment scripts
The scripts in this directory activate virtual environments for Conda environments.
## Usage
The activation and deactivation commands are exported from the `conda` module.
```
> use conda.nu
> conda activate foo
[foo] > conda deactivate
>
```
The `activate` command also includes custom completions for the environment names.
To disable the prompt changes (e.g., to let [Starship](https://starship.rs) include its own), set the environment variable `CONDA_NO_PROMPT`.
To set it globally, add the line `$env.CONDA_NO_PROMPT = true` to `$nu.config-path`.
## Limitations
_(old text, not tested)_
- The "root_prefix" might not actually correspond to the correct path to the Conda envs. You can fix
this for your setup by changing how the root prefix is found in the `conda-env` command.
- Nested envs are not well supported. If you activate a Conda env while another one is
activated, new elements will be appended to the PATH, but the other environment
variables will be overwritten. There's no way to then restore the PATH to the state
it was in before activating the *first* env (at least not with this script directly).

View File

@@ -0,0 +1,34 @@
# Auto virtual environment scripts
The scripts in this directory activate virtual environments whenever you cd into a directory with a "trigger" file
## Usage
1. set `$env.AUTO_VENV_TRIGGER` as the preferred name of a trigger file
1. import `auto-venv` into your environment
1. Create a symlink via `ln -s` of the script file `./auto-venv/venvs/python-venv.nu` to a trigger file in project at `/path/to/project/$AUTO_VENV_TRIGGER`
1. `cd` into `/path/to/project/`
For example:
```nu
# config.nu
export-env {
$env.AUTO_VENV_TRIGGER = '__auto-venv.nu'
source-env ~/path/to/nu_scripts/modules/virtual_environments/auto-venv/auto-venv.nu
}
use ~/path/to/nu_scripts/modules/virtual_environments/auto-venv/auto-venv.nu *
```
When `auto-venv` detects that a file of that name exists in the folder you cd'ed into (or one of it's parents), it will automatically enable an overlay (as defined by the trigger file).
An example overlay for python venv is in `./venvs`
NOTE: the trigger file *must* export a custom command called `auto-venv-on-enter` that takes in the `$env` (this is the environment *before* the overlay was enabled).
## Limitations
- Due to limitations with overlay naming, you cannot nest auto-venv triggers, even for separate languages / toolchains

View File

@@ -0,0 +1,86 @@
# must source-env and 'use *' this file
# must be in 'global' scope when `condition` is called
export use venv_helpers.nu
export use path_extensions.nu
export-env {
# this needs to be set somewhere
# $env.AUTO_VENV_TRIGGER = '__auto-venv.nu'
let hooks = (default-hooks)
let hooks = ($hooks | append (build-hooks))
$env.config = ($env.config | upsert hooks.env_change.PWD $hooks )
}
def default-hooks [] {
(if ($env.config.hooks.env_change.PWD != null) {
[$env.config.hooks.env_change.PWD]
}
else {
[]
})
}
def build-hooks [] {
let trigger = $env.AUTO_VENV_TRIGGER
let on_enter = '
let _env = $env
let pwd = $_env.PWD
let trigger = (path_extensions path find-sub . __trigger__ --type ["symlink", "file"])
cd ($trigger | path dirname)
overlay use __trigger__ as __auto_venv
cd ($pwd)
auto-venv-on-enter $_env
hide _env
hide pwd
hide trigger
'
let on_exit = '
overlay hide __auto_venv --keep-env [PWD]
'
let on_enter = ($on_enter | str replace -a '__trigger__' $trigger)
let hook = [
# activate on entry
{
condition: {|before, after| venv_helpers has-entered-venv $after }
code: $'
($on_enter)
'
}
# re-activate on swap
{
condition: {|before, after| venv_helpers has-swapped-venv $after }
code: $'
($on_exit)
($on_enter)
'
}
# deactivate on exit
{
condition: { |before, after| venv_helpers has-exited-venv $after }
code: $'
($on_exit)
'
}
]
$hook
}

View File

@@ -0,0 +1,59 @@
# Returns a list of full paths starting from root, to the path given
#
#
# walk '/home/user/projects';
# > [
# > '/home/',
# > '/home/user/',
# > '/home/user/projects/',
# > ]
#
export def "path walk" [
path: any
] {
let list = ($path | path expand | path split);
$list | enumerate | each { |$part| (
$list | first ($part.index + 1) | path join;
)}
}
# Returns true if 'subfolder' is found along the path of 'folder', with a given type
export def "path check-sub" [
folder: any,
subfolder: string,
--type: list
] {
(ls -a $folder
| where (
($type == null or $it.type in $type)
and ($it.name | path basename) == $subfolder
)
| length
) > 0;
}
# Walks the path along 'folder', and returns the path of the first subfolder found, or nothing if the subfolder was not found.
#
# path find-sub '/home/user/projects/code' '.venv';
# > /home/user/projects/.venv
export def "path find-sub" [
folder: any,
subfolder: string,
--type: list
] {
let paths = (path walk $folder);
let paths = ( $paths
| where (
path check-sub $it $subfolder --type $type
)
);
if ($paths != null) and ($paths | length) > 0 {
[ ($paths | first), $subfolder ] | path join
} else {[]}
}

View File

@@ -0,0 +1,90 @@
use path_extensions.nu *
def has-sub [
folder: string,
subfolder: string
] {
not (path find-sub $folder $subfolder | is-empty);
}
def get-env [
name: string
default: any
] {
(if ($name in $env) {
$env | get $name
}
else {
$default
})
}
export def venv-is-active [] {
'__auto_venv' in (overlay list)
}
# Creates a virtual environment under the current directory
export def 'venv-create' [] {
let venv_path = $env.PWD
let venv_name = ($env.PWD | path basename)
let is_posix = ($nu.os-info.family == 'unix')
let python_name = if $is_posix {'python3'} else {'py.exe'}
run-external $python_name "-m" "venv" ".venv" "--clear" "--prompt" $venv_name
run-external $".venv/bin/($python_name)" "-m" "pip" "install" "-U" "pip" "wheel" "setuptools"
let trigger_file = ([$env.PWD, $env.AUTO_VENV_TRIGGER] | path join)
ln -sf $'($env.FILE_PWD)/venvs/python-venv.nu' $trigger_file
}
export def has-entered-venv [
after: path,
] {
let target = (path find-sub $after $env.AUTO_VENV_TRIGGER)
(if ($target | is-empty) {
false
}
else {
# if venv is already active, handle it in "venv swap" hook
not (venv-is-active)
})
}
export def has-swapped-venv [
after: path,
] {
(if not (venv-is-active) {
false
}
else {
let target = (path find-sub $after $env.AUTO_VENV_TRIGGER)
(if ($target | is-empty) {
false
}
else {
(if ('VIRTUAL_ENV' in $env) {
$env.VIRTUAL_ENV != $target
} else {
false # should it default to `false`?
})
})
})
}
export def has-exited-venv [
after: path,
] {
(if not (venv-is-active) {
false
}
else {
not (has-sub $after $env.AUTO_VENV_TRIGGER)
})
}

View File

@@ -0,0 +1,107 @@
###
# An example auto-venv module.
# Copy this into `~/your/project/__auto-venv.nu` (or whatever you named your trigger file)
# adapted from https://github.com/pypa/virtualenv/blob/46f68d67c79f2280554f47f3c21265b3a1e899a4/src/virtualenv/activation/nushell/activate.nu
export def --env auto-venv-on-enter [
_env: record,
] {
def is-string [x] {
($x | describe) == 'string'
}
def has-env [name: string] {
$name in ($_env)
}
let virtual_env = (path_extensions path find-sub ($_env.PWD | into string) '.venv')
let bin = ([$virtual_env, "bin"] | path join)
let virtual_prompt = ""
let is_windows = $nu.os-info.name == 'windows'
let path_name = if $is_windows {
if (has-env 'Path') {
'Path'
} else {
'PATH'
}
} else {
'PATH'
}
let path_sep = (if $nu.os-info.name == "windows" {
'\'
}
else {
'/'
})
let old_path = (
if $is_windows {
if (has-env 'Path') {
$_env.Path
} else {
$_env.PATH
}
} else {
$_env.PATH
} | if (is-string $in) {
# if Path/PATH is a string, make it a list
$in | split row $path_sep | path expand
} else {
$in
}
)
let venv_path = ([$virtual_env $bin] | path join)
# let new_path = ($old_path | prepend $venv_path | str join $path_sep)
let new_path = ($old_path | prepend $venv_path)
# Creating the new prompt for the session
let virtual_prompt = if ($virtual_prompt == '') {
$'(char lparen)($virtual_env | path split | drop 1 | path join | path basename)(char rparen) '
} else {
'(' + $virtual_prompt + ') '
}
let old_prompt_command = if (has-env 'PROMPT_COMMAND') {
$_env.PROMPT_COMMAND
} else {
''
}
# If there is no default prompt, then only the env is printed in the prompt
let new_prompt = if (has-env 'PROMPT_COMMAND') {
if (($old_prompt_command | describe) in ['block', 'closure']) {
$'($virtual_prompt)(do $old_prompt_command)'
} else {
$'($virtual_prompt)($old_prompt_command)'
}
} else {
$'($virtual_prompt)'
}
# Add current PWD to NU_LIB_DIRS so we can enter sub-directory without an error
let new_lib_dirs = if not $env.PWD in $env.NU_LIB_DIRS {
$env.NU_LIB_DIRS | prepend $env.PWD
} else {
$env.NU_LIB_DIRS
}
# Environment variables that will be batched loaded to the virtual env
let new_env = {
$path_name : $new_path
VIRTUAL_ENV : $virtual_env
PROMPT_COMMAND : $new_prompt
VIRTUAL_PROMPT : $virtual_prompt
NU_LIB_DIRS : $new_lib_dirs
}
# Activate the environment variables
load-env $new_env
}
export alias pydoc = python -m pydoc
export alias pip = python -m pip

View File

@@ -0,0 +1,159 @@
# Activate conda environment
export def --env activate [
env_name?: string@'nu-complete conda envs' # name of the environment
] {
let conda_info = (conda info --envs --json | from json)
let env_name = if $env_name == null {
"base"
} else {
$env_name
}
let env_dir = if $env_name != "base" {
if ($env_name | path exists) and (($env_name | path expand) in $conda_info.envs ) {
($env_name | path expand)
} else {
((check-if-env-exists $env_name $conda_info) | into string)
}
} else {
$conda_info.root_prefix
}
let old_path = (system-path | str join (char esep))
let new_path = if (windows?) {
conda-create-path-windows $env_dir
} else {
conda-create-path-unix $env_dir
}
let virtual_prompt = $'[($env_name)] '
let new_env = ({
CONDA_DEFAULT_ENV: $env_name
CONDA_PREFIX: $env_dir
CONDA_PROMPT_MODIFIER: $virtual_prompt
CONDA_SHLVL: "1"
CONDA_OLD_PATH: $old_path
} | merge $new_path)
let new_env = if not (has-env CONDA_NO_PROMPT) {
let old_prompt_command = if (has-env CONDA_OLD_PROMPT_COMMAND) {
$env.CONDA_OLD_PROMPT_COMMAND
} else {
if (has-env 'PROMPT_COMMAND') {
$env.PROMPT_COMMAND
} else {
''
}
}
let new_prompt = if (has-env 'PROMPT_COMMAND') {
if 'closure' in ($old_prompt_command | describe) {
{|| $'($virtual_prompt)(do $old_prompt_command)' }
} else {
{|| $'($virtual_prompt)($old_prompt_command)' }
}
} else {
{|| $'($virtual_prompt)' }
}
$new_env | merge {
CONDA_OLD_PROMPT_COMMAND: $old_prompt_command
PROMPT_COMMAND: $new_prompt
}
} else {
$new_env | merge { CONDA_OLD_PROMPT_COMMAND: null }
}
load-env $new_env
}
# Deactivate currently active conda environment
export def --env deactivate [] {
let path_name = if "PATH" in $env { "PATH" } else { "Path" }
$env.$path_name = $env.CONDA_OLD_PATH
hide-env CONDA_PROMPT_MODIFIER
hide-env CONDA_PREFIX
hide-env CONDA_SHLVL
hide-env CONDA_DEFAULT_ENV
hide-env CONDA_OLD_PATH
$env.PROMPT_COMMAND = (
if $env.CONDA_OLD_PROMPT_COMMAND == null {
$env.PROMPT_COMMAND
} else {
$env.CONDA_OLD_PROMPT_COMMAND
}
)
hide-env CONDA_OLD_PROMPT_COMMAND
}
def check-if-env-exists [ env_name: string, conda_info: record ] {
let env_dirs = (
$conda_info.envs_dirs |
each { || path join $env_name }
)
let en = ($env_dirs | each {|en| $conda_info.envs | where $it == $en } | where ($it | length) == 1 | flatten)
if ($en | length) > 1 {
error make --unspanned {msg: $"You have environments in multiple locations: ($en)"}
}
if ($en | length) == 0 {
error make --unspanned {msg: $"Could not find given environment: ($env_name)"}
}
$en.0
}
def 'nu-complete conda envs' [] {
conda info --envs
| lines
| where not ($it | str starts-with '#')
| where not ($it | is-empty)
| each {|entry| $entry | split row ' ' | get 0 }
}
def conda-create-path-windows [env_dir: path] {
# Conda on Windows needs a few additional Path elements
let env_path = [
$env_dir
([$env_dir "Scripts"] | path join)
([$env_dir "Library" "mingw-w64"] | path join)
([$env_dir "Library" "bin"] | path join)
([$env_dir "Library" "usr" "bin"] | path join)
]
let new_path = ([$env_path (system-path)]
| flatten
| str join (char esep))
{ Path: $new_path }
}
def conda-create-path-unix [env_dir: path] {
let env_path = [
([$env_dir "bin"] | path join)
]
let new_path = ([$env_path $env.PATH]
| flatten
| str join (char esep))
{ PATH: $new_path }
}
def windows? [] {
($nu.os-info.name | str downcase) == "windows"
}
def system-path [] {
if "PATH" in $env { $env.PATH } else { $env.Path }
}
def has-env [name: string] {
$name in $env
}

View File

@@ -0,0 +1,62 @@
# Conda Module for Nushell
A simple module for activating and deactivating Conda environments.
## Prerequisites
- [nushell](https://github.com/nushell/nushell) >= 0.94.0
## Installation
Put `nu_conda.nu` into the module folder of your nushell configuration workspace.
## Usage
```nu
use nu_conda.nu # activate module
nu_conda activate py36 # activate a Conda environment, e.g. py36
nu_conda deactivate # deactivate the activated Conda environment
nu_conda list # list available environments, same as `$env.CONDA_ENVS`
```
## How It Works
This module re-implements the activation and deactivation functionalities of
the [conda.nu](https://github.com/Neur1n/nu_scripts/blob/main/virtual_environments/conda.nu)
module while providing a better performance, but not fully replacing it.
This module adds paths of a target Conda environment to `PATH`/`Path` while
activating the environment, and recover the original `PATH`/`Path` while
deactivating an environment. Several environment variables are exported:
- `CONDA_BASE_PATH`: The original `PATH`/`Path` before any activation/deactivation.
- `CONDA_ROOT`: Root directory of Conda installation.
- `CONDA_ENVS`: Available Conda environments for activation.
- `CONDA_CURR`: Current activated Conda environments.
## FAQ
**Q**: How better is the performance?\
**A**: Activating a Conda environment costs ~20ms while conda.nu costs ~1500ms on
a PC with Windows 10 Enterprise OS and Intel i7-8700 3.20GHz CPU.
**Q**: How to show the current Conda environment in the prompt?\
**A**: This module does not automatically change the prompt when a Conda
environment is activated, but an environment variable `$env.CONDA_CURR` is set
to the name of the current Conda environment which can be used to customize the
prompt.
**Q**: Does it support Mamba/Micromamba?\
**A**: As [Mamba's documentation](https://mamba.readthedocs.io/en/latest/) said,
`mamba` is drop-in replacement for `conda`, and `micromamba` seems to be
another thing. This module only uses results of `conda/mamba info --envs --json`.
Therefore, I would say Mamba is (partially?) supported but I'm not sure about
Micromamba.
**Q**: How does it choose between Conda and Mamba?\
**A**: This module prefers calling `mamba` than `conda`, but it should be very
easy to change the preference by modifying the source code.
**Q**: Completions?\
**A**: PRs are welcomed.

View File

@@ -0,0 +1,91 @@
export-env {
if not ("CONDA_CURR" in $env) {
$env.CONDA_BASE_PATH = (if $nu.os-info.name == 'windows' {$env.Path} else {$env.PATH})
let info = (
if not (which mamba | is-empty) {
(mamba info --envs --json | from json)
} else if not (which conda | is-empty) {
(conda info --envs --json | from json)
} else {
('{"root_prefix": "", "envs": ""}' | from json)
})
$env.CONDA_ROOT = $info.root_prefix
$env.CONDA_ENVS = ($info.envs | reduce -f {} {|it, acc|
if $it == $info.root_prefix {
$acc | upsert "base" $it
} else {
$acc | upsert ($it | path basename) $it
}})
$env.CONDA_CURR = null
}
}
export def --env activate [name: string] {
if ($env.CONDA_ROOT | is-empty) {
print "Neither Conda nor Mamba is available."
return
}
if not ($name in $env.CONDA_ENVS) {
print $"Environment ($name) is invalid. Available:"
print $env.CONDA_ENVS
return
}
let new_path = (
if $nu.os-info.name == 'windows' {
update-path-windows ($env.CONDA_ENVS | get $name)
} else {
update-path-linux ($env.CONDA_ENVS | get $name)
})
load-env ({CONDA_CURR: $name} | merge $new_path)
}
export def --env deactivate [] {
if ($env.CONDA_ROOT | is-empty) {
print "Neither Conda nor Mamba is available."
return
}
$env.CONDA_CURR = null
load-env {Path: $env.CONDA_BASE_PATH, PATH: $env.CONDA_BASE_PATH}
}
export def --env list [] {
$env.CONDA_ENVS |
flatten |
transpose |
rename name path |
insert active { |it| $it.name == $env.CONDA_CURR } |
move path --after active
}
def update-path-linux [env_path: path] {
let env_path = [
$env_path,
([$env_path, "bin"] | path join)
]
return {
Path: ($env.PATH | prepend $env_path),
PATH: ($env.PATH | prepend $env_path)
}
}
def update-path-windows [env_path: path] {
let env_path = [
$env_path,
([$env_path, "Scripts"] | path join),
]
return {
Path: ($env.Path | prepend $env_path),
PATH: ($env.Path | prepend $env_path)
}
}

View File

@@ -0,0 +1,28 @@
# nu_conda_2
This script aims to be a drop-in replacement for `conda activate` and `mamba activate`. It is a combination of the conda.nu and nu_conda scripts and some of my own code.
# Features
- Shell prompts
- Uses def-env to 'cache' results of necessary commands
- Auto-complete for environments
- Works for the latest version of Nushell as of writing (December 2023): the other scripts do not work A.T.M
# Trade-offs
The nu_conda script uses def-env which loads everything when Nushell starts up. This results in longer start-up times for Nushell. The loading is unavoidable, but this script loads the data when the command is called. This means that startup of conda is not affected, but calling the command for the first time takes ~1 sec instead of ~100ms when using bash. Input is welcome on how to avoid this.
# Disabling prompts
To disable the prompt changes (e.g., to let [Starship](https://starship.rs) include its own), set the environment variable `CONDA_NO_PROMPT`.
To set it globally, add the line `$env.CONDA_NO_PROMPT = true` to `$nu.config-path`.
# Setup
Install the script to your script folder and import it in your config file:
```
use ~/.config/nushell/scripts/conda.nu
```
## Mamba
To use with mamba, simply add these lines to your config:
```nu
alias "mamba activate" = conda activate
alias "mamba deactivate" = conda deactivate
```

View File

@@ -0,0 +1,182 @@
def --env load-conda-info-env [] {
if (not (has-env CONDA_INFO)) {
export-env {
$env.CONDA_INFO = (
if not (which micromamba | is-empty) {
mut info = micromamba env list --json | from json
let extra_info = micromamba info --json | from json
$info.envs_dirs = $extra_info."envs directories"
$info.root_prefix = $extra_info."base environment"
$info
} else if not (which mamba | is-empty) {
(mamba info --envs --json --no-banner | from json)
} else if not (which conda | is-empty) {
(conda info --envs --json | from json)
} else {
null
}
)
}
}
}
# Activate conda environment
export def --env activate [
env_name: string@'nu-complete conda envs' = "base" # name of the environment
] {
load-conda-info-env
let conda_info = $env.CONDA_INFO
if ($conda_info == null) {
print "Error: No Conda, Mamba or Micromamba install could be found in the environment. Please install either and add them to the environment. See: https://www.nushell.sh/book/environment.html for more info"
return
}
let env_dir = if $env_name != "base" {
if ($env_name | path exists) and (($env_name | path expand) in $conda_info.envs ) {
($env_name | path expand)
} else {
((check-if-env-exists $env_name $conda_info) | into string)
}
} else {
$conda_info.root_prefix
}
let old_path = (system-path | str join (char esep))
let new_path = if (windows?) {
conda-create-path-windows $env_dir
} else {
conda-create-path-unix $env_dir
}
let virtual_prompt = $'[($env_name)] '
let new_env = ({
CONDA_DEFAULT_ENV: $env_name
CONDA_PREFIX: $env_dir
CONDA_PROMPT_MODIFIER: $virtual_prompt
CONDA_SHLVL: "1"
CONDA_OLD_PATH: $old_path
} | merge $new_path)
let new_env = if not (has-env CONDA_NO_PROMPT) {
let old_prompt_command = if (has-env CONDA_OLD_PROMPT_COMMAND) {
$env.CONDA_OLD_PROMPT_COMMAND
} else {
if (has-env 'PROMPT_COMMAND') {
$env.PROMPT_COMMAND
} else {
''
}
}
let new_prompt = if (has-env 'PROMPT_COMMAND') {
if 'closure' in ($old_prompt_command | describe) {
{|| $'($virtual_prompt)(do $old_prompt_command)' }
} else {
{|| $'($virtual_prompt)($old_prompt_command)' }
}
} else {
{|| $'($virtual_prompt)' }
}
$new_env | merge {
CONDA_OLD_PROMPT_COMMAND: $old_prompt_command
PROMPT_COMMAND: $new_prompt
}
} else {
$new_env | merge { CONDA_OLD_PROMPT_COMMAND: null }
}
load-env $new_env
}
# Deactivate currently active conda environment
export def --env deactivate [] {
let path_name = if "PATH" in $env { "PATH" } else { "Path" }
$env.$path_name = $env.CONDA_OLD_PATH
hide-env CONDA_PROMPT_MODIFIER
hide-env CONDA_PREFIX
hide-env CONDA_SHLVL
hide-env CONDA_DEFAULT_ENV
hide-env CONDA_OLD_PATH
$env.PROMPT_COMMAND = (
if $env.CONDA_OLD_PROMPT_COMMAND == null {
$env.PROMPT_COMMAND
} else {
$env.CONDA_OLD_PROMPT_COMMAND
}
)
hide-env CONDA_OLD_PROMPT_COMMAND
}
def check-if-env-exists [ env_name: string, conda_info: record ] {
let env_dirs = (
$conda_info.envs_dirs |
each { || path join $env_name }
)
let en = ($env_dirs | each {|en| $conda_info.envs | where $it == $en } | where ($it | length) == 1 | flatten)
if ($en | length) > 1 {
error make --unspanned {msg: $"You have environments in multiple locations: ($en)"}
}
if ($en | length) == 0 {
error make --unspanned {msg: $"Could not find given environment: ($env_name)"}
}
$en.0
}
def 'nu-complete conda envs' [] {
load-conda-info-env
$env.CONDA_INFO
| get env_vars.CONDA_ENVS
| lines
| where not ($it | str starts-with '#')
| where not ($it | is-empty)
| each {|entry| $entry | split row ' ' | get 0 }
}
def conda-create-path-windows [env_dir: path] {
# Conda on Windows needs a few additional Path elements
let env_path = [
$env_dir
([$env_dir "Scripts"] | path join)
([$env_dir "Library" "mingw-w64"] | path join)
([$env_dir "Library" "bin"] | path join)
([$env_dir "Library" "usr" "bin"] | path join)
]
let new_path = ([$env_path (system-path)]
| flatten
| str join (char esep))
{ Path: $new_path }
}
def conda-create-path-unix [env_dir: path] {
let env_path = [
([$env_dir "bin"] | path join)
]
let new_path = ([$env_path $env.PATH]
| flatten
| str join (char esep))
{ PATH: $new_path }
}
def windows? [] {
$nu.os-info.name == 'windows'
}
def system-path [] {
if "PATH" in $env { $env.PATH } else { $env.Path }
}
def has-env [name: string] {
$name in $env
}

View File

@@ -0,0 +1,43 @@
# MSVS Module for Nushell
A module for Using Microsoft Visual Studio (MSVS) command line tools from Nushell.
## Prerequisites
- [nushell](https://github.com/nushell/nushell) >= 0.94.0
- [vswhere](https://github.com/microsoft/vswhere) standalone or comes with VS
## Installation
Put `nu_msvs.nu` into the module folder of your nushell configuration workspace.
## Usage
```nu
use nu_msvs.nu
nu_msvs activate # Use 'nu_msvs activate --help' to see all available options
nu_msvs deactivate
```
## How It Works
MSVS provides scripts (such as `vsdevcmd.bat` and `vsvarsall.bat`) for
developers to use its command line tools (such as `cl.exe`) in Command Prompt
and Powershell, but these scripts are not available for nushell. One method to
use MSVS command line tools in nushell was described in nushell/nushell#5803,
which requires launching nushell within an Command Prompt/Powershell instance.
However, running `vsdevcmd.bat` or launching Powershell is quite slow.
Therefore, `nu_msvs.nu` is introduced and works similar to a virtual
environment.
When activating the MSVS environment (take MSVC tools for example):
1. Necessary path are added to `PATH`/`Path` to allow command line tools (e.g.
`cl.exe`) to be run in nushell.
2. An environment variable `INCLUDE` is defined and consists of Win32 header
directories, which will be used by `cl.exe`.
3. An environment variable `LIB` is defined and consists of Win32 library
directories, which will be used by `link.exe`.
## Supported Tools
- [x] MSVC
- [ ] ...

View File

@@ -0,0 +1,195 @@
def --env find_msvs [] {
export-env {
$env.MSVS_BASE_PATH = $env.Path
$env.PATH_VAR = (if "Path" in $env { "Path" } else { "PATH" })
# This is a total hack because nushell doesn't like parentheses in an environment variable like `$env.ProgramFiles(x86)`
let programfiles = $env | transpose name value | where name starts-with Program and name ends-with '(x86)' | get value.0
# According to https://github.com/microsoft/vswhere/wiki/Installing, vswhere should always be in this location.
let vswhere_cmd = $'($programfiles)\Microsoft Visual Studio\Installer\vswhere.exe'
let info = (
if ($vswhere_cmd | path exists) {
(^$vswhere_cmd -format json | from json)
} else {
# this should really error out here
('{"installationPath": [""]}' | from json)
}
)
$env.MSVS_ROOT = $info.installationPath.0
$env.MSVS_MSVC_ROOT = (
if not ($'($env.MSVS_ROOT)\VC\Tools\MSVC\' | path exists) {
""
} else if (ls ($'($env.MSVS_ROOT)\VC\Tools\MSVC\*' | into glob) | is-empty) {
""
} else {
((ls ($'($env.MSVS_ROOT)\VC\Tools\MSVC\*' | into glob)).name.0)
})
$env.MSVS_MSDK_ROOT = (registry query --hklm 'SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0' InstallationFolder | get value)
$env.MSVS_MSDK_VER = (registry query --hklm 'SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0' ProductVersion | get value) + '.0'
$env.MSVS_INCLUDE_PATH = ([
$'($env.MSVS_ROOT)\Include\($env.MSVS_MSDK_VER)\cppwinrt\winrt',
$'($env.MSVS_MSVC_ROOT)\Include',
$'($env.MSVS_MSDK_ROOT)Include\($env.MSVS_MSDK_VER)\cppwinrt\winrt',
$'($env.MSVS_MSDK_ROOT)Include\($env.MSVS_MSDK_VER)\shared',
$'($env.MSVS_MSDK_ROOT)Include\($env.MSVS_MSDK_VER)\ucrt',
$'($env.MSVS_MSDK_ROOT)Include\($env.MSVS_MSDK_VER)\um',
$'($env.MSVS_MSDK_ROOT)Include\($env.MSVS_MSDK_VER)\winrt'
] | str join (char esep))
let esep_path_converter = {
from_string: { |s| $s | split row (char esep) }
to_string: { |v| $v | path expand | str join (char esep) }
}
$env.ENV_CONVERSIONS = {
Path: $esep_path_converter
DYLD_FALLBACK_LIBRARY_PATH: $esep_path_converter
PSModulePath: $esep_path_converter
MSVS_BASE_PATH: $esep_path_converter
MSVS_INCLUDE_PATH: $esep_path_converter
INCLUDE: $esep_path_converter
LIB: $esep_path_converter
}
# Debugging Info
# print $"MSVS_BASE_PATH: ($env.MSVS_BASE_PATH)"
# print $"PATH_VAR: ($env.PATH_VAR)"
# print $"MSVS_ROOT: ($env.MSVS_ROOT)"
# print $"MSVS_MSVC_ROOT: ($env.MSVS_MSVC_ROOT)"
# print $"MSVS_MSDK_ROOT: ($env.MSVS_MSDK_ROOT)"
# print $"MSVS_MSDK_VER: ($env.MSVS_MSDK_VER)"
# print $"MSVS_INCLUDE_PATH: ($env.MSVS_INCLUDE_PATH)"
}
}
export def --env activate [
--host (-h): string = x64, # Host architecture, must be x64 or x86 (case insensitive)
--target (-t): string = x64, # Target architecture, must be x64 or x86 (case insensitive)
--sdk (-s): string = latest, # Version of Windows SDK, must be "latest" or a valid version string
--silent,
] {
# I changed export-env {} to a custom command to avoid having export-env run when loading the module
# because:
# 1. I couldn't use activate because it depends on env vars being set that were hidden with deactivate
# 2. It seems that export-env executed at `use`, leaves `$env.FILE_PWD` available in the environment
# which I think may be a bug. So, putting it in a custom command avoids that.
find_msvs
if (($env.MSVS_ROOT | is-empty) or ($env.MSVS_MSVC_ROOT | is-empty)) {
print "Either Microsoft Visual Studio or MSVC is valid."
return
}
let fh = ($host | str downcase)
let ft = ($target | str downcase)
let fs = (
if ($sdk != latest) {
$sdk
} else {
$env.MSVS_MSDK_VER
})
if (($fh != x64) and ($fh != x86)) {
print $"Wrong host architecture specified: ($fh)."
help n_msvc activate
return
}
if (($ft != x64) and ($ft != x86)) {
print $"Wrong target architecture specified: ($ft)."
help n_msvc activate
return
}
if not ($'($env.MSVS_MSDK_ROOT)bin\($fs)' | path exists) {
print $"Invalid Windows SDK version specified: ($fs)."
return
}
let env_path = [
($'($env.MSVS_ROOT)\..\Shared\Common\VSPerfCollectionTools\vs2019' | path expand),
$'($env.MSVS_ROOT)\Common7\IDE',
$'($env.MSVS_ROOT)\Common7\IDE\CommonExtensions\Microsoft\TestWindow',
$'($env.MSVS_ROOT)\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer',
$'($env.MSVS_ROOT)\Common7\IDE\Extensions\Microsoft\IntelliCode\CLI',
$'($env.MSVS_ROOT)\Common7\IDE\Tools',
$'($env.MSVS_ROOT)\Common7\IDE\VC\VCPackages',
$'($env.MSVS_ROOT)\Common7\Tools\devinit',
$'($env.MSVS_ROOT)\MSBuild\Current\bin',
$'($env.MSVS_ROOT)\MSBuild\Current\bin\Roslyn',
$'($env.MSVS_ROOT)\Team Tools\DiagnosticsHub\Collector',
$'($env.MSVS_ROOT)\Team Tools\Performance Tools',
$'($env.MSVS_MSVC_ROOT)\bin\Host($fh)\($ft)',
$'($env.MSVS_MSDK_ROOT)bin\($ft)',
$'($env.MSVS_MSDK_ROOT)bin\($fs)\($ft)'
]
let env_path = (
if ($ft == x64) {
($env_path | prepend ($'($env.MSVS_ROOT)\..\Shared\Common\VSPerfCollectionTools\vs2019\x64' | path expand))
} else {
$env_path
})
let env_path = (
if ($ft == x64) {
($env_path | prepend $'($env.MSVS_ROOT)\Team Tools\Performance Tools\x64')
} else {
$env_path
})
let env_path = (
if ($ft != $fh) {
($env_path | prepend $'($env.MSVS_MSVC_ROOT)\bin\Host($fh)\($fh)')
} else {
$env_path
})
let env_path = ($env.MSVS_BASE_PATH | prepend $env_path)
let lib_path = ([
$'($env.MSVS_MSDK_ROOT)Lib\($env.MSVS_MSDK_VER)\ucrt\($ft)',
$'($env.MSVS_MSDK_ROOT)Lib\($env.MSVS_MSDK_VER)\um\($ft)',
$'($env.MSVS_MSVC_ROOT)\lib\($ft)',
] | str join (char esep))
if (not $silent) {
print "Activating Microsoft Visual Studio environment."
}
load-env {
$env.PATH_VAR: $env_path,
INCLUDE: $env.MSVS_INCLUDE_PATH,
LIB: $lib_path
}
# Debug Information
# print $"PATH_VAR: ($env.PATH_VAR)"
# print $"INCLUDE: ($env.INCLUDE)"
# print $"LIB: ($env.LIB)"
}
export def --env deactivate [] {
if (($env.MSVS_ROOT | is-empty) or ($env.MSVS_MSVC_ROOT | is-empty)) {
print "Either Microsoft Visual Studio or MSVC is valid."
return
}
load-env {
$env.PATH_VAR: $env.MSVS_BASE_PATH,
}
hide-env INCLUDE
hide-env LIB
hide-env MSVS_BASE_PATH
hide-env MSVS_ROOT
hide-env MSVS_MSVC_ROOT
hide-env MSVS_MSDK_ROOT
hide-env MSVS_MSDK_VER
hide-env MSVS_INCLUDE_PATH
}

View File

@@ -0,0 +1,25 @@
export def --env activate [venv_dir] {
let venv_abs_dir = ($venv_dir | path expand)
let venv_name = ($venv_abs_dir | path basename)
let old_path = $env.PATH
let new_path = (venv-path $venv_abs_dir)
let new_env = ({VENV_OLD_PATH: $old_path, VIRTUAL_ENV: $venv_name} | merge $new_path)
load-env $new_env
}
def "venv-path" [venv_dir] {
let env_path = [
$venv_dir,
([$venv_dir, "bin"] | path join)
]
return {
PATH: ($env.PATH | prepend $env_path)
}
}
export def --env deactivate [] {
$env.PATH = $env.VENV_OLD_PATH
hide-env VIRTUAL_ENV
hide-env VENV_OLD_PATH
}