// Copyright 2019 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "fmt" "io/ioutil" "os" "path/filepath" "regexp" "strings" "github.com/golang/glog" "github.com/google/go-licenses/licenses" "github.com/otiai10/copy" "github.com/spf13/cobra" ) var ( saveCmd = &cobra.Command{ Use: "save ", Short: "Saves licenses, copyright notices and source code, as required by a Go package's dependencies, to a directory.", Args: cobra.MinimumNArgs(1), RunE: saveMain, } noticeRegexp = regexp.MustCompile(`^NOTICE(\.(txt|md))?$`) // savePath is where the output of the command is written to. savePath string // overwriteSavePath controls behaviour when the directory indicated by savePath already exists. // If true, the directory will be replaced. If false, the command will fail. overwriteSavePath bool ) func init() { saveCmd.Flags().StringVar(&savePath, "save_path", "", "Directory into which files should be saved that are required by license terms") if err := saveCmd.MarkFlagRequired("save_path"); err != nil { glog.Fatal(err) } if err := saveCmd.MarkFlagFilename("save_path"); err != nil { glog.Fatal(err) } saveCmd.Flags().BoolVar(&overwriteSavePath, "force", false, "Delete the destination directory if it already exists.") rootCmd.AddCommand(saveCmd) } func saveMain(_ *cobra.Command, args []string) error { if overwriteSavePath { if err := os.RemoveAll(savePath); err != nil { return err } } // Check that the save path doesn't exist, otherwise it'd end up with a mix of // existing files and the output of this command. if d, err := os.Open(savePath); err == nil { d.Close() return fmt.Errorf("%s already exists", savePath) } else if !os.IsNotExist(err) { return err } classifier, err := licenses.NewClassifier(confidenceThreshold) if err != nil { return err } libs, err := licenses.Libraries(context.Background(), classifier, args...) if err != nil { return err } libsWithBadLicenses := make(map[licenses.Type][]*licenses.Library) for _, lib := range libs { libSaveDir := filepath.Join(savePath, unvendor(lib.Name())) // Detect what type of license this library has and fulfill its requirements, e.g. copy license, copyright notice, source code, etc. _, licenseType, err := classifier.Identify(lib.LicensePath) if err != nil { return err } switch licenseType { case licenses.Restricted, licenses.Reciprocal: // Copy the entire source directory for the library. libDir := filepath.Dir(lib.LicensePath) if err := copySrc(libDir, libSaveDir); err != nil { return err } case licenses.Notice, licenses.Permissive, licenses.Unencumbered: // Just copy the license and copyright notice. if err := copyNotices(lib.LicensePath, libSaveDir); err != nil { return err } default: libsWithBadLicenses[licenseType] = append(libsWithBadLicenses[licenseType], lib) } } if len(libsWithBadLicenses) > 0 { return fmt.Errorf("one or more libraries have an incompatible/unknown license: %q", libsWithBadLicenses) } return nil } func copySrc(src, dest string) error { // Skip the .git directory for copying, if it exists, since we don't want to save the user's // local Git config along with the source code. opt := copy.Options{Skip: func(src string) (bool, error) { return strings.HasSuffix(src, ".git"), nil }} if err := copy.Copy(src, dest, opt); err != nil { return err } return nil } func copyNotices(licensePath, dest string) error { if err := copy.Copy(licensePath, filepath.Join(dest, filepath.Base(licensePath))); err != nil { return err } src := filepath.Dir(licensePath) files, err := ioutil.ReadDir(src) if err != nil { return err } for _, f := range files { if fName := f.Name(); !f.IsDir() && noticeRegexp.MatchString(fName) { if err := copy.Copy(filepath.Join(src, fName), filepath.Join(dest, fName)); err != nil { return err } } } return nil }