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 os
18import shutil
19import subprocess
20import sys
21from distutils import cmd
22from distutils import log
23
24import setuptools
25from setuptools.command.develop import develop
26from setuptools.command.install import install
27
28FRAMEWORK_DIR = 'acts_framework'
29LOCAL_FRAMEWORK_DIR = '../acts/framework'
30
31acts_tests_dir = os.path.abspath(os.path.dirname(__file__))
32
33install_requires = [
34    # Require an older version of setuptools that does not enforce PEP 440.
35    # This must be added first.
36    'setuptools<66.0.0',
37    'soundfile'
38]
39
40if sys.version_info < (3, 6):
41    # Python <= 3.5 uses bokeh up to 1.4.x
42    install_requires.append('bokeh<1.5')
43elif sys.version_info < (3, 7):
44    # Python 3.6 uses bokeh up to 2.3.x
45    install_requires.append('bokeh<2.4')
46elif sys.version_info < (3, 8):
47    # Python 3.7+ uses bokeh up to 2.4.x
48    install_requires.append('bokeh<2.5')
49else:
50    # Python 3.8+ is support by latest bokeh
51    install_requires.append('bokeh')
52
53def _setup_acts_framework(option, *args):
54    """Locates and runs setup.py for the ACTS framework.
55
56    Args:
57        option: the option to use with setup.py, e.g. install, develop
58        args: additional args for the command
59    """
60    acts_framework_dir = os.path.join(acts_tests_dir, FRAMEWORK_DIR)
61    if not os.path.isdir(acts_framework_dir):
62        log.info('Directory "%s" not found. Attempting to locate ACTS '
63                 'framework through local repository.' % acts_framework_dir)
64        acts_framework_dir = os.path.join(acts_tests_dir, LOCAL_FRAMEWORK_DIR)
65        if not os.path.isdir(acts_framework_dir):
66            log.error('Cannot install ACTS framework. Framework dir "%s" not '
67                      'found.' % acts_framework_dir)
68            exit(1)
69    acts_setup_bin = os.path.join(acts_framework_dir, 'setup.py')
70    if not os.path.isfile(acts_setup_bin):
71        log.error('Cannot install ACTS framework. Setup script not found.')
72        exit(1)
73    command = [sys.executable, acts_setup_bin, option, *args]
74    subprocess.check_call(command, cwd=acts_framework_dir)
75
76
77class ActsContribInstall(install):
78    """Custom installation of the acts_contrib package.
79
80    Also installs the required ACTS framework via its own setup.py script.
81
82    The installation requires the ACTS framework to exist under the
83    "acts_framework" directory, at the same level of this setup script.
84    Otherwise, it will attempt to locate the ACTS framework from the local
85    repository.
86    """
87    def do_egg_install(self):
88        # Ref. https://stackoverflow.com/a/20196065
89        _setup_acts_framework('install')
90        install.do_egg_install(self)
91
92
93class ActsContribDevelop(develop):
94    """Custom installation of the acts_contrib package (in develop mode).
95
96    See ActsContribInstall for more details.
97    """
98
99    def run(self):
100        super().run()
101        if self.uninstall:
102            _setup_acts_framework('develop', '-u')
103        else:
104            _setup_acts_framework('develop')
105
106
107class ActsContribUninstall(cmd.Command):
108    """acts_contrib uninstaller.
109
110    Uninstalls acts_contrib from the current version of python. This will
111    attempt to import acts_contrib from any of the python egg locations. If it
112    finds an import it will use the modules file location to delete it. This is
113    repeated until acts_contrib can no longer be imported and thus is
114    uninstalled.
115    """
116
117    description = 'Uninstall acts_contrib from the local machine.'
118    user_options = []
119
120    def initialize_options(self):
121        pass
122
123    def finalize_options(self):
124        pass
125
126    def uninstall_acts_contrib_module(self, acts_contrib_module):
127        """Uninstalls acts_contrib from a module.
128
129        Args:
130            acts_contrib_module: The acts_contrib module to uninstall.
131        """
132        for acts_contrib_install_dir in acts_contrib_module.__path__:
133            self.announce(
134                'Deleting acts_contrib from: %s' % acts_contrib_install_dir,
135                log.INFO)
136            shutil.rmtree(acts_contrib_install_dir)
137
138    def run(self):
139        """Entry point for the uninstaller."""
140        # Remove the working directory from the python path. This ensures that
141        # Source code is not accidentally targeted.
142        our_dir = os.path.abspath(os.path.dirname(__file__))
143        if our_dir in sys.path:
144            sys.path.remove(our_dir)
145
146        if os.getcwd() in sys.path:
147            sys.path.remove(os.getcwd())
148
149        try:
150            import acts_contrib as acts_contrib_module
151        except ImportError:
152            self.announce(
153                'acts_contrib is not installed, nothing to uninstall.',
154                level=log.ERROR)
155            return
156
157        while acts_contrib_module:
158            self.uninstall_acts_contrib_module(acts_contrib_module)
159            try:
160                del sys.modules['acts_contrib']
161                import acts_contrib as acts_contrib_module
162            except ImportError:
163                acts_contrib_module = None
164
165        self.announce('Finished uninstalling acts_contrib.')
166
167        # Uninstall the ACTS framework
168        _setup_acts_framework('uninstall')
169
170
171def main():
172    os.chdir(acts_tests_dir)
173    packages = setuptools.find_packages(include=('acts_contrib*', ))
174    setuptools.setup(name='acts_contrib',
175                     version='0.9',
176                     description='Android Comms Test Suite',
177                     license='Apache2.0',
178                     packages=packages,
179                     include_package_data=True,
180                     install_requires=install_requires,
181                     cmdclass={
182                         'install': ActsContribInstall,
183                         'develop': ActsContribDevelop,
184                         'uninstall': ActsContribUninstall
185                     },
186                     url="http://www.android.com/")
187
188
189if __name__ == '__main__':
190    main()
191