/bin/partner-repacks.py

https://github.com/lmorchard/byob · Python · 886 lines · 696 code · 104 blank · 86 comment · 157 complexity · 1a8b0bb43bf093f4c5e9501e35ebf576 MD5 · raw file

  1. #!/usr/bin/env python
  2. import os, re, sys
  3. from shutil import copy, copytree, move
  4. import subprocess
  5. from subprocess import Popen
  6. from optparse import OptionParser
  7. import urllib
  8. # Set default values.
  9. PARTNERS_DIR = '../partners'
  10. BUILD_NUMBER = '1'
  11. STAGING_SERVER = 'stage.mozilla.org'
  12. HGROOT = 'http://hg.mozilla.org'
  13. REPO = 'releases/mozilla-1.9.2'
  14. PKG_DMG = 'pkg-dmg'
  15. SEVENZIP_BIN = '7za'
  16. UPX_BIN = 'upx'
  17. SBOX_HOME = '/scratchbox/users/cltbld/home/cltbld/'
  18. SBOX_PATH = '/scratchbox/moz_scratchbox'
  19. SEVENZIP_BUNDLE = 'app.7z'
  20. SEVENZIP_APPTAG = 'app.tag'
  21. SEVENZIP_APPTAG_PATH = os.path.join('browser/installer/windows', SEVENZIP_APPTAG)
  22. SEVENZIP_HEADER = '7zSD.sfx'
  23. SEVENZIP_HEADER_PATH = os.path.join('other-licenses/7zstub/firefox', SEVENZIP_HEADER)
  24. SEVENZIP_HEADER_COMPRESSED = SEVENZIP_HEADER + '.compressed'
  25. #########################################################################
  26. # Source:
  27. # http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
  28. def which(program):
  29. def is_exe(fpath):
  30. return os.path.exists(fpath) and os.access(fpath, os.X_OK)
  31. fpath, fname = os.path.split(program)
  32. if fpath:
  33. if is_exe(program):
  34. return program
  35. else:
  36. for path in os.environ["PATH"].split(os.pathsep):
  37. exe_file = os.path.join(path, program)
  38. if is_exe(exe_file):
  39. return exe_file
  40. return None
  41. #########################################################################
  42. def rmdirRecursive(dir):
  43. """This is a replacement for shutil.rmtree that works better under
  44. windows. Thanks to Bear at the OSAF for the code.
  45. (Borrowed from buildbot.slave.commands)"""
  46. if not os.path.exists(dir):
  47. # This handles broken links
  48. if os.path.islink(dir):
  49. os.remove(dir)
  50. return
  51. if os.path.islink(dir):
  52. os.remove(dir)
  53. return
  54. # Verify the directory is read/write/execute for the current user
  55. os.chmod(dir, 0700)
  56. for name in os.listdir(dir):
  57. full_name = os.path.join(dir, name)
  58. # on Windows, if we don't have write permission we can't remove
  59. # the file/directory either, so turn that on
  60. if os.name == 'nt':
  61. if not os.access(full_name, os.W_OK):
  62. # I think this is now redundant, but I don't have an NT
  63. # machine to test on, so I'm going to leave it in place
  64. # -warner
  65. os.chmod(full_name, 0600)
  66. if os.path.isdir(full_name):
  67. rmdirRecursive(full_name)
  68. else:
  69. # Don't try to chmod links
  70. if not os.path.islink(full_name):
  71. os.chmod(full_name, 0700)
  72. os.remove(full_name)
  73. os.rmdir(dir)
  74. #########################################################################
  75. def printSeparator():
  76. print "##################################################"
  77. #########################################################################
  78. def shellCommand(cmd):
  79. # Shell command output gets dumped immediately to stdout, whereas
  80. # print statements get buffered unless we flush them explicitly.
  81. sys.stdout.flush()
  82. p = Popen(cmd, shell=True)
  83. (rpid, ret) = os.waitpid(p.pid, 0)
  84. if ret != 0:
  85. ret_real = (ret & 0xFF00) >> 8
  86. print "Error: shellCommand had non-zero exit status: %d" % ret_real
  87. print "command was: %s" % cmd
  88. sys.exit(ret_real)
  89. return True
  90. #########################################################################
  91. def mkdir(dir, mode=0777):
  92. if not os.path.exists(dir):
  93. return os.makedirs(dir, mode)
  94. return True
  95. #########################################################################
  96. def isLinux(platform):
  97. if (platform.find('linux') != -1):
  98. return True
  99. return False
  100. #########################################################################
  101. def isLinux32(platform):
  102. if (platform.find('linux32') != -1 or platform.find('linux-i686') != -1):
  103. return True
  104. return False
  105. #########################################################################
  106. def isLinux64(platform):
  107. if (platform.find('linux64') != -1 or platform.find('linux-x86_64') != -1):
  108. return True
  109. return False
  110. #########################################################################
  111. def isMac(platform):
  112. if (platform.find('mac') != -1):
  113. return True
  114. return False
  115. #########################################################################
  116. def isMac32(platform):
  117. return isMac(platform)
  118. #########################################################################
  119. def isMac64(platform):
  120. if (platform.find('mac64') != -1):
  121. return True
  122. return False
  123. #########################################################################
  124. def isWin(platform):
  125. if (platform.find('win') != -1):
  126. return True
  127. return False
  128. #########################################################################
  129. def isWin32(platform):
  130. if (platform.find('win32') != -1):
  131. return True
  132. return False
  133. #########################################################################
  134. def isWin64(platform):
  135. if (platform.find('win64') != -1 or \
  136. platform.find('win64-x86_64') != -1):
  137. return True
  138. return False
  139. #########################################################################
  140. def isMaemo(platform):
  141. if (platform.find('maemo') != -1):
  142. return True
  143. return False
  144. #########################################################################
  145. def isValidPlatform(platform):
  146. if isLinux64(platform) or isLinux32(platform) or \
  147. isMac64(platform) or isMac32(platform) or \
  148. isWin64(platform) or isWin32(platform) or \
  149. isMaemo(platform):
  150. return True
  151. return False
  152. #########################################################################
  153. def createTagFromVersion(version):
  154. return 'FIREFOX_' + str(version).replace('.','_') + '_RELEASE'
  155. #########################################################################
  156. def parseRepackConfig(file, platforms):
  157. config = {}
  158. config['platforms'] = []
  159. f= open(file, 'r')
  160. for line in f:
  161. line = line.rstrip("\n")
  162. [key, value] = line.split('=',2)
  163. value = value.strip('"')
  164. if key == 'dist_id':
  165. config['dist_id'] = value
  166. continue
  167. if key == 'locales':
  168. config['locales'] = value.split(' ')
  169. continue
  170. if key == 'migrationWizardDisabled':
  171. if value.lower() == 'true':
  172. config['migrationWizardDisabled'] = True
  173. continue
  174. if key == 'deb_section':
  175. config['deb_section'] = re.sub('/', '\/', value)
  176. if isValidPlatform(key):
  177. platform_formatted = getFormattedPlatform(key)
  178. if platform_formatted in platforms and value.lower() == 'true':
  179. config['platforms'].append(platform_formatted)
  180. continue
  181. if config['platforms']:
  182. return config
  183. #########################################################################
  184. def getFormattedPlatform(platform):
  185. '''Returns the platform in the format used in building package names.
  186. '''
  187. if isLinux64(platform):
  188. return "linux-x86_64"
  189. if isLinux(platform):
  190. return "linux-i686"
  191. if isMac64(platform):
  192. return "mac64"
  193. if isMac(platform):
  194. return "mac"
  195. if isWin64(platform):
  196. return "win64-x86_64"
  197. if isWin(platform):
  198. return "win32"
  199. if isMaemo(platform):
  200. return platform
  201. return None
  202. #########################################################################
  203. def getFilename(version, platform, locale, file_ext):
  204. '''Returns the properly formatted filename based on the version string.
  205. File location/nomenclature changed starting with 3.5.
  206. '''
  207. version_formatted = version
  208. # Deal with alpha/beta releases.
  209. m = re.match('(\d\.\d)(a|b|rc)(\d+)', version)
  210. if m:
  211. if m.group(2) == 'b':
  212. greek = "Beta"
  213. elif m.group(2) == 'a':
  214. greek = "Alpha"
  215. else:
  216. greek = "RC"
  217. version_formatted = "%s %s %s" % (m.group(1), greek, m.group(3))
  218. if version.startswith('3.0'):
  219. return "firefox-%s.%s.%s.%s" % (version,
  220. locale,
  221. platform,
  222. file_ext)
  223. else:
  224. if isLinux(platform):
  225. return "firefox-%s.%s" % (version,
  226. file_ext)
  227. if isMac(platform):
  228. return "Firefox %s.%s" % (version_formatted,
  229. file_ext)
  230. if isWin(platform):
  231. return "Firefox Setup %s.%s" % (version_formatted,
  232. file_ext)
  233. if isMaemo(platform):
  234. deb_name_url = "http://%s%s/%s/%s/deb_name.txt" % \
  235. (options.staging_server,
  236. candidates_web_dir,
  237. platform_formatted,
  238. locale)
  239. filename = re.sub('\n', '', Popen(['curl', deb_name_url],
  240. stdout=subprocess.PIPE).communicate()[0])
  241. return filename
  242. return None
  243. #########################################################################
  244. def getLocalFilePath(version, base_dir, platform, locale):
  245. '''Returns the properly formatted filepath based on the version string.
  246. File location/nomenclature changed starting with 3.5.
  247. '''
  248. if version.startswith('3.0'):
  249. return "%s" % (base_dir)
  250. return "%s/%s/%s" % (base_dir, platform, locale)
  251. #########################################################################
  252. def getFileExtension(version, platform):
  253. if isLinux(platform):
  254. return "tar.bz2"
  255. if isMac(platform):
  256. return "dmg"
  257. if isWin(platform):
  258. if version.startswith('3.0'):
  259. return "installer.exe"
  260. else:
  261. return "exe"
  262. return None
  263. #########################################################################
  264. class RepackBase(object):
  265. def __init__(self, build, partner_dir, build_dir, working_dir, final_dir,
  266. platform_formatted, repack_info):
  267. self.base_dir = os.getcwd()
  268. self.build = build
  269. self.full_build_path = "%s/%s/%s" % (self.base_dir, build_dir, build)
  270. self.full_partner_path = "%s/%s" % (self.base_dir, partner_dir)
  271. self.working_dir = working_dir
  272. self.final_dir = final_dir
  273. self.platform_formatted = platform_formatted
  274. self.repack_info = repack_info
  275. mkdir(self.working_dir)
  276. def announceStart(self):
  277. print "### Repacking %s build \"%s\"" % (self.platform_formatted,
  278. self.build)
  279. def announceSuccess(self):
  280. print "### Done repacking %s build \"%s\"" % (self.platform_formatted,
  281. self.build)
  282. print
  283. def unpackBuild(self):
  284. copy(self.full_build_path, '.')
  285. def createOverrideIni(self, partner_path):
  286. ''' Some partners need to override the migration wizard. This is done
  287. by adding an override.ini file to the base install dir.
  288. '''
  289. filename='%s/override.ini' % partner_path
  290. if self.repack_info.has_key('migrationWizardDisabled'):
  291. if not os.path.isfile(filename):
  292. f=open(filename,'w')
  293. f.write('[XRE]\n')
  294. f.write('EnableProfileMigrator=0\n')
  295. f.close()
  296. def copyFiles(self, platform_dir):
  297. # Check whether we've already copied files over for this partner.
  298. if not os.path.exists(platform_dir):
  299. mkdir(platform_dir)
  300. for i in ['distribution', 'extensions', 'searchplugins']:
  301. full_path = "%s/%s" % (self.full_partner_path, i)
  302. if os.path.exists(full_path):
  303. copytree(full_path, "%s/%s" % (platform_dir,i))
  304. self.createOverrideIni(platform_dir)
  305. def repackBuild(self):
  306. pass
  307. def cleanup(self):
  308. if self.final_dir == '.':
  309. move(self.build, '..')
  310. else:
  311. move(self.build, "../%s" % self.final_dir)
  312. def doRepack(self):
  313. self.announceStart()
  314. os.chdir(self.working_dir)
  315. self.unpackBuild()
  316. self.copyFiles()
  317. self.repackBuild()
  318. self.announceSuccess()
  319. self.cleanup()
  320. os.chdir(self.base_dir)
  321. #########################################################################
  322. class RepackLinux(RepackBase):
  323. def __init__(self, build, partner_dir, build_dir, working_dir, final_dir,
  324. platform_formatted, repack_info):
  325. super(RepackLinux, self).__init__(build, partner_dir, build_dir,
  326. working_dir, final_dir,
  327. platform_formatted, repack_info)
  328. self.uncompressed_build = build.replace('.bz2','')
  329. def unpackBuild(self):
  330. super(RepackLinux, self).unpackBuild()
  331. bunzip2_cmd = "bunzip2 %s" % self.build
  332. shellCommand(bunzip2_cmd)
  333. if not os.path.exists(self.uncompressed_build):
  334. print "Error: Unable to uncompress build %s" % self.build
  335. sys.exit(1)
  336. def copyFiles(self):
  337. super(RepackLinux, self).copyFiles('firefox')
  338. def repackBuild(self):
  339. if options.quiet:
  340. tar_flags = "rf"
  341. else:
  342. tar_flags = "rvf"
  343. tar_cmd = "tar %s %s firefox" % (tar_flags, self.uncompressed_build)
  344. shellCommand(tar_cmd)
  345. bzip2_command = "bzip2 %s" % self.uncompressed_build
  346. shellCommand(bzip2_command)
  347. #########################################################################
  348. class RepackMac(RepackBase):
  349. def __init__(self, build, partner_dir, build_dir, working_dir, final_dir,
  350. platform_formatted, repack_info):
  351. super(RepackMac, self).__init__(build, partner_dir, build_dir,
  352. working_dir, final_dir,
  353. platform_formatted, repack_info)
  354. self.mountpoint = "/tmp/FirefoxInstaller"
  355. def unpackBuild(self):
  356. mkdir(self.mountpoint)
  357. # Verify that Firefox isn't currently mounted on our mountpoint.
  358. if os.path.exists("%s/Firefox.app" % self.mountpoint):
  359. print "Error: Firefox is already mounted at %s" % self.mountpoint
  360. sys.exit(1)
  361. if options.quiet:
  362. quiet_flag = "-quiet"
  363. else:
  364. quiet_flag = ""
  365. attach_cmd = "hdiutil attach -mountpoint %s -readonly -private %s -noautoopen \"%s\"" % (self.mountpoint, quiet_flag, self.full_build_path)
  366. shellCommand(attach_cmd)
  367. rsync_cmd = "rsync -a %s/ stage/" % self.mountpoint
  368. shellCommand(rsync_cmd)
  369. eject_cmd = "hdiutil eject %s %s" % (quiet_flag, self.mountpoint)
  370. shellCommand(eject_cmd)
  371. # Disk images contain a link " " to "Applications/" that we need
  372. # to get rid of while working with it uncompressed.
  373. os.remove("stage/ ")
  374. def copyFiles(self):
  375. for i in ['distribution', 'extensions', 'searchplugins']:
  376. full_path = "%s/%s" % (self.full_partner_path, i)
  377. if os.path.exists(full_path):
  378. cp_cmd = "cp -r %s stage/Firefox.app/Contents/MacOS" % full_path
  379. shellCommand(cp_cmd)
  380. self.createOverrideIni('stage/Firefox.app/Contents/MacOS')
  381. def repackBuild(self):
  382. if options.quiet:
  383. quiet_flag = "--verbosity 0"
  384. else:
  385. quiet_flag = ""
  386. pkg_cmd = "%s --source stage/ --target \"%s\" --volname 'Firefox' --icon stage/.VolumeIcon.icns --symlink '/Applications':' ' %s" % (options.pkg_dmg,
  387. self.build,
  388. quiet_flag)
  389. shellCommand(pkg_cmd)
  390. def cleanup(self):
  391. super(RepackMac, self).cleanup()
  392. rmdirRecursive("stage")
  393. rmdirRecursive(self.mountpoint)
  394. #########################################################################
  395. class RepackWin(RepackBase):
  396. def __init__(self, build, partner_dir, build_dir, working_dir, final_dir,
  397. platform_formatted, repack_info):
  398. super(RepackWin, self).__init__(build, partner_dir, build_dir,
  399. working_dir, final_dir,
  400. platform_formatted, repack_info)
  401. def copyFiles(self):
  402. super(RepackWin, self).copyFiles('nonlocalized')
  403. def repackBuild(self):
  404. if options.quiet:
  405. zip_redirect = ">/dev/null"
  406. else:
  407. zip_redirect = ""
  408. zip_cmd = "%s a \"%s\" nonlocalized %s" % (SEVENZIP_BIN,
  409. self.build,
  410. zip_redirect)
  411. shellCommand(zip_cmd)
  412. #########################################################################
  413. class RepackMaemo(RepackBase):
  414. def __init__(self, build, partner_dir, build_dir, working_dir, final_dir,
  415. platform_formatted, repack_info, sbox_path=SBOX_PATH,
  416. sbox_home=SBOX_HOME):
  417. super(RepackMaemo, self).__init__(build, partner_dir, build_dir,
  418. working_dir, final_dir,
  419. platform_formatted, repack_info)
  420. self.sbox_path = sbox_path
  421. self.sbox_home = sbox_home
  422. self.tmpdir = "%s/tmp_deb" % self.base_dir
  423. def unpackBuild(self):
  424. mkdir("%s/DEBIAN" % self.tmpdir)
  425. super(RepackMaemo, self).unpackBuild()
  426. commandList = [
  427. 'ar -p %s data.tar.gz | tar -zx -C %s' % (self.build, self.tmpdir),
  428. 'ar -p %s control.tar.gz | tar -zx -C %s/DEBIAN' % (self.build,
  429. self.tmpdir)
  430. ]
  431. for command in commandList:
  432. status = shellCommand(command)
  433. if not status:
  434. print "Error while running '%s'." % command
  435. sys.exit(status)
  436. def copyFiles(self):
  437. full_path = "%s/preferences" % self.full_partner_path
  438. if os.path.exists(full_path):
  439. cp_cmd = "cp %s/* %s/opt/mozilla/[a-z\-\.0-9]*/defaults/pref/" % \
  440. (full_path, self.tmpdir)
  441. shellCommand(cp_cmd)
  442. def mungeControl(self):
  443. print self.repack_info
  444. if 'deb_section' in self.repack_info:
  445. munge_cmd="sed -i -e 's/^Section: .*$/Section: %s/' %s/DEBIAN/control" % (self.repack_info['deb_section'], self.tmpdir)
  446. print munge_cmd
  447. shellCommand(munge_cmd)
  448. def repackBuild(self):
  449. rel_base_dir = re.sub('^.*%s' % self.sbox_home, '', self.base_dir)
  450. repack_cmd = '%s -p -d %s "dpkg-deb -b tmp_deb %s"' % (self.sbox_path,
  451. rel_base_dir,
  452. self.build)
  453. print repack_cmd
  454. shellCommand(repack_cmd)
  455. print self.build
  456. def cleanup(self):
  457. print self.final_dir
  458. move(os.path.join(self.base_dir, self.build), "../%s" % self.final_dir)
  459. rmdirRecursive(self.tmpdir)
  460. def doRepack(self):
  461. self.announceStart()
  462. os.chdir(self.working_dir)
  463. self.unpackBuild()
  464. self.copyFiles()
  465. self.mungeControl()
  466. self.repackBuild()
  467. self.announceSuccess()
  468. self.cleanup()
  469. os.chdir(self.base_dir)
  470. #########################################################################
  471. def repackSignedBuilds(repack_dir):
  472. if not os.path.isdir(repack_dir):
  473. return False
  474. base_dir = os.getcwd()
  475. if not os.path.exists(SEVENZIP_APPTAG):
  476. if not getSingleFileFromHg(SEVENZIP_APPTAG_PATH):
  477. print "Error: Unable to retrieve %s" % SEVENZIP_APPTAG
  478. sys.exit(1)
  479. if not os.path.exists(SEVENZIP_HEADER_COMPRESSED):
  480. if not os.path.exists(SEVENZIP_HEADER) and \
  481. not getSingleFileFromHg(SEVENZIP_HEADER_PATH):
  482. print "Error: Unable to retrieve %s" % SEVENZIP_HEADER
  483. sys.exit(1)
  484. upx_cmd = '%s --best -o \"%s\" \"%s\"' % (UPX_BIN,
  485. SEVENZIP_HEADER_COMPRESSED,
  486. SEVENZIP_HEADER)
  487. shellCommand(upx_cmd)
  488. if not os.path.exists(SEVENZIP_HEADER_COMPRESSED):
  489. print "Error: Unable to compress %s" % SEVENZIP_HEADER
  490. sys.exit(1)
  491. for f in [SEVENZIP_HEADER_COMPRESSED, SEVENZIP_APPTAG, 'repack-signed.sh']:
  492. copy(f, repack_dir)
  493. os.chdir(repack_dir)
  494. print "Running repack.sh"
  495. shellCommand('./repack-signed.sh')
  496. for f in [SEVENZIP_HEADER_COMPRESSED, SEVENZIP_APPTAG, 'repack-signed.sh']:
  497. os.remove(f)
  498. os.chdir(base_dir)
  499. #########################################################################
  500. def retrieveFile(url, file_path):
  501. failedDownload = False
  502. try:
  503. urllib.urlretrieve(url.replace(' ','%20'), file_path)
  504. except:
  505. print "exception: n %s, n %s, n %s n when downloading %s" % \
  506. (sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], url)
  507. failedDownload = True
  508. # remove potentially only partially downloaded file,
  509. if failedDownload:
  510. if os.path.exists(file_path):
  511. try:
  512. os.remove(file_path)
  513. except:
  514. print "exception: n %s, n %s, n %s n when trying to remove file %s" %\
  515. (sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], file_path)
  516. sys.exit(1)
  517. return True
  518. #########################################################################
  519. def getSingleFileFromHg(file):
  520. file_path = os.path.basename(file)
  521. url = os.path.join(options.hgroot, options.repo,
  522. 'raw-file', options.tag, file)
  523. return retrieveFile(url, file_path)
  524. #########################################################################
  525. if __name__ == '__main__':
  526. error = False
  527. partner_builds = {}
  528. default_platforms = ['linux-i686', 'linux-x86_64',
  529. 'mac', 'mac64',
  530. 'win32']
  531. repack_build = {'linux-i686': RepackLinux,
  532. 'linux-x86_64': RepackLinux,
  533. 'mac': RepackMac,
  534. 'mac64': RepackMac,
  535. 'win32': RepackWin,
  536. 'win64-x86_64': RepackWin,
  537. 'maemo4': RepackMaemo,
  538. 'maemo5-gtk': RepackMaemo
  539. }
  540. parser = OptionParser(usage="usage: %prog [options]")
  541. parser.add_option("-d",
  542. "--partners-dir",
  543. action="store",
  544. dest="partners_dir",
  545. default=PARTNERS_DIR,
  546. help="Specify the directory where the partner config files are found.")
  547. parser.add_option("-p",
  548. "--partner",
  549. action="store",
  550. dest="partner",
  551. help="Repack for a single partner, specified by name."
  552. )
  553. parser.add_option("--platform",
  554. action="append",
  555. dest="platforms",
  556. help="Specify platform (multiples ok, e.g. --platform win32 --platform mac64)."
  557. )
  558. parser.add_option("--nightly-dir",
  559. action="store",
  560. dest="nightly_dir",
  561. default="firefox/nightly",
  562. help="Specify the subdirectory where candidates live (default firefox/nightly)."
  563. )
  564. parser.add_option("-v",
  565. "--version",
  566. action="store",
  567. dest="version",
  568. help="Set the version number for repacking")
  569. parser.add_option("-n",
  570. "--build-number",
  571. action="store",
  572. dest="build_number",
  573. default=BUILD_NUMBER,
  574. help="Set the build number for repacking")
  575. parser.add_option("",
  576. "--signed",
  577. action="store_true",
  578. dest="use_signed",
  579. default=False,
  580. help="Use Windows builds that have already been signed")
  581. parser.add_option("",
  582. "--hgroot",
  583. action="store",
  584. dest="hgroot",
  585. default=HGROOT,
  586. help="Set the root URL for retrieving files from hg")
  587. parser.add_option("-r",
  588. "--repo",
  589. action="store",
  590. dest="repo",
  591. default=REPO,
  592. help="Set the release tag used for retrieving files from hg")
  593. parser.add_option("-t",
  594. "--tag",
  595. action="store",
  596. dest="tag",
  597. help="Set the release tag used for retrieving files from hg")
  598. parser.add_option("",
  599. "--pkg-dmg",
  600. action="store",
  601. dest="pkg_dmg",
  602. default=PKG_DMG,
  603. help="Set the path to the pkg-dmg for Mac packaging")
  604. parser.add_option("",
  605. "--staging-server",
  606. action="store",
  607. dest="staging_server",
  608. default=STAGING_SERVER,
  609. help="Set the staging server to use for downloading/uploading.")
  610. parser.add_option("--verify-only",
  611. action="store_true",
  612. dest="verify_only",
  613. default=False,
  614. help="Check for existing partner repacks.")
  615. parser.add_option("-q",
  616. "--quiet",
  617. action="store_true",
  618. dest="quiet",
  619. default=False,
  620. help="Suppress standard output from the packaging tools.")
  621. (options, args) = parser.parse_args()
  622. # Pre-flight checks
  623. if not options.version:
  624. print "Error: you must specify a version number."
  625. error = True
  626. if not options.tag:
  627. options.tag = createTagFromVersion(options.version)
  628. if not options.tag:
  629. print "Error: you must specify a release tag for hg."
  630. error = True
  631. if not os.path.isdir(options.partners_dir):
  632. print "Error: partners dir %s is not a directory." % partners_dir
  633. error = True
  634. if not options.platforms:
  635. options.platforms = default_platforms
  636. # We only care about the tools if we're actually going to
  637. # do some repacking.
  638. if not options.verify_only:
  639. if "win32" in options.platforms and not which(SEVENZIP_BIN):
  640. print "Error: couldn't find the %s executable in PATH." % SEVENZIP_BIN
  641. error = True
  642. if "win32" in options.platforms and \
  643. options.use_signed and \
  644. not which(UPX_BIN):
  645. print "Error: couldn't find the %s executable in PATH." % UPX_BIN
  646. error = True
  647. if "mac" in options.platforms and not which(options.pkg_dmg):
  648. print "Error: couldn't find the pkg-dmg executable in PATH."
  649. error = True
  650. if error:
  651. sys.exit(1)
  652. base_workdir = os.getcwd();
  653. # Remote dir where we can find builds.
  654. candidates_web_dir = "/pub/mozilla.org/%s/%s-candidates/build%s" % (options.nightly_dir, options.version, options.build_number)
  655. if options.use_signed:
  656. win_candidates_web_dir = candidates_web_dir
  657. else:
  658. win_candidates_web_dir = candidates_web_dir + '/unsigned'
  659. # Local directories for builds
  660. original_builds_dir = "original_builds/%s/build%s" % (options.version, str(options.build_number))
  661. repacked_builds_dir = "repacked_builds/%s/build%s" % (options.version, str(options.build_number))
  662. if not options.verify_only:
  663. mkdir(original_builds_dir)
  664. mkdir(repacked_builds_dir)
  665. printSeparator()
  666. # For each partner in the partners dir
  667. ## Read/check the config file
  668. ## Download required builds (if not already on disk)
  669. ## Perform repacks
  670. for partner_dir in os.listdir(options.partners_dir):
  671. if options.partner:
  672. if options.partner != partner_dir:
  673. continue
  674. full_partner_dir = "%s/%s" % (options.partners_dir,partner_dir)
  675. if not os.path.isdir(full_partner_dir):
  676. continue
  677. repack_cfg = "%s/repack.cfg" % str(full_partner_dir)
  678. if not options.verify_only:
  679. print "### Starting repack process for partner: %s" % partner_dir
  680. else:
  681. print "### Verifying existing repacks for partner: %s" % partner_dir
  682. if not os.path.exists(repack_cfg):
  683. print "### %s doesn't exist, skipping this partner" % repack_cfg
  684. continue
  685. repack_info = parseRepackConfig(repack_cfg, options.platforms)
  686. if not repack_info:
  687. continue
  688. partner_repack_dir = "%s/%s" % (repacked_builds_dir, partner_dir)
  689. if not options.verify_only:
  690. if os.path.exists(partner_repack_dir):
  691. rmdirRecursive(partner_repack_dir)
  692. mkdir(partner_repack_dir)
  693. working_dir = "%s/working" % partner_repack_dir
  694. mkdir(working_dir)
  695. # Figure out which base builds we need to repack.
  696. for locale in repack_info['locales']:
  697. for platform in repack_info['platforms']:
  698. # ja-JP-mac only exists for Mac, so skip non-existent
  699. # platform/locale combos.
  700. if (locale == 'ja' and isMac(platform)) or \
  701. (locale == 'ja-JP-mac' and not isMac(platform)):
  702. continue
  703. platform_formatted = getFormattedPlatform(platform)
  704. file_ext = getFileExtension(options.version,
  705. platform_formatted)
  706. filename = getFilename(options.version,
  707. platform_formatted,
  708. locale,
  709. file_ext)
  710. local_filepath = getLocalFilePath(options.version,
  711. original_builds_dir,
  712. platform_formatted,
  713. locale)
  714. if not options.verify_only:
  715. mkdir(local_filepath)
  716. local_filename = "%s/%s" % (local_filepath, filename)
  717. if options.version.startswith('3.0'):
  718. final_dir = '.'
  719. else:
  720. final_dir = "%s/%s" % (platform_formatted,
  721. locale
  722. )
  723. if not options.verify_only:
  724. mkdir("%s/%s" % (partner_repack_dir, final_dir))
  725. # Check to see if this build is already on disk, i.e.
  726. # has already been downloaded.
  727. if not options.verify_only:
  728. if os.path.exists(local_filename):
  729. print "### Found %s on disk, not downloading" % local_filename
  730. else:
  731. # Download original build from stage
  732. print "### Downloading %s" % local_filename
  733. os.chdir(local_filepath)
  734. if isWin(platform):
  735. candidates_dir = win_candidates_web_dir
  736. else:
  737. candidates_dir = candidates_web_dir
  738. if options.version.startswith('3.0'):
  739. original_build_url = "http://%s%s/%s" % \
  740. (options.staging_server,
  741. candidates_dir,
  742. filename
  743. )
  744. else:
  745. original_build_url = "http://%s%s/%s/%s/%s" % \
  746. (options.staging_server,
  747. candidates_dir,
  748. platform_formatted,
  749. locale,
  750. filename
  751. )
  752. retrieveFile(original_build_url, filename)
  753. os.chdir(base_workdir);
  754. # Make sure we have the local file now
  755. if not os.path.exists(local_filename):
  756. print "Error: Unable to retrieve %s" % filename
  757. sys.exit(1)
  758. repackObj = repack_build[platform_formatted](filename,
  759. full_partner_dir,
  760. local_filepath,
  761. working_dir,
  762. final_dir,
  763. platform_formatted,
  764. repack_info)
  765. repackObj.doRepack()
  766. else:
  767. repacked_build = "%s/%s/%s" % (partner_repack_dir, final_dir, filename)
  768. if not os.path.exists(repacked_build):
  769. print "Error: missing expected repack for partner %s (%s/%s): %s" % (partner_dir, platform_formatted, locale, filename)
  770. error = True
  771. if not options.verify_only:
  772. # Check to see whether we repacked any signed Windows builds. If we
  773. # did we need to do some scrubbing before we upload them for
  774. # re-signing.
  775. if 'win32' in repack_info['platforms'] and options.use_signed:
  776. repackSignedBuilds(repacked_builds_dir)
  777. # Remove our working dir so things are all cleaned up and ready for
  778. # easy upload.
  779. rmdirRecursive(working_dir)
  780. printSeparator()
  781. if error:
  782. sys.exit(1)