1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4# Copyright (c) 2016 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
5#
6# This is the install script for the eric debug client. It may be used
7# to just install the debug clients for remote debugging.
8#
9
10"""
11Installation script for the eric debug clients.
12"""
13
14import io
15import sys
16import os
17import re
18import compileall
19import shutil
20import fnmatch
21import contextlib
22
23# Define the globals.
24progName = None
25currDir = os.getcwd()
26modDir = None
27pyModDir = None
28distDir = None
29installPackage = "eric6DebugClients"
30doCleanup = True
31doCompile = True
32sourceDir = "eric"
33eric6SourceDir = os.path.join(sourceDir, "eric6")
34
35
36def exit(rcode=0):
37    """
38    Exit the install script.
39
40    @param rcode result code to report back (integer)
41    """
42    global currDir
43
44    if sys.platform.startswith("win"):
45        with contextlib.suppress():
46            input("Press enter to continue...")         # secok
47
48    os.chdir(currDir)
49
50    sys.exit(rcode)
51
52
53def usage(rcode=2):
54    """
55    Display a usage message and exit.
56
57    @param rcode the return code passed back to the calling process.
58    """
59    global progName, modDir, distDir
60
61    print()
62    print("Usage:")
63    if sys.platform == "darwin":
64        print("    {0} [-chz] [-d dir] [-i dir]".format(progName))
65    elif sys.platform.startswith("win"):
66        print("    {0} [-chz] [-d dir]".format(progName))
67    else:
68        print("    {0} [-chz][-d dir] [-i dir]".format(progName))
69    print("where:")
70    print("    -h, --help display this help message")
71    print("    -d dir     where eric debug client files will be installed")
72    print("               (default: {0})".format(modDir))
73    if not sys.platform.startswith("win"):
74        print("    -i dir     temporary install prefix")
75        print("               (default: {0})".format(distDir))
76    print("    -c         don't cleanup old installation first")
77    print("    -z         don't compile the installed python files")
78
79    exit(rcode)
80
81
82def initGlobals():
83    """
84    Module function to set the values of globals that need more than a
85    simple assignment.
86    """
87    global modDir, pyModDir
88
89    try:
90        import distutils.sysconfig
91    except ImportError:
92        print("Please install the 'distutils' package first.")
93        exit(5)
94
95    modDir = distutils.sysconfig.get_python_lib(True)
96    pyModDir = modDir
97
98
99def copyTree(src, dst, filters, excludeDirs=None, excludePatterns=None):
100    """
101    Copy files of a directory tree.
102
103    @param src name of the source directory
104    @param dst name of the destination directory
105    @param filters list of filter pattern determining the files to be copied
106    @param excludeDirs list of (sub)directories to exclude from copying
107    @param excludePatterns list of filter pattern determining the files to
108        be skipped
109    """
110    if excludeDirs is None:
111        excludeDirs = []
112    if excludePatterns is None:
113        excludePatterns = []
114    try:
115        names = os.listdir(src)
116    except OSError:
117        # ignore missing directories
118        return
119
120    for name in names:
121        skipIt = False
122        for excludePattern in excludePatterns:
123            if fnmatch.fnmatch(name, excludePattern):
124                skipIt = True
125                break
126        if not skipIt:
127            srcname = os.path.join(src, name)
128            dstname = os.path.join(dst, name)
129            for fileFilter in filters:
130                if fnmatch.fnmatch(srcname, fileFilter):
131                    if not os.path.isdir(dst):
132                        os.makedirs(dst)
133                    shutil.copy2(srcname, dstname)
134                    os.chmod(dstname, 0o644)
135                    break
136            else:
137                if os.path.isdir(srcname) and srcname not in excludeDirs:
138                    copyTree(srcname, dstname, filters,
139                             excludePatterns=excludePatterns)
140
141
142def cleanupSource(dirName):
143    """
144    Cleanup the sources directory to get rid of leftover files
145    and directories.
146
147    @param dirName name of the directory to prune (string)
148    """
149    # step 1: delete the __pycache__ directory and all *.pyc files
150    if os.path.exists(os.path.join(dirName, "__pycache__")):
151        shutil.rmtree(os.path.join(dirName, "__pycache__"))
152    for name in [f for f in os.listdir(dirName)
153                 if fnmatch.fnmatch(f, "*.pyc")]:
154        os.remove(os.path.join(dirName, name))
155
156    # step 2: descent into subdirectories and delete them if empty
157    for name in os.listdir(dirName):
158        name = os.path.join(dirName, name)
159        if os.path.isdir(name):
160            cleanupSource(name)
161            if len(os.listdir(name)) == 0:
162                os.rmdir(name)
163
164
165def cleanUp():
166    """
167    Uninstall the old eric debug client files.
168    """
169    global pyModDir
170
171    try:
172        # Cleanup the install directories
173        dirname = os.path.join(pyModDir, installPackage)
174        if os.path.exists(dirname):
175            shutil.rmtree(dirname, True)
176    except OSError as msg:
177        sys.stderr.write(
178            'Error: {0}\nTry install with admin rights.\n'.format(msg))
179        exit(7)
180
181
182def shutilCopy(src, dst, perm=0o644):
183    """
184    Wrapper function around shutil.copy() to ensure the permissions.
185
186    @param src source file name (string)
187    @param dst destination file name or directory name (string)
188    @param perm permissions to be set (integer)
189    """
190    shutil.copy(src, dst)
191    if os.path.isdir(dst):
192        dst = os.path.join(dst, os.path.basename(src))
193    os.chmod(dst, perm)
194
195
196def installEricDebugClients():
197    """
198    Actually perform the installation steps.
199
200    @return result code (integer)
201    """
202    global distDir, doCleanup, sourceDir, modDir
203
204    # set install prefix, if not None
205    targetDir = (
206        os.path.normpath(os.path.join(distDir, installPackage))
207        if distDir else
208        os.path.join(modDir, installPackage)
209    )
210
211    try:
212        # Install the files
213        # copy the various parts of eric debug clients
214        copyTree(
215            os.path.join(eric6SourceDir, "DebugClients"), targetDir,
216            ['*.py', '*.pyc', '*.pyo', '*.pyw'],
217            [os.path.join(sourceDir, ".ropeproject")],
218            excludePatterns=["eric6config.py*"])
219
220        # copy the license file
221        shutilCopy(os.path.join(sourceDir, "docs", "LICENSE.GPL3"), targetDir)
222
223    except OSError as msg:
224        sys.stderr.write(
225            'Error: {0}\nTry install with admin rights.\n'.format(msg))
226        return(7)
227
228    return 0
229
230
231def main(argv):
232    """
233    The main function of the script.
234
235    @param argv the list of command line arguments.
236    """
237    import getopt
238
239    # Parse the command line.
240    global progName, modDir, doCleanup, doCompile, distDir
241    global sourceDir
242
243    if sys.version_info < (3, 6, 0) or sys.version_info >= (4, 0, 0):
244        print('Sorry, the eric debugger requires Python 3.6 or better'
245              ' for running.')
246        exit(5)
247
248    progName = os.path.basename(argv[0])
249
250    if os.path.dirname(argv[0]):
251        os.chdir(os.path.dirname(argv[0]))
252
253    initGlobals()
254
255    try:
256        if sys.platform.startswith("win"):
257            optlist, args = getopt.getopt(
258                argv[1:], "chzd:", ["help"])
259        elif sys.platform == "darwin":
260            optlist, args = getopt.getopt(
261                argv[1:], "chzd:i:", ["help"])
262        else:
263            optlist, args = getopt.getopt(
264                argv[1:], "chzd:i:", ["help"])
265    except getopt.GetoptError as err:
266        print(err)
267        usage()
268
269    for opt, arg in optlist:
270        if opt in ["-h", "--help"]:
271            usage(0)
272        elif opt == "-d":
273            modDir = arg
274        elif opt == "-i":
275            distDir = os.path.normpath(arg)
276        elif opt == "-c":
277            doCleanup = False
278        elif opt == "-z":
279            doCompile = False
280
281    installFromSource = not os.path.isdir(sourceDir)
282    if installFromSource:
283        sourceDir = os.path.abspath("..")
284        eric6SourceDir = os.path.join(sourceDir, "eric6")
285
286    # cleanup source if installing from source
287    if installFromSource:
288        print("Cleaning up source ...")
289        cleanupSource(os.path.join(eric6SourceDir, "DebugClients"))
290        print()
291
292    # cleanup old installation
293    try:
294        if doCleanup:
295            print("Cleaning up old installation ...")
296            if distDir:
297                shutil.rmtree(distDir, True)
298            else:
299                cleanUp()
300    except OSError as msg:
301        sys.stderr.write('Error: {0}\nTry install as root.\n'.format(msg))
302        exit(7)
303
304    if doCompile:
305        print("\nCompiling source files ...")
306        skipRe = re.compile(r"DebugClients[\\/]Python[\\/]")
307        sys.stdout = io.StringIO()
308        if distDir:
309            compileall.compile_dir(
310                os.path.join(eric6SourceDir, "DebugClients"),
311                ddir=os.path.join(distDir, modDir, installPackage),
312                rx=skipRe,
313                quiet=True)
314        else:
315            compileall.compile_dir(
316                os.path.join(eric6SourceDir, "DebugClients"),
317                ddir=os.path.join(modDir, installPackage),
318                rx=skipRe,
319                quiet=True)
320        sys.stdout = sys.__stdout__
321    print("\nInstalling eric debug clients ...")
322    res = installEricDebugClients()
323
324    print("\nInstallation complete.")
325    print()
326
327    exit(res)
328
329
330if __name__ == "__main__":
331    try:
332        main(sys.argv)
333    except SystemExit:
334        raise
335    except Exception:
336        print("""An internal error occured.  Please report all the output"""
337              """ of the program,\nincluding the following traceback, to"""
338              """ eric-bugs@eric-ide.python-projects.org.\n""")
339        raise
340
341#
342# eflag: noqa = M801
343