Add 'ctx.actions' and implement 'ctx.action.declare_file'.

RELNOTES: None.
PiperOrigin-RevId: 160264501
diff --git a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
index 74c7682..4a956db 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
@@ -99,8 +99,8 @@
         + "Files. If you have a Skylark rule that needs to create a new File, you might need to "
         + "add the label to the attrs (if it's an input) or the outputs (if it's an output). Then "
         + "you can access the File through the rule's <a href='ctx.html'>context</a>. You can "
-        + "also use <a href='ctx.html#new_file'>ctx.new_file</a> to create a new file in the rule "
-        + "implementation.</p>")
+        + "also use <a href='actions.html#declare_file'>ctx.actions.declare_file</a> to "
+        + "declare a new file in the rule implementation.</p>")
 public class Artifact
     implements FileType.HasFilename, ActionInput, SkylarkValue, Comparable<Object> {
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkActionFactory.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkActionFactory.java
new file mode 100644
index 0000000..6157392
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkActionFactory.java
@@ -0,0 +1,110 @@
+// Copyright 2017 The Bazel Authors. 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.skylarkinterface.Param;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.Printer;
+import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+/**
+ * Provides a Skylark interface for all action creation needs.
+ */
+@SkylarkModule(
+    name = "actions",
+    category = SkylarkModuleCategory.BUILTIN,
+    doc = "Module providing functions to create actions."
+)
+
+public class SkylarkActionFactory implements SkylarkValue {
+  private final SkylarkRuleContext context;
+  private RuleContext ruleContext;
+
+
+  public SkylarkActionFactory(SkylarkRuleContext context, RuleContext ruleContext) {
+    this.context = context;
+    this.ruleContext = ruleContext;
+  }
+
+  Root newFileRoot() throws EvalException {
+    return context.isForAspect()
+        ? ruleContext.getConfiguration().getBinDirectory(ruleContext.getRule().getRepository())
+        : ruleContext.getBinOrGenfilesDirectory();
+  }
+
+
+  @SkylarkCallable(
+      name = "declare_file",
+      doc =
+          "Declares that rule or aspect creates a file with the given filename. "
+              + "If <code>sibling</code> is not specified, file name is relative to "
+              + "package directory, otherwise the file is in the same directory as "
+              + "<code>sibling</code>. "
+              + "You must create an action that generates the file. <br>"
+              + "Files that are specified in rule's outputs do not need to be declared and are "
+              + "available through <a href=\"ctx.html#outputs\">ctx.outputs</a>.",
+      parameters = {
+          @Param(
+              name = "filename",
+              type = String.class,
+              doc =
+                  "If no 'sibling' provided, path of the new file, relative "
+                  + "to the current package. Otherwise a base name for a file "
+                   + "('sibling' determines a directory)."
+          ),
+          @Param(
+              name = "sibling",
+              doc = "A file that lives in the same directory as the newly created file.",
+              type = Artifact.class,
+              noneable = true,
+              positional = false,
+              named = true,
+              defaultValue = "None"
+          )
+      }
+  )
+  public Artifact declareFile(String filename, Object sibling) throws EvalException {
+    context.checkMutable("actions.declareFile");
+    if (Runtime.NONE.equals(sibling)) {
+      return ruleContext.getPackageRelativeArtifact(filename, newFileRoot());
+    } else {
+      PathFragment original = ((Artifact) sibling).getRootRelativePath();
+      PathFragment fragment = original.replaceName(filename);
+      return ruleContext.getDerivedArtifact(fragment, newFileRoot());
+    }
+  }
+
+  @Override
+  public boolean isImmutable() {
+    return context.isImmutable();
+  }
+
+  @Override
+  public void write(Appendable buffer, char quotationMark) {
+    Printer.append(buffer, "actions for");
+    context.write(buffer, quotationMark);
+  }
+
+  void nullify() {
+    ruleContext = null;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
index d85a2ec..8fee2dd 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
@@ -169,6 +169,8 @@
   // after this object has been nullified.
   private final String ruleLabelCanonicalName;
 
+  private final SkylarkActionFactory actionFactory;
+
   // The fields below intended to be final except that they can be cleared by calling `nullify()`
   // when the object becomes featureless.
   private RuleContext ruleContext;
@@ -195,6 +197,7 @@
   public SkylarkRuleContext(RuleContext ruleContext,
       @Nullable AspectDescriptor aspectDescriptor)
       throws EvalException, InterruptedException {
+    this.actionFactory = new SkylarkActionFactory(this, ruleContext);
     this.ruleContext = Preconditions.checkNotNull(ruleContext);
     this.ruleLabelCanonicalName = ruleContext.getLabel().getCanonicalForm();
     this.fragments = new FragmentCollection(ruleContext, ConfigurationTransition.NONE);
@@ -293,6 +296,7 @@
    * rule implementation function has exited).
    */
   public void nullify() {
+    actionFactory.nullify();
     ruleContext = null;
     fragments = null;
     hostFragments = null;
@@ -611,6 +615,15 @@
     return DefaultProvider.SKYLARK_CONSTRUCTOR;
   }
 
+  @SkylarkCallable(
+      name = "actions",
+      structField = true,
+      doc = "Functions to declare files and create actions."
+  )
+  public SkylarkActionFactory actions() {
+    return actionFactory;
+  }
+
   @SkylarkCallable(name = "created_actions",
       doc = "For rules with <a href=\"globals.html#rule._skylark_testable\">_skylark_testable"
           + "</a> set to <code>True</code>, this returns an "
@@ -867,7 +880,7 @@
     }
   }
 
-  private boolean isForAspect() {
+  boolean isForAspect() {
     return ruleAttributesCollection != null;
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java b/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java
index 6b36afc68..cd2c949 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java
@@ -1304,7 +1304,7 @@
         "    attrs = { 'deps' : attr.label_list(aspects = [aspect1]) })",
         "",
         "def _action_rule_impl(ctx):",
-        "  out = ctx.new_file(ctx.label.name)",
+        "  out = ctx.actions.declare_file(ctx.label.name)",
         "  ctx.action(outputs = [out], command = 'dontcare', mnemonic='Mnemonic')",
         "  return struct()",
         "action_rule = rule(_action_rule_impl, attrs = { 'deps' : attr.label_list() })");
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java
index a39cf85..174e050 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTest.java
@@ -493,7 +493,7 @@
     scratch.file(
         "a/def.bzl",
         "def _aspect_impl(target, ctx):",
-        "  f = ctx.new_file('foo.txt')",
+        "  f = ctx.actions.declare_file('foo.txt')",
         "  ctx.action(outputs = [f], command = 'echo foo > \"$1\"')",
         "  return struct(output=f)",
         "def _rule_impl(ctx):",
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
index 8609c52..4b3740b 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
@@ -866,7 +866,7 @@
     scratch.file(
         "test/aspect.bzl",
         "def _impl(target, ctx):",
-        "  ctx.new_file('missing_in_action.txt')",
+        "  ctx.actions.declare_file('missing_in_action.txt')",
         "  return struct()",
         "",
         "MyAspect = aspect(implementation=_impl)");
@@ -911,13 +911,13 @@
     scratch.file(
         "test/aspect.bzl",
         "def _impl(target, ctx):",
-        "  f = ctx.new_file('f.txt')",
+        "  f = ctx.actions.declare_file('f.txt')",
         "  ctx.file_action(f, 'f')",
         "  return struct(output_groups = { 'duplicate' : depset([f]) })",
         "",
         "MyAspect = aspect(implementation=_impl)",
         "def _rule_impl(ctx):",
-        "  g = ctx.new_file('g.txt')",
+        "  g = ctx.actions.declare_file('g.txt')",
         "  ctx.file_action(g, 'g')",
         "  return struct(output_groups = { 'duplicate' : depset([g]) })",
         "my_rule = rule(_rule_impl)",
@@ -947,7 +947,7 @@
     scratch.file(
         "test/aspect.bzl",
         "def _a1_impl(target, ctx):",
-        "  f = ctx.new_file(target.label.name + '_a1.txt')",
+        "  f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
         "  ctx.file_action(f, 'f')",
         "  return struct(output_groups = { 'a1_group' : depset([f]) })",
         "",
@@ -979,7 +979,7 @@
     scratch.file(
         "test/aspect.bzl",
         "def _a1_impl(target, ctx):",
-        "  f = ctx.new_file(target.label.name + '_a1.txt')",
+        "  f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
         "  ctx.file_action(f, 'f')",
         "  return [OutputGroupInfo(a1_group = depset([f]))]",
         "",
@@ -1010,7 +1010,7 @@
     scratch.file(
         "test/aspect.bzl",
         "def _a1_impl(target, ctx):",
-        "  f = ctx.new_file(target.label.name + '_a1.txt')",
+        "  f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
         "  ctx.file_action(f, 'f')",
         "  return struct(output_groups = { 'a1_group' : depset([f]) })",
         "",
@@ -1022,7 +1022,7 @@
         "  return struct(output_groups = og)",
         "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })",
         "def _a2_impl(target, ctx):",
-        "  g = ctx.new_file(target.label.name + '_a2.txt')",
+        "  g = ctx.actions.declare_file(target.label.name + '_a2.txt')",
         "  ctx.file_action(g, 'f')",
         "  return struct(output_groups = { 'a2_group' : depset([g]) })",
         "",
@@ -1051,7 +1051,7 @@
     scratch.file(
         "test/aspect.bzl",
         "def _a1_impl(target, ctx):",
-        "  f = ctx.new_file(target.label.name + '_a1.txt')",
+        "  f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
         "  ctx.file_action(f, 'f')",
         "  return [OutputGroupInfo(a1_group = depset([f]))]",
         "",
@@ -1068,7 +1068,7 @@
         "  return [OutputGroupInfo(**og)]",
         "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })",
         "def _a2_impl(target, ctx):",
-        "  g = ctx.new_file(target.label.name + '_a2.txt')",
+        "  g = ctx.actions.declare_file(target.label.name + '_a2.txt')",
         "  ctx.file_action(g, 'f')",
         "  return [OutputGroupInfo(a2_group = depset([g]))]",
         "",
@@ -1098,7 +1098,7 @@
     scratch.file(
         "test/aspect.bzl",
         "def _a1_impl(target, ctx):",
-        "  f = ctx.new_file(target.label.name + '_a1.txt')",
+        "  f = ctx.actions.declare_file(target.label.name + '_a1.txt')",
         "  ctx.file_action(f, 'f')",
         "  return struct(output_groups = { 'a1_group' : depset([f]) })",
         "",
@@ -1110,7 +1110,7 @@
         "  return struct(output_groups = og)",
         "my_rule1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1]) })",
         "def _a2_impl(target, ctx):",
-        "  g = ctx.new_file(target.label.name + '_a2.txt')",
+        "  g = ctx.actions.declare_file(target.label.name + '_a2.txt')",
         "  ctx.file_action(g, 'f')",
         "  return struct(output_groups = { 'a1_group' : depset([g]) })",
         "",
@@ -1647,7 +1647,7 @@
     scratch.file(
         "foo/extension.bzl",
         "def _aspect_impl(target, ctx):",
-        "   file = ctx.new_file('aspect-output-' + target.label.name)",
+        "   file = ctx.actions.declare_file('aspect-output-' + target.label.name)",
         "   ctx.file_action(file, 'data')",
         "   return struct(aspect_file = file)",
         "my_aspect = aspect(_aspect_impl)",
@@ -1768,7 +1768,7 @@
     scratch.file(
         "test/aspect.bzl",
         "def _aspect_impl(target,ctx):",
-        "  f = ctx.new_file('dummy.txt')",
+        "  f = ctx.actions.declare_file('dummy.txt')",
         "  ctx.action(outputs = [f], command='echo xxx > $(location f)', mnemonic='AspectAction')",
         "  return struct()",
         "my_aspect = aspect(implementation = _aspect_impl)"
@@ -1918,7 +1918,7 @@
         "",
         "def _a3_impl(target,ctx):",
         "  value = []",
-        "  f = ctx.new_file('a3.out')",
+        "  f = ctx.actions.declare_file('a3.out')",
         "  ctx.file_action(f, 'text')",
         "  for dep in ctx.rule.attr.deps:",
         "     if hasattr(dep, 'a3p'):",
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java
index c2010d5..0e636af 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java
@@ -1902,7 +1902,7 @@
           "def _aspect_impl(target, ctx):",
           "  if ctx.rule.attr.deps:",
           "    dep = ctx.rule.attr.deps[0]",
-          "    file = ctx.new_file('file.txt')",
+          "    file = ctx.actions.declare_file('file.txt')",
           "    foo = dep." + (attribute.startsWith("rule.") ? "" : "ctx.") + attribute,
           "  return struct(ctx = ctx, rule=ctx.rule)",
           "MyAspect = aspect(implementation=_aspect_impl)",
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
index 20a7084..c7bec75 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
@@ -471,8 +471,8 @@
     assertThat(parseFile("x[1::2]").toString()).isEqualTo("[x[1::2]\n]");
     assertThat(parseFile("x[1:]").toString()).isEqualTo("[x[1:]\n]");
     assertThat(parseFile("str[42]").toString()).isEqualTo("[str[42]\n]");
-    assertThat(parseFile("ctx.new_file('hello')").toString())
-        .isEqualTo("[ctx.new_file(\"hello\")\n]");
+    assertThat(parseFile("ctx.actions.declare_file('hello')").toString())
+        .isEqualTo("[ctx.actions.declare_file(\"hello\")\n]");
     assertThat(parseFile("new_file(\"hello\")").toString()).isEqualTo("[new_file(\"hello\")\n]");
   }
 
diff --git a/src/test/shell/bazel/bazel_java_test.sh b/src/test/shell/bazel/bazel_java_test.sh
index 509eb70..093bc70 100755
--- a/src/test/shell/bazel/bazel_java_test.sh
+++ b/src/test/shell/bazel/bazel_java_test.sh
@@ -173,7 +173,7 @@
       deps.append(dep[java_common.provider])
   deps_provider = java_common.merge(deps)
 
-  output_jar = ctx.new_file("lib" + ctx.label.name + ".jar")
+  output_jar = ctx.actions.declare_file("lib" + ctx.label.name + ".jar")
 
   compilation_provider = java_common.compile(
     ctx,
@@ -290,7 +290,7 @@
 
   cat >g/java_custom_library.bzl <<'EOF'
 def _impl(ctx):
-  output_jar = ctx.new_file("lib" + ctx.label.name + ".jar")
+  output_jar = ctx.actions.declare_file("lib" + ctx.label.name + ".jar")
 
   compilation_provider = java_common.compile(
     ctx,
@@ -367,7 +367,7 @@
 
   cat >g/java_custom_library.bzl <<'EOF'
 def _impl(ctx):
-  output_jar = ctx.new_file("lib" + ctx.label.name + ".jar")
+  output_jar = ctx.actions.declare_file("lib" + ctx.label.name + ".jar")
 
   compilation_provider = java_common.compile(
     ctx,
diff --git a/src/test/shell/integration/build_event_stream_test.sh b/src/test/shell/integration/build_event_stream_test.sh
index 3d85cda..f895661 100755
--- a/src/test/shell/integration/build_event_stream_test.sh
+++ b/src/test/shell/integration/build_event_stream_test.sh
@@ -95,7 +95,7 @@
 cat > simpleaspect.bzl <<EOF
 def _simple_aspect_impl(target, ctx):
     for orig_out in ctx.rule.attr.outs:
-        aspect_out = ctx.new_file(orig_out.name + ".aspect")
+        aspect_out = ctx.actions.declare_file(orig_out.name + ".aspect")
         ctx.file_action(
             output=aspect_out,
             content = "Hello from aspect")
diff --git a/src/test/shell/integration/discard_analysis_cache_test.sh b/src/test/shell/integration/discard_analysis_cache_test.sh
index 21fbfb3..eda5823 100755
--- a/src/test/shell/integration/discard_analysis_cache_test.sh
+++ b/src/test/shell/integration/discard_analysis_cache_test.sh
@@ -70,7 +70,7 @@
 def _simple_aspect_impl(target, ctx):
   result=depset()
   for orig_out in target.files:
-    aspect_out = ctx.new_file(orig_out.basename + ".aspect")
+    aspect_out = ctx.actions.declare_file(orig_out.basename + ".aspect")
     ctx.file_action(
         output=aspect_out,
         content = "Hello from aspect for %s" % orig_out.basename)
diff --git a/src/test/shell/integration/discard_graph_edges_test.sh b/src/test/shell/integration/discard_graph_edges_test.sh
index 5baf41d..2e05b01 100755
--- a/src/test/shell/integration/discard_graph_edges_test.sh
+++ b/src/test/shell/integration/discard_graph_edges_test.sh
@@ -69,7 +69,7 @@
 def _simple_aspect_impl(target, ctx):
   result=depset()
   for orig_out in target.files:
-    aspect_out = ctx.new_file(orig_out.basename + ".aspect")
+    aspect_out = ctx.actions.declare_file(orig_out.basename + ".aspect")
     ctx.file_action(
         output=aspect_out,
         content = "Hello from aspect for %s" % orig_out.basename)
@@ -253,7 +253,7 @@
   cat > conflict/conflict_rule.bzl <<EOF || fail "Couldn't write bzl file"
 def _create(ctx):
   files_to_build = set(ctx.outputs.outs)
-  intemediate_outputs = [ctx.new_file("bar")]
+  intemediate_outputs = [ctx.actions.declare_file("bar")]
   intermediate_cmd = "cat %s > %s" % (ctx.attr.name, intemediate_outputs[0].path)
   action_cmd = "touch " + list(files_to_build)[0].path
   ctx.action(outputs=list(intemediate_outputs),