Commit 4df88a25 authored by sbc@chromium.org's avatar sbc@chromium.org

update_nacl_manifest improvements.

Add ability to leave size and checksum blank in the
manifest and have it be filled in based by downloading
the current files.

Add -v/--verbose option which prints more information.

Add -d/--debug option which will show backtraces for
Errors() which is not normally useful.

When in --dryrun mode write the generated file locally
along with the current online one and show a diff of 
the two. 

BUG=155906

Review URL: https://chromiumcodereview.appspot.com/11200002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@162445 0039d316-1c4b-4281-b951-d872f2087c98
parent dc514114
...@@ -61,12 +61,8 @@ ...@@ -61,12 +61,8 @@
"repath": "vs_addin", "repath": "vs_addin",
"archives": [ "archives": [
{ {
"url": "https://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/sdk/1449/vs_addin.tgz", "url": "https://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/sdk/${revision}/vs_addin.tgz",
"checksum": { "host_os": "win"
"sha1": "91acaf2535ad8b464a799af5251197bab02869ee"
},
"host_os": "win",
"size": 86759
} }
], ],
"revision": 1449 "revision": 1449
......
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
import copy import copy
import hashlib import hashlib
import json import json
import string
import sys import sys
import urllib2
MANIFEST_VERSION = 2 MANIFEST_VERSION = 2
...@@ -57,8 +59,8 @@ def DictToJSON(pydict): ...@@ -57,8 +59,8 @@ def DictToJSON(pydict):
def DownloadAndComputeHash(from_stream, to_stream=None, progress_func=None): def DownloadAndComputeHash(from_stream, to_stream=None, progress_func=None):
''' Download the archive data from from-stream and generate sha1 and '''Download the archive data from from-stream and generate sha1 and
size info. size info.
Args: Args:
from_stream: An input stream that supports read. from_stream: An input stream that supports read.
...@@ -146,6 +148,20 @@ class Archive(dict): ...@@ -146,6 +148,20 @@ class Archive(dict):
if key not in VALID_ARCHIVE_KEYS: if key not in VALID_ARCHIVE_KEYS:
raise Error('Archive "%s" has invalid attribute "%s"' % (host_os, key)) raise Error('Archive "%s" has invalid attribute "%s"' % (host_os, key))
def UpdateVitals(self, revision):
"""Update the size and checksum information for this archive
based on the content currently at the URL.
This allows the template mandifest to be maintained without
the need to size and checksums to be present.
"""
template = string.Template(self['url'])
self['url'] = template.substitute({'revision': revision})
from_stream = urllib2.urlopen(self['url'])
sha1_hash, size = DownloadAndComputeHash(from_stream)
self['size'] = size
self['checksum'] = { 'sha1': sha1_hash }
def __getattr__(self, name): def __getattr__(self, name):
"""Retrieve values from this dict using attributes. """Retrieve values from this dict using attributes.
...@@ -215,6 +231,9 @@ class Bundle(dict): ...@@ -215,6 +231,9 @@ class Bundle(dict):
else: else:
self[k] = v self[k] = v
def __str__(self):
return self.GetDataAsString()
def GetDataAsString(self): def GetDataAsString(self):
"""Returns the JSON bundle object, pretty-printed""" """Returns the JSON bundle object, pretty-printed"""
return DictToJSON(self) return DictToJSON(self)
...@@ -245,21 +264,21 @@ class Bundle(dict): ...@@ -245,21 +264,21 @@ class Bundle(dict):
else: else:
self[key] = value self[key] = value
def Validate(self): def Validate(self, add_missing_info=False):
"""Validate the content of the bundle. Raise an Error if an invalid or """Validate the content of the bundle. Raise an Error if an invalid or
missing field is found. """ missing field is found. """
# Check required fields. # Check required fields.
if not self.get(NAME_KEY, None): if not self.get(NAME_KEY):
raise Error('Bundle has no name') raise Error('Bundle has no name')
if self.get(REVISION_KEY, None) == None: if self.get(REVISION_KEY) == None:
raise Error('Bundle "%s" is missing a revision number' % self[NAME_KEY]) raise Error('Bundle "%s" is missing a revision number' % self[NAME_KEY])
if self.get(VERSION_KEY, None) == None: if self.get(VERSION_KEY) == None:
raise Error('Bundle "%s" is missing a version number' % self[NAME_KEY]) raise Error('Bundle "%s" is missing a version number' % self[NAME_KEY])
if not self.get('description', None): if not self.get('description'):
raise Error('Bundle "%s" is missing a description' % self[NAME_KEY]) raise Error('Bundle "%s" is missing a description' % self[NAME_KEY])
if not self.get('stability', None): if not self.get('stability'):
raise Error('Bundle "%s" is missing stability info' % self[NAME_KEY]) raise Error('Bundle "%s" is missing stability info' % self[NAME_KEY])
if self.get('recommended', None) == None: if self.get('recommended') == None:
raise Error('Bundle "%s" is missing the recommended field' % raise Error('Bundle "%s" is missing the recommended field' %
self[NAME_KEY]) self[NAME_KEY])
# Check specific values # Check specific values
...@@ -277,6 +296,8 @@ class Bundle(dict): ...@@ -277,6 +296,8 @@ class Bundle(dict):
(self[NAME_KEY], key)) (self[NAME_KEY], key))
# Validate the archives # Validate the archives
for archive in self[ARCHIVES_KEY]: for archive in self[ARCHIVES_KEY]:
if add_missing_info and 'size' not in archive:
archive.UpdateVitals(self[REVISION_KEY])
archive.Validate() archive.Validate()
def GetArchive(self, host_os_name): def GetArchive(self, host_os_name):
...@@ -387,7 +408,7 @@ class SDKManifest(object): ...@@ -387,7 +408,7 @@ class SDKManifest(object):
"bundles": [], "bundles": [],
} }
def Validate(self): def Validate(self, add_missing_info=False):
"""Validate the Manifest file and raises an exception for problems""" """Validate the Manifest file and raises an exception for problems"""
# Validate the manifest top level # Validate the manifest top level
if self._manifest_data["manifest_version"] > MANIFEST_VERSION: if self._manifest_data["manifest_version"] > MANIFEST_VERSION:
...@@ -399,7 +420,7 @@ class SDKManifest(object): ...@@ -399,7 +420,7 @@ class SDKManifest(object):
raise Error('Manifest has invalid attribute "%s"' % key) raise Error('Manifest has invalid attribute "%s"' % key)
# Validate each bundle # Validate each bundle
for bundle in self._manifest_data[BUNDLES_KEY]: for bundle in self._manifest_data[BUNDLES_KEY]:
bundle.Validate() bundle.Validate(add_missing_info)
def GetBundle(self, name): def GetBundle(self, name):
"""Get a bundle from the array of bundles. """Get a bundle from the array of bundles.
...@@ -457,7 +478,7 @@ class SDKManifest(object): ...@@ -457,7 +478,7 @@ class SDKManifest(object):
(local_bundle[VERSION_KEY], local_bundle[REVISION_KEY]) < (local_bundle[VERSION_KEY], local_bundle[REVISION_KEY]) <
(bundle[VERSION_KEY], bundle[REVISION_KEY])) (bundle[VERSION_KEY], bundle[REVISION_KEY]))
def MergeBundle(self, bundle, allow_existing = True): def MergeBundle(self, bundle, allow_existing=True):
"""Merge a Bundle into this manifest. """Merge a Bundle into this manifest.
The new bundle is added if not present, or merged into the existing bundle. The new bundle is added if not present, or merged into the existing bundle.
...@@ -483,7 +504,7 @@ class SDKManifest(object): ...@@ -483,7 +504,7 @@ class SDKManifest(object):
manifest: The manifest to merge. manifest: The manifest to merge.
''' '''
for bundle in manifest.GetBundles(): for bundle in manifest.GetBundles():
self.MergeBundle(bundle, allow_existing = False) self.MergeBundle(bundle, allow_existing=False)
def FilterBundles(self, predicate): def FilterBundles(self, predicate):
"""Filter the list of bundles by |predicate|. """Filter the list of bundles by |predicate|.
...@@ -497,7 +518,7 @@ class SDKManifest(object): ...@@ -497,7 +518,7 @@ class SDKManifest(object):
""" """
self._manifest_data[BUNDLES_KEY] = filter(predicate, self.GetBundles()) self._manifest_data[BUNDLES_KEY] = filter(predicate, self.GetBundles())
def LoadDataFromString(self, json_string): def LoadDataFromString(self, json_string, add_missing_info=False):
"""Load a JSON manifest string. Raises an exception if json_string """Load a JSON manifest string. Raises an exception if json_string
is not well-formed JSON. is not well-formed JSON.
...@@ -517,7 +538,10 @@ class SDKManifest(object): ...@@ -517,7 +538,10 @@ class SDKManifest(object):
self._manifest_data[key] = bundles self._manifest_data[key] = bundles
else: else:
self._manifest_data[key] = value self._manifest_data[key] = value
self.Validate() self.Validate(add_missing_info)
def __str__(self):
return self.GetDataAsString()
def GetDataAsString(self): def GetDataAsString(self):
"""Returns the current JSON manifest object, pretty-printed""" """Returns the current JSON manifest object, pretty-printed"""
......
...@@ -13,7 +13,7 @@ import build_utils ...@@ -13,7 +13,7 @@ import build_utils
GS_MANIFEST_PATH = 'gs://nativeclient-mirror/nacl/nacl_sdk/' GS_MANIFEST_PATH = 'gs://nativeclient-mirror/nacl/nacl_sdk/'
SDK_MANIFEST = 'naclsdk_manifest2.json' SDK_MANIFEST = 'naclsdk_manifest2.json'
MONO_MANIFEST = 'naclmono_manifest.json' MONO_MANIFEST = 'naclmono_manifest.json'
def build_and_upload_mono(sdk_revision, pepper_revision, sdk_url, def build_and_upload_mono(sdk_revision, pepper_revision, sdk_url,
upload_path, args): upload_path, args):
...@@ -69,7 +69,7 @@ def get_sdk_build_info(): ...@@ -69,7 +69,7 @@ def get_sdk_build_info():
manifest_file = open(MONO_MANIFEST, 'r') manifest_file = open(MONO_MANIFEST, 'r')
mono_manifest = json.loads(manifest_file.read()) mono_manifest = json.loads(manifest_file.read())
manifest_file.close() manifest_file.close()
ret = [] ret = []
mono_manifest_dirty = False mono_manifest_dirty = False
# Check to see if we need to rebuild mono based on sdk revision # Check to see if we need to rebuild mono based on sdk revision
...@@ -145,14 +145,14 @@ def update_mono_sdk_json(infos): ...@@ -145,14 +145,14 @@ def update_mono_sdk_json(infos):
value[loc] = bundle value[loc] = bundle
else: else:
value.append(bundle) value.append(bundle)
# Write out the file locally, then upload to its known location. # Write out the file locally, then upload to its known location.
manifest_file = open(MONO_MANIFEST, 'w') manifest_file = open(MONO_MANIFEST, 'w')
manifest_file.write(json.dumps(mono_manifest, sort_keys=False, indent=2)) manifest_file.write(json.dumps(mono_manifest, sort_keys=False, indent=2))
manifest_file.close() manifest_file.close()
buildbot_common.Run([buildbot_common.GetGsutil(), 'cp', '-a', 'public-read', buildbot_common.Run([buildbot_common.GetGsutil(), 'cp', '-a', 'public-read',
MONO_MANIFEST, GS_MANIFEST_PATH + MONO_MANIFEST]) MONO_MANIFEST, GS_MANIFEST_PATH + MONO_MANIFEST])
def main(args): def main(args):
args = args[1:] args = args[1:]
...@@ -176,7 +176,7 @@ def main(args): ...@@ -176,7 +176,7 @@ def main(args):
build_and_upload_mono(None, info['pepper_revision'], info['sdk_url'], build_and_upload_mono(None, info['pepper_revision'], info['sdk_url'],
upload_path, args) upload_path, args)
update_mono_sdk_json(infos) update_mono_sdk_json(infos)
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -5,10 +5,12 @@ ...@@ -5,10 +5,12 @@
import copy import copy
import datetime import datetime
import hashlib
import os import os
import posixpath import posixpath
import subprocess import subprocess
import sys import sys
import tempfile
import unittest import unittest
import urlparse import urlparse
...@@ -158,6 +160,7 @@ class TestDelegate(update_nacl_manifest.Delegate): ...@@ -158,6 +160,7 @@ class TestDelegate(update_nacl_manifest.Delegate):
self.history = history self.history = history
self.files = files self.files = files
self.version_mapping = version_mapping self.version_mapping = version_mapping
self.dryrun = 0
def GetRepoManifest(self): def GetRepoManifest(self):
return self.manifest return self.manifest
...@@ -513,11 +516,35 @@ mac,canary,21.0.1156.0,2012-05-30 12:14:21.305090""" ...@@ -513,11 +516,35 @@ mac,canary,21.0.1156.0,2012-05-30 12:14:21.305090"""
self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1) self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
def main(): class TestUpdateVitals(unittest.TestCase):
suite = unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__]) def setUp(self):
result = unittest.TextTestRunner(verbosity=2).run(suite) f = tempfile.NamedTemporaryFile('w', prefix="test_update_nacl_manifest")
self.test_file = f.name
f.close()
test_data = "Some test data\n"
self.sha1 = hashlib.sha1(test_data).hexdigest()
with open(self.test_file, 'w') as f:
f.write(test_data)
def tearDown(self):
os.remove(self.test_file)
def testUpdateVitals(self):
archive = manifest_util.Archive(manifest_util.GetHostOS())
archive.url = 'file://%s' % os.path.abspath(self.test_file)
bundle = MakeBundle(18)
bundle.AddArchive(archive)
manifest = MakeManifest(bundle)
archive = manifest.GetBundles()[0]['archives'][0]
self.assertTrue('size' not in archive)
self.assertTrue('checksum' not in archive)
manifest.Validate()
self.assertEqual(archive['size'], 15)
self.assertEqual(archive['checksum']['sha1'], self.sha1)
return int(not result.wasSuccessful())
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main()) unittest.main()
...@@ -167,9 +167,10 @@ class Delegate(object): ...@@ -167,9 +167,10 @@ class Delegate(object):
class RealDelegate(Delegate): class RealDelegate(Delegate):
def __init__(self, dryrun=False, gsutil=None): def __init__(self, dryrun=False, gsutil=None, verbose=False):
super(RealDelegate, self).__init__() super(RealDelegate, self).__init__()
self.dryrun = dryrun self.dryrun = dryrun
self.verbose = verbose
if gsutil: if gsutil:
self.gsutil = gsutil self.gsutil = gsutil
else: else:
...@@ -181,7 +182,7 @@ class RealDelegate(Delegate): ...@@ -181,7 +182,7 @@ class RealDelegate(Delegate):
sdk_json_string = sdk_stream.read() sdk_json_string = sdk_stream.read()
manifest = manifest_util.SDKManifest() manifest = manifest_util.SDKManifest()
manifest.LoadDataFromString(sdk_json_string) manifest.LoadDataFromString(sdk_json_string, add_missing_info=True)
return manifest return manifest
def GetHistory(self): def GetHistory(self):
...@@ -212,6 +213,7 @@ class RealDelegate(Delegate): ...@@ -212,6 +213,7 @@ class RealDelegate(Delegate):
def GsUtil_cp(self, src, dest, stdin=None): def GsUtil_cp(self, src, dest, stdin=None):
"""See Delegate.GsUtil_cp""" """See Delegate.GsUtil_cp"""
if self.dryrun: if self.dryrun:
self.Trace("Skipping upload: %s -> %s" % (src, dest))
return return
# -p ensures we keep permissions when copying "in-the-cloud". # -p ensures we keep permissions when copying "in-the-cloud".
...@@ -220,6 +222,10 @@ class RealDelegate(Delegate): ...@@ -220,6 +222,10 @@ class RealDelegate(Delegate):
def Print(self, *args): def Print(self, *args):
sys.stdout.write(' '.join(map(str, args)) + '\n') sys.stdout.write(' '.join(map(str, args)) + '\n')
def Trace(self, *args):
if self.verbose:
self.Print(*args)
def _RunGsUtil(self, stdin, *args): def _RunGsUtil(self, stdin, *args):
"""Run gsutil as a subprocess. """Run gsutil as a subprocess.
...@@ -230,16 +236,20 @@ class RealDelegate(Delegate): ...@@ -230,16 +236,20 @@ class RealDelegate(Delegate):
Returns: Returns:
The stdout from the process.""" The stdout from the process."""
cmd = [self.gsutil] + list(args) cmd = [self.gsutil] + list(args)
self.Trace("Running: %s" % str(cmd))
if stdin: if stdin:
stdin_pipe = subprocess.PIPE stdin_pipe = subprocess.PIPE
else: else:
stdin_pipe = None stdin_pipe = None
process = subprocess.Popen(cmd, stdin=stdin_pipe, stdout=subprocess.PIPE, try:
stderr=subprocess.PIPE) process = subprocess.Popen(cmd, stdin=stdin_pipe, stdout=subprocess.PIPE,
stdout, stderr = process.communicate(stdin) stderr=subprocess.PIPE)
stdout, stderr = process.communicate(stdin)
except OSError as e:
raise manifest_util.Error("Unable to run '%s': %s" % (cmd[0], str(e)))
if process.returncode != 0: if process.returncode:
sys.stderr.write(stderr) sys.stderr.write(stderr)
raise subprocess.CalledProcessError(process.returncode, ' '.join(cmd)) raise subprocess.CalledProcessError(process.returncode, ' '.join(cmd))
return stdout return stdout
...@@ -518,6 +528,19 @@ class Updater(object): ...@@ -518,6 +528,19 @@ class Updater(object):
Args: Args:
manifest: The new manifest to upload. manifest: The new manifest to upload.
""" """
if self.delegate.dryrun:
name = MANIFEST_BASENAME + ".new"
self.delegate.Print("Writing new manifest: %s" % name)
with open(name, 'w') as f:
f.write(manifest.GetDataAsString())
stdout = self.delegate.GsUtil_cat(GS_SDK_MANIFEST)
online = MANIFEST_BASENAME + ".online"
self.delegate.Print("Writing online manifest: %s" % online)
with open(online, 'w') as f:
f.write(stdout)
os.system('diff -u %s %s' % (online, name))
timestamp_manifest_path = GS_MANIFEST_BACKUP_DIR + \ timestamp_manifest_path = GS_MANIFEST_BACKUP_DIR + \
GetTimestampManifestName() GetTimestampManifestName()
self.delegate.GsUtil_cp('-', timestamp_manifest_path, self.delegate.GsUtil_cp('-', timestamp_manifest_path,
...@@ -597,14 +620,15 @@ class CapturedFile(object): ...@@ -597,14 +620,15 @@ class CapturedFile(object):
def main(args): def main(args):
parser = optparse.OptionParser() parser = optparse.OptionParser()
parser.add_option('--gsutil', help='path to gsutil', dest='gsutil', parser.add_option('--gsutil', help='path to gsutil.')
default=None) parser.add_option('-d', '--debug', help='run in debug mode.',
parser.add_option('--mailfrom', help='email address of sender', action='store_true')
dest='mailfrom', default=None) parser.add_option('--mailfrom', help='email address of sender.')
parser.add_option('--mailto', help='send error mails to...', dest='mailto', parser.add_option('--mailto', help='send error mails to...', action='append')
default=[], action='append') parser.add_option('-n', '--dryrun', help="don't upload the manifest.",
parser.add_option('--dryrun', help='don\'t upload the manifest.', action='store_true')
dest='dryrun', action='store_true', default=False) parser.add_option('-v', '--verbose', help='print more diagnotic messages.',
action='store_true')
options, args = parser.parse_args(args[1:]) options, args = parser.parse_args(args[1:])
if (options.mailfrom is None) != (not options.mailto): if (options.mailfrom is None) != (not options.mailto):
...@@ -618,18 +642,25 @@ def main(args): ...@@ -618,18 +642,25 @@ def main(args):
sys.stderr = CapturedFile(sys.stderr) sys.stderr = CapturedFile(sys.stderr)
try: try:
delegate = RealDelegate(dryrun=options.dryrun, gsutil=options.gsutil) try:
Run(delegate, ('mac', 'win', 'linux')) delegate = RealDelegate(options.dryrun, options.gsutil, options.verbose)
except Exception: Run(delegate, ('mac', 'win', 'linux'))
if options.mailfrom and options.mailto: except Exception:
traceback.print_exc() if options.mailfrom and options.mailto:
scriptname = os.path.basename(sys.argv[0]) traceback.print_exc()
subject = '[%s] Failed to update manifest' % (scriptname,) scriptname = os.path.basename(sys.argv[0])
text = '%s failed.\n\nSTDERR:\n%s\n' % (scriptname, sys.stderr.getvalue()) subject = '[%s] Failed to update manifest' % (scriptname,)
SendMail(options.mailfrom, options.mailto, subject, text) text = '%s failed.\n\nSTDERR:\n%s\n' % (scriptname,
sys.exit(1) sys.stderr.getvalue())
else: SendMail(options.mailfrom, options.mailto, subject, text)
sys.exit(1)
else:
raise
except manifest_util.Error as e:
if options.debug:
raise raise
print e
sys.exit(1)
if __name__ == '__main__': if __name__ == '__main__':
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment