setup.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. import os
  2. import sys
  3. import platform
  4. import glob
  5. import subprocess
  6. from setuptools import setup, find_packages
  7. CLIENT_VERSION = None
  8. execfile('src/mullvad/version.py')
  9. base_dir = os.path.dirname(__file__)
  10. with open(os.path.join(base_dir, 'README.rst')) as f:
  11. long_description = f.read()
  12. with open(os.path.join(base_dir, 'CHANGES.rst')) as f:
  13. long_description = '\n'.join([long_description, f.read()])
  14. common_args = dict(
  15. version=CLIENT_VERSION,
  16. description='The Mullvad VPN Client',
  17. long_description=long_description,
  18. url='https://www.mullvad.net/',
  19. author='Amagicom AB',
  20. author_email='admin@mullvad.net',
  21. license='GPL-2+',
  22. classifiers=[
  23. ('License :: OSI Approved :: '
  24. 'GNU General Public License v2 or later (GPLv2+)'),
  25. 'Development Status :: 5 - Production/Stable',
  26. 'Intended Audience :: End Users/Desktop',
  27. 'Natural Language :: English',
  28. 'Natural Language :: French',
  29. 'Natural Language :: Swedish',
  30. 'Operating System :: MacOS :: MacOS X',
  31. 'Operating System :: Microsoft :: Windows',
  32. 'Programming Language :: Python :: 2 :: Only',
  33. 'Topic :: Internet',
  34. 'Topic :: Security',
  35. ],
  36. keywords='vpn privacy anonymity security',
  37. package_dir={
  38. '': 'src',
  39. },
  40. packages=find_packages('src', exclude=['tests']),
  41. extras_require={
  42. 'obfsproxy': ['obfsproxy'],
  43. },
  44. )
  45. MANIFEST_TEMPLATE = """
  46. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  47. <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  48. <assemblyIdentity
  49. version="5.0.0.0"
  50. processorArchitecture="x86"
  51. name="%(prog)s"
  52. type="win32"
  53. />
  54. <description>%(prog)s</description>
  55. <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
  56. <security>
  57. <requestedPrivileges>
  58. <requestedExecutionLevel
  59. level="requireAdministrator"
  60. uiAccess="false">
  61. </requestedExecutionLevel>
  62. </requestedPrivileges>
  63. </security>
  64. </trustInfo>
  65. <dependency>
  66. <dependentAssembly>
  67. <assemblyIdentity
  68. type="win32"
  69. name="Microsoft.VC90.CRT"
  70. version="9.0.21022.8"
  71. processorArchitecture="x86"
  72. publicKeyToken="1fc8b3b9a1e18e3b">
  73. </assemblyIdentity>
  74. </dependentAssembly>
  75. </dependency>
  76. <dependency>
  77. <dependentAssembly>
  78. <assemblyIdentity
  79. type="win32"
  80. name="Microsoft.Windows.Common-Controls"
  81. version="6.0.0.0"
  82. processorArchitecture="X86"
  83. publicKeyToken="6595b64144ccf1df"
  84. language="*"
  85. />
  86. </dependentAssembly>
  87. </dependency>
  88. </assembly>
  89. """
  90. APPLE_SCRIPT = """
  91. tell application "Finder"
  92. tell disk "wc"
  93. open
  94. tell container window
  95. set current view to icon view
  96. set toolbar visible to false
  97. set statusbar visible to false
  98. set the bounds to {0, 0, 512, 256}
  99. -- set statusbar visible to false
  100. end tell
  101. set opts to the icon view options of container window
  102. tell opts
  103. set icon size to 128
  104. set arrangement to not arranged
  105. end tell
  106. -- TODO Uncomment when background image scaling problem is solved
  107. -- set background picture of opts to file ".background:mullvad_background.png"
  108. tell container window
  109. set position of item "Mullvad.app" to {128, 110}
  110. set position of item "Applications" to {384, 110}
  111. end tell
  112. update without registering applications
  113. end tell
  114. end tell"""
  115. # Helper functions =======================================================
  116. def call(command, **kwargs):
  117. subprocess.call(command.split(), **kwargs)
  118. def create_pot(in_file, out_file, po_dir):
  119. """Generate a pot file from the given source file and merge with existing
  120. po files if any exist in the given directory.
  121. """
  122. call('xgettext -L Python {} -o {}'.format(in_file, out_file))
  123. call('mkdir -p {}'.format(po_dir))
  124. for po in glob.glob(os.path.join(po_dir, '*')):
  125. print 'Merging with existing po:', po
  126. call('msgmerge --update --backup=off {} {}'.format(po, out_file))
  127. os.remove(out_file)
  128. def create_locale(locale_dir, po_dir, domain):
  129. """Compile all po files in the given source directory into mo files for the
  130. given domain and store thme in the given destination directory.
  131. """
  132. po_files = glob.glob(os.path.join(po_dir, '*'))
  133. for po in po_files:
  134. lang = os.path.splitext(os.path.basename(po))[0]
  135. dest_dir = os.path.join(locale_dir, lang, 'LC_MESSAGES')
  136. call('mkdir -p {}'.format(dest_dir))
  137. output = os.path.join(dest_dir, domain + '.mo')
  138. print 'Creating', output
  139. call('msgfmt -o {} {}'.format(output, po))
  140. def list_tree(root, include_root=True, dest_root=''):
  141. """Generates a list of all files in the given directory tree formatted as
  142. an argument to the data_files parameter in setup().
  143. If include_root is True, the entire tree will be included in the
  144. distribution. dest_root can be used to store the tree under a given path
  145. in the distribution.
  146. """
  147. result = []
  148. for dirpath, dirs, files in os.walk(root):
  149. dest = dirpath
  150. if not include_root:
  151. dest = os.path.relpath(dirpath, root)
  152. if dest == '.':
  153. dest = ''
  154. if files:
  155. files = [os.path.join(dirpath, f) for f in files]
  156. result.append((os.path.join(dest_root, dest), files))
  157. return result
  158. def create_dmg(name):
  159. MASTER_DMG = '{}-{}.dmg'.format(name, CLIENT_VERSION)
  160. # Create an empty image
  161. call('mkdir -p template')
  162. call('hdiutil create -fs HFSX -layout SPUD -size 100m {} -srcfolder '
  163. 'template -format UDRW -volname {}'.format('wc.dmg', name))
  164. # Create a mount point and mount the image
  165. call('mkdir -p wc')
  166. call('hdiutil attach {} -noautoopen -quiet -mountpoint {}'.format(
  167. 'wc.dmg', 'wc'))
  168. # Copy the app to the image
  169. call('ditto -rsrc dist/Mullvad.app wc/Mullvad.app')
  170. # TODO Uncomment this when image scaling problem is solved
  171. # Create a hidden directory on the image which contains a background image
  172. # call('mkdir -p wc/.background')
  173. # call('ditto -rsrc mac/mullvad_background.png '
  174. # 'wc/.background/mullvad_background.png')
  175. # Create a shortcut to Applications on the image to make installation
  176. # easier
  177. call('ln -s /Applications wc/Applications')
  178. # Apply a script to the image which defines it's layout
  179. with open('tempscript', 'w') as f:
  180. f.write(APPLE_SCRIPT)
  181. call('osascript tempscript')
  182. # Unmount the disk image
  183. call('hdiutil detach wc -quiet -force')
  184. # Remove old MASTER_DMG if exists
  185. call('rm -f {}'.format(MASTER_DMG))
  186. call('hdiutil convert {} -quiet -format UDZO -imagekey '
  187. 'zlib-level=9 -o {}'.format('wc.dmg', MASTER_DMG))
  188. # Cleanup
  189. call('rm -rf wc wc.dmg tempscript template')
  190. # ========================================================================
  191. if 'pot' in sys.argv:
  192. create_pot('src/mullvad/mui.py', 'msgs.pot', 'po')
  193. elif 'locale' in sys.argv:
  194. create_locale('src/mullvad/locale', 'po', 'mullvad')
  195. create_locale('locale', 'po', 'mullvad')
  196. elif 'dmg' in sys.argv and platform.system() == 'Darwin':
  197. create_dmg('Mullvad')
  198. elif platform.system() == 'Windows': # ==================================
  199. import py2exe
  200. import jinja2
  201. with open('winstaller.nsi.template', 'r') as f:
  202. nsi_template = jinja2.Template(f.read())
  203. with open('winstaller.nsi', 'w') as f:
  204. f.write(nsi_template.render(version=CLIENT_VERSION))
  205. windows_data_files = [
  206. ('ssl', glob.glob('src/mullvad/ssl/*')),
  207. ('', [
  208. 'src/mullvad/client.conf.windows',
  209. 'src/mullvad/backupservers.txt',
  210. 'src/mullvad/harddnsbackup.txt',
  211. 'src/mullvad/openssl.cnf',
  212. 'src/mullvad/mullvad.png',
  213. 'src/mullvad/rdot.png',
  214. 'src/mullvad/ydot.png',
  215. 'src/mullvad/gdot.png',
  216. ]),
  217. ]
  218. windows_data_files.extend(list_tree('locale'))
  219. windows_data_files.extend(list_tree('client-binaries/windows', False))
  220. setup(
  221. name='mullvad',
  222. windows=[{
  223. 'script': 'client-binaries/windows/mullvad.py',
  224. 'icon_resources': [
  225. (1, 'client-binaries/windows/mullvad.ico')
  226. ],
  227. 'other_resources': [
  228. (24, 1, MANIFEST_TEMPLATE % dict(prog='Mullvad Client'))
  229. ],
  230. }],
  231. options={
  232. 'py2exe': {
  233. 'excludes': ['Tkinter'],
  234. 'dll_excludes': [
  235. 'w9xpopen.exe',
  236. 'MSVCP90.dll',
  237. # Pulled in by psutil
  238. 'IPHLPAPI.DLL',
  239. 'NSI.dll',
  240. 'WINNSI.DLL',
  241. 'WTSAPI32.dll',
  242. 'API-MS-Win-Core-DelayLoad-L1-1-0.dll',
  243. 'API-MS-Win-Core-ErrorHandling-L1-1-0.dll',
  244. 'API-MS-Win-Core-File-L1-1-0.dll',
  245. 'API-MS-Win-Core-Handle-L1-1-0.dll',
  246. 'API-MS-Win-Core-Heap-L1-1-0.dll',
  247. 'API-MS-Win-Core-Interlocked-L1-1-0.dll',
  248. 'API-MS-Win-Core-IO-L1-1-0.dll',
  249. 'API-MS-Win-Core-LibraryLoader-L1-1-0.dll',
  250. 'API-MS-Win-Core-LocalRegistry-L1-1-0.dll',
  251. 'API-MS-Win-Core-Misc-L1-1-0.dll',
  252. 'API-MS-Win-Core-ProcessThreads-L1-1-0.dll',
  253. 'API-MS-Win-Core-Profile-L1-1-0.dll',
  254. 'API-MS-Win-Core-String-L1-1-0.dll',
  255. 'API-MS-Win-Core-Synch-L1-1-0.dll',
  256. 'API-MS-Win-Core-SysInfo-L1-1-0.dll',
  257. 'API-MS-Win-Core-ThreadPool-L1-1-0.dll',
  258. 'API-MS-Win-Security-Base-L1-1-0.dll',
  259. ],
  260. },
  261. },
  262. data_files=windows_data_files,
  263. **common_args
  264. )
  265. elif platform.system() == 'Darwin': # ===================================
  266. osx_data_files = [
  267. ('', glob.glob('client-binaries/mac/include/*')),
  268. ('ssl', glob.glob('src/mullvad/ssl/*')),
  269. ('', [
  270. 'src/mullvad/client.conf.mac',
  271. 'src/mullvad/backupservers.txt',
  272. 'src/mullvad/harddnsbackup.txt',
  273. 'src/mullvad/openssl.cnf',
  274. 'src/mullvad/mullvad.xpm',
  275. 'src/mullvad/mullvad.png',
  276. 'src/mullvad/rdot.png',
  277. 'src/mullvad/ydot.png',
  278. 'src/mullvad/gdot.png',
  279. ]),
  280. ]
  281. osx_data_files.extend(list_tree('locale'))
  282. setup(
  283. name='Mullvad',
  284. setup_requires=['py2app'],
  285. # Specify the script to be used as entry point for the application
  286. app=['client-binaries/mac/mullvad_mac.py'],
  287. options={
  288. 'py2app': {
  289. 'iconfile': 'client-binaries/mac/mullvad.icns',
  290. # Specify packages to include. Specifying the package in the
  291. # setup arguments is unfortunately not enough
  292. 'packages': ['src/mullvad'],
  293. 'argv_emulation': True, # Enable file-drop
  294. 'arch': 'i386', # wxPython doesn't like 64bit which is default
  295. },
  296. },
  297. data_files=osx_data_files,
  298. **common_args
  299. )
  300. os.system('chmod a+x dist/mullvad.app/Contents/Resources/openvpn')
  301. os.system('chmod a+x dist/mullvad.app/Contents/Resources/obfsproxy')
  302. os.system('chmod a+x '
  303. 'dist/mullvad.app/Contents/Resources/process-network-changes')
  304. os.system('chmod a+x '
  305. 'dist/mullvad.app/Contents/Resources/client.up.osx.sh')
  306. os.system('chmod a+x '
  307. 'dist/mullvad.app/Contents/Resources/client.down.osx.sh')
  308. else: # =================================================================
  309. # data_files only applies when creating a built distribution such as a
  310. # debian package.
  311. data_files = [
  312. ('share/pixmaps', ['src/mullvad/mullvad.png']),
  313. ('share/applications', ['src/mullvad/mullvad.desktop']),
  314. ]
  315. data_files.extend(list_tree('icons', dest_root='share'))
  316. setup(
  317. name='mullvad',
  318. include_package_data=True,
  319. data_files=data_files,
  320. entry_points={
  321. 'gui_scripts': [
  322. 'mullvad=mullvad.mui:main',
  323. ],
  324. 'console_scripts': [
  325. 'mtunnel=mullvad.tunnelprocess:main',
  326. ],
  327. },
  328. **common_args
  329. )