| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "context" |
| "flag" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "log" |
| "os" |
| "path/filepath" |
| "runtime/pprof" |
| "runtime/trace" |
| "strings" |
| "time" |
| |
| checklicenses "go.fuchsia.dev/fuchsia/tools/check-licenses" |
| ) |
| |
| const ( |
| defaultConfigFile = "{FUCHSIA_DIR}/tools/check-licenses/_config.json" |
| ) |
| |
| var ( |
| configFile_deprecated = flag.String("config_file", "", "Deprecated, but kept around for backwards compatibility.") |
| filter = flag.String("filter", "", "Files that contain dependencies of the target or workspace. Used to filter projects in the final NOTICE file.") |
| |
| diffTarget = flag.String("diff_target", "", "Notice file to diff the current licenses against.") |
| |
| fuchsiaDir = flag.String("fuchsia_dir", os.Getenv("FUCHSIA_DIR"), "Location of the fuchsia root directory (//).") |
| buildDir = flag.String("build_dir", os.Getenv("FUCHSIA_BUILD_DIR"), "Location of GN build directory.") |
| outDir = flag.String("out_dir", "/tmp/check-licenses", "Directory to write outputs to.") |
| |
| gnPath = flag.String("gn_path", "{FUCHSIA_DIR}/prebuilt/third_party/gn/linux-x64/gn", "Path to GN executable. Required when target is specified.") |
| |
| logLevel = flag.Int("log_level", 2, "Log level. Set to 0 for no logs, 1 to log to a file, 2 to log to stdout.") |
| pproffile = flag.String("pprof", "", "generate file that can be parsed by go tool pprof") |
| tracefile = flag.String("trace", "", "generate file that can be parsed by go tool trace") |
| |
| outputLicenseFile = flag.Bool("output_license_file", true, "Flag for enabling template expansions.") |
| ) |
| |
| func mainImpl() error { |
| var err error |
| |
| flag.Parse() |
| |
| // fuchsiaDir |
| if *fuchsiaDir == "" { |
| // TODO: Update CQ to provide the fuchsia home directory. |
| //return fmt.Errorf("--fuchsia_dir cannot be empty.") |
| *fuchsiaDir = "." |
| } |
| *fuchsiaDir, _ = filepath.Abs(*fuchsiaDir) |
| checklicenses.ConfigVars["{FUCHSIA_DIR}"] = *fuchsiaDir |
| |
| // diffTarget |
| if *diffTarget != "" { |
| *diffTarget, _ = filepath.Abs(*diffTarget) |
| } |
| checklicenses.ConfigVars["{DIFF_TARGET}"] = *diffTarget |
| |
| // buildDir |
| if *buildDir == "" && *outputLicenseFile { |
| return fmt.Errorf("--build_dir cannot be empty.") |
| } |
| *buildDir, _ = filepath.Abs(*buildDir) |
| checklicenses.ConfigVars["{BUILD_DIR}"] = *buildDir |
| |
| // outDir |
| if *outDir != "" { |
| *outDir, _ = filepath.Abs(*outDir) |
| } |
| if _, err := os.Stat(*outDir); os.IsNotExist(err) { |
| err := os.Mkdir(*outDir, 0755) |
| if err != nil { |
| return fmt.Errorf("Failed to create out directory [%v]: %v\n", outDir, err) |
| } |
| } |
| checklicenses.ConfigVars["{OUT_DIR}"] = *outDir |
| |
| // gnPath |
| if *gnPath == "" && *outputLicenseFile { |
| return fmt.Errorf("--gn_path cannot be empty.") |
| } |
| *gnPath = strings.ReplaceAll(*gnPath, "{FUCHSIA_DIR}", *fuchsiaDir) |
| checklicenses.ConfigVars["{GN_PATH}"] = *gnPath |
| |
| // logLevel |
| w, err := getLogWriters(*logLevel, *outDir) |
| if err != nil { |
| return err |
| } |
| log.SetOutput(w) |
| |
| // target |
| target := "" |
| if flag.NArg() > 1 { |
| return fmt.Errorf("check-licenses takes a maximum of 1 positional argument (filepath or gn target), got %v\n", flag.NArg()) |
| } |
| if flag.NArg() == 1 { |
| target = flag.Arg(0) |
| } |
| |
| if *outputLicenseFile { |
| if isPath(target) { |
| target, _ = filepath.Abs(target) |
| } else { |
| // Run "fx gn <>" command to generate a filter file. |
| gn, err := NewGn(*gnPath, *buildDir) |
| if err != nil { |
| return err |
| } |
| |
| filterDir := filepath.Join(*outDir, "filter") |
| gnFilterFile := filepath.Join(filterDir, "gnFilter.json") |
| if _, err := os.Stat(filterDir); os.IsNotExist(err) { |
| err := os.Mkdir(filterDir, 0755) |
| if err != nil { |
| return fmt.Errorf("Failed to create filter directory [%v]: %v\n", filterDir, err) |
| } |
| } |
| |
| startGn := time.Now() |
| if target != "" { |
| log.Printf("Running 'fx gn desc %v' command...", target) |
| if err := gn.Dependencies(context.Background(), gnFilterFile, target); err != nil { |
| return err |
| } |
| } else { |
| log.Print("Running 'fx gn gen' command...") |
| if err := gn.Gen(context.Background(), gnFilterFile); err != nil { |
| return err |
| } |
| } |
| if *filter == "" { |
| *filter = gnFilterFile |
| } else { |
| *filter = fmt.Sprintf("%v,%v", gnFilterFile, *filter) |
| } |
| log.Printf("Done. [%v]\n", time.Since(startGn)) |
| } |
| } |
| checklicenses.ConfigVars["{TARGET}"] = target |
| |
| // configFile |
| configFile := strings.ReplaceAll(defaultConfigFile, "{FUCHSIA_DIR}", *fuchsiaDir) |
| config, err := checklicenses.NewCheckLicensesConfig(configFile) |
| if err != nil { |
| return err |
| } |
| |
| // Set non-string config values directly. |
| config.Result.OutputLicenseFile = *outputLicenseFile |
| config.World.Filters = strings.Split(*filter, ",") |
| |
| // Tracing |
| if *tracefile != "" { |
| f, err := os.Create(*tracefile) |
| if err != nil { |
| return fmt.Errorf("failed to create trace output file: %s", err) |
| } |
| defer f.Close() |
| if err := trace.Start(f); err != nil { |
| return fmt.Errorf("failed to start trace: %s", err) |
| } |
| defer trace.Stop() |
| } |
| if *pproffile != "" { |
| f, err := os.Create(*pproffile) |
| if err != nil { |
| return fmt.Errorf("failed to create pprof output file: %s", err) |
| } |
| defer f.Close() |
| if err := pprof.StartCPUProfile(f); err != nil { |
| return fmt.Errorf("failed to start pprof: %s", err) |
| } |
| defer pprof.StopCPUProfile() |
| } |
| |
| if err := os.Chdir(*fuchsiaDir); err != nil { |
| return err |
| } |
| |
| if err := checklicenses.Execute(context.Background(), config); err != nil { |
| return fmt.Errorf("failed to analyze the given directory: %v", err) |
| } |
| return nil |
| } |
| |
| func isPath(target string) bool { |
| if strings.HasPrefix(target, "//") { |
| return false |
| } |
| if strings.HasPrefix(target, ":") { |
| return false |
| } |
| if target == "" { |
| return false |
| } |
| return true |
| } |
| |
| // Log == 0: discard all output |
| // Log == 1: save logs to the outDir folder |
| // Log == 2: save logs to the outDir folder AND print to stdout |
| func getLogWriters(logLevel int, outDir string) (io.Writer, error) { |
| logTargets := []io.Writer{} |
| if logLevel == 0 { |
| // Default: logLevel == 0 |
| // Discard all non-error logs. |
| logTargets = append(logTargets, ioutil.Discard) |
| } else { |
| if logLevel == 1 && outDir != "" { |
| // logLevel == 1 |
| // Write all logs to a log file. |
| if _, err := os.Stat(outDir); os.IsNotExist(err) { |
| err := os.Mkdir(outDir, 0755) |
| if err != nil { |
| return nil, fmt.Errorf("Failed to create out directory [%v]: %v\n", outDir, err) |
| } |
| } |
| logfilePath := filepath.Join(outDir, "logs") |
| f, err := os.OpenFile(logfilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) |
| if err != nil { |
| return nil, fmt.Errorf("Failed to create log file [%v]: %v\n", logfilePath, err) |
| } |
| defer f.Close() |
| logTargets = append(logTargets, f) |
| } |
| if logLevel == 2 { |
| // logLevel == 2 |
| // Write all logs to a log file and stdout. |
| logTargets = append(logTargets, os.Stdout) |
| } |
| } |
| w := io.MultiWriter(logTargets...) |
| |
| return w, nil |
| } |
| |
| func main() { |
| if err := mainImpl(); err != nil { |
| fmt.Fprintf(os.Stderr, "check-licenses: %s\nSee go/fuchsia-licenses-playbook for information on resolving common errors.\n", err) |
| os.Exit(1) |
| } |
| } |