[scripts] add build-cli.py

pull/170/head
lins05 2013-03-08 11:14:36 +08:00
parent fc2700d8f3
commit b0e4c253d2
5 changed files with 790 additions and 1 deletions

View File

@ -521,12 +521,21 @@ def seaf_status(args):
else:
print "%s\t%s" % (repo.name, t.state)
def setup_env():
'''In the released command line only package, we need to set
LD_LIBRARY_PATH before calling ccnet-int/ccnet/seaf-daemon
'''
if 'SEAFILE_LD_LIBRARY_PATH' in os.environ:
os.environ['LD_LIBRARY_PATH'] = os.environ['SEAFILE_LD_LIBRARY_PATH']
def main():
''' Main entry '''
_check_seafile()
setup_env()
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='subcommands', description='')
@ -588,6 +597,10 @@ def main():
parser_desync.add_argument('-c', '--confdir', help='the config directory', type=str)
parser_desync.add_argument('-d', '--folder', help='the local folder', type=str)
if len(sys.argv) == 1:
print parser.format_help()
return
args = parser.parse_args()
args.func(args)

View File

@ -2,4 +2,6 @@ if COMPILE_CLIENT
CLIENT_MANUALS = ccnet.1 seaf-daemon.1 seafile-web.1 seafile-applet.1 seaf-cli.1
endif
dist_man1_MANS = $(CLIENT_MANUALS)
dist_man1_MANS = $(CLIENT_MANUALS)
EXTRA_DIST = cli-readme.txt

82
doc/cli-readme.txt Normal file
View File

