Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / remoting / host / installer / build-installer-archive.py
blob4585d0a231ab8e0c11e2a7de0e8744f5d90bab19
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 zip archive for the Chrome Remote Desktop Host installer.
8 This script builds a zip file that contains all the files needed to build an
9 installer for Chrome Remote Desktop Host.
11 This zip archive is then used by the signing bots to:
12 (1) Sign the binaries
13 (2) Build the final installer
15 TODO(garykac) We should consider merging this with build-webapp.py.
16 """
18 import os
19 import shutil
20 import subprocess
21 import sys
22 import zipfile
25 def cleanDir(dir):
26 """Deletes and recreates the dir to make sure it is clean.
28 Args:
29 dir: The directory to clean.
30 """
31 try:
32 shutil.rmtree(dir)
33 except OSError:
34 if os.path.exists(dir):
35 raise
36 else:
37 pass
38 os.makedirs(dir, 0775)
41 def buildDefDictionary(definitions):
42 """Builds the definition dictionary from the VARIABLE=value array.
44 Args:
45 defs: Array of variable definitions: 'VARIABLE=value'.
47 Returns:
48 Dictionary with the definitions.
49 """
50 defs = {}
51 for d in definitions:
52 (key, val) = d.split('=')
53 defs[key] = val
54 return defs
57 def createZip(zip_path, directory):
58 """Creates a zipfile at zip_path for the given directory.
60 Args:
61 zip_path: Path to zip file to create.
62 directory: Directory with contents to archive.
63 """
64 zipfile_base = os.path.splitext(os.path.basename(zip_path))[0]
65 zip = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
66 for (root, dirs, files) in os.walk(directory):
67 for f in files:
68 full_path = os.path.join(root, f)
69 rel_path = os.path.relpath(full_path, directory)
70 zip.write(full_path, os.path.join(zipfile_base, rel_path))
71 zip.close()
74 def remapSrcFile(dst_root, src_roots, src_file):
75 """Calculates destination file path and creates directory.
77 Any matching |src_roots| prefix is stripped from |src_file| before
78 appending to |dst_root|.
80 For example, given:
81 dst_root = '/output'
82 src_roots = ['host/installer/mac']
83 src_file = 'host/installer/mac/Scripts/keystone_install.sh'
84 The final calculated path is:
85 '/output/Scripts/keystone_install.sh'
87 The |src_file| must match one of the |src_roots| prefixes. If there are no
88 matches, then an error is reported.
90 If multiple |src_roots| match, then only the first match is applied. Because
91 of this, if you have roots that share a common prefix, the longest string
92 should be first in this array.
94 Args:
95 dst_root: Target directory where files are copied.
96 src_roots: Array of path prefixes which will be stripped of |src_file|
97 (if they match) before appending it to the |dst_root|.
98 src_file: Source file to be copied.
99 Returns:
100 Full path to destination file in |dst_root|.
102 # Strip of directory prefix.
103 found_root = False
104 for root in src_roots:
105 root = os.path.normpath(root)
106 src_file = os.path.normpath(src_file)
107 if os.path.commonprefix([root, src_file]) == root:
108 src_file = os.path.relpath(src_file, root)
109 found_root = True
110 break
112 if not found_root:
113 error('Unable to match prefix for %s' % src_file)
115 dst_file = os.path.join(dst_root, src_file)
116 # Make sure target directory exists.
117 dst_dir = os.path.dirname(dst_file)
118 if not os.path.exists(dst_dir):
119 os.makedirs(dst_dir, 0775)
120 return dst_file
123 def copyFileWithDefs(src_file, dst_file, defs):
124 """Copies from src_file to dst_file, performing variable substitution.
126 Any @@VARIABLE@@ in the source is replaced with the value of VARIABLE
127 in the |defs| dictionary when written to the destination file.
129 Args:
130 src_file: Full or relative path to source file to copy.
131 dst_file: Relative path (and filename) where src_file should be copied.
132 defs: Dictionary of variable definitions.
134 data = open(src_file, 'r').read()
135 for key, val in defs.iteritems():
136 try:
137 data = data.replace('@@' + key + '@@', val)
138 except TypeError:
139 print repr(key), repr(val)
140 open(dst_file, 'w').write(data)
141 shutil.copystat(src_file, dst_file)
144 def copyZipIntoArchive(out_dir, files_root, zip_file):
145 """Expands the zip_file into the out_dir, preserving the directory structure.
147 Args:
148 out_dir: Target directory where unzipped files are copied.
149 files_root: Path prefix which is stripped of zip_file before appending
150 it to the out_dir.
151 zip_file: Relative path (and filename) to the zip file.
153 base_zip_name = os.path.basename(zip_file)
155 # We don't use the 'zipfile' module here because it doesn't restore all the
156 # file permissions correctly. We use the 'unzip' command manually.
157 old_dir = os.getcwd();
158 os.chdir(os.path.dirname(zip_file))
159 subprocess.call(['unzip', '-qq', '-o', base_zip_name])
160 os.chdir(old_dir)
162 # Unzip into correct dir in out_dir.
163 out_zip_path = remapSrcFile(out_dir, files_root, zip_file)
164 out_zip_dir = os.path.dirname(out_zip_path)
166 (src_dir, ignore1) = os.path.splitext(zip_file)
167 (base_dir_name, ignore2) = os.path.splitext(base_zip_name)
168 shutil.copytree(src_dir, os.path.join(out_zip_dir, base_dir_name))
171 def buildHostArchive(temp_dir, zip_path, source_file_roots, source_files,
172 gen_files, gen_files_dst, defs):
173 """Builds a zip archive with the files needed to build the installer.
175 Args:
176 temp_dir: Temporary dir used to build up the contents for the archive.
177 zip_path: Full path to the zip file to create.
178 source_file_roots: Array of path prefixes to strip off |files| when adding
179 to the archive.
180 source_files: The array of files to add to archive. The path structure is
181 preserved (except for the |files_root| prefix).
182 gen_files: Full path to binaries to add to archive.
183 gen_files_dst: Relative path of where to add binary files in archive.
184 This array needs to parallel |binaries_src|.
185 defs: Dictionary of variable definitions.
187 cleanDir(temp_dir)
189 for f in source_files:
190 dst_file = remapSrcFile(temp_dir, source_file_roots, f)
191 base_file = os.path.basename(f)
192 (base, ext) = os.path.splitext(f)
193 if ext == '.zip':
194 copyZipIntoArchive(temp_dir, source_file_roots, f)
195 elif ext in ['.packproj', '.pkgproj', '.plist', '.props', '.sh', '.json']:
196 copyFileWithDefs(f, dst_file, defs)
197 else:
198 shutil.copy2(f, dst_file)
200 for bs, bd in zip(gen_files, gen_files_dst):
201 dst_file = os.path.join(temp_dir, bd)
202 if not os.path.exists(os.path.dirname(dst_file)):
203 os.makedirs(os.path.dirname(dst_file))
204 if os.path.isdir(bs):
205 shutil.copytree(bs, dst_file)
206 else:
207 shutil.copy2(bs, dst_file)
209 createZip(zip_path, temp_dir)
212 def error(msg):
213 sys.stderr.write('ERROR: %s\n' % msg)
214 sys.exit(1)
217 def usage():
218 """Display basic usage information."""
219 print ('Usage: %s\n'
220 ' <temp-dir> <zip-path>\n'
221 ' --source-file-roots <list of roots to strip off source files...>\n'
222 ' --source-files <list of source files...>\n'
223 ' --generated-files <list of generated target files...>\n'
224 ' --generated-files-dst <dst for each generated file...>\n'
225 ' --defs <list of VARIABLE=value definitions...>'
226 ) % sys.argv[0]
229 def main():
230 if len(sys.argv) < 2:
231 usage()
232 error('Too few arguments')
234 temp_dir = sys.argv[1]
235 zip_path = sys.argv[2]
237 arg_mode = ''
238 source_file_roots = []
239 source_files = []
240 generated_files = []
241 generated_files_dst = []
242 definitions = []
243 for arg in sys.argv[3:]:
244 if arg == '--source-file-roots':
245 arg_mode = 'src-roots'
246 elif arg == '--source-files':
247 arg_mode = 'files'
248 elif arg == '--generated-files':
249 arg_mode = 'gen-src'
250 elif arg == '--generated-files-dst':
251 arg_mode = 'gen-dst'
252 elif arg == '--defs':
253 arg_mode = 'defs'
255 elif arg_mode == 'src-roots':
256 source_file_roots.append(arg)
257 elif arg_mode == 'files':
258 source_files.append(arg)
259 elif arg_mode == 'gen-src':
260 generated_files.append(arg)
261 elif arg_mode == 'gen-dst':
262 generated_files_dst.append(arg)
263 elif arg_mode == 'defs':
264 definitions.append(arg)
265 else:
266 usage()
267 error('Expected --source-files')
269 # Make sure at least one file was specified.
270 if len(source_files) == 0 and len(generated_files) == 0:
271 error('At least one input file must be specified.')
273 # Sort roots to ensure the longest one is first. See comment in remapSrcFile
274 # for why this is necessary.
275 source_file_roots = map(os.path.normpath, source_file_roots)
276 source_file_roots.sort(key=len, reverse=True)
278 # Verify that the 2 generated_files arrays have the same number of elements.
279 if len(generated_files) != len(generated_files_dst):
280 error('len(--generated-files) != len(--generated-files-dst)')
282 defs = buildDefDictionary(definitions)
284 result = buildHostArchive(temp_dir, zip_path, source_file_roots,
285 source_files, generated_files, generated_files_dst,
286 defs)
288 return 0
291 if __name__ == '__main__':
292 sys.exit(main())