blob: 1af9af8a30792a23613109aa3c3623a3875c6ebe [file] [log] [blame]
#!/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)