OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Creates a zip archive for the Chrome Remote Desktop Host installer. | 6 """Creates a zip archive for the Chrome Remote Desktop Host installer. |
7 | 7 |
8 This script builds a zip file that contains all the files needed to build an | 8 This script builds a zip file that contains all the files needed to build an |
9 installer for Chrome Remote Desktop Host. | 9 installer for Chrome Remote Desktop Host. |
10 | 10 |
11 This zip archive is then used by the signing bots to: | 11 This zip archive is then used by the signing bots to: |
12 (1) Sign the binaries | 12 (1) Sign the binaries |
13 (2) Build the final installer | 13 (2) Build the final installer |
14 | 14 |
15 TODO(garykac) We should consider merging this with build-webapp.py. | 15 TODO(garykac) We should consider merging this with build-webapp.py. |
16 """ | 16 """ |
17 | 17 |
18 import os | 18 import os |
19 import shutil | 19 import shutil |
20 import subprocess | |
20 import sys | 21 import sys |
21 import zipfile | 22 import zipfile |
22 | 23 |
23 | 24 |
24 def cleanDir(dir): | 25 def cleanDir(dir): |
25 """Deletes and recreates the dir to make sure it is clean. | 26 """Deletes and recreates the dir to make sure it is clean. |
26 | 27 |
27 Args: | 28 Args: |
28 dir: The directory to clean. | 29 dir: The directory to clean. |
29 """ | 30 """ |
30 try: | 31 try: |
31 shutil.rmtree(dir) | 32 shutil.rmtree(dir) |
32 except OSError: | 33 except OSError: |
33 if os.path.exists(dir): | 34 if os.path.exists(dir): |
34 raise | 35 raise |
35 else: | 36 else: |
36 pass | 37 pass |
37 os.makedirs(dir, 0775) | 38 os.makedirs(dir, 0775) |
38 | 39 |
39 | 40 |
41 def buildDefDictionary(definitions): | |
42 """Builds the definition dictionary from the VARIABLE=value array. | |
43 | |
44 Args: | |
45 defs: Array of variable definitions: 'VARIABLE=value'. | |
46 | |
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 | |
55 | |
56 | |
40 def createZip(zip_path, directory): | 57 def createZip(zip_path, directory): |
41 """Creates a zipfile at zip_path for the given directory. | 58 """Creates a zipfile at zip_path for the given directory. |
42 | 59 |
43 Args: | 60 Args: |
44 zip_path: Path to zip file to create. | 61 zip_path: Path to zip file to create. |
45 directory: Directory with contents to archive. | 62 directory: Directory with contents to archive. |
46 """ | 63 """ |
47 zipfile_base = os.path.splitext(os.path.basename(zip_path))[0] | 64 zipfile_base = os.path.splitext(os.path.basename(zip_path))[0] |
48 zip = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) | 65 zip = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) |
49 for (root, dirs, files) in os.walk(directory): | 66 for (root, dirs, files) in os.walk(directory): |
50 for f in files: | 67 for f in files: |
51 full_path = os.path.join(root, f) | 68 full_path = os.path.join(root, f) |
52 rel_path = os.path.relpath(full_path, directory) | 69 rel_path = os.path.relpath(full_path, directory) |
53 zip.write(full_path, os.path.join(zipfile_base, rel_path)) | 70 zip.write(full_path, os.path.join(zipfile_base, rel_path)) |
54 zip.close() | 71 zip.close() |
55 | 72 |
56 | 73 |
57 def copyFileIntoArchive(src_file, out_dir, files_root, dst_file): | 74 def copyFileIntoArchive(src_file, out_dir, files_root, dst_file, defs): |
58 """Copies the src_file into the out_dir, preserving the directory structure. | 75 """Copies the src_file into the out_dir, preserving the directory structure. |
59 | 76 |
60 Args: | 77 Args: |
61 src_file: Full or relative path to source file to copy. | 78 src_file: Full or relative path to source file to copy. |
62 out_dir: Target directory where files are copied. | 79 out_dir: Target directory where files are copied. |
63 files_root: Path prefix which is stripped of dst_file before appending | 80 files_root: Path prefix which is stripped of dst_file before appending |
64 it to the out_dir. | 81 it to the out_dir. |
65 dst_file: Relative path (and filename) where src_file should be copied. | 82 dst_file: Relative path (and filename) where src_file should be copied. |
83 defs: Dictionary of variable definitions. | |
66 """ | 84 """ |
67 root_len = len(files_root) | 85 root_len = len(files_root) |
68 local_file_path = dst_file[root_len:] | 86 local_file_path = dst_file[root_len:] |
69 full_dst_file = os.path.join(out_dir, local_file_path) | 87 full_dst_file = os.path.join(out_dir, local_file_path) |
70 dst_dir = os.path.dirname(full_dst_file) | 88 dst_dir = os.path.dirname(full_dst_file) |
71 if not os.path.exists(dst_dir): | 89 if not os.path.exists(dst_dir): |
72 os.makedirs(dst_dir, 0775) | 90 os.makedirs(dst_dir, 0775) |
73 shutil.copy2(src_file, full_dst_file) | 91 |
92 (base, ext) = os.path.splitext(src_file) | |
93 if ext == '.app': | |
94 shutil.copytree(src_file, full_dst_file) | |
95 else: | |
96 copyFileWithDefs(src_file, full_dst_file, defs) | |
alexeypa (please no reviews)
2012/04/24 16:06:19
This means that copyFileWithDefs could be used for
garykac
2012/04/24 19:48:21
Done.
| |
97 | |
98 | |
99 def copyFileWithDefs(src_file, dst_file, defs): | |
100 """Copies from src_file to dst_file, performing variable substitution. | |
101 | |
102 Any @@variables@@ in the source are replaced with | |
103 file with the out_dir, preserving the directory structure. | |
104 | |
105 Args: | |
106 src_file: Full or relative path to source file to copy. | |
107 out_dir: Target directory where files are copied. | |
alexeypa (please no reviews)
2012/04/24 16:06:19
nit: This function does not have |out_dir| and |fi
garykac
2012/04/24 19:48:21
Done.
| |
108 files_root: Path prefix which is stripped of dst_file before appending | |
109 it to the out_dir. | |
110 dst_file: Relative path (and filename) where src_file should be copied. | |
111 defs: Dictionary of variable definitions. | |
112 """ | |
113 data = open(src_file, 'r').read() | |
114 for key, val in defs.iteritems(): | |
115 try: | |
116 data = data.replace('@@' + key + '@@', val) | |
117 except TypeError: | |
118 print repr(key), repr(val) | |
119 open(dst_file, 'w').write(data) | |
120 shutil.copystat(src_file, dst_file) | |
121 #shutil.copy2(src_file, dst_file) | |
alexeypa (please no reviews)
2012/04/24 16:06:19
nit: Remove the commented out code.
garykac
2012/04/24 19:48:21
Done.
| |
74 | 122 |
75 | 123 |
76 def copyZipIntoArchive(out_dir, files_root, zip_file): | 124 def copyZipIntoArchive(out_dir, files_root, zip_file): |
77 """Expands the zip_file into the out_dir, preserving the directory structure. | 125 """Expands the zip_file into the out_dir, preserving the directory structure. |
78 | 126 |
79 Args: | 127 Args: |
80 out_dir: Target directory where unzipped files are copied. | 128 out_dir: Target directory where unzipped files are copied. |
81 files_root: Path prefix which is stripped of zip_file before appending | 129 files_root: Path prefix which is stripped of zip_file before appending |
82 it to the out_dir. | 130 it to the out_dir. |
83 zip_file: Relative path (and filename) to the zip file. | 131 zip_file: Relative path (and filename) to the zip file. |
84 """ | 132 """ |
133 base_zip_name = os.path.basename(zip_file) | |
134 | |
135 # We don't use the 'zipfile' module here because it doesn't restore all the | |
136 # file permissions correctly. We use the 'unzip' command manually. | |
alexeypa (please no reviews)
2012/04/24 16:06:19
This does not seem to be cross-platform. I believe
garykac
2012/04/24 19:48:21
Per our discussion, I'll followup this cl with one
| |
137 old_dir = os.getcwd(); | |
138 os.chdir(os.path.dirname(zip_file)) | |
139 subprocess.call(['unzip', '-qq', '-o', base_zip_name]) | |
140 os.chdir(old_dir) | |
141 | |
85 # Unzip into correct dir in out_dir. | 142 # Unzip into correct dir in out_dir. |
86 zip_archive = zipfile.ZipFile(zip_file, 'r') | |
87 root_len = len(files_root) | 143 root_len = len(files_root) |
88 relative_zip_path = zip_file[root_len:] | 144 relative_zip_path = zip_file[root_len:] |
89 out_zip_path = os.path.join(out_dir, relative_zip_path) | 145 out_zip_path = os.path.join(out_dir, relative_zip_path) |
90 out_zip_dir = os.path.dirname(out_zip_path) | 146 out_zip_dir = os.path.dirname(out_zip_path) |
91 if not os.path.exists(out_zip_dir): | |
92 os.makedirs(out_zip_dir, 0775) | |
93 | 147 |
94 # Ideally, we'd simply do: | 148 (src_dir, ignore1) = os.path.splitext(zip_file) |
95 # zip_archive.extractall(out_zip_dir) | 149 (base_dir_name, ignore2) = os.path.splitext(base_zip_name) |
96 # but http://bugs.python.org/issue4710 bites us because the some 'bots (like | 150 shutil.copytree(src_dir, os.path.join(out_zip_dir, base_dir_name)) |
97 # mac_rel) are still running Python 2.6. | |
98 # See http://stackoverflow.com/questions/639962/unzipping-directory-structure- with-python | |
99 # for workarounds. | |
100 # TODO(garykac): Remove this once all bots are > 2.6. | |
101 for f in zip_archive.namelist(): | |
102 if f.endswith('/'): | |
103 if not os.path.exists(f): | |
104 os.makedirs(os.path.join(out_zip_dir, f)) | |
105 else: | |
106 zip_archive.extract(f, out_zip_dir) | |
107 | 151 |
108 | 152 |
109 def buildHostArchive(temp_dir, zip_path, source_files_root, source_files, | 153 def buildHostArchive(temp_dir, zip_path, source_files_root, source_files, |
110 gen_files, gen_files_dst): | 154 gen_files, gen_files_dst, defs): |
111 """Builds a zip archive with the files needed to build the installer. | 155 """Builds a zip archive with the files needed to build the installer. |
112 | 156 |
113 Args: | 157 Args: |
114 temp_dir: Temporary dir used to build up the contents for the archive. | 158 temp_dir: Temporary dir used to build up the contents for the archive. |
115 zip_path: Full path to the zip file to create. | 159 zip_path: Full path to the zip file to create. |
116 source_files_root: Path prefix to strip off |files| when adding to archive. | 160 source_files_root: Path prefix to strip off |files| when adding to archive. |
117 source_files: The array of files to add to archive. The path structure is | 161 source_files: The array of files to add to archive. The path structure is |
118 preserved (except for the |files_root| prefix). | 162 preserved (except for the |files_root| prefix). |
119 gen_files: Full path to binaries to add to archive. | 163 gen_files: Full path to binaries to add to archive. |
120 gen_files_dst: Relative path of where to add binary files in archive. | 164 gen_files_dst: Relative path of where to add binary files in archive. |
121 This array needs to parallel |binaries_src|. | 165 This array needs to parallel |binaries_src|. |
166 defs: Dictionary of variable definitions. | |
122 """ | 167 """ |
123 cleanDir(temp_dir) | 168 cleanDir(temp_dir) |
124 | 169 |
125 for file in source_files: | 170 for file in source_files: |
126 base_file = os.path.basename(file) | 171 base_file = os.path.basename(file) |
127 (base, ext) = os.path.splitext(file) | 172 (base, ext) = os.path.splitext(file) |
128 if ext == '.zip': | 173 if ext == '.zip': |
alexeypa (please no reviews)
2012/04/24 16:06:19
nit: The logic here can be:
if .zip: copyZipIntoA
garykac
2012/04/24 19:48:21
I like that, but I'll do that in a follow-up cl si
| |
129 copyZipIntoArchive(temp_dir, source_files_root, file) | 174 copyZipIntoArchive(temp_dir, source_files_root, file) |
130 else: | 175 else: |
131 copyFileIntoArchive(file, temp_dir, source_files_root, file) | 176 copyFileIntoArchive(file, temp_dir, source_files_root, file, defs) |
132 | 177 |
133 for bs, bd in zip(gen_files, gen_files_dst): | 178 for bs, bd in zip(gen_files, gen_files_dst): |
134 copyFileIntoArchive(bs, temp_dir, '', bd) | 179 copyFileIntoArchive(bs, temp_dir, '', bd, {}) |
135 | 180 |
136 createZip(zip_path, temp_dir) | 181 createZip(zip_path, temp_dir) |
137 | 182 |
138 | 183 |
184 def error(msg): | |
185 sys.stderr.write('ERROR: %s' % msg) | |
186 sys.exit(1) | |
187 | |
139 def usage(): | 188 def usage(): |
140 """Display basic usage information.""" | 189 """Display basic usage information.""" |
141 print ('Usage: %s\n' | 190 print ('Usage: %s\n' |
142 ' <temp-dir> <zip-path> <files-root-dir>\n' | 191 ' <temp-dir> <zip-path> <files-root-dir>\n' |
143 ' --source-files <list of source files...>\n' | 192 ' --source-files <list of source files...>\n' |
144 ' --generated-files <list of generated target files...>\n' | 193 ' --generated-files <list of generated target files...>\n' |
145 ' --generated-files-dst <dst for each generated file...>' | 194 ' --generated-files-dst <dst for each generated file...>\n' |
195 ' --defs <list of VARIABLE=value definitions...>' | |
146 ) % sys.argv[0] | 196 ) % sys.argv[0] |
147 | 197 |
148 | 198 |
149 def main(): | 199 def main(): |
150 if len(sys.argv) < 3: | 200 if len(sys.argv) < 3: |
151 usage() | 201 usage() |
152 return 1 | 202 error('Too few arguments') |
153 | 203 |
154 temp_dir = sys.argv[1] | 204 temp_dir = sys.argv[1] |
155 zip_path = sys.argv[2] | 205 zip_path = sys.argv[2] |
156 source_files_root = sys.argv[3] | 206 source_files_root = sys.argv[3] |
157 | 207 |
158 arg_mode = '' | 208 arg_mode = '' |
159 source_files = [] | 209 source_files = [] |
160 generated_files = [] | 210 generated_files = [] |
161 generated_files_dst = [] | 211 generated_files_dst = [] |
212 definitions = [] | |
162 for arg in sys.argv[4:]: | 213 for arg in sys.argv[4:]: |
163 if arg == '--source-files': | 214 if arg == '--source-files': |
164 arg_mode = 'files' | 215 arg_mode = 'files' |
165 elif arg == '--generated-files': | 216 elif arg == '--generated-files': |
166 arg_mode = 'gen-src' | 217 arg_mode = 'gen-src' |
167 elif arg == '--generated-files-dst': | 218 elif arg == '--generated-files-dst': |
168 arg_mode = 'gen-dst' | 219 arg_mode = 'gen-dst' |
220 elif arg == '--defs': | |
221 arg_mode = 'defs' | |
169 | 222 |
170 elif arg_mode == 'files': | 223 elif arg_mode == 'files': |
171 source_files.append(arg) | 224 source_files.append(arg) |
172 elif arg_mode == 'gen-src': | 225 elif arg_mode == 'gen-src': |
173 generated_files.append(arg) | 226 generated_files.append(arg) |
174 elif arg_mode == 'gen-dst': | 227 elif arg_mode == 'gen-dst': |
175 generated_files_dst.append(arg) | 228 generated_files_dst.append(arg) |
229 elif arg_mode == 'defs': | |
230 definitions.append(arg) | |
176 else: | 231 else: |
177 print "ERROR: Expected --source-files" | |
178 usage() | 232 usage() |
179 return 1 | 233 error('Expected --source-files') |
180 | 234 |
181 # Make sure at least one file was specified. | 235 # Make sure at least one file was specified. |
182 if len(source_files) == 0 and len(generated_files) == 0: | 236 if len(source_files) == 0 and len(generated_files) == 0: |
183 print "ERROR: At least one input file must be specified." | 237 error('At least one input file must be specified.') |
184 return 1 | |
185 | 238 |
186 # Ensure that source_files_root ends with a directory separator. | 239 # Ensure that source_files_root ends with a directory separator. |
187 if source_files_root[-1:] != os.sep: | 240 if source_files_root[-1:] != os.sep: |
188 source_files_root += os.sep | 241 source_files_root += os.sep |
189 | 242 |
190 # Verify that the 2 generated_files arrays have the same number of elements. | 243 # Verify that the 2 generated_files arrays have the same number of elements. |
191 if len(generated_files) < len(generated_files_dst): | 244 if len(generated_files) != len(generated_files_dst): |
192 print "ERROR: len(--generated-files) != len(--generated-files-dst)" | 245 error('len(--generated-files) != len(--generated-files-dst)') |
193 return 1 | 246 |
194 while len(generated_files) > len(generated_files_dst): | 247 defs = buildDefDictionary(definitions) |
195 generated_files_dst.append('') | |
196 | 248 |
197 result = buildHostArchive(temp_dir, zip_path, source_files_root, | 249 result = buildHostArchive(temp_dir, zip_path, source_files_root, |
198 source_files, generated_files, generated_files_dst) | 250 source_files, generated_files, generated_files_dst, |
251 defs) | |
199 | 252 |
200 return 0 | 253 return 0 |
201 | 254 |
202 if __name__ == '__main__': | 255 if __name__ == '__main__': |
203 sys.exit(main()) | 256 sys.exit(main()) |
OLD | NEW |