@ -0,0 +1,82 @@
Seafile command line client
===========================
For the full manual about seafile CLI client, see [https://github.com/haiwen/seafile/wiki/Seafile-CLI-client]
Table of Contents
=================
1 Requirement:
2 Get Started
2.1 Initialize
2.2 Start seafile client
2.3 Download a library from a server
2.4 stop seafile client
3 Uninstall
1 Requirement:
---------------
- python 2.6/2.7
- If you use python 2.6, you need to install python "argparse" module
see [https://pypi.python.org/pypi/argparse]
2 Get Started
--------------
2.1 Initialize
===============
mkdir ~/seafile-client
./seaf-cli init -d ~/seafile-client
2.2 Start seafile client
=========================
./seaf-cli start
2.3 Download a library from a server
=====================================
First retrieve the library id by browsing on the server -> it's in the url after "/repo/"
Then:
seaf-cli download -l "the id of the library" -s "the url + port of server" -d "the folder where the library folder will be downloaded" -u "username on server" [-p "password"]
seaf-cli status # check status of ongoing downloads
# Name Status Progress
# Apps downloading 9984/10367, 9216.1KB/s
2.4 stop seafile client
========================
./seaf-cli stop
3 Uninstall
------------
First stop the client:
seaf-cli stop
Then remove the data:
rm -rf ~/.seafile-client
rm -rf ~/.ccnet # note this should not be erased if you run the server on the same host
rm -rf seafile-cli-1.5.3

639
scripts/build/build-cli.py Executable file
View File

@ -0,0 +1,639 @@
#!/usr/bin/env python
# coding: UTF-8
'''This scirpt builds the seafile command line client (With no gui).
Some notes:
'''
import sys
####################
### Requires Python 2.6+
####################
if sys.version_info[0] == 3:
print 'Python 3 not supported yet. Quit now.'
sys.exit(1)
if sys.version_info[1] < 6:
print 'Python 2.6 or above is required. Quit now.'
sys.exit(1)
import os
import commands
import tempfile
import shutil
import re
import subprocess
import optparse
import atexit
####################
### Global variables
####################
# command line configuartion
conf = {}
# key names in the conf dictionary.
CONF_VERSION = 'version'
CONF_SEAFILE_VERSION = 'seafile_version'
CONF_LIBSEARPC_VERSION = 'libsearpc_version'
CONF_CCNET_VERSION = 'ccnet_version'
CONF_SRCDIR = 'srcdir'
CONF_KEEP = 'keep'
CONF_BUILDDIR = 'builddir'
CONF_OUTPUTDIR = 'outputdir'
CONF_THIRDPARTDIR = 'thirdpartdir'
CONF_NO_STRIP = 'nostrip'
####################
### Common helper functions
####################
def highlight(content, is_error=False):
'''Add ANSI color to content to get it highlighted on terminal'''
if is_error:
return '\x1b[1;31m%s\x1b[m' % content
else:
return '\x1b[1;32m%s\x1b[m' % content
def info(msg):
print highlight('[INFO] ') + msg
def exist_in_path(prog):
'''Test whether prog exists in system path'''
dirs = os.environ['PATH'].split(':')
for d in dirs:
if d == '':
continue
path = os.path.join(d, prog)
if os.path.exists(path):
return True
return False
def error(msg=None, usage=None):
if msg:
print highlight('[ERROR] ') + msg
if usage:
print usage
sys.exit(1)
def run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):
'''Run a program and wait it to finish, and return its exit code. The
standard output of this program is supressed.
'''
with open(os.devnull, 'w') as devnull:
if suppress_stdout:
stdout = devnull
else:
stdout = sys.stdout
if suppress_stderr:
stderr = devnull
else:
stderr = sys.stderr
proc = subprocess.Popen(argv,
cwd=cwd,
stdout=stdout,
stderr=stderr,
env=env)
return proc.wait()
def run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):
'''Like run_argv but specify a command line string instead of argv'''
with open(os.devnull, 'w') as devnull:
if suppress_stdout:
stdout = devnull
else:
stdout = sys.stdout
if suppress_stderr:
stderr = devnull
else:
stderr = sys.stderr
proc = subprocess.Popen(cmdline,
cwd=cwd,
stdout=stdout,
stderr=stderr,
env=env,
shell=True)
return proc.wait()
def must_mkdir(path):
'''Create a directory, exit on failure'''
try:
os.mkdir(path)
except OSError, e:
error('failed to create directory %s:%s' % (path, e))
def must_copy(src, dst):
'''Copy src to dst, exit on failure'''
try:
shutil.copy(src, dst)
except Exception, e:
error('failed to copy %s to %s: %s' % (src, dst, e))
class Project(object):
'''Base class for a project'''
# Probject name, i.e. libseaprc/ccnet/seafile/
name = ''
# A list of shell commands to configure/build the project
build_commands = []
def __init__(self):
# the path to pass to --prefix=/<prefix>
self.prefix = os.path.join(conf[CONF_BUILDDIR], 'seafile-cli')
self.version = self.get_version()
self.src_tarball = os.path.join(conf[CONF_SRCDIR],
'%s-%s.tar.gz' % (self.name, self.version))
# project dir, like <builddir>/seafile-1.2.2/
self.projdir = os.path.join(conf[CONF_BUILDDIR], '%s-%s' % (self.name, self.version))
def get_version(self):
# libsearpc and ccnet can have different versions from seafile.
raise NotImplementedError
def uncompress(self):
'''Uncompress the source from the tarball'''
info('Uncompressing %s' % self.name)
if run('tar xf %s' % self.src_tarball) < 0:
error('failed to uncompress source of %s' % self.name)
def build(self):
'''Build the source'''
info('Building %s' % self.name)
for cmd in self.build_commands:
if run(cmd, cwd=self.projdir) != 0:
error('error when running command:\n\t%s\n' % cmd)
class Libsearpc(Project):
name = 'libsearpc'
def __init__(self):
Project.__init__(self)
self.build_commands = [
'./configure --prefix=%s --disable-compile-demo' % self.prefix,
'make',
'make install'
]
def get_version(self):
return conf[CONF_LIBSEARPC_VERSION]
class Ccnet(Project):
name = 'ccnet'
def __init__(self):
Project.__init__(self)
self.build_commands = [
'./configure --prefix=%s --disable-compile-demo' % self.prefix,
'make',
'make install'
]
def get_version(self):
return conf[CONF_CCNET_VERSION]
class Seafile(Project):
name = 'seafile'
def __init__(self):
Project.__init__(self)
self.build_commands = [
'./configure --prefix=%s --disable-gui' % self.prefix,
'make',
'make install'
]
def get_version(self):
return conf[CONF_SEAFILE_VERSION]
def check_targz_src(proj, version, srcdir):
src_tarball = os.path.join(srcdir, '%s-%s.tar.gz' % (proj, version))
if not os.path.exists(src_tarball):
error('%s not exists' % src_tarball)
def validate_args(usage, options):
required_args = [
CONF_VERSION,
CONF_LIBSEARPC_VERSION,
CONF_CCNET_VERSION,
CONF_SEAFILE_VERSION,
CONF_SRCDIR,
]
# fist check required args
for optname in required_args:
if getattr(options, optname, None) == None:
error('%s must be specified' % optname, usage=usage)
def get_option(optname):
return getattr(options, optname)
# [ version ]
def check_project_version(version):
'''A valid version must be like 1.2.2, 1.3'''
if not re.match('^[0-9]\.[0-9](\.[0-9])?$', version):
error('%s is not a valid version' % version, usage=usage)
version = get_option(CONF_VERSION)
seafile_version = get_option(CONF_SEAFILE_VERSION)
libsearpc_version = get_option(CONF_LIBSEARPC_VERSION)
ccnet_version = get_option(CONF_CCNET_VERSION)
check_project_version(version)
check_project_version(libsearpc_version)
check_project_version(ccnet_version)
check_project_version(seafile_version)
# [ srcdir ]
srcdir = get_option(CONF_SRCDIR)
check_targz_src('libsearpc', libsearpc_version, srcdir)
check_targz_src('ccnet', ccnet_version, srcdir)
check_targz_src('seafile', seafile_version, srcdir)
# [ builddir ]
builddir = get_option(CONF_BUILDDIR)
if not os.path.exists(builddir):
error('%s does not exist' % builddir, usage=usage)
builddir = os.path.join(builddir, 'seafile-cli-build')
# [ outputdir ]
outputdir = get_option(CONF_OUTPUTDIR)
if outputdir:
if not os.path.exists(outputdir):
error('outputdir %s does not exist' % outputdir, usage=usage)
else:
outputdir = os.getcwd()
# [ keep ]
keep = get_option(CONF_KEEP)
# [ no strip]
nostrip = get_option(CONF_NO_STRIP)
conf[CONF_VERSION] = version
conf[CONF_LIBSEARPC_VERSION] = libsearpc_version
conf[CONF_SEAFILE_VERSION] = seafile_version
conf[CONF_CCNET_VERSION] = ccnet_version
conf[CONF_BUILDDIR] = builddir
conf[CONF_SRCDIR] = srcdir
conf[CONF_OUTPUTDIR] = outputdir
conf[CONF_KEEP] = keep
conf[CONF_NO_STRIP] = nostrip
prepare_builddir(builddir)
show_build_info()
def show_build_info():
'''Print all conf information. Confirm before continue.'''
info('------------------------------------------')
info('Seafile command line client %s: BUILD INFO' % conf[CONF_VERSION])
info('------------------------------------------')
info('seafile: %s' % conf[CONF_SEAFILE_VERSION])
info('ccnet: %s' % conf[CONF_CCNET_VERSION])
info('libsearpc: %s' % conf[CONF_LIBSEARPC_VERSION])
info('builddir: %s' % conf[CONF_BUILDDIR])
info('outputdir: %s' % conf[CONF_OUTPUTDIR])
info('source dir: %s' % conf[CONF_SRCDIR])
info('strip symbols: %s' % (not conf[CONF_NO_STRIP]))
info('clean on exit: %s' % (not conf[CONF_KEEP]))
info('------------------------------------------')
info('press any key to continue ')
info('------------------------------------------')
dummy = raw_input()
def prepare_builddir(builddir):
must_mkdir(builddir)
if not conf[CONF_KEEP]:
def remove_builddir():
'''Remove the builddir when exit'''
info('remove builddir before exit')
shutil.rmtree(builddir, ignore_errors=True)
atexit.register(remove_builddir)
os.chdir(builddir)
must_mkdir(os.path.join(builddir, 'seafile-cli'))
def parse_args():
parser = optparse.OptionParser()
def long_opt(opt):
return '--' + opt
parser.add_option(long_opt(CONF_VERSION),
dest=CONF_VERSION,
nargs=1,
help='the version to build. Must be digits delimited by dots, like 1.3.0')
parser.add_option(long_opt(CONF_SEAFILE_VERSION),
dest=CONF_SEAFILE_VERSION,
nargs=1,
help='the version of seafile as specified in its "configure.ac". Must be digits delimited by dots, like 1.3.0')
parser.add_option(long_opt(CONF_LIBSEARPC_VERSION),
dest=CONF_LIBSEARPC_VERSION,
nargs=1,
help='the version of libsearpc as specified in its "configure.ac". Must be digits delimited by dots, like 1.3.0')
parser.add_option(long_opt(CONF_CCNET_VERSION),
dest=CONF_CCNET_VERSION,
nargs=1,
help='the version of ccnet as specified in its "configure.ac". Must be digits delimited by dots, like 1.3.0')
parser.add_option(long_opt(CONF_BUILDDIR),
dest=CONF_BUILDDIR,
nargs=1,
help='the directory to build the source. Defaults to /tmp',
default=tempfile.gettempdir())
parser.add_option(long_opt(CONF_OUTPUTDIR),
dest=CONF_OUTPUTDIR,
nargs=1,
help='the output directory to put the generated tarball. Defaults to the current directory.',
default=os.getcwd())
parser.add_option(long_opt(CONF_SRCDIR),
dest=CONF_SRCDIR,
nargs=1,
help='''Source tarballs must be placed in this directory.''')
parser.add_option(long_opt(CONF_KEEP),
dest=CONF_KEEP,
action='store_true',
help='''keep the build directory after the script exits. By default, the script would delete the build directory at exit.''')
parser.add_option(long_opt(CONF_NO_STRIP),
dest=CONF_NO_STRIP,
action='store_true',
help='''do not strip debug symbols''')
usage = parser.format_help()
options, remain = parser.parse_args()
if remain:
error(usage=usage)
validate_args(usage, options)
def setup_build_env():
'''Setup environment variables, such as export PATH=$BUILDDDIR/bin:$PATH'''
prefix = os.path.join(conf[CONF_BUILDDIR], 'seafile-cli')
def prepend_env_value(name, value, seperator=':'):
'''append a new value to a list'''
try:
current_value = os.environ[name]
except KeyError:
current_value = ''
new_value = value
if current_value:
new_value += seperator + current_value
os.environ[name] = new_value
prepend_env_value('CPPFLAGS',
'-I%s' % os.path.join(prefix, 'include'),
seperator=' ')
if conf[CONF_NO_STRIP]:
prepend_env_value('CPPFLAGS',
'-g -O0',
seperator=' ')
prepend_env_value('LDFLAGS',
'-L%s' % os.path.join(prefix, 'lib'),
seperator=' ')
prepend_env_value('LDFLAGS',
'-L%s' % os.path.join(prefix, 'lib64'),
seperator=' ')
prepend_env_value('PATH', os.path.join(prefix, 'bin'))
prepend_env_value('PKG_CONFIG_PATH', os.path.join(prefix, 'lib', 'pkgconfig'))
prepend_env_value('PKG_CONFIG_PATH', os.path.join(prefix, 'lib64', 'pkgconfig'))
def copy_scripts_and_libs():
'''Copy scripts and shared libs'''
builddir = conf[CONF_BUILDDIR]
seafile_dir = os.path.join(builddir, Seafile().projdir)
scripts_srcdir = os.path.join(seafile_dir, 'scripts')
doc_dir = os.path.join(seafile_dir, 'doc')
cli_dir = os.path.join(builddir, 'seafile-cli')
# copy the wrapper shell script for seaf-cli.py
src = os.path.join(scripts_srcdir, 'seaf-cli-wrapper.sh')
dst = os.path.join(cli_dir, 'seaf-cli')
must_copy(src, dst)
# copy Readme for cli client
src = os.path.join(doc_dir, 'cli-readme.txt')
dst = os.path.join(cli_dir, 'Readme.txt')
must_copy(src, dst)
# rename seaf-cli to seaf-cli.py to avoid confusing users
src = os.path.join(cli_dir, 'bin', 'seaf-cli')
dst = os.path.join(cli_dir, 'bin', 'seaf-cli.py')
try:
shutil.move(src, dst)
except Exception, e:
error('failed to move %s to %s: %s' % (src, dst, e))
# copy shared c libs
copy_shared_libs()
def get_dependent_libs(executable):
syslibs = ['libsearpc', 'libccnet', 'libseafile', 'libpthread.so', 'libc.so', 'libm.so', 'librt.so', 'libdl.so', 'libselinux.so']
def is_syslib(lib):
for syslib in syslibs:
if syslib in lib:
return True
return False
ldd_output = commands.getoutput('ldd %s' % executable)
ret = []
for line in ldd_output.splitlines():
tokens = line.split()
if len(tokens) != 4:
continue
if is_syslib(tokens[0]):
continue
ret.append(tokens[2])
return ret
def copy_shared_libs():
'''copy shared c libs, such as libevent, glib, libmysqlclient'''
builddir = conf[CONF_BUILDDIR]
dst_dir = os.path.join(builddir,
'seafile-cli',
'lib')
ccnet_daemon_path = os.path.join(builddir,
'seafile-cli',
'bin',
'ccnet')
seaf_daemon_path = os.path.join(builddir,
'seafile-cli',
'bin',
'seaf-daemon')
ccnet_daemon_libs = get_dependent_libs(ccnet_daemon_path)
seaf_daemon_libs = get_dependent_libs(seaf_daemon_path)
libs = ccnet_daemon_libs
for lib in seaf_daemon_libs:
if lib not in libs:
libs.append(lib)
for lib in libs:
info('Copying %s' % lib)
shutil.copy(lib, dst_dir)
def strip_symbols():
def do_strip(fn):
run('chmod u+w %s' % fn)
info('stripping: %s' % fn)
run('strip "%s"' % fn)
def remove_static_lib(fn):
info('removing: %s' % fn)
os.remove(fn)
builddir = conf[CONF_BUILDDIR]
topdir = os.path.join(builddir, 'seafile-cli')
for parent, dnames, fnames in os.walk(topdir):
dummy = dnames # avoid pylint 'unused' warning
for fname in fnames:
fn = os.path.join(parent, fname)
if os.path.isdir(fn):
continue
if fn.endswith(".a") or fn.endswith(".la"):
remove_static_lib(fn)
continue
if os.path.islink(fn):
continue
finfo = commands.getoutput('file "%s"' % fn)
if 'not stripped' in finfo:
do_strip(fn)
def create_tarball(tarball_name):
'''call tar command to generate a tarball'''
version = conf[CONF_VERSION]
cli_dir = 'seafile-cli'
versioned_cli_dir = 'seafile-cli-' + version
# move seafile-cli to seafile-cli-${version}
try:
shutil.move(cli_dir, versioned_cli_dir)
except Exception, e:
error('failed to move %s to %s: %s' % (cli_dir, versioned_cli_dir, e))
ignored_patterns = [
# common ignored files
'*.pyc',
'*~',
'*#',
# seafile
os.path.join(versioned_cli_dir, 'share*'),
os.path.join(versioned_cli_dir, 'include*'),
os.path.join(versioned_cli_dir, 'lib', 'pkgconfig*'),
os.path.join(versioned_cli_dir, 'lib64', 'pkgconfig*'),
os.path.join(versioned_cli_dir, 'bin', 'ccnet-demo*'),
os.path.join(versioned_cli_dir, 'bin', 'ccnet-tool'),
os.path.join(versioned_cli_dir, 'bin', 'ccnet-servtool'),
os.path.join(versioned_cli_dir, 'bin', 'searpc-codegen.py'),
os.path.join(versioned_cli_dir, 'bin', 'seafile-admin'),
os.path.join(versioned_cli_dir, 'bin', 'seafile'),
]
excludes_list = [ '--exclude=%s' % pattern for pattern in ignored_patterns ]
excludes = ' '.join(excludes_list)
tar_cmd = 'tar czvf %(tarball_name)s %(versioned_cli_dir)s %(excludes)s' \
% dict(tarball_name=tarball_name,
versioned_cli_dir=versioned_cli_dir,
excludes=excludes)
if run(tar_cmd) != 0:
error('failed to generate the tarball')
def gen_tarball():
# strip symbols of libraries to reduce size
if not conf[CONF_NO_STRIP]:
try:
strip_symbols()
except Exception, e:
error('failed to strip symbols: %s' % e)
# determine the output name
# 64-bit: seafile-cli_1.2.2_x86-64.tar.gz
# 32-bit: seafile-cli_1.2.2_i386.tar.gz
version = conf[CONF_VERSION]
arch = os.uname()[-1].replace('_', '-')
if arch != 'x86-64':
arch = 'i386'
dbg = ''
if conf[CONF_NO_STRIP]:
dbg = '.dbg'
tarball_name = 'seafile-cli_%(version)s_%(arch)s%(dbg)s.tar.gz' \
% dict(version=version, arch=arch, dbg=dbg)
dst_tarball = os.path.join(conf[CONF_OUTPUTDIR], tarball_name)
# generate the tarball
try:
create_tarball(tarball_name)
except Exception, e:
error('failed to generate tarball: %s' % e)
# move tarball to outputdir
try:
shutil.copy(tarball_name, dst_tarball)
except Exception, e:
error('failed to copy %s to %s: %s' % (tarball_name, dst_tarball, e))
print '---------------------------------------------'
print 'The build is successfully. Output is:\t%s' % dst_tarball
print '---------------------------------------------'
def main():
parse_args()
setup_build_env()
libsearpc = Libsearpc()
ccnet = Ccnet()
seafile = Seafile()
libsearpc.uncompress()
libsearpc.build()
ccnet.uncompress()
ccnet.build()
seafile.uncompress()
seafile.build()
copy_scripts_and_libs()
gen_tarball()
if __name__ == '__main__':
main()

