kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/extractors/config/runextractor/cmakecmd/cmakecmd.go (about) 1 /* 2 * Copyright 2018 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Package cmakecmd extracts from a CMake-based repository. 18 package cmakecmd // import "kythe.io/kythe/go/extractors/config/runextractor/cmakecmd" 19 20 import ( 21 "context" 22 "flag" 23 "fmt" 24 "io/ioutil" 25 "os" 26 "os/exec" 27 "path/filepath" 28 29 "kythe.io/kythe/go/extractors/config/runextractor/compdb" 30 "kythe.io/kythe/go/util/cmdutil" 31 "kythe.io/kythe/go/util/flagutil" 32 "kythe.io/kythe/go/util/log" 33 34 "github.com/google/subcommands" 35 ) 36 37 type cmakeCommand struct { 38 cmdutil.Info 39 40 extractor string 41 buildDir string 42 sourceDir string 43 extraCmakeArgs flagutil.StringList 44 } 45 46 // New creates a new subcommand for running cmake extraction. 47 func New() subcommands.Command { 48 return &cmakeCommand{ 49 Info: cmdutil.NewInfo("cmake", "extract a repo build with CMake", `documight`), 50 } 51 } 52 53 // SetFlags implements the subcommands interface and provides command-specific 54 // flags for cmake extraction. 55 func (c *cmakeCommand) SetFlags(fs *flag.FlagSet) { 56 fs.StringVar(&c.extractor, "extractor", "", "A required path to the extractor binary to use.") 57 fs.StringVar(&c.sourceDir, "sourcedir", ".", "A required path to the repository root. Defaults to the current directory.") 58 fs.StringVar(&c.buildDir, "builddir", "", "An optional path to the directory in which to build. If empty, defaults to a unique subdirectory of sourcedir.") 59 fs.Var(&c.extraCmakeArgs, "extra_cmake_args", "A comma-separated list of extra arguments to pass to CMake.") 60 } 61 62 func (c *cmakeCommand) checkFlags() error { 63 for _, key := range []string{"KYTHE_CORPUS", "KYTHE_ROOT_DIRECTORY", "KYTHE_OUTPUT_DIRECTORY"} { 64 if os.Getenv(key) == "" { 65 return fmt.Errorf("required %s not set", key) 66 } 67 } 68 if c.extractor == "" { 69 return fmt.Errorf("required -extractor not set") 70 } 71 if c.sourceDir == "" { 72 return fmt.Errorf("required -sourcedir not set") 73 } 74 return nil 75 } 76 77 // Execute implements the subcommands interface and runs cmake extraction. 78 func (c *cmakeCommand) Execute(ctx context.Context, fs *flag.FlagSet, args ...any) subcommands.ExitStatus { 79 if err := c.checkFlags(); err != nil { 80 return c.Fail("Incorrect flags: %v", err) 81 } 82 // Since we have to change our working directory, resolve all of our paths early. 83 extractor, err := filepath.Abs(c.extractor) 84 if err != nil { 85 return c.Fail("Unable to resolve path to extractor: %v", err) 86 } 87 sourceDir, err := filepath.Abs(c.sourceDir) 88 if err != nil { 89 return c.Fail("Unable to resolve source directory: %v", err) 90 } 91 var buildDir string 92 if c.buildDir == "" { 93 // sourceDir is already an absolute directory 94 if buildDir, err = ioutil.TempDir(sourceDir, "build-"); err != nil { 95 // Unlike below, we need to fail if the "unique" directory exists. 96 return c.Fail("Unable to create build directory: %v", err) 97 } 98 // Only clean up the build directory if it was unspecified. 99 defer cleanBuild(buildDir) 100 } else { 101 buildDir, err = filepath.Abs(c.buildDir) 102 if err != nil { 103 return c.Fail("Unable to resolve build directory: %v", err) 104 } 105 } 106 107 // Create the build directory if it doesn't already exist. 108 if err := os.MkdirAll(buildDir, 0755); err != nil { 109 return c.Fail("Unable to create build directory: %v", err) 110 } 111 112 // The Kythe extractor is clang based, so tell cmake to use clang so the right 113 // commands are generated. 114 cmakeArgs := []string{"-DCMAKE_CXX_COMPILER=clang++", "-DCMAKE_C_COMPILER=clang", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", sourceDir} 115 cmakeArgs = append(cmakeArgs, c.extraCmakeArgs...) 116 if err := runIn(exec.CommandContext(ctx, "cmake", cmakeArgs...), buildDir); err != nil { 117 return c.Fail("Error configuring cmake: %v", err) 118 } 119 120 if err := runIn(exec.CommandContext(ctx, "cmake", "--build", "."), buildDir); err != nil { 121 return c.Fail("Error building repository: %v", err) 122 } 123 124 if err := compdb.ExtractCompilations(ctx, extractor, filepath.Join(buildDir, "compile_commands.json"), nil); err != nil { 125 return c.Fail("Error extracting repository: %v", err) 126 } 127 return subcommands.ExitSuccess 128 } 129 130 // cleanBuild removes dir and all files beneath it, logging an error if it fails. 131 func cleanBuild(dir string) { 132 if err := os.RemoveAll(dir); err != nil { 133 log.Errorf("unable to remove build directory: %v", err) 134 } 135 } 136 137 // runIn changes the cmd working directory to dir and calls Run. The cmd's 138 // stderr is forwarded to stderr. 139 func runIn(cmd *exec.Cmd, dir string) error { 140 cmd.Dir = dir 141 cmd.Stderr = os.Stderr 142 log.Infof("Running: %q in %q", cmd, cmd.Dir) 143 return cmd.Run() 144 }