Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / remoting / webapp / build-webapp.py
blobdf934f83b929701b5ec014b7efbaabe9c41eaa65
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.
6 """Creates a directory with with the unpacked contents of the remoting webapp.
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 """
14 # Python 2.5 compatibility
15 from __future__ import with_statement
17 import io
18 import os
19 import platform
20 import re
21 import shutil
22 import subprocess
23 import sys
24 import time
25 import zipfile
27 # Update the module path, assuming that this script is in src/remoting/webapp,
28 # and that the google_api_keys module is in src/google_apis. Note that
29 # sys.path[0] refers to the directory containing this script.
30 if __name__ == '__main__':
31 sys.path.append(
32 os.path.abspath(os.path.join(sys.path[0], '../../google_apis')))
33 import google_api_keys
35 def findAndReplace(filepath, findString, replaceString):
36 """Does a search and replace on the contents of a file."""
37 oldFilename = os.path.basename(filepath) + '.old'
38 oldFilepath = os.path.join(os.path.dirname(filepath), oldFilename)
39 os.rename(filepath, oldFilepath)
40 with open(oldFilepath) as input:
41 with open(filepath, 'w') as output:
42 for s in input:
43 output.write(s.replace(findString, replaceString))
44 os.remove(oldFilepath)
47 def createZip(zip_path, directory):
48 """Creates a zipfile at zip_path for the given directory."""
49 zipfile_base = os.path.splitext(os.path.basename(zip_path))[0]
50 zip = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
51 for (root, dirs, files) in os.walk(directory):
52 for f in files:
53 full_path = os.path.join(root, f)
54 rel_path = os.path.relpath(full_path, directory)
55 zip.write(full_path, os.path.join(zipfile_base, rel_path))
56 zip.close()
59 def replaceString(destination, placeholder, value):
60 findAndReplace(os.path.join(destination, 'plugin_settings.js'),
61 "'" + placeholder + "'", "'" + value + "'")
64 def processJinjaTemplate(input_file, output_file, context):
65 jinja2_path = os.path.normpath(
66 os.path.join(os.path.abspath(__file__),
67 '../../../third_party/jinja2'))
68 sys.path.append(os.path.split(jinja2_path)[0])
69 import jinja2
70 (template_path, template_name) = os.path.split(input_file)
71 env = jinja2.Environment(loader=jinja2.FileSystemLoader(template_path))
72 template = env.get_template(template_name)
73 rendered = template.render(context)
74 io.open(output_file, 'w', encoding='utf-8').write(rendered)
78 def buildWebApp(buildtype, version, destination, zip_path,
79 manifest_template, webapp_type, files, locales):
80 """Does the main work of building the webapp directory and zipfile.
82 Args:
83 buildtype: the type of build ("Official" or "Dev").
84 destination: A string with path to directory where the webapp will be
85 written.
86 zipfile: A string with path to the zipfile to create containing the
87 contents of |destination|.
88 manifest_template: jinja2 template file for manifest.
89 webapp_type: webapp type ("v1", "v2" or "v2_pnacl").
90 files: An array of strings listing the paths for resources to include
91 in this webapp.
92 locales: An array of strings listing locales, which are copied, along
93 with their directory structure from the _locales directory down.
94 """
95 # Ensure a fresh directory.
96 try:
97 shutil.rmtree(destination)
98 except OSError:
99 if os.path.exists(destination):
100 raise
101 else:
102 pass
103 os.mkdir(destination, 0775)
105 # Use symlinks on linux and mac for faster compile/edit cycle.
107 # On Windows Vista platform.system() can return 'Microsoft' with some
108 # versions of Python, see http://bugs.python.org/issue1082
109 # should_symlink = platform.system() not in ['Windows', 'Microsoft']
111 # TODO(ajwong): Pending decision on http://crbug.com/27185 we may not be
112 # able to load symlinked resources.
113 should_symlink = False
115 # Copy all the files.
116 for current_file in files:
117 destination_file = os.path.join(destination, os.path.basename(current_file))
118 destination_dir = os.path.dirname(destination_file)
119 if not os.path.exists(destination_dir):
120 os.makedirs(destination_dir, 0775)
122 if should_symlink:
123 # TODO(ajwong): Detect if we're vista or higher. Then use win32file
124 # to create a symlink in that case.
125 targetname = os.path.relpath(os.path.realpath(current_file),
126 os.path.realpath(destination_file))
127 os.symlink(targetname, destination_file)
128 else:
129 shutil.copy2(current_file, destination_file)
131 # Copy all the locales, preserving directory structure
132 destination_locales = os.path.join(destination, "_locales")
133 os.mkdir(destination_locales , 0775)
134 remoting_locales = os.path.join(destination, "remoting_locales")
135 os.mkdir(remoting_locales , 0775)
136 for current_locale in locales:
137 extension = os.path.splitext(current_locale)[1]
138 if extension == '.json':
139 locale_id = os.path.split(os.path.split(current_locale)[0])[1]
140 destination_dir = os.path.join(destination_locales, locale_id)
141 destination_file = os.path.join(destination_dir,
142 os.path.split(current_locale)[1])
143 os.mkdir(destination_dir, 0775)
144 shutil.copy2(current_locale, destination_file)
145 elif extension == '.pak':
146 destination_file = os.path.join(remoting_locales,
147 os.path.split(current_locale)[1])
148 shutil.copy2(current_locale, destination_file)
149 else:
150 raise Exception("Unknown extension: " + current_locale);
152 # Set client plugin type.
153 client_plugin = 'pnacl' if webapp_type == 'v2_pnacl' else 'native'
154 findAndReplace(os.path.join(destination, 'plugin_settings.js'),
155 "'CLIENT_PLUGIN_TYPE'", "'" + client_plugin + "'")
157 # Allow host names for google services/apis to be overriden via env vars.
158 oauth2AccountsHost = os.environ.get(
159 'OAUTH2_ACCOUNTS_HOST', 'https://accounts.google.com')
160 oauth2ApiHost = os.environ.get(
161 'OAUTH2_API_HOST', 'https://www.googleapis.com')
162 directoryApiHost = os.environ.get(
163 'DIRECTORY_API_HOST', 'https://www.googleapis.com')
164 oauth2BaseUrl = oauth2AccountsHost + '/o/oauth2'
165 oauth2ApiBaseUrl = oauth2ApiHost + '/oauth2'
166 directoryApiBaseUrl = directoryApiHost + '/chromoting/v1'
167 replaceString(destination, 'OAUTH2_BASE_URL', oauth2BaseUrl)
168 replaceString(destination, 'OAUTH2_API_BASE_URL', oauth2ApiBaseUrl)
169 replaceString(destination, 'DIRECTORY_API_BASE_URL', directoryApiBaseUrl)
170 # Substitute hosts in the manifest's CSP list.
171 # Ensure we list the API host only once if it's the same for multiple APIs.
172 googleApiHosts = ' '.join(set([oauth2ApiHost, directoryApiHost]))
174 # WCS and the OAuth trampoline are both hosted on talkgadget. Split them into
175 # separate suffix/prefix variables to allow for wildcards in manifest.json.
176 talkGadgetHostSuffix = os.environ.get(
177 'TALK_GADGET_HOST_SUFFIX', 'talkgadget.google.com')
178 talkGadgetHostPrefix = os.environ.get(
179 'TALK_GADGET_HOST_PREFIX', 'https://chromoting-client.')
180 oauth2RedirectHostPrefix = os.environ.get(
181 'OAUTH2_REDIRECT_HOST_PREFIX', 'https://chromoting-oauth.')
183 # Use a wildcard in the manifest.json host specs if the prefixes differ.
184 talkGadgetHostJs = talkGadgetHostPrefix + talkGadgetHostSuffix
185 talkGadgetBaseUrl = talkGadgetHostJs + '/talkgadget/'
186 if talkGadgetHostPrefix == oauth2RedirectHostPrefix:
187 talkGadgetHostJson = talkGadgetHostJs
188 else:
189 talkGadgetHostJson = 'https://*.' + talkGadgetHostSuffix
191 # Set the correct OAuth2 redirect URL.
192 oauth2RedirectHostJs = oauth2RedirectHostPrefix + talkGadgetHostSuffix
193 oauth2RedirectHostJson = talkGadgetHostJson
194 oauth2RedirectPath = '/talkgadget/oauth/chrome-remote-desktop'
195 oauth2RedirectBaseUrlJs = oauth2RedirectHostJs + oauth2RedirectPath
196 oauth2RedirectBaseUrlJson = oauth2RedirectHostJson + oauth2RedirectPath
197 if buildtype == 'Official':
198 oauth2RedirectUrlJs = ("'" + oauth2RedirectBaseUrlJs +
199 "/rel/' + chrome.i18n.getMessage('@@extension_id')")
200 oauth2RedirectUrlJson = oauth2RedirectBaseUrlJson + '/rel/*'
201 else:
202 oauth2RedirectUrlJs = "'" + oauth2RedirectBaseUrlJs + "/dev'"
203 oauth2RedirectUrlJson = oauth2RedirectBaseUrlJson + '/dev*'
204 thirdPartyAuthUrlJs = oauth2RedirectBaseUrlJs + "/thirdpartyauth"
205 thirdPartyAuthUrlJson = oauth2RedirectBaseUrlJson + '/thirdpartyauth*'
206 replaceString(destination, "TALK_GADGET_URL", talkGadgetBaseUrl)
207 findAndReplace(os.path.join(destination, 'plugin_settings.js'),
208 "'OAUTH2_REDIRECT_URL'", oauth2RedirectUrlJs)
210 # Configure xmpp server and directory bot settings in the plugin.
211 xmppServerAddress = os.environ.get(
212 'XMPP_SERVER_ADDRESS', 'talk.google.com:5222')
213 xmppServerUseTls = os.environ.get('XMPP_SERVER_USE_TLS', 'true')
214 directoryBotJid = os.environ.get(
215 'DIRECTORY_BOT_JID', 'remoting@bot.talk.google.com')
217 findAndReplace(os.path.join(destination, 'plugin_settings.js'),
218 "Boolean('XMPP_SERVER_USE_TLS')", xmppServerUseTls)
219 replaceString(destination, "XMPP_SERVER_ADDRESS", xmppServerAddress)
220 replaceString(destination, "DIRECTORY_BOT_JID", directoryBotJid)
221 replaceString(destination, "THIRD_PARTY_AUTH_REDIRECT_URL",
222 thirdPartyAuthUrlJs)
224 # Set the correct API keys.
225 # For overriding the client ID/secret via env vars, see google_api_keys.py.
226 apiClientId = google_api_keys.GetClientID('REMOTING')
227 apiClientSecret = google_api_keys.GetClientSecret('REMOTING')
228 apiClientIdV2 = google_api_keys.GetClientID('REMOTING_IDENTITY_API')
230 replaceString(destination, "API_CLIENT_ID", apiClientId)
231 replaceString(destination, "API_CLIENT_SECRET", apiClientSecret)
233 # Use a consistent extension id for unofficial builds.
234 if buildtype != 'Official':
235 manifestKey = '"key": "remotingdevbuild",'
236 else:
237 manifestKey = ''
239 # Generate manifest.
240 context = {
241 'webapp_type': webapp_type,
242 'FULL_APP_VERSION': version,
243 'MANIFEST_KEY_FOR_UNOFFICIAL_BUILD': manifestKey,
244 'OAUTH2_REDIRECT_URL': oauth2RedirectUrlJson,
245 'TALK_GADGET_HOST': talkGadgetHostJson,
246 'THIRD_PARTY_AUTH_REDIRECT_URL': thirdPartyAuthUrlJson,
247 'REMOTING_IDENTITY_API_CLIENT_ID': apiClientIdV2,
248 'OAUTH2_BASE_URL': oauth2BaseUrl,
249 'OAUTH2_API_BASE_URL': oauth2ApiBaseUrl,
250 'DIRECTORY_API_BASE_URL': directoryApiBaseUrl,
251 'OAUTH2_ACCOUNTS_HOST': oauth2AccountsHost,
252 'GOOGLE_API_HOSTS': googleApiHosts,
254 processJinjaTemplate(manifest_template,
255 os.path.join(destination, 'manifest.json'),
256 context)
258 # Make the zipfile.
259 createZip(zip_path, destination)
261 return 0
264 def main():
265 if len(sys.argv) < 6:
266 print ('Usage: build-webapp.py '
267 '<build-type> <version> <dst> <zip-path> <manifest_template> '
268 '<webapp_type> <other files...> '
269 '[--locales <locales...>]')
270 return 1
272 arg_type = ''
273 files = []
274 locales = []
275 for arg in sys.argv[7:]:
276 if arg in ['--locales']:
277 arg_type = arg
278 elif arg_type == '--locales':
279 locales.append(arg)
280 else:
281 files.append(arg)
283 return buildWebApp(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4],
284 sys.argv[5], sys.argv[6], files, locales)
287 if __name__ == '__main__':
288 sys.exit(main())