mirror of
https://github.com/facebook/proxygen.git
synced 2025-08-01 10:26:51 +03:00
Summary: Pull Request resolved: https://github.com/facebookincubator/zstrong/pull/723 Reviewed By: grievejia Differential Revision: D54471437 fbshipit-source-id: bc644553e31464ceb632034e4ce3f05ba30fbbcd
199 lines
6.3 KiB
Python
199 lines
6.3 KiB
Python
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
#
|
|
# This source code is licensed under the MIT license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
|
|
# pyre-unsafe
|
|
|
|
import os
|
|
import shlex
|
|
import sys
|
|
from typing import Optional
|
|
|
|
|
|
class Env(object):
|
|
def __init__(self, src=None) -> None:
|
|
self._dict = {}
|
|
if src is None:
|
|
self.update(os.environ)
|
|
else:
|
|
self.update(src)
|
|
|
|
def update(self, src) -> None:
|
|
for k, v in src.items():
|
|
self.set(k, v)
|
|
|
|
def copy(self) -> "Env":
|
|
return Env(self._dict)
|
|
|
|
def _key(self, key):
|
|
# The `str` cast may not appear to be needed, but without it we run
|
|
# into issues when passing the environment to subprocess. The main
|
|
# issue is that in python2 `os.environ` (which is the initial source
|
|
# of data for the environment) uses byte based strings, but this
|
|
# project uses `unicode_literals`. `subprocess` will raise an error
|
|
# if the environment that it is passed has a mixture of byte and
|
|
# unicode strings.
|
|
# It is simplest to force everything to be `str` for the sake of
|
|
# consistency.
|
|
key = str(key)
|
|
if sys.platform.startswith("win"):
|
|
# Windows env var names are case insensitive but case preserving.
|
|
# An implementation of PAR files on windows gets confused if
|
|
# the env block contains keys with conflicting case, so make a
|
|
# pass over the contents to remove any.
|
|
# While this O(n) scan is technically expensive and gross, it
|
|
# is practically not a problem because the volume of calls is
|
|
# relatively low and the cost of manipulating the env is dwarfed
|
|
# by the cost of spawning a process on windows. In addition,
|
|
# since the processes that we run are expensive anyway, this
|
|
# overhead is not the worst thing to worry about.
|
|
for k in list(self._dict.keys()):
|
|
if str(k).lower() == key.lower():
|
|
return k
|
|
elif key in self._dict:
|
|
return key
|
|
return None
|
|
|
|
def get(self, key, defval=None):
|
|
key = self._key(key)
|
|
if key is None:
|
|
return defval
|
|
return self._dict[key]
|
|
|
|
def __getitem__(self, key):
|
|
val = self.get(key)
|
|
if key is None:
|
|
raise KeyError(key)
|
|
return val
|
|
|
|
def unset(self, key) -> None:
|
|
if key is None:
|
|
raise KeyError("attempting to unset env[None]")
|
|
|
|
key = self._key(key)
|
|
if key:
|
|
del self._dict[key]
|
|
|
|
def __delitem__(self, key) -> None:
|
|
self.unset(key)
|
|
|
|
def __repr__(self):
|
|
return repr(self._dict)
|
|
|
|
def set(self, key, value) -> None:
|
|
if key is None:
|
|
raise KeyError("attempting to assign env[None] = %r" % value)
|
|
|
|
if value is None:
|
|
raise ValueError("attempting to assign env[%s] = None" % key)
|
|
|
|
# The `str` conversion is important to avoid triggering errors
|
|
# with subprocess if we pass in a unicode value; see commentary
|
|
# in the `_key` method.
|
|
key = str(key)
|
|
value = str(value)
|
|
|
|
# The `unset` call is necessary on windows where the keys are
|
|
# case insensitive. Since this dict is case sensitive, simply
|
|
# assigning the value to the new key is not sufficient to remove
|
|
# the old value. The `unset` call knows how to match keys and
|
|
# remove any potential duplicates.
|
|
self.unset(key)
|
|
self._dict[key] = value
|
|
|
|
def __setitem__(self, key, value) -> None:
|
|
self.set(key, value)
|
|
|
|
def __iter__(self):
|
|
return self._dict.__iter__()
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._dict)
|
|
|
|
def keys(self):
|
|
return self._dict.keys()
|
|
|
|
def values(self):
|
|
return self._dict.values()
|
|
|
|
def items(self):
|
|
return self._dict.items()
|
|
|
|
|
|
def add_path_entry(
|
|
env, name, item, append: bool = True, separator: str = os.pathsep
|
|
) -> None:
|
|
"""Cause `item` to be added to the path style env var named
|
|
`name` held in the `env` dict. `append` specifies whether
|
|
the item is added to the end (the default) or should be
|
|
prepended if `name` already exists."""
|
|
val = env.get(name, "")
|
|
if len(val) > 0:
|
|
val = val.split(separator)
|
|
else:
|
|
val = []
|
|
if append:
|
|
val.append(item)
|
|
else:
|
|
val.insert(0, item)
|
|
env.set(name, separator.join(val))
|
|
|
|
|
|
def add_flag(env, name, flag: str, append: bool = True) -> None:
|
|
"""Cause `flag` to be added to the CXXFLAGS-style env var named
|
|
`name` held in the `env` dict. `append` specifies whether the
|
|
flag is added to the end (the default) or should be prepended if
|
|
`name` already exists."""
|
|
val = shlex.split(env.get(name, ""))
|
|
if append:
|
|
val.append(flag)
|
|
else:
|
|
val.insert(0, flag)
|
|
env.set(name, " ".join(val))
|
|
|
|
|
|
_path_search_cache = {}
|
|
_not_found = object()
|
|
|
|
|
|
def tpx_path() -> str:
|
|
return "xplat/testinfra/tpx/ctp.tpx"
|
|
|
|
|
|
def path_search(env, exename: str, defval: Optional[str] = None) -> Optional[str]:
|
|
"""Search for exename in the PATH specified in env.
|
|
exename is eg: `ninja` and this function knows to append a .exe
|
|
to the end on windows.
|
|
Returns the path to the exe if found, or None if either no
|
|
PATH is set in env or no executable is found."""
|
|
|
|
path = env.get("PATH", None)
|
|
if path is None:
|
|
return defval
|
|
|
|
# The project hash computation code searches for C++ compilers (g++, clang, etc)
|
|
# repeatedly. Cache the result so we don't end up searching for these over and over
|
|
# again.
|
|
cache_key = (path, exename)
|
|
result = _path_search_cache.get(cache_key, _not_found)
|
|
if result is _not_found:
|
|
result = _perform_path_search(path, exename)
|
|
_path_search_cache[cache_key] = result
|
|
return result
|
|
|
|
|
|
def _perform_path_search(path, exename: str) -> Optional[str]:
|
|
is_win = sys.platform.startswith("win")
|
|
if is_win:
|
|
exename = "%s.exe" % exename
|
|
|
|
for bindir in path.split(os.pathsep):
|
|
full_name = os.path.join(bindir, exename)
|
|
if os.path.exists(full_name) and os.path.isfile(full_name):
|
|
if not is_win and not os.access(full_name, os.X_OK):
|
|
continue
|
|
return full_name
|
|
|
|
return None
|