- add sources.
[platform/framework/web/crosswalk.git] / src / remoting / webapp / build-webapp.py
1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Creates a directory with with the unpacked contents of the remoting webapp.
7
8 The directory will contain a copy-of or a link-to to all remoting webapp
9 resources.  This includes HTML/JS and any plugin binaries. The script also
10 massages resulting files appropriately with host plugin data. Finally,
11 a zip archive for all of the above is produced.
12 """
13
14 # Python 2.5 compatibility
15 from __future__ import with_statement
16
17 import os
18 import platform
19 import re
20 import shutil
21 import subprocess
22 import sys
23 import time
24 import zipfile
25
26 # Update the module path, assuming that this script is in src/remoting/webapp,
27 # and that the google_api_keys module is in src/google_apis. Note that
28 # sys.path[0] refers to the directory containing this script.
29 if __name__ == '__main__':
30   sys.path.append(
31       os.path.abspath(os.path.join(sys.path[0], '../../google_apis')))
32 import google_api_keys
33
34 def findAndReplace(filepath, findString, replaceString):
35   """Does a search and replace on the contents of a file."""
36   oldFilename = os.path.basename(filepath) + '.old'
37   oldFilepath = os.path.join(os.path.dirname(filepath), oldFilename)
38   os.rename(filepath, oldFilepath)
39   with open(oldFilepath) as input:
40     with open(filepath, 'w') as output:
41       for s in input:
42         output.write(s.replace(findString, replaceString))
43   os.remove(oldFilepath)
44
45
46 def createZip(zip_path, directory):
47   """Creates a zipfile at zip_path for the given directory."""
48   zipfile_base = os.path.splitext(os.path.basename(zip_path))[0]
49   zip = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
50   for (root, dirs, files) in os.walk(directory):
51     for f in files:
52       full_path = os.path.join(root, f)
53       rel_path = os.path.relpath(full_path, directory)
54       zip.write(full_path, os.path.join(zipfile_base, rel_path))
55   zip.close()
56
57
58 def replaceUrl(destination, url_name, url_value):
59   """Updates a URL in both plugin_settings.js and manifest.json."""
60   findAndReplace(os.path.join(destination, 'plugin_settings.js'),
61                  "'" + url_name + "'", "'" + url_value + "'")
62   findAndReplace(os.path.join(destination, 'manifest.json'),
63                  url_name, url_value)
64
65
66 def buildWebApp(buildtype, version, mimetype, destination, zip_path, plugin,
67                 files, locales, patches):
68   """Does the main work of building the webapp directory and zipfile.
69
70   Args:
71     buildtype: the type of build ("Official" or "Dev")
72     mimetype: A string with mimetype of plugin.
73     destination: A string with path to directory where the webapp will be
74                  written.
75     zipfile: A string with path to the zipfile to create containing the
76              contents of |destination|.
77     plugin: A string with path to the binary plugin for this webapp.
78     files: An array of strings listing the paths for resources to include
79            in this webapp.
80     locales: An array of strings listing locales, which are copied, along
81              with their directory structure from the _locales directory down.
82     patches: An array of strings listing patch files to be applied to the
83              webapp directory. Paths in the patch file should be relative to
84              the remoting/webapp directory, for example a/main.html. Since
85              'git diff -p' works relative to the src/ directory, patches
86              obtained this way will need to be edited.
87   """
88   # Ensure a fresh directory.
89   try:
90     shutil.rmtree(destination)
91   except OSError:
92     if os.path.exists(destination):
93       raise
94     else:
95       pass
96   os.mkdir(destination, 0775)
97
98   # Use symlinks on linux and mac for faster compile/edit cycle.
99   #
100   # On Windows Vista platform.system() can return 'Microsoft' with some
101   # versions of Python, see http://bugs.python.org/issue1082
102   # should_symlink = platform.system() not in ['Windows', 'Microsoft']
103   #
104   # TODO(ajwong): Pending decision on http://crbug.com/27185 we may not be
105   # able to load symlinked resources.
106   should_symlink = False
107
108   # Copy all the files.
109   for current_file in files:
110     destination_file = os.path.join(destination, os.path.basename(current_file))
111     destination_dir = os.path.dirname(destination_file)
112     if not os.path.exists(destination_dir):
113       os.makedirs(destination_dir, 0775)
114
115     if should_symlink:
116       # TODO(ajwong): Detect if we're vista or higher.  Then use win32file
117       # to create a symlink in that case.
118       targetname = os.path.relpath(os.path.realpath(current_file),
119                                    os.path.realpath(destination_file))
120       os.symlink(targetname, destination_file)
121     else:
122       shutil.copy2(current_file, destination_file)
123
124   # Copy all the locales, preserving directory structure
125   destination_locales = os.path.join(destination, "_locales")
126   os.mkdir(destination_locales , 0775)
127   remoting_locales = os.path.join(destination, "remoting_locales")
128   os.mkdir(remoting_locales , 0775)
129   for current_locale in locales:
130     extension = os.path.splitext(current_locale)[1]
131     if extension == '.json':
132       locale_id = os.path.split(os.path.split(current_locale)[0])[1]
133       destination_dir = os.path.join(destination_locales, locale_id)
134       destination_file = os.path.join(destination_dir,
135                                       os.path.split(current_locale)[1])
136       os.mkdir(destination_dir, 0775)
137       shutil.copy2(current_locale, destination_file)
138     elif extension == '.pak':
139       destination_file = os.path.join(remoting_locales,
140                                       os.path.split(current_locale)[1])
141       shutil.copy2(current_locale, destination_file)
142     else:
143       raise Exception("Unknown extension: " + current_locale);
144
145   # Create fake plugin files to appease the manifest checker.
146   # It requires that if there is a plugin listed in the manifest that
147   # there be a file in the plugin with that name.
148   names = [
149     'remoting_host_plugin.dll',  # Windows
150     'remoting_host_plugin.plugin',  # Mac
151     'libremoting_host_plugin.ia32.so',  # Linux 32
152     'libremoting_host_plugin.x64.so'  # Linux 64
153   ]
154   pluginName = os.path.basename(plugin)
155
156   for name in names:
157     if name != pluginName:
158       path = os.path.join(destination, name)
159       f = open(path, 'w')
160       f.write("placeholder for %s" % (name))
161       f.close()
162
163   # Copy the plugin. On some platforms (e.g. ChromeOS) plugin compilation may be
164   # disabled, in which case we don't need to copy anything.
165   if plugin:
166     newPluginPath = os.path.join(destination, pluginName)
167     if os.path.isdir(plugin):
168       # On Mac we have a directory.
169       shutil.copytree(plugin, newPluginPath)
170     else:
171       shutil.copy2(plugin, newPluginPath)
172
173     # Strip the linux build.
174     if ((platform.system() == 'Linux') and (buildtype == 'Official')):
175       subprocess.call(["strip", newPluginPath])
176
177   # Patch the files, if necessary. Do this before updating any placeholders
178   # in case any of the diff contexts refer to the placeholders.
179   for patch in patches:
180     patchfile = os.path.join(os.getcwd(), patch)
181     if subprocess.call(['patch', '-d', destination, '-i', patchfile,
182                         '-p1', '-F0', '-s']) != 0:
183       print 'Patch ' + patch + ' failed to apply.'
184       return 1
185
186   # Set the version number in the manifest version.
187   findAndReplace(os.path.join(destination, 'manifest.json'),
188                  'FULL_APP_VERSION',
189                  version)
190
191   # Set the correct mimetype.
192   findAndReplace(os.path.join(destination, 'plugin_settings.js'),
193                  'HOST_PLUGIN_MIMETYPE',
194                  mimetype)
195
196   # Allow host names for google services/apis to be overriden via env vars.
197   oauth2AccountsHost = os.environ.get(
198       'OAUTH2_ACCOUNTS_HOST', 'https://accounts.google.com')
199   oauth2ApiHost = os.environ.get(
200       'OAUTH2_API_HOST', 'https://www.googleapis.com')
201   directoryApiHost = os.environ.get(
202       'DIRECTORY_API_HOST', 'https://www.googleapis.com')
203   oauth2BaseUrl = oauth2AccountsHost + '/o/oauth2'
204   oauth2ApiBaseUrl = oauth2ApiHost + '/oauth2'
205   directoryApiBaseUrl = directoryApiHost + '/chromoting/v1'
206   replaceUrl(destination, 'OAUTH2_BASE_URL', oauth2BaseUrl)
207   replaceUrl(destination, 'OAUTH2_API_BASE_URL', oauth2ApiBaseUrl)
208   replaceUrl(destination, 'DIRECTORY_API_BASE_URL', directoryApiBaseUrl)
209   # Substitute hosts in the manifest's CSP list.
210   findAndReplace(os.path.join(destination, 'manifest.json'),
211                  'OAUTH2_ACCOUNTS_HOST', oauth2AccountsHost)
212   # Ensure we list the API host only once if it's the same for multiple APIs.
213   googleApiHosts = ' '.join(set([oauth2ApiHost, directoryApiHost]))
214   findAndReplace(os.path.join(destination, 'manifest.json'),
215                  'GOOGLE_API_HOSTS', googleApiHosts)
216
217   # WCS and the OAuth trampoline are both hosted on talkgadget. Split them into
218   # separate suffix/prefix variables to allow for wildcards in manifest.json.
219   talkGadgetHostSuffix = os.environ.get(
220       'TALK_GADGET_HOST_SUFFIX', 'talkgadget.google.com')
221   talkGadgetHostPrefix = os.environ.get(
222       'TALK_GADGET_HOST_PREFIX', 'https://chromoting-client.')
223   oauth2RedirectHostPrefix = os.environ.get(
224       'OAUTH2_REDIRECT_HOST_PREFIX', 'https://chromoting-oauth.')
225
226   # Use a wildcard in the manifest.json host specs if the prefixes differ.
227   talkGadgetHostJs = talkGadgetHostPrefix + talkGadgetHostSuffix
228   talkGadgetBaseUrl = talkGadgetHostJs + '/talkgadget/'
229   if talkGadgetHostPrefix == oauth2RedirectHostPrefix:
230     talkGadgetHostJson = talkGadgetHostJs
231   else:
232     talkGadgetHostJson = 'https://*.' + talkGadgetHostSuffix
233
234   # Set the correct OAuth2 redirect URL.
235   oauth2RedirectHostJs = oauth2RedirectHostPrefix + talkGadgetHostSuffix
236   oauth2RedirectHostJson = talkGadgetHostJson
237   oauth2RedirectPath = '/talkgadget/oauth/chrome-remote-desktop'
238   oauth2RedirectBaseUrlJs = oauth2RedirectHostJs + oauth2RedirectPath
239   oauth2RedirectBaseUrlJson = oauth2RedirectHostJson + oauth2RedirectPath
240   if buildtype == 'Official':
241     oauth2RedirectUrlJs = ("'" + oauth2RedirectBaseUrlJs +
242                            "/rel/' + chrome.i18n.getMessage('@@extension_id')")
243     oauth2RedirectUrlJson = oauth2RedirectBaseUrlJson + '/rel/*'
244   else:
245     oauth2RedirectUrlJs = "'" + oauth2RedirectBaseUrlJs + "/dev'"
246     oauth2RedirectUrlJson = oauth2RedirectBaseUrlJson + '/dev*'
247   thirdPartyAuthUrlJs = "'" + oauth2RedirectBaseUrlJs + "/thirdpartyauth'"
248   thirdPartyAuthUrlJson = oauth2RedirectBaseUrlJson + '/thirdpartyauth*'
249   findAndReplace(os.path.join(destination, 'plugin_settings.js'),
250                  "'TALK_GADGET_URL'", "'" + talkGadgetBaseUrl + "'")
251   findAndReplace(os.path.join(destination, 'plugin_settings.js'),
252                  "'OAUTH2_REDIRECT_URL'", oauth2RedirectUrlJs)
253   findAndReplace(os.path.join(destination, 'manifest.json'),
254                  'TALK_GADGET_HOST', talkGadgetHostJson)
255   findAndReplace(os.path.join(destination, 'manifest.json'),
256                  'OAUTH2_REDIRECT_URL', oauth2RedirectUrlJson)
257
258   # Configure xmpp server and directory bot settings in the plugin.
259   xmppServerAddress = os.environ.get(
260       'XMPP_SERVER_ADDRESS', 'talk.google.com:5222')
261   xmppServerUseTls = os.environ.get('XMPP_SERVER_USE_TLS', 'true')
262   directoryBotJid = os.environ.get(
263       'DIRECTORY_BOT_JID', 'remoting@bot.talk.google.com')
264
265   findAndReplace(os.path.join(destination, 'plugin_settings.js'),
266                  "'XMPP_SERVER_ADDRESS'", "'" + xmppServerAddress + "'")
267   findAndReplace(os.path.join(destination, 'plugin_settings.js'),
268                  "Boolean('XMPP_SERVER_USE_TLS')", xmppServerUseTls)
269   findAndReplace(os.path.join(destination, 'plugin_settings.js'),
270                  "'DIRECTORY_BOT_JID'", "'" + directoryBotJid + "'")
271   findAndReplace(os.path.join(destination, 'plugin_settings.js'),
272                  "'THIRD_PARTY_AUTH_REDIRECT_URL'",
273                  thirdPartyAuthUrlJs)
274   findAndReplace(os.path.join(destination, 'manifest.json'),
275                  "THIRD_PARTY_AUTH_REDIRECT_URL",
276                  thirdPartyAuthUrlJson)
277
278   # Set the correct API keys.
279   # For overriding the client ID/secret via env vars, see google_api_keys.py.
280   apiClientId = google_api_keys.GetClientID('REMOTING')
281   apiClientSecret = google_api_keys.GetClientSecret('REMOTING')
282   apiClientIdV2 = google_api_keys.GetClientID('REMOTING_IDENTITY_API')
283
284   findAndReplace(os.path.join(destination, 'plugin_settings.js'),
285                  "'API_CLIENT_ID'",
286                  "'" + apiClientId + "'")
287   findAndReplace(os.path.join(destination, 'plugin_settings.js'),
288                  "'API_CLIENT_SECRET'",
289                  "'" + apiClientSecret + "'")
290   findAndReplace(os.path.join(destination, 'manifest.json'),
291                  '"REMOTING_IDENTITY_API_CLIENT_ID"',
292                  '"' + apiClientIdV2 + '"')
293
294   # Use a consistent extension id for unofficial builds.
295   if buildtype != 'Official':
296     manifestKey = '"key": "remotingdevbuild",'
297   else:
298     manifestKey = ''
299   findAndReplace(os.path.join(destination, 'manifest.json'),
300                  'MANIFEST_KEY_FOR_UNOFFICIAL_BUILD', manifestKey)
301
302   # Make the zipfile.
303   createZip(zip_path, destination)
304
305   return 0
306
307
308 def main():
309   if len(sys.argv) < 7:
310     print ('Usage: build-webapp.py '
311            '<build-type> <version> <mime-type> <dst> <zip-path> <plugin> '
312            '<other files...> [--patches <patches...>] '
313            '[--locales <locales...>]')
314     return 1
315
316   arg_type = ''
317   files = []
318   locales = []
319   patches = []
320   for arg in sys.argv[7:]:
321     if arg == '--locales' or arg == '--patches':
322       arg_type = arg
323     elif arg_type == '--locales':
324       locales.append(arg)
325     elif arg_type == '--patches':
326       patches.append(arg)
327     else:
328       files.append(arg)
329
330   return buildWebApp(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4],
331                      sys.argv[5], sys.argv[6], files, locales, patches)
332
333
334 if __name__ == '__main__':
335   sys.exit(main())