1#!/usr/bin/env python3 2# 3# Copyright 2017 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16from distutils import cmd 17from distutils import log 18import subprocess 19import os 20import shutil 21import setuptools 22from setuptools.command import test 23import sys 24 25install_requires = [ 26 'backoff', 27 # Future needs to have a newer version that contains urllib. 28 'future>=0.16.0', 29 # Latest version of mock (4.0.0b) causes a number of compatibility issues with ACTS unit tests 30 # b/148695846, b/148814743 31 'mock==3.0.5', 32 # b/157117302: python3.5 is not supported by NumPy 1.19+ 33 'numpy<=1.18.1', 34 # b/157117302: python3.5 is not supported by SciPy 1.5.0+ (Monsoon dependency) 35 'scipy==1.4.1', 36 'pyserial', 37 'pyyaml>=5.1', 38 'protobuf>=3.11.3', 39 'retry', 40 'requests', 41 'scapy', 42 'pylibftdi', 43 'xlsxwriter', 44 'mobly>=1.10.0', 45 'grpcio', 46 'Monsoon', 47 # paramiko-ng is needed vs paramiko as currently paramiko does not support 48 # ed25519 ssh keys, which is what Fuchsia uses. 49 'paramiko-ng', 50 'dlipower' 51] 52 53if sys.version_info < (3, ): 54 install_requires.append('enum34') 55 install_requires.append('statistics') 56 # "futures" is needed for py2 compatibility and it only works in 2.7 57 install_requires.append('futures') 58 install_requires.append('py2-ipaddress') 59 install_requires.append('subprocess32') 60 61DEV_PACKAGES = [ 62 'shiv' 63] 64 65framework_dir = os.path.dirname(os.path.realpath(__file__)) 66 67 68class PyTest(test.test): 69 """Class used to execute unit tests using PyTest. This allows us to execute 70 unit tests without having to install the package. 71 """ 72 def finalize_options(self): 73 test.test.finalize_options(self) 74 self.test_args = ['-x', "tests"] 75 self.test_suite = True 76 77 def run_tests(self): 78 test_path = os.path.join(framework_dir, '../tests/meta/ActsUnitTest.py') 79 result = subprocess.Popen('python3 %s' % test_path, 80 stdout=sys.stdout, 81 stderr=sys.stderr, 82 shell=True) 83 result.communicate() 84 sys.exit(result.returncode) 85 86 87class ActsInstallDependencies(cmd.Command): 88 """Installs only required packages 89 90 Installs all required packages for acts to work. Rather than using the 91 normal install system which creates links with the python egg, pip is 92 used to install the packages. 93 """ 94 95 description = 'Install dependencies needed for acts to run on this machine.' 96 user_options = [] 97 98 def initialize_options(self): 99 pass 100 101 def finalize_options(self): 102 pass 103 104 def run(self): 105 install_args = [sys.executable, '-m', 'pip', 'install'] 106 subprocess.check_call(install_args + ['--upgrade', 'pip']) 107 required_packages = self.distribution.install_requires 108 109 for package in required_packages: 110 self.announce('Installing %s...' % package, log.INFO) 111 subprocess.check_call(install_args + 112 ['-v', '--no-cache-dir', package]) 113 114 self.announce('Dependencies installed.') 115 116 117class ActsUninstall(cmd.Command): 118 """Acts uninstaller. 119 120 Uninstalls acts from the current version of python. This will attempt to 121 import acts from any of the python egg locations. If it finds an import 122 it will use the modules file location to delete it. This is repeated until 123 acts can no longer be imported and thus is uninstalled. 124 """ 125 126 description = 'Uninstall acts from the local machine.' 127 user_options = [] 128 129 def initialize_options(self): 130 pass 131 132 def finalize_options(self): 133 pass 134 135 def uninstall_acts_module(self, acts_module): 136 """Uninstalls acts from a module. 137 138 Args: 139 acts_module: The acts module to uninstall. 140 """ 141 for acts_install_dir in acts_module.__path__: 142 self.announce('Deleting acts from: %s' % acts_install_dir, 143 log.INFO) 144 shutil.rmtree(acts_install_dir) 145 146 def run(self): 147 """Entry point for the uninstaller.""" 148 # Remove the working directory from the python path. This ensures that 149 # Source code is not accidentally targeted. 150 if framework_dir in sys.path: 151 sys.path.remove(framework_dir) 152 153 if os.getcwd() in sys.path: 154 sys.path.remove(os.getcwd()) 155 156 try: 157 import acts as acts_module 158 except ImportError: 159 self.announce('Acts is not installed, nothing to uninstall.', 160 level=log.ERROR) 161 return 162 163 while acts_module: 164 self.uninstall_acts_module(acts_module) 165 try: 166 del sys.modules['acts'] 167 import acts as acts_module 168 except ImportError: 169 acts_module = None 170 171 self.announce('Finished uninstalling acts.') 172 173 174def main(): 175 scripts = [ 176 os.path.join('acts', 'bin', 'act.py'), 177 os.path.join('acts', 'bin', 'monsoon.py') 178 ] 179 # cd to framework directory so the correct package namespace is found 180 os.chdir(framework_dir) 181 setuptools.setup(name='acts', 182 version='0.9', 183 description='Android Comms Test Suite', 184 license='Apache2.0', 185 packages=setuptools.find_packages(), 186 include_package_data=True, 187 tests_require=['pytest'], 188 install_requires=install_requires, 189 extras_require={'dev': DEV_PACKAGES}, 190 scripts=scripts, 191 cmdclass={ 192 'test': PyTest, 193 'install_deps': ActsInstallDependencies, 194 'uninstall': ActsUninstall 195 }, 196 url="http://www.android.com/") 197 198 if {'-u', '--uninstall', 'uninstall'}.intersection(sys.argv): 199 installed_scripts = [ 200 '/usr/local/bin/act.py', '/usr/local/bin/monsoon.py' 201 ] 202 for act_file in installed_scripts: 203 if os.path.islink(act_file): 204 os.unlink(act_file) 205 elif os.path.exists(act_file): 206 os.remove(act_file) 207 208 209if __name__ == '__main__': 210 main() 211