blob: 3957dc330a67ef353628f379894d58da4f4112c4 [file] [log] [blame]
// 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)
}
}