@@ -153,11 +153,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
153153 if copt .Platform == nil {
154154 copt .Platform = opt .TargetPlatform
155155 }
156- st , img , err := opt .Client .NamedContext (ctx , name , copt )
157- if err != nil {
158- return nil , nil , err
159- }
160- return st , img , nil
156+ return opt .Client .NamedContext (ctx , name , copt )
161157 }
162158 return nil , nil , nil
163159 }
@@ -237,6 +233,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
237233 stage : st ,
238234 deps : make (map [* dispatchState ]struct {}),
239235 ctxPaths : make (map [string ]struct {}),
236+ paths : make (map [string ]struct {}),
240237 stageName : st .Name ,
241238 prefixPlatform : opt .MultiPlatformRequested ,
242239 outline : outline .clone (),
@@ -260,7 +257,11 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
260257 }
261258
262259 if st .Name != "" {
263- s , img , err := namedContext (ctx , st .Name , dockerui.ContextOpt {Platform : ds .platform , ResolveMode : opt .ImageResolveMode .String ()})
260+ s , img , err := namedContext (ctx , st .Name , dockerui.ContextOpt {
261+ Platform : ds .platform ,
262+ ResolveMode : opt .ImageResolveMode .String (),
263+ AsyncLocalOpts : ds .asyncLocalOpts ,
264+ })
264265 if err != nil {
265266 return nil , err
266267 }
@@ -391,7 +392,11 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
391392 d .stage .BaseName = reference .TagNameOnly (ref ).String ()
392393
393394 var isScratch bool
394- st , img , err := namedContext (ctx , d .stage .BaseName , dockerui.ContextOpt {ResolveMode : opt .ImageResolveMode .String (), Platform : platform })
395+ st , img , err := namedContext (ctx , d .stage .BaseName , dockerui.ContextOpt {
396+ ResolveMode : opt .ImageResolveMode .String (),
397+ Platform : platform ,
398+ AsyncLocalOpts : d .asyncLocalOpts ,
399+ })
395400 if err != nil {
396401 return err
397402 }
@@ -492,11 +497,23 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
492497 d .state = d .base .state
493498 d .platform = d .base .platform
494499 d .image = clone (d .base .image )
500+ // Utilize the same path index as our base image so we propagate
501+ // the paths we use back to the base image.
502+ d .paths = d .base .paths
503+ }
504+
505+ // Ensure platform is set.
506+ if d .platform == nil {
507+ d .platform = & d .opt .targetPlatform
495508 }
496509
497510 // make sure that PATH is always set
498511 if _ , ok := shell .BuildEnvs (d .image .Config .Env )["PATH" ]; ! ok {
499- d .image .Config .Env = append (d .image .Config .Env , "PATH=" + system .DefaultPathEnv (d .platform .OS ))
512+ var osName string
513+ if d .platform != nil {
514+ osName = d .platform .OS
515+ }
516+ d .image .Config .Env = append (d .image .Config .Env , "PATH=" + system .DefaultPathEnv (osName ))
500517 }
501518
502519 // initialize base metadata from image conf
@@ -565,17 +582,19 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
565582 }
566583 }
567584
585+ // Ensure the entirety of the target state is marked as used.
586+ // This is done after we've already evaluated every stage to ensure
587+ // the paths attribute is set correctly.
588+ target .paths ["/" ] = struct {}{}
589+
568590 if len (opt .Labels ) != 0 && target .image .Config .Labels == nil {
569591 target .image .Config .Labels = make (map [string ]string , len (opt .Labels ))
570592 }
571593 for k , v := range opt .Labels {
572594 target .image .Config .Labels [k ] = v
573595 }
574596
575- opts := []llb.LocalOption {}
576- if includePatterns := normalizeContextPaths (ctxPaths ); includePatterns != nil {
577- opts = append (opts , llb .FollowPaths (includePatterns ))
578- }
597+ opts := filterPaths (ctxPaths )
579598 bctx := opt .MainContext
580599 if opt .Client != nil {
581600 bctx , err = opt .Client .MainContext (ctx , opts ... )
@@ -630,6 +649,7 @@ func toCommand(ic instructions.Command, allDispatchStates *dispatchStates) (comm
630649 stn = & dispatchState {
631650 stage : instructions.Stage {BaseName : c .From , Location : ic .Location ()},
632651 deps : make (map [* dispatchState ]struct {}),
652+ paths : make (map [string ]struct {}),
633653 unregistered : true ,
634654 }
635655 }
@@ -773,9 +793,19 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
773793 location : c .Location (),
774794 opt : opt ,
775795 })
776- if err == nil && len (cmd .sources ) == 0 {
777- for _ , src := range c .SourcePaths {
778- d .ctxPaths [path .Join ("/" , filepath .ToSlash (src ))] = struct {}{}
796+ if err == nil {
797+ if len (cmd .sources ) == 0 {
798+ for _ , src := range c .SourcePaths {
799+ d .ctxPaths [path .Join ("/" , filepath .ToSlash (src ))] = struct {}{}
800+ }
801+ } else {
802+ source := cmd .sources [0 ]
803+ if source .paths == nil {
804+ source .paths = make (map [string ]struct {})
805+ }
806+ for _ , src := range c .SourcePaths {
807+ source .paths [path .Join ("/" , filepath .ToSlash (src ))] = struct {}{}
808+ }
779809 }
780810 }
781811 default :
@@ -784,17 +814,20 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
784814}
785815
786816type dispatchState struct {
787- opt dispatchOpt
788- state llb.State
789- image image.Image
790- platform * ocispecs.Platform
791- stage instructions.Stage
792- base * dispatchState
793- noinit bool
794- deps map [* dispatchState ]struct {}
795- buildArgs []instructions.KeyValuePairOptional
796- commands []command
797- ctxPaths map [string ]struct {}
817+ opt dispatchOpt
818+ state llb.State
819+ image image.Image
820+ platform * ocispecs.Platform
821+ stage instructions.Stage
822+ base * dispatchState
823+ noinit bool
824+ deps map [* dispatchState ]struct {}
825+ buildArgs []instructions.KeyValuePairOptional
826+ commands []command
827+ // ctxPaths marks the paths this dispatchState uses from the build context.
828+ ctxPaths map [string ]struct {}
829+ // paths marks the paths that are used by this dispatchState.
830+ paths map [string ]struct {}
798831 ignoreCache bool
799832 cmdSet bool
800833 unregistered bool
@@ -808,6 +841,10 @@ type dispatchState struct {
808841 scanContext bool
809842}
810843
844+ func (ds * dispatchState ) asyncLocalOpts () []llb.LocalOption {
845+ return filterPaths (ds .paths )
846+ }
847+
811848type dispatchStates struct {
812849 states []* dispatchState
813850 statesByName map [string ]* dispatchState
@@ -890,6 +927,9 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
890927
891928 customname := c .String ()
892929
930+ // Run command can potentially access any file. Mark the full filesystem as used.
931+ d .paths ["/" ] = struct {}{}
932+
893933 var args []string = c .CmdLine
894934 if len (c .Files ) > 0 {
895935 if len (args ) != 1 || ! c .PrependShell {
@@ -1603,6 +1643,11 @@ func hasCircularDependency(states []*dispatchState) (bool, *dispatchState) {
16031643}
16041644
16051645func normalizeContextPaths (paths map [string ]struct {}) []string {
1646+ // Avoid a useless allocation if the set of paths is empty.
1647+ if len (paths ) == 0 {
1648+ return nil
1649+ }
1650+
16061651 pathSlice := make ([]string , 0 , len (paths ))
16071652 for p := range paths {
16081653 if p == "/" {
@@ -1617,6 +1662,15 @@ func normalizeContextPaths(paths map[string]struct{}) []string {
16171662 return pathSlice
16181663}
16191664
1665+ // filterPaths returns the local options required to filter an llb.Local
1666+ // to only the required paths.
1667+ func filterPaths (paths map [string ]struct {}) []llb.LocalOption {
1668+ if includePaths := normalizeContextPaths (paths ); len (includePaths ) > 0 {
1669+ return []llb.LocalOption {llb .FollowPaths (includePaths )}
1670+ }
1671+ return nil
1672+ }
1673+
16201674func proxyEnvFromBuildArgs (args map [string ]string ) * llb.ProxyEnv {
16211675 pe := & llb.ProxyEnv {}
16221676 isNil := true
0 commit comments