// Copyright 2014 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.syntax; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.vfs.PathFragment; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Formattable; import java.util.Formatter; import java.util.IllegalFormatException; import java.util.List; import java.util.Map; import java.util.MissingFormatWidthException; import java.util.Set; /** * Utilities used by the evaluator. */ public abstract class EvalUtils { // TODO(bazel-team): Yet an other hack committed in the name of Skylark. One problem is that the // syntax package cannot depend on actions so we have to have this until Actions are immutable. // The other is that BuildConfigurations are technically not immutable but they cannot be modified // from Skylark. private static final ImmutableSet> quasiImmutableClasses; static { try { ImmutableSet.Builder> builder = ImmutableSet.builder(); builder.add(Class.forName("com.google.devtools.build.lib.actions.Action")); builder.add(Class.forName("com.google.devtools.build.lib.analysis.config.BuildConfiguration")); builder.add(Class.forName("com.google.devtools.build.lib.actions.Root")); quasiImmutableClasses = builder.build(); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } private EvalUtils() { } /** * @return true if the specified sequence is a tuple; false if it's a modifiable list. */ public static boolean isTuple(List l) { return isTuple(l.getClass()); } public static boolean isTuple(Class c) { Preconditions.checkState(List.class.isAssignableFrom(c)); return ImmutableList.class.isAssignableFrom(c); } /** * @return true if the specified value is immutable (suitable for use as a * dictionary key) according to the rules of the Build language. */ public static boolean isImmutable(Object o) { if (o instanceof Map || o instanceof Function || o instanceof FilesetEntry || o instanceof GlobList) { return false; } else if (o instanceof List) { return isTuple((List) o); // tuples are immutable, lists are not. } else { return true; // string/int } } /** * Returns true if the type is immutable in the skylark language. */ public static boolean isSkylarkImmutable(Class c) { if (c.isAnnotationPresent(Immutable.class)) { return true; } else if (c.equals(String.class) || c.equals(Integer.class) || c.equals(Boolean.class) || SkylarkList.class.isAssignableFrom(c) || ImmutableMap.class.isAssignableFrom(c) || NestedSet.class.isAssignableFrom(c)) { return true; } else { for (Class classObject : quasiImmutableClasses) { if (classObject.isAssignableFrom(c)) { return true; } } } return false; } /** * Returns a transitive superclass or interface implemented by c which is annotated * with SkylarkModule. Returns null if no such class or interface exists. */ @VisibleForTesting static Class getParentWithSkylarkModule(Class c) { if (c == null) { return null; } if (c.isAnnotationPresent(SkylarkModule.class)) { return c; } Class parent = getParentWithSkylarkModule(c.getSuperclass()); if (parent != null) { return parent; } for (Class ifparent : c.getInterfaces()) { ifparent = getParentWithSkylarkModule(ifparent); if (ifparent != null) { return ifparent; } } return null; } // TODO(bazel-team): move the following few type-related functions to SkylarkType /** * Return the Skylark-type of {@code c} * *

The result will be a type that Skylark understands and is either equal to {@code c} * or is a supertype of it. For example, all instances of (all subclasses of) SkylarkList * are considered to be SkylarkLists. * *

