1# Copyright 2019 The Meson development team 2 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6 7# http://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, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import os 16import argparse 17import multiprocessing 18import subprocess 19from pathlib import Path 20import typing as T 21 22from ..mesonlib import Popen_safe, split_args 23 24class ExternalProject: 25 def __init__(self, options: argparse.Namespace): 26 self.name = options.name 27 self.src_dir = options.srcdir 28 self.build_dir = options.builddir 29 self.install_dir = options.installdir 30 self.log_dir = options.logdir 31 self.verbose = options.verbose 32 self.stampfile = options.stampfile 33 self.depfile = options.depfile 34 self.make = split_args(options.make) 35 36 def write_depfile(self) -> None: 37 with open(self.depfile, 'w', encoding='utf-8') as f: 38 f.write(f'{self.stampfile}: \\\n') 39 for dirpath, dirnames, filenames in os.walk(self.src_dir): 40 dirnames[:] = [d for d in dirnames if not d.startswith('.')] 41 for fname in filenames: 42 if fname.startswith('.'): 43 continue 44 path = Path(dirpath, fname) 45 f.write(' {} \\\n'.format(path.as_posix().replace(' ', '\\ '))) 46 47 def write_stampfile(self) -> None: 48 with open(self.stampfile, 'w', encoding='utf-8'): 49 pass 50 51 def gnu_make(self) -> bool: 52 p, o, e = Popen_safe(self.make + ['--version']) 53 if p.returncode == 0 and 'GNU Make' in o: 54 return True 55 return False 56 57 def build(self) -> int: 58 is_make = self.make[0] == 'make' 59 make_cmd = self.make.copy() 60 if is_make and self.gnu_make(): 61 make_cmd.append(f'-j{multiprocessing.cpu_count()}') 62 rc = self._run('build', make_cmd) 63 if rc != 0: 64 return rc 65 66 install_cmd = self.make.copy() 67 install_env = {} 68 if is_make: 69 install_cmd.append(f'DESTDIR={self.install_dir}') 70 else: 71 install_env['DESTDIR'] = self.install_dir 72 install_cmd.append('install') 73 rc = self._run('install', install_cmd, install_env) 74 if rc != 0: 75 return rc 76 77 self.write_depfile() 78 self.write_stampfile() 79 80 return 0 81 82 def _run(self, step: str, command: T.List[str], env: T.Optional[T.Dict[str, str]] = None) -> int: 83 m = 'Running command ' + str(command) + ' in directory ' + str(self.build_dir) + '\n' 84 log_filename = Path(self.log_dir, f'{self.name}-{step}.log') 85 output = None 86 if not self.verbose: 87 output = open(log_filename, 'w', encoding='utf-8') 88 output.write(m + '\n') 89 output.flush() 90 else: 91 print(m) 92 run_env = os.environ.copy() 93 if env: 94 run_env.update(env) 95 p, o, e = Popen_safe(command, stderr=subprocess.STDOUT, stdout=output, 96 cwd=self.build_dir, 97 env=run_env) 98 if p.returncode != 0: 99 m = f'{step} step returned error code {p.returncode}.' 100 if not self.verbose: 101 m += '\nSee logs: ' + str(log_filename) 102 print(m) 103 return p.returncode 104 105def run(args: T.List[str]) -> int: 106 parser = argparse.ArgumentParser() 107 parser.add_argument('--name') 108 parser.add_argument('--srcdir') 109 parser.add_argument('--builddir') 110 parser.add_argument('--installdir') 111 parser.add_argument('--logdir') 112 parser.add_argument('--make') 113 parser.add_argument('--verbose', action='store_true') 114 parser.add_argument('stampfile') 115 parser.add_argument('depfile') 116 117 options = parser.parse_args(args) 118 ep = ExternalProject(options) 119 return ep.build() 120