@@ -32,6 +32,12 @@ import {
3232import { shutdownBuildkitd } from "./shutdown" ;
3333import { resolveRemoteBuilderPlatforms } from "./platform-utils" ;
3434import { checkPreviousStepFailures } from "./step-checker" ;
35+ import {
36+ getRequiredFollowerArch ,
37+ setupMultiArchFollower ,
38+ teardownFollower ,
39+ type FollowerInfo ,
40+ } from "./multiarch" ;
3541import { Metric_MetricType } from "@buf/blacksmith_vm-agent.bufbuild_es/stickydisk/v1/stickydisk_pb.js" ;
3642
3743const DEFAULT_BUILDX_VERSION = "v0.23.0" ;
@@ -564,9 +570,7 @@ async function maybeShutdownBuildkitd(): Promise<void> {
564570 await pruneBuildkitCache ( ) ;
565571 core . info ( "BuildKit cache pruned" ) ;
566572 } catch ( error ) {
567- core . warning (
568- `Error pruning BuildKit cache: ${ ( error as Error ) . message } ` ,
569- ) ;
573+ core . warning ( `Error pruning BuildKit cache: ${ ( error as Error ) . message } ` ) ;
570574 }
571575
572576 const buildkitdShutdownStartTime = Date . now ( ) ;
@@ -594,9 +598,7 @@ async function logBuildkitdCrashLogs(): Promise<void> {
594598 core . info ( "Last 100 lines of buildkitd.log:" ) ;
595599 core . info ( stdout ) ;
596600 } catch ( error ) {
597- core . warning (
598- `Could not read buildkitd logs: ${ ( error as Error ) . message } ` ,
599- ) ;
601+ core . warning ( `Could not read buildkitd logs: ${ ( error as Error ) . message } ` ) ;
600602 }
601603}
602604
@@ -665,35 +667,119 @@ void actionsToolkit.run(
665667 } ) ;
666668
667669 if ( builderInfo . addr ) {
670+ // Check if multi-arch follower is needed
671+ let followerInfo : FollowerInfo | null = null ;
672+ const followerArch = getRequiredFollowerArch ( inputs . platforms ) ;
673+ if ( followerArch ) {
674+ await core . group (
675+ `Setting up multi-arch follower (${ followerArch } )` ,
676+ async ( ) => {
677+ try {
678+ followerInfo = await setupMultiArchFollower ( followerArch ) ;
679+ stateHelper . setFollowerVmId ( followerInfo . vmId ) ;
680+ stateHelper . setFollowerArch ( followerInfo . arch ) ;
681+ stateHelper . setFollowerBuildkitdAddr ( followerInfo . buildkitdAddr ) ;
682+ core . info (
683+ `Multi-arch follower ready: arch=${ followerInfo . arch } addr=${ followerInfo . buildkitdAddr } ` ,
684+ ) ;
685+ } catch ( error ) {
686+ core . warning (
687+ `Failed to set up multi-arch follower: ${ ( error as Error ) . message } . ` +
688+ `Multi-arch builds will fall back to QEMU emulation.` ,
689+ ) ;
690+ followerInfo = null ;
691+ }
692+ } ,
693+ ) ;
694+ }
695+
668696 // Create and configure the builder
669697 await core . group ( `Creating builder instance` , async ( ) => {
670698 const name = `blacksmith-${ Date . now ( ) . toString ( 36 ) } ` ;
671699 stateHelper . setBuilderName ( name ) ;
672700
673- // Create the builder with platform configuration
674- const createArgs = [ "create" , "--name" , name , "--driver" , "remote" ] ;
701+ if ( followerInfo ) {
702+ // Multi-arch: create builder with host arch node, then append follower node.
703+ const hostArch = resolveRemoteBuilderPlatforms ( ) ;
704+ core . info (
705+ `Creating multi-arch builder: host=${ hostArch } , follower=linux/${ followerInfo . arch } ` ,
706+ ) ;
707+
708+ // Create first node for host architecture.
709+ const createArgs = [
710+ "create" ,
711+ "--name" ,
712+ name ,
713+ "--driver" ,
714+ "remote" ,
715+ "--platform" ,
716+ hostArch ,
717+ builderInfo . addr ! ,
718+ ] ;
719+ const createCmd = await toolkit . buildx . getCommand ( createArgs ) ;
720+ core . info (
721+ `Creating host builder node: ${ createCmd . command } ${ createCmd . args . join ( " " ) } ` ,
722+ ) ;
723+ await Exec . getExecOutput ( createCmd . command , createCmd . args , {
724+ ignoreReturnCode : true ,
725+ } ) . then ( ( res ) => {
726+ if ( res . stderr . length > 0 && res . exitCode != 0 ) {
727+ throw new Error (
728+ / ( .* ) \ s * $ / . exec ( res . stderr ) ?. [ 0 ] ?. trim ( ) ?? "unknown error" ,
729+ ) ;
730+ }
731+ } ) ;
732+
733+ // Append follower node for the other architecture.
734+ const appendArgs = [
735+ "create" ,
736+ "--name" ,
737+ name ,
738+ "--append" ,
739+ "--driver" ,
740+ "remote" ,
741+ "--platform" ,
742+ `linux/${ followerInfo . arch } ` ,
743+ followerInfo . buildkitdAddr ,
744+ ] ;
745+ const appendCmd = await toolkit . buildx . getCommand ( appendArgs ) ;
746+ core . info (
747+ `Appending follower builder node: ${ appendCmd . command } ${ appendCmd . args . join ( " " ) } ` ,
748+ ) ;
749+ await Exec . getExecOutput ( appendCmd . command , appendCmd . args , {
750+ ignoreReturnCode : true ,
751+ } ) . then ( ( res ) => {
752+ if ( res . stderr . length > 0 && res . exitCode != 0 ) {
753+ throw new Error (
754+ / ( .* ) \ s * $ / . exec ( res . stderr ) ?. [ 0 ] ?. trim ( ) ?? "unknown error" ,
755+ ) ;
756+ }
757+ } ) ;
758+ } else {
759+ // Single-arch: create builder with all platforms on one node.
760+ const createArgs = [ "create" , "--name" , name , "--driver" , "remote" ] ;
675761
676- // Add platform flag - use user-supplied platforms or fallback to host arch
677- const platformFlag = resolveRemoteBuilderPlatforms ( inputs . platforms ) ;
678- core . info ( `Determined remote builder platform(s): ${ platformFlag } ` ) ;
679- createArgs . push ( "--platform" , platformFlag ) ;
762+ const platformFlag = resolveRemoteBuilderPlatforms ( inputs . platforms ) ;
763+ core . info ( `Determined remote builder platform(s): ${ platformFlag } ` ) ;
764+ createArgs . push ( "--platform" , platformFlag ) ;
680765
681- createArgs . push ( builderInfo . addr ! ) ;
766+ createArgs . push ( builderInfo . addr ! ) ;
682767
683- const createCmd = await toolkit . buildx . getCommand ( createArgs ) ;
768+ const createCmd = await toolkit . buildx . getCommand ( createArgs ) ;
684769
685- core . info (
686- `Creating builder with command: ${ createCmd . command } ${ createCmd . args . join ( " " ) } ` ,
687- ) ;
688- await Exec . getExecOutput ( createCmd . command , createCmd . args , {
689- ignoreReturnCode : true ,
690- } ) . then ( ( res ) => {
691- if ( res . stderr . length > 0 && res . exitCode != 0 ) {
692- throw new Error (
693- / ( .* ) \s * $ / . exec ( res . stderr ) ?. [ 0 ] ?. trim ( ) ?? "unknown error" ,
694- ) ;
695- }
696- } ) ;
770+ core . info (
771+ `Creating builder with command: ${ createCmd . command } ${ createCmd . args . join ( " " ) } ` ,
772+ ) ;
773+ await Exec . getExecOutput ( createCmd . command , createCmd . args , {
774+ ignoreReturnCode : true ,
775+ } ) . then ( ( res ) => {
776+ if ( res . stderr . length > 0 && res . exitCode != 0 ) {
777+ throw new Error (
778+ / ( .* ) \ s * $ / . exec ( res . stderr ) ?. [ 0 ] ?. trim ( ) ?? "unknown error" ,
779+ ) ;
780+ }
781+ } ) ;
782+ }
697783
698784 // Set as default builder
699785 const useCmd = await toolkit . buildx . getCommand ( [ "use" , name ] ) ;
@@ -703,7 +789,7 @@ void actionsToolkit.run(
703789 } ) . then ( ( res ) => {
704790 if ( res . stderr . length > 0 && res . exitCode != 0 ) {
705791 throw new Error (
706- / ( .* ) \s * $ / . exec ( res . stderr ) ?. [ 0 ] ?. trim ( ) ?? "unknown error" ,
792+ / ( .* ) \ s * $ / . exec ( res . stderr ) ?. [ 0 ] ?. trim ( ) ?? "unknown error" ,
707793 ) ;
708794 }
709795 } ) ;
@@ -724,7 +810,9 @@ void actionsToolkit.run(
724810 try {
725811 const builder = await toolkit . builder . inspect ( ) ;
726812 if ( builder && builder . driver !== "docker" ) {
727- core . info ( `Found configured builder: ${ builder . name } (driver: ${ builder . driver } )` ) ;
813+ core . info (
814+ `Found configured builder: ${ builder . name } (driver: ${ builder . driver } )` ,
815+ ) ;
728816 } else {
729817 // Create a local builder
730818 const createLocalBuilderCmd =
@@ -750,6 +838,14 @@ void actionsToolkit.run(
750838 } ,
751839 // post action - cleanup
752840 async ( ) => {
841+ // Clean up follower sandbox if one was created.
842+ const followerVmId = stateHelper . getFollowerVmId ( ) ;
843+ if ( followerVmId ) {
844+ await core . group ( "Cleaning up multi-arch follower sandbox" , async ( ) => {
845+ await teardownFollower ( followerVmId ) ;
846+ } ) ;
847+ }
848+
753849 await core . group ( "Cleaning up Docker builder" , async ( ) => {
754850 const exposeId = stateHelper . getExposeId ( ) ;
755851 let cleanupError : Error | null = null ;
0 commit comments