Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / native_client / build / package_version / revision_info.py
1 #!/usr/bin/python
2 # Copyright (c) 2014 The Native Client 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 """Encompasses a revision file of a package.
7
8 A revision file associates an SVN revision with a package. The revision
9 file will encompass the package information for all package targets. All
10 packages must have already been built by the buildbot before a revision file
11 can be made for it.
12 """
13
14 import hashlib
15 import json
16
17 import archive_info
18 import error
19 import package_info
20 import packages_info
21
22
23 FIELD_PACKAGE_NAME = 'package_name'
24 FIELD_REVISION = 'revision'
25 FIELD_PACKAGE_TARGETS = 'package_targets'
26
27 FIELD_REVISION_HASH = 'revision_hash'
28
29
30 class RevisionInfo(object):
31   """Revision information object describing a set revision for a package."""
32   def __init__(self, packages_desc, revision_file=None):
33     """Constructor for a RevisionInfo object.
34
35     Args:
36       packages_desc: Packages description containing all the necessary packages
37                      and package targets to verify a revision file is complete.
38       revision_file: Optional JSON file representing a RevisionInfo object.
39     """
40     assert isinstance(packages_desc, packages_info.PackagesInfo)
41     self._packages_desc = packages_desc
42     self._revision_num = -1
43     self._package_name = None
44
45     # A revision describes all the package_info's for each package target.
46     # Every package target in the package_desc must have its revision set
47     # for the revision info to be valid.
48     self._package_targets = {}
49
50     if revision_file is not None:
51       self.LoadRevisionFile(revision_file)
52
53   def __eq__(self, other):
54     return (type(self) == type(other) and
55             self._revision_num == other._revision_num and
56             self._package_name == other._package_name and
57             self._package_targets == other._package_targets)
58
59   def _GetRevisionHash(self):
60     """Returns a stable hash for a revision file for validation purposes."""
61     hash_string = str(self._revision_num)
62     hash_string += str(self._package_name)
63     for package_target in sorted(self._package_targets):
64       package_desc = self._package_targets[package_target]
65       archive_list = package_desc.GetArchiveList()
66
67       hash_string += str(package_target)
68       for archive in archive_list:
69         for field, member in archive.GetArchiveData()._asdict().iteritems():
70           hash_string += '[%s:%s]' % (field, member)
71
72     return hashlib.sha1(hash_string).hexdigest()
73
74   def _ValidateRevisionComplete(self):
75     """Validate packages to make sure it matches the packages description."""
76     if self._package_name is None:
77       raise error.Error('Invalid revision information - '
78                                   'no package name.')
79     elif self._revision_num == -1:
80       raise error.Error('Invalid revision information - '
81                                   'no revision number')
82
83     package_targets = self._packages_desc.GetPackageTargetsForPackage(
84         self._package_name
85     )
86
87     if package_targets:
88       package_targets = set(package_targets)
89       revision_targets = set(self._package_targets.keys())
90
91       if package_targets != revision_targets:
92         raise error.Error('Invalid revision information - '
93                            'target mismatch:'
94                            + '\n%s:' % self._package_name
95                            + '\n  Required Target Packages:'
96                            + '\n\t' + '\n\t'.join(sorted(package_targets))
97                            + '\n  Supplied Target Packages:'
98                            + '\n\t' + '\n\t'.join(sorted(revision_targets)))
99
100   def LoadRevisionFile(self, revision_file, skip_hash_verify=False):
101     """Loads a revision JSON file into this object.
102
103     Args:
104       revision_file: File name for a revision JSON file.
105       skip_hash_verify: If True, will skip the hash validation check. This
106                         should only be used if a field has been added or
107                         removed in order to recalculate the revision hash.
108     """
109     try:
110       with open(revision_file, 'rt') as f:
111         revision_json = json.load(f)
112
113       self._package_name = revision_json[FIELD_PACKAGE_NAME]
114       self._revision_num = revision_json[FIELD_REVISION]
115       self._package_targets = {}
116
117       package_targets = revision_json[FIELD_PACKAGE_TARGETS]
118       for package_target, archive_list in package_targets.iteritems():
119         self._package_targets[package_target] = package_info.PackageInfo(
120             archive_list
121         )
122     except (TypeError, KeyError) as e:
123       raise error.Error('Invalid revision file [%s]: %s' %
124                                   (revision_file, e))
125
126     self._ValidateRevisionComplete()
127
128     if not skip_hash_verify:
129       hash_value = revision_json[FIELD_REVISION_HASH]
130       if self._GetRevisionHash() != hash_value:
131         raise error.Error('Invalid revision file [%s] - revision hash check '
132             'failed' % revision_file)
133
134   def SaveRevisionFile(self, revision_file):
135     """Saves this object to a revision JSON file to be loaded later.
136
137     Args:
138       revision_file: File name where revision JSON file will be saved.
139     """
140     self._ValidateRevisionComplete()
141
142     package_targets = {}
143     for package_target, package_desc in self._package_targets.iteritems():
144       package_targets[package_target] = package_desc.DumpPackageJson()
145
146     revision_json = {
147         FIELD_PACKAGE_NAME: self._package_name,
148         FIELD_REVISION: self._revision_num,
149         FIELD_PACKAGE_TARGETS: package_targets,
150         FIELD_REVISION_HASH: self._GetRevisionHash()
151     }
152
153     with open(revision_file, 'wt') as f:
154       json.dump(revision_json, f, sort_keys=True,
155                 indent=2, separators=(',', ': '))
156
157   def SetRevisionNumber(self, revision_num):
158     """Sets the current revision number for this object."""
159     self._revision_num = revision_num
160
161   def GetRevisionNumber(self):
162     """Gets the currently set revision number for this object."""
163     return self._revision_num
164
165   def ClearRevisions(self):
166     """Clears all package information for this object"""
167     self._package_name = None
168     self._package_targets = {}
169
170   def SetTargetRevision(self, package_name, package_target, package_desc):
171     """Sets a package description for a package target.
172
173     The package description is a package_info object representing the package
174     for this particular revision.
175
176     Args:
177       package_name: Name of the package this revision object represents.
178       package_target: Package target name for the package we are setting.
179       package_desc: package_info object representing the package target.
180     """
181     if self._package_name is None:
182       self._package_name = package_name
183     elif self._package_name != package_name:
184       raise error.Error('Revision information must be all for the '
185                         'same package\n'
186                         'Original package name: %s\nNew package name: %s'
187                         % (self._package_name, package_name))
188     self._package_targets[package_target] = package_desc
189
190   def GetPackageInfo(self, package_target):
191     """Gets the package description for a particular package target.
192
193     The package description is a package_info object representing the package
194     for this particular revision.
195
196     Args:
197       package_target: Package target name for which we want the package info.
198
199     Returns:
200       A package_info object for the package target, or None for invalid targets.
201     """
202     return self._package_targets.get(package_target, None)