| #!/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) |