1#!/usr/bin/env python3.4
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
17from distutils import cmd
18from distutils import log
19import os
20import shutil
21import setuptools
22from setuptools.command import test
23import sys
24
25install_requires = [
26    # Future needs to have a newer version that contains urllib.
27    'future>=0.16.0',
28    # mock-1.0.1 is the last version compatible with setuptools <17.1,
29    # which is what comes with Ubuntu 14.04 LTS.
30    'mock<=1.0.1',
31    'numpy',
32    'pyserial',
33    'shellescape',
34    'protobuf',
35    'roman',
36    'scapy-python3',
37    'pylibftdi',
38    'xlsxwriter'
39]
40
41if sys.version_info < (3, ):
42    install_requires.append('enum34')
43    install_requires.append('statistics')
44    # "futures" is needed for py2 compatibility and it only works in 2.7
45    install_requires.append('futures')
46    install_requires.append('py2-ipaddress')
47    install_requires.append('subprocess32')
48
49
50class PyTest(test.test):
51    """Class used to execute unit tests using PyTest. This allows us to execute
52    unit tests without having to install the package.
53    """
54
55    def finalize_options(self):
56        test.test.finalize_options(self)
57        self.test_args = ['-x', "tests"]
58        self.test_suite = True
59
60    def run_tests(self):
61        import pytest
62        errno = pytest.main(self.test_args)
63        sys.exit(errno)
64
65
66class ActsInstallDependencies(cmd.Command):
67    """Installs only required packages
68
69    Installs all required packages for acts to work. Rather than using the
70    normal install system which creates links with the python egg, pip is
71    used to install the packages.
72    """
73
74    description = 'Install dependencies needed for acts to run on this machine.'
75    user_options = []
76
77    def initialize_options(self):
78        pass
79
80    def finalize_options(self):
81        pass
82
83    def run(self):
84        import pip
85        pip.main(['install', '--upgrade', 'pip'])
86        required_packages = self.distribution.install_requires
87
88        for package in required_packages:
89            self.announce('Installing %s...' % package, log.INFO)
90            pip.main(['install', '-v', '--no-cache-dir', package])
91
92        self.announce('Dependencies installed.')
93
94
95class ActsUninstall(cmd.Command):
96    """Acts uninstaller.
97
98    Uninstalls acts from the current version of python. This will attempt to
99    import acts from any of the python egg locations. If it finds an import
100    it will use the modules file location to delete it. This is repeated until
101    acts can no longer be imported and thus is uninstalled.
102    """
103
104    description = 'Uninstall acts from the local machine.'
105    user_options = []
106
107    def initialize_options(self):
108        pass
109
110    def finalize_options(self):
111        pass
112
113    def uninstall_acts_module(self, acts_module):
114        """Uninstalls acts from a module.
115
116        Args:
117            acts_module: The acts module to uninstall.
118        """
119        for acts_install_dir in acts_module.__path__:
120            self.announce('Deleting acts from: %s' % acts_install_dir,
121                          log.INFO)
122            shutil.rmtree(acts_install_dir)
123
124    def run(self):
125        """Entry point for the uninstaller."""
126        # Remove the working directory from the python path. This ensures that
127        # Source code is not accidentally targeted.
128        our_dir = os.path.abspath(os.path.dirname(__file__))
129        if our_dir in sys.path:
130            sys.path.remove(our_dir)
131
132        if os.getcwd() in sys.path:
133            sys.path.remove(os.getcwd())
134
135        try:
136            import acts as acts_module
137        except ImportError:
138            self.announce(
139                'Acts is not installed, nothing to uninstall.',
140                level=log.ERROR)
141            return
142
143        while acts_module:
144            self.uninstall_acts_module(acts_module)
145            try:
146                del sys.modules['acts']
147                import acts as acts_module
148            except ImportError:
149                acts_module = None
150
151        self.announce('Finished uninstalling acts.')
152
153
154def main():
155    setuptools.setup(
156        name='acts',
157        version='0.9',
158        description='Android Comms Test Suite',
159        license='Apache2.0',
160        packages=setuptools.find_packages(),
161        include_package_data=False,
162        tests_require=['pytest'],
163        install_requires=install_requires,
164        scripts=['acts/bin/act.py', 'acts/bin/monsoon.py'],
165        cmdclass={
166            'test': PyTest,
167            'install_deps': ActsInstallDependencies,
168            'uninstall': ActsUninstall
169        },
170        url="http://www.android.com/")
171
172    if {'-u', '--uninstall', 'uninstall'}.intersection(sys.argv):
173        act_path = '/usr/local/bin/act.py'
174        if os.path.islink(act_path):
175            os.unlink(act_path)
176        elif os.path.exists(act_path):
177            os.remove(act_path)
178
179
180if __name__ == '__main__':
181    main()
182