Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: tools/sublime/compile_current_file.py

Issue 2006563002: Moved Sublime documentation to markdown and added SublimeClang setup (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Removed unecessary steps in compile work Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « docs/linux_sublime_dev.md ('k') | tools/sublime/ninja_options_script.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/python
2 # Copyright 2016 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 import datetime
7 import fnmatch
8 import logging
9 import os
10 import os.path
11 import queue as Queue
12 import sublime
13 import sublime_plugin
14 import subprocess
15 import tempfile
16 import threading
17 import time
18
19 # Change to an absolute reference if ninja is not on your path
20 path_to_ninja = 'ninja'
21
22 def find_ninja_file(ninja_root_path, relative_file_path_to_find):
23 '''
24 Returns the first *.ninja file in ninja_root_path that contains
25 relative_file_path_to_find. Otherwise, returns None.
26 '''
27 matches = []
28 for root, dirnames, filenames in os.walk(ninja_root_path):
29 for filename in fnmatch.filter(filenames, '*.ninja'):
30 matches.append(os.path.join(root, filename))
31 logging.debug("Found %d Ninja targets", len(matches))
32
33 for ninja_file in matches:
34 for line in open(ninja_file):
35 if relative_file_path_to_find in line:
36 return ninja_file
37 return None
38
39
40 class PrintOutputCommand(sublime_plugin.TextCommand):
41 def run(self, edit, **args):
42 self.view.set_read_only(False)
43 self.view.insert(edit, self.view.size(), args['text'])
44 self.view.show(self.view.size())
45 self.view.set_read_only(True)
46
47
48 class CompileCurrentFile(sublime_plugin.TextCommand):
49 # static thread so that we don't try to run more than once at a time.
50 thread = None
51 lock = threading.Lock()
52
53 def __init__(self, args):
54 super(CompileCurrentFile, self).__init__(args)
55 self.thread_id = threading.current_thread().ident
56 self.text_to_draw = ""
57 self.interrupted = False
58
59 def description(self):
60 return ("Compiles the file in the current view using Ninja, so all that "
61 "this file and it's project depends on will be built first\n"
62 "Note that this command is a toggle so invoking it while it runs "
63 "will interrupt it.")
64
65 def draw_panel_text(self):
66 """Draw in the output.exec panel the text accumulated in self.text_to_draw.
67
68 This must be called from the main UI thread (e.g., using set_timeout).
69 """
70 assert self.thread_id == threading.current_thread().ident
71 logging.debug("draw_panel_text called.")
72 self.lock.acquire()
73 text_to_draw = self.text_to_draw
74 self.text_to_draw = ""
75 self.lock.release()
76
77 if len(text_to_draw):
78 self.output_panel.run_command('print_output', {'text': text_to_draw})
79 self.view.window().run_command("show_panel", {"panel": "output.exec"})
80 logging.debug("Added text:\n%s.", text_to_draw)
81
82 def update_panel_text(self, text_to_draw):
83 self.lock.acquire()
84 self.text_to_draw += text_to_draw
85 self.lock.release()
86 sublime.set_timeout(self.draw_panel_text, 0)
87
88 def execute_command(self, command, cwd):
89 """Execute the provided command and send ouput to panel.
90
91 Because the implementation of subprocess can deadlock on windows, we use
92 a Queue that we write to from another thread to avoid blocking on IO.
93
94 Args:
95 command: A list containing the command to execute and it's arguments.
96 Returns:
97 The exit code of the process running the command or,
98 1 if we got interrupted.
99 -1 if we couldn't start the process
100 -2 if we couldn't poll the running process
101 """
102 logging.debug("Running command: %s", command)
103
104 def EnqueueOutput(out, queue):
105 """Read all the output from the given handle and insert it into the queue.
106
107 Args:
108 queue: The Queue object to write to.
109 """
110 while True:
111 # This readline will block until there is either new input or the handle
112 # is closed. Readline will only return None once the handle is close, so
113 # even if the output is being produced slowly, this function won't exit
114 # early.
115 # The potential dealock here is acceptable because this isn't run on the
116 # main thread.
117 data = out.readline()
118 if not data:
119 break
120 queue.put(data, block=True)
121 out.close()
122
123 try:
124 os.chdir(cwd)
125 proc = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True,
126 stderr=subprocess.STDOUT, stdin=subprocess.PIPE)
127 except OSError as e:
128 logging.exception('Execution of %s raised exception: %s.', (command, e))
129 return -1
130
131 # Use a Queue to pass the text from the reading thread to this one.
132 stdout_queue = Queue.Queue()
133 stdout_thread = threading.Thread(target=EnqueueOutput,
134 args=(proc.stdout, stdout_queue))
135 stdout_thread.daemon = True # Ensure this exits if the parent dies
136 stdout_thread.start()
137
138 # We use the self.interrupted flag to stop this thread.
139 while not self.interrupted:
140 try:
141 exit_code = proc.poll()
142 except OSError as e:
143 logging.exception('Polling execution of %s raised exception: %s.',
144 command, e)
145 return -2
146
147 # Try to read output content from the queue
148 current_content = ""
149 for _ in range(2048):
150 try:
151 current_content += stdout_queue.get_nowait().decode('utf-8')
152 except Queue.Empty:
153 break
154 self.update_panel_text(current_content)
155 current_content = ""
156 if exit_code is not None:
157 while stdout_thread.isAlive() or not stdout_queue.empty():
158 try:
159 current_content += stdout_queue.get(
160 block=True, timeout=1).decode('utf-8')
161 except Queue.Empty:
162 # Queue could still potentially contain more input later.
163 pass
164 time_length = datetime.datetime.now() - self.start_time
165 self.update_panel_text("%s\nDone!\n(%s seconds)" %
166 (current_content, time_length.seconds))
167 return exit_code
168 # We sleep a little to give the child process a chance to move forward
169 # before we poll it again.
170 time.sleep(0.1)
171
172 # If we get here, it's because we were interrupted, kill the process.
173 proc.terminate()
174 return 1
175
176 def run(self, edit, target_build):
177 """The method called by Sublime Text to execute our command.
178
179 Note that this command is a toggle, so if the thread is are already running,
180 calling run will interrupt it.
181
182 Args:
183 edit: Sumblime Text specific edit brace.
184 target_build: Release/Debug/Other... Used for the subfolder of out.
185 """
186 # There can only be one... If we are running, interrupt and return.
187 if self.thread and self.thread.is_alive():
188 self.interrupted = True
189 self.thread.join(5.0)
190 self.update_panel_text("\n\nInterrupted current command:\n%s\n" % command)
191 self.thread = None
192 return
193
194 # It's nice to display how long it took to build.
195 self.start_time = datetime.datetime.now()
196 # Output our results in the same panel as a regular build.
197 self.output_panel = self.view.window().get_output_panel("exec")
198 self.output_panel.set_read_only(True)
199 self.view.window().run_command("show_panel", {"panel": "output.exec"})
200 # TODO(mad): Not sure if the project folder is always the first one... ???
201 project_folder = self.view.window().folders()[0]
202 self.update_panel_text("Compiling current file %s\n" %
203 self.view.file_name())
204 # The file must be somewhere under the project folder...
205 if (project_folder.lower() !=
206 self.view.file_name()[:len(project_folder)].lower()):
207 self.update_panel_text(
208 "ERROR: File %s is not in current project folder %s\n" %
209 (self.view.file_name(), project_folder))
210 else:
211 output_dir = os.path.join(project_folder, 'out', target_build)
212 source_relative_path = os.path.relpath(self.view.file_name(),
213 output_dir)
214 command = [
215 path_to_ninja, "-C", os.path.join(project_folder, 'out',
216 target_build),
217 source_relative_path + '^']
218 self.update_panel_text(' '.join(command) + '\n')
219 self.interrupted = False
220 self.thread = threading.Thread(target=self.execute_command,
221 kwargs={"command":command,
222 "cwd": output_dir})
223 self.thread.start()
224
225 time_length = datetime.datetime.now() - self.start_time
226 logging.debug("Took %s seconds on UI thread to startup",
227 time_length.seconds)
228 self.view.window().focus_view(self.view)
OLDNEW
« no previous file with comments | « docs/linux_sublime_dev.md ('k') | tools/sublime/ninja_options_script.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698