Commit 496a8fa7 authored by Wez Furlong's avatar Wez Furlong Committed by Facebook Github Bot

fbcode_builder: getdeps: add envfuncs.py

Summary:
Add some helpers for manipulating environment variables
that will hold either compiler flags or paths.

These are used in later diffs when setting up arguments/environment
for eg: cmake.

Reviewed By: sinancepel, simpkins

Differential Revision: D14690993

fbshipit-source-id: 7f9753cd99d968550fe9e3ba8b7017a44683061e
parent 003da387
#!/usr/bin/env python
# Copyright (c) 2019-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
from __future__ import absolute_import, division, print_function, unicode_literals
import os
import shlex
import sys
class Env(object):
def __init__(self, src=None):
self._dict = {}
self.update(src or os.environ)
def update(self, src):
for k, v in src.items():
self.set(k, v)
def copy(self):
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 everthing 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):
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):
self.unset(key)
def __repr__(self):
return repr(self._dict)
def set(self, key, value):
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):
self.set(key, value)
def __iter__(self):
return self._dict.__iter__()
def __len__(self):
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=True, separator=os.pathsep):
""" 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, append=True):
""" 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))
def path_search(env, exename, defval=None):
""" 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
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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment