Files.app: Resize MetadataProviderCache depneding on file number in the current direc...
[chromium-blink-merge.git] / remoting / webapp / build-webapp.py
blob9deddc69b4aa7c21eec9c25ba2599921566b132b
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
36 def findAndReplace(filepath, findString, replaceString):
37 """Does a search and replace on the contents of a file."""
38 oldFilename = os.path.basename(filepath) + '.old'
39 oldFilepath = os.path.join(os.path.dirname(filepath), oldFilename)
40 os.rename(filepath, oldFilepath)
41 with open(oldFilepath) as input:
42 with open(filepath, 'w') as output:
43 for s in input:
44 output.write(s.replace(findString, replaceString))
45 os.remove(oldFilepath)
48 def createZip(zip_path, directory):
49 """Creates a zipfile at zip_path for the given directory."""
50 zipfile_base = os.path.splitext(os.path.basename(zip_path))[0]
51 zip = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
52 for (root, dirs, files) in os.walk(directory):
53 for f in files:
54 full_path = os.path.join(root, f)
55 rel_path = os.path.relpath(full_path, directory)
56 zip.write(full_path, os.path.join(zipfile_base, rel_path))
57 zip.close()
60 def replaceString(destination, placeholder, value):
61 findAndReplace(os.path.join(destination, 'plugin_settings.js'),
62 "'" + placeholder + "'", "'" + value + "'")
65 def processJinjaTemplate(input_file, include_paths, output_file, context):
66 jinja2_path = os.path.normpath(
67 os.path.join(os.path.abspath(__file__),
68 '../../../third_party/jinja2'))
69 sys.path.append(os.path.split(jinja2_path)[0])
70 import jinja2
71 (template_path, template_name) = os.path.split(input_file)
72 include_paths = [template_path] + include_paths
73 env = jinja2.Environment(loader=jinja2.FileSystemLoader(include_paths))
74 template = env.get_template(template_name)
75 rendered = template.render(context)
76 io.open(output_file, 'w', encoding='utf-8').write(rendered)
78 def buildWebApp(buildtype, version, destination, zip_path,
79 manifest_template, webapp_type, app_id, app_name,
80 app_description, app_capabilities, files, locales, jinja_paths,
81 service_environment):
82 """Does the main work of building the webapp directory and zipfile.
84 Args:
85 buildtype: the type of build ("Official", "Release" or "Dev").
86 destination: A string with path to directory where the webapp will be
87 written.
88 zipfile: A string with path to the zipfile to create containing the
89 contents of |destination|.
90 manifest_template: jinja2 template file for manifest.
91 webapp_type: webapp type ("v1", "v2", "v2_pnacl" or "app_remoting").
92 app_id: A string with the Remoting Application Id (only used for app
93 remoting webapps). If supplied, it defaults to using the
94 test API server.
95 app_name: A string with the name of the application.
96 app_description: A string with the description of the application.
97 app_capabilities: A set of strings naming the capabilities that should be
98 enabled for this application.
99 files: An array of strings listing the paths for resources to include
100 in this webapp.
101 locales: An array of strings listing locales, which are copied, along
102 with their directory structure from the _locales directory down.
103 jinja_paths: An array of paths to search for {%include} directives in
104 addition to the directory containing the manifest template.
105 service_environment: Used to point the webApp to one of the
106 dev/test/staging/prod environments
108 # Ensure a fresh directory.
109 try:
110 shutil.rmtree(destination)
111 except OSError:
112 if os.path.exists(destination):
113 raise
114 else:
115 pass
116 os.mkdir(destination, 0775)
118 if buildtype != 'Official' and buildtype != 'Release' and buildtype != 'Dev':
119 raise Exception('Unknown buildtype: ' + buildtype)
121 jinja_context = {
122 'webapp_type': webapp_type,
123 'buildtype': buildtype,
126 # Copy all the files.
127 for current_file in files:
128 destination_file = os.path.join(destination, os.path.basename(current_file))
130 # Process *.jinja2 files as jinja2 templates
131 if current_file.endswith(".jinja2"):
132 destination_file = destination_file[:-len(".jinja2")]
133 processJinjaTemplate(current_file, jinja_paths,
134 destination_file, jinja_context)
135 else:
136 shutil.copy2(current_file, destination_file)
138 # Copy all the locales, preserving directory structure
139 destination_locales = os.path.join(destination, '_locales')
140 os.mkdir(destination_locales, 0775)
141 remoting_locales = os.path.join(destination, 'remoting_locales')
142 os.mkdir(remoting_locales, 0775)
143 for current_locale in locales:
144 extension = os.path.splitext(current_locale)[1]
145 if extension == '.json':
146 locale_id = os.path.split(os.path.split(current_locale)[0])[1]
147 destination_dir = os.path.join(destination_locales, locale_id)
148 destination_file = os.path.join(destination_dir,
149 os.path.split(current_locale)[1])
150 os.mkdir(destination_dir, 0775)
151 shutil.copy2(current_locale, destination_file)
152 elif extension == '.pak':
153 destination_file = os.path.join(remoting_locales,
154 os.path.split(current_locale)[1])
155 shutil.copy2(current_locale, destination_file)
156 else:
157 raise Exception('Unknown extension: ' + current_locale)
159 # Set client plugin type.
160 # TODO(wez): Use 'native' in app_remoting until b/17441659 is resolved.
161 client_plugin = 'pnacl' if webapp_type == 'v2_pnacl' else 'native'
162 findAndReplace(os.path.join(destination, 'plugin_settings.js'),
163 "'CLIENT_PLUGIN_TYPE'", "'" + client_plugin + "'")
165 # Allow host names for google services/apis to be overriden via env vars.
166 oauth2AccountsHost = os.environ.get(
167 'OAUTH2_ACCOUNTS_HOST', 'https://accounts.google.com')
168 oauth2ApiHost = os.environ.get(
169 'OAUTH2_API_HOST', 'https://www.googleapis.com')
170 directoryApiHost = os.environ.get(
171 'DIRECTORY_API_HOST', 'https://www.googleapis.com')
173 if webapp_type == 'app_remoting':
174 appRemotingApiHost = os.environ.get(
175 'APP_REMOTING_API_HOST', None)
176 appRemotingApplicationId = os.environ.get(
177 'APP_REMOTING_APPLICATION_ID', None)
179 # Release/Official builds are special because they are what we will upload
180 # to the web store. The checks below will validate that prod builds are
181 # being generated correctly (no overrides) and with the correct buildtype.
182 # They also verify that folks are not accidentally building dev/test/staging
183 # apps for release (no impersonation) instead of dev.
184 if service_environment == 'prod' and buildtype == 'Dev':
185 raise Exception("Prod environment cannot be built for 'dev' builds")
187 if buildtype != 'Dev':
188 if service_environment != 'prod':
189 raise Exception('Invalid service_environment targeted for '
190 + buildtype + ': ' + service_environment)
191 if 'out/Release' not in destination:
192 raise Exception('Prod builds must be placed in the out/Release folder')
193 if app_id != None:
194 raise Exception('Cannot pass in an app_id for '
195 + buildtype + ' builds: ' + service_environment)
196 if appRemotingApiHost != None:
197 raise Exception('Cannot set APP_REMOTING_API_HOST env var for '
198 + buildtype + ' builds')
199 if appRemotingApplicationId != None:
200 raise Exception('Cannot set APP_REMOTING_APPLICATION_ID env var for '
201 + buildtype + ' builds')
203 # If an Application ID was set (either from service_environment variable or
204 # from a command line argument), hardcode it, otherwise get it at runtime.
205 effectiveAppId = appRemotingApplicationId or app_id
206 if effectiveAppId:
207 appRemotingApplicationId = "'" + effectiveAppId + "'"
208 else:
209 appRemotingApplicationId = "chrome.i18n.getMessage('@@extension_id')"
210 findAndReplace(os.path.join(destination, 'plugin_settings.js'),
211 "'APP_REMOTING_APPLICATION_ID'", appRemotingApplicationId)
213 oauth2BaseUrl = oauth2AccountsHost + '/o/oauth2'
214 oauth2ApiBaseUrl = oauth2ApiHost + '/oauth2'
215 directoryApiBaseUrl = directoryApiHost + '/chromoting/v1'
217 if webapp_type == 'app_remoting':
218 # Set the apiary endpoint and then set the endpoint version
219 if not appRemotingApiHost:
220 if service_environment == 'prod':
221 appRemotingApiHost = 'https://www.googleapis.com'
222 else:
223 appRemotingApiHost = 'https://www-googleapis-test.sandbox.google.com'
225 if service_environment == 'dev':
226 appRemotingServicePath = '/appremoting/v1beta1_dev'
227 elif service_environment == 'test':
228 appRemotingServicePath = '/appremoting/v1beta1'
229 elif service_environment == 'staging':
230 appRemotingServicePath = '/appremoting/v1beta1_staging'
231 elif service_environment == 'prod':
232 appRemotingServicePath = '/appremoting/v1beta1'
233 else:
234 raise Exception('Unknown service environment: ' + service_environment)
235 appRemotingApiBaseUrl = appRemotingApiHost + appRemotingServicePath
236 else:
237 appRemotingApiBaseUrl = ''
239 replaceString(destination, 'OAUTH2_BASE_URL', oauth2BaseUrl)
240 replaceString(destination, 'OAUTH2_API_BASE_URL', oauth2ApiBaseUrl)
241 replaceString(destination, 'DIRECTORY_API_BASE_URL', directoryApiBaseUrl)
242 if webapp_type == 'app_remoting':
243 replaceString(destination, 'APP_REMOTING_API_BASE_URL',
244 appRemotingApiBaseUrl)
246 # Substitute hosts in the manifest's CSP list.
247 # Ensure we list the API host only once if it's the same for multiple APIs.
248 googleApiHosts = ' '.join(set([oauth2ApiHost, directoryApiHost]))
250 # WCS and the OAuth trampoline are both hosted on talkgadget. Split them into
251 # separate suffix/prefix variables to allow for wildcards in manifest.json.
252 talkGadgetHostSuffix = os.environ.get(
253 'TALK_GADGET_HOST_SUFFIX', 'talkgadget.google.com')
254 talkGadgetHostPrefix = os.environ.get(
255 'TALK_GADGET_HOST_PREFIX', 'https://chromoting-client.')
256 oauth2RedirectHostPrefix = os.environ.get(
257 'OAUTH2_REDIRECT_HOST_PREFIX', 'https://chromoting-oauth.')
259 # Use a wildcard in the manifest.json host specs if the prefixes differ.
260 talkGadgetHostJs = talkGadgetHostPrefix + talkGadgetHostSuffix
261 talkGadgetBaseUrl = talkGadgetHostJs + '/talkgadget/'
262 if talkGadgetHostPrefix == oauth2RedirectHostPrefix:
263 talkGadgetHostJson = talkGadgetHostJs
264 else:
265 talkGadgetHostJson = 'https://*.' + talkGadgetHostSuffix
267 # Set the correct OAuth2 redirect URL.
268 oauth2RedirectHostJs = oauth2RedirectHostPrefix + talkGadgetHostSuffix
269 oauth2RedirectHostJson = talkGadgetHostJson
270 oauth2RedirectPath = '/talkgadget/oauth/chrome-remote-desktop'
271 oauth2RedirectBaseUrlJs = oauth2RedirectHostJs + oauth2RedirectPath
272 oauth2RedirectBaseUrlJson = oauth2RedirectHostJson + oauth2RedirectPath
273 if buildtype == 'Official':
274 oauth2RedirectUrlJs = ("'" + oauth2RedirectBaseUrlJs +
275 "/rel/' + chrome.i18n.getMessage('@@extension_id')")
276 oauth2RedirectUrlJson = oauth2RedirectBaseUrlJson + '/rel/*'
277 else:
278 oauth2RedirectUrlJs = "'" + oauth2RedirectBaseUrlJs + "/dev'"
279 oauth2RedirectUrlJson = oauth2RedirectBaseUrlJson + '/dev*'
280 thirdPartyAuthUrlJs = oauth2RedirectBaseUrlJs + '/thirdpartyauth'
281 thirdPartyAuthUrlJson = oauth2RedirectBaseUrlJson + '/thirdpartyauth*'
282 replaceString(destination, 'TALK_GADGET_URL', talkGadgetBaseUrl)
283 findAndReplace(os.path.join(destination, 'plugin_settings.js'),
284 "'OAUTH2_REDIRECT_URL'", oauth2RedirectUrlJs)
286 # Configure xmpp server and directory bot settings in the plugin.
287 findAndReplace(os.path.join(destination, 'plugin_settings.js'),
288 "Boolean('XMPP_SERVER_USE_TLS')",
289 os.environ.get('XMPP_SERVER_USE_TLS', 'true'))
290 replaceString(destination, 'XMPP_SERVER_FOR_IT2ME_HOST',
291 os.environ.get('XMPP_SERVER_FOR_IT2ME_HOST',
292 'talk.google.com:5222'))
293 replaceString(destination, 'XMPP_SERVER_FOR_CLIENT',
294 os.environ.get('XMPP_SERVER_FOR_CLIENT',
295 'talk.google.com:443'))
296 replaceString(destination, 'DIRECTORY_BOT_JID',
297 os.environ.get('DIRECTORY_BOT_JID',
298 'remoting@bot.talk.google.com'))
299 replaceString(destination, 'THIRD_PARTY_AUTH_REDIRECT_URL',
300 thirdPartyAuthUrlJs)
302 # Set the correct API keys.
303 # For overriding the client ID/secret via env vars, see google_api_keys.py.
304 apiClientId = google_api_keys.GetClientID('REMOTING')
305 apiClientSecret = google_api_keys.GetClientSecret('REMOTING')
306 apiClientIdV2 = google_api_keys.GetClientID('REMOTING_IDENTITY_API')
308 replaceString(destination, 'API_CLIENT_ID', apiClientId)
309 replaceString(destination, 'API_CLIENT_SECRET', apiClientSecret)
311 # Write the application capabilities.
312 appCapabilities = ','.join(
313 ['remoting.ClientSession.Capability.' + x for x in app_capabilities])
314 findAndReplace(os.path.join(destination, 'app_capabilities.js'),
315 "'APPLICATION_CAPABILITIES'", appCapabilities)
317 # Use a consistent extension id for dev builds.
318 # AppRemoting builds always use the dev app id - the correct app id gets
319 # written into the manifest later.
320 if buildtype != 'Official' or webapp_type == 'app_remoting':
321 manifestKey = '"key": "remotingdevbuild",'
322 else:
323 manifestKey = ''
325 # Generate manifest.
326 if manifest_template:
327 context = {
328 'webapp_type': webapp_type,
329 'FULL_APP_VERSION': version,
330 'MANIFEST_KEY_FOR_UNOFFICIAL_BUILD': manifestKey,
331 'OAUTH2_REDIRECT_URL': oauth2RedirectUrlJson,
332 'TALK_GADGET_HOST': talkGadgetHostJson,
333 'THIRD_PARTY_AUTH_REDIRECT_URL': thirdPartyAuthUrlJson,
334 'REMOTING_IDENTITY_API_CLIENT_ID': apiClientIdV2,
335 'OAUTH2_BASE_URL': oauth2BaseUrl,
336 'OAUTH2_API_BASE_URL': oauth2ApiBaseUrl,
337 'DIRECTORY_API_BASE_URL': directoryApiBaseUrl,
338 'APP_REMOTING_API_BASE_URL': appRemotingApiBaseUrl,
339 'OAUTH2_ACCOUNTS_HOST': oauth2AccountsHost,
340 'GOOGLE_API_HOSTS': googleApiHosts,
341 'APP_NAME': app_name,
342 'APP_DESCRIPTION': app_description,
343 'OAUTH_GDRIVE_SCOPE': '',
345 if 'GOOGLE_DRIVE' in app_capabilities:
346 context['OAUTH_GDRIVE_SCOPE'] = ('https://docs.google.com/feeds/ '
347 'https://www.googleapis.com/auth/drive')
348 processJinjaTemplate(manifest_template,
349 jinja_paths,
350 os.path.join(destination, 'manifest.json'),
351 context)
353 # Make the zipfile.
354 createZip(zip_path, destination)
356 return 0
359 def main():
360 if len(sys.argv) < 6:
361 print ('Usage: build-webapp.py '
362 '<build-type> <version> <dst> <zip-path> <manifest_template> '
363 '<webapp_type> <other files...> '
364 '--app_name <name> '
365 '--app_description <description> '
366 '--app_capabilities <capabilities...> '
367 '[--appid <appid>] '
368 '[--locales_listfile <locales-listfile-name>] '
369 '[--jinja_paths <paths...>] '
370 '[--service_environment <service_environment>]')
371 return 1
373 arg_type = ''
374 files = []
375 locales_listfile = ''
376 jinja_paths = []
377 app_id = None
378 app_name = None
379 app_description = None
380 app_capabilities = set([])
381 service_environment = ''
383 for arg in sys.argv[7:]:
384 if arg in ['--locales_listfile',
385 '--jinja_paths',
386 '--appid',
387 '--app_name',
388 '--app_description',
389 '--app_capabilities',
390 '--service_environment']:
391 arg_type = arg
392 elif arg_type == '--locales_listfile':
393 locales_listfile = arg
394 arg_type = ''
395 elif arg_type == '--jinja_paths':
396 jinja_paths.append(arg)
397 elif arg_type == '--appid':
398 app_id = arg
399 arg_type = ''
400 elif arg_type == '--app_name':
401 app_name = arg
402 arg_type = ''
403 elif arg_type == '--app_description':
404 app_description = arg
405 arg_type = ''
406 elif arg_type == '--app_capabilities':
407 app_capabilities.add(arg)
408 elif arg_type == '--service_environment':
409 service_environment = arg
410 arg_type = ''
411 else:
412 files.append(arg)
414 # Load the locales files from the locales_listfile.
415 if not locales_listfile:
416 raise Exception('You must specify a locales_listfile')
417 locales = []
418 with open(locales_listfile) as input:
419 for s in input:
420 locales.append(s.rstrip())
422 return buildWebApp(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4],
423 sys.argv[5], sys.argv[6], app_id, app_name,
424 app_description, app_capabilities, files, locales,
425 jinja_paths, service_environment)
428 if __name__ == '__main__':
429 sys.exit(main())