blob: 5252254ae27b447c6cadaf02ea46ecd3ece15857 [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// 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 apigen
import (
"errors"
"fmt"
"go/build"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
// editFunc is a function called when a file is copied by copyFile.
//
// If editFunc returns a nil byte array, the copy will be skipped.
//
// On success, editFunc returns the new file data to write.
type editFunc func(relPath string, data []byte) ([]byte, error)
// getPackagePath searches through GOPATH to find the filesystem path of the
// named package.
//
// This is complicated by the fact that the named package might not exist. In
// this case, the package's path will be traversed until one of its parent's
// paths is found.
func getPackagePath(p string) (string, error) {
pkg := strings.Split(p, "/")
for i := len(pkg) - 1; i > 0; i-- {
p, err := build.Import(strings.Join(pkg[:i], "/"), "", build.FindOnly)
if err != nil {
continue
}
return augPath(p.Dir, pkg[i:]...), nil
}
return "", errors.New("could not find package path")
}
// augPath joins a series of path elements to a base path.
func augPath(base string, parts ...string) string {
cpath := make([]string, 0, len(parts)+1)
cpath = append(cpath, base)
cpath = append(cpath, parts...)
return filepath.Join(cpath...)
}
// installSource recursively copies an API generator output directory to a
// package location.
func installSource(src, dst string, edit editFunc) error {
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relpath, err := filepath.Rel(src, path)
if err != nil {
return fmt.Errorf("failed to get relative path [%s]: %s", path, err)
}
dstpath := filepath.Join(dst, relpath)
switch {
case info.IsDir():
// Make sure the directory exists in the target filesystem.
if err := ensureDirectory(dstpath); err != nil {
return fmt.Errorf("failed to ensure directory [%s]: %s", dstpath, err)
}
case !info.Mode().IsRegular():
// Skip non-regular files.
break
default:
// Copy the file from source to destination.
if err := copyFile(path, dstpath, relpath, edit); err != nil {
return fmt.Errorf("failed to copy file ([%s] => [%s]): %s", path, dstpath, err)
}
}
return nil
})
}
// ensureDirectory ensures that the supplied directory exists, creating it and
// its parent directories as-needed.
func ensureDirectory(path string) error {
return os.MkdirAll(path, 0755)
}
// copyFile copies the contents of a single file to a destination.
func copyFile(src, dst string, relPath string, edit editFunc) error {
data, err := ioutil.ReadFile(src)
if err != nil {
return fmt.Errorf("failed to read source: %s", err)
}
if edit != nil {
data, err = edit(relPath, data)
if err != nil {
return fmt.Errorf("edit error: %s", err)
}
}
if data == nil {
return nil
}
return ioutil.WriteFile(dst, data, 0644)
}