Skylark's type validation isn't equipped to deal with inheritance so we must tell it which * of the superclasses or interfaces of {@code c} is the one that matters for type compatibility. * * @param c a class * @return a super-class of c to be used in validation-time type inference. */ public static Class getSkylarkType(Class c) { if (ImmutableList.class.isAssignableFrom(c)) { return ImmutableList.class; } else if (List.class.isAssignableFrom(c)) { return List.class; } else if (SkylarkList.class.isAssignableFrom(c)) { return SkylarkList.class; } else if (Map.class.isAssignableFrom(c)) { return Map.class; } else if (NestedSet.class.isAssignableFrom(c)) { // This could be removed probably return NestedSet.class; } else if (Set.class.isAssignableFrom(c)) { return Set.class; } else { // TODO(bazel-team): also unify all implementations of ClassObject, // that we used to all print the same as "struct"? // // Check if one of the superclasses or implemented interfaces has the SkylarkModule // annotation. If yes return that class. Class parent = getParentWithSkylarkModule(c); if (parent != null) { return parent; } } return c; } /** * Returns a pretty name for the datatype of object 'o' in the Build language. */ public static String getDataTypeName(Object o) { return getDataTypeName(o, false); } /** * Returns a pretty name for the datatype of object {@code object} in Skylark * or the BUILD language, with full details if the {@code full} boolean is true. */ public static String getDataTypeName(Object object, boolean full) { Preconditions.checkNotNull(object); if (object instanceof SkylarkList) { SkylarkList list = (SkylarkList) object; if (list.isTuple()) { return "tuple"; } else { return "list" + (full ? " of " + list.getContentType() + "s" : ""); } } else if (object instanceof SkylarkNestedSet) { SkylarkNestedSet set = (SkylarkNestedSet) object; return "set" + (full ? " of " + set.getContentType() + "s" : ""); } else { return getDataTypeNameFromClass(object.getClass()); } } /** * Returns a pretty name for the datatype equivalent of class 'c' in the Build language. */ public static String getDataTypeNameFromClass(Class c) { if (c.equals(Object.class)) { return "unknown"; } else if (c.equals(String.class)) { return "string"; } else if (c.equals(Integer.class)) { return "int"; } else if (c.equals(Boolean.class)) { return "bool"; } else if (c.equals(Void.TYPE) || c.equals(Environment.NoneType.class)) { return "None"; } else if (List.class.isAssignableFrom(c)) { // NB: the capital here is a subtle way to distinguish java Tuple and java List // from native SkylarkList tuple and list. // TODO(bazel-team): refactor SkylarkList and use it everywhere. return isTuple(c) ? "Tuple" : "List"; } else if (GlobList.class.isAssignableFrom(c)) { return "glob list"; } else if (Map.class.isAssignableFrom(c)) { return "dict"; } else if (Function.class.isAssignableFrom(c)) { return "function"; } else if (c.equals(FilesetEntry.class)) { return "FilesetEntry"; } else if (c.equals(SelectorValue.class)) { return "select"; } else if (NestedSet.class.isAssignableFrom(c) || SkylarkNestedSet.class.isAssignableFrom(c)) { return "set"; } else if (ClassObject.SkylarkClassObject.class.isAssignableFrom(c)) { return "struct"; } else if (SkylarkList.class.isAssignableFrom(c)) { // TODO(bazel-team): Refactor the class hierarchy so we can distinguish list and tuple types. return "list"; } else if (c.isAnnotationPresent(SkylarkModule.class)) { SkylarkModule module = c.getAnnotation(SkylarkModule.class); return c.getAnnotation(SkylarkModule.class).name() + (module.namespace() ? " (a language module)" : ""); } else { if (c.getSimpleName().isEmpty()) { return c.getName(); } else { return c.getSimpleName(); } } } /** * Returns a sequence of the appropriate list/tuple datatype for 'seq', based on 'isTuple'. */ public static List makeSequence(List seq, boolean isTuple) { return isTuple ? ImmutableList.copyOf(seq) : seq; } /** * Print build-language value 'o' in display format into the specified buffer. */ public static void printValue(Object o, Appendable buffer) { // Exception-swallowing wrapper due to annoying Appendable interface. try { printValueX(o, buffer); } catch (IOException e) { throw new AssertionError(e); // can't happen } } private static void printValueX(Object o, Appendable buffer) throws IOException { if (o == null) { throw new NullPointerException(); // Java null is not a build language value. } else if (o instanceof String || o instanceof Integer || o instanceof Double) { buffer.append(o.toString()); } else if (o == Environment.NONE) { buffer.append("None"); } else if (o == Boolean.TRUE) { buffer.append("True"); } else if (o == Boolean.FALSE) { buffer.append("False"); } else if (o instanceof List) { List seq = (List) o; printList(seq, isImmutable(seq), buffer); } else if (o instanceof SkylarkList) { SkylarkList list = (SkylarkList) o; printList(list.toList(), list.isTuple(), buffer); } else if (o instanceof Map) { Map dict = (Map) o; printList(dict.entrySet(), "{", ", ", "}", null, buffer); } else if (o instanceof Map.Entry) { Map.Entry entry = (Map.Entry) o; prettyPrintValue(entry.getKey(), buffer); buffer.append(": "); prettyPrintValue(entry.getValue(), buffer); } else if (o instanceof SkylarkNestedSet) { SkylarkNestedSet set = (SkylarkNestedSet) o; buffer.append("set("); printList(set, "[", ", ", "]", null, buffer); Order order = set.getOrder(); if (order != Order.STABLE_ORDER) { buffer.append(", order = \"" + SkylarkNestedSet.orderString(order) + "\""); } buffer.append(")"); } else if (o instanceof Function) { Function func = (Function) o; buffer.append(""); } else if (o instanceof FilesetEntry) { FilesetEntry entry = (FilesetEntry) o; buffer.append("FilesetEntry(srcdir = "); prettyPrintValue(entry.getSrcLabel().toString(), buffer); buffer.append(", files = "); prettyPrintValue(makeStringList(entry.getFiles()), buffer); buffer.append(", excludes = "); prettyPrintValue(makeList(entry.getExcludes()), buffer); buffer.append(", destdir = "); prettyPrintValue(entry.getDestDir().getPathString(), buffer); buffer.append(", strip_prefix = "); prettyPrintValue(entry.getStripPrefix(), buffer); buffer.append(", symlinks = \""); buffer.append(entry.getSymlinkBehavior().toString()); buffer.append("\")"); } else if (o instanceof PathFragment) { buffer.append(((PathFragment) o).getPathString()); } else { buffer.append(o.toString()); } } private static void printList(Iterable list, String before, String separator, String after, String singletonTerminator, Appendable buffer) throws IOException { boolean printSeparator = false; // don't print the separator before the first element int len = 0; buffer.append(before); for (Object o : list) { if (printSeparator) { buffer.append(separator); } prettyPrintValue(o, buffer); printSeparator = true; len++; } if (singletonTerminator != null && len == 1) { buffer.append(singletonTerminator); } buffer.append(after); } private static void printList(Iterable list, boolean isTuple, Appendable buffer) throws IOException { if (isTuple) { printList(list, "(", ", ", ")", ",", buffer); } else { printList(list, "[", ", ", "]", null, buffer); } } private static List makeList(Collection list) { return list == null ? Lists.newArrayList() : Lists.newArrayList(list); } private static List makeStringList(List