Skip to content

Commit

Permalink
Add generator tool to run in the sdk automation pipeline
Browse files Browse the repository at this point in the history
  • Loading branch information
ArcturusZhang committed Nov 11, 2020
1 parent 56669e1 commit 85a26ce
Show file tree
Hide file tree
Showing 17 changed files with 971 additions and 2 deletions.
2 changes: 1 addition & 1 deletion tools/apidiff/delta/delta.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func GetInterfaceMethods(lhs, rhs exports.Content) map[string]exports.Interface

// Signature contains the details of how a type signature changed (e.g. From:"int" To:"string").
type Signature struct {
// From contains the originial signature.
// From contains the original signature.
From string `json:"from"`

// To contains the new signature.
Expand Down
2 changes: 1 addition & 1 deletion tools/apidiff/exports/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (pkg Package) buildFunc(ft *ast.FuncType) (f Func) {
// appends a to s, comma-delimited style, and returns s
appendString := func(s, a string) string {
if s != "" {
s += ","
s += ", "
}
s += a
return s
Expand Down
108 changes: 108 additions & 0 deletions tools/generator/autorest/autorest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package autorest

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
)

// Task describes a generation task
type Task struct {
// AbsReadmeMd absolute path of the readme.md file to generate
AbsReadmeMd string
}

// Execute executes the autorest task, and then invoke the after scripts
// the error returned will be TaskError
func (t *Task) Execute(options Options) error {
if err := t.executeAutorest(options.AutorestArguments); err != nil {
return err
}

if err := t.executeAfterScript(options.AfterScripts); err != nil {
return err
}

return nil
}

func (t *Task) executeAutorest(options []string) error {
arguments := append(options, t.AbsReadmeMd)
c := exec.Command("autorest", arguments...)
log.Printf("Executing autorest with parameters: %+v", arguments)
c.Stdout = os.Stdout
c.Stderr = os.Stderr
c.Start()
if err := c.Wait(); err != nil {
return &TaskError{
AbsReadmeMd: t.AbsReadmeMd,
Script: "autorest",
Message: err.Error(),
}
}
return nil
}

func (t *Task) executeAfterScript(afterScripts []string) error {
for _, script := range afterScripts {
log.Printf("Executing after scripts %s...", script)
arguments := strings.Split(script, " ")
c := exec.Command(arguments[0], arguments[1:]...)
output, err := c.CombinedOutput()
if err != nil {
return &TaskError{
AbsReadmeMd: t.AbsReadmeMd,
Script: script,
Message: string(output),
}
}
}

return nil
}

// Options describes the options used in an autorest task
type Options struct {
// AutorestArguments are the optional flags for the autorest tool
AutorestArguments []string
// AfterScripts are the scripts that need to be run after the SDK is generated
AfterScripts []string
}

// NewOptionsFrom returns a new options from a io.Reader
func NewOptionsFrom(reader io.Reader) (*Options, error) {
b, err := ioutil.ReadAll(reader)
if err != nil {
return nil, err
}
var result Options
if err := json.Unmarshal(b, &result); err != nil {
return nil, err
}
return &result, nil
}

// String ...
func (o Options) String() string {
b, _ := json.MarshalIndent(o, "", " ")
return string(b)
}

// TaskError the error returned during an autorest task
type TaskError struct {
// AbsReadmeMd relative path of the readme.md file to generate
AbsReadmeMd string
// Script the script running when the error is thrown
Script string
// Message the error message
Message string
}

func (r *TaskError) Error() string {
return fmt.Sprintf("autorest task failed for readme file '%s' during '%s': %s", r.AbsReadmeMd, r.Script, r.Message)
}
113 changes: 113 additions & 0 deletions tools/generator/autorest/package.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package autorest

import (
"fmt"
"os"
"path/filepath"
"strings"
)

type ChangedPackagesMap map[string][]string

func (c *ChangedPackagesMap) addFileToPackage(pkg, file string) {
pkg = strings.ReplaceAll(pkg, "\\", "/")
if _, ok := (*c)[pkg]; !ok {
(*c)[pkg] = []string{}
}
(*c)[pkg] = append((*c)[pkg], file)
}

func (c *ChangedPackagesMap) String() string {
var r []string
for k, v := range *c {
r = append(r, fmt.Sprintf("%s: %+v", k, v))
}
return strings.Join(r, "\n")
}

func (c *ChangedPackagesMap) GetChangedPackages() []string {
var r []string
for k := range *c {
r = append(r, k)
}
return r
}

// GetChangedPackages get the go SDK packages map from the given changed file list.
// the map returned has the package full path as key, and the changed files in the package as the value.
// This function identify the package by checking if a directory has both a `version.go` file and a `client.go` file.
func GetChangedPackages(changedFiles []string) (ChangedPackagesMap, error) {
changedFiles, err := ExpandChangedDirectories(changedFiles)
if err != nil {
return nil, err
}
r := ChangedPackagesMap{}
for _, file := range changedFiles {
fi, err := os.Stat(file)
if err != nil {
return nil, err
}
path := file
if !fi.IsDir() {
path = filepath.Dir(file)
}
if IsValidPackage(path) {
r.addFileToPackage(path, file)
}
}
return r, nil
}

// ExpandChangedDirectories expands every directory listed in the array to all its file
func ExpandChangedDirectories(changedFiles []string) ([]string, error) {
var result []string
for _, path := range changedFiles {
fi, err := os.Stat(path)
if err != nil {
return nil, err
}
if fi.IsDir() {
siblings, err := getAllFiles(path)
if err != nil {
return nil, err
}
result = append(result, siblings...)
} else {
result = append(result, path)
}
}

return result, nil
}

func getAllFiles(root string) ([]string, error) {
var siblings []string
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
siblings = append(siblings, strings.ReplaceAll(path, "\\", "/"))
}
return nil
})
return siblings, err
}

const (
clientGo = "client.go"
versionGo = "version.go"
)

func IsValidPackage(dir string) bool {
client := filepath.Join(dir, clientGo)
version := filepath.Join(dir, versionGo)
// both the above files must exist to return true
if _, err := os.Stat(client); os.IsNotExist(err) {
return false
}
if _, err := os.Stat(version); os.IsNotExist(err) {
return false
}
return true
}
Loading

0 comments on commit 85a26ce

Please sign in to comment.