move scripts into buildozer.scripts.*, and use console_scripts for setup()

This commit is contained in:
Mathieu Virbel 2014-04-20 16:45:40 +02:00
parent c9bd12e0ce
commit d053ef7831
8 changed files with 404 additions and 292 deletions

View file

@ -1,12 +1,8 @@
''' '''
Buildozer
=========
Layout directory for buildozer: Generic Python packager for Android / iOS. Desktop later.
build/
<targetname>/
platform/ - all the platform files necessary
app/ - compiled application
''' '''
@ -15,13 +11,11 @@ __version__ = '0.11-dev'
import fcntl import fcntl
import os import os
import re import re
import socket
import sys import sys
import zipfile import zipfile
import io
from select import select from select import select
from buildozer.jsonstore import JsonStore from buildozer.jsonstore import JsonStore
from sys import stdout, stderr, stdin, exit from sys import stdout, stderr, exit
from re import search from re import search
from os.path import join, exists, dirname, realpath, splitext, expanduser from os.path import join, exists, dirname, realpath, splitext, expanduser
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
@ -36,14 +30,6 @@ except ImportError:
from urllib import FancyURLopener from urllib import FancyURLopener
from ConfigParser import SafeConfigParser from ConfigParser import SafeConfigParser
# windows does not have termios...
try:
import termios
import tty
has_termios = True
except ImportError:
has_termios = False
RESET_SEQ = "\033[0m" RESET_SEQ = "\033[0m"
COLOR_SEQ = "\033[1;{0}m" COLOR_SEQ = "\033[1;{0}m"
BOLD_SEQ = "\033[1m" BOLD_SEQ = "\033[1m"
@ -1062,263 +1048,6 @@ class Buildozer(object):
return default return default
return self.config.getboolean(section, token) return self.config.getboolean(section, token)
class BuildozerRemote(Buildozer):
def run_command(self, args):
while args:
if not args[0].startswith('-'):
break
arg = args.pop(0)
if arg in ('-v', '--verbose'):
self.log_level = 2
elif arg in ('-p', '--profile'):
self.config_profile = args.pop(0)
elif arg in ('-h', '--help'):
self.usage()
exit(0)
elif arg == '--version':
print('Buildozer (remote) {0}'.format(__version__))
exit(0)
self._merge_config_profile()
if len(args) < 2:
self.usage()
return
remote_name = args[0]
remote_section = 'remote:{}'.format(remote_name)
if not self.config.has_section(remote_section):
self.error('Unknown remote "{}", must be configured first.'.format(
remote_name))
return
self.remote_host = remote_host = self.config.get(
remote_section, 'host', '')
self.remote_port = self.config.get(
remote_section, 'port', '22')
self.remote_user = remote_user = self.config.get(
remote_section, 'user', '')
self.remote_build_dir = remote_build_dir = self.config.get(
remote_section, 'build_directory', '')
self.remote_identity = self.config.get(
remote_section, 'identity', '')
if not remote_host:
self.error('Missing "host = " for {}'.format(remote_section))
return
if not remote_user:
self.error('Missing "user = " for {}'.format(remote_section))
return
if not remote_build_dir:
self.error('Missing "build_directory = " for {}'.format(remote_section))
return
# fake the target
self.targetname = 'remote'
self.check_build_layout()
# prepare our source code
self.info('Prepare source code to sync')
self._copy_application_sources()
self._ssh_connect()
try:
self._ensure_buildozer()
self._sync_application_sources()
self._do_remote_commands(args[1:])
self._ssh_sync(os.getcwd(), mode='get')
finally:
self._ssh_close()
def _ssh_connect(self):
self.info('Connecting to {}'.format(self.remote_host))
import paramiko
self._ssh_client = client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.load_system_host_keys()
kwargs = {}
if self.remote_identity:
kwargs['key_filename'] = expanduser(self.remote_identity)
client.connect(self.remote_host, username=self.remote_user,
port=int(self.remote_port), **kwargs)
self._sftp_client = client.open_sftp()
def _ssh_close(self):
self.debug('Closing remote connection')
self._sftp_client.close()
self._ssh_client.close()
def _ensure_buildozer(self):
s = self._sftp_client
root_dir = s.normalize('.')
self.remote_build_dir = join(root_dir, self.remote_build_dir,
self.package_full_name)
self.debug('Remote build directory: {}'.format(self.remote_build_dir))
self._ssh_mkdir(self.remote_build_dir)
self._ssh_sync(__path__[0])
def _sync_application_sources(self):
self.info('Synchronize application sources')
self._ssh_sync(self.app_dir)
# create custom buildozer.spec
self.info('Create custom buildozer.spec')
config = SafeConfigParser()
config.read('buildozer.spec')
config.set('app', 'source.dir', 'app')
fn = join(self.remote_build_dir, 'buildozer.spec')
fd = self._sftp_client.open(fn, 'wb')
config.write(fd)
fd.close()
def _do_remote_commands(self, args):
self.info('Execute remote buildozer')
cmd = (
'source ~/.profile;'
'cd {0};'
'env PYTHONPATH={0}:$PYTHONPATH '
'python -c "import buildozer, sys;'
'buildozer.Buildozer().run_command(sys.argv[1:])" {1} {2} 2>&1').format(
self.remote_build_dir,
'--verbose' if self.log_level == 2 else '',
' '.join(args),
)
self._ssh_command(cmd)
def _ssh_mkdir(self, *args):
directory = join(*args)
self.debug('Create remote directory {}'.format(directory))
try:
self._sftp_client.mkdir(directory)
except IOError:
# already created?
try:
self._sftp_client.stat(directory)
except IOError:
self.error('Unable to create remote directory {}'.format(directory))
raise
def _ssh_sync(self, directory, mode='put'):
self.debug('Syncing {} directory'.format(directory))
directory = realpath(directory)
base_strip = directory.rfind('/')
if mode == 'get':
local_dir = join(directory,'bin')
remote_dir = join(self.remote_build_dir, 'bin')
if not os.path.exists(local_dir):
os.path.makedirs(local_dir)
for _file in self._sftp_client.listdir(path=remote_dir):
self._sftp_client.get(join(remote_dir, _file),
join(local_dir, _file))
return
for root, dirs, files in walk(directory):
self._ssh_mkdir(self.remote_build_dir, root[base_strip + 1:])
for fn in files:
if splitext(fn)[1] in ('.pyo', '.pyc', '.swp'):
continue
local_file = join(root, fn)
remote_file = join(self.remote_build_dir, root[base_strip + 1:], fn)
self.debug('Sync {} -> {}'.format(local_file, remote_file))
self._sftp_client.put(local_file, remote_file)
def _ssh_command(self, command):
self.debug('Execute remote command {}'.format(command))
#shell = self._ssh_client.invoke_shell()
#shell.sendall(command)
#shell.sendall('\nexit\n')
transport = self._ssh_client.get_transport()
channel = transport.open_session()
try:
channel.exec_command(command)
self._interactive_shell(channel)
finally:
channel.close()
def _interactive_shell(self, chan):
if has_termios:
self._posix_shell(chan)
else:
self._windows_shell(chan)
def _posix_shell(self, chan):
oldtty = termios.tcgetattr(stdin)
try:
#tty.setraw(stdin.fileno())
#tty.setcbreak(stdin.fileno())
chan.settimeout(0.0)
while True:
r, w, e = select([chan, stdin], [], [])
if chan in r:
try:
x = chan.recv(128)
if len(x) == 0:
print('\r\n*** EOF\r\n',)
break
stdout.write(x)
stdout.flush()
#print len(x), repr(x)
except socket.timeout:
pass
if stdin in r:
x = stdin.read(1)
if len(x) == 0:
break
chan.sendall(x)
finally:
termios.tcsetattr(stdin, termios.TCSADRAIN, oldtty)
# thanks to Mike Looijmans for this code
def _windows_shell(self,chan):
import threading
stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n")
def writeall(sock):
while True:
data = sock.recv(256)
if not data:
stdout.write('\r\n*** EOF ***\r\n\r\n')
stdout.flush()
break
stdout.write(data)
stdout.flush()
writer = threading.Thread(target=writeall, args=(chan,))
writer.start()
try:
while True:
d = stdin.read(1)
if not d:
break
chan.send(d)
except EOFError:
# user hit ^Z or F6
pass
def run():
try:
Buildozer().run_command(sys.argv[1:])
except BuildozerCommandException:
# don't show the exception in the command line. The log already show the
# command failed.
pass
except BuildozerException as error:
Buildozer().error('%s' % error)
def run_remote():
try:
BuildozerRemote().run_command(sys.argv[1:])
except BuildozerCommandException:
pass
except BuildozerException as error:
Buildozer().error('%s' % error)
def set_config_from_envs(config): def set_config_from_envs(config):
'''Takes a ConfigParser, and checks every section/token for an '''Takes a ConfigParser, and checks every section/token for an
environment variable of the form SECTION_TOKEN, with any dots environment variable of the form SECTION_TOKEN, with any dots

