| 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 menuconfigCmd = &cobra.Command{ | |
| Use: "menuconfig", | |
| Short: "Run menuconfig and update the corresponding config in-place", | |
| Long: `Prepares the current branch as defined by 'cos-kernel prepare', then runs | |
| menuconfig. The provided config file will be copied to .config before the menuconfig | |
| command, and the output .config will be written back to the original location of the | |
| config file after menuconfig runs.`, | |
| 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.Menuconfig(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, menuconfigCmd, 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.StringSliceVarP(&configs, "configs", "c", nil, | |
| "config name for compiling the kernel; extra arguments after the first will be treated as config fragments") | |
| cmdFlags.StringSliceVarP(&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, menuconfigCmd} | |
| for _, cmd := range cmds { | |
| rootCmd.AddCommand(cmd) | |
| } | |
| } |