// 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.rules; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.packages.SkylarkNativeModule; import com.google.devtools.build.lib.syntax.BaseFunction; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.EvaluationContext; import com.google.devtools.build.lib.syntax.MethodLibrary; import com.google.devtools.build.lib.syntax.SkylarkEnvironment; import com.google.devtools.build.lib.syntax.SkylarkModule; import com.google.devtools.build.lib.syntax.SkylarkSignature; import com.google.devtools.build.lib.syntax.ValidationEnvironment; import java.lang.reflect.Field; import java.util.Map; /** * A class to handle all Skylark modules, to create and setup Validation and regular Environments. */ // TODO(bazel-team): move that to the syntax package and // let each extension register itself in a static { } statement. public class SkylarkModules { /** * The list of built in Skylark modules. * Documentation is generated automatically for all these modules. * They are also registered with the {@link ValidationEnvironment} * and the {@link SkylarkEnvironment}. * Note that only functions with a {@link SkylarkSignature} annotations are handled properly. */ // TODO(bazel-team): find a more general, more automated way of registering classes and building // initial environments. And don't give syntax.Environment and packages.MethodLibrary a special // treatment, have them use the same registration mechanism as other classes currently below. public static final ImmutableList> MODULES = ImmutableList.of( SkylarkAttr.class, SkylarkCommandLine.class, SkylarkNativeModule.class, SkylarkRuleClassFunctions.class, SkylarkRuleImplementationFunctions.class); private static final ImmutableMap, ImmutableList> FUNCTION_MAP; private static final ImmutableMap OBJECTS; static { try { ImmutableMap.Builder, ImmutableList> functionMap = ImmutableMap.builder(); ImmutableMap.Builder objects = ImmutableMap.builder(); for (Class moduleClass : MODULES) { if (moduleClass.isAnnotationPresent(SkylarkModule.class)) { objects.put(moduleClass.getAnnotation(SkylarkModule.class).name(), moduleClass.newInstance()); } ImmutableList.Builder functions = ImmutableList.builder(); collectSkylarkFunctionsAndObjectsFromFields(moduleClass, functions, objects); functionMap.put(moduleClass, functions.build()); } FUNCTION_MAP = functionMap.build(); OBJECTS = objects.build(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } /** * Returns a new SkylarkEnvironment with the elements of the Skylark modules. */ public static SkylarkEnvironment getNewEnvironment( EventHandler eventHandler, String astFileContentHashCode) { SkylarkEnvironment env = new SkylarkEnvironment(eventHandler, astFileContentHashCode); setupEnvironment(env); return env; } @VisibleForTesting public static SkylarkEnvironment getNewEnvironment(EventHandler eventHandler) { return getNewEnvironment(eventHandler, null); } private static void setupEnvironment(Environment env) { MethodLibrary.setupMethodEnvironment(env); for (Map.Entry, ImmutableList> entry : FUNCTION_MAP.entrySet()) { for (BaseFunction function : entry.getValue()) { if (function.getObjectType() != null) { env.registerFunction(function.getObjectType(), function.getName(), function); } else { env.update(function.getName(), function); } } } for (Map.Entry entry : OBJECTS.entrySet()) { env.update(entry.getKey(), entry.getValue()); } // Even though PACKAGE_NAME has no value _now_ and will be bound later, // it needs to be visible for the ValidationEnvironment to be happy. env.update(Environment.PKG_NAME, Environment.NONE); } /** * Returns a new ValidationEnvironment with the elements of the Skylark modules. */ public static ValidationEnvironment getValidationEnvironment() { // TODO(bazel-team): refactor constructors so we don't have those null-s return new ValidationEnvironment(getNewEnvironment(null)); } public static EvaluationContext newEvaluationContext(EventHandler eventHandler) { return EvaluationContext.newSkylarkContext( getNewEnvironment(eventHandler), getValidationEnvironment()); } /** * Collects the BaseFunctions from the fields of the class of the object parameter * and adds them into the builder. */ private static void collectSkylarkFunctionsAndObjectsFromFields(Class type, ImmutableList.Builder functions, ImmutableMap.Builder objects) { try { for (Field field : type.getDeclaredFields()) { if (field.isAnnotationPresent(SkylarkSignature.class)) { // Fields in Skylark modules are sometimes private. // Nevertheless they have to be annotated with SkylarkSignature. field.setAccessible(true); SkylarkSignature annotation = field.getAnnotation(SkylarkSignature.class); Object value = field.get(null); if (BaseFunction.class.isAssignableFrom(field.getType())) { functions.add((BaseFunction) value); } else { objects.put(annotation.name(), value); } } } } catch (IllegalArgumentException | IllegalAccessException e) { // This should never happen. throw new RuntimeException(e); } } }