code-intelligence.com/cifuzz@v0.40.0/internal/cmd/bundle/bundle.go (about) 1 package bundle 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "path/filepath" 8 "runtime" 9 10 "github.com/pkg/errors" 11 "github.com/pterm/pterm" 12 "github.com/spf13/cobra" 13 14 "code-intelligence.com/cifuzz/internal/bundler" 15 "code-intelligence.com/cifuzz/internal/cmdutils" 16 "code-intelligence.com/cifuzz/internal/cmdutils/logging" 17 "code-intelligence.com/cifuzz/internal/cmdutils/resolve" 18 "code-intelligence.com/cifuzz/internal/completion" 19 "code-intelligence.com/cifuzz/internal/config" 20 "code-intelligence.com/cifuzz/pkg/log" 21 ) 22 23 type options struct { 24 bundler.Opts `mapstructure:",squash"` 25 } 26 27 func (opts *options) Validate() error { 28 err := config.ValidateBuildSystem(opts.BuildSystem) 29 if err != nil { 30 log.Error(err) 31 return cmdutils.WrapSilentError(err) 32 } 33 34 if opts.BuildSystem == config.BuildSystemNodeJS && !config.AllowUnsupportedPlatforms() { 35 err = errors.Errorf(config.NotSupportedErrorMessage("bundle", opts.BuildSystem)) 36 log.Error(err) 37 return cmdutils.WrapSilentError(err) 38 } 39 40 return opts.Opts.Validate() 41 } 42 43 func New() *cobra.Command { 44 return newWithOptions(&options{}) 45 } 46 47 func newWithOptions(opts *options) *cobra.Command { 48 var bindFlags func() 49 cmd := &cobra.Command{ 50 Use: "bundle [flags] [<fuzz test>]...", 51 Short: "Bundles fuzz tests into an archive", 52 Long: `This command bundles all runtime artifacts required by the 53 given fuzz tests into a self-contained archive (bundle) that can be executed 54 on CI Sense. 55 56 The inputs found in the inputs directory of the fuzz test are also added 57 to the bundle in addition to optional input directories specified with 58 the seed-corpus flag. 59 More details about the build system specific inputs directory location 60 can be found in the help message of the run command. 61 62 The usage of this command depends on the build system 63 configured for the project. 64 65 This command will select an appropriate Docker image for execution based 66 on the build system. This can be overridden with a docker-image flag. 67 68 ` + pterm.Style{pterm.Reset, pterm.Bold}.Sprint("CMake") + ` 69 <fuzz test> is the name of the fuzz test defined in the add_fuzz_test 70 command in your CMakeLists.txt. 71 72 Command completion for the <fuzz test> argument is supported when the 73 fuzz test was built before or after running 'cifuzz reload'. 74 75 The --build-command flag is ignored. 76 77 Additional CMake arguments can be passed after a "--". For example: 78 79 cifuzz run my_fuzz_test -- -G Ninja 80 81 If no fuzz tests are specified, all fuzz tests are added to the bundle. 82 83 ` + pterm.Style{pterm.Reset, pterm.Bold}.Sprint("Bazel") + ` 84 <fuzz test> is the name of the cc_fuzz_test target as defined in your 85 BUILD file, either as a relative or absolute Bazel label. 86 87 Command completion for the <fuzz test> argument is supported. 88 89 The '--build-command' flag is ignored. 90 91 Additional Bazel arguments can be passed after a "--". For example: 92 93 cifuzz run my_fuzz_test -- --sandbox_debug 94 95 ` + pterm.Style{pterm.Reset, pterm.Bold}.Sprint("Maven/Gradle") + ` 96 <fuzz test> is the name of the class containing the fuzz test. 97 98 Command completion for the <fuzz test> argument is supported. 99 100 The --build-command flag is ignored. 101 102 If no fuzz tests are specified, all fuzz tests are added to the bundle. 103 104 ` + pterm.Style{pterm.Reset, pterm.Bold}.Sprint("Other build systems") + ` 105 <fuzz test> is either the path or basename of the fuzz test executable 106 created by the build command. If it's the basename, it will be searched 107 for recursively in the current working directory. 108 109 A command which builds the fuzz test executable must be provided via 110 the --build-command flag or the build-command setting in cifuzz.yaml. 111 112 The value specified for <fuzz test> is made available to the build 113 command in the FUZZ_TEST environment variable. For example: 114 115 echo "build-command: make clean && make \$FUZZ_TEST" >> cifuzz.yaml 116 cifuzz run my_fuzz_test 117 118 To avoid cleaning the build artifacts after building each fuzz test, you 119 can provide a clean command using the --clean-command flag or specifying 120 the "clean-command" option in cifuzz.yaml. The clean command is then 121 executed once before building the fuzz tests. 122 123 `, 124 ValidArgsFunction: completion.ValidFuzzTests, 125 Args: cobra.ArbitraryArgs, 126 PreRunE: func(cmd *cobra.Command, args []string) error { 127 // Bind viper keys to flags. We can't do this in the New 128 // function, because that would re-bind viper keys which 129 // were bound to the flags of other commands before. 130 bindFlags() 131 132 err := SetUpBundleLogging(cmd, &opts.Opts) 133 if err != nil { 134 log.Errorf(err, "Failed to setup logging: %v", err.Error()) 135 return cmdutils.WrapSilentError(err) 136 } 137 138 var argsToPass []string 139 if cmd.ArgsLenAtDash() != -1 { 140 argsToPass = args[cmd.ArgsLenAtDash():] 141 args = args[:cmd.ArgsLenAtDash()] 142 } 143 144 err = config.FindAndParseProjectConfig(opts) 145 if err != nil { 146 log.Errorf(err, "Failed to parse cifuzz.yaml: %v", err.Error()) 147 return cmdutils.WrapSilentError(err) 148 } 149 150 // Fail early if the platform is not supported. Creating the 151 // bundle actually works on all platforms, but the backend 152 // currently only supports running a bundle on Linux, so the 153 // user can't do anything useful with a bundle created on 154 // other platforms. 155 // 156 // We set CIFUZZ_ALLOW_UNSUPPORTED_PLATFORMS in tests to 157 // still be able to test that creating the bundle works on 158 // all platforms. 159 isOSIndependent := opts.BuildSystem == config.BuildSystemMaven || 160 opts.BuildSystem == config.BuildSystemGradle 161 if runtime.GOOS != "linux" && !isOSIndependent && 162 !config.AllowUnsupportedPlatforms() { 163 err = errors.Errorf(config.NotSupportedErrorMessage("bundle", runtime.GOOS)) 164 log.Error(err) 165 return cmdutils.WrapSilentError(err) 166 } 167 168 fuzzTests, err := resolve.FuzzTestArguments(opts.ResolveSourceFilePath, args, opts.BuildSystem, opts.ProjectDir) 169 opts.FuzzTests = fuzzTests 170 opts.BuildSystemArgs = argsToPass 171 172 return opts.Validate() 173 }, 174 RunE: func(c *cobra.Command, args []string) error { 175 logging.StartBuildProgressSpinner(log.BundleInProgressMsg) 176 177 _, err := bundler.New(&opts.Opts).Bundle() 178 if err != nil { 179 logging.StopBuildProgressSpinnerOnError(log.BundleInProgressErrorMsg) 180 var execErr *cmdutils.ExecError 181 if errors.As(err, &execErr) { 182 // It is expected that some commands might fail due to user 183 // configuration so we print the error without the stack trace 184 // (in non-verbose mode) and silence it 185 log.Error(err) 186 return cmdutils.ErrSilent 187 } 188 189 return err 190 } 191 192 logging.StopBuildProgressSpinnerOnSuccess(log.BundleInProgressSuccessMsg, true) 193 log.Successf("Successfully created bundle: %s", opts.OutputPath) 194 195 return nil 196 }, 197 } 198 199 bindFlags = cmdutils.AddFlags(cmd, 200 cmdutils.AddAdditionalFilesFlag, 201 cmdutils.AddBranchFlag, 202 cmdutils.AddBuildCommandFlag, 203 cmdutils.AddCleanCommandFlag, 204 cmdutils.AddBuildJobsFlag, 205 cmdutils.AddCommitFlag, 206 cmdutils.AddDictFlag, 207 cmdutils.AddDockerImageFlag, 208 cmdutils.AddEngineArgFlag, 209 cmdutils.AddEnvFlag, 210 cmdutils.AddProjectDirFlag, 211 cmdutils.AddSeedCorpusFlag, 212 cmdutils.AddTimeoutFlag, 213 cmdutils.AddResolveSourceFileFlag, 214 ) 215 cmd.Flags().StringVarP(&opts.OutputPath, "output", "o", "", "Output path of the bundle (.tar.gz)") 216 217 return cmd 218 } 219 220 // SetUpBundleLogging configures the verbose log and build log file for the bundle command. 221 func SetUpBundleLogging(cmd *cobra.Command, opts *bundler.Opts) error { 222 var err error 223 224 logDir, err := logging.CreateLogDir(opts.ProjectDir) 225 if err != nil { 226 return err 227 } 228 logSuffix := logging.SuffixForLog(opts.FuzzTests) 229 opts.BundleBuildLogFile = filepath.Join(logDir, fmt.Sprintf("%s.log", logSuffix)) 230 231 log.VerboseSecondaryOutput, err = os.OpenFile(opts.BundleBuildLogFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644) 232 if err != nil { 233 return err 234 } 235 236 if logging.ShouldLogBuildToFile() { 237 var buildStdout io.Writer 238 buildStdout, err = logging.BuildOutputToFile(opts.ProjectDir, opts.FuzzTests) 239 if err != nil { 240 return err 241 } 242 243 opts.BuildStdout = io.MultiWriter(buildStdout, log.VerboseSecondaryOutput) 244 opts.BuildStderr = io.MultiWriter(opts.BuildStdout, log.VerboseSecondaryOutput) 245 return nil 246 } 247 248 opts.BuildStdout = io.MultiWriter(cmd.OutOrStdout(), log.VerboseSecondaryOutput) 249 opts.BuildStderr = io.MultiWriter(cmd.OutOrStderr(), log.VerboseSecondaryOutput) 250 return nil 251 }