#!/usr/bin/env python # coding: UTF-8 '''This scirpt builds the seafile server tarball. Some notes: 1. The working directory is always the 'builddir'. 'os.chdir' is only called to change to the 'builddir'. We make use of the 'cwd' argument in 'subprocess.Popen' to run a command in a specific directory. ''' 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 glob 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_LIBSEARPC_VERSION = 'libsearpc_version' CONF_CCNET_VERSION = 'ccnet_version' CONF_SEAFILE_VERSION = 'seafile_version' CONF_SEAFILE_CLIENT_VERSION = 'seafile_client_version' CONF_SRCDIR = 'srcdir' CONF_KEEP = 'keep' CONF_BUILDDIR = 'builddir' CONF_OUTPUTDIR = 'outputdir' 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 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 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/seahub name = '' # A list of shell commands to configure/build the project build_commands = [] def __init__(self): 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 /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 get_source_commit_id(self): '''By convetion, we record the commit id of the source code in the file "/latest_commit" ''' latest_commit_file = os.path.join(self.projdir, 'latest_commit') with open(latest_commit_file, 'r') as fp: commit_id = fp.read().strip('\n\r\t ') return commit_id def append_cflags(self, macros): cflags = ' '.join([ '-D%s=%s' % (k, macros[k]) for k in macros ]) prepend_env_value('DEB_CPPFLAGS_APPEND', cflags, seperator=' ') 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 before_build(self): '''Hook method to do project-specific stuff before running build commands''' pass def build(self): '''Build the source''' self.before_build() 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 get_version(self): return conf[CONF_LIBSEARPC_VERSION] class Ccnet(Project): name = 'ccnet' def get_version(self): return conf[CONF_CCNET_VERSION] def before_build(self): macros = {} # SET CCNET_SOURCE_COMMIT_ID, so it can be printed in the log macros['CCNET_SOURCE_COMMIT_ID'] = '\\"%s\\"' % self.get_source_commit_id() self.append_cflags(macros) class SeafileClient(Project): name = 'seafile-client' def get_version(self): return conf[CONF_SEAFILE_CLIENT_VERSION] def before_build(self): pass class Seafile(Project): name = 'seafile' def __init__(self): Project.__init__(self) self.build_commands = [ 'dpkg-buildpackage -B -nc -uc -us', ] def get_version(self): return conf[CONF_SEAFILE_VERSION] def before_build(self): macros = {} # SET SEAFILE_SOURCE_COMMIT_ID, so it can be printed in the log macros['SEAFILE_SOURCE_COMMIT_ID'] = '\\"%s\\"' % self.get_source_commit_id() self.append_cflags(macros) 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_SEAFILE_CLIENT_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])+)+$', version): error('%s is not a valid version' % version, usage=usage) version = get_option(CONF_VERSION) libsearpc_version = get_option(CONF_LIBSEARPC_VERSION) ccnet_version = get_option(CONF_CCNET_VERSION) seafile_version = get_option(CONF_SEAFILE_VERSION) seafile_client_version = get_option(CONF_SEAFILE_CLIENT_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) check_targz_src('seafile-client', seafile_client_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-deb-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_CCNET_VERSION] = ccnet_version conf[CONF_SEAFILE_VERSION] = seafile_version conf[CONF_SEAFILE_CLIENT_VERSION] = seafile_client_version conf[CONF_BUILDDIR] = builddir conf[CONF_SRCDIR] = srcdir conf[CONF_OUTPUTDIR] = outputdir conf[CONF_KEEP] = True 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 debian package: BUILD INFO') info('------------------------------------------') info('seafile: %s' % conf[CONF_SEAFILE_VERSION]) info('seafile-client: %s' % conf[CONF_SEAFILE_CLIENT_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) 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_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_SEAFILE_VERSION), dest=CONF_SEAFILE_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_SEAFILE_CLIENT_VERSION), dest=CONF_SEAFILE_CLIENT_VERSION, nargs=1, help='the version of seafile-client. 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 server 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(Seafile().projdir, 'debian', 'seafile', 'usr') prepend_env_value('DEB_CPPFLAGS_APPEND', '-DSEAFILE_CLIENT_VERSION=\\"%s\\"' % conf[CONF_VERSION], seperator=' ') if conf[CONF_NO_STRIP]: prepend_env_value('DEB_CPPFLAGS_APPEND', '-g -O0', seperator=' ') os.environ['SEAFILE_NOSTRIP'] = 'true' os.environ['DEB_CFLAGS_SET'] = '' os.environ['DEB_CXXFLAGS_SET'] = '' prepend_env_value('PATH', os.path.join(prefix, 'bin')) prepend_env_value('PKG_CONFIG_PATH', os.path.join(prefix, 'lib', 'pkgconfig')) os.environ['LIBSEARPC_SOURCE_DIR'] = Libsearpc().projdir os.environ['CCNET_SOURCE_DIR'] = Ccnet().projdir os.environ['SEAFILE_CLIENT_SOURCE_DIR'] = SeafileClient().projdir def move_deb(): builddir = conf[CONF_BUILDDIR] deb_name = glob.glob('*.deb')[0] src_deb = os.path.join(builddir, deb_name) dst_deb = os.path.join(conf[CONF_OUTPUTDIR], deb_name) # move deb to outputdir try: shutil.move(src_deb, dst_deb) except Exception, e: error('failed to copy %s to %s: %s' % (src_deb, dst_deb, e)) print '---------------------------------------------' print 'The build is successfully. Output is:\t%s' % dst_deb print '---------------------------------------------' def main(): parse_args() setup_build_env() libsearpc = Libsearpc() ccnet = Ccnet() seafile = Seafile() seafile_client = SeafileClient() libsearpc.uncompress() ccnet.uncompress() seafile.uncompress() seafile_client.uncompress() libsearpc.build() ccnet.build() seafile.build() seafile_client.build() move_deb() if __name__ == '__main__': main()