2 # -*- coding: utf-8 -*-
3 ##############################################################################
5 # OpenERP, Open Source Management Solution
6 # Copyright (C) 2004-Today OpenERP SA (<http://www.openerp.com>).
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
32 from contextlib import contextmanager
34 from os.path import abspath, dirname, join
35 from sys import stdout
36 from tempfile import NamedTemporaryFile
39 #----------------------------------------------------------
41 #----------------------------------------------------------
42 execfile(join(dirname(__file__), '..', 'openerp', 'release.py'))
43 version = version.split('-')[0]
44 GPGPASSPHRASE = os.getenv('GPGPASSPHRASE')
45 GPGID = os.getenv('GPGID')
46 timestamp = time.strftime("%Y%m%d", time.gmtime())
63 if not os.path.isdir(d):
66 def system(l, chdir=None):
71 if isinstance(l, list):
72 rc = os.spawnvp(os.P_WAIT, l[0], l)
73 elif isinstance(l, str):
75 rc = os.spawnvp(os.P_WAIT, tmp[0], tmp)
80 def _rpc_count_modules(addr='http://127.0.0.1', port=8069, dbname='mycompany'):
82 modules = xmlrpclib.ServerProxy('%s:%s/xmlrpc/object' % (addr, port)).execute(
83 dbname, 1, 'admin', 'ir.module.module', 'search', [('state', '=', 'installed')]
85 if modules and len(modules) > 1:
87 toinstallmodules = xmlrpclib.ServerProxy('%s:%s/xmlrpc/object' % (addr, port)).execute(
88 dbname, 1, 'admin', 'ir.module.module', 'search', [('state', '=', 'to install')]
91 print("Package test: FAILED. Not able to install dependencies of base.")
92 raise Exception("Installation of package failed")
94 print("Package test: successfuly installed %s modules" % len(modules))
96 print("Package test: FAILED. Not able to install base.")
97 raise Exception("Installation of package failed")
99 def publish(o, type, releases):
100 def _publish(o, release):
102 filename = release.split(os.path.sep)[-1]
105 for EXTENSION in EXTENSIONS:
106 if filename.endswith(EXTENSION):
107 extension = EXTENSION
108 filename = filename.replace(extension, '')
110 if extension is None:
111 raise Exception("Extension of %s is not handled" % filename)
113 # keep _all or _amd64
114 if filename.count('_') > 1:
115 arch = '_' + filename.split('_')[-1]
117 release_dir = PUBLISH_DIRS[type]
118 release_filename = 'odoo_%s.%s%s%s' % (version, timestamp, arch, extension)
119 release_path = join(o.pub, release_dir, release_filename)
121 system('mkdir -p %s' % join(o.pub, release_dir))
122 shutil.move(join(o.build_dir, release), release_path)
124 # Latest/symlink handler
125 release_abspath = abspath(release_path)
126 latest_abspath = release_abspath.replace(timestamp, 'latest')
128 if os.path.islink(latest_abspath):
129 os.unlink(latest_abspath)
131 os.symlink(release_abspath, latest_abspath)
136 if isinstance(releases, basestring):
137 published.append(_publish(o, releases))
138 elif isinstance(releases, list):
139 for release in releases:
140 published.append(_publish(o, release))
143 class OdooDocker(object):
145 self.log_file = NamedTemporaryFile(mode='w+b', prefix="bash", suffix=".txt", delete=False)
146 self.port = 8069 # TODO sle: reliable way to get a free port?
147 self.prompt_re = '(\r\nroot@|bash-).*# '
150 def system(self, command):
151 self.docker.sendline(command)
152 self.docker.expect(self.prompt_re)
154 def start(self, docker_image, build_dir, pub_dir):
155 self.build_dir = build_dir
156 self.pub_dir = pub_dir
158 self.docker = pexpect.spawn(
159 'docker run -v %s:/opt/release -p 127.0.0.1:%s:8069'
160 ' -t -i %s /bin/bash --noediting' % (self.build_dir, self.port, docker_image),
163 time.sleep(2) # let the bash start
164 self.docker.logfile_read = self.log_file
165 self.id = subprocess.check_output('docker ps -l -q', shell=True)
169 _rpc_count_modules(port=str(self.port))
171 print('Exception during docker execution: %s:' % str(e))
172 print('Error during docker execution: printing the bash output:')
173 with open(self.log_file.name) as f:
174 print '\n'.join(f.readlines())
178 system('docker rm -f %s' % self.id)
179 self.log_file.close()
180 os.remove(self.log_file.name)
183 def docker(docker_image, build_dir, pub_dir):
184 _docker = OdooDocker()
186 _docker.start(docker_image, build_dir, pub_dir)
195 def __init__(self, o, image, ssh_key='', login='openerp'):
198 self.ssh_key = ssh_key
201 def timeout(self,signum,frame):
202 print "vm timeout kill",self.pid
206 l="kvm -net nic,model=rtl8139 -net user,hostfwd=tcp:127.0.0.1:10022-:22,hostfwd=tcp:127.0.0.1:18069-:8069,hostfwd=tcp:127.0.0.1:15432-:5432 -drive".split(" ")
207 #l.append('file=%s,if=virtio,index=0,boot=on,snapshot=on'%self.image)
208 l.append('file=%s,snapshot=on'%self.image)
209 #l.extend(['-vnc','127.0.0.1:1'])
210 l.append('-nographic')
212 self.pid=os.spawnvp(os.P_NOWAIT, l[0], l)
215 signal.signal(signal.SIGALRM, self.timeout)
219 signal.signal(signal.SIGALRM, signal.SIG_DFL)
224 l=['ssh','-o','UserKnownHostsFile=/dev/null','-o','StrictHostKeyChecking=no','-p','10022','-i',self.ssh_key,'%s@127.0.0.1'%self.login,cmd]
227 def rsync(self,args,options='--delete --exclude .bzrignore'):
228 cmd ='rsync -rt -e "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p 10022 -i %s" %s %s' % (self.ssh_key, options, args)
234 class KVMWinBuildExe(KVM):
236 with open(join(self.o.build_dir, 'setup/win32/Makefile.version'), 'w') as f:
237 f.write("VERSION=%s\n" % self.o.version_full)
238 with open(join(self.o.build_dir, 'setup/win32/Makefile.python'), 'w') as f:
239 f.write("PYTHON_VERSION=%s\n" % self.o.vm_winxp_python_version.replace('.', ''))
241 self.ssh("mkdir -p build")
242 self.rsync('%s/ %s@127.0.0.1:build/server/' % (self.o.build_dir, self.login))
243 self.ssh("cd build/server/setup/win32;time make allinone;")
244 self.rsync('%s@127.0.0.1:build/server/setup/win32/release/ %s/' % (self.login, self.o.build_dir), '')
245 print "KVMWinBuildExe.run(): done"
247 class KVMWinTestExe(KVM):
249 # Cannot use o.version_full when the version is not correctly parsed
250 # (for instance, containing *rc* or *dev*)
251 setuppath = glob("%s/openerp-server-setup-*.exe" % self.o.build_dir)[0]
252 setupfile = setuppath.split('/')[-1]
253 setupversion = setupfile.split('openerp-server-setup-')[1].split('.exe')[0]
255 self.rsync('"%s" %s@127.0.0.1:' % (setuppath, self.login))
256 self.ssh("TEMP=/tmp ./%s /S" % setupfile)
257 self.ssh('PGPASSWORD=openpgpwd /cygdrive/c/"Program Files"/"Odoo %s"/PostgreSQL/bin/createdb.exe -e -U openpg mycompany' % setupversion)
258 self.ssh('/cygdrive/c/"Program Files"/"Odoo %s"/server/openerp-server.exe -d mycompany -i base --stop-after-init' % setupversion)
259 self.ssh('net start odoo-server-8.0')
260 _rpc_count_modules(port=18069)
262 #----------------------------------------------------------
264 #----------------------------------------------------------
265 def _prepare_build_dir(o):
266 cmd = ['rsync', '-a', '--exclude', '.git', '--exclude', '*.pyc', '--exclude', '*.pyo']
267 system(cmd + ['%s/' % o.odoo_dir, o.build_dir])
268 for i in glob(join(o.build_dir, 'addons/*')):
269 shutil.move(i, join(o.build_dir, 'openerp/addons'))
272 system(['python2', 'setup.py', '--quiet', 'sdist'], o.build_dir)
273 system(['cp', glob('%s/dist/odoo-*.tar.gz' % o.build_dir)[0], '%s/odoo.tar.gz' % o.build_dir])
276 deb = pexpect.spawn('dpkg-buildpackage -rfakeroot -k%s' % GPGID, cwd=o.build_dir)
278 deb.expect_exact('Enter passphrase: ', timeout=1200)
279 deb.send(GPGPASSPHRASE + '\r\n')
280 deb.expect_exact('Enter passphrase: ')
281 deb.send(GPGPASSPHRASE + '\r\n')
282 deb.expect(pexpect.EOF)
283 system(['mv', glob('%s/../odoo_*.deb' % o.build_dir)[0], '%s' % o.build_dir])
284 system(['mv', glob('%s/../odoo_*.dsc' % o.build_dir)[0], '%s' % o.build_dir])
285 system(['mv', glob('%s/../odoo_*_amd64.changes' % o.build_dir)[0], '%s' % o.build_dir])
286 system(['mv', glob('%s/../odoo_*.tar.gz' % o.build_dir)[0], '%s' % o.build_dir])
289 system(['python2', 'setup.py', '--quiet', 'bdist_rpm'], o.build_dir)
290 system(['cp', glob('%s/dist/odoo-*.noarch.rpm' % o.build_dir)[0], '%s/odoo.noarch.rpm' % o.build_dir])
293 KVMWinBuildExe(o, o.vm_winxp_image, o.vm_winxp_ssh_key, o.vm_winxp_login).start()
294 system(['cp', glob('%s/openerp*.exe' % o.build_dir)[0], '%s/odoo.exe' % o.build_dir])
296 #----------------------------------------------------------
298 #----------------------------------------------------------
300 with docker('debian:stable', o.build_dir, o.pub) as wheezy:
301 wheezy.release = 'odoo.tar.gz'
302 wheezy.system('apt-get update -qq && apt-get upgrade -qq -y')
303 wheezy.system("apt-get install postgresql python-dev postgresql-server-dev-all python-pip build-essential libxml2-dev libxslt1-dev libldap2-dev libsasl2-dev libssl-dev libjpeg-dev -y")
304 wheezy.system("service postgresql start")
305 wheezy.system('su postgres -s /bin/bash -c "pg_dropcluster --stop 9.1 main"')
306 wheezy.system('su postgres -s /bin/bash -c "pg_createcluster --start -e UTF-8 9.1 main"')
307 wheezy.system('pip install -r /opt/release/requirements.txt')
308 wheezy.system('/usr/local/bin/pip install /opt/release/%s' % wheezy.release)
309 wheezy.system("useradd --system --no-create-home odoo")
310 wheezy.system('su postgres -s /bin/bash -c "createuser -s odoo"')
311 wheezy.system('su postgres -s /bin/bash -c "createdb mycompany"')
312 wheezy.system('mkdir /var/lib/odoo')
313 wheezy.system('chown odoo:odoo /var/lib/odoo')
314 wheezy.system('su odoo -s /bin/bash -c "odoo.py --addons-path=/usr/local/lib/python2.7/dist-packages/openerp/addons -d mycompany -i base --stop-after-init"')
315 wheezy.system('su odoo -s /bin/bash -c "odoo.py --addons-path=/usr/local/lib/python2.7/dist-packages/openerp/addons -d mycompany &"')
318 with docker('debian:stable', o.build_dir, o.pub) as wheezy:
319 wheezy.release = '*.deb'
320 wheezy.system('/usr/bin/apt-get update -qq && /usr/bin/apt-get upgrade -qq -y')
321 wheezy.system("apt-get install postgresql -y")
322 wheezy.system("service postgresql start")
323 wheezy.system('su postgres -s /bin/bash -c "pg_dropcluster --stop 9.1 main"')
324 wheezy.system('su postgres -s /bin/bash -c "pg_createcluster --start -e UTF-8 9.1 main"')
325 wheezy.system('su postgres -s /bin/bash -c "createdb mycompany"')
326 wheezy.system('/usr/bin/dpkg -i /opt/release/%s' % wheezy.release)
327 wheezy.system('/usr/bin/apt-get install -f -y')
328 wheezy.system('su odoo -s /bin/bash -c "odoo.py -c /etc/odoo/openerp-server.conf -d mycompany -i base --stop-after-init"')
329 wheezy.system('su odoo -s /bin/bash -c "odoo.py -c /etc/odoo/openerp-server.conf -d mycompany &"')
332 with docker('centos:centos7', o.build_dir, o.pub) as centos7:
333 centos7.release = 'odoo.noarch.rpm'
335 centos7.system('yum install -d 0 -e 0 epel-release -y')
336 centos7.system('yum update -d 0 -e 0 -y')
337 # Manual install/start of postgres
338 centos7.system('yum install -d 0 -e 0 postgresql postgresql-server postgresql-libs postgresql-contrib postgresql-devel -y')
339 centos7.system('mkdir -p /var/lib/postgres/data')
340 centos7.system('chown -R postgres:postgres /var/lib/postgres/data')
341 centos7.system('chmod 0700 /var/lib/postgres/data')
342 centos7.system('su postgres -c "initdb -D /var/lib/postgres/data -E UTF-8"')
343 centos7.system('cp /usr/share/pgsql/postgresql.conf.sample /var/lib/postgres/data/postgresql.conf')
344 centos7.system('su postgres -c "/usr/bin/pg_ctl -D /var/lib/postgres/data start"')
345 centos7.system('sleep 5')
346 centos7.system('su postgres -c "createdb mycompany"')
348 centos7.system('yum install -d 0 -e 0 /opt/release/%s -y' % centos7.release)
349 centos7.system('su odoo -s /bin/bash -c "openerp-server -c /etc/odoo/openerp-server.conf -d mycompany -i base --stop-after-init"')
350 centos7.system('su odoo -s /bin/bash -c "openerp-server -c /etc/odoo/openerp-server.conf -d mycompany &"')
353 KVMWinTestExe(o, o.vm_winxp_image, o.vm_winxp_ssh_key, o.vm_winxp_login).start()
355 #---------------------------------------------------------
356 # Generates Packages, Sources and Release files of debian package
357 #---------------------------------------------------------
358 def gen_deb_package(o, published_files):
359 # Executes command to produce file_name in path, and moves it to o.pub/deb
360 def _gen_file(o, (command, file_name), path):
361 cur_tmp_file_path = os.path.join(path, file_name)
362 with open(cur_tmp_file_path, 'w') as out:
363 subprocess.call(command, stdout=out, cwd=path)
364 system(['cp', cur_tmp_file_path, os.path.join(o.pub, 'deb', file_name)])
366 # Copy files to a temp directory (required because the working directory must contain only the
367 # files of the last release)
368 temp_path = tempfile.mkdtemp(suffix='debPackages')
369 for pub_file_path in published_files:
370 system(['cp', pub_file_path, temp_path])
373 (['dpkg-scanpackages', '.'], "Packages"), # Generate Packages file
374 (['dpkg-scansources', '.'], "Sources"), # Generate Sources file
375 (['apt-ftparchive', 'release', '.'], "Release") # Generate Release file
378 for command in commands:
379 _gen_file(o, command, temp_path)
380 # Remove temp directory
381 shutil.rmtree(temp_path)
383 # Generate Release.gpg (= signed Release)
384 # Options -abs: -a (Create ASCII armored output), -b (Make a detach signature), -s (Make a signature)
385 subprocess.call(['gpg', '--default-key', GPGID, '--passphrase', GPGPASSPHRASE, '--yes', '-abs', '--no-tty', '-o', 'Release.gpg', 'Release'], cwd=os.path.join(o.pub, 'deb'))
387 #---------------------------------------------------------
388 # Generates an RPM repo
389 #---------------------------------------------------------
390 def gen_rpm_repo(o, file_name):
392 rpmsign = pexpect.spawn('/bin/bash', ['-c', 'rpm --resign %s' % file_name], cwd=os.path.join(o.pub, 'rpm'))
393 rpmsign.expect_exact('Enter pass phrase: ')
394 rpmsign.send(GPGPASSPHRASE + '\r\n')
395 rpmsign.expect(pexpect.EOF)
397 # Removes the old repodata
398 subprocess.call(['rm', '-rf', os.path.join(o.pub, 'rpm', 'repodata')])
400 # Copy files to a temp directory (required because the working directory must contain only the
401 # files of the last release)
402 temp_path = tempfile.mkdtemp(suffix='rpmPackages')
403 subprocess.call(['cp', file_name, temp_path])
405 subprocess.call(['createrepo', temp_path]) # creates a repodata folder in temp_path
406 subprocess.call(['cp', '-r', os.path.join(temp_path, "repodata"), os.path.join(o.pub, 'rpm')])
408 # Remove temp directory
409 shutil.rmtree(temp_path)
411 #----------------------------------------------------------
413 #----------------------------------------------------------
415 op = optparse.OptionParser()
416 root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
417 build_dir = "%s-%s" % (root, timestamp)
419 op.add_option("-b", "--build-dir", default=build_dir, help="build directory (%default)", metavar="DIR")
420 op.add_option("-p", "--pub", default=None, help="pub directory (%default)", metavar="DIR")
421 op.add_option("", "--no-testing", action="store_true", help="don't test the builded packages")
422 op.add_option("-v", "--version", default='8.0', help="version (%default)")
424 op.add_option("", "--no-debian", action="store_true", help="don't build the debian package")
425 op.add_option("", "--no-rpm", action="store_true", help="don't build the rpm package")
426 op.add_option("", "--no-tarball", action="store_true", help="don't build the tarball")
427 op.add_option("", "--no-windows", action="store_true", help="don't build the windows package")
430 op.add_option("", "--vm-winxp-image", default='/home/odoo/vm/winxp27/winxp27.vdi', help="%default")
431 op.add_option("", "--vm-winxp-ssh-key", default='/home/odoo/vm/winxp27/id_rsa', help="%default")
432 op.add_option("", "--vm-winxp-login", default='Naresh', help="Windows login (%default)")
433 op.add_option("", "--vm-winxp-python-version", default='2.7', help="Windows Python version installed in the VM (default: %default)")
435 (o, args) = op.parse_args()
436 # derive other options
438 o.pkg = join(o.build_dir, 'pkg')
439 o.version_full = '%s-%s' % (o.version, timestamp)
440 o.work = join(o.build_dir, 'openerp-%s' % o.version_full)
441 o.work_addons = join(o.work, 'openerp', 'addons')
447 _prepare_build_dir(o)
454 published_files = publish(o, 'tarball', ['odoo.tar.gz'])
456 print("Won't publish the tgz release.\n Exception: %s" % str(e))
464 to_publish.append(glob("%s/odoo_*.deb" % o.build_dir)[0])
465 to_publish.append(glob("%s/odoo_*.dsc" % o.build_dir)[0])
466 to_publish.append(glob("%s/odoo_*.changes" % o.build_dir)[0])
467 to_publish.append(glob("%s/odoo_*.tar.gz" % o.build_dir)[0])
468 published_files = publish(o, 'debian', to_publish)
469 gen_deb_package(o, published_files)
471 print("Won't publish the deb release.\n Exception: %s" % str(e))
477 published_files = publish(o, 'redhat', ['odoo.noarch.rpm'])
478 gen_rpm_repo(o, published_files[0])
480 print("Won't publish the rpm release.\n Exception: %s" % str(e))
486 published_files = publish(o, 'windows', ['odoo.exe'])
488 print("Won't publish the exe release.\n Exception: %s" % str(e))
492 shutil.rmtree(o.build_dir)
493 print('Build dir %s removed' % o.build_dir)
496 system("docker rm -f `docker ps -a | awk '{print $1 }'` 2>>/dev/null")
497 print('Remaining dockers removed')
500 if __name__ == '__main__':