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    # Require an older version of setuptools that does not enforce PEP 440.
27    # This must be added first.
28    'setuptools<66.0.0',
29    # Future needs to have a newer version that contains urllib.
30    'future>=0.16.0',
31    'mobly==1.12.0',
32    # Latest version of mock (4.0.0b) causes a number of compatibility issues with ACTS unit tests
33    # b/148695846, b/148814743
34    'mock==3.0.5',
35    'Monsoon',
36    'paramiko[ed25519]',
37    'pylibftdi',
38    'pyserial',
39    'pyyaml>=5.1',
40    'requests',
41    'retry',
42    'scapy',
43    'scp',
44    'usbinfo',
45    'zeroconf'
46]
47
48versioned_deps = {
49    'backoff': 'backoff',
50    'numpy': 'numpy',
51    'scipy': 'scipy',
52    'protobuf': 'protobuf==4.21.5',
53    'grpcio': 'grpcio',
54}
55
56# numpy and scipy version matrix per:
57# https://docs.scipy.org/doc/scipy/dev/toolchain.html
58if sys.version_info < (3, 11):
59    versioned_deps['numpy'] = 'numpy<2.0.0b1'
60if sys.version_info < (3, 9):
61    versioned_deps['scipy'] = 'scipy<1.11'
62if sys.version_info < (3, 8):
63    versioned_deps['numpy'] = 'numpy<1.22'
64    versioned_deps['scipy'] = 'scipy<1.8'
65if sys.version_info < (3, 7):
66    versioned_deps['backoff'] = 'backoff<2.0'
67    versioned_deps['numpy'] = 'numpy<1.20'
68    versioned_deps['scipy'] = 'scipy<1.6'
69    versioned_deps['protobuf'] = 'protobuf==3.20.1'
70    versioned_deps['grpcio'] = 'grpcio==1.48.2'
71    versioned_deps['typing_extensions'] = 'typing_extensions==4.1.1'
72    versioned_deps['cryptography'] = 'cryptography<41.0'
73if (sys.version_info.major, sys.version_info.minor) == (3, 6):
74    versioned_deps['dataclasses'] = 'dataclasses==0.8'
75if sys.version_info < (3, 6):
76    versioned_deps['numpy'] = 'numpy<1.19'
77    versioned_deps['scipy'] = 'scipy<1.5'
78    versioned_deps['typing_extensions'] = 'typing_extensions<4.0.0'
79
80install_requires += list(versioned_deps.values())
81
82if sys.version_info < (3, ):
83    install_requires.append('enum34')
84    install_requires.append('statistics')
85    # "futures" is needed for py2 compatibility and it only works in 2.7
86    install_requires.append('futures')
87    install_requires.append('py2-ipaddress')
88    install_requires.append('subprocess32')
89
90DEV_PACKAGES = ['shiv']
91
92framework_dir = os.path.dirname(os.path.realpath(__file__))
93
94
95class PyTest(test.test):
96    """Class used to execute unit tests using PyTest. This allows us to execute
97    unit tests without having to install the package.
98    """
99
100    def finalize_options(self):
101        test.test.finalize_options(self)
102        self.test_args = ['-x', "tests"]
103        self.test_suite = True
104
105    def run_tests(self):
106        test_path = os.path.join(framework_dir,
107                                 '../tests/meta/ActsUnitTest.py')
108        result = subprocess.Popen('python3 %s' % test_path,
109                                  stdout=sys.stdout,
110                                  stderr=sys.stderr,
111                                  shell=True)
112        result.communicate()
113        sys.exit(result.returncode)
114
115
116class ActsUninstall(cmd.Command):
117    """Acts uninstaller.
118
119    Uninstalls acts from the current version of python. This will attempt to
120    import acts from any of the python egg locations. If it finds an import
121    it will use the modules file location to delete it. This is repeated until
122    acts can no longer be imported and thus is uninstalled.
123    """
124
125    description = 'Uninstall acts from the local machine.'
126    user_options = []
127
128    def initialize_options(self):
129        pass
130
131    def finalize_options(self):
132        pass
133
134    def uninstall_acts_module(self, acts_module):
135        """Uninstalls acts from a module.
136
137        Args:
138            acts_module: The acts module to uninstall.
139        """
140        for acts_install_dir in acts_module.__path__:
141            self.announce('Deleting acts from: %s' % acts_install_dir,
142                          log.INFO)
143            shutil.rmtree(acts_install_dir)
144
145    def run(self):
146        """Entry point for the uninstaller."""
147        # Remove the working directory from the python path. This ensures that
148        # Source code is not accidentally targeted.
149        if framework_dir in sys.path:
150            sys.path.remove(framework_dir)
151
152        if os.getcwd() in sys.path:
153            sys.path.remove(os.getcwd())
154
155        try:
156            import acts as acts_module
157        except ImportError:
158            self.announce('Acts is not installed, nothing to uninstall.',
159                          level=log.ERROR)
160            return
161
162        while acts_module:
163            self.uninstall_acts_module(acts_module)
164            try:
165                del sys.modules['acts']
166                import acts as acts_module
167            except ImportError:
168                acts_module = None
169
170        self.announce('Finished uninstalling acts.')
171
172
173def main():
174    scripts = [
175        os.path.join('acts', 'bin', 'act.py'),
176        os.path.join('acts', 'bin', 'monsoon.py')
177    ]
178    # cd to framework directory so the correct package namespace is found
179    os.chdir(framework_dir)
180    setuptools.setup(name='acts',
181                     version='0.9',
182                     description='Android Comms Test Suite',
183                     license='Apache2.0',
184                     packages=setuptools.find_packages(),
185                     include_package_data=True,
186                     tests_require=['pytest'],
187                     install_requires=install_requires,
188                     extras_require={
189                         'dev': DEV_PACKAGES,
190                         'digital_loggers_pdu': ['dlipower'],
191                     },
192                     scripts=scripts,
193                     cmdclass={
194                         'test': PyTest,
195                         'uninstall': ActsUninstall
196                     },
197                     url="http://www.android.com/")
198
199    if {'-u', '--uninstall', 'uninstall'}.intersection(sys.argv):
200        installed_scripts = [
201            '/usr/local/bin/act.py', '/usr/local/bin/monsoon.py'
202        ]
203        for act_file in installed_scripts:
204            if os.path.islink(act_file):
205                os.unlink(act_file)
206            elif os.path.exists(act_file):
207                os.remove(act_file)
208
209
210if __name__ == '__main__':
211    main()
212