1#!/usr/bin/env python 2# 3# Copyright 2014 The Chromium Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6""" 7Utilities for the modular DevTools build. 8""" 9 10import collections 11from os import path 12import os 13 14try: 15 import simplejson as json 16except ImportError: 17 import json 18 19 20def read_file(filename): 21 with open(path.normpath(filename), 'rt') as input: 22 return input.read() 23 24 25def write_file(filename, content): 26 if path.exists(filename): 27 os.remove(filename) 28 directory = path.dirname(filename) 29 if not path.exists(directory): 30 os.makedirs(directory) 31 with open(filename, 'wt') as output: 32 output.write(content) 33 34 35def bail_error(message): 36 raise Exception(message) 37 38 39def load_and_parse_json(filename): 40 try: 41 return json.loads(read_file(filename)) 42 except: 43 print 'ERROR: Failed to parse %s' % filename 44 raise 45 46 47def concatenate_scripts(file_names, module_dir, output_dir, output): 48 for file_name in file_names: 49 output.write('/* %s */\n' % file_name) 50 file_path = path.join(module_dir, file_name) 51 if not path.isfile(file_path): 52 file_path = path.join(output_dir, path.basename(module_dir), file_name) 53 output.write(read_file(file_path)) 54 output.write(';') 55 56 57class Descriptors: 58 59 def __init__(self, application_name, application_dir, application_descriptor, module_descriptors, extends, has_html, worker): 60 self.application_name = application_name 61 self.application_dir = application_dir 62 self.application = application_descriptor 63 self._cached_sorted_modules = None 64 self.modules = module_descriptors 65 self.extends = extends 66 self.has_html = has_html 67 self.worker = worker 68 69 def application_json(self): 70 result = dict() 71 result['modules'] = self.application.values() 72 result['has_html'] = self.has_html 73 return json.dumps(result) 74 75 def all_compiled_files(self): 76 files = collections.OrderedDict() 77 for name in self.sorted_modules(): 78 module = self.modules[name] 79 skipped_files = set(module.get('skip_compilation', [])) 80 for script in module.get('scripts', []) + module.get('modules', []): 81 if script not in skipped_files: 82 files[path.normpath(path.join(self.application_dir, name, script))] = True 83 return files.keys() 84 85 def all_skipped_compilation_files(self): 86 files = collections.OrderedDict() 87 for name in self.sorted_modules(): 88 module = self.modules[name] 89 skipped_files = set(module.get('skip_compilation', [])) 90 for script in skipped_files: 91 files[path.join(name, script)] = True 92 return files.keys() 93 94 def module_compiled_files(self, name): 95 files = [] 96 module = self.modules.get(name) 97 skipped_files = set(module.get('skip_compilation', [])) 98 for script in module.get('scripts', []): 99 if script not in skipped_files: 100 files.append(script) 101 return files 102 103 def module_resources(self, name): 104 return [name + '/' + resource for resource in self.modules[name].get('resources', [])] 105 106 def sorted_modules(self): 107 if self._cached_sorted_modules: 108 return self._cached_sorted_modules 109 110 result = [] 111 unvisited_modules = set(self.modules) 112 temp_modules = set() 113 114 def visit(parent, name): 115 if name not in unvisited_modules: 116 return None 117 if name not in self.modules: 118 return (parent, name) 119 if name in temp_modules: 120 bail_error('Dependency cycle found at module "%s"' % name) 121 temp_modules.add(name) 122 deps = self.modules[name].get('dependencies') 123 if deps: 124 for dep_name in deps: 125 bad_dep = visit(name, dep_name) 126 if bad_dep: 127 return bad_dep 128 unvisited_modules.remove(name) 129 temp_modules.remove(name) 130 result.append(name) 131 return None 132 133 while len(unvisited_modules): 134 for next in unvisited_modules: 135 break 136 failure = visit(None, next) 137 if failure: 138 # failure[0] can never be None 139 bail_error('Unknown module "%s" encountered in dependencies of "%s"' % (failure[1], failure[0])) 140 141 self._cached_sorted_modules = result 142 return result 143 144 def sorted_dependencies_closure(self, module_name): 145 visited = set() 146 147 def sorted_deps_for_module(name): 148 result = [] 149 desc = self.modules[name] 150 deps = desc.get('dependencies', []) 151 for dep in deps: 152 result += sorted_deps_for_module(dep) 153 if name not in visited: 154 result.append(name) 155 visited.add(name) 156 return result 157 158 return sorted_deps_for_module(module_name) 159 160 161class DescriptorLoader: 162 163 def __init__(self, application_dir): 164 self.application_dir = application_dir 165 166 def load_application(self, application_descriptor_name): 167 all_module_descriptors = {} 168 result = self._load_application(application_descriptor_name, all_module_descriptors) 169 return result 170 171 def load_applications(self, application_descriptor_names): 172 all_module_descriptors = {} 173 all_application_descriptors = {} 174 for application_descriptor_name in application_descriptor_names: 175 descriptors = {} 176 result = self._load_application(application_descriptor_name, descriptors) 177 for name in descriptors: 178 all_module_descriptors[name] = descriptors[name] 179 for name in result.application: 180 all_application_descriptors[name] = result.application[name] 181 return Descriptors('all', self.application_dir, all_application_descriptors, all_module_descriptors, None, False, False) 182 183 def _load_application(self, application_descriptor_name, all_module_descriptors): 184 module_descriptors = {} 185 application_descriptor_filename = path.join(self.application_dir, application_descriptor_name + '.json') 186 descriptor_json = load_and_parse_json(application_descriptor_filename) 187 application_descriptor = {desc['name']: desc for desc in descriptor_json['modules']} 188 extends = descriptor_json['extends'] if 'extends' in descriptor_json else None 189 if extends: 190 extends = self._load_application(extends, all_module_descriptors) 191 has_html = True if 'has_html' in descriptor_json and descriptor_json['has_html'] else False 192 worker = True if 'worker' in descriptor_json and descriptor_json['worker'] else False 193 194 for (module_name, module) in application_descriptor.items(): 195 if all_module_descriptors.get(module_name): 196 bail_error('Duplicate definition of module "%s" in %s' % (module_name, application_descriptor_filename)) 197 module_descriptors[module_name] = self._read_module_descriptor(module_name, application_descriptor_filename) 198 all_module_descriptors[module_name] = module_descriptors[module_name] 199 200 for module in module_descriptors.values(): 201 for dep in module.get('dependencies', []): 202 if dep not in all_module_descriptors: 203 bail_error('Module "%s" (dependency of "%s") not listed in application descriptor %s' % 204 (dep, module['name'], application_descriptor_filename)) 205 206 return Descriptors(application_descriptor_name, self.application_dir, application_descriptor, module_descriptors, extends, 207 has_html, worker) 208 209 def _read_module_descriptor(self, module_name, application_descriptor_filename): 210 json_filename = path.join(self.application_dir, module_name, 'module.json') 211 if not path.exists(json_filename): 212 bail_error('Module descriptor %s referenced in %s is missing' % (json_filename, application_descriptor_filename)) 213 module_json = load_and_parse_json(json_filename) 214 module_json['name'] = module_name 215 return module_json 216