PageRenderTime 58ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/newview/viewer_manifest.py

https://bitbucket.org/lindenlab/viewer-beta/
Python | 1098 lines | 1024 code | 17 blank | 57 comment | 24 complexity | ae1035291460b143073982dc63e76fb7 MD5 | raw file
Possible License(s): LGPL-2.1
  1. #!/usr/bin/env python
  2. """\
  3. @file viewer_manifest.py
  4. @author Ryan Williams
  5. @brief Description of all installer viewer files, and methods for packaging
  6. them into installers for all supported platforms.
  7. $LicenseInfo:firstyear=2006&license=viewerlgpl$
  8. Second Life Viewer Source Code
  9. Copyright (C) 2006-2011, Linden Research, Inc.
  10. This library is free software; you can redistribute it and/or
  11. modify it under the terms of the GNU Lesser General Public
  12. License as published by the Free Software Foundation;
  13. version 2.1 of the License only.
  14. This library is distributed in the hope that it will be useful,
  15. but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. Lesser General Public License for more details.
  18. You should have received a copy of the GNU Lesser General Public
  19. License along with this library; if not, write to the Free Software
  20. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  21. Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
  22. $/LicenseInfo$
  23. """
  24. import sys
  25. import os.path
  26. import re
  27. import tarfile
  28. import time
  29. import random
  30. viewer_dir = os.path.dirname(__file__)
  31. # add llmanifest library to our path so we don't have to muck with PYTHONPATH
  32. sys.path.append(os.path.join(viewer_dir, '../lib/python/indra/util'))
  33. from llmanifest import LLManifest, main, proper_windows_path, path_ancestors
  34. class ViewerManifest(LLManifest):
  35. def is_packaging_viewer(self):
  36. # Some commands, files will only be included
  37. # if we are packaging the viewer on windows.
  38. # This manifest is also used to copy
  39. # files during the build (see copy_w_viewer_manifest
  40. # and copy_l_viewer_manifest targets)
  41. return 'package' in self.args['actions']
  42. def construct(self):
  43. super(ViewerManifest, self).construct()
  44. self.exclude("*.svn*")
  45. self.path(src="../../scripts/messages/message_template.msg", dst="app_settings/message_template.msg")
  46. self.path(src="../../etc/message.xml", dst="app_settings/message.xml")
  47. if self.is_packaging_viewer():
  48. if self.prefix(src="app_settings"):
  49. self.exclude("logcontrol.xml")
  50. self.exclude("logcontrol-dev.xml")
  51. self.path("*.pem")
  52. self.path("*.ini")
  53. self.path("*.xml")
  54. self.path("*.db2")
  55. # include the entire shaders directory recursively
  56. self.path("shaders")
  57. # include the extracted list of contributors
  58. contributor_names = self.extract_names("../../doc/contributions.txt")
  59. self.put_in_file(contributor_names, "contributors.txt")
  60. self.file_list.append(["../../doc/contributions.txt",self.dst_path_of("contributors.txt")])
  61. # include the extracted list of translators
  62. translator_names = self.extract_names("../../doc/translations.txt")
  63. self.put_in_file(translator_names, "translators.txt")
  64. self.file_list.append(["../../doc/translations.txt",self.dst_path_of("translators.txt")])
  65. # include the list of Lindens (if any)
  66. # see https://wiki.lindenlab.com/wiki/Generated_Linden_Credits
  67. linden_names_path = os.getenv("LINDEN_CREDITS")
  68. if linden_names_path :
  69. try:
  70. linden_file = open(linden_names_path,'r')
  71. # all names should be one line, but the join below also converts to a string
  72. linden_names = ', '.join(linden_file.readlines())
  73. self.put_in_file(linden_names, "lindens.txt")
  74. linden_file.close()
  75. print "Linden names extracted from '%s'" % linden_names_path
  76. self.file_list.append([linden_names_path,self.dst_path_of("lindens.txt")])
  77. except IOError:
  78. print "No Linden names found at '%s', using built-in list" % linden_names_path
  79. pass
  80. else :
  81. print "No 'LINDEN_CREDITS' specified in environment, using built-in list"
  82. # ... and the entire windlight directory
  83. self.path("windlight")
  84. self.end_prefix("app_settings")
  85. if self.prefix(src="character"):
  86. self.path("*.llm")
  87. self.path("*.xml")
  88. self.path("*.tga")
  89. self.end_prefix("character")
  90. # Include our fonts
  91. if self.prefix(src="fonts"):
  92. self.path("*.ttf")
  93. self.path("*.txt")
  94. self.end_prefix("fonts")
  95. # skins
  96. if self.prefix(src="skins"):
  97. self.path("paths.xml")
  98. # include the entire textures directory recursively
  99. if self.prefix(src="*/textures"):
  100. self.path("*/*.tga")
  101. self.path("*/*.j2c")
  102. self.path("*/*.jpg")
  103. self.path("*/*.png")
  104. self.path("*.tga")
  105. self.path("*.j2c")
  106. self.path("*.jpg")
  107. self.path("*.png")
  108. self.path("textures.xml")
  109. self.end_prefix("*/textures")
  110. self.path("*/xui/*/*.xml")
  111. self.path("*/xui/*/widgets/*.xml")
  112. self.path("*/*.xml")
  113. # Local HTML files (e.g. loading screen)
  114. if self.prefix(src="*/html"):
  115. self.path("*.png")
  116. self.path("*/*/*.html")
  117. self.path("*/*/*.gif")
  118. self.end_prefix("*/html")
  119. self.end_prefix("skins")
  120. # local_assets dir (for pre-cached textures)
  121. if self.prefix(src="local_assets"):
  122. self.path("*.j2c")
  123. self.path("*.tga")
  124. self.end_prefix("local_assets")
  125. # Files in the newview/ directory
  126. self.path("gpu_table.txt")
  127. # The summary.json file gets left in the base checkout dir by
  128. # build.sh. It's only created for a build.sh build, therefore we
  129. # have to check whether it exists. :-P
  130. summary_json = "summary.json"
  131. summary_json_path = os.path.join(os.pardir, os.pardir, summary_json)
  132. if os.path.exists(os.path.join(self.get_src_prefix(), summary_json_path)):
  133. self.path(summary_json_path, summary_json)
  134. else:
  135. print "No %s" % os.path.join(self.get_src_prefix(), summary_json_path)
  136. def login_channel(self):
  137. """Channel reported for login and upgrade purposes ONLY;
  138. used for A/B testing"""
  139. # NOTE: Do not return the normal channel if login_channel
  140. # is not specified, as some code may branch depending on
  141. # whether or not this is present
  142. return self.args.get('login_channel')
  143. def grid(self):
  144. return self.args['grid']
  145. def channel(self):
  146. return self.args['channel']
  147. def channel_unique(self):
  148. return self.channel().replace("Second Life", "").strip()
  149. def channel_oneword(self):
  150. return "".join(self.channel_unique().split())
  151. def channel_lowerword(self):
  152. return self.channel_oneword().lower()
  153. def icon_path(self):
  154. icon_path="icons/"
  155. channel_type=self.channel_lowerword()
  156. if channel_type == 'release' \
  157. or channel_type == 'development' \
  158. :
  159. icon_path += channel_type
  160. elif channel_type == 'betaviewer' :
  161. icon_path += 'beta'
  162. elif re.match('project.*',channel_type) :
  163. icon_path += 'project'
  164. else :
  165. icon_path += 'test'
  166. return icon_path
  167. def flags_list(self):
  168. """ Convenience function that returns the command-line flags
  169. for the grid"""
  170. # Set command line flags relating to the target grid
  171. grid_flags = ''
  172. if not self.default_grid():
  173. grid_flags = "--grid %(grid)s "\
  174. "--helperuri http://preview-%(grid)s.secondlife.com/helpers/" %\
  175. {'grid':self.grid()}
  176. # set command line flags for channel
  177. channel_flags = ''
  178. if self.login_channel() and self.login_channel() != self.channel():
  179. # Report a special channel during login, but use default
  180. channel_flags = '--channel "%s"' % (self.login_channel())
  181. elif not self.default_channel():
  182. channel_flags = '--channel "%s"' % self.channel()
  183. # Deal with settings
  184. setting_flags = ''
  185. if not self.default_channel() or not self.default_grid():
  186. if self.default_grid():
  187. setting_flags = '--settings settings_%s.xml'\
  188. % self.channel_lowerword()
  189. else:
  190. setting_flags = '--settings settings_%s_%s.xml'\
  191. % (self.grid(), self.channel_lowerword())
  192. return " ".join((channel_flags, grid_flags, setting_flags)).strip()
  193. def extract_names(self,src):
  194. try:
  195. contrib_file = open(src,'r')
  196. except IOError:
  197. print "Failed to open '%s'" % src
  198. raise
  199. lines = contrib_file.readlines()
  200. contrib_file.close()
  201. # All lines up to and including the first blank line are the file header; skip them
  202. lines.reverse() # so that pop will pull from first to last line
  203. while not re.match("\s*$", lines.pop()) :
  204. pass # do nothing
  205. # A line that starts with a non-whitespace character is a name; all others describe contributions, so collect the names
  206. names = []
  207. for line in lines :
  208. if re.match("\S", line) :
  209. names.append(line.rstrip())
  210. # It's not fair to always put the same people at the head of the list
  211. random.shuffle(names)
  212. return ', '.join(names)
  213. class WindowsManifest(ViewerManifest):
  214. def final_exe(self):
  215. if self.default_channel():
  216. if self.default_grid():
  217. return "SecondLife.exe"
  218. else:
  219. return "SecondLifePreview.exe"
  220. else:
  221. return ''.join(self.channel().split()) + '.exe'
  222. def test_msvcrt_and_copy_action(self, src, dst):
  223. # This is used to test a dll manifest.
  224. # It is used as a temporary override during the construct method
  225. from test_win32_manifest import test_assembly_binding
  226. if src and (os.path.exists(src) or os.path.islink(src)):
  227. # ensure that destination path exists
  228. self.cmakedirs(os.path.dirname(dst))
  229. self.created_paths.append(dst)
  230. if not os.path.isdir(src):
  231. if(self.args['configuration'].lower() == 'debug'):
  232. test_assembly_binding(src, "Microsoft.VC80.DebugCRT", "8.0.50727.4053")
  233. else:
  234. test_assembly_binding(src, "Microsoft.VC80.CRT", "8.0.50727.4053")
  235. self.ccopy(src,dst)
  236. else:
  237. raise Exception("Directories are not supported by test_CRT_and_copy_action()")
  238. else:
  239. print "Doesn't exist:", src
  240. def test_for_no_msvcrt_manifest_and_copy_action(self, src, dst):
  241. # This is used to test that no manifest for the msvcrt exists.
  242. # It is used as a temporary override during the construct method
  243. from test_win32_manifest import test_assembly_binding
  244. from test_win32_manifest import NoManifestException, NoMatchingAssemblyException
  245. if src and (os.path.exists(src) or os.path.islink(src)):
  246. # ensure that destination path exists
  247. self.cmakedirs(os.path.dirname(dst))
  248. self.created_paths.append(dst)
  249. if not os.path.isdir(src):
  250. try:
  251. if(self.args['configuration'].lower() == 'debug'):
  252. test_assembly_binding(src, "Microsoft.VC80.DebugCRT", "")
  253. else:
  254. test_assembly_binding(src, "Microsoft.VC80.CRT", "")
  255. raise Exception("Unknown condition")
  256. except NoManifestException, err:
  257. pass
  258. except NoMatchingAssemblyException, err:
  259. pass
  260. self.ccopy(src,dst)
  261. else:
  262. raise Exception("Directories are not supported by test_CRT_and_copy_action()")
  263. else:
  264. print "Doesn't exist:", src
  265. ### DISABLED MANIFEST CHECKING for vs2010. we may need to reenable this
  266. # shortly. If this hasn't been reenabled by the 2.9 viewer release then it
  267. # should be deleted -brad
  268. #def enable_crt_manifest_check(self):
  269. # if self.is_packaging_viewer():
  270. # WindowsManifest.copy_action = WindowsManifest.test_msvcrt_and_copy_action
  271. #def enable_no_crt_manifest_check(self):
  272. # if self.is_packaging_viewer():
  273. # WindowsManifest.copy_action = WindowsManifest.test_for_no_msvcrt_manifest_and_copy_action
  274. #def disable_manifest_check(self):
  275. # if self.is_packaging_viewer():
  276. # del WindowsManifest.copy_action
  277. def construct(self):
  278. super(WindowsManifest, self).construct()
  279. #self.enable_crt_manifest_check()
  280. if self.is_packaging_viewer():
  281. # Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe.
  282. self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe())
  283. # Plugin host application
  284. self.path(os.path.join(os.pardir,
  285. 'llplugin', 'slplugin', self.args['configuration'], "slplugin.exe"),
  286. "slplugin.exe")
  287. #self.disable_manifest_check()
  288. self.path(src="../viewer_components/updater/scripts/windows/update_install.bat", dst="update_install.bat")
  289. # Get shared libs from the shared libs staging directory
  290. if self.prefix(src=os.path.join(os.pardir, 'sharedlibs', self.args['configuration']),
  291. dst=""):
  292. #self.enable_crt_manifest_check()
  293. # Get llcommon and deps. If missing assume static linkage and continue.
  294. try:
  295. self.path('llcommon.dll')
  296. self.path('libapr-1.dll')
  297. self.path('libaprutil-1.dll')
  298. self.path('libapriconv-1.dll')
  299. except RuntimeError, err:
  300. print err.message
  301. print "Skipping llcommon.dll (assuming llcommon was linked statically)"
  302. #self.disable_manifest_check()
  303. # Mesh 3rd party libs needed for auto LOD and collada reading
  304. try:
  305. if self.args['configuration'].lower() == 'debug':
  306. self.path("libcollada14dom22-d.dll")
  307. else:
  308. self.path("libcollada14dom22.dll")
  309. self.path("glod.dll")
  310. except RuntimeError, err:
  311. print err.message
  312. print "Skipping COLLADA and GLOD libraries (assumming linked statically)"
  313. # Get fmod dll, continue if missing
  314. try:
  315. self.path("fmod.dll")
  316. except:
  317. print "Skipping fmod.dll"
  318. # For textures
  319. if self.args['configuration'].lower() == 'debug':
  320. self.path("openjpegd.dll")
  321. else:
  322. self.path("openjpeg.dll")
  323. # These need to be installed as a SxS assembly, currently a 'private' assembly.
  324. # See http://msdn.microsoft.com/en-us/library/ms235291(VS.80).aspx
  325. if self.args['configuration'].lower() == 'debug':
  326. self.path("msvcr100d.dll")
  327. self.path("msvcp100d.dll")
  328. else:
  329. self.path("msvcr100.dll")
  330. self.path("msvcp100.dll")
  331. # Vivox runtimes
  332. self.path("SLVoice.exe")
  333. self.path("vivoxsdk.dll")
  334. self.path("ortp.dll")
  335. self.path("libsndfile-1.dll")
  336. self.path("zlib1.dll")
  337. self.path("vivoxplatform.dll")
  338. self.path("vivoxoal.dll")
  339. # Security
  340. self.path("ssleay32.dll")
  341. self.path("libeay32.dll")
  342. # For google-perftools tcmalloc allocator.
  343. try:
  344. if self.args['configuration'].lower() == 'debug':
  345. self.path('libtcmalloc_minimal-debug.dll')
  346. else:
  347. self.path('libtcmalloc_minimal.dll')
  348. except:
  349. print "Skipping libtcmalloc_minimal.dll"
  350. self.end_prefix()
  351. self.path(src="licenses-win32.txt", dst="licenses.txt")
  352. self.path("featuretable.txt")
  353. self.path("featuretable_xp.txt")
  354. #self.enable_no_crt_manifest_check()
  355. # Media plugins - QuickTime
  356. if self.prefix(src='../media_plugins/quicktime/%s' % self.args['configuration'], dst="llplugin"):
  357. self.path("media_plugin_quicktime.dll")
  358. self.end_prefix()
  359. # Media plugins - WebKit/Qt
  360. if self.prefix(src='../media_plugins/webkit/%s' % self.args['configuration'], dst="llplugin"):
  361. self.path("media_plugin_webkit.dll")
  362. self.end_prefix()
  363. # winmm.dll shim
  364. if self.prefix(src='../media_plugins/winmmshim/%s' % self.args['configuration'], dst=""):
  365. self.path("winmm.dll")
  366. self.end_prefix()
  367. if self.args['configuration'].lower() == 'debug':
  368. if self.prefix(src=os.path.join(os.pardir, 'packages', 'lib', 'debug'),
  369. dst="llplugin"):
  370. self.path("libeay32.dll")
  371. self.path("qtcored4.dll")
  372. self.path("qtguid4.dll")
  373. self.path("qtnetworkd4.dll")
  374. self.path("qtopengld4.dll")
  375. self.path("qtwebkitd4.dll")
  376. self.path("qtxmlpatternsd4.dll")
  377. self.path("ssleay32.dll")
  378. # For WebKit/Qt plugin runtimes (image format plugins)
  379. if self.prefix(src="imageformats", dst="imageformats"):
  380. self.path("qgifd4.dll")
  381. self.path("qicod4.dll")
  382. self.path("qjpegd4.dll")
  383. self.path("qmngd4.dll")
  384. self.path("qsvgd4.dll")
  385. self.path("qtiffd4.dll")
  386. self.end_prefix()
  387. # For WebKit/Qt plugin runtimes (codec/character encoding plugins)
  388. if self.prefix(src="codecs", dst="codecs"):
  389. self.path("qcncodecsd4.dll")
  390. self.path("qjpcodecsd4.dll")
  391. self.path("qkrcodecsd4.dll")
  392. self.path("qtwcodecsd4.dll")
  393. self.end_prefix()
  394. self.end_prefix()
  395. else:
  396. if self.prefix(src=os.path.join(os.pardir, 'packages', 'lib', 'release'),
  397. dst="llplugin"):
  398. self.path("libeay32.dll")
  399. self.path("qtcore4.dll")
  400. self.path("qtgui4.dll")
  401. self.path("qtnetwork4.dll")
  402. self.path("qtopengl4.dll")
  403. self.path("qtwebkit4.dll")
  404. self.path("qtxmlpatterns4.dll")
  405. self.path("ssleay32.dll")
  406. # For WebKit/Qt plugin runtimes (image format plugins)
  407. if self.prefix(src="imageformats", dst="imageformats"):
  408. self.path("qgif4.dll")
  409. self.path("qico4.dll")
  410. self.path("qjpeg4.dll")
  411. self.path("qmng4.dll")
  412. self.path("qsvg4.dll")
  413. self.path("qtiff4.dll")
  414. self.end_prefix()
  415. # For WebKit/Qt plugin runtimes (codec/character encoding plugins)
  416. if self.prefix(src="codecs", dst="codecs"):
  417. self.path("qcncodecs4.dll")
  418. self.path("qjpcodecs4.dll")
  419. self.path("qkrcodecs4.dll")
  420. self.path("qtwcodecs4.dll")
  421. self.end_prefix()
  422. self.end_prefix()
  423. #self.disable_manifest_check()
  424. # pull in the crash logger and updater from other projects
  425. # tag:"crash-logger" here as a cue to the exporter
  426. self.path(src='../win_crash_logger/%s/windows-crash-logger.exe' % self.args['configuration'],
  427. dst="win_crash_logger.exe")
  428. # For CHOP-397, windows updater no longer used.
  429. # self.path(src='../win_updater/%s/windows-updater.exe' % self.args['configuration'],
  430. # dst="updater.exe")
  431. if not self.is_packaging_viewer():
  432. self.package_file = "copied_deps"
  433. def nsi_file_commands(self, install=True):
  434. def wpath(path):
  435. if path.endswith('/') or path.endswith(os.path.sep):
  436. path = path[:-1]
  437. path = path.replace('/', '\\')
  438. return path
  439. result = ""
  440. dest_files = [pair[1] for pair in self.file_list if pair[0] and os.path.isfile(pair[1])]
  441. # sort deepest hierarchy first
  442. dest_files.sort(lambda a,b: cmp(a.count(os.path.sep),b.count(os.path.sep)) or cmp(a,b))
  443. dest_files.reverse()
  444. out_path = None
  445. for pkg_file in dest_files:
  446. rel_file = os.path.normpath(pkg_file.replace(self.get_dst_prefix()+os.path.sep,''))
  447. installed_dir = wpath(os.path.join('$INSTDIR', os.path.dirname(rel_file)))
  448. pkg_file = wpath(os.path.normpath(pkg_file))
  449. if installed_dir != out_path:
  450. if install:
  451. out_path = installed_dir
  452. result += 'SetOutPath ' + out_path + '\n'
  453. if install:
  454. result += 'File ' + pkg_file + '\n'
  455. else:
  456. result += 'Delete ' + wpath(os.path.join('$INSTDIR', rel_file)) + '\n'
  457. # at the end of a delete, just rmdir all the directories
  458. if not install:
  459. deleted_file_dirs = [os.path.dirname(pair[1].replace(self.get_dst_prefix()+os.path.sep,'')) for pair in self.file_list]
  460. # find all ancestors so that we don't skip any dirs that happened to have no non-dir children
  461. deleted_dirs = []
  462. for d in deleted_file_dirs:
  463. deleted_dirs.extend(path_ancestors(d))
  464. # sort deepest hierarchy first
  465. deleted_dirs.sort(lambda a,b: cmp(a.count(os.path.sep),b.count(os.path.sep)) or cmp(a,b))
  466. deleted_dirs.reverse()
  467. prev = None
  468. for d in deleted_dirs:
  469. if d != prev: # skip duplicates
  470. result += 'RMDir ' + wpath(os.path.join('$INSTDIR', os.path.normpath(d))) + '\n'
  471. prev = d
  472. return result
  473. def package_finish(self):
  474. # a standard map of strings for replacing in the templates
  475. substitution_strings = {
  476. 'version' : '.'.join(self.args['version']),
  477. 'version_short' : '.'.join(self.args['version'][:-1]),
  478. 'version_dashes' : '-'.join(self.args['version']),
  479. 'final_exe' : self.final_exe(),
  480. 'grid':self.args['grid'],
  481. 'grid_caps':self.args['grid'].upper(),
  482. # escape quotes becase NSIS doesn't handle them well
  483. 'flags':self.flags_list().replace('"', '$\\"'),
  484. 'channel':self.channel(),
  485. 'channel_oneword':self.channel_oneword(),
  486. 'channel_unique':self.channel_unique(),
  487. }
  488. version_vars = """
  489. !define INSTEXE "%(final_exe)s"
  490. !define VERSION "%(version_short)s"
  491. !define VERSION_LONG "%(version)s"
  492. !define VERSION_DASHES "%(version_dashes)s"
  493. """ % substitution_strings
  494. if self.default_channel():
  495. if self.default_grid():
  496. # release viewer
  497. installer_file = "Second_Life_%(version_dashes)s_Setup.exe"
  498. grid_vars_template = """
  499. OutFile "%(installer_file)s"
  500. !define INSTFLAGS "%(flags)s"
  501. !define INSTNAME "SecondLifeViewer"
  502. !define SHORTCUT "Second Life Viewer"
  503. !define URLNAME "secondlife"
  504. Caption "Second Life"
  505. """
  506. else:
  507. # beta grid viewer
  508. installer_file = "Second_Life_%(version_dashes)s_(%(grid_caps)s)_Setup.exe"
  509. grid_vars_template = """
  510. OutFile "%(installer_file)s"
  511. !define INSTFLAGS "%(flags)s"
  512. !define INSTNAME "SecondLife%(grid_caps)s"
  513. !define SHORTCUT "Second Life (%(grid_caps)s)"
  514. !define URLNAME "secondlife%(grid)s"
  515. !define UNINSTALL_SETTINGS 1
  516. Caption "Second Life %(grid)s ${VERSION}"
  517. """
  518. else:
  519. # some other channel on some grid
  520. installer_file = "Second_Life_%(version_dashes)s_%(channel_oneword)s_Setup.exe"
  521. grid_vars_template = """
  522. OutFile "%(installer_file)s"
  523. !define INSTFLAGS "%(flags)s"
  524. !define INSTNAME "SecondLife%(channel_oneword)s"
  525. !define SHORTCUT "%(channel)s"
  526. !define URLNAME "secondlife"
  527. !define UNINSTALL_SETTINGS 1
  528. Caption "%(channel)s ${VERSION}"
  529. """
  530. if 'installer_name' in self.args:
  531. installer_file = self.args['installer_name']
  532. else:
  533. installer_file = installer_file % substitution_strings
  534. substitution_strings['installer_file'] = installer_file
  535. tempfile = "secondlife_setup_tmp.nsi"
  536. # the following replaces strings in the nsi template
  537. # it also does python-style % substitution
  538. self.replace_in("installers/windows/installer_template.nsi", tempfile, {
  539. "%%VERSION%%":version_vars,
  540. "%%SOURCE%%":self.get_src_prefix(),
  541. "%%GRID_VARS%%":grid_vars_template % substitution_strings,
  542. "%%INSTALL_FILES%%":self.nsi_file_commands(True),
  543. "%%DELETE_FILES%%":self.nsi_file_commands(False)})
  544. # We use the Unicode version of NSIS, available from
  545. # http://www.scratchpaper.com/
  546. # Check two paths, one for Program Files, and one for Program Files (x86).
  547. # Yay 64bit windows.
  548. NSIS_path = os.path.expandvars('${ProgramFiles}\\NSIS\\Unicode\\makensis.exe')
  549. if not os.path.exists(NSIS_path):
  550. NSIS_path = os.path.expandvars('${ProgramFiles(x86)}\\NSIS\\Unicode\\makensis.exe')
  551. self.run_command('"' + proper_windows_path(NSIS_path) + '" ' + self.dst_path_of(tempfile))
  552. # self.remove(self.dst_path_of(tempfile))
  553. # If we're on a build machine, sign the code using our Authenticode certificate. JC
  554. sign_py = os.path.expandvars("${SIGN}")
  555. if not sign_py or sign_py == "${SIGN}":
  556. sign_py = 'C:\\buildscripts\\code-signing\\sign.py'
  557. else:
  558. sign_py = sign_py.replace('\\', '\\\\\\\\')
  559. python = os.path.expandvars("${PYTHON}")
  560. if not python or python == "${PYTHON}":
  561. python = 'python'
  562. if os.path.exists(sign_py):
  563. self.run_command("%s %s %s" % (python, sign_py, self.dst_path_of(installer_file).replace('\\', '\\\\\\\\')))
  564. else:
  565. print "Skipping code signing,", sign_py, "does not exist"
  566. self.created_path(self.dst_path_of(installer_file))
  567. self.package_file = installer_file
  568. class DarwinManifest(ViewerManifest):
  569. def is_packaging_viewer(self):
  570. # darwin requires full app bundle packaging even for debugging.
  571. return True
  572. def construct(self):
  573. # copy over the build result (this is a no-op if run within the xcode script)
  574. self.path(self.args['configuration'] + "/Second Life.app", dst="")
  575. if self.prefix(src="", dst="Contents"): # everything goes in Contents
  576. self.path("Info-SecondLife.plist", dst="Info.plist")
  577. # copy additional libs in <bundle>/Contents/MacOS/
  578. self.path("../packages/lib/release/libndofdev.dylib", dst="Resources/libndofdev.dylib")
  579. self.path("../viewer_components/updater/scripts/darwin/update_install", "MacOS/update_install")
  580. # most everything goes in the Resources directory
  581. if self.prefix(src="", dst="Resources"):
  582. super(DarwinManifest, self).construct()
  583. if self.prefix("cursors_mac"):
  584. self.path("*.tif")
  585. self.end_prefix("cursors_mac")
  586. self.path("licenses-mac.txt", dst="licenses.txt")
  587. self.path("featuretable_mac.txt")
  588. self.path("SecondLife.nib")
  589. icon_path = self.icon_path()
  590. if self.prefix(src=icon_path, dst="") :
  591. self.path("secondlife.icns")
  592. self.end_prefix(icon_path)
  593. self.path("SecondLife.nib")
  594. # Translations
  595. self.path("English.lproj")
  596. self.path("German.lproj")
  597. self.path("Japanese.lproj")
  598. self.path("Korean.lproj")
  599. self.path("da.lproj")
  600. self.path("es.lproj")
  601. self.path("fr.lproj")
  602. self.path("hu.lproj")
  603. self.path("it.lproj")
  604. self.path("nl.lproj")
  605. self.path("pl.lproj")
  606. self.path("pt.lproj")
  607. self.path("ru.lproj")
  608. self.path("tr.lproj")
  609. self.path("uk.lproj")
  610. self.path("zh-Hans.lproj")
  611. libdir = "../packages/lib/release"
  612. dylibs = {}
  613. # Need to get the llcommon dll from any of the build directories as well
  614. lib = "llcommon"
  615. libfile = "lib%s.dylib" % lib
  616. try:
  617. self.path(self.find_existing_file(os.path.join(os.pardir,
  618. lib,
  619. self.args['configuration'],
  620. libfile),
  621. os.path.join(libdir, libfile)),
  622. dst=libfile)
  623. except RuntimeError:
  624. print "Skipping %s" % libfile
  625. dylibs[lib] = False
  626. else:
  627. dylibs[lib] = True
  628. if dylibs["llcommon"]:
  629. for libfile in ("libapr-1.0.dylib",
  630. "libaprutil-1.0.dylib",
  631. "libexpat.1.5.2.dylib",
  632. "libexception_handler.dylib",
  633. "libGLOD.dylib",
  634. "libcollada14dom.dylib"
  635. ):
  636. self.path(os.path.join(libdir, libfile), libfile)
  637. # SLVoice and vivox lols
  638. for libfile in ('libsndfile.dylib', 'libvivoxoal.dylib', 'libortp.dylib', \
  639. 'libvivoxsdk.dylib', 'libvivoxplatform.dylib', 'SLVoice') :
  640. self.path(os.path.join(libdir, libfile), libfile)
  641. try:
  642. # FMOD for sound
  643. self.path(self.args['configuration'] + "/libfmodwrapper.dylib", "libfmodwrapper.dylib")
  644. except:
  645. print "Skipping FMOD - not found"
  646. # our apps
  647. self.path("../mac_crash_logger/" + self.args['configuration'] + "/mac-crash-logger.app", "mac-crash-logger.app")
  648. self.path("../mac_updater/" + self.args['configuration'] + "/mac-updater.app", "mac-updater.app")
  649. # plugin launcher
  650. self.path("../llplugin/slplugin/" + self.args['configuration'] + "/SLPlugin.app", "SLPlugin.app")
  651. # our apps dependencies on shared libs
  652. if dylibs["llcommon"]:
  653. mac_crash_logger_res_path = self.dst_path_of("mac-crash-logger.app/Contents/Resources")
  654. mac_updater_res_path = self.dst_path_of("mac-updater.app/Contents/Resources")
  655. slplugin_res_path = self.dst_path_of("SLPlugin.app/Contents/Resources")
  656. for libfile in ("libllcommon.dylib",
  657. "libapr-1.0.dylib",
  658. "libaprutil-1.0.dylib",
  659. "libexpat.1.5.2.dylib",
  660. "libexception_handler.dylib",
  661. "libGLOD.dylib",
  662. "libcollada14dom.dylib"
  663. ):
  664. target_lib = os.path.join('../../..', libfile)
  665. self.run_command("ln -sf %(target)r %(link)r" %
  666. {'target': target_lib,
  667. 'link' : os.path.join(mac_crash_logger_res_path, libfile)}
  668. )
  669. self.run_command("ln -sf %(target)r %(link)r" %
  670. {'target': target_lib,
  671. 'link' : os.path.join(mac_updater_res_path, libfile)}
  672. )
  673. self.run_command("ln -sf %(target)r %(link)r" %
  674. {'target': target_lib,
  675. 'link' : os.path.join(slplugin_res_path, libfile)}
  676. )
  677. # plugins
  678. if self.prefix(src="", dst="llplugin"):
  679. self.path("../media_plugins/quicktime/" + self.args['configuration'] + "/media_plugin_quicktime.dylib", "media_plugin_quicktime.dylib")
  680. self.path("../media_plugins/webkit/" + self.args['configuration'] + "/media_plugin_webkit.dylib", "media_plugin_webkit.dylib")
  681. self.path("../packages/lib/release/libllqtwebkit.dylib", "libllqtwebkit.dylib")
  682. self.end_prefix("llplugin")
  683. # command line arguments for connecting to the proper grid
  684. self.put_in_file(self.flags_list(), 'arguments.txt')
  685. self.end_prefix("Resources")
  686. self.end_prefix("Contents")
  687. # NOTE: the -S argument to strip causes it to keep enough info for
  688. # annotated backtraces (i.e. function names in the crash log). 'strip' with no
  689. # arguments yields a slightly smaller binary but makes crash logs mostly useless.
  690. # This may be desirable for the final release. Or not.
  691. if ("package" in self.args['actions'] or
  692. "unpacked" in self.args['actions']):
  693. self.run_command('strip -S %(viewer_binary)r' %
  694. { 'viewer_binary' : self.dst_path_of('Contents/MacOS/Second Life')})
  695. def copy_finish(self):
  696. # Force executable permissions to be set for scripts
  697. # see CHOP-223 and http://mercurial.selenic.com/bts/issue1802
  698. for script in 'Contents/MacOS/update_install',:
  699. self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script))
  700. def package_finish(self):
  701. channel_standin = 'Second Life Viewer' # hah, our default channel is not usable on its own
  702. if not self.default_channel():
  703. channel_standin = self.channel()
  704. imagename="SecondLife_" + '_'.join(self.args['version'])
  705. # MBW -- If the mounted volume name changes, it breaks the .DS_Store's background image and icon positioning.
  706. # If we really need differently named volumes, we'll need to create multiple DS_Store file images, or use some other trick.
  707. volname="Second Life Installer" # DO NOT CHANGE without understanding comment above
  708. if self.default_channel():
  709. if not self.default_grid():
  710. # beta case
  711. imagename = imagename + '_' + self.args['grid'].upper()
  712. else:
  713. # first look, etc
  714. imagename = imagename + '_' + self.channel_oneword().upper()
  715. sparsename = imagename + ".sparseimage"
  716. finalname = imagename + ".dmg"
  717. # make sure we don't have stale files laying about
  718. self.remove(sparsename, finalname)
  719. self.run_command('hdiutil create %(sparse)r -volname %(vol)r -fs HFS+ -type SPARSE -megabytes 700 -layout SPUD' % {
  720. 'sparse':sparsename,
  721. 'vol':volname})
  722. # mount the image and get the name of the mount point and device node
  723. hdi_output = self.run_command('hdiutil attach -private %r' % sparsename)
  724. try:
  725. devfile = re.search("/dev/disk([0-9]+)[^s]", hdi_output).group(0).strip()
  726. volpath = re.search('HFS\s+(.+)', hdi_output).group(1).strip()
  727. if devfile != '/dev/disk1':
  728. # adding more debugging info based upon nat's hunches to the
  729. # logs to help track down 'SetFile -a V' failures -brad
  730. print "WARNING: 'SetFile -a V' command below is probably gonna fail"
  731. # Copy everything in to the mounted .dmg
  732. if self.default_channel() and not self.default_grid():
  733. app_name = "Second Life " + self.args['grid']
  734. else:
  735. app_name = channel_standin.strip()
  736. # Hack:
  737. # Because there is no easy way to coerce the Finder into positioning
  738. # the app bundle in the same place with different app names, we are
  739. # adding multiple .DS_Store files to svn. There is one for release,
  740. # one for release candidate and one for first look. Any other channels
  741. # will use the release .DS_Store, and will look broken.
  742. # - Ambroff 2008-08-20
  743. dmg_template = os.path.join(
  744. 'installers', 'darwin', '%s-dmg' % self.channel_lowerword())
  745. if not os.path.exists (self.src_path_of(dmg_template)):
  746. dmg_template = os.path.join ('installers', 'darwin', 'release-dmg')
  747. for s,d in {self.get_dst_prefix():app_name + ".app",
  748. os.path.join(dmg_template, "_VolumeIcon.icns"): ".VolumeIcon.icns",
  749. os.path.join(dmg_template, "background.jpg"): "background.jpg",
  750. os.path.join(dmg_template, "_DS_Store"): ".DS_Store"}.items():
  751. print "Copying to dmg", s, d
  752. self.copy_action(self.src_path_of(s), os.path.join(volpath, d))
  753. # Hide the background image, DS_Store file, and volume icon file (set their "visible" bit)
  754. for f in ".VolumeIcon.icns", "background.jpg", ".DS_Store":
  755. pathname = os.path.join(volpath, f)
  756. # We've observed mysterious "no such file" failures of the SetFile
  757. # command, especially on the first file listed above -- yet
  758. # subsequent inspection of the target directory confirms it's
  759. # there. Timing problem with copy command? Try to handle.
  760. for x in xrange(3):
  761. if os.path.exists(pathname):
  762. print "Confirmed existence: %r" % pathname
  763. break
  764. print "Waiting for %s copy command to complete (%s)..." % (f, x+1)
  765. sys.stdout.flush()
  766. time.sleep(1)
  767. # If we fall out of the loop above without a successful break, oh
  768. # well, possibly we've mistaken the nature of the problem. In any
  769. # case, don't hang up the whole build looping indefinitely, let
  770. # the original problem manifest by executing the desired command.
  771. self.run_command('SetFile -a V %r' % pathname)
  772. # Create the alias file (which is a resource file) from the .r
  773. self.run_command('Rez %r -o %r' %
  774. (self.src_path_of("installers/darwin/release-dmg/Applications-alias.r"),
  775. os.path.join(volpath, "Applications")))
  776. # Set the alias file's alias and custom icon bits
  777. self.run_command('SetFile -a AC %r' % os.path.join(volpath, "Applications"))
  778. # Set the disk image root's custom icon bit
  779. self.run_command('SetFile -a C %r' % volpath)
  780. finally:
  781. # Unmount the image even if exceptions from any of the above
  782. self.run_command('hdiutil detach -force %r' % devfile)
  783. print "Converting temp disk image to final disk image"
  784. self.run_command('hdiutil convert %(sparse)r -format UDZO -imagekey zlib-level=9 -o %(final)r' % {'sparse':sparsename, 'final':finalname})
  785. # get rid of the temp file
  786. self.package_file = finalname
  787. self.remove(sparsename)
  788. class LinuxManifest(ViewerManifest):
  789. def construct(self):
  790. super(LinuxManifest, self).construct()
  791. self.path("licenses-linux.txt","licenses.txt")
  792. if self.prefix("linux_tools", dst=""):
  793. self.path("client-readme.txt","README-linux.txt")
  794. self.path("client-readme-voice.txt","README-linux-voice.txt")
  795. self.path("client-readme-joystick.txt","README-linux-joystick.txt")
  796. self.path("wrapper.sh","secondlife")
  797. self.path("handle_secondlifeprotocol.sh", "etc/handle_secondlifeprotocol.sh")
  798. self.path("register_secondlifeprotocol.sh", "etc/register_secondlifeprotocol.sh")
  799. self.path("refresh_desktop_app_entry.sh", "etc/refresh_desktop_app_entry.sh")
  800. self.path("launch_url.sh","etc/launch_url.sh")
  801. self.path("install.sh")
  802. self.end_prefix("linux_tools")
  803. # Create an appropriate gridargs.dat for this package, denoting required grid.
  804. self.put_in_file(self.flags_list(), 'etc/gridargs.dat')
  805. self.path("secondlife-bin","bin/do-not-directly-run-secondlife-bin")
  806. self.path("../linux_crash_logger/linux-crash-logger","bin/linux-crash-logger.bin")
  807. self.path("../linux_updater/linux-updater", "bin/linux-updater.bin")
  808. self.path("../llplugin/slplugin/SLPlugin", "bin/SLPlugin")
  809. if self.prefix("res-sdl"):
  810. self.path("*")
  811. # recurse
  812. self.end_prefix("res-sdl")
  813. # Get the icons based on the channel
  814. icon_path = self.icon_path()
  815. if self.prefix(src=icon_path, dst="") :
  816. self.path("secondlife_256.png","secondlife_icon.png")
  817. if self.prefix(src="",dst="res-sdl") :
  818. self.path("secondlife_256.BMP","ll_icon.BMP")
  819. self.end_prefix("res-sdl")
  820. self.end_prefix(icon_path)
  821. self.path("../viewer_components/updater/scripts/linux/update_install", "bin/update_install")
  822. # plugins
  823. if self.prefix(src="", dst="bin/llplugin"):
  824. self.path("../media_plugins/webkit/libmedia_plugin_webkit.so", "libmedia_plugin_webkit.so")
  825. self.path("../media_plugins/gstreamer010/libmedia_plugin_gstreamer010.so", "libmedia_plugin_gstreamer.so")
  826. self.end_prefix("bin/llplugin")
  827. try:
  828. self.path("../llcommon/libllcommon.so", "lib/libllcommon.so")
  829. except:
  830. print "Skipping llcommon.so (assuming llcommon was linked statically)"
  831. self.path("featuretable_linux.txt")
  832. def copy_finish(self):
  833. # Force executable permissions to be set for scripts
  834. # see CHOP-223 and http://mercurial.selenic.com/bts/issue1802
  835. for script in 'secondlife', 'bin/update_install':
  836. self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script))
  837. def package_finish(self):
  838. if 'installer_name' in self.args:
  839. installer_name = self.args['installer_name']
  840. else:
  841. installer_name_components = ['SecondLife_', self.args.get('arch')]
  842. installer_name_components.extend(self.args['version'])
  843. installer_name = "_".join(installer_name_components)
  844. if self.default_channel():
  845. if not self.default_grid():
  846. installer_name += '_' + self.args['grid'].upper()
  847. else:
  848. installer_name += '_' + self.channel_oneword().upper()
  849. if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer():
  850. print "* Going strip-crazy on the packaged binaries, since this is a RELEASE build"
  851. self.run_command("find %(d)r/bin %(d)r/lib -type f \\! -name update_install | xargs --no-run-if-empty strip -S" % {'d': self.get_dst_prefix()} ) # makes some small assumptions about our packaged dir structure
  852. # Fix access permissions
  853. self.run_command("""
  854. find %(dst)s -type d | xargs --no-run-if-empty chmod 755;
  855. find %(dst)s -type f -perm 0700 | xargs --no-run-if-empty chmod 0755;
  856. find %(dst)s -type f -perm 0500 | xargs --no-run-if-empty chmod 0555;
  857. find %(dst)s -type f -perm 0600 | xargs --no-run-if-empty chmod 0644;
  858. find %(dst)s -type f -perm 0400 | xargs --no-run-if-empty chmod 0444;
  859. true""" % {'dst':self.get_dst_prefix() })
  860. self.package_file = installer_name + '.tar.bz2'
  861. # temporarily move directory tree so that it has the right
  862. # name in the tarfile
  863. self.run_command("mv %(dst)s %(inst)s" % {
  864. 'dst': self.get_dst_prefix(),
  865. 'inst': self.build_path_of(installer_name)})
  866. try:
  867. # only create tarball if it's a release build.
  868. if self.args['buildtype'].lower() == 'release':
  869. # --numeric-owner hides the username of the builder for
  870. # security etc.
  871. self.run_command('tar -C %(dir)s --numeric-owner -cjf '
  872. '%(inst_path)s.tar.bz2 %(inst_name)s' % {
  873. 'dir': self.get_build_prefix(),
  874. 'inst_name': installer_name,
  875. 'inst_path':self.build_path_of(installer_name)})
  876. else:
  877. print "Skipping %s.tar.bz2 for non-Release build (%s)" % \
  878. (installer_name, self.args['buildtype'])
  879. finally:
  880. self.run_command("mv %(inst)s %(dst)s" % {
  881. 'dst': self.get_dst_prefix(),
  882. 'inst': self.build_path_of(installer_name)})
  883. class Linux_i686Manifest(LinuxManifest):
  884. def construct(self):
  885. super(Linux_i686Manifest, self).construct()
  886. if self.prefix("../packages/lib/release", dst="lib"):
  887. self.path("libapr-1.so")
  888. self.path("libapr-1.so.0")
  889. self.path("libapr-1.so.0.4.2")
  890. self.path("libaprutil-1.so")
  891. self.path("libaprutil-1.so.0")
  892. self.path("libaprutil-1.so.0.3.10")
  893. self.path("libbreakpad_client.so.0.0.0")
  894. self.path("libbreakpad_client.so.0")
  895. self.path("libbreakpad_client.so")
  896. self.path("libcollada14dom.so")
  897. self.path("libdb-5.1.so")
  898. self.path("libdb-5.so")
  899. self.path("libdb.so")
  900. self.path("libcrypto.so.1.0.0")
  901. self.path("libexpat.so.1.5.2")
  902. self.path("libssl.so.1.0.0")
  903. self.path("libglod.so")
  904. self.path("libminizip.so")
  905. self.path("libuuid.so")
  906. self.path("libuuid.so.16")
  907. self.path("libuuid.so.16.0.22")
  908. self.path("libSDL-1.2.so.0.11.3")
  909. self.path("libdirectfb-1.4.so.5.0.4")
  910. self.path("libfusion-1.4.so.5.0.4")
  911. self.path("libdirect-1.4.so.5.0.4")
  912. self.path("libopenjpeg.so.1.4.0")
  913. self.path("libopenjpeg.so.1")
  914. self.path("libopenjpeg.so")
  915. self.path("libalut.so")
  916. self.path("libopenal.so", "libopenal.so.1")
  917. self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname
  918. self.path("libfontconfig.so.1.4.4")
  919. self.path("libtcmalloc.so", "libtcmalloc.so") #formerly called google perf tools
  920. self.path("libtcmalloc.so.0", "libtcmalloc.so.0") #formerly called google perf tools
  921. self.path("libtcmalloc.so.0.1.0", "libtcmalloc.so.0.1.0") #formerly called google perf tools
  922. try:
  923. self.path("libfmod-3.75.so")
  924. pass
  925. except:
  926. print "Skipping libfmod-3.75.so - not found"
  927. pass
  928. self.end_prefix("lib")
  929. # Vivox runtimes
  930. if self.prefix(src="../packages/lib/release", dst="bin"):
  931. self.path("SLVoice")
  932. self.end_prefix()
  933. if self.prefix(src="../packages/lib/release", dst="lib"):
  934. self.path("libortp.so")
  935. self.path("libsndfile.so.1")
  936. #self.path("libvivoxoal.so.1") # no - we'll re-use the viewer's own OpenAL lib
  937. self.path("libvivoxsdk.so")
  938. self.path("libvivoxplatform.so")
  939. self.end_prefix("lib")
  940. if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer():
  941. print "* Going strip-crazy on the packaged binaries, since this is a RELEASE build"
  942. self.run_command("find %(d)r/bin %(d)r/lib -type f \\! -name update_install | xargs --no-run-if-empty strip -S" % {'d': self.get_dst_prefix()} ) # makes some small assumptions about our packaged dir structure
  943. class Linux_x86_64Manifest(LinuxManifest):
  944. def construct(self):
  945. super(Linux_x86_64Manifest, self).construct()
  946. # support file for valgrind debug tool
  947. self.path("secondlife-i686.supp")
  948. ################################################################
  949. if __name__ == "__main__":
  950. main()