1# Copyright 2020 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""The env module defines the environment variables used by Pigweed.""" 15 16from pathlib import Path 17import os 18import sys 19 20from pw_cli import envparse 21from pw_cli.allowed_caller import AllowedCaller, check_caller_in 22 23 24def pigweed_environment_parser() -> envparse.EnvironmentParser: 25 """Defines Pigweed's environment variables on an EnvironmentParser.""" 26 parser = envparse.EnvironmentParser(prefix='PW_') 27 28 parser.add_var('PW_BOOTSTRAP_PYTHON') 29 parser.add_var('PW_ENABLE_PRESUBMIT_HOOK_WARNING', default=False) 30 parser.add_var('PW_EMOJI', type=envparse.strict_bool, default=False) 31 parser.add_var('PW_ENVSETUP') 32 parser.add_var('PW_ENVSETUP_FULL') 33 parser.add_var( 34 'PW_ENVSETUP_NO_BANNER', type=envparse.strict_bool, default=False 35 ) 36 parser.add_var( 37 'PW_ENVSETUP_QUIET', type=envparse.strict_bool, default=False 38 ) 39 parser.add_var('PW_ENVIRONMENT_ROOT', type=Path) 40 parser.add_var('PW_PACKAGE_ROOT', type=Path) 41 parser.add_var('PW_PROJECT_ROOT', type=Path) 42 parser.add_var('PW_ROOT', type=Path) 43 parser.add_var( 44 'PW_DISABLE_ROOT_GIT_REPO_CHECK', 45 type=envparse.strict_bool, 46 default=False, 47 ) 48 parser.add_var('PW_SKIP_BOOTSTRAP') 49 parser.add_var('PW_SUBPROCESS', type=envparse.strict_bool, default=False) 50 parser.add_var('PW_USE_COLOR', type=envparse.strict_bool, default=True) 51 parser.add_var('PW_USE_GCS_ENVSETUP', type=envparse.strict_bool) 52 53 parser.add_allowed_suffix('_CIPD_INSTALL_DIR') 54 55 parser.add_var( 56 'PW_ENVSETUP_DISABLE_SPINNER', type=envparse.strict_bool, default=False 57 ) 58 parser.add_var('PW_DOCTOR_SKIP_CIPD_CHECKS') 59 parser.add_var( 60 'PW_ACTIVATE_SKIP_CHECKS', type=envparse.strict_bool, default=False 61 ) 62 63 parser.add_var('PW_BANNER_FUNC') 64 parser.add_var('PW_BRANDING_BANNER') 65 parser.add_var('PW_BRANDING_BANNER_COLOR', default='magenta') 66 67 parser.add_var( 68 'PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE', type=envparse.strict_bool 69 ) 70 71 parser.add_var('PW_CONSOLE_CONFIG_FILE') 72 parser.add_var('PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED') 73 74 parser.add_var('PW_NO_CIPD_CACHE_DIR') 75 parser.add_var('PW_CIPD_SERVICE_ACCOUNT_JSON') 76 77 # RBE environment variables 78 parser.add_var('PW_USE_RBE', default=False) 79 parser.add_var('PW_RBE_DEBUG', default=False) 80 parser.add_var('PW_RBE_CLANG_CONFIG', default='') 81 parser.add_var('PW_RBE_ARM_GCC_CONFIG', default='') 82 83 parser.add_var( 84 'PW_DISABLE_CLI_ANALYTICS', type=envparse.strict_bool, default=False 85 ) 86 87 return parser 88 89 90# Internal: memoize environment parsing to avoid unnecessary computation in 91# multiple calls to pigweed_environment(). 92_memoized_environment: envparse.EnvNamespace | None = None 93 94 95def pigweed_environment() -> envparse.EnvNamespace: 96 """Returns Pigweed's parsed environment.""" 97 global _memoized_environment # pylint: disable=global-statement 98 99 if _memoized_environment is None: 100 _memoized_environment = pigweed_environment_parser().parse_env() 101 102 return _memoized_environment 103 104 105_BAZEL_PROJECT_ROOT_ALLOW_LIST = [ 106 AllowedCaller( 107 filename='pw_build/py/pw_build/pigweed_upstream_build.py', 108 name='__main__', 109 function='<module>', 110 ), 111 AllowedCaller( 112 filename='pw_build/py/pw_build/project_builder.py', 113 name='*', 114 function='__init__', 115 self_class='ProjectBuilder', 116 ), 117 AllowedCaller( 118 filename='pw_build/py/pw_build/project_builder_presubmit_runner.py', 119 name='pw_build.project_builder_presubmit_runner', 120 function='main', 121 ), 122 AllowedCaller( 123 filename='pw_watch/py/pw_watch/watch.py', 124 name='__main__', 125 function='watch_setup', 126 ), 127 AllowedCaller( 128 filename='pw_watch/py/pw_watch/run.py', 129 name='__main__', 130 function='_parse_args', 131 ), 132] 133 134 135_PROJECT_ROOT_ERROR_MESSAGE = ''' 136Error: Unable to determine the project root directory. Expected environment 137variables are not set. Either $BUILD_WORKSPACE_DIRECTORY for bazel or 138$PW_PROJECT_ROOT for Pigweed bootstrap are required. 139 140Please re-run with either of these scenarios: 141 142 1. Under bazel with "bazelisk run ..." or "bazel run ..." 143 2. After activating a Pigweed bootstrap environment with ". ./activate.sh" 144''' 145 146 147def project_root(env: envparse.EnvNamespace | None = None) -> Path: 148 """Returns the project root by checking bootstrap and bazel env vars. 149 150 Please do not use this function unless the Python script must escape the 151 bazel sandbox. For example, an interactive tool that operates on the project 152 source code like code formatting. 153 """ 154 155 if running_under_bazel(): 156 bazel_source_dir = os.environ.get('BUILD_WORKSPACE_DIRECTORY', '') 157 158 # Ensure this function is only callable by functions in the allow list. 159 check_caller_in(_BAZEL_PROJECT_ROOT_ALLOW_LIST) 160 161 root = Path(bazel_source_dir) 162 else: 163 # Running outside bazel (via GN or bootstrap env). 164 if env is None: 165 env = pigweed_environment() 166 root = env.PW_PROJECT_ROOT 167 168 if not root: 169 print(_PROJECT_ROOT_ERROR_MESSAGE, file=sys.stderr) 170 sys.exit(1) 171 172 return root 173 174 175def running_under_bazel() -> bool: 176 """Returns True if any bazel script environment variables are set. 177 178 For more info on which variables are set when running executables in bazel 179 see: https://bazel.build/docs/user-manual#running-executables 180 """ 181 # The root of the workspace where the build was run. 182 bazel_source_dir = os.environ.get('BUILD_WORKSPACE_DIRECTORY', '') 183 # The current working directory where Bazel was run from. 184 bazel_working_dir = os.environ.get('BUILD_WORKING_DIRECTORY', '') 185 186 return bool(bazel_source_dir or bazel_working_dir) 187