777 lines
30 KiB
Python
Executable File
777 lines
30 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# This script was lifted from http://ros.org/wiki/regression_tests/reproducing
|
|
# Check there for updates and fixes.
|
|
# Usage:
|
|
# mkdir tmp
|
|
# ./devel_run_chroot.py --interactive --workspace tmp --distro lucid --arch i386
|
|
import subprocess
|
|
import os, sys
|
|
import time
|
|
import shutil
|
|
import tempfile
|
|
import optparse
|
|
import traceback
|
|
import urllib
|
|
|
|
# Valid options
|
|
valid_archs = ['i386', 'i686', 'amd64', 'armel']
|
|
valid_ubuntu_distros = ['hardy', 'jaunty', 'karmic', 'lucid', 'maverick', 'natty', 'oneiric']
|
|
valid_debian_distros = ['lenny', 'squeeze']
|
|
valid_redhat_distros = ['fedora-15']
|
|
|
|
# arm requires qemu > 0.13 for lucid and maverick, natty not working yet
|
|
|
|
# mock requires patched version https://bugs.launchpad.net/ubuntu/+source/mock/+bug/600564
|
|
# also you must be a member of mock group
|
|
# usermod -a -G mock myusername
|
|
def local_check_call(cmd, display_output=False):
|
|
if not display_output:
|
|
with open(os.devnull, 'w') as fh:
|
|
subprocess.check_call(cmd, stderr = fh, stdout=fh)
|
|
return
|
|
p = subprocess.Popen(cmd, stderr = subprocess.STDOUT, stdout=subprocess.PIPE)
|
|
|
|
while True:
|
|
l = p.stdout.readline()
|
|
if not l:
|
|
break
|
|
print l, ##extra comma because lines already have \n. I"m assuming this is lower overhead than l.strip()
|
|
|
|
if p.returncode == None:
|
|
#print "stdout finished but process not exited!!!"
|
|
p.communicate()
|
|
if p.returncode != 0:
|
|
raise subprocess.CalledProcessError(p.returncode, cmd)
|
|
|
|
def local_call(cmd, display_output=False):
|
|
if not display_output:
|
|
with open(os.devnull, 'w') as fh:
|
|
return subprocess.call(cmd, stderr = fh, stdout=fh)
|
|
p = subprocess.Popen(cmd, stderr = subprocess.STDOUT, stdout=subprocess.PIPE)
|
|
|
|
while True:
|
|
l = p.stdout.readline()
|
|
if not l:
|
|
break
|
|
print l, ##extra comma because lines already have \n. I"m assuming this is lower overhead than l.strip()
|
|
if p.returncode == None:
|
|
print "stdout finished but process not exited!!!"
|
|
p.communicate()
|
|
return p.returncode
|
|
# else:
|
|
# return subprocess.call(cmd, stderr = subprocess.STDOUT)
|
|
|
|
|
|
def get_mount_points(pattern = "chroot"):
|
|
mnt = subprocess.Popen("mount", stdout=subprocess.PIPE)
|
|
out = mnt.communicate()[0]
|
|
lines = out.split('\n')
|
|
mounts = []
|
|
for l in lines:
|
|
if pattern in l:
|
|
elements = l.split()
|
|
if len(elements) == 6:
|
|
mount_point = elements[2]
|
|
# TODO use os.path.ismount to verify
|
|
mounts.append(mount_point)
|
|
return mounts
|
|
|
|
def get_chroot_processes(patterns):
|
|
mnt = subprocess.Popen(["sudo", "lsof"], stdout=subprocess.PIPE)
|
|
out = mnt.communicate()[0]
|
|
lines = out.split('\n')
|
|
processes = set()
|
|
for l in lines:
|
|
for p in patterns:
|
|
if p in l:
|
|
elements = l.split()
|
|
if len(elements) > 6:
|
|
process = elements[1]
|
|
processes.add(process)
|
|
return processes
|
|
|
|
|
|
def unmount_directories(mounts):
|
|
for m in mounts:
|
|
print "Unmounting %s:"%m
|
|
cmd = "sudo umount -f %s"%m
|
|
local_call(cmd.split())
|
|
|
|
def kill_processes(processes, level=''):
|
|
for p in processes:
|
|
print "Killing %s %s:"%(level, p)
|
|
cmd = "sudo kill %s %s"%(level, p)
|
|
local_call(cmd.split())
|
|
|
|
def add_binfmg_misc_mounts(mounts):
|
|
"""
|
|
binfmg_misc gets mounted inside the chroot and prevents cleanup
|
|
add this for all proc mounts at the top.
|
|
"""
|
|
|
|
additions = [os.path.join(m, 'sys', 'fs', 'binfmt_misc') for m in mounts if m.endswith('proc')]
|
|
print "adding", additions
|
|
return additions + mounts
|
|
|
|
def clean_up_chroots():
|
|
### Try 1
|
|
mounts = get_mount_points()
|
|
mounts.reverse()
|
|
if len(mounts) > 0:
|
|
print "Cleaning up mount points", mounts
|
|
else:
|
|
print "No mounts need cleaning"
|
|
return True
|
|
|
|
mounted_processes = get_chroot_processes(mounts)
|
|
for p in mounted_processes:
|
|
print "the following processes are in chroot", p
|
|
kill_processes(mounted_processes)
|
|
|
|
remaining_processes = get_chroot_processes(mounts)
|
|
print "Remaining processes %s"%remaining_processes
|
|
|
|
mounts = add_binfmg_misc_mounts(mounts)
|
|
unmount_directories(mounts)
|
|
|
|
|
|
mounts = get_mount_points()
|
|
mounts.reverse()
|
|
# test for success
|
|
if len(remaining_processes) == 0 and len(mounts) == 0:
|
|
return True
|
|
print "Escalating to -9 kills"
|
|
|
|
remaining_processes = get_chroot_processes(mounts)
|
|
print "Remaining processes %s"%remaining_processes
|
|
|
|
|
|
kill_processes(remaining_processes, '-9')
|
|
|
|
mounts = add_binfmg_misc_mounts(mounts)
|
|
unmount_directories(mounts)
|
|
|
|
|
|
remaining_processes = get_chroot_processes(mounts)
|
|
mounts = get_mount_points()
|
|
if len(remaining_processes) == 0 and len(mounts) == 0:
|
|
return True
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
class ChrootInstance:
|
|
def __init__(self, distro, arch, path, host_workspace, clear_chroot = True, ssh_key_path = None, use_wg_sources = False, scratch_dir=None, hdd_tmp_dir=None, debug_chroot=False, repo_url=None):
|
|
#logging
|
|
self.profile = []
|
|
self.chroot_path = path
|
|
self.host_workspace = host_workspace
|
|
self.mount_path = "/tmp/workspace"
|
|
self.ccache_dir = "/tmp/ccache"
|
|
self.host_ccache_dir = "~/.ccache"
|
|
self.ccache_remote_dir = os.path.join(self.chroot_path, self.ccache_dir[1:])
|
|
self.ws_remote_path = os.path.join(self.chroot_path, self.mount_path[1:])
|
|
self.failure = False
|
|
self.arch = arch
|
|
self.distro = distro
|
|
self.clear_chroot = clear_chroot
|
|
self.workspace_successfully_copied = False
|
|
self.ssh_key_path = ssh_key_path
|
|
self.use_wg_sources = use_wg_sources
|
|
self.hdd_remote_mount = ""
|
|
self.hdd_tmp_dir = hdd_tmp_dir
|
|
self.scratch_dir = scratch_dir
|
|
self.local_scratch_dir = None
|
|
self.debug_chroot = debug_chroot # if enabled print to screen during setup and teardown
|
|
self.repo_url = repo_url
|
|
|
|
|
|
def clean(self):
|
|
self.unmount_proc_sys()
|
|
|
|
# clear chroot if it exists
|
|
print "Removing tree %s"%self.chroot_path
|
|
#shutil.rmtree(self.chroot_path, True)
|
|
cmd = ["sudo", "rm", "-rf", self.chroot_path]
|
|
print "executing", cmd
|
|
self.call(cmd)
|
|
|
|
def unmount_proc_sys(self):
|
|
cmd = ['sudo', 'umount', '-f', "%s/proc"%self.chroot_path]
|
|
print cmd
|
|
self.call(cmd)
|
|
cmd = ['sudo', 'umount', '-f', "%s/dev/pts"%self.chroot_path]
|
|
print cmd
|
|
self.call(cmd)
|
|
cmd = ['sudo', 'umount', '-f', "%s/sys"%self.chroot_path]
|
|
print cmd
|
|
self.call(cmd)
|
|
|
|
def mount_proc_sys(self):
|
|
#hack since we mount it in 2 places and umount is safe
|
|
print "unmounting before mounting to prevent double mounting"
|
|
self.unmount_proc_sys()
|
|
|
|
cmd = ['sudo', 'mount', '--bind', "/proc", "%s/proc"%self.chroot_path]
|
|
print cmd
|
|
self.call(cmd)
|
|
cmd = ['sudo', 'mount', '--bind', "/dev/pts", "%s/dev/pts"%self.chroot_path]
|
|
print cmd
|
|
self.call(cmd)
|
|
cmd = ['sudo', 'mount', '--bind', "/sys", "%s/sys"%self.chroot_path]
|
|
print cmd
|
|
self.call(cmd)
|
|
|
|
def bootstrap(self):
|
|
if self.distro in valid_debian_distros + valid_ubuntu_distros:
|
|
self.debian_bootstrap()
|
|
if self.distro in valid_redhat_distros:
|
|
self.redhat_bootstrap()
|
|
|
|
def redhat_bootstrap(self):
|
|
cmd = ['sudo', 'apt-get', 'install', 'mock']
|
|
print cmd
|
|
self.check_call(cmd)
|
|
|
|
|
|
|
|
print "ready to redhat chroot..."
|
|
|
|
cmd = ['/usr/bin/mock', '--init','--resultdir', '/tmp/result', '--configdir', '/home/tfoote/rcom/ros_release/hudson/mock_configs']
|
|
print cmd
|
|
print "This will take a few minutes. Please be patient."
|
|
self.check_call(cmd)
|
|
print "Finished mock initing"
|
|
|
|
|
|
def debian_bootstrap(self):
|
|
cmd = ['sudo', 'apt-get', 'install', 'debootstrap']
|
|
print cmd
|
|
self.check_call(cmd)
|
|
|
|
|
|
deboot_url = 'http://us.archive.ubuntu.com/ubuntu'
|
|
if self.distro in valid_debian_distros:
|
|
deboot_url = 'http://ftp.us.debian.org/debian/'
|
|
if self.distro in valid_ubuntu_distros and self.arch == 'armel':
|
|
deboot_url = 'http://ports.ubuntu.com/ubuntu-ports/'
|
|
if self.repo_url: # override if necessary
|
|
deboot_url = self.repo_url
|
|
|
|
|
|
cmd = []
|
|
if self.arch =='armel':
|
|
#cmd = ['sudo', 'build-arm-chroot', self.distro, self.chroot_path] #aptproxy doesn't have armel yet, deboot_url]
|
|
cmd = ['sudo', 'qemu-debootstrap', '--arch', self.arch, self.distro, self.chroot_path, deboot_url]
|
|
else:
|
|
cmd = ['sudo', 'debootstrap', '--arch', self.arch, self.distro, self.chroot_path, deboot_url]
|
|
print cmd
|
|
print "This will take a few minutes. Please be patient."
|
|
self.check_call(cmd)
|
|
print "Finished debootstrap"
|
|
|
|
|
|
# replicate host settings
|
|
cmd = ['sudo', 'cp', '/etc/resolv.conf', os.path.join(self.chroot_path, 'etc')]
|
|
print "Runing cmd", cmd
|
|
self.check_call(cmd)
|
|
cmd = ['sudo', 'cp', '/etc/hosts', os.path.join(self.chroot_path, 'etc')]
|
|
print "Runing cmd", cmd
|
|
self.check_call(cmd)
|
|
|
|
|
|
|
|
if self.distro in valid_ubuntu_distros:
|
|
# Move sources.list to apt-proxy
|
|
sources=os.path.join(self.chroot_path, 'etc', 'apt', 'sources.list.d', 'bootstrap.list')
|
|
|
|
with tempfile.NamedTemporaryFile() as tf:
|
|
print "Setting sources to %s"%deboot_url, sources
|
|
tf.write("deb %s %s main restricted universe multiverse\n" % (deboot_url, self.distro))
|
|
tf.write("deb %s %s-updates main restricted universe multiverse\n" % (deboot_url, self.distro))
|
|
tf.write("deb %s %s-security main restricted universe multiverse\n" % (deboot_url, self.distro))
|
|
|
|
tf.flush()
|
|
cmd = ['sudo', 'cp', tf.name, sources]
|
|
print "Runing cmd", cmd
|
|
self.check_call(cmd)
|
|
|
|
|
|
self.add_ros_sources()
|
|
|
|
# This extra source is to pull in the very latest
|
|
# nvidia-current package from our mirror. It's only guaranteed
|
|
# to be available for Lucid, but we only need it for Lucid.
|
|
if self.use_wg_sources:
|
|
self.add_wg_sources()
|
|
|
|
#disable start-stop-daemon and invokerc
|
|
|
|
with tempfile.NamedTemporaryFile() as tf:
|
|
tf.write("#!/bin/sh\n")
|
|
tf.write("exit 0\n")
|
|
tf.flush()
|
|
|
|
startstop=os.path.join(self.chroot_path,'sbin/start-stop-daemon')
|
|
print "disabling start-stop", startstop
|
|
self.check_call(['sudo', 'cp', tf.name, startstop])
|
|
|
|
invokerc=os.path.join(self.chroot_path,'usr/sbin/invoke-rc.d')
|
|
print "disabling start-stop", invokerc
|
|
self.check_call(['sudo', 'cp', tf.name, invokerc])
|
|
|
|
|
|
self.mount_proc_sys()
|
|
|
|
if self.distro in valid_ubuntu_distros:
|
|
self.execute(['locale-gen', 'en_US.UTF-8'])
|
|
|
|
self.execute(['apt-get', 'update'], robust=True)
|
|
|
|
if self.distro in valid_debian_distros:
|
|
self.execute(['apt-get', 'install', 'sudo', 'lsb-release', '-y', '--force-yes'])
|
|
|
|
# Fix the sudoers file
|
|
sudoers_path = os.path.join(self.chroot_path, 'etc/sudoers')
|
|
self.check_call(['sudo', 'chown', '0.0', sudoers_path])
|
|
|
|
print "debconf executing"
|
|
chrootcmd = ['sudo', 'chroot', self.chroot_path]
|
|
subprocess.Popen(chrootcmd + ['debconf-set-selections'], stdin=subprocess.PIPE).communicate("""
|
|
hddtemp hddtemp/port string 7634
|
|
hddtemp hddtemp/interface string 127.0.0.1
|
|
hddtemp hddtemp/daemon boolean false
|
|
hddtemp hddtemp/syslog string 0
|
|
hddtemp hddtemp/SUID_bit boolean false
|
|
sun-java6-bin shared/accepted-sun-dlj-v1-1 boolean true
|
|
sun-java6-jdk shared/accepted-sun-dlj-v1-1 boolean true
|
|
sun-java6-jre shared/accepted-sun-dlj-v1-1 boolean true
|
|
grub-pc grub2/linux_cmdline string
|
|
grub-pc grub-pc/install_devices_empty boolean true
|
|
""");
|
|
print "debconf complete"
|
|
|
|
|
|
# If we're on lucid, pull in the nvidia drivers, in case we're
|
|
# going to run Gazebo-based tests, which need the GPU.
|
|
if self.distro == 'lucid' and self.arch != 'armel':
|
|
# The --force-yes is necessary to accept the nvidia-current
|
|
# package without a valid GPG signature.
|
|
self.execute(['apt-get', 'install', '-y', '--force-yes', 'linux-headers-2.6.32-23'])
|
|
self.execute(['apt-get', 'install', '-y', '--force-yes', 'linux-headers-2.6.32-23-generic'])
|
|
self.execute(['apt-get', 'install', '-y', '--force-yes', 'linux-image-2.6.32-23-generic'])
|
|
self.execute(['apt-get', 'install', '-y', '--force-yes', 'nvidia-current'])
|
|
self.execute(['mknod', '/dev/nvidia0', 'c', '195', '0'])
|
|
self.execute(['mknod', '/dev/nvidiactl', 'c', '195', '255'])
|
|
self.execute(['chmod', '666', '/dev/nvidia0', '/dev/nvidiactl'])
|
|
|
|
cmd = ("sudo tee -a %s"%sudoers_path).split()
|
|
print "making rosbuild have no passwd", cmd
|
|
tempf = tempfile.TemporaryFile()
|
|
tempf.write("rosbuild ALL = NOPASSWD: ALL\n")
|
|
tempf.seek(0)
|
|
subprocess.check_call(cmd, stdin = tempf)
|
|
|
|
|
|
#fix sudo permissions
|
|
self.execute(['chown', '-R', 'root:root', '/usr/bin/sudo'])
|
|
self.execute(['chmod', '4755', '-R', '/usr/bin/sudo'])
|
|
|
|
|
|
if self.distro in valid_debian_distros + valid_ubuntu_distros:
|
|
self.debian_setup_rosbuild()
|
|
else:
|
|
raise NotImplementedError("non debian rosbuild setup not implemented")
|
|
|
|
def debian_setup_rosbuild(self):
|
|
cmd = "useradd rosbuild -m --groups sudo".split()
|
|
print self.execute(cmd)
|
|
|
|
self.debian_setup_ssh_client()
|
|
self.setup_svn_ssl_certs()
|
|
|
|
def add_ros_sources(self):
|
|
"""
|
|
Add code.ros.org sources to the apt sources
|
|
"""
|
|
ros_source=os.path.join(self.chroot_path, 'etc', 'apt', 'sources.list.d', 'ros-latest.list')
|
|
with tempfile.NamedTemporaryFile() as tf:
|
|
print "Adding packages.ros.org as source"
|
|
#tf.write("deb http://code.ros.org/packages/ros/ubuntu %s main\n" % self.distro)
|
|
tf.write("deb http://packages.ros.org/ros-shadow-fixed/ubuntu %s main\n" % self.distro)
|
|
tf.flush()
|
|
cmd = ['sudo', 'cp', tf.name, ros_source]
|
|
print "Runing cmd", cmd
|
|
self.check_call(cmd)
|
|
|
|
|
|
print "adding code.ros.org gpg key"
|
|
key_file = 'tmp/ros.key'
|
|
abs_key_file =os.path.join(self.chroot_path, key_file)
|
|
urllib.urlretrieve('http://code.ros.org/packages/ros.key', abs_key_file)
|
|
#with open(abs_key_file) as f:
|
|
# print "key file:", f.read()
|
|
cmd = ['apt-key', 'add', os.path.join('/', key_file)]
|
|
self.execute(cmd)
|
|
|
|
def add_wg_sources(self):
|
|
"""
|
|
Add wg-packages to apt sources for nvidia-current drivers.
|
|
"""
|
|
nvidia_source=os.path.join(self.chroot_path, 'etc', 'apt', 'sources.list.d', 'wg.list')
|
|
with tempfile.NamedTemporaryFile() as tf:
|
|
print "Adding code.ros.org as source"
|
|
tf.write("deb http://wgs1.willowgarage.com/wg-packages/ %s-wg main\n" % self.distro)
|
|
tf.flush()
|
|
cmd = ['sudo', 'cp', tf.name, nvidia_source]
|
|
print "Runing cmd", cmd
|
|
self.check_call(cmd)
|
|
|
|
|
|
print "adding wg gpg key"
|
|
key_file = 'tmp/wg.key'
|
|
abs_key_file =os.path.join(self.chroot_path, key_file)
|
|
urllib.urlretrieve('http://wgs1.willowgarage.com/wg-packages/wg.key', abs_key_file)
|
|
|
|
#with open(abs_key_file) as f:
|
|
# print "key file:", f.read()
|
|
cmd = ['apt-key', 'add', os.path.join('/', key_file)]
|
|
self.execute(cmd)
|
|
|
|
def debian_setup_ssh_client(self):
|
|
print 'Setting up ssh client'
|
|
# Pull in ssh, and drop a private key that will allow the slave to
|
|
# upload results of the build.
|
|
self.execute(['apt-get', 'install', '-y', '--force-yes', 'openssh-client'])
|
|
|
|
if self.ssh_key_path:
|
|
# Pull down a tarball of rosbuild's .ssh directory
|
|
tardestdir = os.path.join(self.chroot_path, 'home', 'rosbuild',)
|
|
#tardestname = os.path.join(tardestdir, 'rosbuild-ssh.tar')
|
|
#if not os.path.exists(tardestname):
|
|
local_tmp_dir = tempfile.mkdtemp()
|
|
local_tmp = os.path.join(local_tmp_dir, "rosbuild_ssh.tar.gz")
|
|
print "retrieving %s to %s"%(self.ssh_key_path, local_tmp)
|
|
shutil.copy(self.ssh_key_path, local_tmp)
|
|
|
|
if not os.path.exists(tardestdir):
|
|
os.makedirs(tardestdir)
|
|
print "untarring %s"%local_tmp
|
|
subprocess.check_call(['sudo', 'tar', 'xf', local_tmp], cwd=tardestdir)
|
|
#subprocess.check_call(['sudo', 'rm', '-rf', local_tmp_dir])
|
|
shutil.rmtree(local_tmp_dir)
|
|
|
|
#self.execute(['tar', 'xf', os.path.join('home', 'rosbuild', 'rosbuild-ssh.tar')], cwd=os.path.join('home', 'rosbuild'))
|
|
self.execute(['chown', '-R', 'rosbuild:rosbuild', '/home/rosbuild'])
|
|
|
|
def setup_svn_ssl_certs(self):
|
|
print 'Setting up ssl certs'
|
|
|
|
self.execute(["apt-get", "update"], robust=True)
|
|
cmd = "apt-get install subversion -y --force-yes".split()
|
|
self.execute(cmd)
|
|
|
|
cmd = "svn co https://code.ros.org/svn/ros/stacks/rosorg/trunk/rosbrowse/certs /tmp/chroot_certs".split()
|
|
self.execute(cmd)
|
|
print "successfully checked out certs"
|
|
|
|
cmd = "mkdir -p /home/rosbuild/.subversion/auth/svn.ssl.server".split()
|
|
self.execute(cmd)
|
|
|
|
|
|
cmd = ["bash", '-c', "cp /tmp/chroot_certs/* /home/rosbuild/.subversion/auth/svn.ssl.server/"]
|
|
self.execute(cmd, display=True)
|
|
|
|
self.execute(['chown', '-R', 'rosbuild:rosbuild', '/home/rosbuild/.subversion'])
|
|
|
|
|
|
def replecate_workspace(self):
|
|
print "Linking in workspace"
|
|
self.check_call(["sudo", "mkdir", "-p", self.ws_remote_path]);
|
|
# backwards compatability /tmp/ros
|
|
self.check_call(["sudo", "mkdir", "-p", os.path.join(self.ws_remote_path, "../ros")]);
|
|
self.check_call(['sudo', 'mount', '--bind', self.host_workspace, self.ws_remote_path])
|
|
#backwards compatability /tmp/ros
|
|
self.check_call(['sudo', 'mount', '--bind', self.host_workspace, os.path.join(self.ws_remote_path, "../ros")])
|
|
cmd = ['chown', '-R', 'rosbuild:rosbuild', self.mount_path]
|
|
self.execute(cmd)
|
|
|
|
if self.scratch_dir:
|
|
self.local_scratch_dir = tempfile.mkdtemp(dir=self.hdd_tmp_dir)
|
|
self.hdd_remote_mount = os.path.join(self.chroot_path, self.scratch_dir.lstrip('/'))
|
|
print "created tempdir", self.local_scratch_dir
|
|
self.check_call(['sudo', 'mkdir', '-p', self.hdd_remote_mount])
|
|
self.check_call(['sudo', 'mount', '--bind', self.local_scratch_dir, self.hdd_remote_mount])
|
|
print "mounting tempdir to %s"%os.path.join(self.chroot_path, self.scratch_dir)
|
|
|
|
|
|
def write_back_workspace(self):
|
|
|
|
print "unmounting workspace %s"%self.ws_remote_path
|
|
self.call(['ls', self.ws_remote_path])
|
|
|
|
self.call(['sudo', 'umount', '-f', self.ws_remote_path])
|
|
#backwards compatability /tmp/ros
|
|
self.call(['sudo', 'umount', '-f', os.path.join(self.ws_remote_path, "../ros")])
|
|
|
|
print "Cleaning up permissions on workspace."
|
|
self.call(['sudo', 'chown', '-R', '%d:%d'%(os.geteuid(), os.geteuid()), self.host_workspace])
|
|
|
|
|
|
if self.scratch_dir:
|
|
print "Cleaning up scratch mount %s"%self.hdd_remote_mount
|
|
self.call(['sudo', 'umount', '-f', self.hdd_remote_mount])
|
|
print "deleting tempdir", self.hdd_tmp_dir
|
|
if self.local_scratch_dir:
|
|
shutil.rmtree(self.local_scratch_dir)
|
|
else:
|
|
print >>sys.stderr, "self.local_scratch_dir should have existed if we get here."
|
|
|
|
def manual_init(self):
|
|
|
|
|
|
print "Starting init"
|
|
if self.clear_chroot and os.path.isdir(self.chroot_path):
|
|
print"Clean build requested and directory exists cleaning up old path first."
|
|
self.clean()
|
|
self.bootstrap()
|
|
elif not os.path.isdir(self.chroot_path):
|
|
self.bootstrap() # bootstrap if cleaned or uninitialized
|
|
print "finished bootstrap"
|
|
else:
|
|
print "configuring"
|
|
self.execute(['dpkg', '--configure', '-a']) # clean up in case dpkg was previously interrupted
|
|
|
|
# Even if we're reusing the chroot, we re-mount /proc and /sys.
|
|
self.mount_proc_sys()
|
|
|
|
self.replecate_workspace()
|
|
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
return self
|
|
def __exit__(self, mtype, value, tb):
|
|
if tb:
|
|
if isinstance(value, subprocess.CalledProcessError):
|
|
print "Command failed, shutting down chroot:\n-------------------------------------------\n%s\n------------------------------------------\n"%traceback.extract_tb(tb)
|
|
else:
|
|
print "Exception in chroot, shutting down chroot"
|
|
|
|
self.shutdown()
|
|
|
|
def print_profile(self):
|
|
print "chroot Profile:"
|
|
total_time = 0
|
|
for line in self.profile:
|
|
print " %.1f: %s"%(line[0], line[1])
|
|
total_time += line[0]
|
|
print "Total Time: %f"%(total_time)
|
|
|
|
|
|
|
|
def shutdown(self):
|
|
print "Shutting down chroot"
|
|
self.unmount_proc_sys()
|
|
self.write_back_workspace()
|
|
|
|
def execute(self, cmd, robust = False, user='root', display = False):
|
|
start_time = time.time()
|
|
if robust:
|
|
try:
|
|
self.execute_chroot(cmd, user, display)
|
|
except subprocess.CalledProcessError, ex:
|
|
pass
|
|
else:
|
|
self.execute_chroot(cmd, user, display)
|
|
net_time = time.time() - start_time
|
|
self.profile.append((net_time, "executed: %s"%cmd))
|
|
|
|
|
|
def execute_chroot(self, cmd, user='root', display = False):
|
|
if user == 'root':
|
|
full_cmd = ["sudo", "chroot", self.chroot_path]
|
|
full_cmd.extend(cmd)
|
|
else:
|
|
envs = []
|
|
hudson_envs = ["BUILD_NUMBER", 'BUILD_ID', 'JOB_NAME', 'BUILD_TAG', 'EXECUTOR_NUMBER', 'HUDSON_URL', 'BUILD_URL', 'JOB_URL', 'SVN_REVISION']
|
|
for k,v in os.environ.copy().iteritems():
|
|
if k in hudson_envs:
|
|
envs.append("%s='%s'"%(k, v))
|
|
full_cmd = ['sudo', 'chroot', self.chroot_path, 'su', user, '-s', '/bin/bash', '-c', '%s %s'%(" ".join(envs), " ".join(cmd))]
|
|
print "Executing", full_cmd
|
|
self.check_call(full_cmd, display)
|
|
|
|
def check_call(self, cmd, display = False):
|
|
local_check_call(cmd, display or self.debug_chroot)
|
|
|
|
def call(self, cmd, display = False):
|
|
return local_call(cmd, display or self.debug_chroot)
|
|
|
|
def run_chroot(options, path, workspace, hdd_tmp_dir):
|
|
with ChrootInstance(options.distro, options.arch, path, workspace, clear_chroot = not options.persist, ssh_key_path=options.ssh_key_path, use_wg_sources = options.use_wg_sources, scratch_dir = options.hdd_scratch, hdd_tmp_dir=hdd_tmp_dir, debug_chroot= options.debug_chroot, repo_url=options.repo_url) as chrti:
|
|
|
|
#initialization here so that if it throws the cleanup is called.
|
|
chrti.manual_init()
|
|
print "returning early for debug"
|
|
|
|
|
|
cmd = "apt-get update".split()
|
|
chrti.execute(cmd, robust=True) # continue
|
|
|
|
cmd = "apt-get install -y --force-yes build-essential python-yaml cmake subversion mercurial bzr git-core wget python-setuptools".split()
|
|
chrti.execute(cmd)
|
|
|
|
cmd = "easy_install -U rosinstall".split()
|
|
chrti.execute(cmd)
|
|
|
|
if options.arch in ['i386', 'i686']:
|
|
|
|
setarch = 'setarch %s'%(options.arch)
|
|
else:
|
|
setarch = ''
|
|
|
|
|
|
if options.script:
|
|
remote_script_name = os.path.join("/tmp", os.path.basename(options.script))
|
|
cmd = ["cp", options.script, os.path.join(chrti.chroot_path, "tmp")]
|
|
print "Executing", cmd
|
|
local_check_call(cmd);
|
|
cmd = ("chown rosbuild:rosbuild %s"%remote_script_name).split()
|
|
chrti.execute(cmd)
|
|
cmd = ("chmod +x %s"%remote_script_name).split()
|
|
chrti.execute(cmd)
|
|
cmd = [remote_script_name]
|
|
if options.arch in ['i386', 'i686']:
|
|
cmd.insert(0, options.arch)
|
|
cmd.insert(0, "setarch")
|
|
print "Executing Script", cmd
|
|
print "vvvvvvvvvvvvvvvvvvv Begin Script Output vvvvvvvvvvvvvvvvvv"
|
|
chrti.execute(cmd, user="rosbuild", display=True)
|
|
print "^^^^^^^^^^^^^^^^^^^ End Script Output ^^^^^^^^^^^^^^^^^^^^"
|
|
|
|
if options.interactive:
|
|
print "xhost localhost"
|
|
local_check_call(["xhost", "localhost"])
|
|
|
|
cmd = "apt-get install -y xterm".split()
|
|
print chrti.execute(cmd)
|
|
|
|
cmd = ["xterm", "bash"]
|
|
print chrti.execute(cmd)
|
|
|
|
|
|
print chrti.print_profile()
|
|
|
|
|
|
|
|
class TempRamFS:
|
|
def __init__(self, path, size_str):
|
|
self.path = path
|
|
self.size= size_str
|
|
|
|
def __enter__(self):
|
|
|
|
cmd = ['sudo', 'mkdir', '-p', self.path]
|
|
local_check_call(cmd)
|
|
cmd = ['sudo', 'mount', '-t', 'tmpfs', '-o', 'size=%s,mode=0755'%self.size, 'tmpfs', self.path]
|
|
local_check_call(cmd)
|
|
return self
|
|
|
|
def __exit__(self, mtype, value, tb):
|
|
if tb:
|
|
if isinstance(value, subprocess.CalledProcessError):
|
|
print >> sys.stderr, "Command failed, closing out ramdisk"
|
|
else:
|
|
print >> sys.stderr, "Caught exception, closing out ramdisk"
|
|
|
|
cmd = ['sudo', 'umount', '-f', self.path]
|
|
if not local_call(cmd):
|
|
print "WARNING: UNCLEAN TMPFS CHROOT UNMONT"
|
|
else:
|
|
print "Successfully umounted tmpfs chroot."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
parser = optparse.OptionParser()
|
|
parser.add_option("--arch", type="string", dest="arch",
|
|
help="What architecture %s"%valid_archs)
|
|
parser.add_option("--distro", type="string", dest="distro",
|
|
help="What distro %s "%(valid_ubuntu_distros + valid_debian_distros))
|
|
parser.add_option("--persist-chroot", action="store_true", dest="persist", default=False,
|
|
help="do not clear the chroot before running")
|
|
parser.add_option("--chroot-dir", action="store", dest="chroot_dir", default="/home/rosbuild/chroot",
|
|
type="string", help="Where to put the chroot, + JOB_NAME")
|
|
parser.add_option("--ramdisk-size", action="store", dest="ramdisk_size", default="20000M",
|
|
type="string", help="Ramdisk size string, default '20GB'")
|
|
parser.add_option("--ramdisk", action="store_true", dest="ramdisk", default=False,
|
|
help="Run chroot in a ramdisk")
|
|
parser.add_option("--hdd-scratch", action="store", dest="hdd_scratch", default=False,
|
|
help="Mount a tempdir on the hdd in this location in the chroot.")
|
|
parser.add_option("--use-wg-sources", action="store_true", dest="use_wg_sources", default=False,
|
|
help="Use internal wg sources.")
|
|
parser.add_option("--script", action="store", dest="script",
|
|
type="string", help="Script filename to execute on the remote machine")
|
|
parser.add_option("--ssh-key-file", action="store", dest="ssh_key_path", default=None,
|
|
type="string", help="filename to use for ssh key tarball, instead of URI")
|
|
parser.add_option("--workspace", action="store", dest="workspace", default=None,
|
|
type="string", help="The directory to replecate into the chroot. Overrides WORKSPACE in env.")
|
|
parser.add_option("--interactive", action="store_true", dest="interactive", default=False,
|
|
help="Pop up an xterm to interact in.")
|
|
parser.add_option("--debug-chroot", action="store_true", dest="debug_chroot", default=False,
|
|
help="Display chroot setup console output.")
|
|
parser.add_option("--repo-url", action="store", dest="repo_url", default=None,
|
|
type="string", help="The url of the package repo")
|
|
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
if options.distro not in (valid_ubuntu_distros + valid_debian_distros + valid_redhat_distros):
|
|
parser.error("%s is not a valid distro: %s"%(options.distro, valid_ubuntu_distros+ valid_debian_distros))
|
|
if options.arch not in valid_archs:
|
|
parser.error("%s is not a valid arch: %s"%(options.arch, valid_archs))
|
|
|
|
|
|
workspace = os.getenv("WORKSPACE")
|
|
if options.workspace:
|
|
workspace = options.workspace
|
|
if not workspace:
|
|
parser.error("you must export WORKSPACE or set --workspace")
|
|
|
|
hdd_tmp_dir = os.getenv("HDD_TMP_DIR", "/tmp")
|
|
path = os.path.join(options.chroot_dir, os.getenv("JOB_NAME", "job_name_unset"))
|
|
|
|
|
|
|
|
print "chroot path", path
|
|
print "parameters"
|
|
print "distro", options.distro
|
|
print "arch", options.arch
|
|
print "workspace", workspace
|
|
|
|
print "Checking for abandoned chroots"
|
|
if not clean_up_chroots():
|
|
print "Failed to clean up abandoned chroots, continuing."
|
|
|
|
local_check_call(['sudo', 'mkdir', '-p', path])
|
|
|
|
try:
|
|
if options.ramdisk:
|
|
with TempRamFS(path, options.ramdisk_size):
|
|
run_chroot(options, path, workspace, hdd_tmp_dir)
|
|
else:
|
|
run_chroot(options, path, workspace, hdd_tmp_dir)
|
|
sys.exit(0)
|
|
except subprocess.CalledProcessError, e:
|
|
print >> sys.stderr, "Command failed: %s"%(str(e))
|
|
sys.exit(1)
|
|
|