1515
1616import static com .google .devtools .build .lib .packages .semantics .BuildLanguageOptions .EXPERIMENTAL_SIBLING_REPOSITORY_LAYOUT ;
1717
18+ import com .google .common .base .Joiner ;
1819import com .google .common .collect .ImmutableList ;
1920import com .google .common .collect .ImmutableMap ;
21+ import com .google .common .collect .Sets ;
2022import com .google .devtools .build .lib .actions .Action ;
2123import com .google .devtools .build .lib .actions .ActionAnalysisMetadata ;
2224import com .google .devtools .build .lib .actions .ActionLookupKey ;
2325import com .google .devtools .build .lib .actions .ActionRegistry ;
2426import com .google .devtools .build .lib .actions .Artifact ;
2527import com .google .devtools .build .lib .actions .ArtifactRoot ;
2628import com .google .devtools .build .lib .actions .CommandLine ;
29+ import com .google .devtools .build .lib .actions .ExecException ;
2730import com .google .devtools .build .lib .actions .ParamFileInfo ;
31+ import com .google .devtools .build .lib .actions .ResourceSet ;
32+ import com .google .devtools .build .lib .actions .ResourceSetOrBuilder ;
2833import com .google .devtools .build .lib .actions .RunfilesSupplier ;
34+ import com .google .devtools .build .lib .actions .UserExecException ;
2935import com .google .devtools .build .lib .actions .extra .ExtraActionInfo ;
3036import com .google .devtools .build .lib .actions .extra .SpawnInfo ;
3137import com .google .devtools .build .lib .analysis .BashCommandConstructor ;
4955import com .google .devtools .build .lib .collect .nestedset .Order ;
5056import com .google .devtools .build .lib .packages .TargetUtils ;
5157import com .google .devtools .build .lib .packages .semantics .BuildLanguageOptions ;
58+ import com .google .devtools .build .lib .server .FailureDetails ;
59+ import com .google .devtools .build .lib .server .FailureDetails .FailureDetail ;
60+ import com .google .devtools .build .lib .server .FailureDetails .Interrupted ;
5261import com .google .devtools .build .lib .skyframe .serialization .autocodec .AutoCodec ;
5362import com .google .devtools .build .lib .skyframe .serialization .autocodec .SerializationConstant ;
5463import com .google .devtools .build .lib .starlarkbuildapi .FileApi ;
5564import com .google .devtools .build .lib .starlarkbuildapi .StarlarkActionFactoryApi ;
65+ import com .google .devtools .build .lib .util .OS ;
5666import com .google .devtools .build .lib .vfs .PathFragment ;
5767import com .google .protobuf .GeneratedMessage ;
5868import java .nio .charset .StandardCharsets ;
5969import java .util .ArrayList ;
70+ import java .util .Arrays ;
71+ import java .util .HashSet ;
6072import java .util .List ;
6173import java .util .Map ;
6274import java .util .Optional ;
75+ import java .util .Set ;
6376import java .util .UUID ;
6477import net .starlark .java .eval .Dict ;
6578import net .starlark .java .eval .EvalException ;
79+ import net .starlark .java .eval .Mutability ;
6680import net .starlark .java .eval .Printer ;
6781import net .starlark .java .eval .Sequence ;
6882import net .starlark .java .eval .Starlark ;
83+ import net .starlark .java .eval .StarlarkCallable ;
84+ import net .starlark .java .eval .StarlarkFloat ;
85+ import net .starlark .java .eval .StarlarkFunction ;
86+ import net .starlark .java .eval .StarlarkInt ;
6987import net .starlark .java .eval .StarlarkSemantics ;
7088import net .starlark .java .eval .StarlarkThread ;
7189
@@ -75,6 +93,10 @@ public class StarlarkActionFactory implements StarlarkActionFactoryApi {
7593 /** Counter for actions.run_shell helper scripts. Every script must have a unique name. */
7694 private int runShellOutputCounter = 0 ;
7795
96+ private static final ResourceSet DEFAULT_RESOURCE_SET = ResourceSet .createWithRamCpu (250 , 1 );
97+ private static final Set <String > validResources =
98+ new HashSet <>(Arrays .asList ("cpu" , "memory" , "local_test" ));
99+
78100 public StarlarkActionFactory (StarlarkRuleContext context ) {
79101 this .context = context ;
80102 }
@@ -339,7 +361,8 @@ public void run(
339361 Object executionRequirementsUnchecked ,
340362 Object inputManifestsUnchecked ,
341363 Object execGroupUnchecked ,
342- Object shadowedActionUnchecked )
364+ Object shadowedActionUnchecked ,
365+ Object resourceSetUnchecked )
343366 throws EvalException {
344367 context .checkMutable ("actions.run" );
345368
@@ -377,6 +400,7 @@ public void run(
377400 inputManifestsUnchecked ,
378401 execGroupUnchecked ,
379402 shadowedActionUnchecked ,
403+ resourceSetUnchecked ,
380404 builder );
381405 }
382406
@@ -430,7 +454,8 @@ public void runShell(
430454 Object executionRequirementsUnchecked ,
431455 Object inputManifestsUnchecked ,
432456 Object execGroupUnchecked ,
433- Object shadowedActionUnchecked )
457+ Object shadowedActionUnchecked ,
458+ Object resourceSetUnchecked )
434459 throws EvalException {
435460 context .checkMutable ("actions.run_shell" );
436461 RuleContext ruleContext = getRuleContext ();
@@ -497,6 +522,7 @@ public void runShell(
497522 inputManifestsUnchecked ,
498523 execGroupUnchecked ,
499524 shadowedActionUnchecked ,
525+ resourceSetUnchecked ,
500526 builder );
501527 }
502528
@@ -543,6 +569,7 @@ private void registerStarlarkAction(
543569 Object inputManifestsUnchecked ,
544570 Object execGroupUnchecked ,
545571 Object shadowedActionUnchecked ,
572+ Object resourceSetUnchecked ,
546573 StarlarkAction .Builder builder )
547574 throws EvalException {
548575 if (inputs instanceof Sequence ) {
@@ -648,10 +675,127 @@ private void registerStarlarkAction(
648675 builder .setShadowedAction (Optional .of ((Action ) shadowedActionUnchecked ));
649676 }
650677
678+ if (getSemantics ().getBool (BuildLanguageOptions .EXPERIMENTAL_ACTION_RESOURCE_SET )
679+ && resourceSetUnchecked != Starlark .NONE ) {
680+ validateResourceSetBuilder (resourceSetUnchecked );
681+ builder .setResources (
682+ new StarlarkActionResourceSetBuilder (
683+ (StarlarkFunction ) resourceSetUnchecked , mnemonic , getSemantics ()));
684+ }
685+
651686 // Always register the action
652687 registerAction (builder .build (ruleContext ));
653688 }
654689
690+ private static class StarlarkActionResourceSetBuilder implements ResourceSetOrBuilder {
691+ private final StarlarkCallable fn ;
692+ private final String mnemonic ;
693+ private final StarlarkSemantics semantics ;
694+
695+ public StarlarkActionResourceSetBuilder (
696+ StarlarkCallable fn , String mnemonic , StarlarkSemantics semantics ) {
697+ this .fn = fn ;
698+ this .mnemonic = mnemonic ;
699+ this .semantics = semantics ;
700+ }
701+
702+ @ Override
703+ public ResourceSet buildResourceSet (OS os , int inputsSize ) throws ExecException {
704+ try (Mutability mu = Mutability .create ("resource_set_builder_function" )) {
705+ StarlarkThread thread = new StarlarkThread (mu , semantics );
706+ StarlarkInt inputInt = StarlarkInt .of (inputsSize );
707+ Object response =
708+ Starlark .call (
709+ thread ,
710+ this .fn ,
711+ ImmutableList .of (os .getCanonicalName (), inputInt ),
712+ ImmutableMap .of ());
713+ Map <String , Object > resourceSetMapRaw =
714+ Dict .cast (response , String .class , Object .class , "resource_set" );
715+
716+ if (!validResources .containsAll (resourceSetMapRaw .keySet ())) {
717+ String message =
718+ String .format (
719+ "Illegal resource keys: (%s)" ,
720+ Joiner .on ("," ).join (Sets .difference (resourceSetMapRaw .keySet (), validResources )));
721+ throw new EvalException (message );
722+ }
723+
724+ return ResourceSet .create (
725+ getNumericOrDefault (resourceSetMapRaw , "memory" , DEFAULT_RESOURCE_SET .getMemoryMb ()),
726+ getNumericOrDefault (resourceSetMapRaw , "cpu" , DEFAULT_RESOURCE_SET .getCpuUsage ()),
727+ (int )
728+ getNumericOrDefault (
729+ resourceSetMapRaw ,
730+ "local_test" ,
731+ (double ) DEFAULT_RESOURCE_SET .getLocalTestCount ()));
732+ } catch (EvalException e ) {
733+ throw new UserExecException (
734+ FailureDetail .newBuilder ()
735+ .setMessage (
736+ String .format ("Could not build resources for %s. %s" , mnemonic , e .getMessage ()))
737+ .setStarlarkAction (
738+ FailureDetails .StarlarkAction .newBuilder ()
739+ .setCode (FailureDetails .StarlarkAction .Code .STARLARK_ACTION_UNKNOWN )
740+ .build ())
741+ .build ());
742+ } catch (InterruptedException e ) {
743+ throw new UserExecException (
744+ FailureDetail .newBuilder ()
745+ .setMessage (e .getMessage ())
746+ .setInterrupted (
747+ Interrupted .newBuilder ().setCode (Interrupted .Code .INTERRUPTED ).build ())
748+ .build ());
749+ }
750+ }
751+
752+ private static double getNumericOrDefault (
753+ Map <String , Object > resourceSetMap , String key , double defaultValue ) throws EvalException {
754+ if (!resourceSetMap .containsKey (key )) {
755+ return defaultValue ;
756+ }
757+
758+ Object value = resourceSetMap .get (key );
759+ if (value instanceof StarlarkInt ) {
760+ return ((StarlarkInt ) value ).toDouble ();
761+ }
762+
763+ if (value instanceof StarlarkFloat ) {
764+ return ((StarlarkFloat ) value ).toDouble ();
765+ }
766+ throw new EvalException (
767+ String .format (
768+ "Illegal resource value type for key %s: got %s, want int or float" ,
769+ key , Starlark .type (value )));
770+ }
771+ }
772+
773+ private static StarlarkFunction validateResourceSetBuilder (Object fn ) throws EvalException {
774+ if (!(fn instanceof StarlarkFunction )) {
775+ throw Starlark .errorf (
776+ "resource_set should be a Starlark-defined function, but got %s instead" ,
777+ Starlark .type (fn ));
778+ }
779+
780+ StarlarkFunction sfn = (StarlarkFunction ) fn ;
781+
782+ // Reject non-global functions, because arbitrary closures may cause large
783+ // analysis-phase data structures to remain live into the execution phase.
784+ // We require that the function is "global" as opposed to "not a closure"
785+ // because a global function may be closure if it refers to load bindings.
786+ // This unfortunately disallows such trivially safe non-global
787+ // functions as "lambda x: x".
788+ // See https://github.com/bazelbuild/bazel/issues/12701.
789+ if (sfn .getModule ().getGlobal (sfn .getName ()) != sfn ) {
790+ throw Starlark .errorf (
791+ "to avoid unintended retention of analysis data structures, "
792+ + "the resource_set function (declared at %s) must be declared "
793+ + "by a top-level def statement" ,
794+ sfn .getLocation ());
795+ }
796+ return (StarlarkFunction ) fn ;
797+ }
798+
655799 private String getMnemonic (Object mnemonicUnchecked ) {
656800 String mnemonic = mnemonicUnchecked == Starlark .NONE ? "Action" : (String ) mnemonicUnchecked ;
657801 if (getRuleContext ().getConfiguration ().getReservedActionMnemonics ().contains (mnemonic )) {
0 commit comments