53
scripts/seaf-cli-wrapper.sh Executable file
View File

@ -0,0 +1,53 @@
#!/bin/bash
# This is a wrapper shell script for the real seaf-cli command.
# It prepares necessary environment variables and exec the real script.
# seafile cli client requires python 2.6 or 2.7
function check_python_executable() {
if [[ "$PYTHON" != "" && -x $PYTHON ]]; then
return 0
fi
if which python2.7 2>/dev/null 1>&2; then
PYTHON=python2.7
elif which python27 2>/dev/null 1>&2; then
PYTHON=python27
elif which python2.6 2>/dev/null 1>&2; then
PYTHON=python2.6
elif which python26 2>/dev/null 1>&2; then
PYTHON=python26
else
echo
echo "Can't find a python executable of version 2.6 or above in PATH"
echo "Install python 2.6+ before continue."
echo "Or if you installed it in a non-standard PATH, set the PYTHON enviroment varirable to it"
echo
exit 1
fi
}
check_python_executable
# seafile cli client requires the argparse module
if ! $PYTHON -c 'import argparse' 2>/dev/null 1>&2; then
echo
echo "Python argparse module is required"
echo "see [https://pypi.python.org/pypi/argparse]"
echo
exit 1
fi
SCRIPT=$(readlink -f "$0")
INSTALLPATH=$(dirname "${SCRIPT}")
SEAFILE_BIN_DIR=${INSTALLPATH}/bin
SEAFILE_LIB_DIR=${INSTALLPATH}/lib:${INSTALLPATH}/lib64
SEAFILE_PYTHON_PATH=${INSTALLPATH}/lib/python2.6/site-packages:${INSTALLPATH}/lib64/python2.6/site-packages:${INSTALLPATH}/lib/python2.7/site-packages:${INSTALLPATH}/lib64/python2.7/site-packages
SEAF_CLI=${SEAFILE_BIN_DIR}/seaf-cli.py
PATH=${SEAFILE_BIN_DIR}:${PATH} \
PYTHONPATH=${SEAFILE_PYTHON_PATH}:${PYTHONPATH} \
SEAFILE_LD_LIBRARY_PATH=${SEAFILE_LIB_DIR}:${LD_LIBRARY_PATH} \
exec $PYTHON ${SEAF_CLI} $@