diff --git a/tools/apidiff/report/report.go b/tools/apidiff/report/report.go index d79e893d55ad..b174dd3eb83e 100644 --- a/tools/apidiff/report/report.go +++ b/tools/apidiff/report/report.go @@ -120,6 +120,7 @@ func (p Package) writeNewContent(md *markdown.Writer) { if !p.HasAdditiveChanges() { return } + md.WriteTopLevelHeader("Additive Changes") writeConsts(p.AdditiveChanges.Consts, "New Constants", md) writeFuncs(p.AdditiveChanges.Funcs, "New Funcs", md) writeStructs(p.AdditiveChanges, "New Structs", "New Struct Fields", md) @@ -140,7 +141,7 @@ func writeSigChanges(bc *BreakingChanges, md *markdown.Writer) { if len(bc.Consts) == 0 && len(bc.Funcs) == 0 && len(bc.Structs) == 0 { return } - md.WriteTopLevelHeader("Signature Changes") + md.WriteHeader("Signature Changes") if len(bc.Consts) > 0 { items := make([]string, len(bc.Consts)) i := 0 @@ -149,7 +150,7 @@ func writeSigChanges(bc *BreakingChanges, md *markdown.Writer) { i++ } sort.Strings(items) - md.WriteHeader("Const Types") + md.WriteSubheader("Const Types") for _, item := range items { md.WriteLine(item) } @@ -163,7 +164,7 @@ func writeSigChanges(bc *BreakingChanges, md *markdown.Writer) { i++ } sort.Strings(items) - md.WriteHeader("Funcs") + md.WriteSubheader("Funcs") for _, item := range items { // now add params/returns info changes := bc.Funcs[item] @@ -184,7 +185,7 @@ func writeSigChanges(bc *BreakingChanges, md *markdown.Writer) { } } sort.Strings(items) - md.WriteHeader("Struct Fields") + md.WriteSubheader("Struct Fields") for _, item := range items { md.WriteLine(item) } @@ -245,16 +246,16 @@ func writeStructs(content *delta.Content, sheader1, sheader2 string, md *markdow if len(content.Structs) == 0 { return } - md.WriteTopLevelHeader("Struct Changes") + md.WriteHeader("Struct Changes") if len(content.CompleteStructs) > 0 { - md.WriteHeader(sheader1) + md.WriteSubheader(sheader1) for _, s := range content.CompleteStructs { md.WriteLine(fmt.Sprintf("1. %s", s)) } } modified := content.GetModifiedStructs() if len(modified) > 0 { - md.WriteHeader(sheader2) + md.WriteSubheader(sheader2) items := make([]string, 0, len(content.Structs)-len(content.CompleteStructs)) for s, f := range modified { for _, af := range f.AnonymousFields { diff --git a/tools/generator/autorest/autorest.go b/tools/generator/autorest/autorest.go index 9bad2eb3de56..b2cdc28f030f 100644 --- a/tools/generator/autorest/autorest.go +++ b/tools/generator/autorest/autorest.go @@ -7,13 +7,14 @@ import ( "fmt" "io" "os/exec" + "strings" "github.com/Azure/azure-sdk-for-go/tools/generator/autorest/model" ) // Generator collects all the related context of an autorest generation type Generator struct { - arguments []string + arguments []model.Option cmd *exec.Cmd } @@ -26,7 +27,7 @@ func NewGeneratorFromOptions(o model.Options) *Generator { // WithOption appends an model.Option to the argument list of the autorest generation func (g *Generator) WithOption(option model.Option) *Generator { - g.arguments = append(g.arguments, option.Format()) + g.arguments = append(g.arguments, option) return g } @@ -67,7 +68,16 @@ func (g *Generator) buildCommand() { if g.cmd != nil { return } - g.cmd = exec.Command("autorest", g.arguments...) + arguments := make([]string, len(g.arguments)) + for i, o := range g.arguments { + arguments[i] = o.Format() + } + g.cmd = exec.Command("autorest", arguments...) +} + +// Arguments returns the arguments which are using in the autorest command ('autorest' itself excluded) +func (g *Generator) Arguments() []model.Option { + return g.arguments } // Start starts the generation @@ -120,11 +130,15 @@ func (g *Generator) StderrPipe() (io.ReadCloser, error) { // GenerateError ... type GenerateError struct { - Arguments []string + Arguments []model.Option Message string } // Error ... func (e *GenerateError) Error() string { - return fmt.Sprintf("autorest error with arguments '%s': \n%s", e.Arguments, e.Message) + arguments := make([]string, len(e.Arguments)) + for i, o := range e.Arguments { + arguments[i] = o.Format() + } + return fmt.Sprintf("autorest error with arguments '%s': \n%s", strings.Join(arguments, ", "), e.Message) } diff --git a/tools/generator/autorest/changelog.go b/tools/generator/autorest/changelog.go index d13c29b286c0..a45f5a31d49b 100644 --- a/tools/generator/autorest/changelog.go +++ b/tools/generator/autorest/changelog.go @@ -5,9 +5,9 @@ package autorest import ( "fmt" + "io" "os" "path/filepath" - "strings" "github.com/Azure/azure-sdk-for-go/tools/apidiff/exports" "github.com/Azure/azure-sdk-for-go/tools/apidiff/report" @@ -18,10 +18,7 @@ import ( // ChangelogContext describes all necessary data that would be needed in the processing of changelogs type ChangelogContext interface { SDKRoot() string - SDKCloneRoot() string - SpecRoot() string - SpecCommitHash() string - CodeGenVersion() string + RepoContent() map[string]exports.Content } // ChangelogProcessor processes the metadata and output changelog with the desired format @@ -38,30 +35,12 @@ func NewChangelogProcessorFromContext(ctx ChangelogContext) *ChangelogProcessor } } -// WithLocation adds the information of the metadata-output-folder -func (p *ChangelogProcessor) WithLocation(metadataLocation string) *ChangelogProcessor { - p.metadataLocation = metadataLocation - return p -} - -// WithReadme adds the information of the path of readme.md file. This path could be relative or absolute. -func (p *ChangelogProcessor) WithReadme(readme string) *ChangelogProcessor { - // make sure the readme here is a relative path to the root of spec - readme = utils.NormalizePath(readme) - root := utils.NormalizePath(p.ctx.SpecRoot()) - if filepath.IsAbs(readme) { - readme = strings.TrimPrefix(readme, root) - } - p.readme = readme - return p -} - // ChangelogResult describes the result of the generated changelog for one package type ChangelogResult struct { - PackageName string - PackagePath string - GenerationMetadata GenerationMetadata - Changelog model.Changelog + Tag string + PackageName string + PackageFullPath string + Changelog model.Changelog } // ChangelogProcessError describes the errors during the processing @@ -114,39 +93,43 @@ func (p *ChangelogProcessor) Process(metadataMap map[string]model.Metadata) ([]C } // GenerateChangelog generates a changelog for one package -func (p *ChangelogProcessor) GenerateChangelog(packagePath, tag string) (*ChangelogResult, error) { - // use the relative path to the sdk root as package name - packageName, err := filepath.Rel(p.ctx.SDKRoot(), packagePath) +func (p *ChangelogProcessor) GenerateChangelog(packageFullPath, tag string) (*ChangelogResult, error) { + relativePackagePath, err := p.getRelativePackagePath(packageFullPath) if err != nil { return nil, err } - // normalize the package name - packageName = utils.NormalizePath(packageName) - lhs, err := getExportsForPackage(filepath.Join(p.ctx.SDKCloneRoot(), packageName)) + lhs := p.getLHSContent(relativePackagePath) + rhs, err := getExportsForPackage(packageFullPath) if err != nil { - return nil, fmt.Errorf("failed to get exports from package '%s' in the clone '%s': %+v", packageName, p.ctx.SDKCloneRoot(), err) + return nil, fmt.Errorf("failed to get exports from package '%s' in the sdk '%s': %+v", relativePackagePath, p.ctx.SDKRoot(), err) } - rhs, err := getExportsForPackage(packagePath) + r, err := GetChangelogForPackage(lhs, rhs) if err != nil { - return nil, fmt.Errorf("failed to get exports from package '%s' in the sdk '%s': %+v", packageName, p.ctx.SDKRoot(), err) - } - r, err := getChangelogForPackage(lhs, rhs) - if err != nil { - return nil, fmt.Errorf("failed to generate changelog for package '%s': %+v", packagePath, err) + return nil, fmt.Errorf("failed to generate changelog for package '%s': %+v", relativePackagePath, err) } return &ChangelogResult{ - PackageName: packageName, - PackagePath: packagePath, - GenerationMetadata: GenerationMetadata{ - CommitHash: p.ctx.SpecCommitHash(), - Readme: p.readme, - Tag: tag, - CodeGenVersion: p.ctx.CodeGenVersion(), - }, - Changelog: *r, + Tag: tag, + PackageName: relativePackagePath, + PackageFullPath: packageFullPath, + Changelog: *r, }, nil } +func (p *ChangelogProcessor) getRelativePackagePath(fullPath string) (string, error) { + relative, err := filepath.Rel(p.ctx.SDKRoot(), fullPath) + if err != nil { + return "", err + } + return utils.NormalizePath(relative), nil +} + +func (p *ChangelogProcessor) getLHSContent(relativePackagePath string) *exports.Content { + if v, ok := p.ctx.RepoContent()[relativePackagePath]; ok { + return &v + } + return nil +} + func getExportsForPackage(dir string) (*exports.Content, error) { // The function exports.Get does not handle the circumstance that the package does not exist // therefore we have to check if it exists and if not exit early to ensure we do not return an error @@ -160,7 +143,8 @@ func getExportsForPackage(dir string) (*exports.Content, error) { return &exp, nil } -func getChangelogForPackage(lhs, rhs *exports.Content) (*model.Changelog, error) { +// GetChangelogForPackage generates the changelog report with the given two Contents +func GetChangelogForPackage(lhs, rhs *exports.Content) (*model.Changelog, error) { if lhs == nil && rhs == nil { return nil, fmt.Errorf("this package does not exist even after the generation, this should never happen") } @@ -182,3 +166,19 @@ func getChangelogForPackage(lhs, rhs *exports.Content) (*model.Changelog, error) Modified: &p, }, nil } + +// ToMarkdown convert this ChangelogResult to markdown string +func (r ChangelogResult) ToMarkdown() string { + return r.Changelog.ToMarkdown() +} + +// Write writes this ChangelogResult to the specified writer in markdown format +func (r ChangelogResult) Write(writer io.Writer) error { + _, err := writer.Write([]byte(r.ToMarkdown())) + return err +} + +const ( + // ChangelogFilename ... + ChangelogFilename = "CHANGELOG.md" +) diff --git a/tools/generator/autorest/generationMetadata.go b/tools/generator/autorest/generationMetadata.go index 14891921c88b..ed68896d35a2 100644 --- a/tools/generator/autorest/generationMetadata.go +++ b/tools/generator/autorest/generationMetadata.go @@ -4,64 +4,46 @@ package autorest import ( + "encoding/json" "fmt" - "io" "io/ioutil" "log" "os" "path/filepath" - "regexp" "strings" + "github.com/Azure/azure-sdk-for-go/tools/generator/autorest/model" "github.com/Azure/azure-sdk-for-go/tools/pkgchk/track1" ) -// GeneratedFrom gives the information of the generation metadata, including the commit hash that this package is generated from, -// the readme path, and the tag -func GeneratedFrom(commitHash, readme, tag string) string { - return fmt.Sprintf("Generated from https://github.com/Azure/azure-rest-api-specs/tree/%s/%s tag: `%s`", commitHash, readme, tag) -} - // GenerationMetadata contains all the metadata that has been used when generating a track 1 package type GenerationMetadata struct { - AutorestVersion string `json:"autorest,omitempty"` - CommitHash string `json:"commit,omitempty"` - Readme string `json:"readme,omitempty"` - Tag string `json:"tag,omitempty"` - CodeGenVersion string `json:"use,omitempty"` - RepositoryURL string `json:"repository_url,omitempty"` - AutorestCommand string `json:"autorest_command,omitempty"` - AdditionalProperties map[string]interface{} `json:"additional_properties,omitempty"` + // AutorestVersion is the version of autorest.core + AutorestVersion string `json:"autorest,omitempty"` + // CommitHash is the commit hash of azure-rest-api-specs from which this SDK package is generated + CommitHash string `json:"commit,omitempty"` + // Readme is the normalized path of the readme file from which this SDK package is generated. It should be in this pattern: /_/azure-rest-api-specs/{relative_path} + Readme string `json:"readme,omitempty"` + // Tag is the tag from which this SDK package is generated + Tag string `json:"tag,omitempty"` + // CodeGenVersion is the version of autorest.go using when this package is generated + CodeGenVersion string `json:"use,omitempty"` + // RepositoryURL is the URL of the azure-rest-api-specs. This should always be a constant "https://github.com/Azure/azure-rest-api-specs.git" + RepositoryURL string `json:"repository_url,omitempty"` + // AutorestCommand is the full command that generates this package + AutorestCommand string `json:"autorest_command,omitempty"` + // AdditionalProperties is a map of addition information in this metadata + AdditionalProperties GenerationMetadataAdditionalProperties `json:"additional_properties,omitempty"` } -// String ... -func (m GenerationMetadata) String() string { - return fmt.Sprintf(`%s - -Code generator %s -`, GeneratedFrom(m.CommitHash, m.Readme, m.Tag), m.CodeGenVersion) +// GenerationMetadataAdditionalProperties contains all the additional options other than go-sdk-foler, tag, multiapi, use or the readme path +type GenerationMetadataAdditionalProperties struct { + AdditionalOptions string `json:"additional_options,omitempty"` } -// Parse parses the metadata info stored in a changelog with certain format into the GenerationMetadata struct -func Parse(reader io.Reader) (*GenerationMetadata, error) { - b, err := ioutil.ReadAll(reader) - if err != nil { - return nil, err - } - lines := strings.Split(string(b), "\n") - if len(lines) < 3 { - return nil, fmt.Errorf("expecting at least 3 lines from changelog, but only get %d line(s)", len(lines)) - } - // parse the first line to get readme, tag and commit hash - m, err := parseFirstLine(strings.TrimSpace(lines[0])) - if err != nil { - return nil, err - } - m.CodeGenVersion, err = parseThirdLine(strings.TrimSpace(lines[2])) - if err != nil { - return nil, err - } - return m, nil +// RelativeReadme returns the relative readme path +func (meta *GenerationMetadata) RelativeReadme() string { + return strings.TrimPrefix(meta.Readme, NormalizedSpecRoot) } // CollectGenerationMetadata iterates every track 1 go sdk package under root, and collect all the GenerationMetadata into a map @@ -77,55 +59,75 @@ func CollectGenerationMetadata(root string) (map[string]GenerationMetadata, erro if err != nil { return nil, err } - result[pkg.FullPath()] = *m + if m != nil { + result[pkg.FullPath()] = *m + } } return result, nil } // GetGenerationMetadata gets the GenerationMetadata in one specific package func GetGenerationMetadata(pkg track1.Package) (*GenerationMetadata, error) { - changelogPath := filepath.Join(pkg.FullPath(), ChangelogFilename) + metadataFilepath := filepath.Join(pkg.FullPath(), MetadataFilename) // some classical package might not have a changelog, therefore we need to identify whether the changelog file exist or not - if _, err := os.Stat(changelogPath); os.IsNotExist(err) { - log.Printf("package '%s' does not have a changelog file", pkg.Path()) - return &GenerationMetadata{}, nil + if _, err := os.Stat(metadataFilepath); os.IsNotExist(err) { + log.Printf("package '%s' does not have a metadata file", pkg.Path()) + return nil, nil } - file, err := os.Open(changelogPath) + b, err := ioutil.ReadFile(metadataFilepath) if err != nil { - return nil, fmt.Errorf("cannot open file %s: %+v", changelogPath, err) + return nil, fmt.Errorf("cannot read file %s: %+v", metadataFilepath, err) } - defer file.Close() - return Parse(file) -} -func parseFirstLine(line string) (*GenerationMetadata, error) { - matches := firstLineRegex.FindStringSubmatch(line) - if len(matches) < 4 { - return nil, fmt.Errorf("expecting 4 matches for line '%s', but only get the following matches: [%s]", line, strings.Join(matches, ", ")) + var metadata GenerationMetadata + if err := json.Unmarshal(b, &metadata); err != nil { + return nil, fmt.Errorf("cannot unmarshal metadata: %+v", err) } - return &GenerationMetadata{ - CommitHash: matches[1], - Readme: matches[2], - Tag: matches[3], - }, nil + + return &metadata, nil } -func parseThirdLine(line string) (string, error) { - matches := thirdLineRegex.FindStringSubmatch(line) - if len(matches) < 2 { - return "", fmt.Errorf("expecting 2 matches for line '%s', but only get the following matches: [%s]", line, strings.Join(matches, ", ")) +// AdditionalOptions removes flags that may change over scenarios +func AdditionalOptions(arguments []model.Option) []model.Option { + var transformed []model.Option + for _, argument := range arguments { + switch o := argument.(type) { + case model.ArgumentOption: // omit the readme path argument + continue + case model.FlagOption: + if o.Flag() == "multiapi" { // omit the multiapi flag or use + continue + } + case model.KeyValueOption: + // omit go-sdk-folder, use, tag and metadata-output-folder + if o.Key() == "go-sdk-folder" || o.Key() == "use" || o.Key() == "tag" || o.Key() == "metadata-output-folder" { + continue + } + } + transformed = append(transformed, argument) } - return matches[1], nil + return transformed } -var ( - firstLineRegex = regexp.MustCompile("^Generated from https://github\\.com/Azure/azure-rest-api-specs/tree/([0-9a-f]+)/(.+) tag: `(.+)`$") - thirdLineRegex = regexp.MustCompile(`^Code generator (\S+)$`) -) +// AdditionalOptionsToString removes flags that may change over scenarios and cast them to strings +func AdditionalOptionsToString(arguments []model.Option) []string { + transformed := AdditionalOptions(arguments) + result := make([]string, len(transformed)) + for i, o := range transformed { + result[i] = o.Format() + } + return result +} const ( - // ChangelogFilename ... - ChangelogFilename = "CHANGELOG.md" + // NormalizedSpecRoot this is the prefix for readme + NormalizedSpecRoot = "/_/azure-rest-api-specs/" + + // NormalizedSDKRoot this is the prefix for readme + NormalizedSDKRoot = "/_/azure-sdk-for-go/" + + // MetadataFilename ... + MetadataFilename = "_meta.json" ) diff --git a/tools/generator/autorest/generationMetadata_test.go b/tools/generator/autorest/generationMetadata_test.go deleted file mode 100644 index 2d6091ecfdd1..000000000000 --- a/tools/generator/autorest/generationMetadata_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -package autorest_test - -import ( - "strings" - "testing" - - "github.com/Azure/azure-sdk-for-go/tools/generator/autorest" -) - -func TestParse(t *testing.T) { - tests := []struct { - changelog string - expected autorest.GenerationMetadata - }{ - { - changelog: "Generated from https://github.com/Azure/azure-rest-api-specs/tree/3c764635e7d442b3e74caf593029fcd440b3ef82/specification/compute/resource-manager/readme.md tag: `package-2020-06-30`\n\nCode generator @microsoft.azure/autorest.go@2.1.168\n", - expected: autorest.GenerationMetadata{ - CommitHash: "3c764635e7d442b3e74caf593029fcd440b3ef82", - Readme: "specification/compute/resource-manager/readme.md", - Tag: "package-2020-06-30", - CodeGenVersion: "@microsoft.azure/autorest.go@2.1.168", - }, - }, - } - - for _, c := range tests { - reader := strings.NewReader(c.changelog) - m, err := autorest.Parse(reader) - if err != nil { - t.Fatalf("unexpected error: %+v", err) - } - if m.String() != c.expected.String() { - t.Fatalf("expect %+v, but got %+v", c.expected, *m) - } - } -} diff --git a/tools/generator/autorest/model/changelog.go b/tools/generator/autorest/model/changelog.go index b9e78a77f64b..9b02e8834aff 100644 --- a/tools/generator/autorest/model/changelog.go +++ b/tools/generator/autorest/model/changelog.go @@ -54,23 +54,41 @@ func (c Changelog) ToCompactMarkdown() string { return writeChangelogForPackage(c.Modified) } +// GetBreakingChangeItems returns an array of the breaking change items +func (c Changelog) GetBreakingChangeItems() []string { + if c.Modified == nil { + return nil + } + return getBreakingChanges(c.Modified.BreakingChanges) +} + func writeChangelogForPackage(r *report.Package) string { if r == nil || r.IsEmpty() { return "No exported changes" } md := &markdown.Writer{} + // write breaking changes - writeBreakingChanges(r.BreakingChanges, md) + md.WriteHeader("Breaking Changes") + for _, item := range getBreakingChanges(r.BreakingChanges) { + md.WriteLine(item) + } + // write additional changes - writeNewContent(r.AdditiveChanges, md) + md.WriteHeader("New Content") + for _, item := range getNewContents(r.AdditiveChanges) { + md.WriteLine(item) + } - writeSummaries(r.BreakingChanges, r.AdditiveChanges, md) + md.EmptyLine() + summaries := getSummaries(r.BreakingChanges, r.AdditiveChanges) + md.WriteLine(summaries) return md.String() } -func writeSummaries(breaking *report.BreakingChanges, additive *delta.Content, md *markdown.Writer) { +func getSummaries(breaking *report.BreakingChanges, additive *delta.Content) string { bc := 0 if breaking != nil { bc = breaking.Count() @@ -79,19 +97,21 @@ func writeSummaries(breaking *report.BreakingChanges, additive *delta.Content, m if additive != nil { ac = additive.Count() } - md.WriteLine("") - md.WriteLine(fmt.Sprintf("Total %d breaking change(s), %d additive change(s).", bc, ac)) + + return fmt.Sprintf("Total %d breaking change(s), %d additive change(s).", bc, ac) } -func writeNewContent(c *delta.Content, md *markdown.Writer) { +func getNewContents(c *delta.Content) []string { if c == nil || c.IsEmpty() { - return + return nil } - md.WriteHeader("New Content") + + var items []string + if len(c.Consts) > 0 { for k := range c.Consts { line := fmt.Sprintf("- New const `%s`", k) - md.WriteLine(line) + items = append(items, line) } } if len(c.Funcs) > 0 { @@ -108,13 +128,13 @@ func writeNewContent(c *delta.Content, md *markdown.Writer) { } } line := fmt.Sprintf("- New function `%s(%s) %s`", k, params, returns) - md.WriteLine(line) + items = append(items, line) } } if len(c.CompleteStructs) > 0 { for _, v := range c.CompleteStructs { line := fmt.Sprintf("- New struct `%s`", v) - md.WriteLine(line) + items = append(items, line) } } if len(c.Structs) > 0 { @@ -122,34 +142,46 @@ func writeNewContent(c *delta.Content, md *markdown.Writer) { for s, f := range modified { for _, af := range f.AnonymousFields { line := fmt.Sprintf("- New anonymous field `%s` in struct `%s`", af, s) - md.WriteLine(line) + items = append(items, line) } for f := range f.Fields { line := fmt.Sprintf("- New field `%s` in struct `%s`", f, s) - md.WriteLine(line) + items = append(items, line) } } } + + return items } -func writeBreakingChanges(b *report.BreakingChanges, md *markdown.Writer) { +func getBreakingChanges(b *report.BreakingChanges) []string { if b == nil || b.IsEmpty() { - return + return nil } - md.WriteHeader("Breaking Changes") - writeSignatureChanges(b, md) - writeRemovedContent(b.Removed, md) + + var items []string + + // get signature changes + items = append(items, getSignatureChangeItems(b)...) + + // get removed content + items = append(items, getRemovedContent(b.Removed)...) + + return items } -func writeSignatureChanges(b *report.BreakingChanges, md *markdown.Writer) { +func getSignatureChangeItems(b *report.BreakingChanges) []string { if b.IsEmpty() { - return + return nil } + + var items []string + // write const changes if len(b.Consts) > 0 { for k, v := range b.Consts { line := fmt.Sprintf("- Const `%s` type has been changed from `%s` to `%s`", k, v.From, v.To) - md.WriteLine(line) + items = append(items, line) } // TODO -- sort? } @@ -158,11 +190,11 @@ func writeSignatureChanges(b *report.BreakingChanges, md *markdown.Writer) { for k, v := range b.Funcs { if v.Params != nil { line := fmt.Sprintf("- Function `%s` parameter(s) have been changed from `(%s)` to `(%s)`", k, v.Params.From, v.Params.To) - md.WriteLine(line) + items = append(items, line) } if v.Returns != nil { line := fmt.Sprintf("- Function `%s` return value(s) have been changed from `(%s)` to `(%s)`", k, v.Returns.From, v.Returns.To) - md.WriteLine(line) + items = append(items, line) } } } @@ -171,36 +203,40 @@ func writeSignatureChanges(b *report.BreakingChanges, md *markdown.Writer) { for k, v := range b.Structs { for f, d := range v.Fields { line := fmt.Sprintf("- Type of `%s.%s` has been changed from `%s` to `%s`", k, f, d.From, d.To) - md.WriteLine(line) + items = append(items, line) } } } // interfaces are skipped, which are identical to some of the functions + + return items } -func writeRemovedContent(removed *delta.Content, md *markdown.Writer) { +func getRemovedContent(removed *delta.Content) []string { if removed == nil { - return + return nil } + + var items []string // write constants if len(removed.Consts) > 0 { for k := range removed.Consts { line := fmt.Sprintf("- Const `%s` has been removed", k) - md.WriteLine(line) + items = append(items, line) } } // write functions if len(removed.Funcs) > 0 { for k := range removed.Funcs { line := fmt.Sprintf("- Function `%s` has been removed", k) - md.WriteLine(line) + items = append(items, line) } } // write complete struct removal if len(removed.CompleteStructs) > 0 { for _, v := range removed.CompleteStructs { line := fmt.Sprintf("- Struct `%s` has been removed", v) - md.WriteLine(line) + items = append(items, line) } } // write struct modification (some fields are removed) @@ -209,12 +245,14 @@ func writeRemovedContent(removed *delta.Content, md *markdown.Writer) { for s, f := range modified { for _, af := range f.AnonymousFields { line := fmt.Sprintf("- Field `%s` of struct `%s` has been removed", af, s) - md.WriteLine(line) + items = append(items, line) } for f := range f.Fields { line := fmt.Sprintf("- Field `%s` of struct `%s` has been removed", f, s) - md.WriteLine(line) + items = append(items, line) } } } + + return items } diff --git a/tools/generator/autorest/model/option.go b/tools/generator/autorest/model/option.go index eb80cd171ff1..2fad34262ebb 100644 --- a/tools/generator/autorest/model/option.go +++ b/tools/generator/autorest/model/option.go @@ -3,42 +3,140 @@ package model -import "fmt" +import ( + "fmt" + "regexp" + "strings" +) + +// OptionType describes the type of the option, possible values are 'Argument', 'Flag' or 'KeyValue' +type OptionType string + +const ( + // Argument ... + Argument OptionType = "Argument" + // Flag ... + Flag OptionType = "Flag" + // KeyValue ... + KeyValue OptionType = "KeyValue" +) // Option describes an option of a autorest command line type Option interface { + // Type returns the type of this option + Type() OptionType // Format returns the actual option in string Format() string } +// ArgumentOption is an option not starting with '--' or '-' +type ArgumentOption interface { + Option + Argument() string +} + +// FlagOption is an option that starts with '--' but do not have a value +type FlagOption interface { + Option + Flag() string +} + +// KeyValueOption is an option that starts with '--' and have a value +type KeyValueOption interface { + Option + Key() string + Value() string +} + +// NewOption returns an Option from a formatted string. We should hold this result using this function: input == result.Format() +func NewOption(input string) (Option, error) { + if strings.HasPrefix(input, "--") { + // this option is either a flag or a key value pair option + return parseFlagOrKeyValuePair(strings.TrimPrefix(input, "--")) + } + // this should be an argument + return argument{value: input}, nil +} + +var flagRegex = regexp.MustCompile(`^[a-zA-Z]`) + +func parseFlagOrKeyValuePair(input string) (Option, error) { + if !flagRegex.MatchString(input) { + return nil, fmt.Errorf("cannot parse flag '%s', a flag or option should only start with letters", input) + } + segments := strings.Split(input, "=") + if len(segments) <= 1 { + // this should be a flag + return flagOption{value: input}, nil + } + return keyValueOption{ + key: segments[0], + value: strings.Join(segments[1:], "="), + }, nil +} + type argument struct { value string } +func (f argument) Type() OptionType { + return Argument +} + // Format ... func (f argument) Format() string { return f.value } +func (f argument) Argument() string { + return f.value +} + +var _ ArgumentOption = (*argument)(nil) + type flagOption struct { value string } +func (f flagOption) Type() OptionType { + return Flag +} + // Format ... func (f flagOption) Format() string { return fmt.Sprintf("--%s", f.value) } +func (f flagOption) Flag() string { + return f.value +} + +var _ FlagOption = (*flagOption)(nil) + type keyValueOption struct { key string value string } +func (f keyValueOption) Type() OptionType { + return KeyValue +} + // Format ... func (f keyValueOption) Format() string { return fmt.Sprintf("--%s=%s", f.key, f.value) } +func (f keyValueOption) Key() string { + return f.key +} + +func (f keyValueOption) Value() string { + return f.value +} + +var _ KeyValueOption = (*keyValueOption)(nil) + // NewArgument returns a new argument option (without "--") func NewArgument(value string) Option { return argument{ diff --git a/tools/generator/autorest/model/option_test.go b/tools/generator/autorest/model/option_test.go new file mode 100644 index 000000000000..b9b26e912c49 --- /dev/null +++ b/tools/generator/autorest/model/option_test.go @@ -0,0 +1,38 @@ +package model_test + +import ( + "reflect" + "testing" + + "github.com/Azure/azure-sdk-for-go/tools/generator/autorest/model" +) + +func TestNewOption(t *testing.T) { + testcase := []struct { + input string + expected model.Option + }{ + { + input: "specification/compute/resource-manager/readme.md", + expected: model.NewArgument("specification/compute/resource-manager/readme.md"), + }, + { + input: "--multiapi", + expected: model.NewFlagOption("multiapi"), + }, + { + input: "--tag=package-2020-01-01", + expected: model.NewKeyValueOption("tag", "package-2020-01-01"), + }, + } + + for _, c := range testcase { + o, err := model.NewOption(c.input) + if err != nil { + t.Fatalf("unexpected error: %+v", err) + } + if !reflect.DeepEqual(o, c.expected) { + t.Fatalf("expecting %+v, but got %+v", c.expected, o) + } + } +} diff --git a/tools/generator/autorest/model/options.go b/tools/generator/autorest/model/options.go index 2db54858ca6c..836c85c22913 100644 --- a/tools/generator/autorest/model/options.go +++ b/tools/generator/autorest/model/options.go @@ -12,31 +12,56 @@ import ( // Options ... type Options interface { // Arguments returns the argument defined in this options - Arguments() []string + Arguments() []Option // CodeGeneratorVersion returns the code generator version defined in this options CodeGeneratorVersion() string } -type localOptions struct { +type generateOptions struct { AutorestArguments []string `json:"autorestArguments"` } +type localOptions struct { + arguments []Option + codeGenVersion string +} + // NewOptionsFrom returns a new instance of Options from the given io.Reader func NewOptionsFrom(reader io.Reader) (Options, error) { b, err := ioutil.ReadAll(reader) if err != nil { return nil, err } - var result localOptions - if err := json.Unmarshal(b, &result); err != nil { + var raw generateOptions + if err := json.Unmarshal(b, &raw); err != nil { return nil, err } - return &result, nil + var options []Option + var codeGenVersion string + for _, r := range raw.AutorestArguments { + o, err := NewOption(r) + if err != nil { + return nil, err + } + options = append(options, o) + if v, ok := o.(KeyValueOption); ok && v.Key() == "use" { + codeGenVersion = v.Value() + } + } + return &localOptions{ + arguments: options, + codeGenVersion: codeGenVersion, + }, nil } -// Argument ... -func (o localOptions) Arguments() []string { - return o.AutorestArguments +// Arguments ... +func (o localOptions) Arguments() []Option { + return o.arguments +} + +// CodeGeneratorVersion ... +func (o localOptions) CodeGeneratorVersion() string { + return o.codeGenVersion } // String ... @@ -44,8 +69,3 @@ func (o localOptions) String() string { b, _ := json.MarshalIndent(o, "", " ") return string(b) } - -// CodeGeneratorVersion ... -func (o localOptions) CodeGeneratorVersion() string { - return "" -} diff --git a/tools/generator/autorest/model/options_test.go b/tools/generator/autorest/model/options_test.go new file mode 100644 index 000000000000..bd6dbadca0eb --- /dev/null +++ b/tools/generator/autorest/model/options_test.go @@ -0,0 +1,51 @@ +package model_test + +import ( + "reflect" + "strings" + "testing" + + "github.com/Azure/azure-sdk-for-go/tools/generator/autorest/model" +) + +func TestNewOptionsFrom(t *testing.T) { + testdata := []struct { + input string + expected []model.Option + }{ + { + input: `{ + "autorestArguments": [ + "--use=@microsoft.azure/autorest.go@2.1.178", + "--go", + "--verbose", + "--go-sdk-folder=.", + "--multiapi", + "--use-onever", + "--version=V2", + "--go.license-header=MICROSOFT_MIT_NO_VERSION" + ] +}`, + expected: []model.Option{ + model.NewKeyValueOption("use", "@microsoft.azure/autorest.go@2.1.178"), + model.NewFlagOption("go"), + model.NewFlagOption("verbose"), + model.NewKeyValueOption("go-sdk-folder", "."), + model.NewFlagOption("multiapi"), + model.NewFlagOption("use-onever"), + model.NewKeyValueOption("version", "V2"), + model.NewKeyValueOption("go.license-header", "MICROSOFT_MIT_NO_VERSION"), + }, + }, + } + + for _, c := range testdata { + options, err := model.NewOptionsFrom(strings.NewReader(c.input)) + if err != nil { + t.Fatalf("unexpected error: %+v", err) + } + if !reflect.DeepEqual(options.Arguments(), c.expected) { + t.Fatalf("expected %+v, but got %+v", c.expected, options.Arguments()) + } + } +} diff --git a/tools/generator/cmd/clean.go b/tools/generator/cmd/clean.go index 1ab6be66c3ef..be9d80dd26f0 100644 --- a/tools/generator/cmd/clean.go +++ b/tools/generator/cmd/clean.go @@ -5,8 +5,10 @@ package cmd import ( "fmt" + "io/ioutil" "log" "os" + "path/filepath" "github.com/Azure/azure-sdk-for-go/tools/generator/autorest" ) @@ -32,11 +34,30 @@ func (ctx *cleanUpContext) clean() (readmePackageOutputMap, error) { return nil, fmt.Errorf("cannot remove package '%s': %+v", p.outputFolder, err) } removedPackages.add(readme, p) + + // recursively remove all its parent if this directory is empty after the deletion + if err := removeEmptyParents(filepath.Dir(p.outputFolder)); err != nil { + return nil, err + } } } return removedPackages, nil } +func removeEmptyParents(parent string) error { + fi, err := ioutil.ReadDir(parent) + if err != nil { + return err + } + if len(fi) == 0 { + if err := os.RemoveAll(parent); err != nil { + return err + } + return removeEmptyParents(filepath.Dir(parent)) + } + return nil +} + func summaryReadmePackageOutputMap(root string) (readmePackageOutputMap, error) { // first we list all the go sdk track 1 packages m, err := autorest.CollectGenerationMetadata(root) @@ -45,7 +66,7 @@ func summaryReadmePackageOutputMap(root string) (readmePackageOutputMap, error) } result := readmePackageOutputMap{} for pkg, metadata := range m { - result.add(metadata.Readme, packageOutput{ + result.add(metadata.RelativeReadme(), packageOutput{ tag: metadata.Tag, outputFolder: pkg, }) diff --git a/tools/generator/cmd/generation.go b/tools/generator/cmd/generation.go index 645dc2dc2968..6fe3efdbca8d 100644 --- a/tools/generator/cmd/generation.go +++ b/tools/generator/cmd/generation.go @@ -6,24 +6,20 @@ package cmd import ( "bufio" "fmt" + "github.com/Azure/azure-sdk-for-go/tools/generator/autorest/model" "io" "os" "github.com/Azure/azure-sdk-for-go/tools/generator/autorest" - "github.com/Azure/azure-sdk-for-go/tools/generator/autorest/model" ) type autorestContext struct { - absReadme string - metadataOutput string - options model.Options + generator *autorest.Generator } func (c autorestContext) generate() error { - g := autorest.NewGeneratorFromOptions(c.options).WithReadme(c.absReadme).WithMetadataOutput(c.metadataOutput) - - stdout, _ := g.StdoutPipe() - stderr, _ := g.StderrPipe() + stdout, _ := c.generator.StdoutPipe() + stderr, _ := c.generator.StderrPipe() defer stdout.Close() defer stderr.Close() outScanner := bufio.NewScanner(stdout) @@ -31,20 +27,24 @@ func (c autorestContext) generate() error { errScanner := bufio.NewScanner(stderr) errScanner.Split(bufio.ScanLines) - if err := g.Start(); err != nil { + if err := c.generator.Start(); err != nil { return err } go printWithPrefixTo(outScanner, os.Stdout, "[AUTOREST] ") go printWithPrefixTo(errScanner, os.Stderr, "[AUTOREST] ") - if err := g.Wait(); err != nil { + if err := c.generator.Wait(); err != nil { return err } return nil } +func (c autorestContext) autorestArguments() []model.Option { + return c.generator.Arguments() +} + func printWithPrefixTo(scanner *bufio.Scanner, writer io.Writer, prefix string) error { for scanner.Scan() { line := scanner.Text() diff --git a/tools/generator/cmd/metadata.go b/tools/generator/cmd/metadata.go index ab1de9aeddee..e9507d6a2724 100644 --- a/tools/generator/cmd/metadata.go +++ b/tools/generator/cmd/metadata.go @@ -4,44 +4,38 @@ package cmd import ( + "encoding/json" "fmt" "os" "path/filepath" "strings" + "github.com/Azure/azure-sdk-for-go/tools/apidiff/exports" "github.com/Azure/azure-sdk-for-go/tools/generator/autorest" "github.com/Azure/azure-sdk-for-go/tools/generator/autorest/model" + "github.com/Azure/azure-sdk-for-go/tools/generator/utils" "github.com/Azure/azure-sdk-for-go/tools/generator/validate" ) type changelogContext struct { - sdkRoot string - clnRoot string - specRoot string - commitHash string - codeGenVer string - readme string + sdkRoot string + readme string + removedPackages []packageOutput -} -func (ctx changelogContext) SDKRoot() string { - return ctx.sdkRoot -} + repoContent map[string]exports.Content -func (ctx changelogContext) SDKCloneRoot() string { - return ctx.clnRoot -} + commonMetadata autorest.GenerationMetadata -func (ctx changelogContext) SpecRoot() string { - return ctx.specRoot + autorestArguments []model.Option } -func (ctx changelogContext) SpecCommitHash() string { - return ctx.commitHash +func (ctx changelogContext) SDKRoot() string { + return ctx.sdkRoot } -func (ctx changelogContext) CodeGenVersion() string { - return ctx.codeGenVer +func (ctx changelogContext) RepoContent() map[string]exports.Content { + return ctx.repoContent } func (ctx changelogContext) process(metadataLocation string) ([]autorest.ChangelogResult, error) { @@ -56,17 +50,30 @@ func (ctx changelogContext) process(metadataLocation string) ([]autorest.Changel return nil, err } // generate the changelogs - p := autorest.NewChangelogProcessorFromContext(ctx).WithLocation(metadataLocation).WithReadme(ctx.readme) + p := autorest.NewChangelogProcessorFromContext(ctx) changelogResults, err := p.Process(metadataMap) if err != nil { return nil, err } for _, result := range changelogResults { // we need to write the changelog file to the corresponding package here - if err := writeChangelogFile(result); err != nil { + if err := WriteChangelogFile(result); err != nil { + return nil, err + } + // we need to write the generation metadata to the corresponding package here + metadata := ctx.commonMetadata + metadata.Tag = result.Tag + options := autorest.AdditionalOptionsToString(ctx.autorestArguments) + metadata.AdditionalProperties = autorest.GenerationMetadataAdditionalProperties{ + AdditionalOptions: strings.Join(options, " "), + } + metadata.AutorestCommand = fmt.Sprintf("autorest --use=%s --tag=%s --go-sdk-folder=/_/azure-sdk-for-go %s /_/azure-rest-api-specs/%s", + metadata.CodeGenVersion, result.Tag, strings.Join(options, " "), utils.NormalizePath(ctx.readme)) + if err := WriteGenerationMetadata(result.PackageFullPath, metadata); err != nil { return nil, err } } + // iterate over the removed packages, generate changelogs for them as well var removedResults []autorest.ChangelogResult for _, rp := range ctx.removedPackages { @@ -81,33 +88,63 @@ func (ctx changelogContext) process(metadataLocation string) ([]autorest.Changel removedResults = append(removedResults, *result) } changelogResults = append(changelogResults, removedResults...) - return changelogResults, nil + + // omit the packages not in services directory + var results []autorest.ChangelogResult + for _, result := range changelogResults { + if strings.HasPrefix(result.PackageName, "services/") { + results = append(results, result) + } + } + + return results, nil } func contains(array []autorest.ChangelogResult, item string) bool { for _, r := range array { - if r.PackagePath == item { + if utils.NormalizePath(r.PackageFullPath) == utils.NormalizePath(item) { return true } } return false } -func writeChangelogFile(result autorest.ChangelogResult) error { - fileContent := fmt.Sprintf(`%s +// WriteGenerationMetadata writes the metadata to _meta.json file +func WriteGenerationMetadata(path string, metadata autorest.GenerationMetadata) error { + b, err := json.MarshalIndent(metadata, "", " ") + if err != nil { + return fmt.Errorf("cannot marshal metadata: %+v", err) + } -%s`, result.GenerationMetadata.String(), result.Changelog.ToMarkdown()) - changelogFile, err := os.Create(filepath.Join(result.PackagePath, autorest.ChangelogFilename)) + metadataFile, err := os.Create(filepath.Join(path, autorest.MetadataFilename)) if err != nil { return err } - defer changelogFile.Close() - if _, err := changelogFile.WriteString(fileContent); err != nil { + defer metadataFile.Close() + + if _, err := metadataFile.Write(b); err != nil { return err } return nil } +// WriteChangelogFile writes the changelog to CHANGELOG.md +func WriteChangelogFile(result autorest.ChangelogResult) error { + changelogFile, err := os.Create(filepath.Join(result.PackageFullPath, autorest.ChangelogFilename)) + if err != nil { + return err + } + defer changelogFile.Close() + + if _, err := changelogFile.WriteString(`# Unreleased Content + +`); err != nil { + return err + } + + return result.Write(changelogFile) +} + func (ctx changelogContext) validateMetadata(metadataMap map[string]model.Metadata) error { builder := validationErrorBuilder{ readme: ctx.readme, @@ -117,7 +154,6 @@ func (ctx changelogContext) validateMetadata(metadataMap map[string]model.Metada SDKRoot: ctx.sdkRoot, Validators: []validate.MetadataValidateFunc{ validate.PreviewCheck, - // TODO -- we do have some exceptions (see file tools/pkgchk/exceptions.txt) that might need to be considered here validate.MgmtCheck, validate.NamespaceCheck, }, diff --git a/tools/generator/cmd/root.go b/tools/generator/cmd/root.go index 6d992467314d..6485f52fa324 100644 --- a/tools/generator/cmd/root.go +++ b/tools/generator/cmd/root.go @@ -13,11 +13,14 @@ import ( "strings" "time" + "github.com/Azure/azure-sdk-for-go/tools/apidiff/exports" + "github.com/Azure/azure-sdk-for-go/tools/generator/autorest" "github.com/Azure/azure-sdk-for-go/tools/generator/autorest/model" "github.com/Azure/azure-sdk-for-go/tools/generator/pipeline" "github.com/Azure/azure-sdk-for-go/tools/generator/utils" "github.com/Azure/azure-sdk-for-go/tools/internal/ioext" "github.com/Azure/azure-sdk-for-go/tools/pkgchk/track1" + "github.com/Azure/azure-sdk-for-go/version" "github.com/spf13/cobra" ) @@ -69,17 +72,18 @@ func execute(inputPath, outputPath string, flags Flags) error { if err != nil { return err } - log.Printf("Backuping azure-sdk-for-go to temp directory...") - backupRoot, err := backupSDKRepository(cwd) - if err != nil { - return err - } - defer eraseBackup(backupRoot) - log.Printf("Finished backuping to '%s'", backupRoot) + + // we no longer need to back up the repo + //log.Printf("Backuping azure-sdk-for-go to temp directory...") + //backupRoot, err := backupSDKRepository(cwd) + //if err != nil { + // return err + //} + //defer eraseBackup(backupRoot) + //log.Printf("Finished backuping to '%s'", backupRoot) ctx := generateContext{ sdkRoot: utils.NormalizePath(cwd), - clnRoot: backupRoot, specRoot: input.SpecFolder, commitHash: input.HeadSha, optionPath: flags.OptionPath, @@ -121,6 +125,8 @@ type generateContext struct { specRoot string commitHash string optionPath string + + repoContent map[string]exports.Content } // TODO -- support dry run @@ -128,7 +134,11 @@ func (ctx generateContext) generate(input *pipeline.GenerateInput) (*pipeline.Ge if input.DryRun { return nil, fmt.Errorf("dry run not supported yet") } - log.Printf("Reading options from file '%s'...", ctx.optionPath) + + log.Printf("Reading packages in azure-sdk-for-go...") + if err := ctx.readRepoContent(); err != nil { + return nil, err + } // now we summary all the metadata in sdk log.Printf("Cleaning up all the packages related with the following readme files: [%s]", strings.Join(input.RelatedReadmeMdFiles, ", ")) @@ -146,6 +156,7 @@ func (ctx generateContext) generate(input *pipeline.GenerateInput) (*pipeline.Ge } log.Printf("The following %d package(s) have been cleaned up: [%s]", len(removedPackagePaths), strings.Join(removedPackagePaths, ", ")) + log.Printf("Reading options from file '%s'...", ctx.optionPath) optionFile, err := os.Open(ctx.optionPath) if err != nil { return nil, err @@ -163,11 +174,10 @@ func (ctx generateContext) generate(input *pipeline.GenerateInput) (*pipeline.Ge for _, readme := range input.RelatedReadmeMdFiles { log.Printf("Processing readme '%s'...", readme) absReadme := filepath.Join(input.SpecFolder, readme) + metadataOutput := filepath.Dir(absReadme) // generate code g := autorestContext{ - absReadme: absReadme, - metadataOutput: filepath.Dir(absReadme), - options: options, + generator: autorest.NewGeneratorFromOptions(options).WithReadme(absReadme).WithMetadataOutput(metadataOutput), } if err := g.generate(); err != nil { errorBuilder.add(fmt.Errorf("cannot generate readme '%s': %+v", readme, err)) @@ -175,15 +185,19 @@ func (ctx generateContext) generate(input *pipeline.GenerateInput) (*pipeline.Ge } m := changelogContext{ sdkRoot: ctx.sdkRoot, - clnRoot: ctx.clnRoot, - specRoot: ctx.specRoot, - commitHash: ctx.commitHash, - codeGenVer: options.CodeGeneratorVersion(), readme: readme, removedPackages: removedPackages[readme], + commonMetadata: autorest.GenerationMetadata{ + CommitHash: ctx.commitHash, + Readme: autorest.NormalizedSpecRoot + utils.NormalizePath(readme), + CodeGenVersion: options.CodeGeneratorVersion(), + RepositoryURL: "https://github.com/Azure/azure-rest-api-specs.git", + }, + repoContent: ctx.repoContent, + autorestArguments: g.autorestArguments(), } log.Printf("Processing metadata generated in readme '%s'...", readme) - packages, err := m.process(g.metadataOutput) + packages, err := m.process(metadataOutput) if err != nil { errorBuilder.add(fmt.Errorf("cannot process metadata for readme '%s': %+v", readme, err)) continue @@ -196,12 +210,14 @@ func (ctx generateContext) generate(input *pipeline.GenerateInput) (*pipeline.Ge content := p.Changelog.ToCompactMarkdown() breaking := p.Changelog.HasBreakingChanges() set.add(pipeline.PackageResult{ + Version: version.Number, PackageName: getPackageIdentifier(p.PackageName), Path: []string{p.PackageName}, ReadmeMd: []string{readme}, Changelog: &pipeline.Changelog{ - Content: &content, - HasBreakingChange: &breaking, + Content: &content, + HasBreakingChange: &breaking, + BreakingChangeItems: p.Changelog.GetBreakingChangeItems(), }, }) } @@ -220,6 +236,32 @@ func (ctx generateContext) generate(input *pipeline.GenerateInput) (*pipeline.Ge }, errorBuilder.build() } +func (ctx *generateContext) readRepoContent() error { + ctx.repoContent = make(map[string]exports.Content) + pkgs, err := track1.List(filepath.Join(ctx.sdkRoot, "services")) + if err != nil { + return fmt.Errorf("failed to list track 1 packages: %+v", err) + } + + for _, pkg := range pkgs { + relativePath, err := filepath.Rel(ctx.sdkRoot, pkg.FullPath()) + if err != nil { + return err + } + relativePath = utils.NormalizePath(relativePath) + if _, ok := ctx.repoContent[relativePath]; ok { + return fmt.Errorf("duplicate package: %s", pkg.Path()) + } + exp, err := exports.Get(pkg.FullPath()) + if err != nil { + return err + } + ctx.repoContent[relativePath] = exp + } + + return nil +} + type generateErrorBuilder struct { errors []error } diff --git a/tools/generator/pipeline/model.go b/tools/generator/pipeline/model.go index bb2af1585af2..2f6d44da13eb 100644 --- a/tools/generator/pipeline/model.go +++ b/tools/generator/pipeline/model.go @@ -74,6 +74,7 @@ func (o GenerateOutput) WriteTo(writer io.Writer) (int64, error) { // PackageResult ... type PackageResult struct { + Version string `json:"version,omitempty"` PackageName string `json:"packageName,omitempty"` Path []string `json:"path,omitempty"` ReadmeMd []string `json:"readmeMd,omitempty"` @@ -84,8 +85,9 @@ type PackageResult struct { // Changelog ... type Changelog struct { - Content *string `json:"content,omitempty"` - HasBreakingChange *bool `json:"hasBreakingChange,omitempty"` + Content *string `json:"content,omitempty"` + HasBreakingChange *bool `json:"hasBreakingChange,omitempty"` + BreakingChangeItems []string `json:"breakingChangeItems,omitempty"` } // InstallInstructionScriptOutput ... diff --git a/tools/pkgchk/track1/packages.go b/tools/pkgchk/track1/packages.go index 8668ce85b441..7884299ee50e 100644 --- a/tools/pkgchk/track1/packages.go +++ b/tools/pkgchk/track1/packages.go @@ -84,10 +84,10 @@ func List(root string) ([]Package, error) { return err } if len(packages) < 1 { - return fmt.Errorf("did not find any packages which is unexpected") + return fmt.Errorf("did not find any packages in '%s' which is unexpected", path) } if len(packages) > 1 { - return fmt.Errorf("found more than one package which is unexpected") + return fmt.Errorf("found more than one package in '%s' which is unexpected", path) } pkgName := "" for _, pkg := range packages {