@ -653,11 +653,166 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op
return & so , releaseF , nil
return & so , releaseF , nil
}
}
func build ( ctx context . Context , c * client . Client , opt Options , so client . SolveOpt , pw progress . Writer ) ( * ResultContext , error ) {
frontendInputs := make ( map [ string ] * pb . Definition )
for key , st := range so . FrontendInputs {
def , err := st . Marshal ( ctx )
if err != nil {
return nil , err
}
frontendInputs [ key ] = def . ToPB ( )
}
req := gateway . SolveRequest {
Frontend : so . Frontend ,
FrontendInputs : frontendInputs ,
FrontendOpt : make ( map [ string ] string ) ,
}
for k , v := range so . FrontendAttrs {
req . FrontendOpt [ k ] = v
}
so . Frontend = ""
so . FrontendInputs = nil
ch , chdone := progress . NewChannel ( pw )
rcs := make ( chan * ResultContext )
suspend := make ( chan struct { } )
suspendDone := make ( chan struct { } )
go func ( ) {
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
var rc * ResultContext
var printRes map [ string ] [ ] byte
rr , err := c . Build ( ctx , so , "buildx" , func ( ctx context . Context , c gateway . Client ) ( * gateway . Result , error ) {
var isFallback bool
var origErr error
for {
if opt . PrintFunc != nil {
if _ , ok := req . FrontendOpt [ "frontend.caps" ] ; ! ok {
req . FrontendOpt [ "frontend.caps" ] = "moby.buildkit.frontend.subrequests+forward"
} else {
req . FrontendOpt [ "frontend.caps" ] += ",moby.buildkit.frontend.subrequests+forward"
}
req . FrontendOpt [ "requestid" ] = "frontend." + opt . PrintFunc . Name
if isFallback {
req . FrontendOpt [ "build-arg:BUILDKIT_SYNTAX" ] = printFallbackImage
}
}
res , err := c . Solve ( ctx , req )
if err != nil {
if origErr != nil {
return nil , err
}
var reqErr * errdefs . UnsupportedSubrequestError
if ! isFallback {
if errors . As ( err , & reqErr ) {
switch reqErr . Name {
case "frontend.outline" , "frontend.targets" :
isFallback = true
origErr = err
continue
}
return nil , err
}
// buildkit v0.8 vendored in Docker 20.10 does not support typed errors
if strings . Contains ( err . Error ( ) , "unsupported request frontend.outline" ) || strings . Contains ( err . Error ( ) , "unsupported request frontend.targets" ) {
isFallback = true
origErr = err
continue
}
}
}
// TODO: would we want to compute this on demand instead of blocking?
eg , ctx2 := errgroup . WithContext ( ctx )
res . EachRef ( func ( r gateway . Reference ) error {
eg . Go ( func ( ) error {
return r . Evaluate ( ctx2 )
} )
return nil
} )
err = eg . Wait ( )
if opt . PrintFunc != nil {
printRes = res . Metadata
}
rc = & ResultContext {
gwClient : c ,
gwCtx : ctx ,
gwDone : cancel ,
gwRef : res ,
suspend : suspend ,
suspendDone : suspendDone ,
}
var se * errdefs . SolveError
if errors . As ( err , & se ) {
rc . gwErr = se
} else if err != nil {
return nil , err
}
rcs <- rc
<- suspend
return res , err
}
} , ch )
<- chdone
if rr != nil {
if rr . ExporterResponse == nil {
rr . ExporterResponse = map [ string ] string { }
}
for k , v := range printRes {
rr . ExporterResponse [ k ] = string ( v )
}
}
rc . resp = rr
rc . err = err
close ( suspendDone )
} ( )
select {
case <- ctx . Done ( ) :
return nil , ctx . Err ( )
case rc := <- rcs :
return rc , nil
}
}
func Build ( ctx context . Context , nodes [ ] builder . Node , opt map [ string ] Options , docker * dockerutil . Client , configDir string , w progress . Writer ) ( resp map [ string ] * client . SolveResponse , err error ) {
func Build ( ctx context . Context , nodes [ ] builder . Node , opt map [ string ] Options , docker * dockerutil . Client , configDir string , w progress . Writer ) ( resp map [ string ] * client . SolveResponse , err error ) {
return BuildWithResultHandler ( ctx , nodes , opt , docker , configDir , w , nil )
resp = map [ string ] * client . SolveResponse { }
rcs , err := BuildResults ( ctx , nodes , opt , docker , configDir , w )
if err != nil {
return nil , err
}
eg , ctx := errgroup . WithContext ( ctx )
for k , v := range rcs {
k , v := k , v
eg . Go ( func ( ) error {
v2 , err := v . Wait ( ctx )
if err != nil {
return err
}
resp [ k ] = v2
return nil
} )
}
if err := eg . Wait ( ) ; err != nil {
return nil , err
}
return resp , nil
}
}
func BuildWithResultHandler ( ctx context . Context , nodes [ ] builder . Node , opt map [ string ] Options , docker * dockerutil . Client , configDir string , w progress . Writer , resultHandleFunc func ( driverIndex int , rCtx * ResultContext ) ) ( resp map [ string ] * client . SolveResponse , err error ) {
func Build Results ( ctx context . Context , nodes [ ] builder . Node , opt map [ string ] Options , docker * dockerutil . Client , configDir string , w progress . Writer ) ( resp map [ string ] * ResultContext , err error ) {
if len ( nodes ) == 0 {
if len ( nodes ) == 0 {
return nil , errors . Errorf ( "driver required for build" )
return nil , errors . Errorf ( "driver required for build" )
}
}
@ -708,8 +863,6 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
}
}
} ( )
} ( )
eg , ctx := errgroup . WithContext ( ctx )
for k , opt := range opt {
for k , opt := range opt {
multiDriver := len ( m [ k ] ) > 1
multiDriver := len ( m [ k ] ) > 1
hasMobyDriver := false
hasMobyDriver := false
@ -785,8 +938,9 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
}
}
}
}
resp = map [ string ] * client . SolveResponse { }
eg, ctx := errgroup . WithContext ( ctx )
var respMu sync . Mutex
var respMu sync . Mutex
resp = map [ string ] * ResultContext { }
results := waitmap . New ( )
results := waitmap . New ( )
multiTarget := len ( opt ) > 1
multiTarget := len ( opt ) > 1
@ -801,15 +955,17 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
if multiTarget {
if multiTarget {
span , ctx = tracing . StartSpan ( ctx , k )
span , ctx = tracing . StartSpan ( ctx , k )
}
}
baseCtx := ctx
res := make ( [ ] * client . SolveResponse , len ( dps ) )
res := make ( [ ] * ResultContext , len ( dps ) )
eg2 , ctx := errgroup . WithContext ( ctx )
var pushNames string
var pushNames string
var insecurePush bool
var insecurePush bool
wg := sync . WaitGroup { }
for i , dp := range dps {
for i , dp := range dps {
wg . Add ( 1 )
i , dp , so := i , dp , * dp . so
i , dp , so := i , dp , * dp . so
if multiDriver {
if multiDriver {
for i , e := range so . Exports {
for i , e := range so . Exports {
@ -842,104 +998,24 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
pw := progress . WithPrefix ( w , k , multiTarget )
pw := progress . WithPrefix ( w , k , multiTarget )
c := clients [ dp . driverIndex ]
c := clients [ dp . driverIndex ]
eg 2 . Go ( func ( ) error {
eg . Go ( func ( ) error {
pw = progress . ResetTime ( pw )
pw = progress . ResetTime ( pw )
if err := waitContextDeps ( ctx , dp . driverIndex , results , & so ) ; err != nil {
if err := waitContextDeps ( ctx , dp . driverIndex , results , & so ) ; err != nil {
return err
return err
}
}
frontendInputs := make ( map [ string ] * pb . Definition )
result , err := build ( ctx , c , opt , so , pw )
for key , st := range so . FrontendInputs {
def , err := st . Marshal ( ctx )
if err != nil {
if err != nil {
return err
return err
}
}
frontendInputs [ key ] = def . ToPB ( )
results . Set ( resultKey ( dp . driverIndex , k ) , result . gwRef )
}
req := gateway . SolveRequest {
res [ i ] = result
Frontend : so . Frontend ,
resp [ k ] = result
FrontendInputs : frontendInputs ,
FrontendOpt : make ( map [ string ] string ) ,
}
for k , v := range so . FrontendAttrs {
req . FrontendOpt [ k ] = v
}
so . Frontend = ""
so . FrontendInputs = nil
ch , done := progress . NewChannel ( pw )
result . hook ( func ( ctx context . Context ) error {
defer func ( ) { <- done } ( )
defer wg . Done ( )
cc := c
var printRes map [ string ] [ ] byte
rr , err := c . Build ( ctx , so , "buildx" , func ( ctx context . Context , c gateway . Client ) ( * gateway . Result , error ) {
var isFallback bool
var origErr error
for {
if opt . PrintFunc != nil {
if _ , ok := req . FrontendOpt [ "frontend.caps" ] ; ! ok {
req . FrontendOpt [ "frontend.caps" ] = "moby.buildkit.frontend.subrequests+forward"
} else {
req . FrontendOpt [ "frontend.caps" ] += ",moby.buildkit.frontend.subrequests+forward"
}
req . FrontendOpt [ "requestid" ] = "frontend." + opt . PrintFunc . Name
if isFallback {
req . FrontendOpt [ "build-arg:BUILDKIT_SYNTAX" ] = printFallbackImage
}
}
res , err := c . Solve ( ctx , req )
if err != nil {
if origErr != nil {
return nil , err
}
var reqErr * errdefs . UnsupportedSubrequestError
if ! isFallback {
if errors . As ( err , & reqErr ) {
switch reqErr . Name {
case "frontend.outline" , "frontend.targets" :
isFallback = true
origErr = err
continue
}
return nil , err
}
// buildkit v0.8 vendored in Docker 20.10 does not support typed errors
if strings . Contains ( err . Error ( ) , "unsupported request frontend.outline" ) || strings . Contains ( err . Error ( ) , "unsupported request frontend.targets" ) {
isFallback = true
origErr = err
continue
}
}
return nil , err
}
if opt . PrintFunc != nil {
printRes = res . Metadata
}
results . Set ( resultKey ( dp . driverIndex , k ) , res )
if resultHandleFunc != nil {
resultCtx , err := NewResultContext ( cc , so , res )
if err == nil {
resultHandleFunc ( dp . driverIndex , resultCtx )
} else {
logrus . Warnf ( "failed to record result: %s" , err )
}
}
return res , nil
}
} , ch )
if err != nil {
return err
}
res [ i ] = rr
if rr . ExporterResponse == nil {
rr . ExporterResponse = map [ string ] string { }
}
for k , v := range printRes {
rr . ExporterResponse [ k ] = string ( v )
}
node := nodes [ dp . driverIndex ] . Driver
node := nodes [ dp . driverIndex ] . Driver
if node . IsMobyDriver ( ) {
if node . IsMobyDriver ( ) {
@ -963,12 +1039,12 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
if err == nil && remoteDigest != "" {
if err == nil && remoteDigest != "" {
// old daemons might not have containerimage.config.digest set
// old daemons might not have containerimage.config.digest set
// in response so use containerimage.digest value for it if available
// in response so use containerimage.digest value for it if available
if _ , ok := r r. ExporterResponse [ exptypes . ExporterImageConfigDigestKey ] ; ! ok {
if _ , ok := r esult. resp . ExporterResponse [ exptypes . ExporterImageConfigDigestKey ] ; ! ok {
if v , ok := r r. ExporterResponse [ exptypes . ExporterImageDigestKey ] ; ok {
if v , ok := r esult. resp . ExporterResponse [ exptypes . ExporterImageDigestKey ] ; ok {
r r. ExporterResponse [ exptypes . ExporterImageConfigDigestKey ] = v
r esult. resp . ExporterResponse [ exptypes . ExporterImageConfigDigestKey ] = v
}
}
}
}
r r. ExporterResponse [ exptypes . ExporterImageDigestKey ] = remoteDigest
r esult. resp . ExporterResponse [ exptypes . ExporterImageDigestKey ] = remoteDigest
} else if err != nil {
} else if err != nil {
return err
return err
}
}
@ -976,21 +1052,21 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
}
}
}
}
}
}
return nil
return nil
} )
} )
}
eg . Go ( func ( ) ( err error ) {
if i == 0 {
ctx := baseCtx
result . hook ( func ( ctx context . Context ) error {
wg . Wait ( )
defer func ( ) {
defer func ( ) {
if span != nil {
if span != nil {
tracing . FinishWithError ( span , err )
tracing . FinishWithError ( span , err )
}
}
} ( )
} ( )
pw := progress . WithPrefix ( w , "default" , false )
pw := progress . WithPrefix ( w , "default" , false )
if err := eg2 . Wait ( ) ; err != nil {
return err
}
respMu . Lock ( )
respMu . Lock ( )
resp [ k ] = res [ 0 ]
resp [ k ] = res [ 0 ]
@ -998,13 +1074,15 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
if len ( res ) == 1 {
if len ( res ) == 1 {
return nil
return nil
}
}
if pushNames == "" {
return nil
}
if pushNames != "" {
progress . Write ( pw , fmt . Sprintf ( "merging manifest list %s" , pushNames ) , func ( ) error {
progress . Write ( pw , fmt . Sprintf ( "merging manifest list %s" , pushNames ) , func ( ) error {
descs := make ( [ ] specs . Descriptor , 0 , len ( res ) )
descs := make ( [ ] specs . Descriptor , 0 , len ( res ) )
for _ , r := range res {
for _ , r := range res {
s , ok := r . ExporterResponse [ exptypes . ExporterImageDescriptorKey ]
s , ok := r . resp . ExporterResponse [ exptypes . ExporterImageDescriptorKey ]
if ok {
if ok {
dt , err := base64 . StdEncoding . DecodeString ( s )
dt , err := base64 . StdEncoding . DecodeString ( s )
if err != nil {
if err != nil {
@ -1021,7 +1099,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
// Note that the mediatype isn't really correct as most of the time it is image manifest and
// Note that the mediatype isn't really correct as most of the time it is image manifest and
// not manifest list but actually both are handled because for Docker mediatypes the
// not manifest list but actually both are handled because for Docker mediatypes the
// mediatype value in the Accpet header does not seem to matter.
// mediatype value in the Accpet header does not seem to matter.
s , ok = r . ExporterResponse [ exptypes . ExporterImageDigestKey ]
s , ok = r . resp . ExporterResponse [ exptypes . ExporterImageDigestKey ]
if ok {
if ok {
descs = append ( descs , specs . Descriptor {
descs = append ( descs , specs . Descriptor {
Digest : digest . Digest ( s ) ,
Digest : digest . Digest ( s ) ,
@ -1087,7 +1165,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
}
}
respMu . Lock ( )
respMu . Lock ( )
resp [ k ] = & client . SolveResponse {
resp [ k ] . resp = & client . SolveResponse {
ExporterResponse : map [ string ] string {
ExporterResponse : map [ string ] string {
exptypes . ExporterImageDigestKey : desc . Digest . String ( ) ,
exptypes . ExporterImageDigestKey : desc . Digest . String ( ) ,
} ,
} ,
@ -1096,10 +1174,13 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
}
}
return nil
return nil
} )
} )
return nil
} )
}
}
return nil
return nil
} )
} )
}
}
}
if err := eg . Wait ( ) ; err != nil {
if err := eg . Wait ( ) ; err != nil {
return nil , err
return nil , err