Files
pkgscripts-ng/EnvDeploy
2018-07-17 19:43:36 +08:00

340 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) 2000-2016 Synology Inc. All rights reserved.
import argparse
import urllib.request
import sys
import os
import subprocess
import glob
import shutil
sys.path.append(os.path.realpath(os.path.dirname(__file__)) + "/include/python")
import BuildEnv
from parallel import doPlatformParallel
from cache import cache
from chroot import Chroot
from tee import Tee
from toolkit import TarballManager
log_file = os.path.join(BuildEnv.SynoBase, 'envdeploy.log')
sys.stdout = Tee(sys.stdout, log_file)
sys.stderr = Tee(sys.stderr, log_file, move=False)
VersionMap = 'version_map'
DownloadDir = os.path.join(BuildEnv.SynoBase, 'toolkit_tarballs')
ToolkitServer = 'https://sourceforge.net/projects/dsgpl/files/toolkit'
Product = "DSM"
@cache
def split_version(version):
if '-' in version:
return version.split('-')
else:
return version, None
class EnvDeployError(RuntimeError):
pass
class TarballNotFoundError(EnvDeployError):
pass
class PlatformNotAvailableError(EnvDeployError):
pass
class DownloadToolkitError(EnvDeployError):
pass
class ToolkitDownloader:
def __init__(self, version, platforms, tarball_manager, quiet):
self._download_list = []
self.version, self.build_num = split_version(version)
self.platforms = platforms
self.tarball_manager = tarball_manager
self.quiet = quiet
self.append_base_tarball()
self.append_env_tarball()
self.append_dev_tarball()
if not os.path.isdir(DownloadDir):
os.makedirs(DownloadDir)
def _join_download_url(self, *patterns):
url = ToolkitServer
for pattern in list(patterns):
if not pattern:
continue
url += '/%s' % pattern
return url
def _download(self, url):
print("Download... " + url)
if self.quiet:
reporthook = None
else:
reporthook = self.dl_progress
try:
dest = os.path.join(DownloadDir, url.split("/")[-1])
urllib.request.urlretrieve(url, dest, reporthook=reporthook)
print("Download destination: " + dest)
except urllib.error.HTTPError:
raise DownloadToolkitError("Failed to download toolkit: " + url)
def dl_progress(self, count, dl_size, total_size):
percent = int(count * dl_size * 50 / total_size)
sys.stdout.write("[%-50s] %d%%" % ('=' * (percent-1) + ">", 2 * percent))
sys.stdout.flush()
sys.stdout.write("\b" * 102)
def _test_url_available(self, url):
try:
return int(urllib.request.urlopen(url).getcode()) == 200
except urllib.error.HTTPError:
return False
def append_base_tarball(self):
self._download_list.append(self._join_download_url(Product + self.version, self.tarball_manager.base_tarball_name))
def append_env_tarball(self):
self.append_platform_tarball_list(self.tarball_manager.get_env_tarball_name)
def append_dev_tarball(self):
self.append_platform_tarball_list(self.tarball_manager.get_dev_tarball_name)
def append_platform_tarball_list(self, get_tarball_name):
for platform in self.platforms:
self._download_list.append(self._join_download_url(Product + self.version, get_tarball_name(platform)))
def download_toolkit(self):
for url in self._download_list:
if self._test_url_available(url):
self._download(url)
else:
raise DownloadToolkitError("URL {} does not exist. Please ask synology support for assistance.".format(url))
class ToolkitDeployer:
def __init__(self, args, platforms, tarball_manager):
self.clear = args.clear
self.version, self.build_num = split_version(args.version)
self.platforms = platforms
self.suffix = args.suffix
self.tarball_manager = tarball_manager
@property
def has_pixz(self):
try:
with open(os.devnull, 'rb') as null:
subprocess.check_call(['which', 'pixz'], stdout=null, stderr=null)
except subprocess.CalledProcessError:
return False
return True
def __extract__(self, tarball, dest_dir):
cmd = ['tar']
if self.has_pixz:
cmd.append('-Ipixz')
cmd += ['-xhf', tarball, '-C', dest_dir]
print(" ".join(cmd))
subprocess.check_call(cmd)
def deploy_base_env(self, platform):
base_tarball = self.tarball_manager.base_tarball_path
self.__extract__(base_tarball, BuildEnv.getChrootSynoBase(platform, self.version, self.suffix))
def deploy_env(self, platform):
self.__extract__(self.tarball_manager.get_env_tarball_path(platform),
BuildEnv.getChrootSynoBase(platform, self.version, self.suffix))
# clear and mkdir chroot
def setup_chroot(self, platform):
chroot = BuildEnv.getChrootSynoBase(platform, self.version, self.suffix)
if not os.path.isdir(chroot):
os.makedirs(chroot)
return
if not self.clear:
return
print("Clear %s..." % chroot)
try:
with open(os.devnull, 'wb') as null:
subprocess.check_call(['umount', os.path.join(chroot, 'proc')], stderr=null)
except subprocess.CalledProcessError:
pass
for f in os.listdir(chroot):
if 'ccaches' in f:
continue
file_path = os.path.join(chroot, f)
subprocess.check_call(['rm', '-rf', file_path])
def __install_debs__(self, chroot):
with Chroot(chroot) as chroot:
deb_list = glob.glob('*.deb')
if not deb_list:
return
for deb in deb_list:
subprocess.check_call(['dpkg', '-i', chroot.get_inside_path(deb)])
os.remove(deb)
def deploy_dev(self, platform):
chroot = BuildEnv.getChrootSynoBase(platform, self.version, self.suffix)
self.__extract__(self.tarball_manager.get_dev_tarball_path(platform), chroot)
self.__install_debs__(chroot)
def adjust_chroot(self, platform):
def mkdir_source(chroot):
source_dir = os.path.join(chroot, 'source')
if not os.path.isdir(source_dir):
os.makedirs(source_dir)
def link_python(chroot):
python2_x = glob.glob(os.path.join(chroot, 'usr', 'bin', 'python2.[0-9]'))[0]
python = os.path.join(chroot, 'usr', 'bin', 'python')
if os.path.exists(python):
os.remove(python)
os.symlink(os.path.basename(python2_x), python)
def copy_user_env_config(chroot):
configs = ['/etc/hosts', '/root/.gitconfig', '/root/.ssh', '/etc/resolv.conf']
for config in configs:
dest = chroot + config
if os.path.isdir(config):
if os.path.isdir(dest):
shutil.rmtree(dest)
shutil.copytree(config, dest)
elif os.path.isfile(config):
shutil.copy(config, dest)
chroot = BuildEnv.getChrootSynoBase(platform, self.version, self.suffix)
mkdir_source(chroot)
link_python(chroot)
copy_user_env_config(chroot)
src = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
if src == 'pkgscripts':
dst = os.path.join(chroot, 'pkgscripts-ng')
elif src == 'pkgscripts-ng':
dst = os.path.join(chroot, 'pkgscripts')
else:
raise RuntimeError("Script directory should be pkgscripts or pkgscripts-ng.")
if not os.path.islink(dst):
os.symlink(src, dst)
def deploy(self):
doPlatformParallel(self.setup_chroot, self.platforms)
doPlatformParallel(self.deploy_base_env, self.platforms)
doPlatformParallel(self.deploy_env, self.platforms)
doPlatformParallel(self.deploy_dev, self.platforms)
doPlatformParallel(self.adjust_chroot, self.platforms)
def check_tarball_exists(build_num, platforms, tarball_manager):
files = []
files.append(tarball_manager.base_tarball_path)
for platform in platforms:
files.append(tarball_manager.get_dev_tarball_path(platform))
files.append(tarball_manager.get_env_tarball_path(platform))
for f in files:
if not os.path.isfile(f):
raise TarballNotFoundError("Needed file not found! " + f)
def get_all_platforms(dsm_ver, build_num):
pattern = 'AvailablePlatform_%s_%s' % (dsm_ver.split('.')[0], dsm_ver.split('.')[1])
# -v 6.0-8405
if build_num:
try:
cmd = ['git', '-C', os.path.dirname(__file__), 'show', 'origin/%sPKGDEV:include/toolkit.config' % build_num]
config = subprocess.check_output(cmd).decode().split('\n')
except subprocess.CalledProcessError:
raise PlatformNotAvailableError("Please check `%sPKGDEV' branch exist!" % build_num)
for line in config:
if pattern in line:
platforms = line.split('=')[1].strip('"').split()
# -v 6.0
else:
platforms = BuildEnv.getIncludeVariable('toolkit.config', pattern).split()
return platforms
def get_platforms(dsm_ver, build_num, platforms):
all_platforms = get_all_platforms(dsm_ver, build_num)
if not platforms:
return all_platforms
redundant_platforms = set(platforms) - set(all_platforms)
if redundant_platforms:
raise PlatformNotAvailableError("[%s] is not available platform." % " ".join(redundant_platforms))
return platforms
def parse_args(argv):
argparser = argparse.ArgumentParser()
argparser.add_argument('-v', '--version', dest='version',
help='Deploy toolkit version (6.0 or 6.0-9527), default is latest version')
argparser.add_argument('-c', '--clear', action='store_true', default=False,
help='Clear chroot before deploy')
argparser.add_argument('-t', '--tarball', dest='local_tarball', default=None, help='Use local tarball dir')
argparser.add_argument('-s', '--suffix', help='Assign build_env suffix, ex build_env-demo')
argparser.add_argument('-q', '--quiet', action='store_true', help="Don't display download status bar")
argparser.add_argument('-l', '--list', action="store_true", default=False, help='List available platforms')
argparser.add_argument('-p', dest='platforms', default="", help='Deploy platforms')
args = argparser.parse_args(argv)
args.platforms = args.platforms.split()
if not args.version:
args.version = BuildEnv.getIncludeVariable('toolkit.config', 'LatestVersion')
return args
def main(argv):
args = parse_args(argv)
dsm_ver, build_num = split_version(args.version)
platforms = get_platforms(dsm_ver, build_num, args.platforms)
tarball_root = DownloadDir
if args.list:
print("Available platforms: " + " ".join(platforms))
return
if args.local_tarball:
tarball_root = args.local_tarball
tarball_manager = TarballManager(dsm_ver, tarball_root)
if not args.local_tarball:
ToolkitDownloader(args.version, platforms, tarball_manager, args.quiet).download_toolkit()
check_tarball_exists(build_num, platforms, tarball_manager)
ToolkitDeployer(args, platforms, tarball_manager).deploy()
print("All task finished.")
if __name__ == '__main__':
try:
main(sys.argv[1:])
except EnvDeployError as e:
print("\n\033[91m%s:\033[0m" % type(e).__name__)
print(str(e))
print("\n[ERROR] " + " ".join(sys.argv) + " failed!")