mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-01-02 16:14:25 +00:00
Make "git command" and "forall" work on Windows
Python on Windows does not support non blocking file operations. To workaround this issue, we instead use Threads and a Queue to simulate non-blocking calls. This is happens only when running with the native Windows version of Python, meaning Linux and Cygwin are not affected by this change. Change-Id: I4ce23827b096c5138f67a85c721f58a12279bb6f
This commit is contained in:
parent
35d22217a5
commit
2e70291162
@ -14,14 +14,14 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import fcntl
|
|
||||||
import os
|
import os
|
||||||
import select
|
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
from signal import SIGTERM
|
from signal import SIGTERM
|
||||||
|
|
||||||
from error import GitError
|
from error import GitError
|
||||||
|
import platform_utils
|
||||||
from trace import REPO_TRACE, IsTrace, Trace
|
from trace import REPO_TRACE, IsTrace, Trace
|
||||||
from wrapper import Wrapper
|
from wrapper import Wrapper
|
||||||
|
|
||||||
@ -78,16 +78,6 @@ def terminate_ssh_clients():
|
|||||||
|
|
||||||
_git_version = None
|
_git_version = None
|
||||||
|
|
||||||
class _sfd(object):
|
|
||||||
"""select file descriptor class"""
|
|
||||||
def __init__(self, fd, dest, std_name):
|
|
||||||
assert std_name in ('stdout', 'stderr')
|
|
||||||
self.fd = fd
|
|
||||||
self.dest = dest
|
|
||||||
self.std_name = std_name
|
|
||||||
def fileno(self):
|
|
||||||
return self.fd.fileno()
|
|
||||||
|
|
||||||
class _GitCall(object):
|
class _GitCall(object):
|
||||||
def version(self):
|
def version(self):
|
||||||
p = GitCommand(None, ['--version'], capture_stdout=True)
|
p = GitCommand(None, ['--version'], capture_stdout=True)
|
||||||
@ -253,19 +243,16 @@ class GitCommand(object):
|
|||||||
|
|
||||||
def _CaptureOutput(self):
|
def _CaptureOutput(self):
|
||||||
p = self.process
|
p = self.process
|
||||||
s_in = [_sfd(p.stdout, sys.stdout, 'stdout'),
|
s_in = platform_utils.FileDescriptorStreams.create()
|
||||||
_sfd(p.stderr, sys.stderr, 'stderr')]
|
s_in.add(p.stdout, sys.stdout, 'stdout')
|
||||||
|
s_in.add(p.stderr, sys.stderr, 'stderr')
|
||||||
self.stdout = ''
|
self.stdout = ''
|
||||||
self.stderr = ''
|
self.stderr = ''
|
||||||
|
|
||||||
for s in s_in:
|
while not s_in.is_done:
|
||||||
flags = fcntl.fcntl(s.fd, fcntl.F_GETFL)
|
in_ready = s_in.select()
|
||||||
fcntl.fcntl(s.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
|
||||||
|
|
||||||
while s_in:
|
|
||||||
in_ready, _, _ = select.select(s_in, [], [])
|
|
||||||
for s in in_ready:
|
for s in in_ready:
|
||||||
buf = s.fd.read(4096)
|
buf = s.read()
|
||||||
if not buf:
|
if not buf:
|
||||||
s_in.remove(s)
|
s_in.remove(s)
|
||||||
continue
|
continue
|
||||||
|
169
platform_utils.py
Normal file
169
platform_utils.py
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2016 The Android Open Source Project
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import select
|
||||||
|
|
||||||
|
from Queue import Queue
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
|
||||||
|
def isWindows():
|
||||||
|
""" Returns True when running with the native port of Python for Windows,
|
||||||
|
False when running on any other platform (including the Cygwin port of
|
||||||
|
Python).
|
||||||
|
"""
|
||||||
|
# Note: The cygwin port of Python returns "CYGWIN_NT_xxx"
|
||||||
|
return platform.system() == "Windows"
|
||||||
|
|
||||||
|
|
||||||
|
class FileDescriptorStreams(object):
|
||||||
|
""" Platform agnostic abstraction enabling non-blocking I/O over a
|
||||||
|
collection of file descriptors. This abstraction is required because
|
||||||
|
fctnl(os.O_NONBLOCK) is not supported on Windows.
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def create(cls):
|
||||||
|
""" Factory method: instantiates the concrete class according to the
|
||||||
|
current platform.
|
||||||
|
"""
|
||||||
|
if isWindows():
|
||||||
|
return _FileDescriptorStreamsThreads()
|
||||||
|
else:
|
||||||
|
return _FileDescriptorStreamsNonBlocking()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.streams = []
|
||||||
|
|
||||||
|
def add(self, fd, dest, std_name):
|
||||||
|
""" Wraps an existing file descriptor as a stream.
|
||||||
|
"""
|
||||||
|
self.streams.append(self._create_stream(fd, dest, std_name))
|
||||||
|
|
||||||
|
def remove(self, stream):
|
||||||
|
""" Removes a stream, when done with it.
|
||||||
|
"""
|
||||||
|
self.streams.remove(stream)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_done(self):
|
||||||
|
""" Returns True when all streams have been processed.
|
||||||
|
"""
|
||||||
|
return len(self.streams) == 0
|
||||||
|
|
||||||
|
def select(self):
|
||||||
|
""" Returns the set of streams that have data available to read.
|
||||||
|
The returned streams each expose a read() and a close() method.
|
||||||
|
When done with a stream, call the remove(stream) method.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _create_stream(fd, dest, std_name):
|
||||||
|
""" Creates a new stream wrapping an existing file descriptor.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class _FileDescriptorStreamsNonBlocking(FileDescriptorStreams):
|
||||||
|
""" Implementation of FileDescriptorStreams for platforms that support
|
||||||
|
non blocking I/O.
|
||||||
|
"""
|
||||||
|
class Stream(object):
|
||||||
|
""" Encapsulates a file descriptor """
|
||||||
|
def __init__(self, fd, dest, std_name):
|
||||||
|
self.fd = fd
|
||||||
|
self.dest = dest
|
||||||
|
self.std_name = std_name
|
||||||
|
self.set_non_blocking()
|
||||||
|
|
||||||
|
def set_non_blocking(self):
|
||||||
|
import fcntl
|
||||||
|
flags = fcntl.fcntl(self.fd, fcntl.F_GETFL)
|
||||||
|
fcntl.fcntl(self.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||||
|
|
||||||
|
def fileno(self):
|
||||||
|
return self.fd.fileno()
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
return self.fd.read(4096)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.fd.close()
|
||||||
|
|
||||||
|
def _create_stream(self, fd, dest, std_name):
|
||||||
|
return self.Stream(fd, dest, std_name)
|
||||||
|
|
||||||
|
def select(self):
|
||||||
|
ready_streams, _, _ = select.select(self.streams, [], [])
|
||||||
|
return ready_streams
|
||||||
|
|
||||||
|
|
||||||
|
class _FileDescriptorStreamsThreads(FileDescriptorStreams):
|
||||||
|
""" Implementation of FileDescriptorStreams for platforms that don't support
|
||||||
|
non blocking I/O. This implementation requires creating threads issuing
|
||||||
|
blocking read operations on file descriptors.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
super(_FileDescriptorStreamsThreads, self).__init__()
|
||||||
|
# The queue is shared accross all threads so we can simulate the
|
||||||
|
# behavior of the select() function
|
||||||
|
self.queue = Queue(10) # Limit incoming data from streams
|
||||||
|
|
||||||
|
def _create_stream(self, fd, dest, std_name):
|
||||||
|
return self.Stream(fd, dest, std_name, self.queue)
|
||||||
|
|
||||||
|
def select(self):
|
||||||
|
# Return only one stream at a time, as it is the most straighforward
|
||||||
|
# thing to do and it is compatible with the select() function.
|
||||||
|
item = self.queue.get()
|
||||||
|
stream = item.stream
|
||||||
|
stream.data = item.data
|
||||||
|
return [stream]
|
||||||
|
|
||||||
|
class QueueItem(object):
|
||||||
|
""" Item put in the shared queue """
|
||||||
|
def __init__(self, stream, data):
|
||||||
|
self.stream = stream
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
class Stream(object):
|
||||||
|
""" Encapsulates a file descriptor """
|
||||||
|
def __init__(self, fd, dest, std_name, queue):
|
||||||
|
self.fd = fd
|
||||||
|
self.dest = dest
|
||||||
|
self.std_name = std_name
|
||||||
|
self.queue = queue
|
||||||
|
self.data = None
|
||||||
|
self.thread = Thread(target=self.read_to_queue)
|
||||||
|
self.thread.daemon = True
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.fd.close()
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
data = self.data
|
||||||
|
self.data = None
|
||||||
|
return data
|
||||||
|
|
||||||
|
def read_to_queue(self):
|
||||||
|
""" The thread function: reads everything from the file descriptor into
|
||||||
|
the shared queue and terminates when reaching EOF.
|
||||||
|
"""
|
||||||
|
for line in iter(self.fd.readline, b''):
|
||||||
|
self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, line))
|
||||||
|
self.fd.close()
|
||||||
|
self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, None))
|
@ -15,17 +15,16 @@
|
|||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import errno
|
import errno
|
||||||
import fcntl
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import select
|
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from color import Coloring
|
from color import Coloring
|
||||||
from command import Command, MirrorSafeCommand
|
from command import Command, MirrorSafeCommand
|
||||||
|
import platform_utils
|
||||||
|
|
||||||
_CAN_COLOR = [
|
_CAN_COLOR = [
|
||||||
'branch',
|
'branch',
|
||||||
@ -344,35 +343,25 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config):
|
|||||||
if opt.project_header:
|
if opt.project_header:
|
||||||
out = ForallColoring(config)
|
out = ForallColoring(config)
|
||||||
out.redirect(sys.stdout)
|
out.redirect(sys.stdout)
|
||||||
class sfd(object):
|
|
||||||
def __init__(self, fd, dest):
|
|
||||||
self.fd = fd
|
|
||||||
self.dest = dest
|
|
||||||
def fileno(self):
|
|
||||||
return self.fd.fileno()
|
|
||||||
|
|
||||||
empty = True
|
empty = True
|
||||||
errbuf = ''
|
errbuf = ''
|
||||||
|
|
||||||
p.stdin.close()
|
p.stdin.close()
|
||||||
s_in = [sfd(p.stdout, sys.stdout),
|
s_in = platform_utils.FileDescriptorStreams.create()
|
||||||
sfd(p.stderr, sys.stderr)]
|
s_in.add(p.stdout, sys.stdout, 'stdout')
|
||||||
|
s_in.add(p.stderr, sys.stderr, 'stderr')
|
||||||
|
|
||||||
for s in s_in:
|
while not s_in.is_done:
|
||||||
flags = fcntl.fcntl(s.fd, fcntl.F_GETFL)
|
in_ready = s_in.select()
|
||||||
fcntl.fcntl(s.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
|
||||||
|
|
||||||
while s_in:
|
|
||||||
in_ready, _out_ready, _err_ready = select.select(s_in, [], [])
|
|
||||||
for s in in_ready:
|
for s in in_ready:
|
||||||
buf = s.fd.read(4096)
|
buf = s.read()
|
||||||
if not buf:
|
if not buf:
|
||||||
s.fd.close()
|
s.close()
|
||||||
s_in.remove(s)
|
s_in.remove(s)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not opt.verbose:
|
if not opt.verbose:
|
||||||
if s.fd != p.stdout:
|
if s.std_name == 'stderr':
|
||||||
errbuf += buf
|
errbuf += buf
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user