Changes to Tahoe needed to work with new zetuptoolz (that does not use .exe wrappers on Windows), and to support Unicode arguments and stdout/stderr -- v5

This commit is contained in:
david-sarah 2010-07-25 01:32:16 -07:00
parent 54a9ba8232
commit 37b07a545f
7 changed files with 191 additions and 147 deletions

@ -5,12 +5,17 @@ import errno, sys, os, subprocess
where = os.path.realpath(sys.argv[0])
base = os.path.dirname(os.path.dirname(where))
if sys.platform == "win32":
installed_tahoe = os.path.join(os.path.dirname(sys.executable), 'Scripts', 'tahoe.pyscript')
else:
installed_tahoe = "/usr/bin/tahoe"
whoami = '''\
I am a "bin/tahoe" executable who is only for the convenience of running
I am a "bin%stahoe" executable who is only for the convenience of running
Tahoe from its source distribution -- I work only when invoked as the "tahoe"
script that lives in the "bin/" subdirectory of a Tahoe source code
distribution, and only if you have already run "make".
'''
''' % (os.path.sep,)
# look for Tahoe.home .
homemarker = os.path.join(base, "Tahoe.home")
@ -19,9 +24,9 @@ if not os.path.exists(homemarker):
print '''\
I just tried to run and found that I am not living in such a directory, so I
am stopping now. To run Tahoe after it has been is installed, please execute
my brother, also named "tahoe", who gets installed into the appropriate place
for executables when you run "make install" (perhaps as /usr/bin/tahoe).
'''
my brother, who gets installed into the appropriate place for executables
when you run "make install" (perhaps as "%s").
''' % (installed_tahoe,)
sys.exit(1)
# we've found our home. Put the tahoe support/lib etc. in our PYTHONPATH.
@ -41,30 +46,50 @@ else:
pp = supportdir
os.environ["PYTHONPATH"] = pp
# find the location of the tahoe executable.
bin_dir = "bin"
# find commandline args and the location of the tahoe executable.
if sys.platform == "win32":
bin_dir = "Scripts"
executable = os.path.join(base, "support", bin_dir, "tahoe")
import re
from ctypes import WINFUNCTYPE, POINTER, byref, c_wchar_p, c_int, windll
GetCommandLineW = WINFUNCTYPE(c_wchar_p)(("GetCommandLineW", windll.kernel32))
CommandLineToArgvW = WINFUNCTYPE(POINTER(c_wchar_p), c_wchar_p, POINTER(c_int)) \
(("CommandLineToArgvW", windll.shell32))
argc = c_int(0)
argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc))
# See src/allmydata/scripts/runner.py for the corresponding unmangler.
# Note that this doesn't escape \x7F. If it did, test_unicode_arguments_and_output
# in test_runner.py wouldn't work.
def mangle(s):
return str(re.sub(ur'[^\x20-\x7F]', lambda m: u'\x7F%x;' % (ord(m.group(0)),), s))
argv = [mangle(argv_unicode[i]) for i in xrange(1, argc.value)]
local_tahoe = "Scripts\\tahoe.pyscript"
else:
argv = sys.argv
local_tahoe = "bin/tahoe"
script = os.path.join(base, "support", local_tahoe)
try:
res = subprocess.call([executable] + sys.argv[1:], env=os.environ)
res = subprocess.call([sys.executable, script] + argv[1:], env=os.environ)
except (OSError, IOError), le:
if le.args[0] == errno.ENOENT:
print whoami
print '''\
I just tried to run and could not find my brother, named
"../support/bin/tahoe". To run Tahoe when it is installed, please execute my
brother, also named "tahoe", who gets installed into the appropriate place
for executables when you run "make install" (perhaps as /usr/bin/tahoe).
'''
I just tried to run and could not find my brother at
"%s". To run Tahoe when it is installed, please execute my
brother, who gets installed into the appropriate place for executables
when you run "make install" (perhaps as "%s").
''' % (script, installed_tahoe)
raise
except Exception, le:
print whoami
print '''\
I just tried to invoke my brother, named "../support/bin/tahoe" and got an
exception.
'''
I just tried to invoke my brother at "%s"
and got an exception.
''' % (script,)
raise
else:
sys.exit(res)

