| 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) |
| } |
| } |