mirror of
https://github.com/rclone/rclone.git
synced 2024-12-12 02:02:02 +01:00
398 lines
12 KiB
Go
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...)
|
|
}
|
|
}()
|