blob: 42b97be9567eee210b69e7a7d5cccc8f3b41e0bd [file] [edit]
package actions
import (
"context"
"errors"
"fmt"
"os"
"path"
"cos.googlesource.com/cos/tools.git/src/pkg/cos"
"cos.googlesource.com/cos/tools.git/src/pkg/git"
"cos.googlesource.com/cos/tools.git/src/pkg/kernel"
"github.com/spf13/cobra"
)
var configs, patches []string
var toolchain, toolchainArch, feature string
var clean, cleanFailedPatches bool
// loadKernelVariantConfig reads a kernel variant config from the path
// specified in the configFile variable, filling in values from command
// line flags or defaults where appropriate.
//
// This will also update the corresponding global values to match the
// values read from the config if they are not yet set.
func loadKernelVariantConfig() (kernel.KernelVariantConfig, error) {
var conf kernel.KernelVariantConfig
if configFile != "" {
loadedConf, err := kernel.ReadConfig(configFile)
if err != nil {
return conf, fmt.Errorf("failed to load kernel config: %v", err)
}
conf = loadedConf
}
// Make sure that global vars and config vars match.
// Pattern is always to copy the value from the global variable to the
// config if it's set; otherwise, try to take the value from the conf.
if arch == "" {
arch = conf.Arch
} else {
conf.Arch = arch
}
if build == "" {
build = conf.Build
} else {
conf.Build = build
}
if toolchainArch == "" {
toolchainArch = conf.ToolchainArch
} else {
conf.ToolchainArch = toolchainArch
}
if board == "" {
board = conf.Board
} else {
conf.Board = board
}
if configs == nil {
configs = conf.Configs
} else {
conf.Configs = configs
}
if patches == nil {
patches = conf.Patches
} else {
conf.Patches = patches
}
if toolchain == "" {
toolchain = conf.Toolchain
} else {
conf.Toolchain = toolchain
}
// conf and global variables now match.
// Fill in default values for any unset configs.
// General pattern is to check the global value, update it if it's unset, then
// update the config value to match.
if arch == "" {
arch = "x86_64"
fmt.Fprintf(os.Stderr, "Using default arch %s\n", arch)
}
conf.Arch = arch
if toolchainArch == "" {
toolchainArch = arch
if arch == "arm64" {
toolchainArch = "aarch64"
}
fmt.Fprintf(os.Stderr, "Using default toolchain arch %s\n", toolchainArch)
}
conf.ToolchainArch = toolchainArch
if board == "" {
board = "lakitu"
if arch == "arm64" {
board = "lakitu-arm64"
}
fmt.Fprintf(os.Stderr, "Using default board %s\n", board)
}
conf.Board = board
if len(configs) == 0 {
configs = append(configs, "lakitu_defconfig")
fmt.Fprintf(os.Stderr, "Using default config %s\n", configs[0])
}
conf.Configs = configs
guessedBuild, err := cos.GuessBuildNumber(build)
if err != nil {
return conf, fmt.Errorf("could not determine build number from build string %s: %v", build, err)
}
if build != guessedBuild {
fmt.Fprintf(os.Stderr, "Inferred build number %s from build '%s'\n", guessedBuild, build)
}
build = guessedBuild
conf.Build = build
if toolchain == "" {
toolchain = path.Join(os.TempDir(), "toolchains", board+"-"+build)
fmt.Fprintf(os.Stderr, "Using default toolchain location %s\n", toolchain)
}
conf.Toolchain = toolchain
return conf, nil
}
var prepareCmd = &cobra.Command{
Use: "prepare",
Short: "Prepare the local git repo for building",
Long: `Applies the provided patches to the local git repo and generates a new .config
based on the provided base config and config fragments. If the toolchain is not
already downloaded, this will download the toolchain to a temporary directory.
When passing multiple configs, the first config is treated as the base, and any
other configs are treated as fragments.
In order to avoid clobbering user work, the git repo must be clean before
applying patches or switching branches. When patches are applied, an end commit
will be created so that other commands can determine which patches have already
been applied in order to avoid re-applying them. The end commit will include a
cos-kernel-patches-applied.txt which lists all of the applied patches.`,
RunE: func(cmd *cobra.Command, args []string) error {
conf, err := loadKernelVariantConfig()
if err != nil {
return fmt.Errorf("couldn't load kernel config: %v", err)
}
ctx := context.Background()
downloader, err := defaultDownloader(ctx)
if err != nil {
return fmt.Errorf("failed to create artifact downloader: %v", err)
}
return kernel.Prepare(ctx, downloader, cleanFailedPatches, conf)
},
}
var prepareBranchCmd = &cobra.Command{
Use: "prepare-branch <branch-name>",
Short: "Prepare a new branch for a feature",
Long: `Create and switch to a new branch with the given feature name, prepare the
branch as defined by 'cos-kernel prepare', then set up the branch for creating
patches via 'cos-kernel make-patches'.
The branch setup will create a base commit that includes a
cos-kernel-patch-base.txt file containing the feature name. This can be used
by 'cos-kernel make-patches' to determine which commits are exclusive to the
new branch and should be converted to patches.`,
RunE: func(cmd *cobra.Command, args []string) error {
conf, err := loadKernelVariantConfig()
if err != nil {
return fmt.Errorf("couldn't load kernel config: %v", err)
}
ctx := context.Background()
downloader, err := defaultDownloader(ctx)
if err != nil {
return fmt.Errorf("failed to create artifact downloader: %v", err)
}
if len(args) >= 1 {
branchName := args[0]
return kernel.PrepareBranch(ctx, downloader, cleanFailedPatches, conf, branchName)
} else {
return errors.New("prepare-branch requires a branch name as its first argument; got no branch name")
}
},
}
var makePatchesCmd = &cobra.Command{
Use: "make-patches [revision-range]",
Short: "Make a patch set out of the commits on the current feature branch",
Long: `Makes a patch set out of the commits which have been applied on the current
feature branch and copies them to the google/patches/<feature-name> directory.
If a feature name is not explicitly passed, this reads the
cos-kernel-patch-base.txt file to determine the feature name from the
'cos-kernel prepare-branch' call that was used to create the branch. If a
revision range is not explicitly passed, this will scan the commit history for
the commit which added cos-kernel-patch-base.txt for the feature in order to
determine the set of patches to include.`,
RunE: func(cmd *cobra.Command, args []string) error {
var err error
if feature == "" {
feature, err = kernel.ReadPatchBase()
if err != nil {
return fmt.Errorf("failed to determine feature name; either run `cos-kernel prepare-branch <feature>` first, or pass a value with --feature: %v", err)
}
}
revisionRange := ""
if len(args) >= 1 {
revisionRange = args[0]
} else {
patchBaseCommit, err := kernel.FindPatchBaseCommit(feature)
if err != nil {
return fmt.Errorf("could not find patch base commit for feature %s: %v", feature, err)
}
if patchBaseCommit == "" {
return fmt.Errorf(
"could not find patch base commit for feature %s; make sure to run `cos-kernel prepare-branch %s` first or manually specify a revision range",
feature, feature,
)
}
revisionRange = patchBaseCommit + ".."
}
outDir := path.Join("google/patches", feature)
return git.FormatPatch(revisionRange, outDir, os.Stderr)
},
}
var makeCmd = &cobra.Command{
Use: "make [targets] [-- make-opts]",
Short: "Run arbitrary make commands for the kernel",
Long: `Prepares the current branch as defined by 'cos-kernel prepare', then runs the
provided make targets. make options that overlap with the cos-kernel arguments
can be passed after a --.`,
RunE: func(cmd *cobra.Command, args []string) error {
conf, err := loadKernelVariantConfig()
if err != nil {
return fmt.Errorf("couldn't load kernel config: %v", err)
}
ctx := context.Background()
downloader, err := defaultDownloader(ctx)
if err != nil {
return fmt.Errorf("failed to create artifact downloader: %v", err)
}
if err := kernel.Prepare(ctx, downloader, cleanFailedPatches, conf); err != nil {
return fmt.Errorf("failed to prepare kernel for kmake: %v", err)
}
return kernel.Kmake(conf, args)
},
}
var buildCmd = &cobra.Command{
Use: "build [-- make-opts]",
Short: "Build the kernel and kernel modules",
Long: `Prepares the current branch as defined by 'cos-kernel prepare', then builds the
kernel and kernel modules and packages them into a tarball. The last line of
the output to stdout will be the name of the tarball so that the command can be
chained with others. Extra arguments to the make calls can be passed after a --.`,
RunE: func(cmd *cobra.Command, args []string) error {
conf, err := loadKernelVariantConfig()
if err != nil {
return fmt.Errorf("couldn't load kernel config: %v", err)
}
ctx := context.Background()
downloader, err := defaultDownloader(ctx)
if err != nil {
return fmt.Errorf("failed to create artifact downloader: %v", err)
}
if clean {
if err := kernel.Kmake(conf, []string{"mrproper"}); err != nil {
return fmt.Errorf("failed to clean directory: %v", err)
}
}
if err := kernel.Prepare(ctx, downloader, cleanFailedPatches, conf); err != nil {
return fmt.Errorf("failed to prepare kernel for kmake: %v", err)
}
tarName, err := kernel.BuildKernelTarball(conf, args)
if err != nil {
return err
}
fmt.Println(tarName)
return nil
},
}
func init() {
// Add common flags for any command which takes a kernel config.
kernelConfigCommands := []*cobra.Command{prepareCmd, prepareBranchCmd, makeCmd, buildCmd}
for _, cmd := range kernelConfigCommands {
cmdFlags := cmd.PersistentFlags()
addCommonFlags(cmdFlags)
cmdFlags.StringVar(&toolchain, "toolchain", "",
"path to the directory containing the COS toolchain; if unset, a temporary directory will be used")
cmdFlags.StringVar(&toolchainArch, "toolchain-arch", "",
"the architecture of the toolchain")
cmdFlags.StringArrayVarP(&configs, "configs", "c", nil,
"config name for compiling the kernel; extra arguments after the first will be treated as config fragments")
cmdFlags.StringArrayVarP(&patches, "patches", "p", nil,
"patch files or directories under google/patches to apply to the source tree")
cmdFlags.StringVarP(&configFile, "config-file", "f", "",
"json file containing command line argument defaults")
cmdFlags.BoolVarP(&cleanFailedPatches, "clean-failed-patches", "", true,
"unapply any patches which fail to apply when preparing the kernel sources")
}
buildPflags := buildCmd.PersistentFlags()
buildPflags.BoolVarP(&clean, "clean", "", false,
"run mrproper before building kernel")
makePatchesPflags := makePatchesCmd.PersistentFlags()
makePatchesPflags.StringVar(&feature, "feature", "",
"name to use for the directory where the feature patches will be output; if unset, the name used during 'cos-kernel prepare-branch' will be used")
cmds := []*cobra.Command{prepareCmd, prepareBranchCmd, makePatchesCmd, makeCmd, buildCmd}
for _, cmd := range cmds {
rootCmd.AddCommand(cmd)
}
}