rclone/vendor/github.com/Azure/azure-sdk-for-go/tools/profileBuilder/program.go
2018-01-16 13:20:59 +00:00

398 lines
12 KiB
Go

// +build go1.9
// Copyright 2017 Microsoft Corporation and contributors
//
// 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.
// profileBuilder creates a series of packages filled entirely with alias types
// and functions supporting those alias types by directing traffic to the
// functions supporting the original types. This is useful associating a series
// of packages in separate API Versions for easier/safer use.
//
// The Azure-SDK-for-Go teams intends to use this tool to generated profiles
// that we will publish in this repository for general use. However, this tool
// in the case that one has their own list of Services at given API Versions,
// this may prove to be a useful tool for you.
package main
import (
"bytes"
"errors"
"flag"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"time"
"github.com/marstr/collection"
goalias "github.com/marstr/goalias/model"
"github.com/marstr/randname"
)
var (
profileName string
outputLocation string
inputRoot string
inputList io.Reader
packageStrategy collection.Enumerable
outputLog *log.Logger
errLog *log.Logger
)
// WellKnownStrategy is an Enumerable which lists all known strategies for choosing packages for a profile.
type WellKnownStrategy string
// This block declares the definitive list of WellKnownStrategies
const (
WellKnownStrategyList WellKnownStrategy = "list"
WellKnownStrategyLatest WellKnownStrategy = "latest"
WellKnownStrategyPreview WellKnownStrategy = "preview"
)
const armPathModifier = "mgmt"
// If not the empty string, this string should be stamped into files generated by the profileBuilder.
// Note: This variable should be set by passing the argument "-X main.version=`{your value}`" to the Go linker. example: `go build -ldflags "-X main.version=f43d726b6e3f1e3eb7cbdba3982f0253000d5dc5"`
var version string
func main() {
var packages collection.Enumerator
type alias struct {
*goalias.AliasPackage
TargetPath string
}
// Find the names of all of the packages for inclusion in this profile.
packages = packageStrategy.Enumerate(nil).Select(func(x interface{}) interface{} {
if cast, ok := x.(string); ok {
return cast
}
return nil
})
// Parse the packages that were selected for inclusion in this profile.
packages = packages.SelectMany(func(x interface{}) collection.Enumerator {
results := make(chan interface{})
go func() {
defer close(results)
cast, ok := x.(string)
if !ok {
return
}
files := token.NewFileSet()
parsed, err := parser.ParseDir(files, cast, nil, 0)
if err != nil {
errLog.Printf("Couldn't open %q because: %v", cast, err)
return
}
for _, entry := range parsed {
results <- entry
}
}()
return results
})
// Generate the alias package from the originally parsed one.
packages = packages.ParallelSelect(func(x interface{}) interface{} {
var err error
var subject *goalias.AliasPackage
cast, ok := x.(*ast.Package)
if !ok {
return nil
}
var bundle alias
for filename := range cast.Files {
bundle.TargetPath = filepath.Dir(filename)
bundle.TargetPath = trimGoPath(bundle.TargetPath)
subject, err = goalias.NewAliasPackage(cast, bundle.TargetPath)
if err != nil {
errLog.Print(err)
return nil
}
bundle.TargetPath, err = getAliasPath(bundle.TargetPath, profileName)
if err != nil {
errLog.Print(err)
return nil
}
break
}
bundle.AliasPackage = subject
return &bundle
})
packages = packages.Where(func(x interface{}) bool {
return x != nil
})
// Update the "UserAgent" function in the generated profile, if it is present.
packages = packages.Select(func(x interface{}) interface{} {
cast := x.(*alias)
var userAgent *ast.FuncDecl
// Grab all functions in the alias package named "UserAgent"
userAgentCandidates := collection.Where(collection.AsEnumerable(cast.Files["models.go"].Decls), func(x interface{}) bool {
cast, ok := x.(*ast.FuncDecl)
return ok && cast.Name.Name == "UserAgent"
})
// There should really only be one of them, otherwise bailout because we don't understand the world anymore.
candidate, err := collection.Single(userAgentCandidates)
if err != nil {
return x
}
userAgent, ok := candidate.(*ast.FuncDecl)
if !ok {
return x
}
// Grab the expression being returned.
retResults := &userAgent.Body.List[0].(*ast.ReturnStmt).Results[0]
// Append a string literal to the result
updated := &ast.BinaryExpr{
Op: token.ADD,
X: *retResults,
Y: &ast.BasicLit{
Value: fmt.Sprintf("\" profiles/%s\"", profileName),
},
}
*retResults = updated
return x
})
// Add the MSFT Copyright Header, then write the alias package to disk.
products := packages.ParallelSelect(func(x interface{}) interface{} {
cast, ok := x.(*alias)
if !ok {
return false
}
files := token.NewFileSet()
outputPath := filepath.Join(outputLocation, cast.TargetPath, "models.go")
outputPath = strings.Replace(outputPath, `\`, `/`, -1)
err := os.MkdirAll(path.Dir(outputPath), os.ModePerm|os.ModeDir)
if err != nil {
errLog.Print("error creating directory:", err)
return false
}
outputFile, err := os.Create(outputPath)
if err != nil {
errLog.Print("error creating file: ", err)
return false
}
// TODO: This should really be added by the `goalias` package itself. Doing it here is a work around
fmt.Fprintln(outputFile, "// +build go1.9")
fmt.Fprintln(outputFile)
generatorStampBuilder := new(bytes.Buffer)
fmt.Fprintf(generatorStampBuilder, "// Copyright %4d Microsoft Corporation\n", time.Now().Year())
fmt.Fprintln(generatorStampBuilder, `//
// 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.`)
fmt.Fprintln(outputFile, generatorStampBuilder.String())
generatorStampBuilder.Reset()
fmt.Fprintln(generatorStampBuilder, "// This code was auto-generated by:")
fmt.Fprintln(generatorStampBuilder, "// github.com/Azure/azure-sdk-for-go/tools/profileBuilder")
if version != "" {
fmt.Fprintln(generatorStampBuilder, "// commit ID:", version)
}
fmt.Fprintln(generatorStampBuilder)
fmt.Fprint(outputFile, generatorStampBuilder.String())
outputLog.Printf("Writing File: %s", outputPath)
printer.Fprint(outputFile, files, cast.ModelFile())
return true
})
generated := 0
// Write each aliased package that was found
for entry := range products {
if entry.(bool) {
generated++
}
}
outputLog.Print(generated, " packages generated.")
if err := exec.Command("gofmt", "-w", outputLocation).Run(); err == nil {
outputLog.Print("Success formatting profile.")
} else {
errLog.Print("Trouble formatting profile: ", err)
}
}
func init() {
const defaultName = "{randomly generated}"
var selectedStrategy string
var inputListLocation string
var useVerbose bool
flag.StringVar(&profileName, "name", defaultName, "The name that should be given to the generated profile.")
flag.StringVar(&outputLocation, "o", defaultOutputLocation(), "The output location for the package generated as a profile.")
flag.StringVar(&inputRoot, "root", defaultInputRoot(), "The location of the Azure SDK for Go's service packages.")
flag.StringVar(&inputListLocation, "l", "", "If the `list` strategy is chosen, -l is the location of the file to read for said list. If not present, stdin is used.")
flag.StringVar(&selectedStrategy, "s", string(WellKnownStrategyLatest), "The strategy to employ for finding packages to put in a profile.")
flag.BoolVar(&useVerbose, "v", false, "Write status to stderr as the program progresses")
flag.Parse()
// Setup Verbose Status Log and Error Log
var logWriter io.Writer
if useVerbose {
logWriter = os.Stderr
} else {
logWriter = ioutil.Discard
}
outputLog = log.New(logWriter, "[STATUS] ", 0)
outputLog.Print("Status Logging Enabled")
errLog = log.New(logWriter, "[ERROR] ", 0)
if version != "" {
outputLog.Print("profileBuilder Version: ", version)
}
// Sort out the Profile Name to be used.
if profileName == defaultName {
profileName = randname.AdjNoun{}.Generate()
outputLog.Print("Profile Name Set to: ", profileName)
}
inputList = os.Stdin
if inputListLocation == "" {
outputLog.Print("Reading input from standard input")
} else {
var err error
outputLog.Print("Reading input from: ", inputListLocation)
inputList, err = os.Open(inputListLocation)
if err != nil {
errLog.Print(err)
os.Exit(1)
}
}
wellKnownStrategies := map[WellKnownStrategy]collection.Enumerable{
WellKnownStrategyList: ListStrategy{Reader: inputList},
WellKnownStrategyLatest: LatestStrategy{Root: inputRoot, Predicate: IgnorePreview, VerboseOutput: outputLog},
WellKnownStrategyPreview: LatestStrategy{Root: inputRoot, Predicate: AcceptAll},
}
if s, ok := wellKnownStrategies[WellKnownStrategy(selectedStrategy)]; ok {
packageStrategy = s
outputLog.Printf("Using Well Known Strategy: %s", selectedStrategy)
} else {
errLog.Printf("Unknown strategy for identifying packages: %s\n", selectedStrategy)
os.Exit(1)
}
}
// AzureSDKforGoLocation returns the default location for the Azure-SDK-for-Go to reside.
func AzureSDKforGoLocation() string {
return path.Join(
os.Getenv("GOPATH"),
"src",
"github.com",
"Azure",
"azure-sdk-for-go",
)
}
func defaultOutputLocation() string {
return path.Join(AzureSDKforGoLocation(), "profiles")
}
func defaultInputRoot() string {
return path.Join(AzureSDKforGoLocation(), "services")
}
// getAliasPath takes an existing API Version path and a package name, and converts the path
// to a path which uses the new profile layout.
func getAliasPath(subject, profile string) (transformed string, err error) {
subject = strings.TrimSuffix(subject, "/")
subject = trimGoPath(subject)
matches := packageName.FindAllStringSubmatch(subject, -1)
if matches == nil {
err = errors.New("path does not resemble a known package path")
return
}
output := []string{
profile,
matches[0][1],
}
if matches[0][2] == armPathModifier {
output = append(output, armPathModifier)
}
output = append(output, matches[0][4])
transformed = strings.Join(output, "/")
return
}
// trimGoPath removes the prefix defined in the environment variabe GOPATH if it is present in the string provided.
var trimGoPath = func() func(string) string {
splitGo := strings.Split(os.Getenv("GOPATH"), string(os.PathSeparator))
splitGo = append(splitGo, "src")
return func(subject string) string {
splitPath := strings.Split(subject, string(os.PathSeparator))
for i, dir := range splitGo {
if splitPath[i] != dir {
return subject
}
}
packageIdentifier := splitPath[len(splitGo):]
return path.Join(packageIdentifier...)
}
}()