Loading core/clear_vars.mk +1 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ LOCAL_INTERMEDIATE_SOURCES:= LOCAL_INTERMEDIATE_SOURCE_DIR:= LOCAL_JAVACFLAGS:= LOCAL_JAVA_LIBRARIES:= LOCAL_JAVA_LAYERS_FILE:= LOCAL_NO_STANDARD_LIBRARIES:= LOCAL_CLASSPATH:= LOCAL_DROIDDOC_USE_STANDARD_DOCLET:= Loading core/definitions.mk +2 −0 Original line number Diff line number Diff line Loading @@ -1455,6 +1455,8 @@ $(hide) $(1) -encoding UTF-8 \ $(PRIVATE_JAVACFLAGS) \ \@$(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list-uniq \ || ( rm -rf $(PRIVATE_CLASS_INTERMEDIATES_DIR) ; exit 41 ) $(if $(PRIVATE_JAVA_LAYERS_FILE), $(hide) build/tools/java-layers.py \ $(PRIVATE_JAVA_LAYERS_FILE) \@$(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list-uniq,) $(hide) rm -f $(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list $(hide) rm -f $(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list-uniq $(hide) jar $(if $(strip $(PRIVATE_JAR_MANIFEST)),-cfm,-cf) \ Loading core/java.mk +8 −2 Original line number Diff line number Diff line Loading @@ -238,12 +238,18 @@ $(full_classes_stubs_jar) : $(LOCAL_BUILT_MODULE) | $(ACP) $(hide) $(ACP) -fp $(PRIVATE_SOURCE_FILE) $@ ALL_MODULES.$(LOCAL_MODULE).STUBS := $(full_classes_stubs_jar) # The layers file allows you to enforce a layering between java packages. # Run build/tools/java-layers.py for more details. layers_file := $(addprefix $(LOCAL_PATH)/, $(LOCAL_JAVA_LAYERS_FILE)) $(full_classes_compiled_jar): PRIVATE_JAVA_LAYERS_FILE := $(layers_file) # Compile the java files to a .jar file. # This intentionally depends on java_sources, not all_java_sources. # Deps for generated source files must be handled separately, # via deps on the target that generates the sources. $(full_classes_compiled_jar): PRIVATE_JAVACFLAGS := $(LOCAL_JAVACFLAGS) $(full_classes_compiled_jar): $(java_sources) $(java_resource_sources) $(full_java_lib_deps) $(jar_manifest_file) \ $(full_classes_compiled_jar): $(java_sources) $(java_resource_sources) $(full_java_lib_deps)\ $(jar_manifest_file) $(layers_file) \ $(RenderScript_file_stamp) $(proto_java_sources_file_stamp) $(transform-java-to-classes.jar) Loading tools/java-layers.py 0 → 100755 +257 −0 Original line number Diff line number Diff line #!/usr/bin/env python import os import re import sys def fail_with_usage(): sys.stderr.write("usage: java-layers.py DEPENDENCY_FILE SOURCE_DIRECTORIES...\n") sys.stderr.write("\n") sys.stderr.write("Enforces layering between java packages. Scans\n") sys.stderr.write("DIRECTORY and prints errors when the packages violate\n") sys.stderr.write("the rules defined in the DEPENDENCY_FILE.\n") sys.stderr.write("\n") sys.stderr.write("Prints a warning when an unknown package is encountered\n") sys.stderr.write("on the assumption that it should fit somewhere into the\n") sys.stderr.write("layering.\n") sys.stderr.write("\n") sys.stderr.write("DEPENDENCY_FILE format\n") sys.stderr.write(" - # starts comment\n") sys.stderr.write(" - Lines consisting of two java package names: The\n") sys.stderr.write(" first package listed must not contain any references\n") sys.stderr.write(" to any classes present in the second package, or any\n") sys.stderr.write(" of its dependencies.\n") sys.stderr.write(" - Lines consisting of one java package name: The\n") sys.stderr.write(" packge is assumed to be a high level package and\n") sys.stderr.write(" nothing may depend on it.\n") sys.stderr.write(" - Lines consisting of a dash (+) followed by one java\n") sys.stderr.write(" package name: The package is considered a low level\n") sys.stderr.write(" package and may not import any of the other packages\n") sys.stderr.write(" listed in the dependency file.\n") sys.stderr.write(" - Lines consisting of a plus (-) followed by one java\n") sys.stderr.write(" package name: The package is considered \'legacy\'\n") sys.stderr.write(" and excluded from errors.\n") sys.stderr.write("\n") sys.exit(1) class Dependency: def __init__(self, filename, lineno, lower, top, lowlevel, legacy): self.filename = filename self.lineno = lineno self.lower = lower self.top = top self.lowlevel = lowlevel self.legacy = legacy self.uppers = [] self.transitive = set() def matches(self, imp): for d in self.transitive: if imp.startswith(d): return True return False class Dependencies: def __init__(self, deps): def recurse(obj, dep, visited): global err if dep in visited: sys.stderr.write("%s:%d: Circular dependency found:\n" % (dep.filename, dep.lineno)) for v in visited: sys.stderr.write("%s:%d: Dependency: %s\n" % (v.filename, v.lineno, v.lower)) err = True return visited.append(dep) for upper in dep.uppers: obj.transitive.add(upper) if upper in deps: recurse(obj, deps[upper], visited) self.deps = deps self.parts = [(dep.lower.split('.'),dep) for dep in deps.itervalues()] # transitive closure of dependencies for dep in deps.itervalues(): recurse(dep, dep, []) # disallow everything from the low level components for dep in deps.itervalues(): if dep.lowlevel: for d in deps.itervalues(): if dep != d and not d.legacy: dep.transitive.add(d.lower) # disallow the 'top' components everywhere but in their own package for dep in deps.itervalues(): if dep.top and not dep.legacy: for d in deps.itervalues(): if dep != d and not d.legacy: d.transitive.add(dep.lower) for dep in deps.itervalues(): dep.transitive = set([x+"." for x in dep.transitive]) if False: for dep in deps.itervalues(): print "-->", dep.lower, "-->", dep.transitive # Lookup the dep object for the given package. If pkg is a subpackage # of one with a rule, that one will be returned. If no matches are found, # None is returned. def lookup(self, pkg): # Returns the number of parts that match def compare_parts(parts, pkg): if len(parts) > len(pkg): return 0 n = 0 for i in range(0, len(parts)): if parts[i] != pkg[i]: return 0 n = n + 1 return n pkg = pkg.split(".") matched = 0 result = None for (parts,dep) in self.parts: x = compare_parts(parts, pkg) if x > matched: matched = x result = dep return result def parse_dependency_file(filename): global err f = file(filename) lines = f.readlines() f.close() def lineno(s, i): i[0] = i[0] + 1 return (i[0],s) n = [0] lines = [lineno(x,n) for x in lines] lines = [(n,s.split("#")[0].strip()) for (n,s) in lines] lines = [(n,s) for (n,s) in lines if len(s) > 0] lines = [(n,s.split()) for (n,s) in lines] deps = {} for n,words in lines: if len(words) == 1: lower = words[0] top = True legacy = False lowlevel = False if lower[0] == '+': lower = lower[1:] top = False lowlevel = True elif lower[0] == '-': lower = lower[1:] legacy = True if lower in deps: sys.stderr.write(("%s:%d: Package '%s' already defined on" + " line %d.\n") % (filename, n, lower, deps[lower].lineno)) err = True else: deps[lower] = Dependency(filename, n, lower, top, lowlevel, legacy) elif len(words) == 2: lower = words[0] upper = words[1] if lower in deps: dep = deps[lower] if dep.top: sys.stderr.write(("%s:%d: Can't add dependency to top level package " + "'%s'\n") % (filename, n, lower)) err = True else: dep = Dependency(filename, n, lower, False, False, False) deps[lower] = dep dep.uppers.append(upper) else: sys.stderr.write("%s:%d: Too many words on line starting at \'%s\'\n" % ( filename, n, words[2])) err = True return Dependencies(deps) def find_java_files(srcs): result = [] for d in srcs: if d[0] == '@': f = file(d[1:]) result.extend([fn for fn in [s.strip() for s in f.readlines()] if len(fn) != 0]) f.close() else: for root, dirs, files in os.walk(d): result.extend([os.sep.join((root,f)) for f in files if f.lower().endswith(".java")]) return result COMMENTS = re.compile("//.*?\n|/\*.*?\*/", re.S) PACKAGE = re.compile("package\s+(.*)") IMPORT = re.compile("import\s+(.*)") def examine_java_file(deps, filename): global err # Yes, this is a crappy java parser. Write a better one if you want to. f = file(filename) text = f.read() f.close() text = COMMENTS.sub("", text) index = text.find("{") if index < 0: sys.stderr.write(("%s: Error: Unable to parse java. Can't find class " + "declaration.\n") % filename) err = True return text = text[0:index] statements = [s.strip() for s in text.split(";")] # First comes the package declaration. Then iterate while we see import # statements. Anything else is either bad syntax that we don't care about # because the compiler will fail, or the beginning of the class declaration. m = PACKAGE.match(statements[0]) if not m: sys.stderr.write(("%s: Error: Unable to parse java. Missing package " + "statement.\n") % filename) err = True return pkg = m.group(1) imports = [] for statement in statements[1:]: m = IMPORT.match(statement) if not m: break imports.append(m.group(1)) # Do the checking if False: print filename print "'%s' --> %s" % (pkg, imports) dep = deps.lookup(pkg) if not dep: sys.stderr.write(("%s: Error: Package does not appear in dependency file: " + "%s\n") % (filename, pkg)) err = True return for imp in imports: if dep.matches(imp): sys.stderr.write("%s: Illegal import in package '%s' of '%s'\n" % (filename, pkg, imp)) err = True err = False def main(argv): if len(argv) < 3: fail_with_usage() deps = parse_dependency_file(argv[1]) if err: sys.exit(1) java = find_java_files(argv[2:]) for filename in java: examine_java_file(deps, filename) if err: sys.stderr.write("%s: Using this file as dependency file.\n" % argv[1]) sys.exit(1) sys.exit(0) if __name__ == "__main__": main(sys.argv) Loading
core/clear_vars.mk +1 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ LOCAL_INTERMEDIATE_SOURCES:= LOCAL_INTERMEDIATE_SOURCE_DIR:= LOCAL_JAVACFLAGS:= LOCAL_JAVA_LIBRARIES:= LOCAL_JAVA_LAYERS_FILE:= LOCAL_NO_STANDARD_LIBRARIES:= LOCAL_CLASSPATH:= LOCAL_DROIDDOC_USE_STANDARD_DOCLET:= Loading
core/definitions.mk +2 −0 Original line number Diff line number Diff line Loading @@ -1455,6 +1455,8 @@ $(hide) $(1) -encoding UTF-8 \ $(PRIVATE_JAVACFLAGS) \ \@$(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list-uniq \ || ( rm -rf $(PRIVATE_CLASS_INTERMEDIATES_DIR) ; exit 41 ) $(if $(PRIVATE_JAVA_LAYERS_FILE), $(hide) build/tools/java-layers.py \ $(PRIVATE_JAVA_LAYERS_FILE) \@$(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list-uniq,) $(hide) rm -f $(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list $(hide) rm -f $(PRIVATE_CLASS_INTERMEDIATES_DIR)/java-source-list-uniq $(hide) jar $(if $(strip $(PRIVATE_JAR_MANIFEST)),-cfm,-cf) \ Loading
core/java.mk +8 −2 Original line number Diff line number Diff line Loading @@ -238,12 +238,18 @@ $(full_classes_stubs_jar) : $(LOCAL_BUILT_MODULE) | $(ACP) $(hide) $(ACP) -fp $(PRIVATE_SOURCE_FILE) $@ ALL_MODULES.$(LOCAL_MODULE).STUBS := $(full_classes_stubs_jar) # The layers file allows you to enforce a layering between java packages. # Run build/tools/java-layers.py for more details. layers_file := $(addprefix $(LOCAL_PATH)/, $(LOCAL_JAVA_LAYERS_FILE)) $(full_classes_compiled_jar): PRIVATE_JAVA_LAYERS_FILE := $(layers_file) # Compile the java files to a .jar file. # This intentionally depends on java_sources, not all_java_sources. # Deps for generated source files must be handled separately, # via deps on the target that generates the sources. $(full_classes_compiled_jar): PRIVATE_JAVACFLAGS := $(LOCAL_JAVACFLAGS) $(full_classes_compiled_jar): $(java_sources) $(java_resource_sources) $(full_java_lib_deps) $(jar_manifest_file) \ $(full_classes_compiled_jar): $(java_sources) $(java_resource_sources) $(full_java_lib_deps)\ $(jar_manifest_file) $(layers_file) \ $(RenderScript_file_stamp) $(proto_java_sources_file_stamp) $(transform-java-to-classes.jar) Loading
tools/java-layers.py 0 → 100755 +257 −0 Original line number Diff line number Diff line #!/usr/bin/env python import os import re import sys def fail_with_usage(): sys.stderr.write("usage: java-layers.py DEPENDENCY_FILE SOURCE_DIRECTORIES...\n") sys.stderr.write("\n") sys.stderr.write("Enforces layering between java packages. Scans\n") sys.stderr.write("DIRECTORY and prints errors when the packages violate\n") sys.stderr.write("the rules defined in the DEPENDENCY_FILE.\n") sys.stderr.write("\n") sys.stderr.write("Prints a warning when an unknown package is encountered\n") sys.stderr.write("on the assumption that it should fit somewhere into the\n") sys.stderr.write("layering.\n") sys.stderr.write("\n") sys.stderr.write("DEPENDENCY_FILE format\n") sys.stderr.write(" - # starts comment\n") sys.stderr.write(" - Lines consisting of two java package names: The\n") sys.stderr.write(" first package listed must not contain any references\n") sys.stderr.write(" to any classes present in the second package, or any\n") sys.stderr.write(" of its dependencies.\n") sys.stderr.write(" - Lines consisting of one java package name: The\n") sys.stderr.write(" packge is assumed to be a high level package and\n") sys.stderr.write(" nothing may depend on it.\n") sys.stderr.write(" - Lines consisting of a dash (+) followed by one java\n") sys.stderr.write(" package name: The package is considered a low level\n") sys.stderr.write(" package and may not import any of the other packages\n") sys.stderr.write(" listed in the dependency file.\n") sys.stderr.write(" - Lines consisting of a plus (-) followed by one java\n") sys.stderr.write(" package name: The package is considered \'legacy\'\n") sys.stderr.write(" and excluded from errors.\n") sys.stderr.write("\n") sys.exit(1) class Dependency: def __init__(self, filename, lineno, lower, top, lowlevel, legacy): self.filename = filename self.lineno = lineno self.lower = lower self.top = top self.lowlevel = lowlevel self.legacy = legacy self.uppers = [] self.transitive = set() def matches(self, imp): for d in self.transitive: if imp.startswith(d): return True return False class Dependencies: def __init__(self, deps): def recurse(obj, dep, visited): global err if dep in visited: sys.stderr.write("%s:%d: Circular dependency found:\n" % (dep.filename, dep.lineno)) for v in visited: sys.stderr.write("%s:%d: Dependency: %s\n" % (v.filename, v.lineno, v.lower)) err = True return visited.append(dep) for upper in dep.uppers: obj.transitive.add(upper) if upper in deps: recurse(obj, deps[upper], visited) self.deps = deps self.parts = [(dep.lower.split('.'),dep) for dep in deps.itervalues()] # transitive closure of dependencies for dep in deps.itervalues(): recurse(dep, dep, []) # disallow everything from the low level components for dep in deps.itervalues(): if dep.lowlevel: for d in deps.itervalues(): if dep != d and not d.legacy: dep.transitive.add(d.lower) # disallow the 'top' components everywhere but in their own package for dep in deps.itervalues(): if dep.top and not dep.legacy: for d in deps.itervalues(): if dep != d and not d.legacy: d.transitive.add(dep.lower) for dep in deps.itervalues(): dep.transitive = set([x+"." for x in dep.transitive]) if False: for dep in deps.itervalues(): print "-->", dep.lower, "-->", dep.transitive # Lookup the dep object for the given package. If pkg is a subpackage # of one with a rule, that one will be returned. If no matches are found, # None is returned. def lookup(self, pkg): # Returns the number of parts that match def compare_parts(parts, pkg): if len(parts) > len(pkg): return 0 n = 0 for i in range(0, len(parts)): if parts[i] != pkg[i]: return 0 n = n + 1 return n pkg = pkg.split(".") matched = 0 result = None for (parts,dep) in self.parts: x = compare_parts(parts, pkg) if x > matched: matched = x result = dep return result def parse_dependency_file(filename): global err f = file(filename) lines = f.readlines() f.close() def lineno(s, i): i[0] = i[0] + 1 return (i[0],s) n = [0] lines = [lineno(x,n) for x in lines] lines = [(n,s.split("#")[0].strip()) for (n,s) in lines] lines = [(n,s) for (n,s) in lines if len(s) > 0] lines = [(n,s.split()) for (n,s) in lines] deps = {} for n,words in lines: if len(words) == 1: lower = words[0] top = True legacy = False lowlevel = False if lower[0] == '+': lower = lower[1:] top = False lowlevel = True elif lower[0] == '-': lower = lower[1:] legacy = True if lower in deps: sys.stderr.write(("%s:%d: Package '%s' already defined on" + " line %d.\n") % (filename, n, lower, deps[lower].lineno)) err = True else: deps[lower] = Dependency(filename, n, lower, top, lowlevel, legacy) elif len(words) == 2: lower = words[0] upper = words[1] if lower in deps: dep = deps[lower] if dep.top: sys.stderr.write(("%s:%d: Can't add dependency to top level package " + "'%s'\n") % (filename, n, lower)) err = True else: dep = Dependency(filename, n, lower, False, False, False) deps[lower] = dep dep.uppers.append(upper) else: sys.stderr.write("%s:%d: Too many words on line starting at \'%s\'\n" % ( filename, n, words[2])) err = True return Dependencies(deps) def find_java_files(srcs): result = [] for d in srcs: if d[0] == '@': f = file(d[1:]) result.extend([fn for fn in [s.strip() for s in f.readlines()] if len(fn) != 0]) f.close() else: for root, dirs, files in os.walk(d): result.extend([os.sep.join((root,f)) for f in files if f.lower().endswith(".java")]) return result COMMENTS = re.compile("//.*?\n|/\*.*?\*/", re.S) PACKAGE = re.compile("package\s+(.*)") IMPORT = re.compile("import\s+(.*)") def examine_java_file(deps, filename): global err # Yes, this is a crappy java parser. Write a better one if you want to. f = file(filename) text = f.read() f.close() text = COMMENTS.sub("", text) index = text.find("{") if index < 0: sys.stderr.write(("%s: Error: Unable to parse java. Can't find class " + "declaration.\n") % filename) err = True return text = text[0:index] statements = [s.strip() for s in text.split(";")] # First comes the package declaration. Then iterate while we see import # statements. Anything else is either bad syntax that we don't care about # because the compiler will fail, or the beginning of the class declaration. m = PACKAGE.match(statements[0]) if not m: sys.stderr.write(("%s: Error: Unable to parse java. Missing package " + "statement.\n") % filename) err = True return pkg = m.group(1) imports = [] for statement in statements[1:]: m = IMPORT.match(statement) if not m: break imports.append(m.group(1)) # Do the checking if False: print filename print "'%s' --> %s" % (pkg, imports) dep = deps.lookup(pkg) if not dep: sys.stderr.write(("%s: Error: Package does not appear in dependency file: " + "%s\n") % (filename, pkg)) err = True return for imp in imports: if dep.matches(imp): sys.stderr.write("%s: Illegal import in package '%s' of '%s'\n" % (filename, pkg, imp)) err = True err = False def main(argv): if len(argv) < 3: fail_with_usage() deps = parse_dependency_file(argv[1]) if err: sys.exit(1) java = find_java_files(argv[2:]) for filename in java: examine_java_file(deps, filename) if err: sys.stderr.write("%s: Using this file as dependency file.\n" % argv[1]) sys.exit(1) sys.exit(0) if __name__ == "__main__": main(sys.argv)