49
buildozer/jsonstore.py Normal file
View file

@ -0,0 +1,49 @@
"""
Replacement for shelve, using json.
This is currently needed to correctly support db between Python 2 and 3.
"""
__all__ = ["JsonStore"]
import io
from json import load, dump
from os.path import exists
class JsonStore(object):
def __init__(self, filename):
super(JsonStore, self).__init__()
self.filename = filename
self.data = {}
if exists(filename):
try:
with io.open(filename, encoding='utf-8') as fd:
self.data = load(fd)
except ValueError:
print("Unable to read the state.db, content will be replaced.")
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
self.data[key] = value
self.sync()
def __delitem__(self, key):
del self.data[key]
self.sync()
def __contains__(self, item):
return item in self.data
def get(self, item, default=None):
return self.data.get(item, default)
def keys(self):
return self.data.keys()
def sync(self):
with open(self.filename, 'w', encoding='utf-8') as fd:
dump(self.data, fd)

View file

View file

@ -0,0 +1,22 @@
'''
Main Buildozer client
=====================
'''
import sys
from buildozer import Buildozer, BuildozerCommandException, BuildozerException
def main():
try:
Buildozer().run_command(sys.argv[1:])
except BuildozerCommandException:
# don't show the exception in the command line. The log already show
# the command failed.
pass
except BuildozerException as error:
Buildozer().error('%s' % error)
if __name__ == '__main__':
main()

