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:
13 (2) Build the final installer
15 TODO(garykac) We should consider merging this with build-webapp.py.
26 """Deletes and recreates the dir to make sure it is clean.
29 dir: The directory to clean.
34 if os
.path
.exists(dir):
38 os
.makedirs(dir, 0775)
41 def buildDefDictionary(definitions
):
42 """Builds the definition dictionary from the VARIABLE=value array.
45 defs: Array of variable definitions: 'VARIABLE=value'.
48 Dictionary with the definitions.
52 (key
, val
) = d
.split('=')
57 def createZip(zip_path
, directory
):
58 """Creates a zipfile at zip_path for the given directory.
61 zip_path: Path to zip file to create.
62 directory: Directory with contents to archive.
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
):
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
))
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|.
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.
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.
100 Full path to destination file in |dst_root|.
102 # Strip of directory prefix.
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
)
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)
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.
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():
137 data
= data
.replace('@@' + key
+ '@@', val
)
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.
148 out_dir: Target directory where unzipped files are copied.
149 files_root: Path prefix which is stripped of zip_file before appending
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
])
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.
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
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.
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
)
194 copyZipIntoArchive(temp_dir
, source_file_roots
, f
)
195 elif ext
in ['.packproj', '.pkgproj', '.plist', '.props', '.sh', '.json']:
196 copyFileWithDefs(f
, dst_file
, defs
)
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
)
207 shutil
.copy2(bs
, dst_file
)
209 createZip(zip_path
, temp_dir
)
213 sys
.stderr
.write('ERROR: %s\n' % msg
)
218 """Display basic usage information."""
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...>'
230 if len(sys
.argv
) < 2:
232 error('Too few arguments')
234 temp_dir
= sys
.argv
[1]
235 zip_path
= sys
.argv
[2]
238 source_file_roots
= []
241 generated_files_dst
= []
243 for arg
in sys
.argv
[3:]:
244 if arg
== '--source-file-roots':
245 arg_mode
= 'src-roots'
246 elif arg
== '--source-files':
248 elif arg
== '--generated-files':
250 elif arg
== '--generated-files-dst':
252 elif arg
== '--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
)
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
,
291 if __name__
== '__main__':