blob: 8b65cd5f7dde33d3a843f9229f088cb61ea2418d [file] [edit]
package actions
import (
"context"
"errors"
"fmt"
"os"
"path"
"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 toolchainDir, toolchainArch, feature string
var clean 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
}
if arch == "" {
arch = conf.Arch
} else {
conf.Arch = arch
}
if conf.ToolchainArch == "" {
conf.ToolchainArch = arch
if arch == "arm64" {
conf.ToolchainArch = "aarch64"
}
}
if toolchainArch == "" {
toolchainArch = conf.ToolchainArch
} else {
conf.ToolchainArch = toolchainArch
}
if build == "" {
build = conf.Build
} else {
conf.Build = build
}
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 conf.Toolchain == "" {
conf.Toolchain = path.Join(os.TempDir(), "toolchains", board+"-"+build)
}
if toolchainDir == "" {
toolchainDir = conf.Toolchain
} else {
conf.Toolchain = toolchainDir
}
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, 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, 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, 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, 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(&toolchainDir, "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")
}
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)
}
}