summaryrefslogtreecommitdiffstats
path: root/native_client_sdk
diff options
context:
space:
mode:
authorbinji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-10 23:13:18 +0000
committerbinji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-10 23:13:18 +0000
commitc7422b9efa963d88b645527f23073cbc62c679fd (patch)
tree9091f87696e433d0640423b25908fc970d0a992c /native_client_sdk
parent5caa3dafde2294e2e5791485a2986aec1f932ff8 (diff)
downloadchromium_src-c7422b9efa963d88b645527f23073cbc62c679fd.zip
chromium_src-c7422b9efa963d88b645527f23073cbc62c679fd.tar.gz
chromium_src-c7422b9efa963d88b645527f23073cbc62c679fd.tar.bz2
[NaCl SDK] Add tests for update_nacl_manifest.py
BUG=none TEST=none Review URL: https://chromiumcodereview.appspot.com/10332023 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@136435 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'native_client_sdk')
-rw-r--r--native_client_sdk/src/build_tools/manifest_util.py160
-rwxr-xr-xnative_client_sdk/src/build_tools/tests/test_update_manifest.py366
-rwxr-xr-xnative_client_sdk/src/build_tools/update_nacl_manifest.py554
3 files changed, 811 insertions, 269 deletions
diff --git a/native_client_sdk/src/build_tools/manifest_util.py b/native_client_sdk/src/build_tools/manifest_util.py
index 890d8f9..9ce6495 100644
--- a/native_client_sdk/src/build_tools/manifest_util.py
+++ b/native_client_sdk/src/build_tools/manifest_util.py
@@ -2,6 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import copy
import hashlib
import json
import sys
@@ -144,25 +145,36 @@ class Archive(dict):
if key not in VALID_ARCHIVE_KEYS:
raise Error('Archive "%s" has invalid attribute "%s"' % (host_os, key))
- @property
- def url(self):
- """Returns the URL of this archive"""
- return self['url']
+ def __getattr__(self, name):
+ """Retrieve values from this dict using attributes.
- @url.setter
- def url(self, url):
- """Set the URL of this archive"""
- self['url'] = url
+ This allows for foo.bar instead of foo['bar'].
- @property
- def size(self):
- """Returns the size of this archive, in bytes"""
- return self['size']
+ Args:
+ name: the name of the key, 'bar' in the example above.
+ Returns:
+ The value associated with that key."""
+ if name not in VALID_ARCHIVE_KEYS:
+ raise AttributeError(name)
+ # special case, self.checksum returns the sha1, not the checksum dict.
+ if name == 'checksum':
+ return self.GetChecksum()
+ return self.__getitem__(name)
+
+ def __setattr__(self, name, value):
+ """Set values in this dict using attributes.
- @property
- def host_os(self):
- """Returns the host OS of this archive"""
- return self['host_os']
+ This allows for foo.bar instead of foo['bar'].
+
+ Args:
+ name: The name of the key, 'bar' in the example above.
+ value: The value to associate with that key."""
+ if name not in VALID_ARCHIVE_KEYS:
+ raise AttributeError(name)
+ # special case, self.checksum returns the sha1, not the checksum dict.
+ if name == 'checksum':
+ self.setdefault('checksum', {})['sha1'] = value
+ return self.__setitem__(name, value)
def GetChecksum(self, type='sha1'):
"""Returns a given cryptographic checksum of the archive"""
@@ -285,51 +297,79 @@ class Bundle(dict):
"""Returns all the archives in this bundle"""
return self[ARCHIVES_KEY]
+ def AddArchive(self, archive):
+ """Add an archive to this bundle."""
+ self.RemoveArchive(archive.host_os)
+ self[ARCHIVES_KEY].append(archive)
+
def RemoveArchive(self, host_os_name):
"""Remove an archive from this Bundle."""
- for i, archive in enumerate(self[ARCHIVES_KEY]):
- if archive.host_os == host_os_name:
- del self[ARCHIVES_KEY][i]
-
- @property
- def name(self):
- """Returns the name of this bundle"""
- return self[NAME_KEY]
-
- @name.setter
- def name(self, name):
- """Set the name of this bundle"""
- self[NAME_KEY] = name
-
- @property
- def version(self):
- """Returns the version of this bundle"""
- return self[VERSION_KEY]
-
- @property
- def revision(self):
- """Returns the revision of this bundle"""
- return self[REVISION_KEY]
-
- @property
- def recommended(self):
- """Returns whether this bundle is recommended"""
- return self['recommended']
-
- @recommended.setter
- def recommended(self, value):
- """Sets whether this bundle is recommended"""
- self['recommended'] = value
-
- @property
- def stability(self):
- """Returns the stability of this bundle"""
- return self['stability']
-
- @stability.setter
- def stability(self, value):
- """Sets the stability of this bundle"""
- self['stability'] = value
+ if host_os_name == 'all':
+ del self[ARCHIVES_KEY][:]
+ else:
+ for i, archive in enumerate(self[ARCHIVES_KEY]):
+ if archive.host_os == host_os_name:
+ del self[ARCHIVES_KEY][i]
+
+ def __getattr__(self, name):
+ """Retrieve values from this dict using attributes.
+
+ This allows for foo.bar instead of foo['bar'].
+
+ Args:
+ name: the name of the key, 'bar' in the example above.
+ Returns:
+ The value associated with that key."""
+ if name not in VALID_BUNDLES_KEYS:
+ raise AttributeError(name)
+ return self.__getitem__(name)
+
+ def __setattr__(self, name, value):
+ """Set values in this dict using attributes.
+
+ This allows for foo.bar instead of foo['bar'].
+
+ Args:
+ name: The name of the key, 'bar' in the example above.
+ value: The value to associate with that key."""
+ if name not in VALID_BUNDLES_KEYS:
+ raise AttributeError(name)
+ self.__setitem__(name, value)
+
+ def __eq__(self, bundle):
+ """Test if two bundles are equal.
+
+ Normally the default comparison for two dicts is fine, but in this case we
+ don't care about the list order of the archives.
+
+ Args:
+ bundle: The other bundle to compare against.
+ Returns:
+ True if the bundles are equal."""
+ if not isinstance(bundle, Bundle):
+ return False
+ if len(self.keys()) != len(bundle.keys()):
+ return False
+ for key in self.keys():
+ if key not in bundle:
+ return False
+ # special comparison for ARCHIVE_KEY because we don't care about the list
+ # ordering.
+ if key == ARCHIVES_KEY:
+ if len(self[key]) != len(bundle[key]):
+ return False
+ for archive in self[key]:
+ if archive != bundle.GetArchive(archive.host_os):
+ return False
+ elif self[key] != bundle[key]:
+ return False
+ return True
+
+ def __ne__(self, bundle):
+ """Test if two bundles are unequal.
+
+ See __eq__ for more info."""
+ return not self.__eq__(bundle)
class SDKManifest(object):
@@ -394,7 +434,7 @@ class SDKManifest(object):
for i, bundle in enumerate(bundles):
if bundle[NAME_KEY] == name:
del bundles[i]
- bundles.append(new_bundle)
+ bundles.append(copy.deepcopy(new_bundle))
def BundleNeedsUpdate(self, bundle):
"""Decides if a bundle needs to be updated.
diff --git a/native_client_sdk/src/build_tools/tests/test_update_manifest.py b/native_client_sdk/src/build_tools/tests/test_update_manifest.py
new file mode 100755
index 0000000..3bcfce5
--- /dev/null
+++ b/native_client_sdk/src/build_tools/tests/test_update_manifest.py
@@ -0,0 +1,366 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import copy
+import datetime
+import os
+import posixpath
+import subprocess
+import sys
+import unittest
+import urlparse
+
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+BUILD_TOOLS_DIR = os.path.dirname(SCRIPT_DIR)
+
+sys.path.append(BUILD_TOOLS_DIR)
+import manifest_util
+import update_nacl_manifest
+
+
+HTTPS_BASE_URL = 'https://commondatastorage.googleapis.com' \
+ '/nativeclient_mirror/nacl/nacl_sdk/'
+
+OS_CR = ('cros',)
+OS_M = ('mac',)
+OS_ML = ('mac', 'linux')
+OS_MLW = ('mac', 'linux', 'win')
+STABLE = 'stable'
+BETA = 'beta'
+DEV = 'dev'
+CANARY = 'canary'
+
+
+def GetArchiveUrl(host_os, version):
+ basename = 'naclsdk_%s.bz2' % (host_os,)
+ return urlparse.urljoin(HTTPS_BASE_URL, posixpath.join(version, basename))
+
+
+def GetPathFromGsUrl(url):
+ assert url.startswith(update_nacl_manifest.GS_BUCKET_PATH)
+ return url[len(update_nacl_manifest.GS_BUCKET_PATH):]
+
+
+def GetPathFromHttpsUrl(url):
+ assert url.startswith(HTTPS_BASE_URL)
+ return url[len(HTTPS_BASE_URL):]
+
+
+def MakeArchive(host_os, version):
+ archive = manifest_util.Archive(host_os)
+ archive.url = GetArchiveUrl(host_os, version)
+ # dummy values that won't succeed if we ever use them, but will pass
+ # validation. :)
+ archive.checksum = {'sha1': 'foobar'}
+ archive.size = 1
+ return archive
+
+
+def MakeNonPepperBundle(name, with_archives=False):
+ bundle = manifest_util.Bundle(name)
+ bundle.version = 1
+ bundle.revision = 1
+ bundle.description = 'Dummy bundle'
+ bundle.recommended = 'yes'
+ bundle.stability = 'stable'
+
+ if with_archives:
+ for host_os in OS_MLW:
+ archive = manifest_util.Archive(host_os)
+ archive.url = 'http://example.com'
+ archive.checksum = {'sha1': 'blah'}
+ archive.size = 2
+ bundle.AddArchive(archive)
+ return bundle
+
+
+def MakeBundle(major_version, revision, version=None, host_oses=None):
+ assert version is None or version.split('.')[0] == major_version
+ bundle_name = 'pepper_' + major_version
+ bundle = manifest_util.Bundle(bundle_name)
+ bundle.version = int(major_version)
+ bundle.revision = revision
+ bundle.description = 'Chrome %s bundle, revision %s' % (major_version,
+ revision)
+ bundle.repath = bundle_name
+ bundle.recommended = 'no'
+ bundle.stability = 'dev'
+
+ if host_oses:
+ for host_os in host_oses:
+ bundle.AddArchive(MakeArchive(host_os, version))
+ return bundle
+
+
+class MakeManifest(manifest_util.SDKManifest):
+ def __init__(self, *args):
+ manifest_util.SDKManifest.__init__(self)
+
+ for bundle in args:
+ self.AddBundle(bundle)
+
+ def AddBundle(self, bundle):
+ self.MergeBundle(bundle, allow_existing=False)
+
+
+class MakeHistory(object):
+ def __init__(self):
+ # used for a dummy timestamp
+ self.datetime = datetime.datetime.utcnow()
+ self.history = []
+
+ def Add(self, host_oses, channel, version):
+ for host_os in host_oses:
+ timestamp = self.datetime.strftime('%Y-%m-%d %H:%M:%S.%f')
+ self.history.append((host_os, channel, version, timestamp))
+ self.datetime += datetime.timedelta(0, -3600) # one hour earlier
+ self.datetime += datetime.timedelta(-1) # one day earlier
+
+
+class MakeFiles(dict):
+ def Add(self, bundle, add_archive_for_os=OS_MLW, add_json_for_os=OS_MLW):
+ for archive in bundle.GetArchives():
+ if not archive.host_os in add_archive_for_os:
+ continue
+
+ # add a dummy file for each archive
+ path = GetPathFromHttpsUrl(archive.url)
+ self[path] = 'My Dummy Archive'
+
+ if archive.host_os in add_json_for_os:
+ # add .json manifest snippet, it should look like a normal Bundle, but
+ # only has one archive.
+ new_bundle = manifest_util.Bundle('')
+ new_bundle.CopyFrom(bundle)
+ del new_bundle.archives[:]
+ new_bundle.AddArchive(archive)
+ self[path + '.json'] = new_bundle.GetDataAsString()
+
+
+class TestDelegate(update_nacl_manifest.Delegate):
+ def __init__(self, manifest, history, files):
+ self.manifest = manifest
+ self.history = history
+ self.files = files
+
+ def GetRepoManifest(self):
+ return self.manifest
+
+ def GetHistory(self):
+ return self.history
+
+ def GsUtil_ls(self, url):
+ path = GetPathFromGsUrl(url)
+ result = []
+ for filename, _ in self.files.iteritems():
+ if filename.startswith(path):
+ result.append(filename)
+ return result
+
+ def GsUtil_cat(self, url):
+ path = GetPathFromGsUrl(url)
+ if path not in self.files:
+ raise subprocess.CalledProcessError(1, 'gsutil cat %s' % (url,))
+ return self.files[path]
+
+ def GsUtil_cp(self, src, dest, stdin=None):
+ dest_path = GetPathFromGsUrl(dest)
+ if src == '-':
+ self.files[dest_path] = stdin
+ else:
+ src_path = GetPathFromGsUrl(src)
+ if src_path not in self.files:
+ raise subprocess.CalledProcessError(1, 'gsutil cp %s %s' % (src, dest))
+ self.files[dest_path] = self.files[src_path]
+
+ def Print(self, *args):
+ # eat all informational messages
+ pass
+
+
+# Shorthand for premade bundles/versions
+V18_0_1025_163 = '18.0.1025.163'
+V18_0_1025_175 = '18.0.1025.175'
+V18_0_1025_184 = '18.0.1025.184'
+V19_0_1084_41 = '19.0.1084.41'
+V19_0_1084_67 = '19.0.1084.67'
+B18_0_1025_163_R1_MLW = MakeBundle('18', '1', V18_0_1025_163, OS_MLW)
+B18_0_1025_184_R1_MLW = MakeBundle('18', '1', V18_0_1025_184, OS_MLW)
+B18_R1_NONE = MakeBundle('18', '1')
+B19_0_1084_41_R1_MLW = MakeBundle('19', '1', V19_0_1084_41, OS_MLW)
+B19_0_1084_67_R1_MLW = MakeBundle('19', '1', V19_0_1084_67, OS_MLW)
+B19_R1_NONE = MakeBundle('19', '1')
+NON_PEPPER_BUNDLE_NOARCHIVES = MakeNonPepperBundle('foo')
+NON_PEPPER_BUNDLE_ARCHIVES = MakeNonPepperBundle('bar', with_archives=True)
+
+
+class TestUpdateManifest(unittest.TestCase):
+ def setUp(self):
+ self.history = MakeHistory()
+ self.files = MakeFiles()
+ self.delegate = None
+ self.uploaded_manifest = None
+ self.manifest = None
+
+ def _MakeDelegate(self):
+ self.delegate = TestDelegate(self.manifest, self.history.history,
+ self.files)
+
+ def _Run(self, host_oses):
+ update_nacl_manifest.Run(self.delegate, host_oses)
+
+ def _HasUploadedManifest(self):
+ return 'naclsdk_manifest2.json' in self.files
+
+ def _ReadUploadedManifest(self):
+ self.uploaded_manifest = manifest_util.SDKManifest()
+ self.uploaded_manifest.LoadDataFromString(
+ self.files['naclsdk_manifest2.json'])
+
+ def _AssertUploadedManifestHasBundle(self, bundle, stability):
+ uploaded_manifest_bundle = self.uploaded_manifest.GetBundle(bundle.name)
+ # Bundles that we create in the test (and in the manifest snippets) have
+ # their stability set to "dev". update_nacl_manifest correctly updates it.
+ # So we have to force the stability of |bundle| so they compare equal.
+ test_bundle = copy.copy(bundle)
+ test_bundle.stability = stability
+ self.assertEqual(uploaded_manifest_bundle, test_bundle)
+
+ def testNoUpdateNeeded(self):
+ self.manifest = MakeManifest(B18_0_1025_163_R1_MLW)
+ self._MakeDelegate()
+ self._Run(OS_MLW)
+ self.assertEqual(self._HasUploadedManifest(), False)
+
+ # Add another bundle, make sure it still doesn't update
+ self.manifest.AddBundle(B19_0_1084_41_R1_MLW)
+ self._Run(OS_MLW)
+ self.assertEqual(self._HasUploadedManifest(), False)
+
+ def testSimpleUpdate(self):
+ self.manifest = MakeManifest(B18_R1_NONE)
+ self.history.Add(OS_MLW, BETA, V18_0_1025_163)
+ self.files.Add(B18_0_1025_163_R1_MLW)
+ self._MakeDelegate()
+ self._Run(OS_MLW)
+ self._ReadUploadedManifest()
+ self._AssertUploadedManifestHasBundle(B18_0_1025_163_R1_MLW, BETA)
+ self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
+
+ def testOnePlatformHasNewerRelease(self):
+ self.manifest = MakeManifest(B18_R1_NONE)
+ self.history.Add(OS_M, BETA, V18_0_1025_175) # Mac has newer version
+ self.history.Add(OS_MLW, BETA, V18_0_1025_163)
+ self.files.Add(B18_0_1025_163_R1_MLW)
+ self._MakeDelegate()
+ self._Run(OS_MLW)
+ self._ReadUploadedManifest()
+ self._AssertUploadedManifestHasBundle(B18_0_1025_163_R1_MLW, BETA)
+ self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
+
+ def testMultipleMissingPlatformsInHistory(self):
+ self.manifest = MakeManifest(B18_R1_NONE)
+ self.history.Add(OS_ML, BETA, V18_0_1025_184)
+ self.history.Add(OS_M, BETA, V18_0_1025_175)
+ self.history.Add(OS_MLW, BETA, V18_0_1025_163)
+ self.files.Add(B18_0_1025_163_R1_MLW)
+ self._MakeDelegate()
+ self._Run(OS_MLW)
+ self._ReadUploadedManifest()
+ self._AssertUploadedManifestHasBundle(B18_0_1025_163_R1_MLW, BETA)
+ self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
+
+ def testUpdateOnlyOneBundle(self):
+ self.manifest = MakeManifest(B18_R1_NONE, B19_0_1084_41_R1_MLW)
+ self.history.Add(OS_MLW, BETA, V18_0_1025_163)
+ self.files.Add(B18_0_1025_163_R1_MLW)
+ self._MakeDelegate()
+ self._Run(OS_MLW)
+ self._ReadUploadedManifest()
+ self._AssertUploadedManifestHasBundle(B18_0_1025_163_R1_MLW, BETA)
+ self._AssertUploadedManifestHasBundle(B19_0_1084_41_R1_MLW, DEV)
+ self.assertEqual(len(self.uploaded_manifest.GetBundles()), 2)
+
+ def testUpdateTwoBundles(self):
+ self.manifest = MakeManifest(B18_R1_NONE, B19_R1_NONE)
+ self.history.Add(OS_MLW, BETA, V19_0_1084_41)
+ self.history.Add(OS_MLW, STABLE, V18_0_1025_163)
+ self.files.Add(B18_0_1025_163_R1_MLW)
+ self.files.Add(B19_0_1084_41_R1_MLW)
+ self._MakeDelegate()
+ self._Run(OS_MLW)
+ self._ReadUploadedManifest()
+ self._AssertUploadedManifestHasBundle(B18_0_1025_163_R1_MLW, STABLE)
+ self._AssertUploadedManifestHasBundle(B19_0_1084_41_R1_MLW, BETA)
+ self.assertEqual(len(self.uploaded_manifest.GetBundles()), 2)
+
+ def testUpdateWithMissingPlatformsInArchives(self):
+ self.manifest = MakeManifest(B18_R1_NONE)
+ self.history.Add(OS_MLW, BETA, V18_0_1025_184)
+ self.history.Add(OS_MLW, BETA, V18_0_1025_163)
+ self.files.Add(B18_0_1025_184_R1_MLW, add_archive_for_os=OS_M)
+ self.files.Add(B18_0_1025_163_R1_MLW)
+ self._MakeDelegate()
+ self._Run(OS_MLW)
+ self._ReadUploadedManifest()
+ self._AssertUploadedManifestHasBundle(B18_0_1025_163_R1_MLW, BETA)
+ self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
+
+ def testUpdateWithMissingManifestSnippets(self):
+ self.manifest = MakeManifest(B18_R1_NONE)
+ self.history.Add(OS_MLW, BETA, V18_0_1025_184)
+ self.history.Add(OS_MLW, BETA, V18_0_1025_163)
+ self.files.Add(B18_0_1025_184_R1_MLW, add_json_for_os=OS_ML)
+ self.files.Add(B18_0_1025_163_R1_MLW)
+ self._MakeDelegate()
+ self._Run(OS_MLW)
+ self._ReadUploadedManifest()
+ self._AssertUploadedManifestHasBundle(B18_0_1025_163_R1_MLW, BETA)
+ self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
+
+ def testRecommendedIsMaintained(self):
+ for recommended in 'yes', 'no':
+ self.setUp()
+ bundle = copy.deepcopy(B18_R1_NONE)
+ bundle.recommended = recommended
+ self.manifest = MakeManifest(bundle)
+ self.history.Add(OS_MLW, BETA, V18_0_1025_163)
+ self.files.Add(B18_0_1025_163_R1_MLW)
+ self._MakeDelegate()
+ self._Run(OS_MLW)
+ self._ReadUploadedManifest()
+ self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
+ uploaded_bundle = self.uploaded_manifest.GetBundle('pepper_18')
+ self.assertEqual(uploaded_bundle.recommended, recommended)
+
+ def testNoUpdateWithNonPepperBundle(self):
+ self.manifest = MakeManifest(NON_PEPPER_BUNDLE_NOARCHIVES,
+ B18_0_1025_163_R1_MLW)
+ self._MakeDelegate()
+ self._Run(OS_MLW)
+ self.assertEqual(self._HasUploadedManifest(), False)
+
+ def testUpdateWithHistoryWithExtraneousPlatforms(self):
+ self.manifest = MakeManifest(B18_R1_NONE)
+ self.history.Add(OS_ML, BETA, V18_0_1025_184)
+ self.history.Add(OS_CR, BETA, V18_0_1025_184)
+ self.history.Add(OS_CR, BETA, V18_0_1025_175)
+ self.history.Add(OS_MLW, BETA, V18_0_1025_163)
+ self.files.Add(B18_0_1025_163_R1_MLW)
+ self._MakeDelegate()
+ self._Run(OS_MLW)
+ self._ReadUploadedManifest()
+ self._AssertUploadedManifestHasBundle(B18_0_1025_163_R1_MLW, BETA)
+ self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
+
+
+def main():
+ suite = unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__])
+ result = unittest.TextTestRunner(verbosity=2).run(suite)
+
+ return int(not result.wasSuccessful())
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/native_client_sdk/src/build_tools/update_nacl_manifest.py b/native_client_sdk/src/build_tools/update_nacl_manifest.py
index ac37553..b72ef88 100755
--- a/native_client_sdk/src/build_tools/update_nacl_manifest.py
+++ b/native_client_sdk/src/build_tools/update_nacl_manifest.py
@@ -11,6 +11,7 @@ import buildbot_common
import csv
import manifest_util
import os
+import posixpath
import re
import subprocess
import sys
@@ -21,13 +22,16 @@ import urllib2
# TODO(binji) handle pushing pepper_trunk
-BUCKET_PATH = 'nativeclient-mirror/nacl/nacl_sdk'
-GS_SDK_MANIFEST = 'gs://%s/naclsdk_manifest2.json' % (BUCKET_PATH,)
+MANIFEST_BASENAME = 'naclsdk_manifest2.json'
+SCRIPT_DIR = os.path.dirname(__file__)
+REPO_MANIFEST = os.path.join(SCRIPT_DIR, 'json', MANIFEST_BASENAME)
+GS_BUCKET_PATH = 'gs://nativeclient-mirror/nacl/nacl_sdk/'
+GS_SDK_MANIFEST = GS_BUCKET_PATH + MANIFEST_BASENAME
def SplitVersion(version_string):
"""Split a version string (e.g. "18.0.1025.163") into its components.
-
+
Note that this function doesn't handle versions in the form "trunk.###".
"""
return tuple(map(int, version_string.split('.')))
@@ -41,243 +45,375 @@ def JoinVersion(version_tuple):
return '.'.join(map(str, version_tuple))
-def GetChromeRepoSDKManifest():
- with open(os.path.join('json', 'naclsdk_manifest2.json'), 'r') as sdk_stream:
- sdk_json_string = sdk_stream.read()
-
- manifest = manifest_util.SDKManifest()
- manifest.LoadDataFromString(sdk_json_string)
- return manifest
-
-
-def GetChromeVersionHistory():
- """Read Chrome version history from omahaproxy.appspot.com/history.
-
- Here is an example of data from this URL:
- cros,stable,18.0.1025.168,2012-05-01 17:04:05.962578\n
- win,canary,20.0.1123.0,2012-05-01 13:59:31.703020\n
- mac,canary,20.0.1123.0,2012-05-01 11:54:13.041875\n
- win,stable,18.0.1025.168,2012-04-30 20:34:56.078490\n
- mac,stable,18.0.1025.168,2012-04-30 20:34:55.231141\n
- ...
- Where each line has comma separated values in the following format:
- platform, channel, version, date/time\n
-
- Returns:
- A list where each element is a line from the document, represented as a
- tuple. The version number has also been split at '.', and converted to ints
- for easier comparisons.
- """
- url_stream = urllib2.urlopen('https://omahaproxy.appspot.com/history')
- return [(platform, channel, SplitVersion(version), date)
- for platform, channel, version, date in csv.reader(url_stream)]
-
-
-def GetPlatformMajorVersionHistory(history, with_platform, with_major_version):
- """Yields Chrome history for a given platform and major version.
-
- Args:
- history: Chrome update history as returned from GetChromeVersionHistory.
- with_platform: The name of the platform to filter for.
- with_major_version: The major version to filter for.
- Returns:
- A generator that yields a tuple (channel, version) for each version that
- matches the platform and major version. The version returned is a tuple as
- returned from SplitVersion.
- """
- for platform, channel, version, _ in history:
- if with_platform == platform and with_major_version == version[0]:
- yield channel, version
-
-
-def FindNextSharedVersion(history, major_version, platforms):
- """Yields versions of Chrome that exist on all given platforms, in order of
- newest to oldest.
-
- Versions are compared in reverse order of release. That is, the most recently
- updated version will be tested first.
+def GetTimestampManifestName():
+ """Create a manifest name with a timestamp.
- Args:
- history: Chrome update history as returned from GetChromeVersionHistory.
- major_version: The major version to filter for.
- platforms: A sequence of platforms to filter for. Any other platforms will
- be ignored.
Returns:
- A generator that yields a tuple (version, channel) for each version that
- matches all platforms and the major version. The version returned is a
- string (e.g. "18.0.1025.164").
+ A manifest name with an embedded date. This should make it easier to roll
+ back if necessary.
"""
- platform_generators = []
- for platform in platforms:
- platform_generators.append(GetPlatformMajorVersionHistory(history, platform,
- major_version))
-
- shared_version = None
- platform_versions = [(tuple(), '')] * len(platforms)
- while True:
- try:
- for i, platform_gen in enumerate(platform_generators):
- if platform_versions[i][1] != shared_version:
- platform_versions[i] = platform_gen.next()
- except StopIteration:
- return
-
- shared_version = min(v for c, v in platform_versions)
-
- if all(v == shared_version for c, v in platform_versions):
- # grab the channel from an arbitrary platform
- first_platform = platform_versions[0]
- channel = first_platform[0]
- yield JoinVersion(shared_version), channel
-
- # force increment to next version for all platforms
- shared_version = None
+ return time.strftime('naclsdk_manifest2.%Y_%m_%d_%H_%M_%S.json',
+ time.gmtime())
-def GetAvailableNaClSDKArchivesFor(version_string):
- """Downloads a list of all available archives for a given version.
+def GetPlatformFromArchiveUrl(url):
+ """Get the platform name given an archive url.
Args:
- version_string: The version to find archives for. (e.g. "18.0.1025.164")
+ url: An archive url.
Returns:
- A list of strings, each of which is a platform-specific archive URL. (e.g.
- "https://commondatastorage.googleapis.com/nativeclient_mirror/nacl/"
- "nacl_sdk/18.0.1025.164/naclsdk_linux.bz2".
- """
- gsutil = buildbot_common.GetGsutil()
- path = 'gs://%s/%s' % (BUCKET_PATH, version_string,)
- cmd = [gsutil, 'ls', path]
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
- stdout, stderr = process.communicate()
- if process.returncode != 0:
- raise subprocess.CalledProcessError(process.returncode, ''.join(cmd))
-
- # filter out empty lines
- files = filter(lambda x: x, stdout.split('\n'))
- archives = [file for file in files if not file.endswith('.json')]
- manifests = [file for file in files if file.endswith('.json')]
-
- # don't include any archives that don't have an associated manifest.
- return filter(lambda a: a + '.json' in manifests, archives)
+ A platform name (e.g. 'linux')."""
+ match = re.match(r'naclsdk_(.*)\.bz2', posixpath.basename(url))
+ if not match:
+ return None
+ return match.group(1)
-def GetPlatformsFromArchives(archives):
- """Get the platform names for a sequence of archives.
+def GetPlatformsFromArchives(archive_urls):
+ """Get the platform names for a sequence of archive urls.
Args:
- archives: A sequence of archives.
+ archives: A sequence of archive urls.
Returns:
- A list of platforms, one for each archive in |archives|."""
+ A list of platforms, one for each url in |archive_urls|."""
platforms = []
- for path in archives:
- match = re.match(r'naclsdk_(.*)\.bz2', os.path.basename(path))
- if match:
- platforms.append(match.group(1))
+ for url in archive_urls:
+ platform = GetPlatformFromArchiveUrl(url)
+ if platform:
+ platforms.append(platform)
return platforms
-def GetPlatformArchiveBundle(archive):
- """Downloads the manifest "snippet" for an archive, and reads it as a Bundle.
+class Delegate(object):
+ """Delegate all external access; reading/writing to filesystem, gsutil etc."""
+ def GetRepoManifest(self):
+ """Read the manifest file from the NaCl SDK repository.
- Args:
- archive: The URL of a platform-specific archive.
- Returns:
- An object of type manifest_util.Bundle, read from a JSON file storing
- metadata for this archive.
- """
- gsutil = buildbot_common.GetGsutil()
- cmd = [gsutil, 'cat', archive + '.json']
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
- stdout, stderr = process.communicate()
- if process.returncode != 0:
- return None
+ This manifest is used as a template for the auto updater; only pepper
+ bundles with no archives are considered for auto updating.
- bundle = manifest_util.Bundle('')
- bundle.LoadDataFromString(stdout)
- return bundle
+ Returns:
+ A manifest_util.SDKManifest object read from the NaCl SDK repo."""
+ raise NotImplementedError()
+ def GetHistory(self):
+ """Read Chrome release history from omahaproxy.appspot.com
-def GetTimestampManifestName():
- """Create a manifest name with a timestamp.
-
- Returns:
- A manifest name with an embedded date. This should make it easier to roll
- back if necessary.
- """
- return time.strftime('naclsdk_manifest2.%Y_%m_%d_%H_%M_%S.json',
- time.gmtime())
+ Here is an example of data from this URL:
+ cros,stable,18.0.1025.168,2012-05-01 17:04:05.962578\n
+ win,canary,20.0.1123.0,2012-05-01 13:59:31.703020\n
+ mac,canary,20.0.1123.0,2012-05-01 11:54:13.041875\n
+ win,stable,18.0.1025.168,2012-04-30 20:34:56.078490\n
+ mac,stable,18.0.1025.168,2012-04-30 20:34:55.231141\n
+ ...
+ Where each line has comma separated values in the following format:
+ platform, channel, version, date/time\n
+ Returns:
+ A list where each element is a line from the document, represented as a
+ tuple."""
+ raise NotImplementedError()
-def UploadManifest(manifest):
- """Upload a serialized manifest_util.SDKManifest object.
+ def GsUtil_ls(self, url):
+ """Runs gsutil ls |url|
- Upload one copy to gs://<BUCKET_PATH>/naclsdk_manifest2.json, and a copy to
- gs://<BUCKET_PATH>/manifest_backups/naclsdk_manifest2.<TIMESTAMP>.json.
+ Args:
+ url: The commondatastorage url to list."""
+ raise NotImplementedError()
- Args:
- manifest: The new manifest to upload.
- """
- manifest_string = manifest.GetDataAsString()
- gsutil = buildbot_common.GetGsutil()
- timestamp_manifest_path = 'gs://%s/manifest_backups/%s' % (
- BUCKET_PATH, GetTimestampManifestName())
-
- cmd = [gsutil, 'cp', '-a', 'public-read', '-', timestamp_manifest_path]
- process = subprocess.Popen(cmd, stdin=subprocess.PIPE)
- stdout, stderr = process.communicate(manifest_string)
-
- # copy from timestampped copy over the official manifest.
- cmd = [gsutil, 'cp', '-a', 'public-read', timestamp_manifest_path,
- GS_SDK_MANIFEST]
- subprocess.check_call(cmd)
-
-
-def main(args):
- manifest = GetChromeRepoSDKManifest()
+ def GsUtil_cat(self, url):
+ """Runs gsutil cat |url|
+
+ Args:
+ url: The commondatastorage url to read from."""
+ raise NotImplementedError()
+
+ def GsUtil_cp(self, src, dest, stdin=None):
+ """Runs gsutil cp |src| |dest|
+
+ Args:
+ src: The file path or url to copy from.
+ dest: The file path or url to copy to.
+ stdin: If src is '-', this is used as the stdin to give to gsutil. The
+ effect is that text in stdin is copied to |dest|."""
+ raise NotImplementedError()
+
+
+ def Print(self, *args):
+ """Print a message."""
+ raise NotImplementedError()
+
+
+class RealDelegate(Delegate):
+ def __init__(self):
+ pass
+
+ def GetRepoManifest(self):
+ """See Delegate.GetRepoManifest"""
+ with open(REPO_MANIFEST, 'r') as sdk_stream:
+ sdk_json_string = sdk_stream.read()
+
+ manifest = manifest_util.SDKManifest()
+ manifest.LoadDataFromString(sdk_json_string)
+ return manifest
+
+ def GetHistory(self):
+ """See Delegate.GetHistory"""
+ url_stream = urllib2.urlopen('https://omahaproxy.appspot.com/history')
+ return [(platform, channel, version, date)
+ for platform, channel, version, date in csv.reader(url_stream)]
+
+ def GsUtil_ls(self, url):
+ """See Delegate.GsUtil_ls"""
+ stdout = self._RunGsUtil(None, 'ls', url)
+
+ # filter out empty lines
+ return filter(None, stdout.split('\n'))
+
+ def GsUtil_cat(self, url):
+ """See Delegate.GsUtil_cat"""
+ return self._RunGsUtil(None, 'cat', url)
+
+ def GsUtil_cp(self, src, dest, stdin=None):
+ """See Delegate.GsUtil_cp"""
+ return self._RunGsUtil(stdin, 'cp', '-a', 'public-read', src, dest)
+
+ def Print(self, *args):
+ sys.stdout.write(' '.join(map(str, args)) + '\n')
+
+ def _RunGsUtil(self, stdin, *args):
+ """Run gsutil as a subprocess.
+
+ Args:
+ stdin: If non-None, used as input to the process.
+ *args: Arguments to pass to gsutil. The first argument should be an
+ operation such as ls, cp or cat.
+ Returns:
+ The stdout from the process."""
+ cmd = [buildbot_common.GetGsutil()] + list(args)
+ if stdin:
+ stdin_pipe = subprocess.PIPE
+ else:
+ stdin_pipe = None
+
+ process = subprocess.Popen(cmd, stdin=stdin_pipe, stdout=subprocess.PIPE)
+ stdout, _ = process.communicate(stdin)
+
+ if process.returncode != 0:
+ raise subprocess.CalledProcessError(process.returncode, ' '.join(cmd))
+ return stdout
+
+
+class VersionFinder(object):
+ """Finds a version of a pepper bundle that all desired platforms share."""
+ def __init__(self, delegate):
+ self.delegate = delegate
+ self.history = delegate.GetHistory()
+
+ def GetMostRecentSharedVersion(self, major_version, platforms):
+ """Returns the most recent version of a pepper bundle that exists on all
+ given platforms.
+
+ Specifically, the resulting version should be the most recently released
+ (meaning closest to the top of the listing on
+ omahaproxy.appspot.com/history) version that has a Chrome release on all
+ given platforms, and has a pepper bundle archive for each platform as well.
+
+ Args:
+ major_version: The major version of the pepper bundle, e.g. 19.
+ platforms: A sequence of platforms to consider, e.g.
+ ('mac', 'linux', 'win')
+ Returns:
+ A tuple (version, channel, archives). The version is a string such as
+ "19.0.1084.41". The channel is one of ('stable', 'beta', or 'dev').
+ |archives| is a list of archive URLs."""
+ shared_version_generator = self._FindNextSharedVersion(major_version,
+ platforms)
+ version = None
+ while True:
+ try:
+ version, channel = shared_version_generator.next()
+ except StopIteration:
+ raise Exception('No shared version for major_version %s, '
+ 'platforms: %s. Last version checked = %s' % (
+ major_version, ', '.join(platforms), version))
+
+ archives = self._GetAvailableNaClSDKArchivesFor(version)
+ archive_platforms = GetPlatformsFromArchives(archives)
+ if set(archive_platforms) == set(platforms):
+ return version, channel, archives
+
+ def _GetPlatformMajorVersionHistory(self, with_major_version, with_platform):
+ """Yields Chrome history for a given platform and major version.
+
+ Args:
+ with_platform: The name of the platform to filter for.
+ Returns:
+ A generator that yields a tuple (channel, version) for each version that
+ matches the platform and major version. The version returned is a tuple as
+ returned from SplitVersion.
+ """
+ for platform, channel, version, _ in self.history:
+ version = SplitVersion(version)
+ if with_platform == platform and with_major_version == version[0]:
+ yield channel, version
+
+ def _FindNextSharedVersion(self, major_version, platforms):
+ """Yields versions of Chrome that exist on all given platforms, in order of
+ newest to oldest.
+
+ Versions are compared in reverse order of release. That is, the most
+ recently updated version will be tested first.
+
+ Args:
+ platforms: A sequence of platforms to filter for. Any other platforms will
+ be ignored.
+ Returns:
+ A generator that yields a tuple (version, channel) for each version that
+ matches all platforms and the major version. The version returned is a
+ string (e.g. "18.0.1025.164").
+ """
+ platform_generators = []
+ for platform in platforms:
+ platform_generators.append(self._GetPlatformMajorVersionHistory(
+ major_version, platform))
+
+ shared_version = None
+ platform_versions = [(tuple(), '')] * len(platforms)
+ while True:
+ try:
+ for i, platform_gen in enumerate(platform_generators):
+ if platform_versions[i][1] != shared_version:
+ platform_versions[i] = platform_gen.next()
+ except StopIteration:
+ return
+
+ shared_version = min(v for c, v in platform_versions)
+
+ if all(v == shared_version for c, v in platform_versions):
+ # grab the channel from an arbitrary platform
+ first_platform = platform_versions[0]
+ channel = first_platform[0]
+ yield JoinVersion(shared_version), channel
+
+ # force increment to next version for all platforms
+ shared_version = None
+
+ def _GetAvailableNaClSDKArchivesFor(self, version_string):
+ """Downloads a list of all available archives for a given version.
+
+ Args:
+ version_string: The version to find archives for. (e.g. "18.0.1025.164")
+ Returns:
+ A list of strings, each of which is a platform-specific archive URL. (e.g.
+ "https://commondatastorage.googleapis.com/nativeclient_mirror/nacl/"
+ "nacl_sdk/18.0.1025.164/naclsdk_linux.bz2".
+ """
+ files = self.delegate.GsUtil_ls(GS_BUCKET_PATH + version_string)
+ archives = [file for file in files if not file.endswith('.json')]
+ manifests = [file for file in files if file.endswith('.json')]
+
+ # don't include any archives that don't have an associated manifest.
+ return filter(lambda a: a + '.json' in manifests, archives)
+
+
+class Updater(object):
+ def __init__(self, delegate):
+ self.delegate = delegate
+ self.versions_to_update = []
+
+ def AddVersionToUpdate(self, bundle_name, version, channel, archives):
+ """Add a pepper version to update in the uploaded manifest.
+
+ Args:
+ bundle_name: The name of the pepper bundle, e.g. 'pepper_18'
+ version: The version of the pepper bundle, e.g. '18.0.1025.64'
+ channel: The stability of the pepper bundle, e.g. 'beta'
+ archives: A sequence of archive URLs for this bundle."""
+ self.versions_to_update.append((bundle_name, version, channel, archives))
+
+ def Update(self, manifest):
+ """Update a manifest and upload it.
+
+ Args:
+ manifest: The manifest used as a template for updating. Only pepper
+ bundles that contain no archives will be considered for auto-updating."""
+ for bundle_name, version, channel, archives in self.versions_to_update:
+ self.delegate.Print('Updating %s to %s...' % (bundle_name, version))
+ bundle = manifest.GetBundle(bundle_name)
+ bundle_recommended = bundle.recommended
+ for archive in archives:
+ platform_bundle = self._GetPlatformArchiveBundle(archive)
+ bundle.MergeWithBundle(platform_bundle)
+ bundle.stability = channel
+ bundle.recommended = bundle_recommended
+ manifest.MergeBundle(bundle)
+ self._UploadManifest(manifest)
+ self.delegate.Print('Done.')
+
+ def _GetPlatformArchiveBundle(self, archive):
+ """Downloads the manifest "snippet" for an archive, and reads it as a
+ Bundle.
+
+ Args:
+ archive: The URL of a platform-specific archive.
+ Returns:
+ An object of type manifest_util.Bundle, read from a JSON file storing
+ metadata for this archive.
+ """
+ stdout = self.delegate.GsUtil_cat(GS_BUCKET_PATH + archive + '.json')
+ bundle = manifest_util.Bundle('')
+ bundle.LoadDataFromString(stdout)
+ return bundle
+
+ def _UploadManifest(self, manifest):
+ """Upload a serialized manifest_util.SDKManifest object.
+
+ Upload one copy to gs://<BUCKET_PATH>/naclsdk_manifest2.json, and a copy to
+ gs://<BUCKET_PATH>/manifest_backups/naclsdk_manifest2.<TIMESTAMP>.json.
+
+ Args:
+ manifest: The new manifest to upload.
+ """
+ timestamp_manifest_path = GS_BUCKET_PATH + GetTimestampManifestName()
+ self.delegate.GsUtil_cp('-', timestamp_manifest_path,
+ stdin=manifest.GetDataAsString())
+
+ # copy from timestampped copy over the official manifest.
+ self.delegate.GsUtil_cp(timestamp_manifest_path, GS_SDK_MANIFEST)
+
+
+def Run(delegate, platforms):
+ """Entry point for the auto-updater."""
+ manifest = delegate.GetRepoManifest()
auto_update_major_versions = []
for bundle in manifest.GetBundles():
+ if not bundle.name.startswith('pepper_'):
+ continue
archives = bundle.GetArchives()
if not archives:
auto_update_major_versions.append(bundle.version)
- if auto_update_major_versions:
- history = GetChromeVersionHistory()
- platforms = ('mac', 'win', 'linux')
-
- versions_to_update = []
- for major_version in auto_update_major_versions:
- shared_version_generator = FindNextSharedVersion(history, major_version,
- platforms)
- version = None
- while True:
- try:
- version, channel = shared_version_generator.next()
- except StopIteration:
- raise Exception('No shared version for major_version %s, '
- 'platforms: %s. Last version checked = %s' % (
- major_version, ', '.join(platforms), version))
-
- archives = GetAvailableNaClSDKArchivesFor(version)
- archive_platforms = GetPlatformsFromArchives(archives)
- if set(archive_platforms) == set(platforms):
- versions_to_update.append((major_version, version, channel, archives))
- break
-
- if versions_to_update:
- for major_version, version, channel, archives in versions_to_update:
- bundle_name = 'pepper_%s' % (major_version,)
- bundle = manifest.GetBundle(bundle_name)
- bundle_recommended = bundle.recommended
- for archive in archives:
- platform_bundle = GetPlatformArchiveBundle(archive)
- bundle.MergeWithBundle(platform_bundle)
- bundle.stability = channel
- bundle.recommended = bundle_recommended
- manifest.MergeBundle(bundle)
- UploadManifest(manifest)
-
- else:
- print 'No versions need auto-updating.'
+ if not auto_update_major_versions:
+ delegate.Print('No versions need auto-updating.')
+ return
+
+ version_finder = VersionFinder(delegate)
+ updater = Updater(delegate)
+
+ for major_version in auto_update_major_versions:
+ version, channel, archives = version_finder.GetMostRecentSharedVersion(
+ major_version, platforms)
+
+ bundle_name = 'pepper_' + str(major_version)
+ updater.AddVersionToUpdate(bundle_name, version, channel, archives)
+
+ updater.Update(manifest)
+
+
+def main(args):
+ delegate = RealDelegate()
+ Run(delegate, ('mac', 'win', 'linux'))
if __name__ == '__main__':