1#!/usr/bin/env python
2# Copyright (c) 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.
5
6# Disable the lint error for too-long lines for the URL below.
7# pylint: disable=C0301
8
9"""Fix Chrome App manifest.json files for use with multi-platform zip files.
10
11See info about multi-platform zip files here:
12https://developer.chrome.com/native-client/devguide/distributing#packaged-application
13
14The manifest.json file needs to point to the correct platform-specific paths,
15but we build all toolchains and configurations in the same tree. As a result,
16we can't have one manifest.json for all combinations.
17
18Instead, we update the top-level manifest.json file during the build:
19
20  "platforms": [
21    {
22      "nacl_arch": "x86-64",
23      "sub_package_path": "_platform_specific/x86-64/"
24    },
25    ...
26
27Becomes
28
29  "platforms": [
30    {
31      "nacl_arch": "x86-64",
32      "sub_package_path": "<toolchain>/<config>/_platform_specific/x86-64/"
33    },
34    ...
35"""
36
37import collections
38import json
39import optparse
40import os
41import sys
42
43if sys.version_info < (2, 6, 0):
44  sys.stderr.write("python 2.6 or later is required run this script\n")
45  sys.exit(1)
46
47
48class Error(Exception):
49  """Local Error class for this file."""
50  pass
51
52
53def Trace(msg):
54  if Trace.verbose:
55    sys.stderr.write(str(msg) + '\n')
56
57Trace.verbose = False
58
59
60def main(argv):
61  parser = optparse.OptionParser(
62      usage='Usage: %prog [options] manifest.json', description=__doc__)
63  parser.add_option('-p', '--prefix',
64                    help='Prefix to set for all sub_package_paths in the '
65                    'manifest. If none is specified, the prefix will be '
66                    'removed; i.e. the start of the path will be '
67                    '"_platform_specific/..."')
68  parser.add_option('-v', '--verbose',
69                    help='Verbose output', action='store_true')
70
71  options, args = parser.parse_args(argv)
72  if options.verbose:
73    Trace.verbose = True
74
75  if not args:
76    parser.error('Expected manifest file.')
77
78  manifest = args[0]
79
80  Trace('Reading %s' % manifest)
81  with open(manifest) as f:
82    # Keep the dictionary order. This is only supported on Python 2.7+
83    if sys.version_info >= (2, 7, 0):
84      data = json.load(f, object_pairs_hook=collections.OrderedDict)
85    else:
86      data = json.load(f)
87
88  if 'platforms' not in data:
89    raise Error('%s does not have "platforms" key.' % manifest)
90
91  platforms = data['platforms']
92  if type(platforms) is not list:
93    raise Error('Expected "platforms" key to be array.')
94
95  if options.prefix:
96    prefix = options.prefix + '/'
97  else:
98    prefix = ''
99
100  for platform in platforms:
101    nacl_arch = platform.get('nacl_arch')
102
103    if 'sub_package_path' not in platform:
104      raise Error('Expected each platform to have "sub_package_path" key.')
105
106    sub_package_path = platform['sub_package_path']
107    index = sub_package_path.find('_platform_specific')
108    if index == -1:
109      raise Error('Could not find "_platform_specific" in the '
110                  '"sub_package_path" key.')
111
112    new_path = prefix + sub_package_path[index:]
113    platform['sub_package_path'] = new_path
114
115    Trace('  %s: "%s" -> "%s"' % (nacl_arch, sub_package_path, new_path))
116
117  with open(manifest, 'w') as f:
118    json.dump(data, f, indent=2)
119
120  return 0
121
122
123if __name__ == '__main__':
124  try:
125    rtn = main(sys.argv[1:])
126  except Error, e:
127    sys.stderr.write('%s: %s\n' % (os.path.basename(__file__), e))
128    rtn = 1
129  except KeyboardInterrupt:
130    sys.stderr.write('%s: interrupted\n' % os.path.basename(__file__))
131    rtn = 1
132  sys.exit(rtn)
133