| #!/usr/bin/python |
| # Copyright 2018 The Tulsi Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Bootstraps the presence and setup of ~/.lldbinit-tulsiproj.""" |
| |
| import os |
| import shutil |
| import StringIO |
| import sys |
| |
| |
| TULSI_LLDBINIT_FILE = os.path.expanduser('~/.lldbinit-tulsiproj') |
| |
| |
| class BootstrapLLDBInit(object): |
| """Bootstrap Xcode's preferred lldbinit for Bazel debugging.""" |
| |
| def _ExtractLLDBInitContent(self, lldbinit_path, source_string): |
| """Extracts non-Tulsi content in a given lldbinit if needed. |
| |
| Args: |
| lldbinit_path: Absolute path to the lldbinit we are writing to. |
| source_string: String that we wish to write to lldbinit. |
| |
| Returns: |
| (int, [string]): A tuple featuring the status code along with the list |
| of strings representing content to write to lldbinit |
| that does not account for the Tulsi-generated strings. |
| Status code will be 0 if Tulsi-generated strings are |
| not all there. Status code will be 1 if all Tulsi |
| strings were accounted for. Status code will be 2 if |
| the lldbinit file could not be found. |
| """ |
| if not os.path.isfile(lldbinit_path): |
| return (2, []) |
| content = [] |
| with open(lldbinit_path) as f: |
| ignoring = False |
| |
| # Split on the newline. This works as long as the last string isn't |
| # suffixed with \n. |
| source_lines = source_string.split('\n') |
| |
| source_idx = 0 |
| |
| # If the last line was suffixed with \n, last elements would be length |
| # minus 2, accounting for the extra \n. |
| source_last = len(source_lines) - 1 |
| |
| for line in f: |
| |
| # For each line found matching source_string, increment the iterator |
| # and do not append that line to the list. |
| if source_lines[source_idx] in line: |
| |
| # If all lines were found, return an error code with empty content. |
| if source_idx == source_last: |
| return (1, []) |
| |
| # Increment for each matching line found. |
| source_idx += 1 |
| ignoring = True |
| continue |
| |
| if ignoring: |
| |
| # If the last line was found... |
| if source_lines[source_last] in line: |
| # Stop ignoring lines and continue appending to content. |
| ignoring = False |
| continue |
| |
| # If the line could not be found within source_string, append to the |
| # content array. |
| content.append(line) |
| |
| return (0, content) |
| |
| def _LinkTulsiLLDBInit(self): |
| """Adds a reference to ~/.lldbinit-tulsiproj to the primary lldbinit file. |
| |
| Xcode 8+ executes the contents of the first available lldbinit on startup. |
| To help work around this, an external reference to ~/.lldbinit-tulsiproj is |
| added to that lldbinit. This causes Xcode's lldb-rpc-server to load the |
| possibly modified contents between Debug runs of any given app. Note that |
| this only happens after a Debug session terminates; the cache is only fully |
| invalidated after Xcode is relaunched. |
| """ |
| |
| # ~/.lldbinit-Xcode is the only lldbinit file that Xcode will read if it is |
| # present, therefore it has priority. |
| lldbinit_path = os.path.expanduser('~/.lldbinit-Xcode') |
| if not os.path.isfile(lldbinit_path): |
| # If ~/.lldbinit-Xcode does not exist, write the reference to |
| # ~/.lldbinit-tulsiproj to ~/.lldbinit, the second lldbinit file that |
| # Xcode will attempt to read if ~/.lldbinit-Xcode isn't present. |
| lldbinit_path = os.path.expanduser('~/.lldbinit') |
| |
| # String that we plan to inject into this lldbinit. |
| source_string = ('# <TULSI> LLDB bridge [:\n' |
| '# This was autogenerated by Tulsi in order to modify ' |
| 'LLDB source-maps at build time.\n' |
| 'command source %s\n' % TULSI_LLDBINIT_FILE + |
| '# ]: <TULSI> LLDB bridge') |
| |
| # Retrieve the contents of lldbinit if applicable along with a return code. |
| return_code, content = self._ExtractLLDBInitContent(lldbinit_path, |
| source_string) |
| |
| out = StringIO.StringIO() |
| |
| if return_code == 0: |
| # Print the existing contents of this ~/.lldbinit without any malformed |
| # tulsi lldbinit block, and add the correct tulsi lldbinit block to the |
| # end of it. |
| for line in content: |
| out.write(line) |
| elif return_code == 1: |
| # If we should ignore the contents of this lldbinit, and it has the |
| # association with ~/.lldbinit-tulsiproj that we want, do not modify it. |
| return |
| |
| # Add a newline after the source_string for protection from other elements |
| # within the lldbinit file. |
| out.write(source_string + '\n') |
| |
| with open(lldbinit_path, 'w') as outfile: |
| out.seek(0) |
| # Negative length to make copyfileobj write the whole file at once. |
| shutil.copyfileobj(out, outfile, -1) |
| |
| def __init__(self): |
| self._LinkTulsiLLDBInit() |
| |
| |
| if __name__ == '__main__': |
| BootstrapLLDBInit() |
| sys.exit(0) |