286
buildozer/scripts/remote.py Normal file
View file

@ -0,0 +1,286 @@
'''
Buildozer remote
================
.. warning::
This is an experimental tool and not widely used. It might not fit for you.
Pack and send the source code to a remote SSH server, bundle buildozer with it,
and start the build on the remote.
You need paramiko to make it work.
'''
__all__ = ["BuildozerRemote"]
import socket
import sys
from buildozer import (
Buildozer, BuildozerCommandException, BuildozerException, __version__)
from sys import stdout, stdin, exit
from select import select
from os.path import join, expanduser, realpath, exists, splitext
from os import makedirs, walk, getcwd
try:
from configparser import SafeConfigParser
except ImportError:
from ConfigParser import SafeConfigParser
try:
import termios
import tty
has_termios = True
except ImportError:
has_termios = False
try:
import paramiko
except ImportError:
print('Paramiko missing: pip install paramiko')
class BuildozerRemote(Buildozer):
def run_command(self, args):
while args:
if not args[0].startswith('-'):
break
arg = args.pop(0)
if arg in ('-v', '--verbose'):
self.log_level = 2
elif arg in ('-p', '--profile'):
self.config_profile = args.pop(0)
elif arg in ('-h', '--help'):
self.usage()
exit(0)
elif arg == '--version':
print('Buildozer (remote) {0}'.format(__version__))
exit(0)
self._merge_config_profile()
if len(args) < 2:
self.usage()
return
remote_name = args[0]
remote_section = 'remote:{}'.format(remote_name)
if not self.config.has_section(remote_section):
self.error('Unknown remote "{}", must be configured first.'.format(
remote_name))
return
self.remote_host = remote_host = self.config.get(
remote_section, 'host', '')
self.remote_port = self.config.get(
remote_section, 'port', '22')
self.remote_user = remote_user = self.config.get(
remote_section, 'user', '')
self.remote_build_dir = remote_build_dir = self.config.get(
remote_section, 'build_directory', '')
self.remote_identity = self.config.get(
remote_section, 'identity', '')
if not remote_host:
self.error('Missing "host = " for {}'.format(remote_section))
return
if not remote_user:
self.error('Missing "user = " for {}'.format(remote_section))
return
if not remote_build_dir:
self.error('Missing "build_directory = " for {}'.format(remote_section))
return
# fake the target
self.targetname = 'remote'
self.check_build_layout()
# prepare our source code
self.info('Prepare source code to sync')
self._copy_application_sources()
self._ssh_connect()
try:
self._ensure_buildozer()
self._sync_application_sources()
self._do_remote_commands(args[1:])
self._ssh_sync(getcwd(), mode='get')
finally:
self._ssh_close()
def _ssh_connect(self):
self.info('Connecting to {}'.format(self.remote_host))
self._ssh_client = client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.load_system_host_keys()
kwargs = {}
if self.remote_identity:
kwargs['key_filename'] = expanduser(self.remote_identity)
client.connect(self.remote_host, username=self.remote_user,
port=int(self.remote_port), **kwargs)
self._sftp_client = client.open_sftp()
def _ssh_close(self):
self.debug('Closing remote connection')
self._sftp_client.close()
self._ssh_client.close()
def _ensure_buildozer(self):
s = self._sftp_client
root_dir = s.normalize('.')
self.remote_build_dir = join(root_dir, self.remote_build_dir,
self.package_full_name)
self.debug('Remote build directory: {}'.format(self.remote_build_dir))
self._ssh_mkdir(self.remote_build_dir)
self._ssh_sync(__path__[0])
def _sync_application_sources(self):
self.info('Synchronize application sources')
self._ssh_sync(self.app_dir)
# create custom buildozer.spec
self.info('Create custom buildozer.spec')
config = SafeConfigParser()
config.read('buildozer.spec')
config.set('app', 'source.dir', 'app')
fn = join(self.remote_build_dir, 'buildozer.spec')
fd = self._sftp_client.open(fn, 'wb')
config.write(fd)
fd.close()
def _do_remote_commands(self, args):
self.info('Execute remote buildozer')
cmd = (
'source ~/.profile;'
'cd {0};'
'env PYTHONPATH={0}:$PYTHONPATH '
'python -c "import buildozer, sys;'
'buildozer.Buildozer().run_command(sys.argv[1:])" {1} {2} 2>&1').format(
self.remote_build_dir,
'--verbose' if self.log_level == 2 else '',
' '.join(args),
)
self._ssh_command(cmd)
def _ssh_mkdir(self, *args):
directory = join(*args)
self.debug('Create remote directory {}'.format(directory))
try:
self._sftp_client.mkdir(directory)
except IOError:
# already created?
try:
self._sftp_client.stat(directory)
except IOError:
self.error('Unable to create remote directory {}'.format(directory))
raise
def _ssh_sync(self, directory, mode='put'):
self.debug('Syncing {} directory'.format(directory))
directory = realpath(directory)
base_strip = directory.rfind('/')
if mode == 'get':
local_dir = join(directory,'bin')
remote_dir = join(self.remote_build_dir, 'bin')
if not exists(local_dir):
makedirs(local_dir)
for _file in self._sftp_client.listdir(path=remote_dir):
self._sftp_client.get(join(remote_dir, _file),
join(local_dir, _file))
return
for root, dirs, files in walk(directory):
self._ssh_mkdir(self.remote_build_dir, root[base_strip + 1:])
for fn in files:
if splitext(fn)[1] in ('.pyo', '.pyc', '.swp'):
continue
local_file = join(root, fn)
remote_file = join(self.remote_build_dir, root[base_strip + 1:], fn)
self.debug('Sync {} -> {}'.format(local_file, remote_file))
self._sftp_client.put(local_file, remote_file)
def _ssh_command(self, command):
self.debug('Execute remote command {}'.format(command))
#shell = self._ssh_client.invoke_shell()
#shell.sendall(command)
#shell.sendall('\nexit\n')
transport = self._ssh_client.get_transport()
channel = transport.open_session()
try:
channel.exec_command(command)
self._interactive_shell(channel)
finally:
channel.close()
def _interactive_shell(self, chan):
if has_termios:
self._posix_shell(chan)
else:
self._windows_shell(chan)
def _posix_shell(self, chan):
oldtty = termios.tcgetattr(stdin)
try:
#tty.setraw(stdin.fileno())
#tty.setcbreak(stdin.fileno())
chan.settimeout(0.0)
while True:
r, w, e = select([chan, stdin], [], [])
if chan in r:
try:
x = chan.recv(128)
if len(x) == 0:
print('\r\n*** EOF\r\n',)
break
stdout.write(x)
stdout.flush()
#print len(x), repr(x)
except socket.timeout:
pass
if stdin in r:
x = stdin.read(1)
if len(x) == 0:
break
chan.sendall(x)
finally:
termios.tcsetattr(stdin, termios.TCSADRAIN, oldtty)
# thanks to Mike Looijmans for this code
def _windows_shell(self,chan):
import threading
stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n")
def writeall(sock):
while True:
data = sock.recv(256)
if not data:
stdout.write('\r\n*** EOF ***\r\n\r\n')
stdout.flush()
break
stdout.write(data)
stdout.flush()
writer = threading.Thread(target=writeall, args=(chan,))
writer.start()
try:
while True:
d = stdin.read(1)
if not d:
break
chan.send(d)
except EOFError:
# user hit ^Z or F6
pass
def main():
try:
BuildozerRemote().run_command(sys.argv[1:])
except BuildozerCommandException:
pass
except BuildozerException as error:
Buildozer().error('%s' % error)
if __name__ == '__main__':
main()

