1# Copyright 2022 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"""Crate a venv.""" 15 16import argparse 17import os 18import pathlib 19import platform 20import shutil 21import stat 22import sys 23import venv 24 25 26def _parse_args() -> argparse.Namespace: 27 parser = argparse.ArgumentParser(description=__doc__) 28 parser.add_argument( 29 '--depfile', 30 type=pathlib.Path, 31 required=True, 32 help='Path at which a depfile should be written.', 33 ) 34 parser.add_argument( 35 '--destination-dir', 36 type=pathlib.Path, 37 required=True, 38 help='Path to venv directory.', 39 ) 40 parser.add_argument( 41 '--stampfile', 42 type=pathlib.Path, 43 required=True, 44 help="Path to this target's stamp file.", 45 ) 46 return parser.parse_args() 47 48 49def _rm_dir(path_to_delete: pathlib.Path) -> None: 50 """Delete a directory recursively. 51 52 On Windows if a file can't be deleted, mark it as writable then delete. 53 """ 54 55 def make_writable_and_delete(_func, path, _exc_info): 56 os.chmod(path, stat.S_IWRITE) 57 os.unlink(path) 58 59 on_rm_error = None 60 if platform.system() == 'Windows': 61 on_rm_error = make_writable_and_delete 62 shutil.rmtree(path_to_delete, onerror=on_rm_error) 63 64 65def main( 66 depfile: pathlib.Path, 67 destination_dir: pathlib.Path, 68 stampfile: pathlib.Path, 69) -> None: 70 # Create the virtualenv. 71 if destination_dir.exists(): 72 _rm_dir(destination_dir) 73 venv.create(destination_dir, symlinks=True, with_pip=True) 74 75 # Write out the depfile, making sure the Python path is 76 # relative to the outdir so that this doesn't add user-specific 77 # info to the build. 78 out_dir_path = pathlib.Path(os.getcwd()).resolve() 79 python_path = pathlib.Path(sys.executable).resolve() 80 rel_python_path = os.path.relpath(python_path, start=out_dir_path) 81 depfile.write_text(f'{stampfile}: \\\n {rel_python_path}') 82 83 84if __name__ == '__main__': 85 main(**vars(_parse_args())) 86