Respect $XDG_* directories on Windows.

This fixes #1481.
This commit is contained in:
Qi Xiao 2022-06-09 22:48:39 +01:00
parent cb8628bcb6
commit 49115dba1d
5 changed files with 91 additions and 102 deletions

View File

@ -1,10 +1,13 @@
package shell
import (
"fmt"
"os"
"path/filepath"
"strings"
"src.elv.sh/pkg/daemon/daemondefs"
"src.elv.sh/pkg/env"
"src.elv.sh/pkg/fsutil"
"src.elv.sh/pkg/prog"
)
@ -12,16 +15,46 @@ import (
func rcPath() (string, error) {
if legacyRC, exists := legacyDataPath("rc.elv", false); exists {
return legacyRC, nil
} else if configHome := os.Getenv(env.XDG_CONFIG_HOME); configHome != "" {
return filepath.Join(configHome, "elvish", "rc.elv"), nil
} else if configHome, err := defaultConfigHome(); err == nil {
return filepath.Join(configHome, "elvish", "rc.elv"), nil
} else {
return "", fmt.Errorf("find rc.elv: %w", err)
}
return newRCPath()
}
func libPaths() ([]string, error) {
paths, err := newLibPaths()
var paths []string
if configHome := os.Getenv(env.XDG_CONFIG_HOME); configHome != "" {
paths = append(paths, filepath.Join(configHome, "elvish", "lib"))
} else if configHome, err := defaultConfigHome(); err == nil {
paths = append(paths, filepath.Join(configHome, "elvish", "lib"))
} else {
return nil, fmt.Errorf("find roaming lib directory: %w", err)
}
if dataHome := os.Getenv(env.XDG_DATA_HOME); dataHome != "" {
paths = append(paths, filepath.Join(dataHome, "elvish", "lib"))
} else if dataHome, err := defaultDataHome(); err == nil {
paths = append(paths, filepath.Join(dataHome, "elvish", "lib"))
} else {
return nil, fmt.Errorf("find local lib directory: %w", err)
}
if dataDirs := os.Getenv(env.XDG_DATA_DIRS); dataDirs != "" {
// We intentionally do not use filepath.SplitList and always follow the
// semantics of XDG, even on Windows.
paths = append(paths, strings.Split(dataDirs, ":")...)
} else {
paths = append(paths, defaultDataDirs...)
}
if legacyLib, exists := legacyDataPath("lib", true); exists {
paths = append(paths, legacyLib)
}
return paths, err
return paths, nil
}
// Returns a SpawnConfig containing all the paths needed by the daemon. It
@ -54,8 +87,13 @@ func daemonPaths(p *prog.DaemonPaths) (*daemondefs.SpawnConfig, error) {
func dbPath() (string, error) {
if legacyDB, exists := legacyDataPath("db", false); exists {
return legacyDB, nil
} else if stateHome := os.Getenv(env.XDG_STATE_HOME); stateHome != "" {
return filepath.Join(stateHome, "elvish", "db.bolt"), nil
} else if stateHome, err := defaultStateHome(); err == nil {
return filepath.Join(stateHome, "elvish", "db.bolt"), nil
} else {
return "", fmt.Errorf("find db: %w", err)
}
return newDBPath()
}
// Returns a path in the legacy data directory path, and whether it exists and

View File

@ -8,53 +8,27 @@ import (
"path/filepath"
"syscall"
"src.elv.sh/pkg/diag"
"src.elv.sh/pkg/env"
"src.elv.sh/pkg/fsutil"
)
func newRCPath() (string, error) {
return xdgHomePath(env.XDG_CONFIG_HOME, ".config", "elvish/rc.elv")
func defaultConfigHome() (string, error) { return homePath(".config") }
func defaultDataHome() (string, error) { return homePath(".local/share") }
var defaultDataDirs = []string{
"/usr/local/share/elvish/lib",
"/usr/share/elvish/lib",
}
const elvishLib = "elvish/lib"
func defaultStateHome() (string, error) { return homePath(".local/state") }
func newLibPaths() ([]string, error) {
var paths []string
libConfig, errConfig := xdgHomePath(env.XDG_CONFIG_HOME, ".config", elvishLib)
if errConfig == nil {
paths = append(paths, libConfig)
func homePath(suffix string) (string, error) {
home, err := fsutil.GetHome("")
if err != nil {
return "", fmt.Errorf("resolve ~/%s: %w", suffix, err)
}
libData, errData := xdgHomePath(env.XDG_DATA_HOME, ".local/share", elvishLib)
if errData == nil {
paths = append(paths, libData)
}
libSystem := os.Getenv(env.XDG_DATA_DIRS)
if libSystem == "" {
libSystem = "/usr/local/share:/usr/share"
}
for _, p := range filepath.SplitList(libSystem) {
paths = append(paths, filepath.Join(p, elvishLib))
}
return paths, diag.Errors(errConfig, errData)
}
func newDBPath() (string, error) {
return xdgHomePath(env.XDG_STATE_HOME, ".local/state", "elvish/db.bolt")
}
func xdgHomePath(envName, fallback, suffix string) (string, error) {
dir := os.Getenv(envName)
if dir == "" {
home, err := fsutil.GetHome("")
if err != nil {
return "", fmt.Errorf("resolve ~/%s/%s: %w", fallback, suffix, err)
}
dir = filepath.Join(home, fallback)
}
return filepath.Join(dir, suffix), nil
return filepath.Join(home, suffix), nil
}
// Returns a "run directory" for storing ephemeral files, which is guaranteed

View File

@ -9,37 +9,12 @@ import (
"src.elv.sh/pkg/env"
)
func newRCPath() (string, error) {
d, err := roamingAppData()
if err != nil {
return "", err
}
return filepath.Join(d, "elvish", "rc.elv"), nil
}
func newLibPaths() ([]string, error) {
local, err := localAppData()
if err != nil {
return nil, err
}
localLib := filepath.Join(local, "elvish", "lib")
roaming, err := roamingAppData()
if err != nil {
return nil, err
}
roamingLib := filepath.Join(roaming, "elvish", "lib")
return []string{roamingLib, localLib}, nil
}
func newDBPath() (string, error) {
d, err := localAppData()
if err != nil {
return "", err
}
return filepath.Join(d, "elvish", "db.bolt"), nil
}
var (
defaultConfigHome = roamingAppData
defaultDataHome = localAppData
defaultDataDirs = []string{}
defaultStateHome = localAppData
)
func localAppData() (string, error) {
return windows.KnownFolderPath(windows.FOLDERID_LocalAppData, windows.KF_FLAG_CREATE)

View File

@ -119,9 +119,10 @@ func MakeEvaler(stderr io.Writer) *eval.Evaler {
ev := eval.NewEvaler()
libs, err := libPaths()
if err != nil {
fmt.Fprintln(stderr, "Warning:", err)
fmt.Fprintln(stderr, "Warning: resolving lib paths:", err)
} else {
ev.LibDirs = libs
}
ev.LibDirs = libs
mods.AddTo(ev)
return ev
}

View File

@ -23,16 +23,14 @@ Each unit of code read is executed as a [code chunk](language.html#code-chunk).
Before the REPL starts, Elvish will execute the **RC file**. Its path is
determined as follows:
- If the legacy `~/.elvish/rc.elv` exists, it is used (this will be ignored in
a future version).
1. If the legacy `~/.elvish/rc.elv` exists, it is used (this will be ignored
starting from 0.20.0).
- Otherwise:
2. If the `XDG_CONFIG_HOME` environment variable is defined and non-empty,
`$XDG_CONFIG_HOME/elvish/rc.elv` is used.
- On UNIX (including macOS), `$XDG_CONFIG_HOME/elvish/rc.elv` is used,
defaulting to `~/.config/elvish/rc.elv` if `$XDG_CONFIG_HOME` is unset
or empty.
- On Windows, `%AppData%\elvish\rc.elv` is used.
3. Otherwise, `~/.config/elvish/rc.elv` (non-Windows OSes) or
`%AppData%\elvish\rc.elv` (Windows) is used.
If the RC file doesn't exist, Elvish does not execute any RC file.
@ -41,16 +39,14 @@ If the RC file doesn't exist, Elvish does not execute any RC file.
Elvish in interactive mode uses a database file to keep command and directory
history. Its path is determined as follows:
- If the legacy `~/.elvish/db` exists, it is used (this will be ignored in a
future version).
1. If the legacy `~/.elvish/db` exists, it is used (this will be ignored
starting from 0.20.0).
- Otherwise:
2. If the `XDG_STATE_HOME` environment variable is defined and non-empty,
`$XDG_STATE_HOME/elvish/db.bolt` is used.
- On UNIX (including macOS), `$XDG_STATE_HOME/elvish/db.bolt` is used,
defaulting to `~/.local/state/elvish/db.bolt` if `$XDG_STATE_HOME` is
unset or empty.
- On Windows, `%LocalAppData%\elvish\db.bolt` is used.
3. Othersie, `~/.local/state/elvish/db.bolt` (non-Windows OSes) or
`%LocalAppData%\elvish\db.bolt` is used.
# Running a script
@ -72,21 +68,26 @@ When running a script, Elvish does not evaluate the [RC file](#rc-file).
When importing [modules](language.html#modules), Elvish searches the following
directories:
- On UNIX:
1. If the `XDG_CONFIG_HOME` environment variable is defined and non-empty,
`$XDG_CONFIG_HOME/elvish/lib` is searched.
1. `$XDG_CONFIG_HOME/elvish/lib`, defaulting to `~/.config/elvish/lib` if
`$XDG_CONFIG_HOME` is unset or empty;
Otherwise, `~/.config/elvish/lib` (non-Window OSes) or
`%RoamingAppData%\elvish\lib` (Windows) is searched.
2. `$XDG_DATA_HOME/elvish/lib`, defaulting to `~/.local/share/elvish/lib` if
`$XDG_DATA_HOME` is unset or empty;
2. If the `XDG_DATA_HOME` environment variable is defined and non-empty,
`$XDG_DATA_HOME/elvish/lib` is searched.
3. Paths specified in the colon-delimited `$XDG_DATA_DIRS`, followed by
`elvish/lib`, defaulting to `/usr/local/share/elvish/lib` and
`/usr/share/elvish/lib` if `$XDG_DATA_DIRS` is unset or empty.
Otherwise, `~/.local/share/elvish/lib` (non-Windows OSes) or
`%LocalAppData%\elvish\lib` (Windows) is searched.
- On Windows: `%AppData%\elvish\lib`, followed by `%LocalAppData%\elvish\lib`.
3. If the `XDG_DATA_DIRS` environment variable is defined and non-empty, it is
treated as a colon-delimited list of paths, which are all searched.
If the legacy `~/.elvish/lib` directory exists, it is also searched.
Otherwise, `/usr/local/share/elvish/lib` and `/usr/share/elvish/lib` are
searched on non-Windows OSes. On Windows, no directories are searched.
4. If the legacy `~/.elvish/lib` directory exists, it is also searched (this
will be ignored starting from 0.20.0).
# Command-line flags