1"""
2Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3See https://llvm.org/LICENSE.txt for license information.
4SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5
6Sync lldb and related source from a local machine to a remote machine.
7
8This facilitates working on the lldb sourcecode on multiple machines
9and multiple OS types, verifying changes across all.
10
11Provides helper support for adding lldb test paths to the python path.
12"""
13
14from __future__ import print_function
15from __future__ import absolute_import
16
17# System modules
18import os
19import platform
20import subprocess
21import sys
22
23# Third-party modules
24
25# LLDB modules
26
27
28def add_lldb_test_paths(check_dir):
29    # pylint: disable=line-too-long
30    """Adds lldb test-related paths to the python path.
31
32    Starting with the given directory and working upward through
33    each parent directory up to the root, it looks for the lldb
34    test directory.  When found, the lldb test directory and its
35    child test_runner/lib directory will be added to the python
36    system path.
37
38    Instructions for use:
39
40    This method supports a simple way of getting pylint to be able
41    to reliably lint lldb python test scripts (including the test
42    infrastructure itself).  To do so, add the following to a
43    .pylintrc file in your home directory:
44
45    [Master]
46    init-hook='import os; import sys; sys.path.append(os.path.expanduser("~/path/to/lldb/packages/Python/lldbsuite/test")); import lldb_pylint_helper; lldb_pylint_helper.add_lldb_test_paths(os.getcwd()); print("sys.path={}\n".format(sys.path))'
47
48    Replace ~/path/to/lldb with a valid path to your local lldb source
49    tree.  Note you can have multiple lldb source trees on your system, and
50    this will work just fine.  The path in your .pylintrc is just needed to
51    find the paths needed for pylint in whatever lldb source tree you're in.
52    pylint will use the python files in whichever tree it is run from.
53
54    Note it is critical that the init-hook line be contained on a single line.
55    You can remove the print line at the end once you know the pythonpath is
56    getting set up the way you expect.
57
58    With these changes, you will be able to run the following, for example.
59
60    cd lldb/sourcetree/1-of-many/test/lang/c/anonymous
61    pylint TestAnonymous.py
62
63    This will work, and include all the lldb/sourcetree/1-of-many lldb-specific
64    python directories to your path.
65
66    You can then run it in another lldb source tree on the same machine like
67    so:
68
69    cd lldb/sourcetree/2-of-many/test/functionalities/inferior-assert
70    pyline TestInferiorAssert.py
71
72    and this will properly lint that file, using the lldb-specific python
73    directories from the 2-of-many source tree.
74
75    Note at the time I'm writing this, our tests are in pretty sad shape
76    as far as a stock pylint setup goes.  But we need to start somewhere :-)
77
78    @param check_dir specifies a directory that will be used to start
79    looking for the lldb test infrastructure python library paths.
80    """
81    # Add the test-related packages themselves.
82    add_lldb_test_package_paths(check_dir)
83
84    # Add the lldb directory itself
85    add_lldb_module_directory()
86
87
88def add_lldb_module_directory():
89    """
90    Desired Approach:
91
92    Part A: find an lldb
93
94    1. Walk up the parent chain from the current directory, looking for
95    a directory matching *build*.  If we find that, use it as the
96    root of a directory search for an lldb[.exe] executable.
97
98    2. If 1 fails, use the path and look for an lldb[.exe] in there.
99
100    If Part A ends up with an lldb, go to part B.  Otherwise, give up
101    on the lldb python module path.
102
103    Part B: use the output from 'lldb[.exe] -P' to find the lldb dir.
104
105    Current approach:
106    If Darwin, use 'xcrun lldb -P'; others: find lldb on path.
107
108    Drawback to current approach:
109    If the tester is changing the SB API (adding new methods), pylint
110    will not know about them as it is using the wrong lldb python module.
111    In practice, this should be minor.
112    """
113    try:
114        lldb_module_path = None
115
116        if platform.system() == 'Darwin':
117            # Use xcrun to find the selected lldb.
118            lldb_module_path = subprocess.check_output(["xcrun", "lldb", "-P"])
119        elif platform.system() == 'Windows':
120            lldb_module_path = subprocess.check_output(
121                ["lldb.exe", "-P"], shell=True)
122        else:
123            # Use the shell to run lldb from the path.
124            lldb_module_path = subprocess.check_output(
125                ["lldb", "-P"], shell=True)
126
127        # Trim the result.
128        if lldb_module_path is not None:
129            lldb_module_path = lldb_module_path.strip()
130
131        # If we have a result, add it to the path
132        if lldb_module_path is not None and len(lldb_module_path) > 0:
133            sys.path.insert(0, lldb_module_path)
134    # pylint: disable=broad-except
135    except Exception as exception:
136        print("failed to find python path: {}".format(exception))
137
138
139def add_lldb_test_package_paths(check_dir):
140    """Adds the lldb test infrastructure modules to the python path.
141
142    See add_lldb_test_paths for more details.
143
144    @param check_dir the directory of the test.
145    """
146
147    def child_dirs(parent_dir):
148        return [os.path.join(parent_dir, child)
149                for child in os.listdir(parent_dir)
150                if os.path.isdir(os.path.join(parent_dir, child))]
151
152    check_dir = os.path.realpath(check_dir)
153    while check_dir and len(check_dir) > 0:
154        # If the current directory contains a packages/Python
155        # directory, add that directory to the path.
156        packages_python_child_dir = os.path.join(
157            check_dir, "packages", "Python")
158        if os.path.exists(packages_python_child_dir):
159            sys.path.insert(0, packages_python_child_dir)
160            sys.path.insert(0, os.path.join(
161                packages_python_child_dir, "test_runner", "lib"))
162
163            # Handle third_party module/package directory.
164            third_party_module_dir = os.path.join(
165                check_dir, "third_party", "Python", "module")
166            for child_dir in child_dirs(third_party_module_dir):
167                # Yes, we embed the module in the module parent dir
168                sys.path.insert(0, child_dir)
169
170            # We're done.
171            break
172
173        # Continue looking up the parent chain until we have no more
174        # directories to check.
175        new_check_dir = os.path.dirname(check_dir)
176        # We're done when the new check dir is not different
177        # than the current one.
178        if new_check_dir == check_dir:
179            break
180        check_dir = new_check_dir
181