View file

@ -1,10 +1,33 @@
from distutils.core import setup '''
Buildozer
'''
import buildozer from setuptools import setup
import codecs
import os
import re
here = os.path.abspath(os.path.dirname(__file__))
def find_version(*file_paths):
# Open in Latin-1 so that we avoid encoding errors.
# Use codecs.open for Python 2 compatibility
with codecs.open(os.path.join(here, *file_paths), 'r', 'utf-8') as f:
version_file = f.read()
# The version line must have the form
# __version__ = 'ver'
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
setup( setup(
name='buildozer', name='buildozer',
version=buildozer.__version__, version=find_version('buildozer', '__init__.py'),
description='Generic Python packager for Android / iOS and Desktop',
author='Mathieu Virbel', author='Mathieu Virbel',
author_email='mat@kivy.org', author_email='mat@kivy.org',
url='http://github.com/kivy/buildozer', url='http://github.com/kivy/buildozer',
@ -12,8 +35,21 @@ setup(
packages=[ packages=[
'buildozer', 'buildozer',
'buildozer.targets', 'buildozer.targets',
'buildozer.libs'], 'buildozer.libs',
'buildozer.scripts'],
package_data={'buildozer': ['default.spec']}, package_data={'buildozer': ['default.spec']},
scripts=['tools/buildozer', 'tools/buildozer-remote'], include_package_data=True,
description='Generic Python packager for Android / iOS and Desktop' classifiers=[
) 'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Topic :: Software Development :: Build Tools',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.1',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3'],
entry_points={
'console_scripts': [
'buildozer=buildozer.scripts.client:main',
'buildozer-remote=buildozer.scripts.remote:main']})

View file

@ -1,5 +0,0 @@
#!/usr/bin/env python2.7
if __name__ == '__main__':
from buildozer import run
run()

View file

@ -1,5 +0,0 @@
#!/usr/bin/env python2.7
if __name__ == '__main__':
from buildozer import run_remote
run_remote()