Targets can now be configured add exec_properties on top of the platform's exec_properties
See https://docs.google.com/document/d/1w3fu8zu_sRw_gK1dFAvkY2suhbQQ82tc0zdjet-dpCI/edit#heading=h.5mcn15i0e1ch

RELNOTES: introducing per-target exec_properties
PiperOrigin-RevId: 265540815
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java b/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java
index 6573036..60e5f39 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java
@@ -27,6 +27,7 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.config.HostTransition;
@@ -423,6 +424,7 @@
                   .allowedFileTypes(FileTypeSet.ANY_FILE)
                   .dontCheckConstraints())
           .executionPlatformConstraintsAllowed(ExecutionPlatformConstraintsAllowed.PER_TARGET)
+          .add(attr(RuleClass.EXEC_PROPERTIES, Type.STRING_DICT).value(ImmutableMap.of()))
           .build();
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index b11ba26..f73e723 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -410,7 +410,12 @@
   public ActionOwner getActionOwner() {
     if (actionOwner == null) {
       actionOwner =
-          createActionOwner(rule, aspectDescriptors, getConfiguration(), getExecutionPlatform());
+          createActionOwner(
+              rule,
+              aspectDescriptors,
+              getConfiguration(),
+              getTargetExecProperties(),
+              getExecutionPlatform());
     }
     return actionOwner;
   }
@@ -508,20 +513,28 @@
             AnalysisUtils.isStampingEnabled(this, getConfiguration()), key, getConfiguration());
   }
 
+  private static ImmutableMap<String, String> computeExecProperties(
+      Map<String, String> targetExecProperties, @Nullable PlatformInfo executionPlatform) {
+    Map<String, String> execProperties = new HashMap<>();
+
+    if (executionPlatform != null) {
+      execProperties.putAll(executionPlatform.execProperties());
+    }
+
+    // If the same key occurs both in the platform and in target-specific properties, the
+    // value is taken from target-specific properties (effectively overriding the platform
+    // properties).
+    execProperties.putAll(targetExecProperties);
+    return ImmutableMap.copyOf(execProperties);
+  }
+
   @VisibleForTesting
   public static ActionOwner createActionOwner(
       Rule rule,
       ImmutableList<AspectDescriptor> aspectDescriptors,
       BuildConfiguration configuration,
+      Map<String, String> targetExecProperties,
       @Nullable PlatformInfo executionPlatform) {
-    ImmutableMap<String, String> execProperties;
-    if (executionPlatform != null) {
-      execProperties = executionPlatform.execProperties();
-    } else {
-      execProperties = ImmutableMap.of();
-    }
-    // TODO(agoulti): Insert logic to include per-target execution properties
-
     return ActionOwner.create(
         rule.getLabel(),
         aspectDescriptors,
@@ -531,7 +544,7 @@
         configuration.checksum(),
         configuration.toBuildEvent(),
         configuration.isHostConfiguration() ? HOST_CONFIGURATION_PROGRESS_TAG : null,
-        execProperties,
+        computeExecProperties(targetExecProperties, executionPlatform),
         executionPlatform);
   }
 
@@ -1202,6 +1215,14 @@
     return constraintSemantics;
   }
 
+  public Map<String, String> getTargetExecProperties() {
+    if (isAttrDefined(RuleClass.EXEC_PROPERTIES, Type.STRING_DICT)) {
+      return attributes.get(RuleClass.EXEC_PROPERTIES, Type.STRING_DICT);
+    } else {
+      return ImmutableMap.of();
+    }
+  }
+
   @Override
   @Nullable
   public PlatformInfo getExecutionPlatform() {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
index 28c00d7..7aa099b 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
@@ -137,6 +137,7 @@
                   .allowedFileTypes(FileTypeSet.NO_FILE)
                   .mandatoryProviders(ImmutableList.of(TemplateVariableInfo.PROVIDER.id()))
                   .dontCheckConstraints())
+          .add(attr(RuleClass.EXEC_PROPERTIES, Type.STRING_DICT).value(ImmutableMap.of()))
           .build();
 
   /** Parent rule class for executable non-test Skylark rules. */