Permit empty environment variables as unset in `setup.py`
diff --git a/setup.py b/setup.py
index 2c3d4ee161f9..d9aed9b47bd8 100755
--- a/setup.py
+++ b/setup.py
@@ -24,6 +24,7 @@
 from setuptools import setup, find_packages
 from setuptools.command import egg_info
 import time
+import typing
 
 torch_available = True
 try:
@@ -56,6 +57,22 @@ def fetch_requirements(path):
         return [r.strip() for r in fd.readlines()]
 
 
+def is_env_set(key):
+    """
+    Checks if an environment variable is set and not "".
+    """
+    return bool(os.environ.get(key, None))
+
+
+def get_env_if_set(key, default: typing.Any = ""):
+    """
+    Returns an environment variable if it is set and not "",
+    otherwise returns a default value. In contrast, the fallback
+    parameter of os.environ.get() is skipped if the variable is set to "".
+    """
+    return os.environ.get(key, None) or default
+
+
 install_requires = fetch_requirements('requirements/requirements.txt')
 extras_require = {
     '1bit': [],  # add cupy based on cuda/rocm version
@@ -116,14 +133,14 @@ def fetch_requirements(path):
     print("[WARNING] Torch did not find cuda available, if cross-compiling or running with cpu only "
           "you can ignore this message. Adding compute capability for Pascal, Volta, and Turing "
           "(compute capabilities 6.0, 6.1, 6.2)")
-    if os.environ.get("TORCH_CUDA_ARCH_LIST", None) is None:
+    if not is_env_set("TORCH_CUDA_ARCH_LIST"):
         os.environ["TORCH_CUDA_ARCH_LIST"] = get_default_compute_capabilities()
 
 ext_modules = []
 
 # Default to pre-install kernels to false so we rely on JIT on Linux, opposite on Windows.
 BUILD_OP_PLATFORM = 1 if sys.platform == "win32" else 0
-BUILD_OP_DEFAULT = int(os.environ.get('DS_BUILD_OPS', BUILD_OP_PLATFORM))
+BUILD_OP_DEFAULT = int(get_env_if_set('DS_BUILD_OPS', BUILD_OP_PLATFORM))
 print(f"DS_BUILD_OPS={BUILD_OP_DEFAULT}")
 
 if BUILD_OP_DEFAULT:
@@ -147,7 +164,7 @@ def op_envvar(op_name):
 
 def op_enabled(op_name):
     env_var = op_envvar(op_name)
-    return int(os.environ.get(env_var, BUILD_OP_DEFAULT))
+    return int(get_env_if_set(env_var, BUILD_OP_DEFAULT))
 
 
 compatible_ops = dict.fromkeys(ALL_OPS.keys(), False)
@@ -160,7 +177,7 @@ def op_enabled(op_name):
     # If op is requested but not available, throw an error.
     if op_enabled(op_name) and not op_compatible:
         env_var = op_envvar(op_name)
-        if env_var not in os.environ:
+        if not is_env_set(env_var):
             builder.warning(f"One can disable {op_name} with {env_var}=0")
         abort(f"Unable to pre-compile {op_name}")
 
@@ -179,7 +196,7 @@ def op_enabled(op_name):
 # Write out version/git info.
 git_hash_cmd = "git rev-parse --short HEAD"
 git_branch_cmd = "git rev-parse --abbrev-ref HEAD"
-if command_exists('git') and 'DS_BUILD_STRING' not in os.environ:
+if command_exists('git') and not is_env_set('DS_BUILD_STRING'):
     try:
         result = subprocess.check_output(git_hash_cmd, shell=True)
         git_hash = result.decode('utf-8').strip()
@@ -216,11 +233,11 @@ def create_dir_symlink(src, dest):
 # Example: DS_BUILD_STRING=".dev20201022" python setup.py sdist bdist_wheel.
 
 # Building wheel for distribution, update version file.
-if 'DS_BUILD_STRING' in os.environ:
+if is_env_set('DS_BUILD_STRING'):
     # Build string env specified, probably building for distribution.
     with open('build.txt', 'w') as fd:
-        fd.write(os.environ.get('DS_BUILD_STRING'))
-    version_str += os.environ.get('DS_BUILD_STRING')
+        fd.write(os.environ['DS_BUILD_STRING'])
+    version_str += os.environ['DS_BUILD_STRING']
 elif os.path.isfile('build.txt'):
     # build.txt exists, probably installing from distribution.
     with open('build.txt', 'r') as fd:

This change fixes a bug that causes `setup.py` to fail when any of its configuration environment variables are the empty string.

For example, running the following:
```bash
export DS_BUILD_OPS=""
pip install deepspeed
```
Currently fails with the following message:
```
BUILD_OP_DEFAULT = int(os.environ.get('DS_BUILD_OPS', BUILD_OP_PLATFORM))
ValueError: invalid literal for int() with base 10: ''
```
Because `os.environ.get` does not use its fallback argument when an environment variable exists, but is empty.
Similarly, `key in os.environ` returns `True` for empty (but still set) keys.

Shells usually do not distinguish between unset variables and empty variables, so setting variables to the empty string is usually seen as another way to unset them. This specifically arose as an issue for me while I was writing a Dockerfile, since those have no way to unset `ENV` and `ARG` directives—only to set them to the empty string (or to figure out and hardcode what the default *would have* resolved to for each `ARG`). I figure fixing this here is more helpful than writing weird workarounds.

This changes `setup.py` to treat unset and empty environment variables identically. This behaviour could potentially be changed in other files, as well, but that is not included here.

Co-authored-by: Eta0 <Eta0@users.noreply.github.com>