github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/autogo/shadow.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // autogo creates a shadow copy of the go packages at origin in a destination.
    18  //
    19  // In other words this program will walk the directory tree at origin
    20  // and for each:
    21  // * directory - create a directory with the same name in destination
    22  // * go-related-file - link the file into the matching destination directory
    23  //
    24  // The effect is similar to:
    25  //   rsync -zarv --include="*/" --include="*.sh" --exclude="*" "$from" "$to"
    26  // TODO(fejta): investigate just using rsync?
    27  //
    28  // The intended use case of this program is with the autogo_generate in //autogo:def.bzl. This rule will clone your primary workspace into an autogo workspace, and then run gazelle to generate rules for go packages.
    29  //
    30  // Usage:
    31  //   autogo -- <ORIGIN_DIR> <DEST_DIR>
    32  //
    33  package main
    34  
    35  import (
    36  	"fmt"
    37  	"log"
    38  	"os"
    39  	"path/filepath"
    40  	"strings"
    41  )
    42  
    43  // main calls shadowClone and exits non-zero on failure
    44  func main() {
    45  	if n := len(os.Args); n != 3 {
    46  		log.Printf("Expected 2 args, not %d", n)
    47  		log.Fatalf("Usage: %s <ORIGIN> <DEST>", filepath.Base(os.Args[0]))
    48  	}
    49  	if err := shadowClone(os.Args[1], os.Args[2]); err != nil {
    50  		log.Fatalf("Failed to clone %s to %s: %v", os.Args[1], os.Args[2], err)
    51  	}
    52  }
    53  
    54  // shadowClone walks origin to clone the directory structure and link files at the destination.
    55  func shadowClone(origin, dest string) error {
    56  	v := visitor{
    57  		origin: origin,
    58  		dest:   dest,
    59  	}
    60  	return filepath.Walk(v.origin, v.visit)
    61  }
    62  
    63  // action does something with the current file (mkdir or link)
    64  type action func(origin string, info os.FileInfo, dest string) error
    65  
    66  // visitor stores the origin we're cloning from and destination we clone to.
    67  type visitor struct {
    68  	// origin is the path we read to determine what to clone
    69  	origin string
    70  	// dest is where we clone
    71  	dest string
    72  }
    73  
    74  // visit chooses and then performs the right action for the current path
    75  func (v visitor) visit(path string, info os.FileInfo, verr error) error {
    76  	act, dest, err := v.choose(path, info, verr)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	if act == nil {
    81  		return nil
    82  	}
    83  	return act(path, info, dest)
    84  }
    85  
    86  // choose looks at the current path and returns the appropriate action:
    87  // - testdata directory => skip it and everything under it
    88  // - directory => create the directory
    89  // - go file (.go, .s) => create it at dest
    90  // - other files => do nothing
    91  // TODO(fejta): consider including .proto in the mix
    92  func (v visitor) choose(path string, info os.FileInfo, verr error) (action, string, error) {
    93  	if verr != nil {
    94  		return nil, "", fmt.Errorf("failed to walk to %s: %v", path, verr)
    95  	}
    96  
    97  	// If origin is /foo/bar and we receive /foo/bar/something/special
    98  	// Change this to something/special
    99  	r, err := filepath.Rel(v.origin, path)
   100  	// First ensure the path is a child of origin
   101  	if err != nil {
   102  		return nil, "", fmt.Errorf("%q is not relative to %q: %v", path, v.origin, err)
   103  	}
   104  	if r == ".." || strings.HasPrefix(r, "../") {
   105  		return nil, "", fmt.Errorf("%q is not a child of %q", path, v.origin)
   106  	}
   107  
   108  	// TODO(fejta): handle symlink loops better
   109  	// Many repos (dep is one) have testdata folders which are
   110  	// either symlink loops back to some other part of the repo
   111  	// or else intentionally do not compile.
   112  	//
   113  	// The gazelle tool is not robust to packages containing files that fail to compile.
   114  	//
   115  	// For now just ignore these testdata folders (usually in vendored packages). This makes bazel build work at the cost of breaking bazel test
   116  	if info.IsDir() && strings.HasSuffix(path, "/testdata") {
   117  		log.Printf("Skipping %s...", path)
   118  		return nil, "", filepath.SkipDir
   119  	}
   120  
   121  	d := filepath.Join(v.dest, r)
   122  	// Create dirs
   123  	if info.IsDir() {
   124  		return mkdir, d, nil
   125  	}
   126  
   127  	if strings.Contains(path, "/testdata/") {
   128  		return nil, "", fmt.Errorf("%s is within a testdata dir, which should not happen", path)
   129  	}
   130  
   131  	// Ignore files irrelevant to go
   132  	if !strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, ".s") {
   133  		// Assume .go and .s are the only relevant files to a build
   134  		// TODO(fejta): consider adding BUILD and BUILD.bazel files, or at least the go rules within them
   135  		return nil, "", nil
   136  	}
   137  
   138  	// Link in golang files
   139  	return link, d, nil
   140  }
   141  
   142  // mkdir creates directories for dest
   143  func mkdir(_ string, info os.FileInfo, dest string) error {
   144  	if err := os.MkdirAll(dest, info.Mode()); err != nil {
   145  		return fmt.Errorf("failed to create %q: %v", dest, err)
   146  	}
   147  	return nil
   148  }
   149  
   150  // link clones source at dest
   151  func link(source string, _ os.FileInfo, dest string) error {
   152  	// First try a hard link
   153  	if err := os.Link(source, dest); err == nil {
   154  		return nil
   155  	}
   156  
   157  	// If that fails require a symlink
   158  	return os.Symlink(source, dest)
   159  }