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.
14 # Python 2.5 compatibility
15 from __future__
import with_statement
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__':
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
:
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
):
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
))
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])
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.
83 buildtype: the type of build ("Official" or "Dev").
84 destination: A string with path to directory where the webapp will be
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
92 locales: An array of strings listing locales, which are copied, along
93 with their directory structure from the _locales directory down.
95 # Ensure a fresh directory.
97 shutil
.rmtree(destination
)
99 if os
.path
.exists(destination
):
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)
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
)
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
)
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
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/*'
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",
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",'
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'),
259 createZip(zip_path
, destination
)
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...>]')
275 for arg
in sys
.argv
[7:]:
276 if arg
in ['--locales']:
278 elif arg_type
== '--locales':
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__':