Skip to content

Commit

Permalink
feat(action): add edk2 build function
Browse files Browse the repository at this point in the history
  • Loading branch information
AtomicFS committed Sep 12, 2023
1 parent d0b8a97 commit a64e305
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 5 deletions.
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"BOOTLOADER",
"HEALTHCHECK",
"Hadolint",
"IPXE",
"NOTSET",
"TARGETARCH",
"Taskfile",
Expand Down
7 changes: 6 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ inputs:
*defconfig_path* gives the (relative to repo_path) path to the defconfig that
should be used to build the target.
For coreboot and linux this is a defconfig.
For EDK2 this is a one-line file containing the build defines.
For EDK2 this is a one-line file containing the build arguments
such as '-t GCC5 -D BOOTLOADER=COREBOOT -D TPM_ENABLE=TRUE -D NETWORK_IPXE=TRUE'.
Some arguments will be added automatically from action inputs:
'-a <architecture>'
'-p <edk2__platform>'
'-b <edk2__release_type>'
required: true
output:
description: |
Expand Down
67 changes: 67 additions & 0 deletions action/recipes/edk2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT

// Package recipes / edk2
package recipes

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
"github.com/9elements/firmware-action/action/container"
)

// edk2 builds edk2
func edk2(ctx context.Context, client *dagger.Client, common *commonOpts, dockerfileDirectoryPath string, opts *edk2Opts, artifacts *[]container.Artifacts) error {
// TODO: get blobs in place!
envVars := map[string]string{
"WORKSPACE": common.containerWorkDir,
"EDK_TOOLS_PATH": "/tools/Edk2/BaseTools",
}

// Spin up container
containerOpts := container.SetupOpts{
ContainerURL: common.sdkVersion,
MountContainerDir: common.containerWorkDir,
MountHostDir: common.repoPath,
WorkdirContainer: common.containerWorkDir,
}
myContainer, err := container.Setup(ctx, client, &containerOpts, dockerfileDirectoryPath)
if err != nil {
return err
}

// There is no copying of config file at "defconfig_path"
// content of the config file at "defconfig_path" should be read into string instead

// Setup environment variables in the container
for key, value := range envVars {
myContainer = myContainer.WithEnvVariable(key, value)
}

// Assemble build arguments
buildArgs := fmt.Sprintf("-a %s -p %s -b %s", common.arch, opts.platform, opts.releaseType)
defconfigFileArgs, err := os.ReadFile(common.defconfigPath)
if err != nil {
return err
}

// Assemble commands to build
buildSteps := [][]string{
{"bash", "-c", fmt.Sprintf("source ./edksetup.sh; build %s %s", buildArgs, string(defconfigFileArgs))},
}

// Build
for step := range buildSteps {
myContainer, err = myContainer.
WithExec(buildSteps[step]).
Sync(ctx)
if err != nil {
return fmt.Errorf("%s build failed: %w", common.target, err)
}
}

// Extract artifacts
return container.GetArtifacts(ctx, myContainer, artifacts)
}
121 changes: 121 additions & 0 deletions action/recipes/edk2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: MIT

// Package recipes / edk2
package recipes

import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"

"dagger.io/dagger"
"github.com/9elements/firmware-action/action/container"
"github.com/stretchr/testify/assert"
)

