mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-05 04:27:44 +00:00
* Add use of Windows Wide CRT API for env. vars
Replaces use of CRT API `getenv` and `putenv` with respectively
`_wgetenv` and `_wputenv`. Motivation is to reliably convert environment
variables to UTF-8, and the wide API is best there, because it's
reliably UTF-16.
Changed the hack in `lib/std/private/win_setenv.nim` by switching the
order of the Unicode and MBCS environment update; Unicode first, MBCS
second. Because `_wgetenv`/`_wputenv` is now used, the Unicode
environment will be initialized, so it should always be updated.
Stop updating MBCS environment with the name of `getEnv`. It's not
necessarily true that MBCS encoding and the `string` encoding is the
same. Instead convert UTF-16 to current Windows code page with
`wcstombs`, and use that string to update MBCS.
Fixes regression in `6b3c77e` that caused `std/envvars.getEnv` or
`std/os.getEnv` on Windows to return non-UTF-8 encoded strings.
Add tests that test environment variables with Unicode characters in
their name or value.
* Fix test issues
Fixes
* `nim cpp` didn't compile the tests
* Nimscript import of `tosenv.nim` from `test_nimscript.nims` failed
with "cannot importc"
* Fix missing error check on `wcstombs`
* Fix ANSI testing errors
* Separate ANSI-related testing to their own tests, and only executing
them if running process has a specific code page
* Setting locale with `setlocale` was not reliable and didn't work on
certain machines
* Add handling of a "no character representation" error in second
`wcstombs` call
* tests/newruntime_misc: Increment allocCount
Increments overall allocations in `tnewruntime_misc` test. This is
because `getEnv` now does an additional allocation: allocation of the
UTF-16 string used as parameter to `c_wgetenv`.
* Revert "tests/newruntime_misc: Increment allocCount"
This reverts commit 4d4fe8bd3e.
* tests/newruntime_misc: Increment allocCount on Windows
Increments overall allocations in `tnewruntime_misc` test for Windows.
This is because `getEnv` on Windows now does an additional allocation:
allocation of the UTF-16 string used as parameter to `c_wgetenv`.
* Refactor, adding suggestions from code review
Co-authored-by: Clay Sweetser <Varriount@users.noreply.github.com>
* Document, adding suggestions
Co-authored-by: Clay Sweetser <Varriount@users.noreply.github.com>
Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com>
Co-authored-by: Clay Sweetser <Varriount@users.noreply.github.com>
217 lines
8.0 KiB
Nim
217 lines
8.0 KiB
Nim
#
|
|
#
|
|
# Nim's Runtime Library
|
|
# (c) Copyright 2022 Nim contributors
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
|
|
## The `std/envvars` module implements environment variables handling.
|
|
import std/oserrors
|
|
|
|
type
|
|
ReadEnvEffect* = object of ReadIOEffect ## Effect that denotes a read
|
|
## from an environment variable.
|
|
WriteEnvEffect* = object of WriteIOEffect ## Effect that denotes a write
|
|
## to an environment variable.
|
|
|
|
|
|
when not defined(nimscript):
|
|
when defined(nodejs):
|
|
proc getEnv*(key: string, default = ""): string {.tags: [ReadEnvEffect].} =
|
|
var ret = default.cstring
|
|
let key2 = key.cstring
|
|
{.emit: "const value = process.env[`key2`];".}
|
|
{.emit: "if (value !== undefined) { `ret` = value };".}
|
|
result = $ret
|
|
|
|
proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} =
|
|
var key2 = key.cstring
|
|
var ret: bool
|
|
{.emit: "`ret` = `key2` in process.env;".}
|
|
result = ret
|
|
|
|
proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} =
|
|
var key2 = key.cstring
|
|
var val2 = val.cstring
|
|
{.emit: "process.env[`key2`] = `val2`;".}
|
|
|
|
proc delEnv*(key: string) {.tags: [WriteEnvEffect].} =
|
|
var key2 = key.cstring
|
|
{.emit: "delete process.env[`key2`];".}
|
|
|
|
iterator envPairsImpl(): tuple[key, value: string] {.tags: [ReadEnvEffect].} =
|
|
var num: int
|
|
var keys: RootObj
|
|
{.emit: "`keys` = Object.keys(process.env); `num` = `keys`.length;".}
|
|
for i in 0..<num:
|
|
var key, value: cstring
|
|
{.emit: "`key` = `keys`[`i`]; `value` = process.env[`key`];".}
|
|
yield ($key, $value)
|
|
|
|
# commented because it must keep working with js+VM
|
|
# elif defined(js):
|
|
# {.error: "requires -d:nodejs".}
|
|
|
|
else:
|
|
|
|
when defined(windows):
|
|
proc c_putenv(envstring: cstring): cint {.importc: "_putenv", header: "<stdlib.h>".}
|
|
from std/private/win_setenv import setEnvImpl
|
|
import winlean
|
|
proc c_wgetenv(varname: WideCString): WideCString {.importc: "_wgetenv",
|
|
header: "<stdlib.h>".}
|
|
proc getEnvImpl(env: cstring): WideCString = c_wgetenv(env.newWideCString)
|
|
else:
|
|
proc c_getenv(env: cstring): cstring {.
|
|
importc: "getenv", header: "<stdlib.h>".}
|
|
proc c_setenv(envname: cstring, envval: cstring, overwrite: cint): cint {.importc: "setenv", header: "<stdlib.h>".}
|
|
proc c_unsetenv(env: cstring): cint {.importc: "unsetenv", header: "<stdlib.h>".}
|
|
proc getEnvImpl(env: cstring): cstring = c_getenv(env)
|
|
|
|
proc getEnv*(key: string, default = ""): string {.tags: [ReadEnvEffect].} =
|
|
## Returns the value of the `environment variable`:idx: named `key`.
|
|
##
|
|
## If the variable does not exist, `""` is returned. To distinguish
|
|
## whether a variable exists or it's value is just `""`, call
|
|
## `existsEnv(key) proc`_.
|
|
##
|
|
## See also:
|
|
## * `existsEnv proc`_
|
|
## * `putEnv proc`_
|
|
## * `delEnv proc`_
|
|
## * `envPairs iterator`_
|
|
runnableExamples:
|
|
assert getEnv("unknownEnv") == ""
|
|
assert getEnv("unknownEnv", "doesn't exist") == "doesn't exist"
|
|
|
|
let env = getEnvImpl(key)
|
|
if env == nil: return default
|
|
result = $env
|
|
|
|
proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} =
|
|
## Checks whether the environment variable named `key` exists.
|
|
## Returns true if it exists, false otherwise.
|
|
##
|
|
## See also:
|
|
## * `getEnv proc`_
|
|
## * `putEnv proc`_
|
|
## * `delEnv proc`_
|
|
## * `envPairs iterator`_
|
|
runnableExamples:
|
|
assert not existsEnv("unknownEnv")
|
|
|
|
return getEnvImpl(key) != nil
|
|
|
|
proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} =
|
|
## Sets the value of the `environment variable`:idx: named `key` to `val`.
|
|
## If an error occurs, `OSError` is raised.
|
|
##
|
|
## See also:
|
|
## * `getEnv proc`_
|
|
## * `existsEnv proc`_
|
|
## * `delEnv proc`_
|
|
## * `envPairs iterator`_
|
|
when defined(windows):
|
|
if key.len == 0 or '=' in key:
|
|
raise newException(OSError, "invalid key, got: " & $(key, val))
|
|
if setEnvImpl(key, val, 1'i32) != 0'i32:
|
|
raiseOSError(osLastError(), $(key, val))
|
|
else:
|
|
if c_setenv(key, val, 1'i32) != 0'i32:
|
|
raiseOSError(osLastError(), $(key, val))
|
|
|
|
proc delEnv*(key: string) {.tags: [WriteEnvEffect].} =
|
|
## Deletes the `environment variable`:idx: named `key`.
|
|
## If an error occurs, `OSError` is raised.
|
|
##
|
|
## See also:ven
|
|
## * `getEnv proc`_
|
|
## * `existsEnv proc`_
|
|
## * `putEnv proc`_
|
|
## * `envPairs iterator`_
|
|
template bail = raiseOSError(osLastError(), key)
|
|
when defined(windows):
|
|
#[
|
|
# https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-s-wputenv-s?view=msvc-160
|
|
> You can remove a variable from the environment by specifying an empty string (that is, "") for value_string
|
|
note that nil is not legal
|
|
]#
|
|
if key.len == 0 or '=' in key:
|
|
raise newException(OSError, "invalid key, got: " & key)
|
|
let envToDel = key & "="
|
|
if c_putenv(cstring envToDel) != 0'i32: bail
|
|
else:
|
|
if c_unsetenv(key) != 0'i32: bail
|
|
|
|
when defined(windows):
|
|
when defined(cpp):
|
|
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.importcpp: "(NI16*)wcschr((const wchar_t *)#, #)",
|
|
header: "<string.h>".}
|
|
else:
|
|
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.importc: "wcschr",
|
|
header: "<string.h>".}
|
|
elif defined(macosx) and not defined(ios) and not defined(emscripten):
|
|
# From the manual:
|
|
# Shared libraries and bundles don't have direct access to environ,
|
|
# which is only available to the loader ld(1) when a complete program
|
|
# is being linked.
|
|
# The environment routines can still be used, but if direct access to
|
|
# environ is needed, the _NSGetEnviron() routine, defined in
|
|
# <crt_externs.h>, can be used to retrieve the address of environ
|
|
# at runtime.
|
|
proc NSGetEnviron(): ptr cstringArray {.importc: "_NSGetEnviron",
|
|
header: "<crt_externs.h>".}
|
|
elif defined(haiku):
|
|
var gEnv {.importc: "environ", header: "<stdlib.h>".}: cstringArray
|
|
else:
|
|
var gEnv {.importc: "environ".}: cstringArray
|
|
|
|
iterator envPairsImpl(): tuple[key, value: string] {.tags: [ReadEnvEffect].} =
|
|
when defined(windows):
|
|
block:
|
|
template impl(get_fun, typ, size, zero, free_fun) =
|
|
let env = get_fun()
|
|
var e = env
|
|
if e == nil: break
|
|
while true:
|
|
let eend = strEnd(e)
|
|
let kv = $e
|
|
let p = find(kv, '=')
|
|
yield (substr(kv, 0, p-1), substr(kv, p+1))
|
|
e = cast[typ](cast[ByteAddress](eend)+size)
|
|
if typeof(zero)(eend[1]) == zero: break
|
|
discard free_fun(env)
|
|
impl(getEnvironmentStringsW, WideCString, 2, 0, freeEnvironmentStringsW)
|
|
else:
|
|
var i = 0
|
|
when defined(macosx) and not defined(ios) and not defined(emscripten):
|
|
var gEnv = NSGetEnviron()[]
|
|
while gEnv[i] != nil:
|
|
let kv = $gEnv[i]
|
|
inc(i)
|
|
let p = find(kv, '=')
|
|
yield (substr(kv, 0, p-1), substr(kv, p+1))
|
|
|
|
proc envPairsImplSeq(): seq[tuple[key, value: string]] = discard # vmops
|
|
|
|
iterator envPairs*(): tuple[key, value: string] {.tags: [ReadEnvEffect].} =
|
|
## Iterate over all `environments variables`:idx:.
|
|
##
|
|
## In the first component of the tuple is the name of the current variable stored,
|
|
## in the second its value.
|
|
##
|
|
## Works in native backends, nodejs and vm, like the following APIs:
|
|
## * `getEnv proc`_
|
|
## * `existsEnv proc`_
|
|
## * `putEnv proc`_
|
|
## * `delEnv proc`_
|
|
when nimvm:
|
|
for ai in envPairsImplSeq(): yield ai
|
|
else:
|
|
when defined(nimscript): discard
|
|
else:
|
|
for ai in envPairsImpl(): yield ai
|