github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/please.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "net/http" 6 _ "net/http/pprof" 7 "os" 8 "path" 9 "runtime" 10 "runtime/pprof" 11 "strings" 12 "syscall" 13 "time" 14 15 "github.com/jessevdk/go-flags" 16 "gopkg.in/op/go-logging.v1" 17 18 "build" 19 "cache" 20 "clean" 21 "cli" 22 "core" 23 "export" 24 "follow" 25 "fs" 26 "gc" 27 "hashes" 28 "help" 29 "metrics" 30 "output" 31 "parse" 32 "query" 33 "run" 34 "sync" 35 "test" 36 "tool" 37 "update" 38 "utils" 39 "watch" 40 ) 41 42 var log = logging.MustGetLogger("plz") 43 44 var config *core.Configuration 45 46 var opts struct { 47 Usage string `usage:"Please is a high-performance multi-language build system.\n\nIt uses BUILD files to describe what to build and how to build it.\nSee https://please.build for more information about how it works and what Please can do for you."` 48 BuildFlags struct { 49 Config string `short:"c" long:"config" description:"Build config to use. Defaults to opt."` 50 Arch cli.Arch `short:"a" long:"arch" description:"Architecture to compile for."` 51 RepoRoot cli.Filepath `short:"r" long:"repo_root" description:"Root of repository to build."` 52 KeepGoing bool `short:"k" long:"keep_going" description:"Don't stop on first failed target."` 53 NumThreads int `short:"n" long:"num_threads" description:"Number of concurrent build operations. Default is number of CPUs + 2."` 54 Include []string `short:"i" long:"include" description:"Label of targets to include in automatic detection."` 55 Exclude []string `short:"e" long:"exclude" description:"Label of targets to exclude from automatic detection."` 56 Option ConfigOverrides `short:"o" long:"override" env:"PLZ_OVERRIDES" env-delim:";" description:"Options to override from .plzconfig (e.g. -o please.selfupdate:false)"` 57 Profile string `long:"profile" env:"PLZ_CONFIG_PROFILE" description:"Configuration profile to load; e.g. --profile=dev will load .plzconfig.dev if it exists."` 58 } `group:"Options controlling what to build & how to build it"` 59 60 OutputFlags struct { 61 Verbosity int `short:"v" long:"verbosity" description:"Verbosity of output (higher number = more output, default 1 -> warnings and errors only)" default:"1"` 62 LogFile cli.Filepath `long:"log_file" description:"File to echo full logging output to" default:"plz-out/log/build.log"` 63 LogFileLevel int `long:"log_file_level" description:"Log level for file output" default:"4"` 64 InteractiveOutput bool `long:"interactive_output" description:"Show interactive output ina terminal"` 65 PlainOutput bool `short:"p" long:"plain_output" description:"Don't show interactive output."` 66 Colour bool `long:"colour" description:"Forces coloured output from logging & other shell output."` 67 NoColour bool `long:"nocolour" description:"Forces colourless output from logging & other shell output."` 68 TraceFile cli.Filepath `long:"trace_file" description:"File to write Chrome tracing output into"` 69 ShowAllOutput bool `long:"show_all_output" description:"Show all output live from all commands. Implies --plain_output."` 70 CompletionScript bool `long:"completion_script" description:"Prints the bash / zsh completion script to stdout"` 71 Version bool `long:"version" description:"Print the version of the tool"` 72 } `group:"Options controlling output & logging"` 73 74 FeatureFlags struct { 75 NoUpdate bool `long:"noupdate" description:"Disable Please attempting to auto-update itself."` 76 NoCache bool `long:"nocache" description:"Disable caches (NB. not incrementality)"` 77 NoHashVerification bool `long:"nohash_verification" description:"Hash verification errors are nonfatal."` 78 NoLock bool `long:"nolock" description:"Don't attempt to lock the repo exclusively. Use with care."` 79 KeepWorkdirs bool `long:"keep_workdirs" description:"Don't clean directories in plz-out/tmp after successfully building targets."` 80 } `group:"Options that enable / disable certain features"` 81 82 Profile string `long:"profile_file" hidden:"true" description:"Write profiling output to this file"` 83 MemProfile string `long:"mem_profile_file" hidden:"true" description:"Write a memory profile to this file"` 84 ProfilePort int `long:"profile_port" hidden:"true" description:"Serve profiling info on this port."` 85 ParsePackageOnly bool `description:"Parses a single package only. All that's necessary for some commands." no-flag:"true"` 86 Complete string `long:"complete" hidden:"true" env:"PLZ_COMPLETE" description:"Provide completion options for this build target."` 87 VisibilityParse bool `description:"Parse all targets that the original targets are visible to. Used for some query steps." no-flag:"true"` 88 89 Build struct { 90 Prepare bool `long:"prepare" description:"Prepare build directory for these targets but don't build them."` 91 Shell bool `long:"shell" description:"Like --prepare, but opens a shell in the build directory with the appropriate environment variables."` 92 ShowStatus bool `long:"show_status" hidden:"true" description:"Show status of each target in output after build"` 93 Args struct { // Inner nesting is necessary to make positional-args work :( 94 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to build"` 95 } `positional-args:"true" required:"true"` 96 } `command:"build" description:"Builds one or more targets"` 97 98 Rebuild struct { 99 Args struct { 100 Targets []core.BuildLabel `positional-arg-name:"targets" required:"true" description:"Targets to rebuild"` 101 } `positional-args:"true" required:"true"` 102 } `command:"rebuild" description:"Forces a rebuild of one or more targets"` 103 104 Hash struct { 105 Detailed bool `long:"detailed" description:"Produces a detailed breakdown of the hash"` 106 Update bool `short:"u" long:"update" description:"Rewrites the hashes in the BUILD file to the new values"` 107 Args struct { 108 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to build"` 109 } `positional-args:"true" required:"true"` 110 } `command:"hash" description:"Calculates hash for one or more targets"` 111 112 Test struct { 113 FailingTestsOk bool `long:"failing_tests_ok" hidden:"true" description:"Exit with status 0 even if tests fail (nonzero only if catastrophe happens)"` 114 NumRuns int `long:"num_runs" short:"n" description:"Number of times to run each test target."` 115 TestResultsFile cli.Filepath `long:"test_results_file" default:"plz-out/log/test_results.xml" description:"File to write combined test results to."` 116 ShowOutput bool `short:"s" long:"show_output" description:"Always show output of tests, even on success."` 117 Debug bool `short:"d" long:"debug" description:"Allows starting an interactive debugger on test failure. Does not work with all test types (currently only python/pytest, C and C++). Implies -c dbg unless otherwise set."` 118 Failed bool `short:"f" long:"failed" description:"Runs just the test cases that failed from the immediately previous run."` 119 // Slightly awkward since we can specify a single test with arguments or multiple test targets. 120 Args struct { 121 Target core.BuildLabel `positional-arg-name:"target" description:"Target to test"` 122 Args []string `positional-arg-name:"arguments" description:"Arguments or test selectors"` 123 } `positional-args:"true"` 124 } `command:"test" description:"Builds and tests one or more targets"` 125 126 Cover struct { 127 FailingTestsOk bool `long:"failing_tests_ok" hidden:"true" description:"Exit with status 0 even if tests fail (nonzero only if catastrophe happens)"` 128 NoCoverageReport bool `long:"nocoverage_report" description:"Suppress the per-file coverage report displayed in the shell"` 129 LineCoverageReport bool `short:"l" long:"line_coverage_report" description:" Show a line-by-line coverage report for all affected files."` 130 NumRuns int `short:"n" long:"num_runs" description:"Number of times to run each test target."` 131 IncludeAllFiles bool `short:"a" long:"include_all_files" description:"Include all dependent files in coverage (default is just those from relevant packages)"` 132 IncludeFile []string `long:"include_file" description:"Filenames to filter coverage display to"` 133 TestResultsFile cli.Filepath `long:"test_results_file" default:"plz-out/log/test_results.xml" description:"File to write combined test results to."` 134 CoverageResultsFile cli.Filepath `long:"coverage_results_file" default:"plz-out/log/coverage.json" description:"File to write combined coverage results to."` 135 ShowOutput bool `short:"s" long:"show_output" description:"Always show output of tests, even on success."` 136 Debug bool `short:"d" long:"debug" description:"Allows starting an interactive debugger on test failure. Does not work with all test types (currently only python/pytest, C and C++). Implies -c dbg unless otherwise set."` 137 Failed bool `short:"f" long:"failed" description:"Runs just the test cases that failed from the immediately previous run."` 138 Args struct { 139 Target core.BuildLabel `positional-arg-name:"target" description:"Target to test" group:"one test"` 140 Args []string `positional-arg-name:"arguments" description:"Arguments or test selectors" group:"one test"` 141 } `positional-args:"true"` 142 } `command:"cover" description:"Builds and tests one or more targets, and calculates coverage."` 143 144 Run struct { 145 Env bool `long:"env" description:"Overrides environment variables (e.g. PATH) in the new process."` 146 Parallel struct { 147 NumTasks int `short:"n" long:"num_tasks" default:"10" description:"Maximum number of subtasks to run in parallel"` 148 Quiet bool `short:"q" long:"quiet" description:"Suppress output from successful subprocesses."` 149 PositionalArgs struct { 150 Targets []core.BuildLabel `positional-arg-name:"target" description:"Targets to run"` 151 } `positional-args:"true" required:"true"` 152 Args []string `short:"a" long:"arg" description:"Arguments to pass to the called processes."` 153 } `command:"parallel" description:"Runs a sequence of targets in parallel"` 154 Sequential struct { 155 Quiet bool `short:"q" long:"quiet" description:"Suppress output from successful subprocesses."` 156 PositionalArgs struct { 157 Targets []core.BuildLabel `positional-arg-name:"target" description:"Targets to run"` 158 } `positional-args:"true" required:"true"` 159 Args []string `short:"a" long:"arg" description:"Arguments to pass to the called processes."` 160 } `command:"sequential" description:"Runs a sequence of targets sequentially."` 161 Args struct { 162 Target core.BuildLabel `positional-arg-name:"target" required:"true" description:"Target to run"` 163 Args []string `positional-arg-name:"arguments" description:"Arguments to pass to target when running (to pass flags to the target, put -- before them)"` 164 } `positional-args:"true"` 165 } `command:"run" subcommands-optional:"true" description:"Builds and runs a single target"` 166 167 Clean struct { 168 NoBackground bool `long:"nobackground" short:"f" description:"Don't fork & detach until clean is finished."` 169 Remote bool `long:"remote" description:"Clean entire remote cache when no targets are given (default is local only)"` 170 Args struct { // Inner nesting is necessary to make positional-args work :( 171 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to clean (default is to clean everything)"` 172 } `positional-args:"true"` 173 } `command:"clean" description:"Cleans build artifacts" subcommands-optional:"true"` 174 175 Watch struct { 176 Run bool `short:"r" long:"run" description:"Runs the specified targets when they change (default is to build or test as appropriate)."` 177 Args struct { 178 Targets []core.BuildLabel `positional-arg-name:"targets" required:"true" description:"Targets to watch the sources of for changes"` 179 } `positional-args:"true" required:"true"` 180 } `command:"watch" description:"Watches sources of targets for changes and rebuilds them"` 181 182 Update struct { 183 Force bool `long:"force" description:"Forces a re-download of the new version."` 184 NoVerify bool `long:"noverify" description:"Skips signature verification of downloaded version"` 185 Latest bool `long:"latest" description:"Update to latest available version (overrides config)."` 186 Version cli.Version `long:"version" description:"Updates to a particular version (overrides config)."` 187 } `command:"update" description:"Checks for an update and updates if needed."` 188 189 Op struct { 190 } `command:"op" description:"Re-runs previous command."` 191 192 Init struct { 193 Dir cli.Filepath `long:"dir" description:"Directory to create config in" default:"."` 194 BazelCompatibility bool `long:"bazel_compat" description:"Initialises config for Bazel compatibility mode."` 195 } `command:"init" description:"Initialises a .plzconfig file in the current directory"` 196 197 Gc struct { 198 Conservative bool `short:"c" long:"conservative" description:"Runs a more conservative / safer GC."` 199 TargetsOnly bool `short:"t" long:"targets_only" description:"Only print the targets to delete"` 200 SrcsOnly bool `short:"s" long:"srcs_only" description:"Only print the source files to delete"` 201 NoPrompt bool `short:"y" long:"no_prompt" description:"Remove targets without prompting"` 202 DryRun bool `short:"n" long:"dry_run" description:"Don't remove any targets or files, just print what would be done"` 203 Git bool `short:"g" long:"git" description:"Use 'git rm' to remove unused files instead of just 'rm'."` 204 Args struct { 205 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to limit gc to."` 206 } `positional-args:"true"` 207 } `command:"gc" description:"Analyzes the repo to determine unneeded targets."` 208 209 Export struct { 210 Output string `short:"o" long:"output" required:"true" description:"Directory to export into"` 211 Args struct { 212 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to export."` 213 } `positional-args:"true"` 214 215 Outputs struct { 216 Args struct { 217 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to export."` 218 } `positional-args:"true"` 219 } `command:"outputs" description:"Exports outputs of a set of targets"` 220 } `command:"export" subcommands-optional:"true" description:"Exports a set of targets and files from the repo."` 221 222 Follow struct { 223 Retries int `long:"retries" description:"Number of times to retry the connection"` 224 Delay cli.Duration `long:"delay" default:"1s" description:"Delay between timeouts"` 225 Args struct { 226 URL cli.URL `positional-arg-name:"URL" required:"true" description:"URL of remote server to connect to, e.g. 10.23.0.5:7777"` 227 } `positional-args:"true"` 228 } `command:"follow" description:"Connects to a remote Please instance to stream build events from."` 229 230 Help struct { 231 Args struct { 232 Topic help.Topic `positional-arg-name:"topic" description:"Topic to display help on"` 233 } `positional-args:"true"` 234 } `command:"help" alias:"halp" description:"Displays help about various parts of plz or its build rules"` 235 236 Tool struct { 237 Args struct { 238 Tool tool.Tool `positional-arg-name:"tool" description:"Tool to invoke (jarcat, lint, etc)"` 239 Args []string `positional-arg-name:"arguments" description:"Arguments to pass to the tool"` 240 } `positional-args:"true"` 241 } `command:"tool" hidden:"true" description:"Invoke one of Please's sub-tools"` 242 243 Query struct { 244 Deps struct { 245 Unique bool `long:"unique" short:"u" description:"Only output each dependency once"` 246 Args struct { 247 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to query" required:"true"` 248 } `positional-args:"true" required:"true"` 249 } `command:"deps" description:"Queries the dependencies of a target."` 250 ReverseDeps struct { 251 Args struct { 252 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to query" required:"true"` 253 } `positional-args:"true" required:"true"` 254 } `command:"reverseDeps" alias:"revdeps" description:"Queries all the reverse dependencies of a target."` 255 SomePath struct { 256 Args struct { 257 Target1 core.BuildLabel `positional-arg-name:"target1" description:"First build target" required:"true"` 258 Target2 core.BuildLabel `positional-arg-name:"target2" description:"Second build target" required:"true"` 259 } `positional-args:"true" required:"true"` 260 } `command:"somepath" description:"Queries for a path between two targets"` 261 AllTargets struct { 262 Hidden bool `long:"hidden" description:"Show hidden targets as well"` 263 Args struct { 264 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to query"` 265 } `positional-args:"true"` 266 } `command:"alltargets" description:"Lists all targets in the graph"` 267 Print struct { 268 Fields []string `short:"f" long:"field" description:"Individual fields to print of the target"` 269 Args struct { 270 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to print" required:"true"` 271 } `positional-args:"true" required:"true"` 272 } `command:"print" description:"Prints a representation of a single target"` 273 Completions struct { 274 Cmd string `long:"cmd" description:"Command to complete for" default:"build"` 275 Args struct { 276 Fragments cli.StdinStrings `positional-arg-name:"fragment" description:"Initial fragment to attempt to complete"` 277 } `positional-args:"true"` 278 } `command:"completions" subcommands-optional:"true" description:"Prints possible completions for a string."` 279 AffectedTargets struct { 280 Tests bool `long:"tests" description:"Shows only affected tests, no other targets."` 281 Intransitive bool `long:"intransitive" description:"Shows only immediately affected targets, not transitive dependencies."` 282 Args struct { 283 Files cli.StdinStrings `positional-arg-name:"files" required:"true" description:"Files to query affected tests for"` 284 } `positional-args:"true"` 285 } `command:"affectedtargets" description:"Prints any targets affected by a set of files."` 286 Input struct { 287 Args struct { 288 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to display inputs for" required:"true"` 289 } `positional-args:"true" required:"true"` 290 } `command:"input" alias:"inputs" description:"Prints all transitive inputs of a target."` 291 Output struct { 292 Args struct { 293 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to display outputs for" required:"true"` 294 } `positional-args:"true" required:"true"` 295 } `command:"output" alias:"outputs" description:"Prints all outputs of a target."` 296 Graph struct { 297 Args struct { 298 Targets []core.BuildLabel `positional-arg-name:"targets" description:"Targets to render graph for"` 299 } `positional-args:"true"` 300 } `command:"graph" description:"Prints a JSON representation of the build graph."` 301 WhatOutputs struct { 302 EchoFiles bool `long:"echo_files" description:"Echo the file for which the printed output is responsible."` 303 Args struct { 304 Files cli.StdinStrings `positional-arg-name:"files" required:"true" description:"Files to query targets responsible for"` 305 } `positional-args:"true"` 306 } `command:"whatoutputs" description:"Prints out target(s) responsible for outputting provided file(s)"` 307 Rules struct { 308 Args struct { 309 Targets []core.BuildLabel `position-arg-name:"targets" description:"Additional targets to load rules from"` 310 } `positional-args:"true"` 311 } `command:"rules" description:"Prints built-in rules to stdout as JSON"` 312 Changes struct { 313 Since string `short:"s" long:"since" default:"origin/master" description:"Revision to compare against"` 314 CheckoutCommand string `long:"checkout_command" default:"git checkout %s" description:"Command to run to check out the before/after revisions."` 315 CurrentCommand string `long:"current_revision_command" default:"git rev-parse HEAD" description:"Command to run to get the current revision (which will be checked out again at the end)"` 316 Args struct { 317 Files cli.StdinStrings `positional-arg-name:"files" description:"Files to consider changed"` 318 } `positional-args:"true"` 319 } `command:"changes" description:"Calculates the difference between two different states of the build graph"` 320 } `command:"query" description:"Queries information about the build graph"` 321 } 322 323 // Definitions of what we do for each command. 324 // Functions are called after args are parsed and return true for success. 325 var buildFunctions = map[string]func() bool{ 326 "build": func() bool { 327 success, _ := runBuild(opts.Build.Args.Targets, true, false) 328 return success 329 }, 330 "rebuild": func() bool { 331 // It would be more pure to require --nocache for this, but in basically any context that 332 // you use 'plz rebuild', you don't want the cache coming in and mucking things up. 333 // 'plz clean' followed by 'plz build' would still work in those cases, anyway. 334 opts.FeatureFlags.NoCache = true 335 success, _ := runBuild(opts.Rebuild.Args.Targets, true, false) 336 return success 337 }, 338 "hash": func() bool { 339 success, state := runBuild(opts.Hash.Args.Targets, true, false) 340 if opts.Hash.Detailed { 341 for _, target := range state.ExpandOriginalTargets() { 342 build.PrintHashes(state, state.Graph.TargetOrDie(target)) 343 } 344 } 345 if opts.Hash.Update { 346 hashes.RewriteHashes(state, state.ExpandOriginalTargets()) 347 } 348 return success 349 }, 350 "test": func() bool { 351 targets := testTargets(opts.Test.Args.Target, opts.Test.Args.Args, opts.Test.Failed, opts.Test.TestResultsFile) 352 os.RemoveAll(string(opts.Test.TestResultsFile)) 353 success, state := runBuild(targets, true, true) 354 test.WriteResultsToFileOrDie(state.Graph, string(opts.Test.TestResultsFile)) 355 return success || opts.Test.FailingTestsOk 356 }, 357 "cover": func() bool { 358 if opts.BuildFlags.Config != "" { 359 log.Warning("Build config overridden; coverage may not be available for some languages") 360 } else { 361 opts.BuildFlags.Config = "cover" 362 } 363 targets := testTargets(opts.Cover.Args.Target, opts.Cover.Args.Args, opts.Cover.Failed, opts.Cover.TestResultsFile) 364 os.RemoveAll(string(opts.Cover.TestResultsFile)) 365 os.RemoveAll(string(opts.Cover.CoverageResultsFile)) 366 success, state := runBuild(targets, true, true) 367 test.WriteResultsToFileOrDie(state.Graph, string(opts.Cover.TestResultsFile)) 368 test.AddOriginalTargetsToCoverage(state, opts.Cover.IncludeAllFiles) 369 test.RemoveFilesFromCoverage(state.Coverage, state.Config.Cover.ExcludeExtension) 370 test.WriteCoverageToFileOrDie(state.Coverage, string(opts.Cover.CoverageResultsFile)) 371 if opts.Cover.LineCoverageReport { 372 output.PrintLineCoverageReport(state, opts.Cover.IncludeFile) 373 } else if !opts.Cover.NoCoverageReport { 374 output.PrintCoverage(state, opts.Cover.IncludeFile) 375 } 376 return success || opts.Cover.FailingTestsOk 377 }, 378 "run": func() bool { 379 if success, state := runBuild([]core.BuildLabel{opts.Run.Args.Target}, true, false); success { 380 run.Run(state, opts.Run.Args.Target, opts.Run.Args.Args, opts.Run.Env) 381 } 382 return false // We should never return from run.Run so if we make it here something's wrong. 383 }, 384 "parallel": func() bool { 385 if success, state := runBuild(opts.Run.Parallel.PositionalArgs.Targets, true, false); success { 386 os.Exit(run.Parallel(state, state.ExpandOriginalTargets(), opts.Run.Parallel.Args, opts.Run.Parallel.NumTasks, opts.Run.Parallel.Quiet, opts.Run.Env)) 387 } 388 return false 389 }, 390 "sequential": func() bool { 391 if success, state := runBuild(opts.Run.Sequential.PositionalArgs.Targets, true, false); success { 392 os.Exit(run.Sequential(state, state.ExpandOriginalTargets(), opts.Run.Sequential.Args, opts.Run.Sequential.Quiet, opts.Run.Env)) 393 } 394 return false 395 }, 396 "clean": func() bool { 397 config.Cache.DirClean = false 398 if len(opts.Clean.Args.Targets) == 0 { 399 if len(opts.BuildFlags.Include) == 0 && len(opts.BuildFlags.Exclude) == 0 { 400 // Clean everything, doesn't require parsing at all. 401 if !opts.Clean.Remote { 402 // Don't construct the remote caches if they didn't pass --remote. 403 config.Cache.RPCURL = "" 404 config.Cache.HTTPURL = "" 405 } 406 clean.Clean(config, newCache(config), !opts.Clean.NoBackground) 407 return true 408 } 409 opts.Clean.Args.Targets = core.WholeGraph 410 } 411 if success, state := runBuild(opts.Clean.Args.Targets, false, false); success { 412 clean.Targets(state, state.ExpandOriginalTargets(), !opts.FeatureFlags.NoCache) 413 return true 414 } 415 return false 416 }, 417 "watch": func() bool { 418 success, state := runBuild(opts.Watch.Args.Targets, false, false) 419 if success { 420 watch.Watch(state, state.ExpandOriginalTargets(), opts.Watch.Run) 421 } 422 return success 423 }, 424 "update": func() bool { 425 fmt.Printf("Up to date (version %s).\n", core.PleaseVersion) 426 return true // We'd have died already if something was wrong. 427 }, 428 "op": func() bool { 429 cmd := core.ReadLastOperationOrDie() 430 log.Notice("OP PLZ: %s", strings.Join(cmd, " ")) 431 // Annoyingly we don't seem to have any access to execvp() which would be rather useful here... 432 executable, err := os.Executable() 433 if err == nil { 434 err = syscall.Exec(executable, append([]string{executable}, cmd...), os.Environ()) 435 } 436 log.Fatalf("SORRY OP: %s", err) // On success Exec never returns. 437 return false 438 }, 439 "gc": func() bool { 440 success, state := runBuild(core.WholeGraph, false, false) 441 if success { 442 state.OriginalTargets = state.Config.Gc.Keep 443 gc.GarbageCollect(state, opts.Gc.Args.Targets, state.ExpandOriginalTargets(), state.Config.Gc.Keep, state.Config.Gc.KeepLabel, 444 opts.Gc.Conservative, opts.Gc.TargetsOnly, opts.Gc.SrcsOnly, opts.Gc.NoPrompt, opts.Gc.DryRun, opts.Gc.Git) 445 } 446 return success 447 }, 448 "export": func() bool { 449 success, state := runBuild(opts.Export.Args.Targets, false, false) 450 if success { 451 export.ToDir(state, opts.Export.Output, state.ExpandOriginalTargets()) 452 } 453 return success 454 }, 455 "follow": func() bool { 456 // This is only temporary, ConnectClient will alter it to match the server. 457 state := core.NewBuildState(1, nil, opts.OutputFlags.Verbosity, config) 458 return follow.ConnectClient(state, opts.Follow.Args.URL.String(), opts.Follow.Retries, time.Duration(opts.Follow.Delay)) 459 }, 460 "outputs": func() bool { 461 success, state := runBuild(opts.Export.Outputs.Args.Targets, true, false) 462 if success { 463 export.Outputs(state, opts.Export.Output, state.ExpandOriginalTargets()) 464 } 465 return success 466 }, 467 "help": func() bool { 468 return help.Help(string(opts.Help.Args.Topic)) 469 }, 470 "tool": func() bool { 471 tool.Run(config, opts.Tool.Args.Tool, opts.Tool.Args.Args) 472 return false // If the function returns (which it shouldn't), something went wrong. 473 }, 474 "deps": func() bool { 475 return runQuery(true, opts.Query.Deps.Args.Targets, func(state *core.BuildState) { 476 query.Deps(state, state.ExpandOriginalTargets(), opts.Query.Deps.Unique) 477 }) 478 }, 479 "reverseDeps": func() bool { 480 opts.VisibilityParse = true 481 return runQuery(false, opts.Query.ReverseDeps.Args.Targets, func(state *core.BuildState) { 482 query.ReverseDeps(state.Graph, state.ExpandOriginalTargets()) 483 }) 484 }, 485 "somepath": func() bool { 486 return runQuery(true, 487 []core.BuildLabel{opts.Query.SomePath.Args.Target1, opts.Query.SomePath.Args.Target2}, 488 func(state *core.BuildState) { 489 query.SomePath(state.Graph, opts.Query.SomePath.Args.Target1, opts.Query.SomePath.Args.Target2) 490 }, 491 ) 492 }, 493 "alltargets": func() bool { 494 return runQuery(true, opts.Query.AllTargets.Args.Targets, func(state *core.BuildState) { 495 query.AllTargets(state.Graph, state.ExpandOriginalTargets(), opts.Query.AllTargets.Hidden) 496 }) 497 }, 498 "print": func() bool { 499 return runQuery(false, opts.Query.Print.Args.Targets, func(state *core.BuildState) { 500 query.Print(state.Graph, state.ExpandOriginalTargets(), opts.Query.Print.Fields) 501 }) 502 }, 503 "affectedtargets": func() bool { 504 files := opts.Query.AffectedTargets.Args.Files 505 targets := core.WholeGraph 506 if opts.Query.AffectedTargets.Intransitive { 507 state := core.NewBuildState(1, nil, 1, config) 508 targets = core.FindOwningPackages(state, files) 509 } 510 return runQuery(true, targets, func(state *core.BuildState) { 511 query.AffectedTargets(state, files.Get(), opts.BuildFlags.Include, opts.BuildFlags.Exclude, opts.Query.AffectedTargets.Tests, !opts.Query.AffectedTargets.Intransitive) 512 }) 513 }, 514 "input": func() bool { 515 return runQuery(true, opts.Query.Input.Args.Targets, func(state *core.BuildState) { 516 query.TargetInputs(state.Graph, state.ExpandOriginalTargets()) 517 }) 518 }, 519 "output": func() bool { 520 return runQuery(true, opts.Query.Output.Args.Targets, func(state *core.BuildState) { 521 query.TargetOutputs(state.Graph, state.ExpandOriginalTargets()) 522 }) 523 }, 524 "completions": func() bool { 525 // Somewhat fiddly because the inputs are not necessarily well-formed at this point. 526 opts.ParsePackageOnly = true 527 fragments := opts.Query.Completions.Args.Fragments.Get() 528 if opts.Query.Completions.Cmd == "help" { 529 // Special-case completing help topics rather than build targets. 530 if len(fragments) == 0 { 531 help.Topics("") 532 } else { 533 help.Topics(fragments[0]) 534 } 535 return true 536 } 537 if len(fragments) == 0 || len(fragments) == 1 && strings.Trim(fragments[0], "/ ") == "" { 538 os.Exit(0) // Don't do anything for empty completion, it's normally too slow. 539 } 540 labels, parseLabels, hidden := query.CompletionLabels(config, fragments, core.RepoRoot) 541 if success, state := Please(parseLabels, config, false, false, false); success { 542 binary := opts.Query.Completions.Cmd == "run" 543 test := opts.Query.Completions.Cmd == "test" || opts.Query.Completions.Cmd == "cover" 544 query.Completions(state.Graph, labels, binary, test, hidden) 545 return true 546 } 547 return false 548 }, 549 "graph": func() bool { 550 return runQuery(true, opts.Query.Graph.Args.Targets, func(state *core.BuildState) { 551 if len(opts.Query.Graph.Args.Targets) == 0 { 552 state.OriginalTargets = opts.Query.Graph.Args.Targets // It special-cases doing the full graph. 553 } 554 query.Graph(state, state.ExpandOriginalTargets()) 555 }) 556 }, 557 "whatoutputs": func() bool { 558 return runQuery(true, core.WholeGraph, func(state *core.BuildState) { 559 query.WhatOutputs(state.Graph, opts.Query.WhatOutputs.Args.Files.Get(), opts.Query.WhatOutputs.EchoFiles) 560 }) 561 }, 562 "rules": func() bool { 563 targets := opts.Query.Rules.Args.Targets 564 success, state := Please(opts.Query.Rules.Args.Targets, config, true, true, false) 565 if !success { 566 return false 567 } 568 targets = state.ExpandOriginalTargets() 569 parse.PrintRuleArgs(state, targets) 570 return true 571 }, 572 "changes": func() bool { 573 original := query.MustGetRevision(opts.Query.Changes.CurrentCommand) 574 files := opts.Query.Changes.Args.Files.Get() 575 query.MustCheckout(opts.Query.Changes.Since, opts.Query.Changes.CheckoutCommand) 576 success, before := runBuild(core.WholeGraph, false, false) 577 if !success { 578 return false 579 } 580 query.MustCheckout(original, opts.Query.Changes.CheckoutCommand) 581 success, after := runBuild(core.WholeGraph, false, false) 582 if !success { 583 return false 584 } 585 for _, target := range query.DiffGraphs(before, after, files) { 586 fmt.Printf("%s\n", target) 587 } 588 return true 589 }, 590 } 591 592 // ConfigOverrides are used to implement completion on the -o flag. 593 type ConfigOverrides map[string]string 594 595 // Complete implements the flags.Completer interface. 596 func (overrides ConfigOverrides) Complete(match string) []flags.Completion { 597 return core.DefaultConfiguration().Completions(match) 598 } 599 600 // Used above as a convenience wrapper for query functions. 601 func runQuery(needFullParse bool, labels []core.BuildLabel, onSuccess func(state *core.BuildState)) bool { 602 if !needFullParse { 603 opts.ParsePackageOnly = true 604 } 605 if len(labels) == 0 { 606 labels = core.WholeGraph 607 } 608 if success, state := runBuild(labels, false, false); success { 609 onSuccess(state) 610 return true 611 } 612 return false 613 } 614 615 func please(tid int, state *core.BuildState, parsePackageOnly bool, include, exclude []string) { 616 for { 617 label, dependor, t := state.NextTask() 618 switch t { 619 case core.Stop, core.Kill: 620 return 621 case core.Parse, core.SubincludeParse: 622 t := t 623 label := label 624 dependor := dependor 625 state.ParsePool <- func() { 626 parse.Parse(tid, state, label, dependor, parsePackageOnly, include, exclude, t == core.SubincludeParse) 627 if opts.VisibilityParse && state.IsOriginalTarget(label) { 628 parseForVisibleTargets(state, label) 629 } 630 state.TaskDone() 631 } 632 case core.Build, core.SubincludeBuild: 633 build.Build(tid, state, label) 634 state.TaskDone() 635 case core.Test: 636 test.Test(tid, state, label) 637 state.TaskDone() 638 } 639 } 640 } 641 642 // parseForVisibleTargets adds parse tasks for any targets that the given label is visible to. 643 func parseForVisibleTargets(state *core.BuildState, label core.BuildLabel) { 644 if target := state.Graph.Target(label); target != nil { 645 for _, vis := range target.Visibility { 646 findOriginalTask(state, vis, false) 647 } 648 } 649 } 650 651 // prettyOutputs determines from input flags whether we should show 'pretty' output (ie. interactive). 652 func prettyOutput(interactiveOutput bool, plainOutput bool, verbosity int) bool { 653 if interactiveOutput && plainOutput { 654 log.Fatal("Can't pass both --interactive_output and --plain_output") 655 } 656 return interactiveOutput || (!plainOutput && cli.StdErrIsATerminal && verbosity < 4) 657 } 658 659 // newCache constructs a new cache based on the current config / flags. 660 func newCache(config *core.Configuration) core.Cache { 661 if opts.FeatureFlags.NoCache { 662 return nil 663 } 664 return cache.NewCache(config) 665 } 666 667 // Please starts & runs the main build process through to its completion. 668 func Please(targets []core.BuildLabel, config *core.Configuration, prettyOutput, shouldBuild, shouldTest bool) (bool, *core.BuildState) { 669 if opts.BuildFlags.NumThreads > 0 { 670 config.Please.NumThreads = opts.BuildFlags.NumThreads 671 } else if config.Please.NumThreads <= 0 { 672 config.Please.NumThreads = runtime.NumCPU() + 2 673 } 674 debugTests := opts.Test.Debug || opts.Cover.Debug 675 if opts.BuildFlags.Config != "" { 676 config.Build.Config = opts.BuildFlags.Config 677 } else if debugTests { 678 config.Build.Config = "dbg" 679 } 680 c := newCache(config) 681 state := core.NewBuildState(config.Please.NumThreads, c, opts.OutputFlags.Verbosity, config) 682 state.VerifyHashes = !opts.FeatureFlags.NoHashVerification 683 state.NumTestRuns = opts.Test.NumRuns + opts.Cover.NumRuns // Only one of these can be passed. 684 state.TestArgs = append(opts.Test.Args.Args, opts.Cover.Args.Args...) // Similarly here. 685 state.NeedCoverage = !opts.Cover.Args.Target.IsEmpty() 686 state.NeedBuild = shouldBuild 687 state.NeedTests = shouldTest 688 state.NeedHashesOnly = len(opts.Hash.Args.Targets) > 0 689 state.PrepareOnly = opts.Build.Prepare || opts.Build.Shell 690 state.PrepareShell = opts.Build.Shell 691 state.CleanWorkdirs = !opts.FeatureFlags.KeepWorkdirs 692 state.ForceRebuild = len(opts.Rebuild.Args.Targets) > 0 693 state.ShowTestOutput = opts.Test.ShowOutput || opts.Cover.ShowOutput 694 state.DebugTests = debugTests 695 state.ShowAllOutput = opts.OutputFlags.ShowAllOutput 696 state.SetIncludeAndExclude(opts.BuildFlags.Include, opts.BuildFlags.Exclude) 697 parse.InitParser(state) 698 if config.Events.Port != 0 && shouldBuild { 699 shutdown := follow.InitialiseServer(state, config.Events.Port) 700 defer shutdown() 701 } 702 if config.Events.Port != 0 || config.Display.SystemStats { 703 go follow.UpdateResources(state) 704 } 705 metrics.InitFromConfig(config) 706 // Acquire the lock before we start building 707 if (shouldBuild || shouldTest) && !opts.FeatureFlags.NoLock { 708 core.AcquireRepoLock() 709 defer core.ReleaseRepoLock() 710 } 711 if state.DebugTests && len(targets) != 1 { 712 log.Fatalf("-d/--debug flag can only be used with a single test target") 713 } 714 // Start looking for the initial targets to kick the build off 715 go findOriginalTasks(state, targets) 716 // Start up all the build workers 717 var wg sync.WaitGroup 718 wg.Add(config.Please.NumThreads) 719 for i := 0; i < config.Please.NumThreads; i++ { 720 go func(tid int) { 721 please(tid, state, opts.ParsePackageOnly, opts.BuildFlags.Include, opts.BuildFlags.Exclude) 722 wg.Done() 723 }(i) 724 } 725 // Wait until they've all exited, which they'll do once they have no tasks left. 726 go func() { 727 wg.Wait() 728 close(state.Results) // This will signal MonitorState (below) to stop. 729 }() 730 // Draw stuff to the screen while there are still results coming through. 731 shouldRun := !opts.Run.Args.Target.IsEmpty() 732 success := output.MonitorState(state, config.Please.NumThreads, !prettyOutput, opts.BuildFlags.KeepGoing, shouldBuild, shouldTest, shouldRun, opts.Build.ShowStatus, string(opts.OutputFlags.TraceFile)) 733 metrics.Stop() 734 build.StopWorkers() 735 if c != nil { 736 c.Shutdown() 737 } 738 return success, state 739 } 740 741 // findOriginalTasks finds the original parse tasks for the original set of targets. 742 func findOriginalTasks(state *core.BuildState, targets []core.BuildLabel) { 743 if state.Config.Bazel.Compatibility && fs.FileExists("WORKSPACE") { 744 // We have to parse the WORKSPACE file before anything else to understand subrepos. 745 // This is a bit crap really since it inhibits parallelism for the first step. 746 parse.Parse(0, state, core.NewBuildLabel("workspace", "all"), core.OriginalTarget, false, state.Include, state.Exclude, false) 747 } 748 if opts.BuildFlags.Arch.Arch != "" { 749 // Set up a new subrepo for this architecture. 750 state.Graph.AddSubrepo(core.SubrepoForArch(state, opts.BuildFlags.Arch)) 751 } 752 for _, target := range targets { 753 if target == core.BuildLabelStdin { 754 for label := range cli.ReadStdin() { 755 findOriginalTask(state, core.ParseBuildLabels([]string{label})[0], true) 756 } 757 } else { 758 findOriginalTask(state, target, true) 759 } 760 } 761 state.TaskDone() // initial target adding counts as one. 762 } 763 764 func findOriginalTask(state *core.BuildState, target core.BuildLabel, addToList bool) { 765 if opts.BuildFlags.Arch.Arch != "" { 766 target.PackageName = path.Join(opts.BuildFlags.Arch.String(), target.PackageName) 767 } 768 if target.IsAllSubpackages() { 769 for pkg := range utils.FindAllSubpackages(state.Config, target.PackageName, "") { 770 state.AddOriginalTarget(core.NewBuildLabel(pkg, "all"), addToList) 771 } 772 } else { 773 state.AddOriginalTarget(target, addToList) 774 } 775 } 776 777 // testTargets handles test targets which can be given in two formats; a list of targets or a single 778 // target with a list of trailing arguments. 779 // Alternatively they can be completely omitted in which case we test everything under the working dir. 780 // One can also pass a 'failed' flag which runs the failed tests from last time. 781 func testTargets(target core.BuildLabel, args []string, failed bool, resultsFile cli.Filepath) []core.BuildLabel { 782 if failed { 783 targets, args := test.LoadPreviousFailures(string(resultsFile)) 784 // Have to reset these - it doesn't matter which gets which. 785 opts.Test.Args.Args = args 786 opts.Cover.Args.Args = nil 787 return targets 788 } else if target.Name == "" { 789 return core.InitialPackage() 790 } else if len(args) > 0 && core.LooksLikeABuildLabel(args[0]) { 791 opts.Cover.Args.Args = []string{} 792 opts.Test.Args.Args = []string{} 793 return append(core.ParseBuildLabels(args), target) 794 } 795 return []core.BuildLabel{target} 796 } 797 798 // readConfig sets various things up and reads the initial configuration. 799 func readConfig(forceUpdate bool) *core.Configuration { 800 if opts.FeatureFlags.NoHashVerification { 801 log.Warning("You've disabled hash verification; this is intended to help temporarily while modifying build targets. You shouldn't use this regularly.") 802 } 803 804 config, err := core.ReadConfigFiles([]string{ 805 core.MachineConfigFileName, 806 core.ExpandHomePath(core.UserConfigFileName), 807 path.Join(core.RepoRoot, core.ConfigFileName), 808 path.Join(core.RepoRoot, core.ArchConfigFileName), 809 path.Join(core.RepoRoot, core.LocalConfigFileName), 810 }, opts.BuildFlags.Profile) 811 if err != nil { 812 log.Fatalf("Error reading config file: %s", err) 813 } else if err := config.ApplyOverrides(opts.BuildFlags.Option); err != nil { 814 log.Fatalf("Can't override requested config setting: %s", err) 815 } 816 // Now apply any flags that override this 817 if opts.Update.Latest { 818 config.Please.Version.Unset() 819 } else if opts.Update.Version.IsSet { 820 config.Please.Version = opts.Update.Version 821 } 822 update.CheckAndUpdate(config, !opts.FeatureFlags.NoUpdate, forceUpdate, opts.Update.Force, !opts.Update.NoVerify) 823 return config 824 } 825 826 // Runs the actual build 827 // Which phases get run are controlled by shouldBuild and shouldTest. 828 func runBuild(targets []core.BuildLabel, shouldBuild, shouldTest bool) (bool, *core.BuildState) { 829 if len(targets) == 0 { 830 targets = core.InitialPackage() 831 } 832 pretty := prettyOutput(opts.OutputFlags.InteractiveOutput, opts.OutputFlags.PlainOutput, opts.OutputFlags.Verbosity) 833 return Please(targets, config, pretty, shouldBuild, shouldTest) 834 } 835 836 // readConfigAndSetRoot reads the .plzconfig files and moves to the repo root. 837 func readConfigAndSetRoot(forceUpdate bool) *core.Configuration { 838 if opts.BuildFlags.RepoRoot == "" { 839 log.Debug("Found repo root at %s", core.MustFindRepoRoot()) 840 } else { 841 core.RepoRoot = string(opts.BuildFlags.RepoRoot) 842 } 843 844 // Please always runs from the repo root, so move there now. 845 if err := os.Chdir(core.RepoRoot); err != nil { 846 log.Fatalf("%s", err) 847 } 848 // Reset this now we're at the repo root. 849 if opts.OutputFlags.LogFile != "" { 850 if !path.IsAbs(string(opts.OutputFlags.LogFile)) { 851 opts.OutputFlags.LogFile = cli.Filepath(path.Join(core.RepoRoot, string(opts.OutputFlags.LogFile))) 852 } 853 cli.InitFileLogging(string(opts.OutputFlags.LogFile), opts.OutputFlags.LogFileLevel) 854 } 855 856 return readConfig(forceUpdate) 857 } 858 859 // handleCompletions handles shell completion. Typically it just prints to stdout but 860 // may do a little more if we think we need to handle aliases. 861 func handleCompletions(parser *flags.Parser, items []flags.Completion) { 862 if len(items) > 0 { 863 cli.PrintCompletions(items) 864 } else { 865 cli.InitLogging(0) // Ensure this is quiet 866 opts.FeatureFlags.NoUpdate = true // Ensure we don't try to update 867 config := readConfigAndSetRoot(false) 868 if len(config.Aliases) > 0 { 869 for k, v := range config.Aliases { 870 parser.AddCommand(k, v, v, &struct{}{}) 871 } 872 // Run again without this registered as a completion handler 873 parser.CompletionHandler = nil 874 parser.ParseArgs(os.Args[1:]) 875 } 876 } 877 // Regardless of what happened, always exit with 0 at this point. 878 os.Exit(0) 879 } 880 881 func main() { 882 parser, extraArgs, flagsErr := cli.ParseFlags("Please", &opts, os.Args, handleCompletions) 883 // Note that we must leave flagsErr for later, because it may be affected by aliases. 884 if opts.OutputFlags.Version { 885 fmt.Printf("Please version %s\n", core.PleaseVersion) 886 os.Exit(0) // Ignore other flags if --version was passed. 887 } 888 if opts.OutputFlags.Colour { 889 output.SetColouredOutput(true) 890 } else if opts.OutputFlags.NoColour { 891 output.SetColouredOutput(false) 892 } 893 if opts.OutputFlags.ShowAllOutput { 894 opts.OutputFlags.PlainOutput = true 895 } 896 // Init logging, but don't do file output until we've chdir'd. 897 cli.InitLogging(opts.OutputFlags.Verbosity) 898 899 command := cli.ActiveCommand(parser.Command) 900 if opts.Complete != "" { 901 // Completion via PLZ_COMPLETE env var sidesteps other commands 902 opts.Query.Completions.Cmd = command 903 opts.Query.Completions.Args.Fragments = []string{opts.Complete} 904 command = "completions" 905 } else if command == "init" { 906 if flagsErr != nil { // This error otherwise doesn't get checked until later. 907 cli.ParseFlagsFromArgsOrDie("Please", core.PleaseVersion.String(), &opts, os.Args) 908 } 909 // If we're running plz init then we obviously don't expect to read a config file. 910 utils.InitConfig(string(opts.Init.Dir), opts.Init.BazelCompatibility) 911 os.Exit(0) 912 } else if command == "help" || command == "follow" { 913 config = core.DefaultConfiguration() 914 if !buildFunctions[command]() { 915 os.Exit(1) 916 } 917 os.Exit(0) 918 } else if opts.OutputFlags.CompletionScript { 919 utils.PrintCompletionScript() 920 os.Exit(0) 921 } 922 // Read the config now 923 config = readConfigAndSetRoot(command == "update") 924 if parser.Command.Active != nil && parser.Command.Active.Name == "query" { 925 // Query commands don't need either of these set. 926 opts.OutputFlags.PlainOutput = true 927 config.Cache.DirClean = false 928 } 929 930 // Now we've read the config file, we may need to re-run the parser; the aliases in the config 931 // can affect how we parse otherwise illegal flag combinations. 932 if flagsErr != nil || len(extraArgs) > 0 { 933 for idx, arg := range os.Args[1:] { 934 // Please should not touch anything that comes after `--` 935 if arg == "--" { 936 break 937 } 938 for k, v := range config.Aliases { 939 if arg == k { 940 // We could insert every token in v into os.Args at this point and then we could have 941 // aliases defined in terms of other aliases but that seems rather like overkill so just 942 // stick the replacement in wholesale instead. 943 os.Args[idx+1] = v 944 } 945 } 946 } 947 argv := strings.Join(os.Args[1:], " ") 948 command = cli.ParseFlagsFromArgsOrDie("Please", core.PleaseVersion.String(), &opts, strings.Fields(os.Args[0]+" "+argv)) 949 } 950 951 if opts.ProfilePort != 0 { 952 go func() { 953 log.Warning("%s", http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", opts.ProfilePort), nil)) 954 }() 955 } 956 if opts.Profile != "" { 957 f, err := os.Create(opts.Profile) 958 if err != nil { 959 log.Fatalf("Failed to open profile file: %s", err) 960 } 961 if err := pprof.StartCPUProfile(f); err != nil { 962 log.Fatalf("could not start profiler: %s", err) 963 } 964 defer pprof.StopCPUProfile() 965 } 966 if opts.MemProfile != "" { 967 f, err := os.Create(opts.MemProfile) 968 if err != nil { 969 log.Fatalf("Failed to open memory profile file: %s", err) 970 } 971 defer f.Close() 972 defer pprof.WriteHeapProfile(f) 973 } 974 975 if !buildFunctions[command]() { 976 os.Exit(7) // Something distinctive, is sometimes useful to identify this externally. 977 } 978 }