RELNOTES: Invoking blaze run on an objc_binary target launches iossim with the resulting app.

This adds very basic run support for objc_binary targets. It simply launches them in iossim with the default device and SDK.

--
MOS_MIGRATED_REVID=87286604
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java
index b2c83fb..0a80682 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java
@@ -18,6 +18,7 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.actions.ActionOwner;
@@ -92,6 +93,29 @@
         }
       };
     }
+
+    @Override
+    public boolean equals(Object object) {
+      if (this == object) {
+        return true;
+      }
+      if (object instanceof Substitution) {
+        Substitution substitution = (Substitution) object;
+        return substitution.getKey().equals(this.getKey())
+            && substitution.getValue().equals(this.getValue());
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(getKey(), getValue());
+    }
+
+    @Override
+    public String toString() {
+      return "Substitution(" + getKey() + " -> " + getValue() + ")";
+    }
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
index 8797a2e..b679327 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
@@ -252,6 +252,7 @@
     builder.addRuleDefinition(ObjcRuleClasses.IosTestBaseRule.class);
     builder.addRuleDefinition(ObjcRuleClasses.BundlingRule.class);
     builder.addRuleDefinition(ObjcRuleClasses.ReleaseBundlingRule.class);
+    builder.addRuleDefinition(ObjcRuleClasses.SimulatorRule.class);
     builder.addRuleDefinition(ObjcRuleClasses.CompilingRule.class);
     builder.addRuleDefinition(ObjcRuleClasses.LinkingRule.class);
     builder.addRuleDefinition(ObjcRuleClasses.ResourcesRule.class);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/BinaryLinkingTargetFactory.java b/src/main/java/com/google/devtools/build/lib/rules/objc/BinaryLinkingTargetFactory.java
index 5542a11..4e166ea 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/BinaryLinkingTargetFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/BinaryLinkingTargetFactory.java
@@ -23,7 +23,9 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
 import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.RunfilesSupport;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
 import com.google.devtools.build.lib.rules.objc.ObjcActionsBuilder.ExtraLinkArgs;
@@ -31,6 +33,7 @@
 import com.google.devtools.build.lib.rules.objc.ObjcCommon.CompilationAttributes;
 import com.google.devtools.build.lib.rules.objc.ObjcCommon.ResourceAttributes;
 import com.google.devtools.build.lib.rules.objc.ReleaseBundlingSupport.LinkedBinary;
+import com.google.devtools.build.xcode.common.Platform;
 
 /**
  * Implementation for rules that link binaries.
@@ -70,9 +73,12 @@
       return null;
     }
 
+    IntermediateArtifacts intermediateArtifacts =
+        ObjcRuleClasses.intermediateArtifacts(ruleContext);
+
     XcodeProvider.Builder xcodeProviderBuilder = new XcodeProvider.Builder();
     NestedSetBuilder<Artifact> filesToBuild = NestedSetBuilder.<Artifact>stableOrder()
-        .add(ObjcRuleClasses.intermediateArtifacts(ruleContext).singleArchitectureBinary());
+        .add(intermediateArtifacts.singleArchitectureBinary());
 
     new CompilationSupport(ruleContext)
         .registerJ2ObjcCompileAndArchiveActions(optionsProvider, objcProvider)
@@ -82,6 +88,7 @@
         .validateAttributes();
 
     Optional<XcTestAppProvider> xcTestAppProvider;
+    Optional<RunfilesSupport> maybeRunfilesSupport = Optional.<RunfilesSupport>absent();
     switch (hasReleaseBundlingSupport) {
       case YES:
         // TODO(bazel-team): Remove once all bundle users are migrated to ios_application.
@@ -94,6 +101,12 @@
             .addFilesToBuild(filesToBuild)
             .validateAttributes();
         xcTestAppProvider = Optional.of(releaseBundlingSupport.xcTestAppProvider());
+        if (ObjcRuleClasses.objcConfiguration(ruleContext).getPlatform() == Platform.SIMULATOR) {
+          Artifact runnerScript = intermediateArtifacts.runnerScript();
+          Artifact ipaFile = ruleContext.getImplicitOutputArtifact(ReleaseBundlingSupport.IPA);
+          releaseBundlingSupport.registerGenerateRunnerScriptAction(runnerScript, ipaFile);
+          maybeRunfilesSupport = Optional.of(releaseBundlingSupport.runfilesSupport(runnerScript));
+        }
         break;
       case NO:
         xcTestAppProvider = Optional.absent();
@@ -120,12 +133,16 @@
 
     // TODO(bazel-team): Stop exporting an XcTestAppProvider once objc_binary no longer creates an
     // application bundle.
-    return common.configuredTarget(
+    RuleConfiguredTargetBuilder target = common.configuredTargetBuilder(
         filesToBuild.build(),
         Optional.of(xcodeProvider),
         Optional.of(objcProvider),
         xcTestAppProvider,
         Optional.<J2ObjcSrcsProvider>absent());
+    for (RunfilesSupport runfilesSupport : maybeRunfilesSupport.asSet()) {
+      target.setRunfilesSupport(runfilesSupport, runfilesSupport.getExecutable());
+    }
+    return target.build();
   }
 
   private OptionsProvider optionsProvider(RuleContext ruleContext) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java b/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java
index 2961d2a..f2e7bdc 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java
@@ -223,4 +223,11 @@
   public Artifact breakpadSym() {
     return appendExtension(".breakpad");
   }
+
+  /**
+   * Shell script that launches the binary.
+   */
+  public Artifact runnerScript() {
+    return appendExtension("_runner.sh");
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplication.java b/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplication.java
index d931dae..d35bfa0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplication.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplication.java
@@ -15,8 +15,11 @@
 package com.google.devtools.build.lib.rules.objc;
 
 import com.google.common.base.Optional;
+import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
 import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.xcode.common.Platform;
 
 /**
  * Implementation for {@code ios_application}.
@@ -37,4 +40,16 @@
                 ruleContext.getPrerequisite("options", Mode.TARGET, OptionsProvider.class)))
         .build();
   }
+
+  @Override
+  protected void configureTarget(RuleConfiguredTargetBuilder target, RuleContext ruleContext,
+      ReleaseBundlingSupport releaseBundlingSupport) {
+    // If this is an application built for the simulator, make it runnable.
+    if (ObjcRuleClasses.objcConfiguration(ruleContext).getPlatform() == Platform.SIMULATOR) {
+      Artifact runnerScript = ObjcRuleClasses.intermediateArtifacts(ruleContext).runnerScript();
+      Artifact ipaFile = ruleContext.getImplicitOutputArtifact(ReleaseBundlingSupport.IPA);
+      releaseBundlingSupport.registerGenerateRunnerScriptAction(runnerScript, ipaFile);
+      target.setRunfilesSupport(releaseBundlingSupport.runfilesSupport(runnerScript), runnerScript);
+    }
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java b/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java
index dd54b68..b4c8605 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java
@@ -14,7 +14,9 @@
 
 package com.google.devtools.build.lib.rules.objc;
 
+import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST;
 import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
 import static com.google.devtools.build.lib.packages.Type.LABEL;
 
 import com.google.devtools.build.lib.analysis.BaseRuleClasses;
@@ -33,7 +35,8 @@
     ancestors = {
         BaseRuleClasses.BaseRule.class,
         ObjcRuleClasses.ReleaseBundlingRule.class,
-        ObjcRuleClasses.XcodegenRule.class, })
+        ObjcRuleClasses.XcodegenRule.class,
+        ObjcRuleClasses.SimulatorRule.class })
 public class IosApplicationRule implements RuleDefinition {
 
   @Override
@@ -58,6 +61,10 @@
             .allowedFileTypes()
             .mandatory()
             .direct_compile_time_input())
+        .add(attr("$runner_script_template", LABEL).cfg(HOST)
+            .value(env.getLabel("//tools/objc:ios_runner.sh.mac_template")))
+        .add(attr("$is_executable", BOOLEAN).value(true)
+            .nonconfigurable("Called from RunCommand.isExecutable, which takes a Target"))
         .build();
   }
 }
@@ -72,4 +79,4 @@
 
 ${ATTRIBUTE_DEFINITION}
 
-<!-- #END_BLAZE_RULE -->*/
\ No newline at end of file
+<!-- #END_BLAZE_RULE -->*/
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcBinaryRule.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcBinaryRule.java
index 41ac421..fb887c5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcBinaryRule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcBinaryRule.java
@@ -14,6 +14,11 @@
 
 package com.google.devtools.build.lib.rules.objc;
 
+import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST;
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
+import static com.google.devtools.build.lib.packages.Type.LABEL;
+
 import com.google.devtools.build.lib.analysis.BaseRuleClasses;
 import com.google.devtools.build.lib.analysis.BlazeRule;
 import com.google.devtools.build.lib.analysis.RuleDefinition;
@@ -32,7 +37,8 @@
         BaseRuleClasses.BaseRule.class,
         ObjcRuleClasses.LinkingRule.class,
         ObjcRuleClasses.XcodegenRule.class,
-        ObjcRuleClasses.ReleaseBundlingRule.class })
+        ObjcRuleClasses.ReleaseBundlingRule.class,
+        ObjcRuleClasses.SimulatorRule.class })
 public class ObjcBinaryRule implements RuleDefinition {
 
   @Override
@@ -48,6 +54,11 @@
         <!-- #END_BLAZE_RULE.IMPLICIT_OUTPUTS -->*/
         .setImplicitOutputsFunction(
             ImplicitOutputsFunction.fromFunctions(ReleaseBundlingSupport.IPA, XcodeSupport.PBXPROJ))
+        // TODO(bazel-team): Remove these when this rule no longer produces a bundle.
+        .add(attr("$runner_script_template", LABEL).cfg(HOST)
+            .value(env.getLabel("//tools/objc:ios_runner.sh.mac_template")))
+        .add(attr("$is_executable", BOOLEAN).value(true)
+            .nonconfigurable("Called from RunCommand.isExecutable, which takes a Target"))
         .build();
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommon.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommon.java
index 95d546a..89d8618 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommon.java
@@ -584,6 +584,8 @@
   }
 
   /**
+   * Returns a {@link RuleConfiguredTargetBuilder}.
+   *
    * @param filesToBuild files to build for this target. These also become the data runfiles. Note
    *     that this method may add more files to create the complete list of files to build for this
    *     target.
@@ -592,7 +594,7 @@
    *     present whenever {@code objc_} rules may depend on this target.
    * @param maybeJ2ObjcSrcsProvider the {@link J2ObjcSrcsProvider} for this target.
    */