@ -235,54 +235,46 @@ class MakeExecutable(Command):
def run(self):
bin_tahoe_template = os.path.join("bin", "tahoe-script.template")
# Create the 'tahoe-script.py' file under the 'bin' directory. The
# 'tahoe-script.py' file is exactly the same as the
# 'tahoe-script.template' script except that the shebang line is
# rewritten to use our sys.executable for the interpreter. On
# Windows, create a tahoe.exe will execute it. On non-Windows, make a
# symlink to it from 'tahoe'. The tahoe.exe will be copied from the
# setuptools egg's cli.exe and this will work from a zip-safe and
# non-zip-safe setuptools egg.
if sys.platform == 'win32':
# 'tahoe' script is needed for cygwin
script_names = ["tahoe.pyscript", "tahoe"]
else:
script_names = ["tahoe"]
# Create the tahoe script file under the 'bin' directory. This
# file is exactly the same as the 'tahoe-script.template' script
# except that the shebang line is rewritten to use our sys.executable
# for the interpreter.
f = open(bin_tahoe_template, "rU")
script_lines = f.readlines()
f.close()
script_lines[0] = "#!%s\n" % sys.executable
tahoe_script = os.path.join("bin", "tahoe-script.py")
f = open(tahoe_script, "w")
for line in script_lines:
f.write(line)
f.close()
if sys.platform == "win32":
from pkg_resources import require
setuptools_egg = require("setuptools")[0].location
if os.path.isfile(setuptools_egg):
z = zipfile.ZipFile(setuptools_egg, 'r')
for filename in z.namelist():
if 'cli.exe' in filename:
cli_exe = z.read(filename)
else:
cli_exe = os.path.join(setuptools_egg, 'setuptools', 'cli.exe')
tahoe_exe = os.path.join("bin", "tahoe.exe")
if os.path.isfile(setuptools_egg):
f = open(tahoe_exe, 'wb')
f.write(cli_exe)
f.close()
else:
shutil.copy(cli_exe, tahoe_exe)
else:
script_lines[0] = '#!%s\n' % (sys.executable,)
for script_name in script_names:
tahoe_script = os.path.join("bin", script_name)
try:
os.remove(os.path.join('bin', 'tahoe'))
except:
# okay, probably it was already gone
pass
os.symlink('tahoe-script.py', os.path.join('bin', 'tahoe'))
os.remove(tahoe_script)
except Exception:
if os.path.exists(tahoe_script):
raise
f = open(tahoe_script, "wb")
for line in script_lines:
f.write(line)
f.close()
# chmod +x
old_mode = stat.S_IMODE(os.stat(tahoe_script)[stat.ST_MODE])
new_mode = old_mode | (stat.S_IXUSR | stat.S_IRUSR |
stat.S_IXGRP | stat.S_IRGRP |
stat.S_IXOTH | stat.S_IROTH )
os.chmod(tahoe_script, new_mode)
old_tahoe_exe = os.path.join("bin", "tahoe.exe")
try:
os.remove(old_tahoe_exe)
except Exception:
if os.path.exists(old_tahoe_exe):
raise
# chmod +x bin/tahoe-script.py
old_mode = stat.S_IMODE(os.stat(tahoe_script)[stat.ST_MODE])
new_mode = old_mode | (stat.S_IXUSR | stat.S_IRUSR |
stat.S_IXGRP | stat.S_IRGRP |
stat.S_IXOTH | stat.S_IROTH )
os.chmod(tahoe_script, new_mode)
class MySdist(sdist.sdist):
""" A hook in the sdist command so that we can determine whether this the

@ -10,6 +10,7 @@ import allmydata
pkg_resources.require(allmydata.__appname__)
from allmydata.scripts.common import BaseOptions
from allmydata.scripts import debug, create_node, startstop_node, cli, keygen, stats_gatherer
from allmydata.util.encodingutil import quote_output, get_argv_encoding
def GROUP(s):
# Usage.parseOptions compares argv[1] against command[0], so it will
@ -19,7 +20,7 @@ def GROUP(s):
class Options(BaseOptions, usage.Options):
synopsis = "Usage: tahoe <command> [command options]"
synopsis = "\nUsage: tahoe <command> [command options]"
subCommands = ( GROUP("Administration")
+ create_node.subCommands
+ keygen.subCommands
@ -42,9 +43,13 @@ class Options(BaseOptions, usage.Options):
def runner(argv,
run_by_human=True,
stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr,
stdin=None, stdout=None, stderr=None,
install_node_control=True, additional_commands=None):
stdin = stdin or sys.stdin
stdout = stdout or sys.stdout
stderr = stderr or sys.stderr
config = Options()
if install_node_control:
config.subCommands.extend(startstop_node.subCommands)
@ -63,8 +68,12 @@ def runner(argv,
c = config
while hasattr(c, 'subOptions'):
c = c.subOptions
print str(c)
print "%s: %s" % (sys.argv[0], e)
print >>stdout, str(c)
try:
msg = e.args[0].decode(get_argv_encoding())
except Exception:
msg = repr(e)
print >>stdout, "%s: %s\n" % (sys.argv[0], quote_output(msg, quotemarks=False))
return 1
command = config.subCommand
@ -99,6 +108,11 @@ def runner(argv,
return rc
def run(install_node_control=True):
rc = runner(sys.argv[1:])
if sys.platform == "win32":
from allmydata.windows.fixups import initialize
initialize()
rc = runner(sys.argv[1:], install_node_control=install_node_control)
sys.exit(rc)

@ -24,3 +24,8 @@ def disable_foolscap_incidents():
# we disable incident reporting for all unit tests.
disable_foolscap_incidents()
import sys
if sys.platform == "win32":
from allmydata.windows.fixups import initialize
initialize()

@ -21,17 +21,23 @@ if __name__ == "__main__":
if len(sys.argv) != 2:
print "Usage: %s lumi<e-grave>re" % sys.argv[0]
sys.exit(1)
if sys.platform == "win32":
try:
from allmydata.windows.fixups import initialize
except ImportError:
print "set PYTHONPATH to the src directory"
sys.exit(1)
initialize()
print
print "class MyWeirdOS(EncodingUtil, unittest.TestCase):"
print " uname = '%s'" % ' '.join(platform.uname())
if sys.platform != "win32":
print " argv = %s" % repr(sys.argv[1])
print " argv = %s" % repr(sys.argv[1])
print " platform = '%s'" % sys.platform
print " filesystem_encoding = '%s'" % sys.getfilesystemencoding()
print " output_encoding = '%s'" % sys.stdout.encoding
print " argv_encoding = '%s'" % (sys.platform == "win32" and 'ascii' or sys.stdout.encoding)
print " argv_encoding = '%s'" % sys.stdout.encoding
try:
tmpdir = tempfile.mkdtemp()
for fname in TEST_FILENAMES:
@ -56,6 +62,7 @@ from mock import patch
import os, sys, locale
from allmydata.test.common_util import ReallyEqualMixin
from allmydata.util import encodingutil
from allmydata.util.encodingutil import argv_to_unicode, unicode_to_url, \
unicode_to_output, quote_output, unicode_platform, listdir_unicode, \
FilenameEncodingError, get_output_encoding, get_filesystem_encoding, _reload
@ -64,8 +71,6 @@ from allmydata.dirnode import normalize
from twisted.python import usage
class EncodingUtilErrors(ReallyEqualMixin, unittest.TestCase):
def tearDown(self):
_reload()
@patch('sys.stdout')
def test_get_output_encoding(self, mock_stdout):
@ -78,11 +83,16 @@ class EncodingUtilErrors(ReallyEqualMixin, unittest.TestCase):
self.failUnlessReallyEqual(get_output_encoding(), 'utf-8')
mock_stdout.encoding = 'koi8-r'
expected = sys.platform == "win32" and 'utf-8' or 'koi8-r'
_reload()
self.failUnlessReallyEqual(get_output_encoding(), 'koi8-r')
self.failUnlessReallyEqual(get_output_encoding(), expected)
mock_stdout.encoding = 'nonexistent_encoding'
self.failUnlessRaises(AssertionError, _reload)
if sys.platform == "win32":
_reload()
self.failUnlessReallyEqual(get_output_encoding(), 'utf-8')
else:
self.failUnlessRaises(AssertionError, _reload)
@patch('locale.getpreferredencoding')
def test_get_output_encoding_not_from_stdout(self, mock_locale_getpreferredencoding):
@ -94,12 +104,13 @@ class EncodingUtilErrors(ReallyEqualMixin, unittest.TestCase):
old_stdout = sys.stdout
sys.stdout = DummyStdout()
try:
expected = sys.platform == "win32" and 'utf-8' or 'koi8-r'
_reload()
self.failUnlessReallyEqual(get_output_encoding(), 'koi8-r')
self.failUnlessReallyEqual(get_output_encoding(), expected)
sys.stdout.encoding = None
_reload()
self.failUnlessReallyEqual(get_output_encoding(), 'koi8-r')
self.failUnlessReallyEqual(get_output_encoding(), expected)
mock_locale_getpreferredencoding.return_value = None
_reload()
@ -107,20 +118,14 @@ class EncodingUtilErrors(ReallyEqualMixin, unittest.TestCase):
finally:
sys.stdout = old_stdout
@patch('sys.stdout')
def test_argv_to_unicode(self, mock):
mock.encoding = 'utf-8'
_reload()
def test_argv_to_unicode(self):
encodingutil.output_encoding = 'utf-8'
self.failUnlessRaises(usage.UsageError,
argv_to_unicode,
lumiere_nfc.encode('latin1'))
@patch('sys.stdout')
def test_unicode_to_output(self, mock):
# Encoding koi8-r cannot represent e-grave
mock.encoding = 'koi8-r'
_reload()
def test_unicode_to_output(self):
encodingutil.output_encoding = 'koi8-r'
self.failUnlessRaises(UnicodeEncodeError, unicode_to_output, lumiere_nfc)
@patch('os.listdir')
@ -171,9 +176,9 @@ class EncodingUtilNonUnicodePlatform(unittest.TestCase):
listdir_unicode,
u'/' + lumiere_nfc)
class EncodingUtil(ReallyEqualMixin):
def setUp(self):
# Mock sys.platform because unicode_platform() uses it
self.original_platform = sys.platform
sys.platform = self.platform
@ -197,12 +202,12 @@ class EncodingUtil(ReallyEqualMixin):
@patch('sys.stdout')
def test_unicode_to_output(self, mock):
if 'output' not in dir(self):
if 'argv' not in dir(self):
return
mock.encoding = self.output_encoding
_reload()
self.failUnlessReallyEqual(unicode_to_output(lumiere_nfc), self.output)
self.failUnlessReallyEqual(unicode_to_output(lumiere_nfc), self.argv)
def test_unicode_platform(self):
matrix = {
@ -287,19 +292,23 @@ class StdlibUnicode(unittest.TestCase):
class QuoteOutput(ReallyEqualMixin, unittest.TestCase):
def tearDown(self):
_reload()
def _check(self, inp, out, enc, optional_quotes):
out2 = out
if optional_quotes:
out2 = out2[1:-1]
self.failUnlessReallyEqual(quote_output(inp, encoding=enc), out)
self.failUnlessReallyEqual(quote_output(inp, encoding=enc, quotemarks=False), out2)
if out[0:2] != 'b"':
if isinstance(inp, str):
self.failUnlessReallyEqual(quote_output(unicode(inp), encoding=enc), out)
self.failUnlessReallyEqual(quote_output(unicode(inp), encoding=enc, quotemarks=False), out2)
else:
self.failUnlessReallyEqual(quote_output(inp.encode('utf-8'), encoding=enc), out)
self.failUnlessReallyEqual(quote_output(inp.encode('utf-8'), encoding=enc, quotemarks=False), out2)
if out[0:2] == 'b"':
pass
elif isinstance(inp, str):
self.failUnlessReallyEqual(quote_output(unicode(inp), encoding=enc), out)
self.failUnlessReallyEqual(quote_output(unicode(inp), encoding=enc, quotemarks=False), out2)
else:
self.failUnlessReallyEqual(quote_output(inp.encode('utf-8'), encoding=enc), out)
self.failUnlessReallyEqual(quote_output(inp.encode('utf-8'), encoding=enc, quotemarks=False), out2)
def _test_quote_output_all(self, enc):
def check(inp, out, optional_quotes=False):
@ -368,24 +377,19 @@ class QuoteOutput(ReallyEqualMixin, unittest.TestCase):
check(u"\"\u2621", u"'\"\u2621'")
check(u"\u2621\"", u"'\u2621\"'", True)
@patch('sys.stdout')
def test_quote_output_mock(self, mock_stdout):
mock_stdout.encoding = 'ascii'
_reload()
def test_quote_output_default(self):
encodingutil.output_encoding = 'ascii'
self.test_quote_output_ascii(None)
mock_stdout.encoding = 'latin1'
_reload()
encodingutil.output_encoding = 'latin1'
self.test_quote_output_latin1(None)
mock_stdout.encoding = 'utf-8'
_reload()
encodingutil.output_encoding = 'utf-8'
self.test_quote_output_utf8(None)
class UbuntuKarmicUTF8(EncodingUtil, unittest.TestCase):
uname = 'Linux korn 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:05:01 UTC 2009 x86_64'
output = 'lumi\xc3\xa8re'
argv = 'lumi\xc3\xa8re'
platform = 'linux2'
filesystem_encoding = 'UTF-8'
@ -395,7 +399,6 @@ class UbuntuKarmicUTF8(EncodingUtil, unittest.TestCase):
class UbuntuKarmicLatin1(EncodingUtil, unittest.TestCase):
uname = 'Linux korn 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:05:01 UTC 2009 x86_64'
output = 'lumi\xe8re'
argv = 'lumi\xe8re'
platform = 'linux2'
filesystem_encoding = 'ISO-8859-1'
@ -403,37 +406,18 @@ class UbuntuKarmicLatin1(EncodingUtil, unittest.TestCase):
argv_encoding = 'ISO-8859-1'
dirlist = ['test_file', 'Blah blah.txt', '\xc4rtonwall.mp3']
class WindowsXP(EncodingUtil, unittest.TestCase):
class Windows(EncodingUtil, unittest.TestCase):
uname = 'Windows XP 5.1.2600 x86 x86 Family 15 Model 75 Step ping 2, AuthenticAMD'
output = 'lumi\x8are'
argv = 'lumi\xc3\xa8re'
platform = 'win32'
filesystem_encoding = 'mbcs'
output_encoding = 'cp850'
argv_encoding = 'ascii'
dirlist = [u'Blah blah.txt', u'test_file', u'\xc4rtonwall.mp3']
class WindowsXP_UTF8(EncodingUtil, unittest.TestCase):
uname = 'Windows XP 5.1.2600 x86 x86 Family 15 Model 75 Step ping 2, AuthenticAMD'
output = 'lumi\xc3\xa8re'
platform = 'win32'
filesystem_encoding = 'mbcs'
output_encoding = 'cp65001'
argv_encoding = 'ascii'
dirlist = [u'Blah blah.txt', u'test_file', u'\xc4rtonwall.mp3']
class WindowsVista(EncodingUtil, unittest.TestCase):
uname = 'Windows Vista 6.0.6000 x86 x86 Family 6 Model 15 Stepping 11, GenuineIntel'
output = 'lumi\x8are'
platform = 'win32'
filesystem_encoding = 'mbcs'
output_encoding = 'cp850'
argv_encoding = 'ascii'
output_encoding = 'utf-8'
argv_encoding = 'utf-8'
dirlist = [u'Blah blah.txt', u'test_file', u'\xc4rtonwall.mp3']
class MacOSXLeopard(EncodingUtil, unittest.TestCase):
uname = 'Darwin g5.local 9.8.0 Darwin Kernel Version 9.8.0: Wed Jul 15 16:57:01 PDT 2009; root:xnu-1228.15.4~1/RELEASE_PPC Power Macintosh powerpc'
output = 'lumi\xc3\xa8re'
argv = 'lumi\xc3\xa8re'
platform = 'darwin'
filesystem_encoding = 'utf-8'
output_encoding = 'UTF-8'

@ -7,6 +7,7 @@ from twisted.internet import utils
import os.path, re, sys
from cStringIO import StringIO
from allmydata.util import fileutil, pollmixin
from allmydata.util.encodingutil import unicode_to_argv, unicode_to_output
from allmydata.scripts import runner
from allmydata.test import common_util
@ -47,6 +48,24 @@ class BinTahoe(common_util.SignalMixin, unittest.TestCase, SkipMixin):
d.addCallback(_cb)
return d
def test_unicode_arguments_and_output(self):
self.skip_if_cannot_run_bintahoe()
tricky = u"\u2621"
try:
tricky_arg = unicode_to_argv(tricky, mangle=True)
tricky_out = unicode_to_output(tricky)
except UnicodeEncodeError:
raise unittest.SkipTest("A non-ASCII argument/output could not be encoded on this platform.")
d = utils.getProcessOutputAndValue(bintahoe, args=[tricky_arg], env=os.environ)
def _cb(res):
out, err, rc_or_sig = res
self.failUnlessEqual(rc_or_sig, 1, str((out, err, rc_or_sig)))
self.failUnlessIn("Unknown command: "+tricky_out, out)
d.addCallback(_cb)
return d
def test_version_no_noise(self):
self.skip_if_cannot_run_bintahoe()
import pkg_resources

@ -13,7 +13,7 @@ from allmydata.util import log
from allmydata.util.fileutil import abspath_expanduser_unicode
def _canonical_encoding(encoding):
def canonical_encoding(encoding):
if encoding is None:
log.msg("Warning: falling back to UTF-8 encoding.", level=log.WEIRD)
encoding = 'utf-8'
@ -23,6 +23,9 @@ def _canonical_encoding(encoding):
elif encoding == "us-ascii" or encoding == "646" or encoding == "ansi_x3.4-1968":
encoding = 'ascii'
return encoding
def check_encoding(encoding):
# sometimes Python returns an encoding name that it doesn't support for conversion
# fail early if this happens
try:
@ -30,8 +33,6 @@ def _canonical_encoding(encoding):
except (LookupError, AttributeError):
raise AssertionError("The character encoding '%s' is not supported for conversion." % (encoding,))
return encoding
filesystem_encoding = None
output_encoding = None
argv_encoding = None
@ -40,23 +41,27 @@ is_unicode_platform = False
def _reload():
global filesystem_encoding, output_encoding, argv_encoding, is_unicode_platform
filesystem_encoding = _canonical_encoding(sys.getfilesystemencoding())
outenc = None
if hasattr(sys.stdout, 'encoding'):
outenc = sys.stdout.encoding
if outenc is None:
try:
outenc = locale.getpreferredencoding()
except Exception:
pass # work around <http://bugs.python.org/issue1443504>
output_encoding = _canonical_encoding(outenc)
filesystem_encoding = canonical_encoding(sys.getfilesystemencoding())
check_encoding(filesystem_encoding)
if sys.platform == 'win32':
# Unicode arguments are not supported on Windows yet; see #565 and #1074.
argv_encoding = 'ascii'
# On Windows we install UTF-8 stream wrappers for sys.stdout and
# sys.stderr, and reencode the arguments as UTF-8 (see scripts/runner.py).
output_encoding = 'utf-8'
else:
argv_encoding = output_encoding
outenc = None
if hasattr(sys.stdout, 'encoding'):
outenc = sys.stdout.encoding
if outenc is None:
try:
outenc = locale.getpreferredencoding()
except Exception:
pass # work around <http://bugs.python.org/issue1443504>
output_encoding = canonical_encoding(outenc)
check_encoding(output_encoding)
argv_encoding = output_encoding
is_unicode_platform = sys.platform in ["win32", "darwin"]
_reload()