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