github.com/9elements/firmware-action/action@v0.0.0-20240514065043-044ed91c9ed8/recipes/edk2.go (about)

     1  // SPDX-License-Identifier: MIT
     2  
     3  // Package recipes / edk2
     4  package recipes
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"log/slog"
    11  	"os"
    12  
    13  	"dagger.io/dagger"
    14  	"github.com/9elements/firmware-action/action/container"
    15  )
    16  
    17  // Edk2Specific is used to store data specific to coreboot.
    18  /* TODO: removed because of issue #92
    19  type Edk2Specific struct {
    20  	// Gives the (relative) path to the defconfig that should be used to build the target.
    21  	// For EDK2 this is a one-line file containing the build arguments such as
    22  	//   '-D BOOTLOADER=COREBOOT -D TPM_ENABLE=TRUE -D NETWORK_IPXE=TRUE'.
    23  	//   Some arguments will be added automatically:
    24  	//     '-a <architecture>'
    25  	//     '-p <edk2__platform>'
    26  	//     '-b <edk2__release_type>'
    27  	//     '-t <GCC version>' (defined as part of container toolchain, selected by SdkURL)
    28  	DefconfigPath string `json:"defconfig_path" validate:"filepath"`
    29  
    30  	// Specifies the DSC to use when building EDK2
    31  	// Example: UefiPayloadPkg/UefiPayloadPkg.dsc
    32  	Platform string `json:"platform" validate:"filepath"`
    33  
    34  	// Specifies the build type to use when building EDK2
    35  	// Supported options: DEBUG, RELEASE
    36  	ReleaseType string `json:"release_type" validate:"required"`
    37  
    38  	// Specifies which build command to use
    39  	// Examples:
    40  	//   "source ./edksetup.sh; build"
    41  	//   "python UefiPayloadPkg/UniversalPayloadBuild.py"
    42  	//   "Intel/AlderLakeFspPkg/BuildFv.sh"
    43  	BuildCommand string `json:"build_command" validate:"required"`
    44  }
    45  */
    46  // ANCHOR: Edk2Specific
    47  // Edk2Specific is used to store data specific to coreboot.
    48  type Edk2Specific struct {
    49  	// Specifies which build command to use
    50  	// GCC version is exposed in the container container as USE_GCC_VERSION environment variable
    51  	// Examples:
    52  	//   "source ./edksetup.sh; build -t GCC5 -a IA32 -p UefiPayloadPkg/UefiPayloadPkg.dsc"
    53  	//   "python UefiPayloadPkg/UniversalPayloadBuild.py"
    54  	//   "Intel/AlderLakeFspPkg/BuildFv.sh"
    55  	BuildCommand string `json:"build_command" validate:"required"`
    56  }
    57  
    58  // ANCHOR_END: Edk2Specific
    59  
    60  // Edk2Opts is used to store all data needed to build edk2.
    61  type Edk2Opts struct {
    62  	// List of IDs this instance depends on
    63  	// Example: [ "MyLittleCoreboot", "MyLittleLinux"]
    64  	Depends []string `json:"depends"`
    65  
    66  	// Common options like paths etc.
    67  	CommonOpts
    68  
    69  	// Specifies target architecture, such as 'x86' or 'arm64'. Currently unused for coreboot.
    70  	// Supported options:
    71  	//   - 'AARCH64'
    72  	//   - 'ARM'
    73  	//   - 'IA32'
    74  	//   - 'IA32X64'
    75  	//   - 'X64'
    76  	Arch string `json:"arch"`
    77  
    78  	// Gives the (relative) path to the defconfig that should be used to build the target.
    79  	// For EDK2 this is a one-line file containing the build arguments such as
    80  	//   '-D BOOTLOADER=COREBOOT -D TPM_ENABLE=TRUE -D NETWORK_IPXE=TRUE'.
    81  	DefconfigPath string `json:"defconfig_path" validate:"filepath"`
    82  
    83  	// Coreboot specific options
    84  	Edk2Specific `validate:"required"`
    85  }
    86  
    87  // GetDepends is used to return list of dependencies
    88  func (opts Edk2Opts) GetDepends() []string {
    89  	return opts.Depends
    90  }
    91  
    92  // GetArtifacts returns list of wanted artifacts from container
    93  func (opts Edk2Opts) GetArtifacts() *[]container.Artifacts {
    94  	return opts.CommonOpts.GetArtifacts()
    95  }
    96  
    97  // buildFirmware builds edk2 or Intel FSP
    98  func (opts Edk2Opts) buildFirmware(ctx context.Context, client *dagger.Client, dockerfileDirectoryPath string) (*dagger.Container, error) {
    99  	envVars := map[string]string{
   100  		"WORKSPACE":      ContainerWorkDir,
   101  		"EDK_TOOLS_PATH": "/tools/Edk2/BaseTools",
   102  	}
   103  
   104  	// Spin up container
   105  	containerOpts := container.SetupOpts{
   106  		ContainerURL:      opts.SdkURL,
   107  		MountContainerDir: ContainerWorkDir,
   108  		MountHostDir:      opts.RepoPath,
   109  		WorkdirContainer:  ContainerWorkDir,
   110  	}
   111  
   112  	myContainer, err := container.Setup(ctx, client, &containerOpts, dockerfileDirectoryPath)
   113  	if err != nil {
   114  		slog.Error(
   115  			"Failed to start a container",
   116  			slog.Any("error", err),
   117  		)
   118  		return nil, err
   119  	}
   120  
   121  	// Setup environment variables in the container
   122  	for key, value := range envVars {
   123  		myContainer = myContainer.WithEnvVariable(key, value)
   124  	}
   125  
   126  	// Get GCC version from environment variable
   127  	/* TODO: removed because of issue #92
   128  	gccVersion, err := myContainer.EnvVariable(ctx, "USE_GCC_VERSION")
   129  	if err != nil {
   130  		return err
   131  	}
   132  	*/
   133  
   134  	// Figure out target architectures
   135  	/* TODO: removed because of issue #92
   136  	architectures := map[string]string{
   137  		"AARCH64": "-a AARCH64",
   138  		"ARM":     "-a ARM",
   139  		"IA32":    "-a IA32",
   140  		"IA32X64": "-a IA32 -a X64",
   141  		"X64":     "-a X64",
   142  	}
   143  	arch, ok := architectures[opts.Arch]
   144  	if !ok {
   145  		return fmt.Errorf("%w: %s", errUnknownArch, opts.Arch)
   146  	}
   147  	*/
   148  
   149  	// Assemble build arguments
   150  	//   and read content of the config file at "defconfig_path"
   151  	// NOTE: removed because of issue #92
   152  	// buildArgs := fmt.Sprintf("%s -p %s -b %s -t GCC%s", arch, opts.Specific.Platform, opts.Specific.ReleaseType, gccVersion)
   153  	var defconfigFileArgs []byte
   154  	if opts.DefconfigPath != "" {
   155  		if _, err := os.Stat(opts.DefconfigPath); !errors.Is(err, os.ErrNotExist) {
   156  			defconfigFileArgs, err = os.ReadFile(opts.DefconfigPath)
   157  			if err != nil {
   158  				return nil, err
   159  			}
   160  		} else {
   161  			slog.Warn(
   162  				fmt.Sprintf("Failed to read file '%s' as defconfig_path: file does not exist", opts.DefconfigPath),
   163  				slog.String("suggestion", "Double check the path for defconfig"),
   164  				slog.Any("error", err),
   165  			)
   166  		}
   167  	}
   168  
   169  	// Assemble commands to build
   170  	buildSteps := [][]string{
   171  		//{"bash", "-c", fmt.Sprintf("source ./edksetup.sh; build %s %s", buildArgs, string(defconfigFileArgs))},
   172  		{"bash", "-c", fmt.Sprintf("%s %s", opts.BuildCommand, string(defconfigFileArgs))},
   173  	}
   174  
   175  	// Build
   176  	var myContainerPrevious *dagger.Container
   177  	for step := range buildSteps {
   178  		myContainerPrevious = myContainer
   179  		myContainer, err = myContainer.
   180  			WithExec(buildSteps[step]).
   181  			Sync(ctx)
   182  		if err != nil {
   183  			slog.Error(
   184  				"Failed to build edk2",
   185  				slog.Any("error", err),
   186  			)
   187  			return myContainerPrevious, fmt.Errorf("edk2 build failed: %w", err)
   188  		}
   189  	}
   190  
   191  	// Extract artifacts
   192  	return myContainer, container.GetArtifacts(ctx, myContainer, opts.GetArtifacts())
   193  }