-  public ConfiguredTarget configuredTarget(NestedSet<Artifact> filesToBuild,
+  public RuleConfiguredTargetBuilder configuredTargetBuilder(NestedSet<Artifact> filesToBuild,
       Optional<XcodeProvider> maybeTargetProvider, Optional<ObjcProvider> maybeExportedProvider,
       Optional<XcTestAppProvider> maybeXcTestAppProvider,
       Optional<J2ObjcSrcsProvider> maybeJ2ObjcSrcsProvider) {
@@ -623,6 +625,25 @@
     for (J2ObjcSrcsProvider j2ObjcSrcsProvider : maybeJ2ObjcSrcsProvider.asSet()) {
       target.addProvider(J2ObjcSrcsProvider.class, j2ObjcSrcsProvider);
     }
-    return target.build();
+    return target;
+  }
+
+  /**
+   * Creates a {@link ConfiguredTarget}.
+   *
+   * @param filesToBuild files to build for this target. These also become the data runfiles. Note
+   *     that this method may add more files to create the complete list of files to build for this
+   *     target.
+   * @param maybeTargetProvider the provider for this target.
+   * @param maybeExportedProvider the {@link ObjcProvider} for this target. This should generally be
+   *     present whenever {@code objc_} rules may depend on this target.
+   * @param maybeJ2ObjcSrcsProvider the {@link J2ObjcSrcsProvider} for this target.
+   */
+  public ConfiguredTarget configuredTarget(NestedSet<Artifact> filesToBuild,
+      Optional<XcodeProvider> maybeTargetProvider, Optional<ObjcProvider> maybeExportedProvider,
+      Optional<XcTestAppProvider> maybeXcTestAppProvider,
+      Optional<J2ObjcSrcsProvider> maybeJ2ObjcSrcsProvider) {
+    return configuredTargetBuilder(filesToBuild, maybeTargetProvider, maybeExportedProvider,
+        maybeXcTestAppProvider, maybeJ2ObjcSrcsProvider).build();
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java
index d8c9f75..cce0b8b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java
@@ -650,7 +650,8 @@
       ancestors = {
           ReleaseBundlingRule.class,
           LinkingRule.class,
-          XcodegenRule.class, })
+          XcodegenRule.class,
+          SimulatorRule.class })
   public static class IosTestBaseRule implements RuleDefinition {
     @Override
     public RuleClass build(Builder builder, final RuleDefinitionEnvironment env) {
@@ -823,6 +824,22 @@
   }
 
   /**
+   * Common attributes for {@code objc_*} rules that use the iOS simulator.
+   */
+  @BlazeRule(name = "$objc_simulator_rule",
+      type = RuleClassType.ABSTRACT)
+  public static class SimulatorRule implements RuleDefinition {
+    @Override
+    public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+      return builder
+          // Needed to run the binary in the simulator.
+          .add(attr("$iossim", LABEL).cfg(HOST).exec()
+              .value(env.getLabel("//third_party/iossim:iossim")))
+          .build();
+    }
+  }
+
+  /**
    * Object that supplies tools used by all rules which have the helper tools common to most rule
    * implementations.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java
index aa39dc4..e00a98d 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java
@@ -26,9 +26,13 @@
 import com.google.devtools.build.lib.analysis.FilesToRunProvider;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
 import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.Runfiles;
+import com.google.devtools.build.lib.analysis.RunfilesSupport;
 import com.google.devtools.build.lib.analysis.actions.BinaryFileWriteAction;
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
+import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
@@ -287,6 +291,35 @@
         ruleContext.getImplicitOutputArtifact(IPA), partialObjcProvider);
   }
 
+  /**
+   * Registers an action to generate a runner script based on a template.
+   */
+  ReleaseBundlingSupport registerGenerateRunnerScriptAction(Artifact runnerScript,
+      Artifact ipaInput) {
+    ImmutableList<Substitution> substitutions = ImmutableList.of(
+        Substitution.of("%app_name%", ruleContext.getLabel().getName()),
+        Substitution.of("%ipa_file%", ipaInput.getRootRelativePath().getPathString()),
+        Substitution.of("%iossim%", attributes.iossim().getRootRelativePath().getPathString()));
+
+    ruleContext.registerAction(
+        new TemplateExpansionAction(ruleContext.getActionOwner(), attributes.runnerScriptTemplate(),
+            runnerScript, substitutions, true));
+    return this;
+  }
+
+  /**
+   * Returns a {@link RunfilesSupport} that uses the provided runner script as the executable.
+   */
+  RunfilesSupport runfilesSupport(Artifact runnerScript) {
+    Artifact ipaFile = ruleContext.getImplicitOutputArtifact(ReleaseBundlingSupport.IPA);
+    Runfiles runfiles = new Runfiles.Builder()
+        .addArtifact(ipaFile)
+        .addArtifact(runnerScript)
+        .addArtifact(attributes.iossim())
+        .build();
+    return RunfilesSupport.withExecutable(ruleContext, runfiles, runnerScript);
+  }
+
   private ExtraActoolArgs extraActoolArgs() {
     ImmutableList.Builder<String> extraArgs = ImmutableList.builder();
     if (attributes.appIcon() != null) {
@@ -590,6 +623,15 @@
       return checkNotNull(ruleContext.getExecutablePrerequisite("$bundlemerge", Mode.HOST));
     }
 
+    Artifact iossim() {
+      return checkNotNull(ruleContext.getPrerequisiteArtifact("$iossim", Mode.HOST));
+    }
+
+    Artifact runnerScriptTemplate() {
+      return checkNotNull(
+          ruleContext.getPrerequisiteArtifact("$runner_script_template", Mode.HOST));
+    }
+
     String bundleId() {
       return checkNotNull(stringAttribute("bundle_id"));
     }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingTargetFactory.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingTargetFactory.java
index d02093e..4bae44a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingTargetFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingTargetFactory.java
@@ -20,6 +20,7 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
@@ -88,12 +89,14 @@
       exposedObjcProvider = Optional.absent();
     }
 
-    return common.configuredTarget(
+    RuleConfiguredTargetBuilder target = common.configuredTargetBuilder(
         filesToBuild.build(),
         Optional.of(xcodeProviderBuilder.build()),
         exposedObjcProvider,
         Optional.<XcTestAppProvider>absent(),
         Optional.<J2ObjcSrcsProvider>absent());
+    configureTarget(target, ruleContext, releaseBundlingSupport);
+    return target.build();
   }
 
   /**
@@ -101,6 +104,13 @@
    */
   protected abstract OptionsProvider optionsProvider(RuleContext ruleContext);
 
+  /**
+   * Performs additional configuration of the target. The default implementation does nothing, but
+   * subclasses may override it to add logic.
+   */
+  protected void configureTarget(RuleConfiguredTargetBuilder target, RuleContext ruleContext,
+      ReleaseBundlingSupport releaseBundlingSupport) {}
+
   private ObjcCommon common(RuleContext ruleContext) {
     return new ObjcCommon.Builder(ruleContext)
         .setIntermediateArtifacts(ObjcRuleClasses.intermediateArtifacts(ruleContext))