blob: b31dbb4674e3411110d7cf335567c7bf7c4e3aab [file] [log] [blame]
# Copyright 2019 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Environment utility functions.
Usage:
api.environment.initialize(checkout_root=...)
with api.environment():
...
"""
import attr
from recipe_engine import recipe_api
@attr.s
class Package(object):
name = attr.ib(type=bytes)
version = attr.ib(type=bytes)
class EnvironmentApi(recipe_api.RecipeApi):
"""Environment utility functions."""
def __init__(self, *args, **kwargs):
super(EnvironmentApi, self).__init__(*args, **kwargs)
self._cipd_dir = None
self._path_prefixes = []
self._ldpath_prefixes = []
self._env = {}
self._initialized = False
def _process_ensure_path(self, ensure_path, ensure_file):
"""Add packages from ensure_path to ensure_file for later installation."""
ensure_text = self.m.file.read_text(
'read {}'.format(self.m.path.basename(ensure_path)), ensure_path)
packages = []
for line in ensure_text.splitlines():
line = line.strip()
if not line or line[0] in '$#':
continue
packages.append(Package(*line.split()))
for package in packages:
ensure_file.add_package(package.name, package.version)
def _init_cipd(self, checkout_root):
"""Install CIPD packages."""
with self.m.step.nest('setup cipd'):
ensure_paths = [
checkout_root.join('env_setup/cipd/pigweed.ensure'),
# TODO(mohrr) handle multiple files in non-predetermined places
]
ensure_file = self.m.cipd.EnsureFile()
for ensure_path in ensure_paths:
self._process_ensure_path(ensure_path, ensure_file)
self._cipd_dir = self.m.path['start_dir'].join('cipd')
self.m.cipd.max_threads = 0
self.m.cipd.ensure(self._cipd_dir, ensure_file)
self._path_prefixes.append(self._cipd_dir)
self._path_prefixes.append(self._cipd_dir.join('bin'))
self._ldpath_prefixes.append(self._cipd_dir)
self._ldpath_prefixes.append(self._cipd_dir.join('lib'))
self._env['PW_CIPD_INSTALL_DIR'] = self._cipd_dir
self._env['PW_PIGWEED_CIPD_INSTALL_DIR'] = self._cipd_dir
def _all_python_packages(self, checkout_root):
"""Return all folders with setup.py entries.
Note: this function should not be called from within a virtualenv. It screws
with how vpython works.
Args:
checkout_root(Path): root of source tree
Returns:
A list of package paths.
"""
files = self.m.file.listdir('ls **/setup.py', checkout_root, recursive=True)
matches = [
self.m.path.dirname(x)
for x in files
if self.m.path.basename(x) == 'setup.py'
]
with self.m.step.nest('packages') as step:
step.logs['matches'] = [str(x) for x in matches]
return matches
def _init_python(self, checkout_root):
"""Initialize the Python environment. (Specifically, install 'pw'.)"""
with self.m.step.nest('setup python'):
packages = self._all_python_packages(checkout_root)
with self.m.step.nest('setup virtualenv'), self():
venv_dir = self.m.path['start_dir'].join('venv')
venv_bin = venv_dir.join('bin')
python = venv_bin.join('python3')
self.m.step('create venv', ['python3', '-m', 'venv', venv_dir])
self.m.step('upgrade pip',
[python, '-m', 'pip', 'install', '--upgrade', 'pip'])
# Need to insert at beginning because venv python should trump cipd
# python.
self._path_prefixes.insert(0, venv_bin)
self._env['VIRTUAL_ENV'] = venv_dir
# Need to exit and reenter 'with self()' to include new context from
# creating the virtualenv.
with self.m.step.nest('install packages'), self():
pip_install_prefix = (python, '-m', 'pip', 'install')
pw_cmd = list(pip_install_prefix)
for package in packages:
pw_cmd.append('--editable={}'.format(package))
self.m.step('pigweed tools', pw_cmd)
requirements = checkout_root.join(
'env_setup/virtualenv/requirements.txt')
req_cmd = list(pip_install_prefix)
req_cmd.extend(('-r', requirements))
self.m.step('build requirements', req_cmd)
def init(self, checkout_root):
if not self._initialized:
with self.m.step.nest('environment'):
# Setting _initialized immediately because some setup steps need to
# use the context of previous steps, and invoking self() is the
# easiest way to do so.
self._initialized = True
self._env['PW_ROOT'] = checkout_root
self._init_cipd(checkout_root)
self._init_python(checkout_root)
def __call__(self):
assert self._initialized
env_prefixes = {}
if self._path_prefixes:
env_prefixes['PATH'] = self._path_prefixes
if self._ldpath_prefixes:
env_prefixes['LD_LIBRARY_PATH'] = self._ldpath_prefixes
return self.m.context(env_prefixes=env_prefixes, env=self._env)