func TestEdk2(t *testing.T) {
// This test is really slow (like 100 seconds)
if testing.Short() {
t.Skip("skipping test in short mode")
}

pwd, err := os.Getwd()
assert.NoError(t, err)
defer os.Chdir(pwd) // nolint:errcheck

// Use "" if you want to test containers from github package registry
// Use "../../docker/edk2" if you want to test containers built fresh from Dockerfile
dockerfilePath := ""
if false {
dockerfilePath, err = filepath.Abs("../../docker/edk2")
assert.NoError(t, err)
}

testCases := []struct {
name string
edk2Version string
platform string
arch string
release string
wantErr error
}{
{
name: "normal build",
edk2Version: "edk2-stable202105",
platform: "UefiPayloadPkg/UefiPayloadPkg.dsc",
arch: "X64",
release: "DEBUG",
wantErr: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stdout))
assert.NoError(t, err)
defer client.Close()

// Prepare options
tmpDir := t.TempDir()
opts := map[string]string{
"target": "edk2",
"sdk_version": fmt.Sprintf("%s:main", tc.edk2Version),
"architecture": tc.arch,
"repo_path": filepath.Join(tmpDir, "Edk2"),
"defconfig_path": "defconfig",
"containerWorkDir": "/Edk2",
"GITHUB_WORKSPACE": "/Edk2",
"output": "output",
}
getFunc := func(key string) string {
return opts[key]
}
common, err := commonGetOpts(getFunc)
assert.NoError(t, err)
edk2Opts := edk2Opts{
platform: tc.platform,
releaseType: tc.release,
}

// Change current working directory
err = os.Chdir(tmpDir)
assert.NoError(t, err)
defer os.Chdir(pwd) // nolint:errcheck

// Clone coreboot repo
cmd := exec.Command("git", "clone", "--recurse-submodules", "--branch", tc.edk2Version, "--depth", "1", "https://github.com/tianocore/edk2.git", "Edk2")
err = cmd.Run()
assert.NoError(t, err)
err = os.Chdir(common.repoPath)
assert.NoError(t, err)

// Create "defconfig_path" file
err = os.WriteFile(common.defconfigPath, []byte("-D BOOTLOADER=COREBOOT -a IA32 -t GCC5"), 0o644)
assert.NoError(t, err)

// Artifacts
outputPath := filepath.Join(tmpDir, common.outputDir)
err = os.MkdirAll(outputPath, os.ModePerm)
assert.NoError(t, err)
artifacts := []container.Artifacts{
{
ContainerPath: filepath.Join(common.containerWorkDir, "Build"),
ContainerDir: true,
HostPath: outputPath,
},
}

// Try to build edk2
err = edk2(ctx, client, &common, dockerfilePath, &edk2Opts, &artifacts)
assert.NoError(t, err)

// Check artifacts
fileInfo, err := os.Stat(filepath.Join(outputPath, "Build"))
assert.NoError(t, err)
assert.True(t, fileInfo.IsDir())
})
}
}
49 changes: 45 additions & 4 deletions action/recipes/recipes.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,38 @@ func linuxGetOpts(_ getValFunc) (linuxOpts, error) {
// EDK2
//======

// Used to store data from githubaction.Action
// For details see action.yml
type edk2Opts struct {
platform string
releaseType string
}

// edk2GetOpts is used to fill edk2Opts with data from githubaction.Action
func edk2GetOpts(get getValFunc) (edk2Opts, error) {
opts := edk2Opts{
platform: get("edk2__platform"),
releaseType: get("edk2__release_type"),
}

// Check if required options are not empty
missing := []string{}
requiredOptions := map[string]string{
"edk2__platform": opts.platform,
"edk2__release_type": opts.releaseType,
}
for key, val := range requiredOptions {
if val == "" {
missing = append(missing, key)
}
}
if len(missing) > 0 {
return opts, fmt.Errorf("%w: %s", errRequiredOptionUndefined, strings.Join(missing, ", "))
}

return opts, nil
}

//=====================
// Universal Functions
//=====================
Expand Down Expand Up @@ -186,10 +218,19 @@ func Execute(ctx context.Context, client *dagger.Client, action *githubactions.A
},
}
return linux(ctx, client, &common, "", &opts, &artifacts)
/*
case "edk2":
return edk2(ctx, action, client)
*/
case "edk2":
opts, err := edk2GetOpts(action.GetInput)
if err != nil {
return err
}
artifacts := []container.Artifacts{
{
ContainerPath: filepath.Join(common.containerWorkDir, "Build"),
ContainerDir: true,
HostPath: common.outputDir,
},
}
return edk2(ctx, client, &common, "", &opts, &artifacts)
case "":
return fmt.Errorf("no target specified")
default:
Expand Down

0 comments on commit a64e305

Please sign in to comment.