xref: /aosp_15_r20/external/pigweed/pw_cli/py/pw_cli/env.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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='get_common_excludes',
126    ),
127]
128
129
130_PROJECT_ROOT_ERROR_MESSAGE = '''
131Error: Unable to determine the project root directory. Expected environment
132variables are not set. Either $BUILD_WORKSPACE_DIRECTORY for bazel or
133$PW_PROJECT_ROOT for Pigweed bootstrap are required.
134
135Please re-run with either of these scenarios:
136
137  1. Under bazel with "bazelisk run ..." or "bazel run ..."
138  2. After activating a Pigweed bootstrap environment with ". ./activate.sh"
139'''
140
141
142def project_root(env: envparse.EnvNamespace | None = None) -> Path:
143    """Returns the project root by checking bootstrap and bazel env vars.
144
145    Please do not use this function unless the Python script must escape the
146    bazel sandbox. For example, an interactive tool that operates on the project
147    source code like code formatting.
148    """
149
150    if running_under_bazel():
151        bazel_source_dir = os.environ.get('BUILD_WORKSPACE_DIRECTORY', '')
152
153        # Ensure this function is only callable by functions in the allow list.
154        check_caller_in(_BAZEL_PROJECT_ROOT_ALLOW_LIST)
155
156        root = Path(bazel_source_dir)
157    else:
158        # Running outside bazel (via GN or bootstrap env).
159        if env is None:
160            env = pigweed_environment()
161        root = env.PW_PROJECT_ROOT
162
163    if not root:
164        print(_PROJECT_ROOT_ERROR_MESSAGE, file=sys.stderr)
165        sys.exit(1)
166
167    return root
168
169
170def running_under_bazel() -> bool:
171    """Returns True if any bazel script environment variables are set.
172
173    For more info on which variables are set when running executables in bazel
174    see: https://bazel.build/docs/user-manual#running-executables
175    """
176    # The root of the workspace where the build was run.
177    bazel_source_dir = os.environ.get('BUILD_WORKSPACE_DIRECTORY', '')
178    # The current working directory where Bazel was run from.
179    bazel_working_dir = os.environ.get('BUILD_WORKING_DIRECTORY', '')
180
181    return bool(bazel_source_dir or bazel_working_dir)
182