2 # Copyright 2014 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.
15 SUPPORTED_ARCHES
= ['i386', 'x86_64', 'armv7', 'arm64']
18 class SubprocessError(Exception):
22 class ConfigurationError(Exception):
26 def out_directories(root
):
27 """Returns all output directories containing crnet objects under root.
29 Currently this list is just hardcoded.
32 root: prefix for output directories.
34 out_dirs
= ['Release-iphoneos', 'Release-iphonesimulator']
35 return map(lambda x
: os
.path
.join(root
, 'out', x
), out_dirs
)
38 def check_command(command
):
39 """Runs a command, raising an exception if it fails.
41 If the command returns a nonzero exit code, prints any data the command
42 emitted on stdout and stderr.
45 command: command to execute, in argv format.
48 SubprocessError: the specified command returned nonzero exit status.
50 p
= subprocess
.Popen(command
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
51 (stdout
, stderr
) = p
.communicate()
54 message
= 'Command failed: {0} (status {1})'.format(command
, p
.returncode
)
56 print 'stdout: {0}'.format(stdout
)
57 print 'stderr: {0}'.format(stderr
)
58 raise SubprocessError(message
)
61 def file_contains_string(path
, string
):
62 """Returns whether the file named by path contains string.
65 path: path of the file to search.
66 string: string to search the file for.
69 True if file contains string, False otherwise.
71 with
open(path
, 'r') as f
:
78 def is_object_filename(filename
):
79 """Returns whether the given filename names an object file.
82 filename: filename to inspect.
85 True if filename names an object file, false otherwise.
87 (_
, ext
) = os
.path
.splitext(filename
)
88 return ext
in ('.a', '.o')
92 """Represents a single step of the crnet build process.
94 This parent class exists only to define the interface Steps present and keep
95 track of elapsed time for each step. Subclasses of Step should override the
96 run() method, which is called internally by start().
99 name: human-readable name of this step, used in debug output.
100 started_at: seconds since epoch that this step started running at.
102 def __init__(self
, name
):
104 self
._started
_at
= None
105 self
._ended
_at
= None
112 """Start running this step.
114 This method keeps track of how long the run() method takes to run and emits
115 the elapsed time after run() returns.
117 self
._started
_at
= time
.time()
118 print '{0}: '.format(self
._name
),
121 self
._ended
_at
= time
.time()
122 print '{0:.2f}s'.format(self
._ended
_at
- self
._started
_at
)
125 """Actually run this step.
127 Subclasses should override this method to implement their own step logic.
129 raise NotImplementedError
132 class CleanStep(Step
):
133 """Clean the build output directories.
135 This step deletes intermediates generated by the build process. Some of these
136 intermediates (crnet_consumer.app and crnet_resources.bundle) are directories,
137 which contain files ninja doesn't know and hence won't remove, so the run()
138 method here explicitly deletes those directories before running 'ninja -t
142 dirs: list of output directories to clean.
144 def __init__(self
, root
):
145 super(CleanStep
, self
).__init
__('clean')
146 self
._dirs
= out_directories(root
)
149 """Runs the clean step.
151 Deletes crnet_consumer.app and crnet_resources.bundle in each output
152 directory and runs 'ninja -t clean' in each output directory.
155 if os
.path
.exists(os
.path
.join(d
, 'crnet_consumer.app')):
156 shutil
.rmtree(os
.path
.join(d
, 'crnet_consumer.app'))
157 if os
.path
.exists(os
.path
.join(d
, 'crnet_resources.bundle')):
158 shutil
.rmtree(os
.path
.join(d
, 'crnet_resources.bundle'))
159 check_command(['ninja', '-C', d
, '-t', 'clean'])
162 class HooksStep(Step
):
163 """Validates the gyp config and reruns gclient hooks.
166 root: directory to find gyp config under.
168 def __init__(self
, root
):
169 super(HooksStep
, self
).__init
__('hooks')
173 """Runs the hooks step.
175 Checks that root/build/common.gypi contains target_subarch = both in a crude
176 way, then calls 'gclient runhooks'. TODO(ellyjones): parse common.gypi in a
180 ConfigurationError: if target_subarch != both
182 common_gypi
= os
.path
.join(self
._root
, 'build', 'common.gypi')
183 if not file_contains_string(common_gypi
, "'target_subarch%': 'both'"):
184 raise ConfigurationError('target_subarch must be both in {0}'.format(
186 check_command(['gclient', 'runhooks'])
189 class BuildStep(Step
):
190 """Builds all the intermediate crnet binaries.
192 All the hard work of this step is done by ninja; this step just shells out to
193 ninja to build the crnet_pack target.
196 dirs: output directories to run ninja in.
198 def __init__(self
, root
):
199 super(BuildStep
, self
).__init
__('build')
200 self
._dirs
= out_directories(root
)
203 """Runs the build step.
205 For each output directory, run ninja to build the crnet_pack target in that
209 check_command(['ninja', '-C', d
, 'crnet_pack'])
212 class PackageStep(Step
):
213 """Packages the built object files for release.
215 The release format is a tarball, containing one gzipped tarball per
216 architecture and a manifest file, which lists metadata about the build.
219 outdirs: directories containing built object files.
220 workdir: temporary working directory. Deleted at end of the step.
221 archdir: temporary directory under workdir. Used for collecting per-arch
223 proddir: temporary directory under workdir. Used for intermediate per-arch
226 def __init__(self
, root
, outfile
):
227 super(PackageStep
, self
).__init
__('package')
228 self
._outdirs
= out_directories(root
)
229 self
._outfile
= outfile
232 """Runs the package step.
234 Packages each architecture from |root| into an individual .tar.gz file, then
235 packages all the .tar.gz files into one .tar file, which is written to
238 (workdir
, archdir
, proddir
) = self
.create_work_dirs()
239 for arch
in SUPPORTED_ARCHES
:
240 self
.package_arch(archdir
, proddir
, arch
)
241 self
.package(proddir
)
242 shutil
.rmtree(workdir
)
244 def create_work_dirs(self
):
245 """Creates working directories and returns their paths."""
246 workdir
= tempfile
.mkdtemp()
247 archdir
= os
.path
.join(workdir
, 'arch')
248 proddir
= os
.path
.join(workdir
, 'prod')
251 return (workdir
, archdir
, proddir
)
253 def object_files_for_arch(self
, arch
):
254 """Returns a list of object files for the given architecture.
256 Under each outdir d, per-arch files are stored in d/arch, and object files
257 for a given arch contain the arch's name as a substring.
260 arch: architecture name. Must be in SUPPORTED_ARCHES.
263 List of full pathnames to object files in outdirs for the named arch.
266 for d
in self
._outdirs
:
267 files
= os
.listdir(os
.path
.join(d
, 'arch'))
268 for f
in filter(is_object_filename
, files
):
270 arch_files
.append(os
.path
.join(d
, 'arch', f
))
273 def package_arch(self
, archdir
, proddir
, arch
):
274 """Packages an individual architecture.
276 Copies all the object files for the specified arch into a working directory
277 under self.archdir, then tars them up into a gzipped tarball under
281 archdir: directory to stage architecture files in.
282 proddir: directory to stage result tarballs in.
283 arch: architecture name to package. Must be in SUPPORTED_ARCHES.
285 arch_files
= self
.object_files_for_arch(arch
)
286 os
.mkdir(os
.path
.join(archdir
, arch
))
288 shutil
.copy(f
, os
.path
.join(archdir
, arch
))
289 out_filename
= os
.path
.join(proddir
, '{0}.tar.gz'.format(arch
))
290 check_command(['tar', '-C', archdir
, '-czf', out_filename
, arch
])
292 def package(self
, proddir
):
293 """Final packaging step. Packages all the arch tarballs into one tarball."""
295 for a
in SUPPORTED_ARCHES
:
296 arch_tarballs
.append('{0}.tar.gz'.format(a
))
297 check_command(['tar', '-C', proddir
, '-cf', self
._outfile
] +
303 'clean': lambda: CleanStep(args
.rootdir
),
304 'hooks': lambda: HooksStep(args
.rootdir
),
305 'build': lambda: BuildStep(args
.rootdir
),
306 'package': lambda: PackageStep(args
.rootdir
, args
.outfile
)
308 parser
= argparse
.ArgumentParser(description
='Build and package crnet.')
309 parser
.add_argument('--outfile', dest
='outfile', default
='crnet.tar',
310 help='Output file to generate (default: crnet.tar)')
311 parser
.add_argument('--rootdir', dest
='rootdir', default
='../..',
312 help='Root directory to build from (default: ../..)')
313 parser
.add_argument('steps', metavar
='step', nargs
='*')
314 args
= parser
.parse_args()
315 step_names
= args
.steps
or ['clean', 'hooks', 'build', 'package']
316 steps
= [step_classes
[x
]() for x
in step_names
]
321 if __name__
== '__main__':