Index: chrome/common/extensions/docs/build/directory.py |
diff --git a/chrome/common/extensions/docs/build/directory.py b/chrome/common/extensions/docs/build/directory.py |
index 1c7cb757f80deec00846c81701199b30139e3c01..f95344fcb099a27b46e54c358f24ac5548d29a5e 100644 |
--- a/chrome/common/extensions/docs/build/directory.py |
+++ b/chrome/common/extensions/docs/build/directory.py |
@@ -225,8 +225,7 @@ class SamplesManifest(object): |
Args: |
path: The path to write the samples manifest file to. |
""" |
- |
- manifest_text = json.dumps(self._manifest_data, indent=2) |
+ manifest_text = json.dumps(self._manifest_data, indent=2, sort_keys=True) |
output_path = os.path.realpath(path) |
try: |
output_file = open(output_path, 'w') |
@@ -238,10 +237,18 @@ class SamplesManifest(object): |
def writeZippedSamples(self): |
""" For each sample in the current manifest, create a zip file with the |
- sample contents in the sample's parent directory. """ |
+ sample contents in the sample's parent directory if not zip exists, or |
+ update the zip file if the sample has been updated. |
+ Returns: |
+ A set of paths representing zip files which have been modified. |
+ """ |
+ modified_paths = [] |
for sample in self._manifest_data['samples']: |
- sample.write_zip() |
+ path = sample.write_zip() |
+ if path: |
+ modified_paths.append(path) |
+ return modified_paths |
class Sample(dict): |
""" Represents metadata about a Chrome extension sample. |
@@ -264,10 +271,13 @@ class Sample(dict): |
self._manifest = parse_json_file(self._manifest_path) |
self._locale_data = self._parse_locale_data() |
- # The following properties will be serialized when converting this object |
- # to JSON. |
+ # The following calls set data which will be serialized when converting |
+ # this object to JSON. |
+ source_data = self._parse_source_data(api_methods) |
+ self['api_calls'] = source_data['api_calls'] |
+ self['source_files'] = source_data['source_files'] |
+ self['source_hash'] = source_data['source_hash'] |
- self['api_calls'] = self._parse_api_calls(api_methods) |
self['name'] = self._parse_name() |
self['description'] = self._parse_description() |
self['icon'] = self._parse_icon() |
@@ -275,7 +285,6 @@ class Sample(dict): |
self['protocols'] = self._parse_protocols() |
self['path'] = self._get_relative_path() |
self['search_string'] = self._get_search_string() |
- self['source_files'] = self._parse_source_files() |
self['id'] = hashlib.sha1(self['path']).hexdigest() |
self['zip_path'] = self._get_relative_zip_path() |
@@ -421,62 +430,6 @@ class Sample(dict): |
sample_dirname = os.path.basename(sample_path) |
return "%s.zip" % sample_dirname |
- def _parse_api_calls(self, api_methods): |
- """ Returns a list of Chrome extension API calls the sample makes. |
- |
- Parses any *.html and *.js files in the sample directory and matches them |
- against the supplied list of all available API methods, returning methods |
- which show up in the sample code. |
- |
- Args: |
- api_methods: A list of strings containing the potential |
- API calls the and the extension sample could be making. |
- |
- Returns: |
- A set of every member of api_methods that appears in any *.html or *.js |
- file contained in this sample's directory (or subdirectories). |
- |
- Raises: |
- Exception: If any of the *.html or *.js files cannot be read. |
- """ |
- api_calls = set() |
- extension_dir_path = os.path.dirname(self._manifest_path) |
- for root, dirs, files in sorted_walk(extension_dir_path): |
- for file in files: |
- if file[-5:] == '.html' or file[-3:] == '.js': |
- path = os.path.join(root, file) |
- try: |
- code_file = open(path, "r") |
- except IOError, msg: |
- raise Exception("Failed to read %s: %s" % (path, msg)) |
- code_contents = unicode(code_file.read(), errors="replace") |
- code_file.close() |
- |
- for method in api_methods: |
- if (code_contents.find(method) > -1): |
- api_calls.add(method) |
- return sorted(api_calls) |
- |
- def _parse_source_files(self): |
- """ Returns a list of paths to source files present in the extenion. |
- |
- Returns: |
- A list of paths relative to the manifest file directory. |
- """ |
- source_paths = [] |
- base_path = os.path.realpath(os.path.dirname(self._manifest_path)) |
- for root, directories, files in sorted_walk(base_path): |
- if '.svn' in directories: |
- directories.remove('.svn') # Don't go into SVN metadata directories |
- |
- for file_name in files: |
- ext = os.path.splitext(file_name)[1] |
- if ext in self._SOURCE_FILE_EXTENSIONS: |
- path = os.path.realpath(os.path.join(root, file_name)) |
- path = path.replace(base_path, '')[1:] |
- source_paths.append(path) |
- return sorted(source_paths) |
- |
def _parse_description(self): |
""" Returns a localized description of the extension. |
@@ -577,6 +530,66 @@ class Sample(dict): |
protocols.append(split[0] + "://") |
return protocols |
+ def _parse_source_data(self, api_methods): |
+ """ Iterates over the sample's source files and parses data from them. |
+ |
+ Parses any files in the sample directory with known source extensions |
+ (as defined in self._SOURCE_FILE_EXTENSIONS). For each file, this method: |
+ |
+ 1. Stores a relative path from the manifest.json directory to the file. |
+ 2. Searches through the contents of the file for chrome.* API calls. |
+ 3. Calculates a SHA1 digest for the contents of the file. |
+ |
+ Args: |
+ api_methods: A list of strings containing the potential |
+ API calls the and the extension sample could be making. |
+ |
+ Raises: |
+ Exception: If any of the source files cannot be read. |
+ |
+ Returns: |
+ A dictionary containing the keys/values: |
+ 'api_calls' A sorted list of API calls the sample makes. |
+ 'source_files' A sorted list of paths to files the extension uses. |
+ 'source_hash' A hash of the individual file hashes. |
+ """ |
+ data = {} |
+ source_paths = [] |
+ source_hashes = [] |
+ api_calls = set() |
+ base_path = os.path.realpath(os.path.dirname(self._manifest_path)) |
+ for root, directories, files in sorted_walk(base_path): |
+ if '.svn' in directories: |
+ directories.remove('.svn') # Don't go into SVN metadata directories |
+ |
+ for file_name in files: |
+ ext = os.path.splitext(file_name)[1] |
+ if ext in self._SOURCE_FILE_EXTENSIONS: |
+ # Add the file path to the list of source paths. |
+ fullpath = os.path.realpath(os.path.join(root, file_name)) |
+ path = fullpath.replace(base_path, '')[1:] |
+ source_paths.append(path) |
+ |
+ # Read the contents and parse out API calls. |
+ try: |
+ code_file = open(fullpath, "r") |
+ except IOError, msg: |
+ raise Exception("Failed to read %s: %s" % (fullpath, msg)) |
+ code_contents = unicode(code_file.read(), errors="replace") |
+ code_file.close() |
+ for method in api_methods: |
+ if (code_contents.find(method) > -1): |
+ api_calls.add(method) |
+ |
+ # Get a hash of the file contents for zip file generation. |
+ hash = hashlib.sha1(code_contents.encode("ascii", "replace")) |
+ source_hashes.append(hash.hexdigest()) |
+ |
+ data['api_calls'] = sorted(api_calls) |
+ data['source_files'] = sorted(source_paths) |
+ data['source_hash'] = hashlib.sha1(''.join(source_hashes)).hexdigest() |
+ return data |
+ |
def _uses_background(self): |
""" Returns true if the extension defines a background page. """ |
return self._manifest.has_key('background_page') |
@@ -618,6 +631,26 @@ class Sample(dict): |
zip_filename = self._get_zip_filename() |
zip_path = os.path.join(sample_parentpath, zip_filename) |
+ zip_manifest_path = os.path.join(sample_dirname, 'manifest.json') |
+ |
+ zipfile.ZipFile.debug = 3 |
+ |
+ if os.path.isfile(zip_path): |
+ try: |
+ old_zip_file = zipfile.ZipFile(zip_path, 'r') |
+ except IOError, msg: |
+ raise Exception("Could not read zip at %s: %s" % (zip_path, msg)) |
+ |
+ try: |
+ info = old_zip_file.getinfo(zip_manifest_path) |
+ hash = info.comment |
+ if hash == self['source_hash']: |
+ return None # Hashes match - no need to generate file |
+ except KeyError, msg: |
+ pass # The old zip file doesn't contain a hash - overwrite |
+ finally: |
+ old_zip_file.close() |
+ |
zip_file = zipfile.ZipFile(zip_path, 'w') |
try: |
@@ -629,8 +662,14 @@ class Sample(dict): |
abspath = os.path.realpath(os.path.join(root, file)) |
# Relative path to store the file in under the zip. |
relpath = sample_dirname + abspath.replace(sample_path, "") |
+ |
zip_file.write(abspath, relpath) |
+ if file == 'manifest.json': |
+ info = zip_file.getinfo(zip_manifest_path) |
+ info.comment = self['source_hash'] |
except RuntimeError, msg: |
- raise Exception("Could not write zip at " % zip_path) |
+ raise Exception("Could not write zip at %s: %s" % (zip_path, msg)) |
finally: |
zip_file.close() |
+ |
+ return self._get_relative_zip_path() |