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
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__':
31 os
.path
.abspath(os
.path
.join(sys
.path
[0], '../../google_apis')))
32 import google_api_keys
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
:
42 output
.write(s
.replace(findString
, replaceString
))
43 os
.remove(oldFilepath
)
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
):
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
))
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'),
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.
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
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
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.
88 # Ensure a fresh directory.
90 shutil
.rmtree(destination
)
92 if os
.path
.exists(destination
):
96 os
.mkdir(destination
, 0775)
98 # Use symlinks on linux and mac for faster compile/edit cycle.
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']
104 # TODO(ajwong): Pending decision on http://crbug.com/27185 we may not be
105 # able to load symlinked resources.
106 should_symlink
= False
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)
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
)
122 shutil
.copy2(current_file
, destination_file
)
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
)
143 raise Exception("Unknown extension: " + current_locale
);
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.
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
154 pluginName
= os
.path
.basename(plugin
)
157 if name
!= pluginName
:
158 path
= os
.path
.join(destination
, name
)
160 f
.write("placeholder for %s" % (name
))
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.
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
)
171 shutil
.copy2(plugin
, newPluginPath
)
173 # Strip the linux build.
174 if ((platform
.system() == 'Linux') and (buildtype
== 'Official')):
175 subprocess
.call(["strip", newPluginPath
])
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.'
186 # Set the version number in the manifest version.
187 findAndReplace(os
.path
.join(destination
, 'manifest.json'),
191 # Set the correct mimetype.
192 findAndReplace(os
.path
.join(destination
, 'plugin_settings.js'),
193 'HOST_PLUGIN_MIMETYPE',
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
)
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.')
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
232 talkGadgetHostJson
= 'https://*.' + talkGadgetHostSuffix
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/*'
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
)
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')
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'",
274 findAndReplace(os
.path
.join(destination
, 'manifest.json'),
275 "THIRD_PARTY_AUTH_REDIRECT_URL",
276 thirdPartyAuthUrlJson
)
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')
284 findAndReplace(os
.path
.join(destination
, 'plugin_settings.js'),
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
+ '"')
294 # Use a consistent extension id for unofficial builds.
295 if buildtype
!= 'Official':
296 manifestKey
= '"key": "remotingdevbuild",'
299 findAndReplace(os
.path
.join(destination
, 'manifest.json'),
300 'MANIFEST_KEY_FOR_UNOFFICIAL_BUILD', manifestKey
)
303 createZip(zip_path
, destination
)
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...>]')
320 for arg
in sys
.argv
[7:]:
321 if arg
== '--locales' or arg
== '--patches':
323 elif arg_type
== '--locales':
325 elif arg_type
== '--patches':
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
)
334 if __name__
== '__main__':