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.
16
17import logging
18import os
19import shutil
20import subprocess
21import sys
22from distutils import cmd
23from distutils import log
24
25import setuptools
26from setuptools.command.install import install
27
28FRAMEWORK_DIR = 'acts_framework'
29
30acts_tests_dir = os.path.abspath(os.path.dirname(__file__))
31
32install_requires = [
33    # Future needs to have a newer version that contains urllib.
34    'future>=0.16.0',
35    # Latest version of mock (4.0.0b) causes a number of compatibility issues
36    # with ACTS unit tests (b/148695846, b/148814743)
37    'mock==3.0.5',
38    'numpy',
39    'pyserial',
40    'pyyaml>=5.1',
41    'protobuf>=3.11.3',
42    'requests',
43    'scapy',
44    'xlsxwriter',
45    'mobly>=1.10.0',
46]
47
48if sys.version_info < (3, ):
49    install_requires.append('enum34')
50    install_requires.append('statistics')
51    # "futures" is needed for py2 compatibility and it only works in 2.7
52    install_requires.append('futures')
53    install_requires.append('subprocess32')
54
55
56class ActsContribInstall(install):
57    """Custom installation of the acts_contrib package.
58
59    First installs the required ACTS framework via its own setup.py script,
60    before proceeding with the rest of the installation.
61
62    The installation requires the ACTS framework to exist under the
63    "acts_framework" directory.
64    """
65    def run(self):
66        acts_framework_dir = os.path.join(acts_tests_dir, FRAMEWORK_DIR)
67        if not os.path.isdir(acts_framework_dir):
68            logging.error('Cannot install ACTS framework. Framework dir '
69                          '"%s" not found' % acts_framework_dir)
70            exit(1)
71        acts_setup_bin = os.path.join(acts_framework_dir, 'setup.py')
72        if not os.path.isfile(acts_setup_bin):
73            logging.error('Cannot install ACTS framework. Setup script not '
74                          'found.')
75            exit(1)
76        command = [sys.executable, acts_setup_bin, 'install']
77        subprocess.check_call(command, cwd=acts_framework_dir)
78        super().run()
79
80
81class ActsContribInstallDependencies(cmd.Command):
82    """Installs only required packages
83
84    Installs all required packages for acts_contrib to work. Rather than using
85    the normal install system which creates links with the python egg, pip is
86    used to install the packages.
87    """
88
89    description = 'Install dependencies needed for acts_contrib packages.'
90    user_options = []
91
92    def initialize_options(self):
93        pass
94
95    def finalize_options(self):
96        pass
97
98    def run(self):
99        install_args = [sys.executable, '-m', 'pip', 'install']
100        subprocess.check_call(install_args + ['--upgrade', 'pip'])
101        required_packages = self.distribution.install_requires
102
103        for package in required_packages:
104            self.announce('Installing %s...' % package, log.INFO)
105            subprocess.check_call(install_args +
106                                  ['-v', '--no-cache-dir', package])
107
108        self.announce('Dependencies installed.')
109
110
111class ActsContribUninstall(cmd.Command):
112    """acts_contrib uninstaller.
113
114    Uninstalls acts_contrib from the current version of python. This will
115    attempt to import acts_contrib from any of the python egg locations. If it
116    finds an import it will use the modules file location to delete it. This is
117    repeated until acts_contrib can no longer be imported and thus is
118    uninstalled.
119    """
120
121    description = 'Uninstall acts_contrib from the local machine.'
122    user_options = []
123
124    def initialize_options(self):
125        pass
126
127    def finalize_options(self):
128        pass
129
130    def uninstall_acts_contrib_module(self, acts_contrib_module):
131        """Uninstalls acts_contrib from a module.
132
133        Args:
134            acts_contrib_module: The acts_contrib module to uninstall.
135        """
136        for acts_contrib_install_dir in acts_contrib_module.__path__:
137            self.announce('Deleting acts_contrib from: %s'
138                          % acts_contrib_install_dir, log.INFO)
139            shutil.rmtree(acts_contrib_install_dir)
140
141    def run(self):
142        """Entry point for the uninstaller."""
143        # Remove the working directory from the python path. This ensures that
144        # Source code is not accidentally targeted.
145        our_dir = os.path.abspath(os.path.dirname(__file__))
146        if our_dir in sys.path:
147            sys.path.remove(our_dir)
148
149        if os.getcwd() in sys.path:
150            sys.path.remove(os.getcwd())
151
152        try:
153            import acts_contrib as acts_contrib_module
154        except ImportError:
155            self.announce('acts_contrib is not installed, nothing to uninstall.',
156                          level=log.ERROR)
157            return
158
159        while acts_contrib_module:
160            self.uninstall_acts_contrib_module(acts_contrib_module)
161            try:
162                del sys.modules['acts_contrib']
163                import acts_contrib as acts_contrib_module
164            except ImportError:
165                acts_contrib_module = None
166
167        self.announce('Finished uninstalling acts_contrib.')
168
169
170def main():
171    os.chdir(acts_tests_dir)
172    packages = setuptools.find_packages(include=('acts_contrib*',))
173    setuptools.setup(name='acts_contrib',
174                     version='0.9',
175                     description='Android Comms Test Suite',
176                     license='Apache2.0',
177                     packages=packages,
178                     include_package_data=True,
179                     install_requires=install_requires,
180                     cmdclass={
181                         'install': ActsContribInstall,
182                         'install_deps': ActsContribInstallDependencies,
183                         'uninstall': ActsContribUninstall
184                     },
185                     url="http://www.android.com/")
186
187
188if __name__ == '__main__':
189    main()
190