diff --git a/TODO.txt b/TODO.txt index 37c0331b5f..eeb62c8d71 100644 --- a/TODO.txt +++ b/TODO.txt @@ -20,6 +20,13 @@ - document behavior of file searching, config override - fix tamper-proofing - go through all todos +- Un-skip relevant tests in generate_test.go and generate_unix_test.go, when + - the behavior of --path and --exclude-path is updated to match what's on main +- Debug the skipped test with proto file ref in generate_test.go +- Use syserror when possible (especially in buf gen related code). +- Find the right place (right way to pass a logger) to print a warning when a buf.gen.yaml with + non-empty managed mode but without `enabled: true`. The key is to decide where to pass the logger. +- Fix the issue where reading a buf.lock with empty digest errors. Run `buf mod update` to reproduce. NOTE: We are not allowing cross-workspace finding for include_package_files=true diff --git a/make/buf/all.mk b/make/buf/all.mk index a36004efe1..3694fe3adc 100644 --- a/make/buf/all.mk +++ b/make/buf/all.mk @@ -61,6 +61,7 @@ testbufnew: installbuf ./private/buf/bufcurl/... \ ./private/buf/buffetch/... \ ./private/buf/bufformat/... \ + ./private/buf/bufgen/... \ ./private/buf/bufprint/... \ ./private/buf/bufworkspace/... \ ./private/buf/cmd/buf/command/alpha/package/... \ @@ -74,6 +75,7 @@ testbufnew: installbuf ./private/buf/cmd/buf/command/build/... \ ./private/buf/cmd/buf/command/breaking/... \ ./private/buf/cmd/buf/command/convert/... \ + ./private/buf/cmd/buf/command/generate/... \ ./private/buf/cmd/buf/command/lint/... \ ./private/buf/cmd/buf/command/lsfiles/... \ ./private/buf/cmd/buf/command/mod/... \ diff --git a/private/buf/bufctl/controller.go b/private/buf/bufctl/controller.go index 92e0d3a30e..b18300d4f2 100644 --- a/private/buf/bufctl/controller.go +++ b/private/buf/bufctl/controller.go @@ -86,6 +86,11 @@ type Controller interface { input string, options ...FunctionOption, ) (bufimage.Image, error) + GetImageForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, + options ...FunctionOption, + ) (bufimage.Image, error) GetImageForWorkspace( ctx context.Context, workspace bufworkspace.Workspace, @@ -275,6 +280,18 @@ func (c *controller) GetImage( return c.getImage(ctx, input, functionOptions) } +func (c *controller) GetImageForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, + options ...FunctionOption, +) (bufimage.Image, error) { + functionOptions := newFunctionOptions() + for _, option := range options { + option(functionOptions) + } + return c.getImageForInputConfig(ctx, inputConfig, functionOptions) +} + func (c *controller) GetImageForWorkspace( ctx context.Context, workspace bufworkspace.Workspace, @@ -630,6 +647,26 @@ func (c *controller) getImage( if err != nil { return nil, err } + return c.getImageForRef(ctx, ref, functionOptions) +} + +func (c *controller) getImageForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, + functionOptions *functionOptions, +) (bufimage.Image, error) { + ref, err := c.buffetchRefParser.GetRefForInputConfig(ctx, inputConfig) + if err != nil { + return nil, err + } + return c.getImageForRef(ctx, ref, functionOptions) +} + +func (c *controller) getImageForRef( + ctx context.Context, + ref buffetch.Ref, + functionOptions *functionOptions, +) (bufimage.Image, error) { switch t := ref.(type) { case buffetch.ProtoFileRef: workspace, err := c.getWorkspaceForProtoFileRef(ctx, t, functionOptions) diff --git a/private/buf/buffetch/buffetch.go b/private/buf/buffetch/buffetch.go index 6a16f44671..7ed696581f 100644 --- a/private/buf/buffetch/buffetch.go +++ b/private/buf/buffetch/buffetch.go @@ -16,10 +16,12 @@ package buffetch import ( "context" + "fmt" "io" "net/http" "github.com/bufbuild/buf/private/buf/buffetch/internal" + "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/pkg/app" "github.com/bufbuild/buf/private/pkg/git" @@ -132,18 +134,33 @@ type ProtoFileRef interface { type MessageRefParser interface { // GetMessageRef gets the reference for the message file. GetMessageRef(ctx context.Context, value string) (MessageRef, error) + // GetMessageRefForInputConfig gets the reference for the message file. + GetMessageRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, + ) (MessageRef, error) } // SourceRefParser is a source ref parser for Buf. type SourceRefParser interface { // GetSourceRef gets the reference for the source file. GetSourceRef(ctx context.Context, value string) (SourceRef, error) + // GetSourceRef gets the reference for the source file. + GetSourceRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, + ) (SourceRef, error) } // DirRefParser is a dif ref parser for Buf. type DirRefParser interface { // GetDirRef gets the reference for the source file. GetDirRef(ctx context.Context, value string) (DirRef, error) + // GetDirRefForInputConfig gets the reference for the source file. + GetDirRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, + ) (DirRef, error) } // ModuleRefParser is a source ref parser for Buf. @@ -161,6 +178,11 @@ type SourceOrModuleRefParser interface { // GetSourceOrModuleRef gets the reference for the message file or source bucket. GetSourceOrModuleRef(ctx context.Context, value string) (SourceOrModuleRef, error) + // GetSourceOrModuleRefForInputConfig gets the reference for the message file or source bucket. + GetSourceOrModuleRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, + ) (SourceOrModuleRef, error) } // RefParser is a ref parser for Buf. @@ -170,8 +192,11 @@ type RefParser interface { DirRefParser SourceOrModuleRefParser + // TODO: should this be renamed to GetRefForString? // GetRef gets the reference for the message file, source bucket, or module. GetRef(ctx context.Context, value string) (Ref, error) + // GetRefForInputConfig gets the reference for the message file, source bucket, or module. + GetRefForInputConfig(ctx context.Context, inputConfig bufconfig.InputConfig) (Ref, error) } // NewRefParser returns a new RefParser. @@ -394,6 +419,42 @@ func NewWriter( ) } +// GetInputConfigForString returns the input config for the input string. +func GetInputConfigForString( + ctx context.Context, + refParser RefParser, + value string, +) (bufconfig.InputConfig, error) { + ref, err := refParser.GetRef(ctx, value) + if err != nil { + return nil, err + } + switch t := ref.(type) { + case MessageRef: + switch t.MessageEncoding() { + case MessageEncodingBinpb: + return bufconfig.NewBinaryImageInputConfig( + t.Path(), + t.internalSingleRef().CompressionType().String(), + ) + case MessageEncodingJSON: + return bufconfig.NewJSONImageInputConfig( + t.Path(), + t.internalSingleRef().CompressionType().String(), + ) + case MessageEncodingTxtpb: + return bufconfig.NewBinaryImageInputConfig( + t.Path(), + t.internalSingleRef().CompressionType().String(), + ) + default: + // TODO: handle refs with YAML type + return nil, fmt.Errorf("unknown encoding: %v", t.MessageEncoding()) + } + } + return internal.GetInputConfigForRef(ref.internalRef(), value) +} + type getBucketOptions struct { noSearch bool } diff --git a/private/buf/buffetch/internal/internal.go b/private/buf/buffetch/internal/internal.go index a4a4fc7aac..262f2672da 100644 --- a/private/buf/buffetch/internal/internal.go +++ b/private/buf/buffetch/internal/internal.go @@ -16,9 +16,12 @@ package internal import ( "context" + "fmt" "io" "net/http" + "strconv" + "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/pkg/app" "github.com/bufbuild/buf/private/pkg/git" @@ -83,6 +86,20 @@ type ArchiveType int // CompressionType is a compression type. type CompressionType int +// String implements fmt.Stringer +func (c CompressionType) String() string { + switch c { + case CompressionTypeNone: + return "none" + case CompressionTypeGzip: + return "gzip" + case CompressionTypeZstd: + return "zstd" + default: + return strconv.Itoa(int(c)) + } +} + // Ref is a reference. type Ref interface { ref() @@ -360,6 +377,12 @@ type RefParser interface { // // The options should be used to validate that you are getting one of the correct formats. GetParsedRef(ctx context.Context, value string, options ...GetParsedRefOption) (ParsedRef, error) + // GetParsedRefForInputConfig gets the ParsedRef for the input config. + // + // The returned ParsedRef will be either a ParsedSingleRef, ParsedArchiveRef, ParsedDirRef, ParsedGitRef, or ParsedModuleRef. + // + // The options should be used to validate that you are getting one of the correct formats. + GetParsedRefForInputConfig(ctx context.Context, inputConfig bufconfig.InputConfig, options ...GetParsedRefOption) (ParsedRef, error) } // NewRefParser returns a new RefParser. @@ -809,3 +832,62 @@ func WithPutFileNoFileCompression() PutFileOption { // GetModuleOption is a GetModule option. type GetModuleOption func(*getModuleOptions) + +// GetInputConfigForRef returns the input config for the ref. A string is also +// passed because if the ref is a git ref, it would only have a git.Name, instead +// of a git branch, a git ref and a git tag. Therefore the original string is passed. +func GetInputConfigForRef(ref Ref, value string) (bufconfig.InputConfig, error) { + _, options, err := getRawPathAndOptions(value) + if err != nil { + return nil, err + } + switch t := ref.(type) { + case ArchiveRef: + switch t.ArchiveType() { + case ArchiveTypeZip: + return bufconfig.NewZipArchiveInputConfig( + t.Path(), + t.SubDirPath(), + t.StripComponents(), + ) + case ArchiveTypeTar: + return bufconfig.NewTarballInputConfig( + t.Path(), + t.SubDirPath(), + t.CompressionType().String(), + t.StripComponents(), + ) + default: + return nil, fmt.Errorf("invalid archive type: %v", t.ArchiveType()) + } + case DirRef: + return bufconfig.NewDirectoryInputConfig( + t.Path(), + ) + case ModuleRef: + return bufconfig.NewModuleInputConfig( + t.ModuleRef().String(), + ) + case ProtoFileRef: + return bufconfig.NewProtoFileInputConfig( + t.Path(), + t.IncludePackageFiles(), + ) + case GitRef: + return bufconfig.NewGitRepoInputConfig( + t.Path(), + t.SubDirPath(), + options["branch"], + options["tag"], + options["ref"], + toPointer(t.Depth()), + t.RecurseSubmodules(), + ) + default: + return nil, fmt.Errorf("unexpected Ref of type %T", ref) + } +} + +func toPointer[T any](value T) *T { + return &value +} diff --git a/private/buf/buffetch/internal/ref_parser.go b/private/buf/buffetch/internal/ref_parser.go index e88c61a15a..b5d10efa0d 100644 --- a/private/buf/buffetch/internal/ref_parser.go +++ b/private/buf/buffetch/internal/ref_parser.go @@ -20,6 +20,7 @@ import ( "strconv" "strings" + "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/pkg/app" "github.com/bufbuild/buf/private/pkg/git" "github.com/bufbuild/buf/private/pkg/normalpath" @@ -65,68 +66,53 @@ func (a *refParser) GetParsedRef( return a.getParsedRef(ctx, value, getParsedRefOptions.allowedFormats) } +func (a *refParser) GetParsedRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, + options ...GetParsedRefOption, +) (ParsedRef, error) { + getParsedRefOptions := newGetParsedRefOptions() + for _, option := range options { + option(getParsedRefOptions) + } + return a.getParsedRefForInputConfig(ctx, inputConfig, getParsedRefOptions.allowedFormats) +} + func (a *refParser) getParsedRef( ctx context.Context, value string, allowedFormats map[string]struct{}, ) (ParsedRef, error) { - rawRef, err := a.getRawRef(value) + // path is never empty after returning from this function + path, options, err := getRawPathAndOptions(value) if err != nil { return nil, err } - singleFormatInfo, singleOK := a.singleFormatToInfo[rawRef.Format] - archiveFormatInfo, archiveOK := a.archiveFormatToInfo[rawRef.Format] - _, dirOK := a.dirFormatToInfo[rawRef.Format] - _, gitOK := a.gitFormatToInfo[rawRef.Format] - _, moduleOK := a.moduleFormatToInfo[rawRef.Format] - _, protoFileOK := a.protoFileFormatToInfo[rawRef.Format] - if !(singleOK || archiveOK || dirOK || gitOK || moduleOK || protoFileOK) { - return nil, NewFormatUnknownError(rawRef.Format) - } - if len(allowedFormats) > 0 { - if _, ok := allowedFormats[rawRef.Format]; !ok { - return nil, NewFormatNotAllowedError(rawRef.Format, allowedFormats) - } - } - if !singleOK && len(rawRef.UnrecognizedOptions) > 0 { - // Only single refs allow custom options. In every other case, this is an error. - // - // We verify unrecognized options match what is expected in getSingleRef. - keys := make([]string, 0, len(rawRef.UnrecognizedOptions)) - for key := range rawRef.UnrecognizedOptions { - keys = append(keys, key) - } - sort.Strings(keys) - return nil, NewOptionsInvalidKeysError(keys...) - } - if singleOK { - return getSingleRef(rawRef, singleFormatInfo.defaultCompressionType, singleFormatInfo.customOptionKeys) - } - if archiveOK { - return getArchiveRef(rawRef, archiveFormatInfo.archiveType, archiveFormatInfo.defaultCompressionType) - } - if protoFileOK { - return getProtoFileRef(rawRef) - } - if dirOK { - return getDirRef(rawRef) - } - if gitOK { - return getGitRef(rawRef) - } - if moduleOK { - return getModuleRef(rawRef) + rawRef, err := a.getRawRef(path, value, options) + if err != nil { + return nil, err } - return nil, NewFormatUnknownError(rawRef.Format) + return a.parseRawRef(rawRef, allowedFormats) } -// validated per rules on rawRef -func (a *refParser) getRawRef(value string) (*RawRef, error) { - // path is never empty after returning from this function - path, options, err := getRawPathAndOptions(value) +func (a *refParser) getParsedRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, + allowedFormats map[string]struct{}, +) (ParsedRef, error) { + rawRef, err := a.getRawRefForInputConfig(inputConfig) if err != nil { return nil, err } + return a.parseRawRef(rawRef, allowedFormats) +} + +func (a *refParser) getRawRef( + path string, + // Used to reference the input config in error messages. + displayName string, + options map[string]string, +) (*RawRef, error) { rawRef := &RawRef{ Path: path, UnrecognizedOptions: make(map[string]string), @@ -144,37 +130,23 @@ func (a *refParser) getRawRef(value string) (*RawRef, error) { } rawRef.Format = value case "compression": - switch value { - case "none": - rawRef.CompressionType = CompressionTypeNone - case "gzip": - rawRef.CompressionType = CompressionTypeGzip - case "zstd": - rawRef.CompressionType = CompressionTypeZstd - default: - return nil, NewCompressionUnknownError(value) + compressionType, err := parseCompressionType(value) + if err != nil { + return nil, err } + rawRef.CompressionType = compressionType case "branch": - if rawRef.GitBranch != "" || rawRef.GitTag != "" { - return nil, NewCannotSpecifyGitBranchAndTagError() - } rawRef.GitBranch = value case "tag": - if rawRef.GitBranch != "" || rawRef.GitTag != "" { - return nil, NewCannotSpecifyGitBranchAndTagError() - } rawRef.GitTag = value case "ref": rawRef.GitRef = value case "depth": - depth, err := strconv.ParseUint(value, 10, 32) + depth, err := parseGitDepth(value) if err != nil { - return nil, NewDepthParseError(value) - } - if depth == 0 { - return nil, NewDepthZeroError() + return nil, err } - rawRef.GitDepth = uint32(depth) + rawRef.GitDepth = depth case "recurse_submodules": // TODO: need to refactor to make sure this is not set for any non-git input // ie right now recurse_submodules=false will not error @@ -194,13 +166,11 @@ func (a *refParser) getRawRef(value string) (*RawRef, error) { } rawRef.ArchiveStripComponents = uint32(stripComponents) case "subdir": - subDirPath, err := normalpath.NormalizeAndValidate(value) + subDirPath, err := parseSubDirPath(value) if err != nil { return nil, err } - if subDirPath != "." { - rawRef.SubDirPath = subDirPath - } + rawRef.SubDirPath = subDirPath case "include_package_files": switch value { case "true": @@ -214,52 +184,222 @@ func (a *refParser) getRawRef(value string) (*RawRef, error) { rawRef.UnrecognizedOptions[key] = value } } + // This cannot be set ahead of time, it can only happen after all options are read. + if rawRef.Format == "git" && rawRef.GitDepth == 0 { + // Default to 1 + rawRef.GitDepth = 1 + if rawRef.GitRef != "" { + // Default to 50 when using ref + rawRef.GitDepth = 50 + } + } + if err := a.validateRawRef(displayName, rawRef); err != nil { + return nil, err + } + return rawRef, nil +} - if rawRef.Format == "" { - return nil, NewFormatCannotBeDeterminedError(value) +func (a *refParser) getRawRefForInputConfig( + inputConfig bufconfig.InputConfig, +) (*RawRef, error) { + rawRef := &RawRef{ + Path: inputConfig.Location(), + UnrecognizedOptions: make(map[string]string), + } + if a.rawRefProcessor != nil { + if err := a.rawRefProcessor(rawRef); err != nil { + return nil, err + } } + switch inputConfig.Type() { + case bufconfig.InputConfigTypeModule: + rawRef.Format = "mod" + case bufconfig.InputConfigTypeDirectory: + rawRef.Format = "dir" + case bufconfig.InputConfigTypeGitRepo: + rawRef.Format = "git" + case bufconfig.InputConfigTypeProtoFile: + rawRef.Format = "protofile" + case bufconfig.InputConfigTypeTarball: + rawRef.Format = "tar" + case bufconfig.InputConfigTypeZipArchive: + rawRef.Format = "zip" + case bufconfig.InputConfigTypeBinaryImage: + rawRef.Format = "binpb" + case bufconfig.InputConfigTypeJSONImage: + rawRef.Format = "jsonpb" + case bufconfig.InputConfigTypeTextImage: + rawRef.Format = "txtpb" + } + // This cannot be set ahead of time, it can only happen after all options are read. + if rawRef.GitDepth == 0 { + // Default to 1 + rawRef.GitDepth = 1 + if rawRef.GitRef != "" { + // Default to 50 when using ref + rawRef.GitDepth = 50 + } + } + var err error + rawRef.CompressionType, err = parseCompressionType(inputConfig.Compression()) + if err != nil { + return nil, err + } + rawRef.GitBranch = inputConfig.Branch() + rawRef.GitTag = inputConfig.Tag() + if err := a.validateRawRef(inputConfig.Location(), rawRef); err != nil { + return nil, err + } + rawRef.GitRef = inputConfig.Ref() + // TODO: might change rawRef.Depth into a pointer or use some other way to handle the case where 0 is specified + if inputConfig.Depth() != nil { + if *inputConfig.Depth() == 0 { + return nil, NewDepthZeroError() + } + } + rawRef.GitRecurseSubmodules = inputConfig.RecurseSubmodules() + rawRef.IncludePackageFiles = inputConfig.IncludePackageFiles() + rawRef.SubDirPath, err = parseSubDirPath(inputConfig.SubDir()) + if err != nil { + return nil, err + } + if err := a.validateRawRef(inputConfig.Location(), rawRef); err != nil { + return nil, err + } + return rawRef, nil +} + +func (a *refParser) parseRawRef( + rawRef *RawRef, + allowedFormats map[string]struct{}, +) (ParsedRef, error) { + singleFormatInfo, singleOK := a.singleFormatToInfo[rawRef.Format] + archiveFormatInfo, archiveOK := a.archiveFormatToInfo[rawRef.Format] + _, dirOK := a.dirFormatToInfo[rawRef.Format] + _, gitOK := a.gitFormatToInfo[rawRef.Format] + _, moduleOK := a.moduleFormatToInfo[rawRef.Format] + _, protoFileOK := a.protoFileFormatToInfo[rawRef.Format] + if !(singleOK || archiveOK || dirOK || gitOK || moduleOK || protoFileOK) { + return nil, NewFormatUnknownError(rawRef.Format) + } + if len(allowedFormats) > 0 { + if _, ok := allowedFormats[rawRef.Format]; !ok { + return nil, NewFormatNotAllowedError(rawRef.Format, allowedFormats) + } + } + if !singleOK && len(rawRef.UnrecognizedOptions) > 0 { + // Only single refs allow custom options. In every other case, this is an error. + // + // We verify unrecognized options match what is expected in getSingleRef. + keys := make([]string, 0, len(rawRef.UnrecognizedOptions)) + for key := range rawRef.UnrecognizedOptions { + keys = append(keys, key) + } + sort.Strings(keys) + return nil, NewOptionsInvalidKeysError(keys...) + } + if singleOK { + return getSingleRef(rawRef, singleFormatInfo.defaultCompressionType, singleFormatInfo.customOptionKeys) + } + if archiveOK { + return getArchiveRef(rawRef, archiveFormatInfo.archiveType, archiveFormatInfo.defaultCompressionType) + } + if protoFileOK { + return getProtoFileRef(rawRef) + } + if dirOK { + return getDirRef(rawRef) + } + if gitOK { + return getGitRef(rawRef) + } + if moduleOK { + return getModuleRef(rawRef) + } + return nil, NewFormatUnknownError(rawRef.Format) +} +func (a *refParser) validateRawRef( + displayName string, + rawRef *RawRef, +) error { + // probably move everything below this point to a new function, perhaps called validateRawRef + if rawRef.Format == "" { + return NewFormatCannotBeDeterminedError(displayName) + } _, gitOK := a.gitFormatToInfo[rawRef.Format] archiveFormatInfo, archiveOK := a.archiveFormatToInfo[rawRef.Format] _, singleOK := a.singleFormatToInfo[rawRef.Format] if gitOK { - if rawRef.GitRef != "" && rawRef.GitTag != "" { - return nil, NewCannotSpecifyTagWithRefError() + if rawRef.GitBranch != "" && rawRef.GitTag != "" { + return NewCannotSpecifyGitBranchAndTagError() } - if rawRef.GitDepth == 0 { - // Default to 1 - rawRef.GitDepth = 1 - if rawRef.GitRef != "" { - // Default to 50 when using ref - rawRef.GitDepth = 50 - } + if rawRef.GitRef != "" && rawRef.GitTag != "" { + return NewCannotSpecifyTagWithRefError() } } else { if rawRef.GitBranch != "" || rawRef.GitTag != "" || rawRef.GitRef != "" || rawRef.GitRecurseSubmodules || rawRef.GitDepth > 0 { - return nil, NewOptionsInvalidForFormatError(rawRef.Format, value) + return NewOptionsInvalidForFormatError(rawRef.Format, displayName) } } // not an archive format if !archiveOK { if rawRef.ArchiveStripComponents > 0 { - return nil, NewOptionsInvalidForFormatError(rawRef.Format, value) + return NewOptionsInvalidForFormatError(rawRef.Format, displayName) } } else { if archiveFormatInfo.archiveType == ArchiveTypeZip && rawRef.CompressionType != 0 { - return nil, NewCannotSpecifyCompressionForZipError() + return NewCannotSpecifyCompressionForZipError() } } if !singleOK && !archiveOK { if rawRef.CompressionType != 0 { - return nil, NewOptionsInvalidForFormatError(rawRef.Format, value) + return NewOptionsInvalidForFormatError(rawRef.Format, displayName) } } if !archiveOK && !gitOK { if rawRef.SubDirPath != "" { - return nil, NewOptionsInvalidForFormatError(rawRef.Format, value) + return NewOptionsInvalidForFormatError(rawRef.Format, displayName) } } - return rawRef, nil + return nil +} + +// TODO: these functions may not be necessary +// empty value is an error +func parseCompressionType(value string) (CompressionType, error) { + switch value { + case "none": + return CompressionTypeNone, nil + case "gzip": + return CompressionTypeGzip, nil + case "zstd": + return CompressionTypeZstd, nil + default: + return 0, NewCompressionUnknownError(value) + } +} + +func parseGitDepth(value string) (uint32, error) { + depth, err := strconv.ParseUint(value, 10, 32) + if err != nil { + return 0, NewDepthParseError(value) + } + if depth == 0 { + return 0, NewDepthZeroError() + } + return uint32(depth), nil +} + +func parseSubDirPath(value string) (string, error) { + subDirPath, err := normalpath.NormalizeAndValidate(value) + if err != nil { + return "", err + } + if subDirPath == "." { + return "", nil + } + return subDirPath, nil } // getRawPathAndOptions returns the raw path and options from the value provided, diff --git a/private/buf/buffetch/ref_parser.go b/private/buf/buffetch/ref_parser.go index 8babcfbf84..0c715f62e9 100644 --- a/private/buf/buffetch/ref_parser.go +++ b/private/buf/buffetch/ref_parser.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/bufbuild/buf/private/buf/buffetch/internal" + "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/pkg/app" "github.com/bufbuild/buf/private/pkg/syserror" @@ -207,7 +208,7 @@ func newSourceOrModuleRefParser(logger *zap.Logger) *refParser { func (a *refParser) GetRef( ctx context.Context, value string, -) (_ Ref, retErr error) { +) (Ref, error) { parsedRef, err := a.getParsedRef(ctx, value, allFormats) if err != nil { return nil, err @@ -234,10 +235,40 @@ func (a *refParser) GetRef( } } +func (a *refParser) GetRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, +) (Ref, error) { + parsedRef, err := a.getParsedRefForInputConfig(ctx, inputConfig, allFormats) + if err != nil { + return nil, err + } + switch t := parsedRef.(type) { + case internal.ParsedSingleRef: + messageEncoding, err := parseMessageEncoding(t.Format()) + if err != nil { + return nil, err + } + return newMessageRef(t, messageEncoding) + case internal.ParsedArchiveRef: + return newSourceRef(t), nil + case internal.ParsedDirRef: + return newSourceRef(t), nil + case internal.ParsedGitRef: + return newSourceRef(t), nil + case internal.ParsedModuleRef: + return newModuleRef(t), nil + case internal.ProtoFileRef: + return newProtoFileRef(t), nil + default: + return nil, fmt.Errorf("unknown ParsedRef type: %T", parsedRef) + } +} + func (a *refParser) GetSourceOrModuleRef( ctx context.Context, value string, -) (_ SourceOrModuleRef, retErr error) { +) (SourceOrModuleRef, error) { parsedRef, err := a.getParsedRef(ctx, value, sourceOrModuleFormats) if err != nil { return nil, err @@ -260,10 +291,36 @@ func (a *refParser) GetSourceOrModuleRef( } } +func (a *refParser) GetSourceOrModuleRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, +) (SourceOrModuleRef, error) { + parsedRef, err := a.getParsedRefForInputConfig(ctx, inputConfig, sourceOrModuleFormats) + if err != nil { + return nil, err + } + switch t := parsedRef.(type) { + case internal.ParsedSingleRef: + return nil, fmt.Errorf("invalid ParsedRef type for source or module: %T", parsedRef) + case internal.ParsedArchiveRef: + return newSourceRef(t), nil + case internal.ParsedDirRef: + return newSourceRef(t), nil + case internal.ParsedGitRef: + return newSourceRef(t), nil + case internal.ParsedModuleRef: + return newModuleRef(t), nil + case internal.ProtoFileRef: + return newProtoFileRef(t), nil + default: + return nil, fmt.Errorf("unknown ParsedRef type: %T", parsedRef) + } +} + func (a *refParser) GetMessageRef( ctx context.Context, value string, -) (_ MessageRef, retErr error) { +) (MessageRef, error) { parsedRef, err := a.getParsedRef(ctx, value, messageFormats) if err != nil { return nil, err @@ -279,10 +336,29 @@ func (a *refParser) GetMessageRef( return newMessageRef(parsedSingleRef, messageEncoding) } +func (a *refParser) GetMessageRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, +) (MessageRef, error) { + parsedRef, err := a.getParsedRefForInputConfig(ctx, inputConfig, messageFormats) + if err != nil { + return nil, err + } + parsedSingleRef, ok := parsedRef.(internal.ParsedSingleRef) + if !ok { + return nil, fmt.Errorf("invalid ParsedRef type for message: %T", parsedRef) + } + messageEncoding, err := parseMessageEncoding(parsedSingleRef.Format()) + if err != nil { + return nil, err + } + return newMessageRef(parsedSingleRef, messageEncoding) +} + func (a *refParser) GetSourceRef( ctx context.Context, value string, -) (_ SourceRef, retErr error) { +) (SourceRef, error) { parsedRef, err := a.getParsedRef(ctx, value, sourceFormats) if err != nil { return nil, err @@ -295,10 +371,26 @@ func (a *refParser) GetSourceRef( return newSourceRef(parsedBucketRef), nil } +func (a *refParser) GetSourceRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, +) (SourceRef, error) { + parsedRef, err := a.getParsedRefForInputConfig(ctx, inputConfig, sourceFormats) + if err != nil { + return nil, err + } + parsedBucketRef, ok := parsedRef.(internal.ParsedBucketRef) + if !ok { + // this should never happen + return nil, fmt.Errorf("invalid ParsedRef type for source: %T", parsedRef) + } + return newSourceRef(parsedBucketRef), nil +} + func (a *refParser) GetDirRef( ctx context.Context, value string, -) (_ DirRef, retErr error) { +) (DirRef, error) { parsedRef, err := a.getParsedRef(ctx, value, dirFormats) if err != nil { return nil, err @@ -311,10 +403,26 @@ func (a *refParser) GetDirRef( return newDirRef(parsedDirRef), nil } +func (a *refParser) GetDirRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, +) (DirRef, error) { + parsedRef, err := a.getParsedRefForInputConfig(ctx, inputConfig, dirFormats) + if err != nil { + return nil, err + } + parsedDirRef, ok := parsedRef.(internal.ParsedDirRef) + if !ok { + // this should never happen + return nil, fmt.Errorf("invalid ParsedRef type for source: %T", parsedRef) + } + return newDirRef(parsedDirRef), nil +} + func (a *refParser) GetModuleRef( ctx context.Context, value string, -) (_ ModuleRef, retErr error) { +) (ModuleRef, error) { parsedRef, err := a.getParsedRef(ctx, value, moduleFormats) if err != nil { return nil, err @@ -327,6 +435,23 @@ func (a *refParser) GetModuleRef( return newModuleRef(parsedModuleRef), nil } +func (a *refParser) GetModuleRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, +) (ModuleRef, error) { + parsedRef, err := a.getParsedRefForInputConfig(ctx, inputConfig, moduleFormats) + if err != nil { + return nil, err + } + parsedModuleRef, ok := parsedRef.(internal.ParsedModuleRef) + if !ok { + // this should never happen + return nil, fmt.Errorf("invalid ParsedRef type for source: %T", parsedRef) + } + return newModuleRef(parsedModuleRef), nil +} + +// TODO: rename to getParsedRefForString func (a *refParser) getParsedRef( ctx context.Context, value string, @@ -344,6 +469,23 @@ func (a *refParser) getParsedRef( return parsedRef, nil } +func (a *refParser) getParsedRefForInputConfig( + ctx context.Context, + inputConfig bufconfig.InputConfig, + allowedFormats []string, +) (internal.ParsedRef, error) { + parsedRef, err := a.fetchRefParser.GetParsedRefForInputConfig( + ctx, + inputConfig, + internal.WithAllowedFormats(allowedFormats...), + ) + if err != nil { + return nil, err + } + a.checkDeprecated(parsedRef) + return parsedRef, nil +} + func (a *refParser) checkDeprecated(parsedRef internal.ParsedRef) { format := parsedRef.Format() if replacementFormat, ok := deprecatedCompressionFormatToReplacementFormat[format]; ok { diff --git a/private/buf/buffetch/ref_parser_test.go b/private/buf/buffetch/ref_parser_test.go index 40b4ad562e..f829b8c45a 100644 --- a/private/buf/buffetch/ref_parser_test.go +++ b/private/buf/buffetch/ref_parser_test.go @@ -30,6 +30,7 @@ import ( "go.uber.org/zap" ) +// TODO: test ref from input config as well. func TestGetParsedRefSuccess(t *testing.T) { t.Parallel() // This allows us to test an os-agnostic root directory diff --git a/private/buf/bufgen/bufgen.go b/private/buf/bufgen/bufgen.go index e82d238134..39f68329e8 100644 --- a/private/buf/bufgen/bufgen.go +++ b/private/buf/bufgen/bufgen.go @@ -19,31 +19,18 @@ package bufgen import ( "context" - "encoding/json" "fmt" "strconv" + "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufimage" - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/bufbuild/buf/private/bufpkg/bufplugin/bufpluginref" - "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin" "github.com/bufbuild/buf/private/bufpkg/bufwasm" "github.com/bufbuild/buf/private/pkg/app" "github.com/bufbuild/buf/private/pkg/command" "github.com/bufbuild/buf/private/pkg/connectclient" - "github.com/bufbuild/buf/private/pkg/storage" "github.com/bufbuild/buf/private/pkg/storage/storageos" + "github.com/bufbuild/buf/private/pkg/tracing" "go.uber.org/zap" - "google.golang.org/protobuf/types/descriptorpb" -) - -const ( - // ExternalConfigFilePath is the default external configuration file path. - ExternalConfigFilePath = "buf.gen.yaml" - // V1Version is the string used to identify the v1 version of the generate template. - V1Version = "v1" - // V1Beta1Version is the string used to identify the v1beta1 version of the generate template. - V1Beta1Version = "v1beta1" ) const ( @@ -84,19 +71,6 @@ func (s Strategy) String() string { } } -// Provider is a provider. -type Provider interface { - // GetConfig gets the Config for the YAML data at ExternalConfigFilePath. - // - // If the data is of length 0, returns the default config. - GetConfig(ctx context.Context, readBucket storage.ReadBucket) (*Config, error) -} - -// NewProvider returns a new Provider. -func NewProvider(logger *zap.Logger) Provider { - return newProvider(logger) -} - // Generator generates Protobuf stubs based on configurations. type Generator interface { // Generate calls the generation logic. @@ -106,8 +80,8 @@ type Generator interface { Generate( ctx context.Context, container app.EnvStdioContainer, - config *Config, - image bufimage.Image, + config bufconfig.GenerateConfig, + images []bufimage.Image, options ...GenerateOption, ) error } @@ -115,13 +89,18 @@ type Generator interface { // NewGenerator returns a new Generator. func NewGenerator( logger *zap.Logger, + tracer tracing.Tracer, storageosProvider storageos.Provider, runner command.Runner, wasmPluginExecutor bufwasm.PluginExecutor, + // Pass a clientConfig instead of a CodeGenerationServiceClient because the + // plugins' remotes/registries is not known at this time, and remotes/registries + // may be different for different plugins. clientConfig *connectclient.Config, ) Generator { return newGenerator( logger, + tracer, storageosProvider, runner, wasmPluginExecutor, @@ -167,415 +146,3 @@ func GenerateWithWASMEnabled() GenerateOption { generateOptions.wasmEnabled = true } } - -// Config is a configuration. -type Config struct { - // Required - PluginConfigs []*PluginConfig - // Optional - ManagedConfig *ManagedConfig - // Optional - TypesConfig *TypesConfig -} - -// PluginConfig is a plugin configuration. -type PluginConfig struct { - // One of Plugin, Name or Remote is required - Plugin string - Name string - Remote string - // Optional, used with Plugin to pin a specific revision - Revision int - // Required - Out string - // Optional - Opt string - // Optional, exclusive with Remote - Path []string - // Required - Strategy Strategy - // Optional - ProtocPath string -} - -// PluginName returns this PluginConfig's plugin name. -// Only one of Plugin, Name or Remote will be set. -func (p *PluginConfig) PluginName() string { - if p == nil { - return "" - } - if p.Plugin != "" { - return p.Plugin - } - if p.Name != "" { - return p.Name - } - if p.Remote != "" { - return p.Remote - } - return "" -} - -// IsRemote returns true if the PluginConfig uses a remotely executed plugin. -func (p *PluginConfig) IsRemote() bool { - return p.GetRemoteHostname() != "" -} - -// GetRemoteHostname returns the hostname of the remote plugin. -func (p *PluginConfig) GetRemoteHostname() string { - if p == nil { - return "" - } - if identity, err := bufpluginref.PluginIdentityForString(p.Plugin); err == nil { - return identity.Remote() - } - if reference, err := bufpluginref.PluginReferenceForString(p.Plugin, 0); err == nil { - return reference.Remote() - } - if p.Remote == "" { - return "" - } - if remote, _, _, _, err := bufremoteplugin.ParsePluginVersionPath(p.Remote); err == nil { - return remote - } - return "" -} - -// ManagedConfig is the managed mode configuration. -type ManagedConfig struct { - CcEnableArenas *bool - JavaMultipleFiles *bool - JavaStringCheckUtf8 *bool - JavaPackagePrefixConfig *JavaPackagePrefixConfig - CsharpNameSpaceConfig *CsharpNameSpaceConfig - OptimizeForConfig *OptimizeForConfig - GoPackagePrefixConfig *GoPackagePrefixConfig - ObjcClassPrefixConfig *ObjcClassPrefixConfig - RubyPackageConfig *RubyPackageConfig - Override map[string]map[string]string -} - -// JavaPackagePrefixConfig is the java_package prefix configuration. -type JavaPackagePrefixConfig struct { - Default string - Except []bufmodule.ModuleFullName - // bufmodule.ModuleFullName -> java_package prefix. - Override map[bufmodule.ModuleFullName]string -} - -type OptimizeForConfig struct { - Default descriptorpb.FileOptions_OptimizeMode - Except []bufmodule.ModuleFullName - // bufmodule.ModuleFullName -> optimize_for. - Override map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode -} - -// GoPackagePrefixConfig is the go_package prefix configuration. -type GoPackagePrefixConfig struct { - Default string - Except []bufmodule.ModuleFullName - // bufmodule.ModuleFullName -> go_package prefix. - Override map[bufmodule.ModuleFullName]string -} - -// ObjcClassPrefixConfig is the objc_class_prefix configuration. -type ObjcClassPrefixConfig struct { - Default string - Except []bufmodule.ModuleFullName - // bufmodule.ModuleFullName -> objc_class_prefix. - Override map[bufmodule.ModuleFullName]string -} - -// RubyPackgeConfig is the ruby_package configuration. -type RubyPackageConfig struct { - Except []bufmodule.ModuleFullName - // bufmodule.ModuleFullName -> ruby_package. - Override map[bufmodule.ModuleFullName]string -} - -// CsharpNameSpaceConfig is the csharp_namespace configuration. -type CsharpNameSpaceConfig struct { - Except []bufmodule.ModuleFullName - // bufmodule.ModuleFullName -> csharp_namespace prefix. - Override map[bufmodule.ModuleFullName]string -} - -// TypesConfig is a types configuration -type TypesConfig struct { - Include []string -} - -// ReadConfig reads the configuration from the OS or an override, if any. -// -// Only use in CLI tools. -func ReadConfig( - ctx context.Context, - logger *zap.Logger, - provider Provider, - readBucket storage.ReadBucket, - options ...ReadConfigOption, -) (*Config, error) { - return readConfig( - ctx, - logger, - provider, - readBucket, - options..., - ) -} - -// ReadConfigOption is an option for ReadConfig. -type ReadConfigOption func(*readConfigOptions) - -// ReadConfigWithOverride sets the override. -// -// If override is set, this will first check if the override ends in .json or .yaml, if so, -// this reads the file at this path and uses it. Otherwise, this assumes this is configuration -// data in either JSON or YAML format, and unmarshals it. -// -// If no override is set, this reads ExternalConfigFilePath in the bucket. -func ReadConfigWithOverride(override string) ReadConfigOption { - return func(readConfigOptions *readConfigOptions) { - readConfigOptions.override = override - } -} - -// ConfigExists checks if a generation configuration file exists. -func ConfigExists(ctx context.Context, readBucket storage.ReadBucket) (bool, error) { - return storage.Exists(ctx, readBucket, ExternalConfigFilePath) -} - -// ExternalConfigV1 is an external configuration. -type ExternalConfigV1 struct { - Version string `json:"version,omitempty" yaml:"version,omitempty"` - Plugins []ExternalPluginConfigV1 `json:"plugins,omitempty" yaml:"plugins,omitempty"` - Managed ExternalManagedConfigV1 `json:"managed,omitempty" yaml:"managed,omitempty"` - Types ExternalTypesConfigV1 `json:"types,omitempty" yaml:"types,omitempty"` -} - -// ExternalPluginConfigV1 is an external plugin configuration. -type ExternalPluginConfigV1 struct { - Plugin string `json:"plugin,omitempty" yaml:"plugin,omitempty"` - Revision int `json:"revision,omitempty" yaml:"revision,omitempty"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Remote string `json:"remote,omitempty" yaml:"remote,omitempty"` - Out string `json:"out,omitempty" yaml:"out,omitempty"` - Opt interface{} `json:"opt,omitempty" yaml:"opt,omitempty"` - Path interface{} `json:"path,omitempty" yaml:"path,omitempty"` - ProtocPath string `json:"protoc_path,omitempty" yaml:"protoc_path,omitempty"` - Strategy string `json:"strategy,omitempty" yaml:"strategy,omitempty"` -} - -// ExternalManagedConfigV1 is an external managed mode configuration. -// -// Only use outside of this package for testing. -type ExternalManagedConfigV1 struct { - Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` - CcEnableArenas *bool `json:"cc_enable_arenas,omitempty" yaml:"cc_enable_arenas,omitempty"` - JavaMultipleFiles *bool `json:"java_multiple_files,omitempty" yaml:"java_multiple_files,omitempty"` - JavaStringCheckUtf8 *bool `json:"java_string_check_utf8,omitempty" yaml:"java_string_check_utf8,omitempty"` - JavaPackagePrefix ExternalJavaPackagePrefixConfigV1 `json:"java_package_prefix,omitempty" yaml:"java_package_prefix,omitempty"` - CsharpNamespace ExternalCsharpNamespaceConfigV1 `json:"csharp_namespace,omitempty" yaml:"csharp_namespace,omitempty"` - OptimizeFor ExternalOptimizeForConfigV1 `json:"optimize_for,omitempty" yaml:"optimize_for,omitempty"` - GoPackagePrefix ExternalGoPackagePrefixConfigV1 `json:"go_package_prefix,omitempty" yaml:"go_package_prefix,omitempty"` - ObjcClassPrefix ExternalObjcClassPrefixConfigV1 `json:"objc_class_prefix,omitempty" yaml:"objc_class_prefix,omitempty"` - RubyPackage ExternalRubyPackageConfigV1 `json:"ruby_package,omitempty" yaml:"ruby_package,omitempty"` - Override map[string]map[string]string `json:"override,omitempty" yaml:"override,omitempty"` -} - -// IsEmpty returns true if the config is empty, excluding the 'Enabled' setting. -func (e ExternalManagedConfigV1) IsEmpty() bool { - return e.CcEnableArenas == nil && - e.JavaMultipleFiles == nil && - e.JavaStringCheckUtf8 == nil && - e.JavaPackagePrefix.IsEmpty() && - e.CsharpNamespace.IsEmpty() && - e.CsharpNamespace.IsEmpty() && - e.OptimizeFor.IsEmpty() && - e.GoPackagePrefix.IsEmpty() && - e.ObjcClassPrefix.IsEmpty() && - e.RubyPackage.IsEmpty() && - len(e.Override) == 0 -} - -// ExternalJavaPackagePrefixConfigV1 is the external java_package prefix configuration. -type ExternalJavaPackagePrefixConfigV1 struct { - Default string `json:"default,omitempty" yaml:"default,omitempty"` - Except []string `json:"except,omitempty" yaml:"except,omitempty"` - Override map[string]string `json:"override,omitempty" yaml:"override,omitempty"` -} - -// IsEmpty returns true if the config is empty. -func (e ExternalJavaPackagePrefixConfigV1) IsEmpty() bool { - return e.Default == "" && - len(e.Except) == 0 && - len(e.Override) == 0 -} - -// UnmarshalYAML satisfies the yaml.Unmarshaler interface. This is done to maintain backward compatibility -// of accepting a plain string value for java_package_prefix. -func (e *ExternalJavaPackagePrefixConfigV1) UnmarshalYAML(unmarshal func(interface{}) error) error { - return e.unmarshalWith(unmarshal) -} - -// UnmarshalJSON satisfies the json.Unmarshaler interface. This is done to maintain backward compatibility -// of accepting a plain string value for java_package_prefix. -func (e *ExternalJavaPackagePrefixConfigV1) UnmarshalJSON(data []byte) error { - unmarshal := func(v interface{}) error { - return json.Unmarshal(data, v) - } - - return e.unmarshalWith(unmarshal) -} - -// unmarshalWith is used to unmarshal into json/yaml. See https://abhinavg.net/posts/flexible-yaml for details. -func (e *ExternalJavaPackagePrefixConfigV1) unmarshalWith(unmarshal func(interface{}) error) error { - var prefix string - if err := unmarshal(&prefix); err == nil { - e.Default = prefix - return nil - } - - type rawExternalJavaPackagePrefixConfigV1 ExternalJavaPackagePrefixConfigV1 - if err := unmarshal((*rawExternalJavaPackagePrefixConfigV1)(e)); err != nil { - return err - } - - return nil -} - -// ExternalOptimizeForConfigV1 is the external optimize_for configuration. -type ExternalOptimizeForConfigV1 struct { - Default string `json:"default,omitempty" yaml:"default,omitempty"` - Except []string `json:"except,omitempty" yaml:"except,omitempty"` - Override map[string]string `json:"override,omitempty" yaml:"override,omitempty"` -} - -// IsEmpty returns true if the config is empty -func (e ExternalOptimizeForConfigV1) IsEmpty() bool { - return e.Default == "" && - len(e.Except) == 0 && - len(e.Override) == 0 -} - -// UnmarshalYAML satisfies the yaml.Unmarshaler interface. This is done to maintain backward compatibility -// of accepting a plain string value for optimize_for. -func (e *ExternalOptimizeForConfigV1) UnmarshalYAML(unmarshal func(interface{}) error) error { - return e.unmarshalWith(unmarshal) -} - -// UnmarshalJSON satisfies the json.Unmarshaler interface. This is done to maintain backward compatibility -// of accepting a plain string value for optimize_for. -func (e *ExternalOptimizeForConfigV1) UnmarshalJSON(data []byte) error { - unmarshal := func(v interface{}) error { - return json.Unmarshal(data, v) - } - - return e.unmarshalWith(unmarshal) -} - -// unmarshalWith is used to unmarshal into json/yaml. See https://abhinavg.net/posts/flexible-yaml for details. -func (e *ExternalOptimizeForConfigV1) unmarshalWith(unmarshal func(interface{}) error) error { - var optimizeFor string - if err := unmarshal(&optimizeFor); err == nil { - e.Default = optimizeFor - return nil - } - - type rawExternalOptimizeForConfigV1 ExternalOptimizeForConfigV1 - if err := unmarshal((*rawExternalOptimizeForConfigV1)(e)); err != nil { - return err - } - - return nil -} - -// ExternalGoPackagePrefixConfigV1 is the external go_package prefix configuration. -type ExternalGoPackagePrefixConfigV1 struct { - Default string `json:"default,omitempty" yaml:"default,omitempty"` - Except []string `json:"except,omitempty" yaml:"except,omitempty"` - Override map[string]string `json:"override,omitempty" yaml:"override,omitempty"` -} - -// IsEmpty returns true if the config is empty. -func (e ExternalGoPackagePrefixConfigV1) IsEmpty() bool { - return e.Default == "" && - len(e.Except) == 0 && - len(e.Override) == 0 -} - -// ExternalCsharpNamespaceConfigV1 is the external csharp_namespace configuration. -type ExternalCsharpNamespaceConfigV1 struct { - Except []string `json:"except,omitempty" yaml:"except,omitempty"` - Override map[string]string `json:"override,omitempty" yaml:"override,omitempty"` -} - -// IsEmpty returns true if the config is empty. -func (e ExternalCsharpNamespaceConfigV1) IsEmpty() bool { - return len(e.Except) == 0 && - len(e.Override) == 0 -} - -// ExternalRubyPackageConfigV1 is the external ruby_package configuration -type ExternalRubyPackageConfigV1 struct { - Except []string `json:"except,omitempty" yaml:"except,omitempty"` - Override map[string]string `json:"override,omitempty" yaml:"override,omitempty"` -} - -// IsEmpty returns true is the config is empty -func (e ExternalRubyPackageConfigV1) IsEmpty() bool { - return len(e.Except) == 0 && len(e.Override) == 0 -} - -// ExternalObjcClassPrefixConfigV1 is the external objc_class_prefix configuration. -type ExternalObjcClassPrefixConfigV1 struct { - Default string `json:"default,omitempty" yaml:"default,omitempty"` - Except []string `json:"except,omitempty" yaml:"except,omitempty"` - Override map[string]string `json:"override,omitempty" yaml:"override,omitempty"` -} - -func (e ExternalObjcClassPrefixConfigV1) IsEmpty() bool { - return e.Default == "" && - len(e.Except) == 0 && - len(e.Override) == 0 -} - -// ExternalConfigV1Beta1 is an external configuration. -type ExternalConfigV1Beta1 struct { - Version string `json:"version,omitempty" yaml:"version,omitempty"` - Managed bool `json:"managed,omitempty" yaml:"managed,omitempty"` - Plugins []ExternalPluginConfigV1Beta1 `json:"plugins,omitempty" yaml:"plugins,omitempty"` - Options ExternalOptionsConfigV1Beta1 `json:"options,omitempty" yaml:"options,omitempty"` -} - -// ExternalPluginConfigV1Beta1 is an external plugin configuration. -type ExternalPluginConfigV1Beta1 struct { - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Out string `json:"out,omitempty" yaml:"out,omitempty"` - Opt interface{} `json:"opt,omitempty" yaml:"opt,omitempty"` - Path string `json:"path,omitempty" yaml:"path,omitempty"` - Strategy string `json:"strategy,omitempty" yaml:"strategy,omitempty"` -} - -// ExternalOptionsConfigV1Beta1 is an external options configuration. -type ExternalOptionsConfigV1Beta1 struct { - CcEnableArenas *bool `json:"cc_enable_arenas,omitempty" yaml:"cc_enable_arenas,omitempty"` - JavaMultipleFiles *bool `json:"java_multiple_files,omitempty" yaml:"java_multiple_files,omitempty"` - OptimizeFor string `json:"optimize_for,omitempty" yaml:"optimize_for,omitempty"` -} - -// ExternalConfigVersion defines the subset of all config -// file versions that is used to determine the configuration version. -type ExternalConfigVersion struct { - Version string `json:"version,omitempty" yaml:"version,omitempty"` -} - -// ExternalTypesConfigV1 is an external types configuration. -type ExternalTypesConfigV1 struct { - Include []string `json:"include,omitempty" yaml:"include"` -} - -// IsEmpty returns true if e is empty. -func (e ExternalTypesConfigV1) IsEmpty() bool { - return len(e.Include) == 0 -} diff --git a/private/buf/bufgen/bufgen_test.go b/private/buf/bufgen/bufgen_test.go deleted file mode 100644 index a135aeb25e..0000000000 --- a/private/buf/bufgen/bufgen_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufgen - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPluginConfig_GetRemoteHostname(t *testing.T) { - t.Parallel() - assertPluginConfigRemoteHostname := func(config *PluginConfig, expected string) { - t.Helper() - assert.Equal(t, config.GetRemoteHostname(), expected) - } - assertPluginConfigRemoteHostname(&PluginConfig{Plugin: "buf.build/protocolbuffers/go:v1.28.1"}, "buf.build") - assertPluginConfigRemoteHostname(&PluginConfig{Plugin: "buf.build/protocolbuffers/go"}, "buf.build") - assertPluginConfigRemoteHostname(&PluginConfig{Remote: "buf.build/protocolbuffers/plugins/go:v1.28.1-1"}, "buf.build") - assertPluginConfigRemoteHostname(&PluginConfig{Remote: "buf.build/protocolbuffers/plugins/go"}, "buf.build") -} diff --git a/private/buf/bufgen/config.go b/private/buf/bufgen/config.go deleted file mode 100644 index 541ed442de..0000000000 --- a/private/buf/bufgen/config.go +++ /dev/null @@ -1,666 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufgen - -import ( - "context" - "errors" - "fmt" - "os" - "path/filepath" - - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/bufbuild/buf/private/bufpkg/bufplugin/bufpluginref" - "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin" - "github.com/bufbuild/buf/private/pkg/encoding" - "github.com/bufbuild/buf/private/pkg/normalpath" - "github.com/bufbuild/buf/private/pkg/storage" - "go.uber.org/zap" - "google.golang.org/protobuf/types/descriptorpb" -) - -func readConfig( - ctx context.Context, - logger *zap.Logger, - provider Provider, - readBucket storage.ReadBucket, - options ...ReadConfigOption, -) (*Config, error) { - readConfigOptions := newReadConfigOptions() - for _, option := range options { - option(readConfigOptions) - } - if override := readConfigOptions.override; override != "" { - switch filepath.Ext(override) { - case ".json": - return getConfigJSONFile(logger, override) - case ".yaml", ".yml": - return getConfigYAMLFile(logger, override) - default: - return getConfigJSONOrYAMLData(logger, override) - } - } - return provider.GetConfig(ctx, readBucket) -} - -func getConfigJSONFile(logger *zap.Logger, file string) (*Config, error) { - data, err := os.ReadFile(file) - if err != nil { - return nil, fmt.Errorf("could not read file %s: %v", file, err) - } - return getConfig( - logger, - encoding.UnmarshalJSONNonStrict, - encoding.UnmarshalJSONStrict, - data, - file, - ) -} - -func getConfigYAMLFile(logger *zap.Logger, file string) (*Config, error) { - data, err := os.ReadFile(file) - if err != nil { - return nil, fmt.Errorf("could not read file %s: %v", file, err) - } - return getConfig( - logger, - encoding.UnmarshalYAMLNonStrict, - encoding.UnmarshalYAMLStrict, - data, - file, - ) -} - -func getConfigJSONOrYAMLData(logger *zap.Logger, data string) (*Config, error) { - return getConfig( - logger, - encoding.UnmarshalJSONOrYAMLNonStrict, - encoding.UnmarshalJSONOrYAMLStrict, - []byte(data), - "Generate configuration data", - ) -} - -func getConfig( - logger *zap.Logger, - unmarshalNonStrict func([]byte, interface{}) error, - unmarshalStrict func([]byte, interface{}) error, - data []byte, - id string, -) (*Config, error) { - var externalConfigVersion ExternalConfigVersion - if err := unmarshalNonStrict(data, &externalConfigVersion); err != nil { - return nil, err - } - switch externalConfigVersion.Version { - case V1Beta1Version: - var externalConfigV1Beta1 ExternalConfigV1Beta1 - if err := unmarshalStrict(data, &externalConfigV1Beta1); err != nil { - return nil, err - } - if err := validateExternalConfigV1Beta1(externalConfigV1Beta1, id); err != nil { - return nil, err - } - return newConfigV1Beta1(externalConfigV1Beta1, id) - case V1Version: - var externalConfigV1 ExternalConfigV1 - if err := unmarshalStrict(data, &externalConfigV1); err != nil { - return nil, err - } - if err := validateExternalConfigV1(externalConfigV1, id); err != nil { - return nil, err - } - return newConfigV1(logger, externalConfigV1, id) - default: - return nil, fmt.Errorf(`%s has no version set. Please add "version: %s"`, id, V1Version) - } -} - -func newConfigV1(logger *zap.Logger, externalConfig ExternalConfigV1, id string) (*Config, error) { - managedConfig, err := newManagedConfigV1(logger, externalConfig.Managed) - if err != nil { - return nil, err - } - pluginConfigs := make([]*PluginConfig, 0, len(externalConfig.Plugins)) - for _, plugin := range externalConfig.Plugins { - strategy, err := ParseStrategy(plugin.Strategy) - if err != nil { - return nil, err - } - opt, err := encoding.InterfaceSliceOrStringToCommaSepString(plugin.Opt) - if err != nil { - return nil, err - } - path, err := encoding.InterfaceSliceOrStringToStringSlice(plugin.Path) - if err != nil { - return nil, err - } - pluginConfig := &PluginConfig{ - Plugin: plugin.Plugin, - Revision: plugin.Revision, - Name: plugin.Name, - Remote: plugin.Remote, - Out: plugin.Out, - Opt: opt, - Path: path, - ProtocPath: plugin.ProtocPath, - Strategy: strategy, - } - if pluginConfig.IsRemote() { - // Always use StrategyAll for remote plugins - pluginConfig.Strategy = StrategyAll - } - pluginConfigs = append(pluginConfigs, pluginConfig) - } - typesConfig := newTypesConfigV1(externalConfig.Types) - return &Config{ - PluginConfigs: pluginConfigs, - ManagedConfig: managedConfig, - TypesConfig: typesConfig, - }, nil -} - -func validateExternalConfigV1(externalConfig ExternalConfigV1, id string) error { - if len(externalConfig.Plugins) == 0 { - return fmt.Errorf("%s: no plugins set", id) - } - for _, plugin := range externalConfig.Plugins { - var numPluginIdentifiers int - var pluginIdentifier string - for _, possibleIdentifier := range []string{plugin.Plugin, plugin.Name, plugin.Remote} { - if possibleIdentifier != "" { - numPluginIdentifiers++ - // Doesn't matter if we reassign here - we only allow one to be set below - pluginIdentifier = possibleIdentifier - } - } - if numPluginIdentifiers == 0 { - return fmt.Errorf("%s: one of plugin, name or remote is required", id) - } - if numPluginIdentifiers > 1 { - return fmt.Errorf("%s: only one of plugin, name, or remote can be set", id) - } - if plugin.Out == "" { - return fmt.Errorf("%s: plugin %s out is required", id, pluginIdentifier) - } - switch { - case plugin.Plugin != "": - if bufpluginref.IsPluginReferenceOrIdentity(pluginIdentifier) { - // plugin.Plugin is a remote plugin - if err := checkPathAndStrategyUnset(id, plugin, pluginIdentifier); err != nil { - return err - } - } else { - // plugin.Plugin is a local plugin - verify it isn't using an alpha remote plugin path - if _, _, _, _, err := bufremoteplugin.ParsePluginVersionPath(pluginIdentifier); err == nil { - return fmt.Errorf("%s: invalid plugin reference: %s", id, pluginIdentifier) - } - } - case plugin.Remote != "": - // Remote generation alpha features have been deprecated, but we continue to detect - // the remote field to surface a better error message. - return fmt.Errorf("%s: the remote field no longer works as the remote generation alpha has been deprecated, see the migration guide to now-stable remote plugins: %s", - id, - "https://buf.build/docs/migration-guides/migrate-remote-generation-alpha/#migrate-to-remote-plugins", - ) - case plugin.Name != "": - // Check that the plugin name doesn't look like a plugin reference - if bufpluginref.IsPluginReferenceOrIdentity(pluginIdentifier) { - return fmt.Errorf("%s: invalid local plugin name: %s", id, pluginIdentifier) - } - // Check that the plugin name doesn't look like an alpha remote plugin - if _, _, _, _, err := bufremoteplugin.ParsePluginVersionPath(pluginIdentifier); err == nil { - return fmt.Errorf("%s: invalid plugin name %s, did you mean to use a remote plugin?", id, pluginIdentifier) - } - default: - // unreachable - validated above - return errors.New("one of plugin, name, or remote is required") - } - } - return nil -} - -func checkPathAndStrategyUnset(id string, plugin ExternalPluginConfigV1, pluginIdentifier string) error { - if plugin.Path != nil { - return fmt.Errorf("%s: remote plugin %s cannot specify a path", id, pluginIdentifier) - } - if plugin.Strategy != "" { - return fmt.Errorf("%s: remote plugin %s cannot specify a strategy", id, pluginIdentifier) - } - if plugin.ProtocPath != "" { - return fmt.Errorf("%s: remote plugin %s cannot specify a protoc path", id, pluginIdentifier) - } - return nil -} - -func newManagedConfigV1(logger *zap.Logger, externalManagedConfig ExternalManagedConfigV1) (*ManagedConfig, error) { - if !externalManagedConfig.Enabled { - if !externalManagedConfig.IsEmpty() && logger != nil { - logger.Sugar().Warn("managed mode options are set but are not enabled") - } - return nil, nil - } - javaPackagePrefixConfig, err := newJavaPackagePrefixConfigV1(externalManagedConfig.JavaPackagePrefix) - if err != nil { - return nil, err - } - csharpNamespaceConfig, err := newCsharpNamespaceConfigV1(externalManagedConfig.CsharpNamespace) - if err != nil { - return nil, err - } - optimizeForConfig, err := newOptimizeForConfigV1(externalManagedConfig.OptimizeFor) - if err != nil { - return nil, err - } - goPackagePrefixConfig, err := newGoPackagePrefixConfigV1(externalManagedConfig.GoPackagePrefix) - if err != nil { - return nil, err - } - objcClassPrefixConfig, err := newObjcClassPrefixConfigV1(externalManagedConfig.ObjcClassPrefix) - if err != nil { - return nil, err - } - rubyPackageConfig, err := newRubyPackageConfigV1(externalManagedConfig.RubyPackage) - if err != nil { - return nil, err - } - override := externalManagedConfig.Override - for overrideID, overrideValue := range override { - for importPath := range overrideValue { - normalizedImportPath, err := normalpath.NormalizeAndValidate(importPath) - if err != nil { - return nil, fmt.Errorf( - "failed to normalize import path: %s provided for override: %s", - importPath, - overrideID, - ) - } - if importPath != normalizedImportPath { - return nil, fmt.Errorf( - "override can only take normalized import paths, invalid import path: %s provided for override: %s", - importPath, - overrideID, - ) - } - } - } - return &ManagedConfig{ - CcEnableArenas: externalManagedConfig.CcEnableArenas, - JavaMultipleFiles: externalManagedConfig.JavaMultipleFiles, - JavaStringCheckUtf8: externalManagedConfig.JavaStringCheckUtf8, - JavaPackagePrefixConfig: javaPackagePrefixConfig, - CsharpNameSpaceConfig: csharpNamespaceConfig, - OptimizeForConfig: optimizeForConfig, - GoPackagePrefixConfig: goPackagePrefixConfig, - ObjcClassPrefixConfig: objcClassPrefixConfig, - RubyPackageConfig: rubyPackageConfig, - Override: override, - }, nil -} - -func newJavaPackagePrefixConfigV1(externalJavaPackagePrefixConfig ExternalJavaPackagePrefixConfigV1) (*JavaPackagePrefixConfig, error) { - if externalJavaPackagePrefixConfig.IsEmpty() { - return nil, nil - } - if externalJavaPackagePrefixConfig.Default == "" { - return nil, errors.New("java_package_prefix setting requires a default value") - } - seenModuleFullNames := make(map[string]struct{}, len(externalJavaPackagePrefixConfig.Except)) - except := make([]bufmodule.ModuleFullName, 0, len(externalJavaPackagePrefixConfig.Except)) - for _, moduleName := range externalJavaPackagePrefixConfig.Except { - moduleFullName, err := bufmodule.ParseModuleFullName(moduleName) - if err != nil { - return nil, fmt.Errorf("invalid java_package_prefix except: %w", err) - } - if _, ok := seenModuleFullNames[moduleFullName.String()]; ok { - return nil, fmt.Errorf("invalid java_package_prefix except: %q is defined multiple times", moduleFullName.String()) - } - seenModuleFullNames[moduleFullName.String()] = struct{}{} - except = append(except, moduleFullName) - } - override := make(map[bufmodule.ModuleFullName]string, len(externalJavaPackagePrefixConfig.Override)) - for moduleName, javaPackagePrefix := range externalJavaPackagePrefixConfig.Override { - moduleFullName, err := bufmodule.ParseModuleFullName(moduleName) - if err != nil { - return nil, fmt.Errorf("invalid java_package_prefix override key: %w", err) - } - if _, ok := seenModuleFullNames[moduleFullName.String()]; ok { - return nil, fmt.Errorf("invalid java_package_prefix override: %q is already defined as an except", moduleFullName.String()) - } - seenModuleFullNames[moduleFullName.String()] = struct{}{} - override[moduleFullName] = javaPackagePrefix - } - return &JavaPackagePrefixConfig{ - Default: externalJavaPackagePrefixConfig.Default, - Except: except, - Override: override, - }, nil -} - -func newOptimizeForConfigV1(externalOptimizeForConfigV1 ExternalOptimizeForConfigV1) (*OptimizeForConfig, error) { - if externalOptimizeForConfigV1.IsEmpty() { - return nil, nil - } - if externalOptimizeForConfigV1.Default == "" { - return nil, errors.New("optimize_for setting requires a default value") - } - value, ok := descriptorpb.FileOptions_OptimizeMode_value[externalOptimizeForConfigV1.Default] - if !ok { - return nil, fmt.Errorf( - "invalid optimize_for default value; expected one of %v", - enumMapToStringSlice(descriptorpb.FileOptions_OptimizeMode_value), - ) - } - defaultOptimizeFor := descriptorpb.FileOptions_OptimizeMode(value) - seenModuleFullNames := make(map[string]struct{}, len(externalOptimizeForConfigV1.Except)) - except := make([]bufmodule.ModuleFullName, 0, len(externalOptimizeForConfigV1.Except)) - for _, moduleName := range externalOptimizeForConfigV1.Except { - moduleFullName, err := bufmodule.ParseModuleFullName(moduleName) - if err != nil { - return nil, fmt.Errorf("invalid optimize_for except: %w", err) - } - if _, ok := seenModuleFullNames[moduleFullName.String()]; ok { - return nil, fmt.Errorf("invalid optimize_for except: %q is defined multiple times", moduleFullName.String()) - } - seenModuleFullNames[moduleFullName.String()] = struct{}{} - except = append(except, moduleFullName) - } - override := make(map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode, len(externalOptimizeForConfigV1.Override)) - for moduleName, optimizeFor := range externalOptimizeForConfigV1.Override { - moduleFullName, err := bufmodule.ParseModuleFullName(moduleName) - if err != nil { - return nil, fmt.Errorf("invalid optimize_for override key: %w", err) - } - value, ok := descriptorpb.FileOptions_OptimizeMode_value[optimizeFor] - if !ok { - return nil, fmt.Errorf( - "invalid optimize_for override value; expected one of %v", - enumMapToStringSlice(descriptorpb.FileOptions_OptimizeMode_value), - ) - } - if _, ok := seenModuleFullNames[moduleFullName.String()]; ok { - return nil, fmt.Errorf( - "invalid optimize_for override: %q is already defined as an except", - moduleFullName.String(), - ) - } - seenModuleFullNames[moduleFullName.String()] = struct{}{} - override[moduleFullName] = descriptorpb.FileOptions_OptimizeMode(value) - } - return &OptimizeForConfig{ - Default: defaultOptimizeFor, - Except: except, - Override: override, - }, nil -} - -func newGoPackagePrefixConfigV1(externalGoPackagePrefixConfig ExternalGoPackagePrefixConfigV1) (*GoPackagePrefixConfig, error) { - if externalGoPackagePrefixConfig.IsEmpty() { - return nil, nil - } - if externalGoPackagePrefixConfig.Default == "" { - return nil, errors.New("go_package_prefix setting requires a default value") - } - defaultGoPackagePrefix, err := normalpath.NormalizeAndValidate(externalGoPackagePrefixConfig.Default) - if err != nil { - return nil, fmt.Errorf("invalid go_package_prefix default: %w", err) - } - seenModuleFullNames := make(map[string]struct{}, len(externalGoPackagePrefixConfig.Except)) - except := make([]bufmodule.ModuleFullName, 0, len(externalGoPackagePrefixConfig.Except)) - for _, moduleName := range externalGoPackagePrefixConfig.Except { - moduleFullName, err := bufmodule.ParseModuleFullName(moduleName) - if err != nil { - return nil, fmt.Errorf("invalid go_package_prefix except: %w", err) - } - if _, ok := seenModuleFullNames[moduleFullName.String()]; ok { - return nil, fmt.Errorf("invalid go_package_prefix except: %q is defined multiple times", moduleFullName.String()) - } - seenModuleFullNames[moduleFullName.String()] = struct{}{} - except = append(except, moduleFullName) - } - override := make(map[bufmodule.ModuleFullName]string, len(externalGoPackagePrefixConfig.Override)) - for moduleName, goPackagePrefix := range externalGoPackagePrefixConfig.Override { - moduleFullName, err := bufmodule.ParseModuleFullName(moduleName) - if err != nil { - return nil, fmt.Errorf("invalid go_package_prefix override key: %w", err) - } - normalizedGoPackagePrefix, err := normalpath.NormalizeAndValidate(goPackagePrefix) - if err != nil { - return nil, fmt.Errorf("invalid go_package_prefix override value: %w", err) - } - if _, ok := seenModuleFullNames[moduleFullName.String()]; ok { - return nil, fmt.Errorf("invalid go_package_prefix override: %q is already defined as an except", moduleFullName.String()) - } - seenModuleFullNames[moduleFullName.String()] = struct{}{} - override[moduleFullName] = normalizedGoPackagePrefix - } - return &GoPackagePrefixConfig{ - Default: defaultGoPackagePrefix, - Except: except, - Override: override, - }, nil -} - -func newRubyPackageConfigV1( - externalRubyPackageConfig ExternalRubyPackageConfigV1, -) (*RubyPackageConfig, error) { - if externalRubyPackageConfig.IsEmpty() { - return nil, nil - } - seenModuleFullNames := make(map[string]struct{}, len(externalRubyPackageConfig.Except)) - except := make([]bufmodule.ModuleFullName, 0, len(externalRubyPackageConfig.Except)) - for _, moduleName := range externalRubyPackageConfig.Except { - moduleFullName, err := bufmodule.ParseModuleFullName(moduleName) - if err != nil { - return nil, fmt.Errorf("invalid ruby_package except: %w", err) - } - if _, ok := seenModuleFullNames[moduleFullName.String()]; ok { - return nil, fmt.Errorf("invalid ruby_package except: %q is defined multiple times", moduleFullName.String()) - } - seenModuleFullNames[moduleFullName.String()] = struct{}{} - except = append(except, moduleFullName) - } - override := make(map[bufmodule.ModuleFullName]string, len(externalRubyPackageConfig.Override)) - for moduleName, rubyPackage := range externalRubyPackageConfig.Override { - moduleFullName, err := bufmodule.ParseModuleFullName(moduleName) - if err != nil { - return nil, fmt.Errorf("invalid ruby_package override key: %w", err) - } - if _, ok := seenModuleFullNames[moduleFullName.String()]; ok { - return nil, fmt.Errorf("invalid ruby_package override: %q is already defined as an except", moduleFullName.String()) - } - seenModuleFullNames[moduleFullName.String()] = struct{}{} - override[moduleFullName] = rubyPackage - } - return &RubyPackageConfig{ - Except: except, - Override: override, - }, nil -} - -func newCsharpNamespaceConfigV1( - externalCsharpNamespaceConfig ExternalCsharpNamespaceConfigV1, -) (*CsharpNameSpaceConfig, error) { - if externalCsharpNamespaceConfig.IsEmpty() { - return nil, nil - } - seenModuleFullNames := make(map[string]struct{}, len(externalCsharpNamespaceConfig.Except)) - except := make([]bufmodule.ModuleFullName, 0, len(externalCsharpNamespaceConfig.Except)) - for _, moduleName := range externalCsharpNamespaceConfig.Except { - moduleFullName, err := bufmodule.ParseModuleFullName(moduleName) - if err != nil { - return nil, fmt.Errorf("invalid csharp_namespace except: %w", err) - } - if _, ok := seenModuleFullNames[moduleFullName.String()]; ok { - return nil, fmt.Errorf("invalid csharp_namespace except: %q is defined multiple times", moduleFullName.String()) - } - seenModuleFullNames[moduleFullName.String()] = struct{}{} - except = append(except, moduleFullName) - } - override := make(map[bufmodule.ModuleFullName]string, len(externalCsharpNamespaceConfig.Override)) - for moduleName, csharpNamespace := range externalCsharpNamespaceConfig.Override { - moduleFullName, err := bufmodule.ParseModuleFullName(moduleName) - if err != nil { - return nil, fmt.Errorf("invalid csharp_namespace override key: %w", err) - } - if _, ok := seenModuleFullNames[moduleFullName.String()]; ok { - return nil, fmt.Errorf("invalid csharp_namespace override: %q is already defined as an except", moduleFullName.String()) - } - seenModuleFullNames[moduleFullName.String()] = struct{}{} - override[moduleFullName] = csharpNamespace - } - return &CsharpNameSpaceConfig{ - Except: except, - Override: override, - }, nil -} - -func newObjcClassPrefixConfigV1(externalObjcClassPrefixConfig ExternalObjcClassPrefixConfigV1) (*ObjcClassPrefixConfig, error) { - if externalObjcClassPrefixConfig.IsEmpty() { - return nil, nil - } - // It's ok to have an empty default, which will have the same effect as previously enabling managed mode. - defaultObjcClassPrefix := externalObjcClassPrefixConfig.Default - seenModuleFullNames := make(map[string]struct{}, len(externalObjcClassPrefixConfig.Except)) - except := make([]bufmodule.ModuleFullName, 0, len(externalObjcClassPrefixConfig.Except)) - for _, moduleName := range externalObjcClassPrefixConfig.Except { - moduleFullName, err := bufmodule.ParseModuleFullName(moduleName) - if err != nil { - return nil, fmt.Errorf("invalid objc_class_prefix except: %w", err) - } - if _, ok := seenModuleFullNames[moduleFullName.String()]; ok { - return nil, fmt.Errorf("invalid objc_class_prefix except: %q is defined multiple times", moduleFullName.String()) - } - seenModuleFullNames[moduleFullName.String()] = struct{}{} - except = append(except, moduleFullName) - } - override := make(map[bufmodule.ModuleFullName]string, len(externalObjcClassPrefixConfig.Override)) - for moduleName, objcClassPrefix := range externalObjcClassPrefixConfig.Override { - moduleFullName, err := bufmodule.ParseModuleFullName(moduleName) - if err != nil { - return nil, fmt.Errorf("invalid objc_class_prefix override key: %w", err) - } - if _, ok := seenModuleFullNames[moduleFullName.String()]; ok { - return nil, fmt.Errorf("invalid objc_class_prefix override: %q is already defined as an except", moduleFullName.String()) - } - seenModuleFullNames[moduleFullName.String()] = struct{}{} - override[moduleFullName] = objcClassPrefix - } - return &ObjcClassPrefixConfig{ - Default: defaultObjcClassPrefix, - Except: except, - Override: override, - }, nil -} - -func newConfigV1Beta1(externalConfig ExternalConfigV1Beta1, id string) (*Config, error) { - managedConfig, err := newManagedConfigV1Beta1(externalConfig.Options, externalConfig.Managed) - if err != nil { - return nil, err - } - pluginConfigs := make([]*PluginConfig, 0, len(externalConfig.Plugins)) - for _, plugin := range externalConfig.Plugins { - strategy, err := ParseStrategy(plugin.Strategy) - if err != nil { - return nil, err - } - opt, err := encoding.InterfaceSliceOrStringToCommaSepString(plugin.Opt) - if err != nil { - return nil, err - } - pluginConfig := &PluginConfig{ - Name: plugin.Name, - Out: plugin.Out, - Opt: opt, - Strategy: strategy, - } - if plugin.Path != "" { - pluginConfig.Path = []string{plugin.Path} - } - pluginConfigs = append(pluginConfigs, pluginConfig) - } - return &Config{ - PluginConfigs: pluginConfigs, - ManagedConfig: managedConfig, - }, nil -} - -func validateExternalConfigV1Beta1(externalConfig ExternalConfigV1Beta1, id string) error { - if len(externalConfig.Plugins) == 0 { - return fmt.Errorf("%s: no plugins set", id) - } - for _, plugin := range externalConfig.Plugins { - if plugin.Name == "" { - return fmt.Errorf("%s: plugin name is required", id) - } - if plugin.Out == "" { - return fmt.Errorf("%s: plugin %s out is required", id, plugin.Name) - } - } - return nil -} - -func newManagedConfigV1Beta1(externalOptionsConfig ExternalOptionsConfigV1Beta1, enabled bool) (*ManagedConfig, error) { - if !enabled || externalOptionsConfig == (ExternalOptionsConfigV1Beta1{}) { - return nil, nil - } - var optimizeForConfig *OptimizeForConfig - if externalOptionsConfig.OptimizeFor != "" { - value, ok := descriptorpb.FileOptions_OptimizeMode_value[externalOptionsConfig.OptimizeFor] - if !ok { - return nil, fmt.Errorf( - "invalid optimize_for value; expected one of %v", - enumMapToStringSlice(descriptorpb.FileOptions_OptimizeMode_value), - ) - } - optimizeForConfig = &OptimizeForConfig{ - Default: descriptorpb.FileOptions_OptimizeMode(value), - Except: make([]bufmodule.ModuleFullName, 0), - Override: make(map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode), - } - } - return &ManagedConfig{ - CcEnableArenas: externalOptionsConfig.CcEnableArenas, - JavaMultipleFiles: externalOptionsConfig.JavaMultipleFiles, - OptimizeForConfig: optimizeForConfig, - }, nil -} - -// enumMapToStringSlice is a convenience function for mapping Protobuf enums -// into a slice of strings. -func enumMapToStringSlice(enums map[string]int32) []string { - slice := make([]string, 0, len(enums)) - for enum := range enums { - slice = append(slice, enum) - } - return slice -} - -type readConfigOptions struct { - override string -} - -func newReadConfigOptions() *readConfigOptions { - return &readConfigOptions{} -} - -func newTypesConfigV1(externalConfig ExternalTypesConfigV1) *TypesConfig { - if externalConfig.IsEmpty() { - return nil - } - return &TypesConfig{ - Include: externalConfig.Include, - } -} diff --git a/private/buf/bufgen/config_test.go b/private/buf/bufgen/config_test.go deleted file mode 100644 index 99eb7e0a78..0000000000 --- a/private/buf/bufgen/config_test.go +++ /dev/null @@ -1,746 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufgen - -import ( - "context" - "os" - "path/filepath" - "testing" - - "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimagemodify" - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/bufbuild/buf/private/pkg/storage" - "github.com/bufbuild/buf/private/pkg/storage/storagemem" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" - "google.golang.org/protobuf/types/descriptorpb" -) - -func TestReadConfigV1Beta1(t *testing.T) { - t.Parallel() - truth := true - successConfig := &Config{ - PluginConfigs: []*PluginConfig{ - { - Name: "go", - Out: "gen/go", - Opt: "plugins=connect", - Path: []string{"/path/to/foo"}, - Strategy: StrategyAll, - }, - }, - ManagedConfig: &ManagedConfig{ - CcEnableArenas: &truth, - JavaMultipleFiles: &truth, - JavaStringCheckUtf8: nil, - OptimizeForConfig: &OptimizeForConfig{ - Default: descriptorpb.FileOptions_CODE_SIZE, - Except: make([]bufmodule.ModuleFullName, 0), - Override: make(map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode), - }, - }, - } - successConfig2 := &Config{ - ManagedConfig: &ManagedConfig{ - OptimizeForConfig: &OptimizeForConfig{ - Default: descriptorpb.FileOptions_SPEED, - Except: make([]bufmodule.ModuleFullName, 0), - Override: make(map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode), - }, - }, - PluginConfigs: []*PluginConfig{ - { - Name: "go", - Out: "gen/go", - Opt: "plugins=connect,foo=bar", - Path: []string{"/path/to/foo"}, - Strategy: StrategyAll, - }, - }, - } - successConfig3 := &Config{ - ManagedConfig: &ManagedConfig{ - OptimizeForConfig: &OptimizeForConfig{ - Default: descriptorpb.FileOptions_LITE_RUNTIME, - Except: make([]bufmodule.ModuleFullName, 0), - Override: make(map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode), - }, - }, - PluginConfigs: []*PluginConfig{ - { - Name: "go", - Out: "gen/go", - Path: []string{"/path/to/foo"}, - Strategy: StrategyAll, - }, - }, - } - successConfig4 := &Config{ - ManagedConfig: &ManagedConfig{ - OptimizeForConfig: &OptimizeForConfig{ - Default: descriptorpb.FileOptions_LITE_RUNTIME, - Except: make([]bufmodule.ModuleFullName, 0), - Override: make(map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode), - }, - }, - PluginConfigs: []*PluginConfig{ - { - Name: "go", - Out: "gen/go", - Strategy: StrategyAll, - }, - }, - } - ctx := context.Background() - nopLogger := zap.NewNop() - provider := NewProvider(zap.NewNop()) - readBucket, err := storagemem.NewReadBucket(nil) - require.NoError(t, err) - - testReadConfigSuccess := func(t *testing.T, configPath string, expected *Config) { - t.Helper() - config, err := ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(configPath)) - require.NoError(t, err) - assert.Equal(t, expected, config) - data, err := os.ReadFile(configPath) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - assert.Equal(t, expected, config) - } - - testReadConfigSuccess(t, filepath.Join("testdata", "v1beta1", "gen_success1.yaml"), successConfig) - testReadConfigSuccess(t, filepath.Join("testdata", "v1beta1", "gen_success1.json"), successConfig) - testReadConfigSuccess(t, filepath.Join("testdata", "v1beta1", "gen_success2.yaml"), successConfig2) - testReadConfigSuccess(t, filepath.Join("testdata", "v1beta1", "gen_success2.json"), successConfig2) - testReadConfigSuccess(t, filepath.Join("testdata", "v1beta1", "gen_success3.yaml"), successConfig3) - testReadConfigSuccess(t, filepath.Join("testdata", "v1beta1", "gen_success3.json"), successConfig3) - testReadConfigSuccess(t, filepath.Join("testdata", "v1beta1", "gen_success3.yml"), successConfig3) - testReadConfigSuccess(t, filepath.Join("testdata", "v1beta1", "gen_success4_nopath.yaml"), successConfig4) - - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1beta1", "gen_error1.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1beta1", "gen_error2.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1beta1", "gen_error3.yaml")) -} - -func TestReadConfigV1(t *testing.T) { - t.Parallel() - truth := true - successConfig := &Config{ - PluginConfigs: []*PluginConfig{ - { - Name: "go", - Out: "gen/go", - Opt: "plugins=connect", - Path: []string{"/path/to/foo"}, - Strategy: StrategyAll, - }, - }, - ManagedConfig: &ManagedConfig{ - CcEnableArenas: &truth, - JavaMultipleFiles: &truth, - JavaStringCheckUtf8: &truth, - JavaPackagePrefixConfig: &JavaPackagePrefixConfig{ - Default: "org", - Except: make([]bufmodule.ModuleFullName, 0), - Override: make(map[bufmodule.ModuleFullName]string), - }, - OptimizeForConfig: &OptimizeForConfig{ - Default: descriptorpb.FileOptions_CODE_SIZE, - Except: make([]bufmodule.ModuleFullName, 0), - Override: make(map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode), - }, - Override: map[string]map[string]string{ - bufimagemodify.JavaPackageID: {"a.proto": "override"}, - }, - }, - TypesConfig: &TypesConfig{ - Include: []string{ - "buf.alpha.lint.v1.IDPaths", - }, - }, - } - successConfig2 := &Config{ - ManagedConfig: &ManagedConfig{ - OptimizeForConfig: &OptimizeForConfig{ - Default: descriptorpb.FileOptions_SPEED, - Except: make([]bufmodule.ModuleFullName, 0), - Override: make(map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode), - }, - }, - PluginConfigs: []*PluginConfig{ - { - Name: "go", - Out: "gen/go", - Opt: "plugins=connect,foo=bar", - Path: []string{"/path/to/foo"}, - Strategy: StrategyAll, - }, - }, - } - successConfig3 := &Config{ - ManagedConfig: &ManagedConfig{ - OptimizeForConfig: &OptimizeForConfig{ - Default: descriptorpb.FileOptions_LITE_RUNTIME, - Except: make([]bufmodule.ModuleFullName, 0), - Override: make(map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode), - }, - }, - PluginConfigs: []*PluginConfig{ - { - Name: "go", - Out: "gen/go", - Path: []string{"/path/to/foo"}, - Strategy: StrategyAll, - }, - }, - } - successConfig4 := &Config{ - PluginConfigs: []*PluginConfig{ - { - Plugin: "someremote.com/owner/myplugin:v1.1.0-1", - Out: "gen/go", - Strategy: StrategyAll, - }, - }, - } - successConfig5 := &Config{ - PluginConfigs: []*PluginConfig{ - { - Plugin: "someremote.com/owner/myplugin", - Out: "gen/go", - Strategy: StrategyAll, - }, - }, - } - moduleFullName, err := bufmodule.NewModuleFullName( - "someremote.com", - "owner", - "repo", - ) - require.NoError(t, err) - successConfig6 := &Config{ - ManagedConfig: &ManagedConfig{ - JavaPackagePrefixConfig: &JavaPackagePrefixConfig{ - Default: "org", - Except: []bufmodule.ModuleFullName{moduleFullName}, - Override: make(map[bufmodule.ModuleFullName]string), - }, - }, - PluginConfigs: []*PluginConfig{ - { - Plugin: "someremote.com/owner/myplugin", - Out: "gen/go", - Strategy: StrategyAll, - }, - }, - } - successConfig7 := &Config{ - PluginConfigs: []*PluginConfig{ - { - Name: "go", - Out: "gen/go", - Opt: "plugins=connect", - Path: []string{"/path/to/foo"}, - Strategy: StrategyAll, - }, - }, - ManagedConfig: nil, - } - moduleFullName1 := mustCreateModuleFullName( - t, - "someremote.com", - "owner", - "repo", - ) - moduleFullName2 := mustCreateModuleFullName( - t, - "someremote.com", - "owner", - "foo", - ) - moduleFullName3 := mustCreateModuleFullName( - t, - "someremote.com", - "owner", - "bar", - ) - moduleFullName4 := mustCreateModuleFullName( - t, - "someremote.com", - "owner", - "baz", - ) - successConfig8 := &Config{ - ManagedConfig: &ManagedConfig{ - CsharpNameSpaceConfig: &CsharpNameSpaceConfig{ - Except: []bufmodule.ModuleFullName{ - moduleFullName1, - }, - Override: map[bufmodule.ModuleFullName]string{ - moduleFullName2: "a", - moduleFullName3: "b", - moduleFullName4: "c", - }, - }, - ObjcClassPrefixConfig: &ObjcClassPrefixConfig{ - Default: "default", - Except: []bufmodule.ModuleFullName{ - moduleFullName1, - }, - Override: map[bufmodule.ModuleFullName]string{ - moduleFullName2: "a", - moduleFullName3: "b", - moduleFullName4: "c", - }, - }, - RubyPackageConfig: &RubyPackageConfig{ - Except: []bufmodule.ModuleFullName{ - moduleFullName1, - }, - Override: map[bufmodule.ModuleFullName]string{ - moduleFullName2: "x", - moduleFullName3: "y", - moduleFullName4: "z", - }, - }, - }, - PluginConfigs: []*PluginConfig{ - { - Plugin: "someremote.com/owner/myplugin", - Out: "gen/go", - Strategy: StrategyAll, - }, - }, - } - successConfig9 := &Config{ - ManagedConfig: &ManagedConfig{ - OptimizeForConfig: &OptimizeForConfig{ - Default: descriptorpb.FileOptions_CODE_SIZE, - Except: []bufmodule.ModuleFullName{ - mustCreateModuleFullName( - t, - "someremote.com", - "owner", - "repo", - ), - mustCreateModuleFullName( - t, - "someremote.com", - "owner", - "foo", - ), - }, - Override: map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode{ - mustCreateModuleFullName( - t, - "someremote.com", - "owner", - "bar", - ): descriptorpb.FileOptions_SPEED, - mustCreateModuleFullName( - t, - "someremote.com", - "owner", - "baz", - ): descriptorpb.FileOptions_LITE_RUNTIME, - }, - }, - }, - PluginConfigs: []*PluginConfig{ - { - Plugin: "someremote.com/owner/myplugin", - Out: "gen/go", - Strategy: StrategyAll, - }, - }, - } - - ctx := context.Background() - nopLogger := zap.NewNop() - provider := NewProvider(zap.NewNop()) - readBucket, err := storagemem.NewReadBucket(nil) - require.NoError(t, err) - config, err := ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success1.yaml"))) - require.NoError(t, err) - require.Equal(t, successConfig, config) - data, err := os.ReadFile(filepath.Join("testdata", "v1", "gen_success1.yaml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success1.json"))) - require.NoError(t, err) - require.Equal(t, successConfig, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success1.json")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig, config) - - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success2.yaml"))) - require.NoError(t, err) - require.Equal(t, successConfig2, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success2.yaml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig2, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success2.json"))) - require.NoError(t, err) - require.Equal(t, successConfig2, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success2.json")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig2, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success3.yaml"))) - require.NoError(t, err) - require.Equal(t, successConfig3, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success3.yaml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig3, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success3.json"))) - require.NoError(t, err) - require.Equal(t, successConfig3, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success3.json")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig3, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success3.yml"))) - require.NoError(t, err) - require.Equal(t, successConfig3, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success3.yml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig3, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success4.yaml"))) - require.NoError(t, err) - require.Equal(t, successConfig4, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success4.yaml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig4, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success4.json"))) - require.NoError(t, err) - require.Equal(t, successConfig4, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success4.json")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig4, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success4.yml"))) - require.NoError(t, err) - require.Equal(t, successConfig4, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success4.yml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig4, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success5.yaml"))) - require.NoError(t, err) - require.Equal(t, successConfig5, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success5.yaml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig5, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success5.json"))) - require.NoError(t, err) - require.Equal(t, successConfig5, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success5.json")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig5, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success5.yml"))) - require.NoError(t, err) - require.Equal(t, successConfig5, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success5.yml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig5, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success6.yaml"))) - require.NoError(t, err) - require.Equal(t, successConfig6, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success6.yaml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig6, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success6.json"))) - require.NoError(t, err) - require.Equal(t, successConfig6, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success6.json")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig6, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success6.yml"))) - require.NoError(t, err) - require.Equal(t, successConfig6, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success6.yml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig6, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success7.yaml"))) - require.NoError(t, err) - require.Equal(t, successConfig7, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success7.yaml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig7, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success7.json"))) - require.NoError(t, err) - require.Equal(t, successConfig7, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success7.json")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig7, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success7.yml"))) - require.NoError(t, err) - require.Equal(t, successConfig7, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success7.yml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig7, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success8.yaml"))) - require.NoError(t, err) - assertConfigsWithEqualCsharpnamespace(t, successConfig8, config) - assertConfigsWithEqualObjcPrefix(t, successConfig8, config) - assertConfigsWithEqualRubyPackage(t, successConfig8, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success8.yaml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - assertConfigsWithEqualCsharpnamespace(t, successConfig8, config) - assertConfigsWithEqualObjcPrefix(t, successConfig8, config) - assertConfigsWithEqualRubyPackage(t, successConfig8, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success8.json"))) - require.NoError(t, err) - assertConfigsWithEqualCsharpnamespace(t, successConfig8, config) - assertConfigsWithEqualObjcPrefix(t, successConfig8, config) - assertConfigsWithEqualRubyPackage(t, successConfig8, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success8.json")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - assertConfigsWithEqualCsharpnamespace(t, successConfig8, config) - assertConfigsWithEqualObjcPrefix(t, successConfig8, config) - assertConfigsWithEqualRubyPackage(t, successConfig8, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success8.yml"))) - require.NoError(t, err) - assertConfigsWithEqualCsharpnamespace(t, successConfig8, config) - assertConfigsWithEqualObjcPrefix(t, successConfig8, config) - assertConfigsWithEqualRubyPackage(t, successConfig8, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success8.yml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - assertConfigsWithEqualCsharpnamespace(t, successConfig8, config) - assertConfigsWithEqualObjcPrefix(t, successConfig8, config) - assertConfigsWithEqualRubyPackage(t, successConfig8, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success9.yaml"))) - require.NoError(t, err) - assertConfigsWithEqualOptimizeFor(t, successConfig9, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success9.yaml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - assertConfigsWithEqualOptimizeFor(t, successConfig9, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success9.json"))) - require.NoError(t, err) - assertConfigsWithEqualOptimizeFor(t, successConfig9, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success9.json")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - assertConfigsWithEqualOptimizeFor(t, successConfig9, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "gen_success9.yml"))) - require.NoError(t, err) - assertConfigsWithEqualOptimizeFor(t, successConfig9, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "gen_success9.yml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - assertConfigsWithEqualOptimizeFor(t, successConfig9, config) - - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error1.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error2.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error3.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error4.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error5.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error6.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error7.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error8.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error9.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error10.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error11.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error12.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error13.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error14.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error15.yaml")) - assertContainsReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "gen_error15.yaml"), "the remote field no longer works") - - successConfig = &Config{ - PluginConfigs: []*PluginConfig{ - { - Name: "go", - Out: "gen/go", - Opt: "plugins=connect", - Path: []string{"/path/to/foo"}, - Strategy: StrategyAll, - }, - }, - ManagedConfig: &ManagedConfig{ - GoPackagePrefixConfig: &GoPackagePrefixConfig{ - Default: "github.com/foo/bar/gen/go", - Except: make([]bufmodule.ModuleFullName, 0), - Override: make(map[bufmodule.ModuleFullName]string), - }, - }, - } - readBucket, err = storagemem.NewReadBucket(nil) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "go_gen_success1.yaml"))) - require.NoError(t, err) - require.Equal(t, successConfig, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "go_gen_success1.yaml")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig, config) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(filepath.Join("testdata", "v1", "go_gen_success1.json"))) - require.NoError(t, err) - require.Equal(t, successConfig, config) - data, err = os.ReadFile(filepath.Join("testdata", "v1", "go_gen_success1.json")) - require.NoError(t, err) - config, err = ReadConfig(ctx, nopLogger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.NoError(t, err) - require.Equal(t, successConfig, config) - - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "go_gen_error2.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "go_gen_error3.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "go_gen_error4.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "go_gen_error5.yaml")) - testReadConfigError(t, nopLogger, provider, readBucket, filepath.Join("testdata", "v1", "go_gen_error6.yaml")) -} - -func testReadConfigError(t *testing.T, logger *zap.Logger, provider Provider, readBucket storage.ReadBucket, testFilePath string) { - ctx := context.Background() - _, err := ReadConfig(ctx, logger, provider, readBucket, ReadConfigWithOverride(testFilePath)) - require.Error(t, err) - data, err := os.ReadFile(testFilePath) - require.NoError(t, err) - _, err = ReadConfig(ctx, logger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.Error(t, err) -} - -func assertContainsReadConfigError(t *testing.T, logger *zap.Logger, provider Provider, readBucket storage.ReadBucket, testFilePath string, message string) { - ctx := context.Background() - _, err := ReadConfig(ctx, logger, provider, readBucket, ReadConfigWithOverride(testFilePath)) - require.Error(t, err) - data, err := os.ReadFile(testFilePath) - require.NoError(t, err) - _, err = ReadConfig(ctx, logger, provider, readBucket, ReadConfigWithOverride(string(data))) - require.Error(t, err) - assert.Contains(t, err.Error(), message) -} - -func mustCreateModuleFullName( - t *testing.T, - remote string, - owner string, - repository string, -) bufmodule.ModuleFullName { - moduleFullName, err := bufmodule.NewModuleFullName(remote, owner, repository) - require.NoError(t, err) - return moduleFullName -} - -func assertConfigsWithEqualObjcPrefix(t *testing.T, successConfig *Config, config *Config) { - require.Equal(t, successConfig.PluginConfigs, config.PluginConfigs) - require.NotNil(t, successConfig.ManagedConfig) - require.NotNil(t, config.ManagedConfig) - require.NotNil(t, successConfig.ManagedConfig.ObjcClassPrefixConfig) - require.NotNil(t, config.ManagedConfig.ObjcClassPrefixConfig) - successObjcPrefixConfig := successConfig.ManagedConfig.ObjcClassPrefixConfig - objcPrefixConfig := config.ManagedConfig.ObjcClassPrefixConfig - require.Equal(t, successObjcPrefixConfig.Default, objcPrefixConfig.Default) - require.Equal(t, successObjcPrefixConfig.Except, objcPrefixConfig.Except) - assertEqualModuleFullNameKeyedMaps(t, successObjcPrefixConfig.Override, objcPrefixConfig.Override) -} - -func assertConfigsWithEqualCsharpnamespace(t *testing.T, successConfig *Config, config *Config) { - require.Equal(t, successConfig.PluginConfigs, config.PluginConfigs) - require.NotNil(t, successConfig.ManagedConfig) - require.NotNil(t, config.ManagedConfig) - require.NotNil(t, successConfig.ManagedConfig.CsharpNameSpaceConfig) - require.NotNil(t, config.ManagedConfig.CsharpNameSpaceConfig) - successCsharpConfig := successConfig.ManagedConfig.CsharpNameSpaceConfig - csharpConfig := config.ManagedConfig.CsharpNameSpaceConfig - require.Equal(t, successCsharpConfig.Except, csharpConfig.Except) - assertEqualModuleFullNameKeyedMaps(t, successCsharpConfig.Override, csharpConfig.Override) -} - -func assertConfigsWithEqualRubyPackage(t *testing.T, successConfig *Config, config *Config) { - require.Equal(t, successConfig.PluginConfigs, config.PluginConfigs) - require.NotNil(t, successConfig.ManagedConfig) - require.NotNil(t, config.ManagedConfig) - require.NotNil(t, successConfig.ManagedConfig.RubyPackageConfig) - require.NotNil(t, config.ManagedConfig.RubyPackageConfig) - successRubyConfig := successConfig.ManagedConfig.RubyPackageConfig - rubyConfig := config.ManagedConfig.RubyPackageConfig - require.Equal(t, successRubyConfig.Except, rubyConfig.Except) - assertEqualModuleFullNameKeyedMaps(t, successRubyConfig.Override, rubyConfig.Override) -} - -func assertConfigsWithEqualOptimizeFor(t *testing.T, successConfig *Config, config *Config) { - require.Equal(t, successConfig.PluginConfigs, config.PluginConfigs) - require.NotNil(t, successConfig.ManagedConfig) - require.NotNil(t, config.ManagedConfig) - successOptimizeForConfig := successConfig.ManagedConfig.OptimizeForConfig - require.NotNil(t, successOptimizeForConfig) - optimizeForConfig := config.ManagedConfig.OptimizeForConfig - require.NotNil(t, optimizeForConfig) - require.Equal(t, successOptimizeForConfig.Default, optimizeForConfig.Default) - require.Equal(t, successOptimizeForConfig.Except, optimizeForConfig.Except) - assertEqualModuleFullNameKeyedMaps(t, optimizeForConfig.Override, optimizeForConfig.Override) -} - -func assertEqualModuleFullNameKeyedMaps[V any](t *testing.T, m1 map[bufmodule.ModuleFullName]V, m2 map[bufmodule.ModuleFullName]V) { - require.Equal(t, len(m1), len(m2)) - keyedM1 := make(map[string]V, len(m1)) - keyedM2 := make(map[string]V, len(m2)) - for k, v := range m1 { - keyedM1[k.IdentityString()] = v - } - for k, v := range m2 { - keyedM2[k.IdentityString()] = v - } - require.Equal(t, keyedM1, keyedM2) -} diff --git a/private/buf/bufgen/features.go b/private/buf/bufgen/features.go index 4f6aa6dd04..cd2b434cac 100644 --- a/private/buf/bufgen/features.go +++ b/private/buf/bufgen/features.go @@ -19,6 +19,7 @@ import ( "sort" "strings" + "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufimage" "github.com/bufbuild/buf/private/pkg/app" "google.golang.org/protobuf/types/descriptorpb" @@ -60,7 +61,7 @@ func checkRequiredFeatures( container app.StderrContainer, required requiredFeatures, responses []*pluginpb.CodeGeneratorResponse, - configs []*PluginConfig, + configs []bufconfig.GeneratePluginConfig, ) { for responseIndex, response := range responses { if response == nil || response.GetError() != "" { @@ -81,7 +82,7 @@ func checkRequiredFeatures( if len(failed) > 0 { // TODO: plugin config to turn this into an error _, _ = fmt.Fprintf(container.Stderr(), "Warning: plugin %q does not support required features.\n", - configs[responseIndex].PluginName()) + configs[responseIndex].Name()) sort.Slice(failedFeatures, func(i, j int) bool { return failedFeatures[i].Number() < failedFeatures[j].Number() }) diff --git a/private/buf/bufgen/generator.go b/private/buf/bufgen/generator.go index a5938ee769..3bbd759706 100644 --- a/private/buf/bufgen/generator.go +++ b/private/buf/bufgen/generator.go @@ -21,22 +21,23 @@ import ( "path/filepath" connect "connectrpc.com/connect" + "github.com/bufbuild/buf/private/buf/bufpluginexec" + "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufimage" "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimagemodify" - "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/bufpkg/bufplugin" "github.com/bufbuild/buf/private/bufpkg/bufplugin/bufpluginref" - "github.com/bufbuild/buf/private/buf/bufpluginexec" "github.com/bufbuild/buf/private/bufpkg/bufwasm" "github.com/bufbuild/buf/private/gen/proto/connect/buf/alpha/registry/v1alpha1/registryv1alpha1connect" registryv1alpha1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1" "github.com/bufbuild/buf/private/pkg/app" - "github.com/bufbuild/buf/private/pkg/protoplugin" - "github.com/bufbuild/buf/private/pkg/protoplugin/protopluginos" "github.com/bufbuild/buf/private/pkg/command" "github.com/bufbuild/buf/private/pkg/connectclient" + "github.com/bufbuild/buf/private/pkg/protoplugin" + "github.com/bufbuild/buf/private/pkg/protoplugin/protopluginos" "github.com/bufbuild/buf/private/pkg/storage/storageos" "github.com/bufbuild/buf/private/pkg/thread" + "github.com/bufbuild/buf/private/pkg/tracing" "go.uber.org/multierr" "go.uber.org/zap" "google.golang.org/protobuf/types/pluginpb" @@ -44,6 +45,7 @@ import ( type generator struct { logger *zap.Logger + tracer tracing.Tracer storageosProvider storageos.Provider pluginexecGenerator bufpluginexec.Generator clientConfig *connectclient.Config @@ -51,6 +53,7 @@ type generator struct { func newGenerator( logger *zap.Logger, + tracer tracing.Tracer, storageosProvider storageos.Provider, runner command.Runner, wasmPluginExecutor bufwasm.PluginExecutor, @@ -58,8 +61,9 @@ func newGenerator( ) *generator { return &generator{ logger: logger, + tracer: tracer, storageosProvider: storageosProvider, - pluginexecGenerator: bufpluginexec.NewGenerator(logger, storageosProvider, runner, wasmPluginExecutor), + pluginexecGenerator: bufpluginexec.NewGenerator(logger, tracer, storageosProvider, runner, wasmPluginExecutor), clientConfig: clientConfig, } } @@ -84,47 +88,50 @@ func newGenerator( func (g *generator) Generate( ctx context.Context, container app.EnvStdioContainer, - config *Config, - image bufimage.Image, + config bufconfig.GenerateConfig, + images []bufimage.Image, options ...GenerateOption, ) error { generateOptions := newGenerateOptions() for _, option := range options { option(generateOptions) } - return g.generate( - ctx, - container, - config, - image, - generateOptions.baseOutDirPath, - generateOptions.includeImports, - generateOptions.includeWellKnownTypes, - generateOptions.wasmEnabled, - ) + for _, image := range images { + if err := bufimagemodify.Modify(ctx, image, config.GenerateManagedConfig()); err != nil { + return err + } + if err := g.generateCode( + ctx, + container, + image, + generateOptions.baseOutDirPath, + config.GeneratePluginConfigs(), + generateOptions.includeImports, + generateOptions.includeWellKnownTypes, + ); err != nil { + return err + } + } + return nil } -func (g *generator) generate( +func (g *generator) generateCode( ctx context.Context, container app.EnvStdioContainer, - config *Config, - image bufimage.Image, - baseOutDirPath string, - includeImports bool, - includeWellKnownTypes bool, - wasmEnabled bool, + inputImage bufimage.Image, + baseOutDir string, + pluginConfigs []bufconfig.GeneratePluginConfig, + alwaysIncludeImports bool, + alwaysIncludeWKT bool, ) error { - if err := modifyImage(ctx, g.logger, config, image); err != nil { - return err - } responses, err := g.execPlugins( ctx, container, - config, - image, - includeImports, - includeWellKnownTypes, - wasmEnabled, + pluginConfigs, + inputImage, + alwaysIncludeImports, + alwaysIncludeWKT, + false, // wasm enabled is false ) if err != nil { return err @@ -135,21 +142,21 @@ func (g *generator) generate( g.storageosProvider, protopluginos.ResponseWriterWithCreateOutDirIfNotExists(), ) - for i, pluginConfig := range config.PluginConfigs { - out := pluginConfig.Out - if baseOutDirPath != "" && baseOutDirPath != "." { - out = filepath.Join(baseOutDirPath, out) + for i, pluginConfig := range pluginConfigs { + out := pluginConfig.Out() + if baseOutDir != "" && baseOutDir != "." { + out = filepath.Join(baseOutDir, out) } response := responses[i] if response == nil { - return fmt.Errorf("failed to get plugin response for %s", pluginConfig.PluginName()) + return fmt.Errorf("failed to get plugin response for %s", pluginConfig.Name()) } if err := responseWriter.AddResponse( ctx, response, out, ); err != nil { - return fmt.Errorf("plugin %s: %v", pluginConfig.PluginName(), err) + return fmt.Errorf("plugin %s: %v", pluginConfig.Name(), err) } } if err := responseWriter.Close(); err != nil { @@ -161,22 +168,22 @@ func (g *generator) generate( func (g *generator) execPlugins( ctx context.Context, container app.EnvStdioContainer, - config *Config, + pluginConfigs []bufconfig.GeneratePluginConfig, image bufimage.Image, - includeImports bool, - includeWellKnownTypes bool, + alwaysIncludeImports bool, + alwaysIncludeWellKnownTypes bool, wasmEnabled bool, ) ([]*pluginpb.CodeGeneratorResponse, error) { imageProvider := newImageProvider(image) // Collect all of the plugin jobs so that they can be executed in parallel. - jobs := make([]func(context.Context) error, 0, len(config.PluginConfigs)) - responses := make([]*pluginpb.CodeGeneratorResponse, len(config.PluginConfigs)) + jobs := make([]func(context.Context) error, 0, len(pluginConfigs)) + responses := make([]*pluginpb.CodeGeneratorResponse, len(pluginConfigs)) requiredFeatures := computeRequiredFeatures(image) - remotePluginConfigTable := make(map[string][]*remotePluginExecArgs, len(config.PluginConfigs)) - for i, pluginConfig := range config.PluginConfigs { + remotePluginConfigTable := make(map[string][]*remotePluginExecArgs, len(pluginConfigs)) + for i, pluginConfig := range pluginConfigs { index := i currentPluginConfig := pluginConfig - remote := currentPluginConfig.GetRemoteHostname() + remote := currentPluginConfig.RemoteHost() if remote != "" { remotePluginConfigTable[remote] = append( remotePluginConfigTable[remote], @@ -192,8 +199,9 @@ func (g *generator) execPlugins( container, imageProvider, currentPluginConfig, - includeImports, - includeWellKnownTypes, + // TODO: can the user override this to false on the command line? i.e. is `buf generate --include-imports=false` possible? + alwaysIncludeImports || currentPluginConfig.IncludeImports(), + alwaysIncludeWellKnownTypes || currentPluginConfig.IncludeWKT(), wasmEnabled, ) if err != nil { @@ -208,23 +216,16 @@ func (g *generator) execPlugins( for remote, indexedPluginConfigs := range remotePluginConfigTable { remote := remote indexedPluginConfigs := indexedPluginConfigs - v2Args := make([]*remotePluginExecArgs, 0, len(indexedPluginConfigs)) - for _, param := range indexedPluginConfigs { - if param.PluginConfig.Remote != "" { - return nil, fmt.Errorf("invalid plugin reference: %s", param.PluginConfig.Remote) - } - v2Args = append(v2Args, param) - } - if len(v2Args) > 0 { + if len(indexedPluginConfigs) > 0 { jobs = append(jobs, func(ctx context.Context) error { results, err := g.execRemotePluginsV2( ctx, container, image, remote, - v2Args, - includeImports, - includeWellKnownTypes, + indexedPluginConfigs, + alwaysIncludeImports, + alwaysIncludeWellKnownTypes, ) if err != nil { return err @@ -260,10 +261,10 @@ func (g *generator) execPlugins( } return nil, err } - if err := validateResponses(responses, config.PluginConfigs); err != nil { + if err := validateResponses(responses, pluginConfigs); err != nil { return nil, err } - checkRequiredFeatures(container, requiredFeatures, responses, config.PluginConfigs) + checkRequiredFeatures(container, requiredFeatures, responses, pluginConfigs) return responses, nil } @@ -271,18 +272,18 @@ func (g *generator) execLocalPlugin( ctx context.Context, container app.EnvStdioContainer, imageProvider *imageProvider, - pluginConfig *PluginConfig, + pluginConfig bufconfig.GeneratePluginConfig, includeImports bool, includeWellKnownTypes bool, wasmEnabled bool, ) (*pluginpb.CodeGeneratorResponse, error) { - pluginImages, err := imageProvider.GetImages(pluginConfig.Strategy) + pluginImages, err := imageProvider.GetImages(Strategy(pluginConfig.Strategy())) if err != nil { return nil, err } generateOptions := []bufpluginexec.GenerateOption{ - bufpluginexec.GenerateWithPluginPath(pluginConfig.Path...), - bufpluginexec.GenerateWithProtocPath(pluginConfig.ProtocPath), + bufpluginexec.GenerateWithPluginPath(pluginConfig.Path()...), + bufpluginexec.GenerateWithProtocPath(pluginConfig.ProtocPath()), } if wasmEnabled { generateOptions = append( @@ -293,10 +294,10 @@ func (g *generator) execLocalPlugin( response, err := g.pluginexecGenerator.Generate( ctx, container, - pluginConfig.PluginName(), + pluginConfig.Name(), bufimage.ImagesToCodeGeneratorRequests( pluginImages, - pluginConfig.Opt, + pluginConfig.Opt(), nil, includeImports, includeWellKnownTypes, @@ -304,14 +305,14 @@ func (g *generator) execLocalPlugin( generateOptions..., ) if err != nil { - return nil, fmt.Errorf("plugin %s: %v", pluginConfig.PluginName(), err) + return nil, fmt.Errorf("plugin %s: %v", pluginConfig.Name(), err) } return response, nil } type remotePluginExecArgs struct { Index int - PluginConfig *PluginConfig + PluginConfig bufconfig.GeneratePluginConfig } type remotePluginExecutionResult struct { @@ -325,12 +326,16 @@ func (g *generator) execRemotePluginsV2( image bufimage.Image, remote string, pluginConfigs []*remotePluginExecArgs, - includeImports bool, - includeWellKnownTypes bool, + alwaysIncludeImports bool, + alwaysIncludeWellKnownTypes bool, ) ([]*remotePluginExecutionResult, error) { requests := make([]*registryv1alpha1.PluginGenerationRequest, len(pluginConfigs)) for i, pluginConfig := range pluginConfigs { - request, err := getPluginGenerationRequest(pluginConfig.PluginConfig) + request, err := getPluginGenerationRequest( + pluginConfig.PluginConfig, + alwaysIncludeImports || pluginConfig.PluginConfig.IncludeImports(), + alwaysIncludeWellKnownTypes || pluginConfig.PluginConfig.IncludeWKT(), + ) if err != nil { return nil, err } @@ -343,8 +348,8 @@ func (g *generator) execRemotePluginsV2( ®istryv1alpha1.GenerateCodeRequest{ Image: bufimage.ImageToProtoImage(image), Requests: requests, - IncludeImports: includeImports, - IncludeWellKnownTypes: includeWellKnownTypes, + IncludeImports: alwaysIncludeImports, + IncludeWellKnownTypes: alwaysIncludeWellKnownTypes, }, ), ) @@ -370,224 +375,40 @@ func (g *generator) execRemotePluginsV2( } func getPluginGenerationRequest( - pluginConfig *PluginConfig, + pluginConfig bufconfig.GeneratePluginConfig, + includeImports bool, + includeWKT bool, ) (*registryv1alpha1.PluginGenerationRequest, error) { var curatedPluginReference *registryv1alpha1.CuratedPluginReference - if reference, err := bufpluginref.PluginReferenceForString(pluginConfig.Plugin, pluginConfig.Revision); err == nil { + if reference, err := bufpluginref.PluginReferenceForString(pluginConfig.Name(), pluginConfig.Revision()); err == nil { curatedPluginReference = bufplugin.PluginReferenceToProtoCuratedPluginReference(reference) } else { // Try parsing as a plugin identity (no version information) - identity, err := bufpluginref.PluginIdentityForString(pluginConfig.Plugin) + identity, err := bufpluginref.PluginIdentityForString(pluginConfig.Name()) if err != nil { - return nil, fmt.Errorf("invalid remote plugin %q", pluginConfig.Plugin) + return nil, fmt.Errorf("invalid remote plugin %q", pluginConfig.Name()) } curatedPluginReference = bufplugin.PluginIdentityToProtoCuratedPluginReference(identity) } var options []string - if len(pluginConfig.Opt) > 0 { + if len(pluginConfig.Opt()) > 0 { // Only include parameters if they're not empty. - options = []string{pluginConfig.Opt} + options = []string{pluginConfig.Opt()} } return ®istryv1alpha1.PluginGenerationRequest{ - PluginReference: curatedPluginReference, - Options: options, + PluginReference: curatedPluginReference, + Options: options, + IncludeImports: &includeImports, + IncludeWellKnownTypes: &includeWKT, }, nil } -// modifyImage modifies the image according to the given configuration (i.e. managed mode). -func modifyImage( - ctx context.Context, - logger *zap.Logger, - config *Config, - image bufimage.Image, -) error { - if config.ManagedConfig == nil { - // If the config is nil, it implies that the - // user has not enabled managed mode. - return nil - } - sweeper := bufimagemodify.NewFileOptionSweeper() - modifier, err := newModifier(logger, config.ManagedConfig, sweeper) - if err != nil { - return err - } - modifier = bufimagemodify.Merge(modifier, bufimagemodify.ModifierFunc(sweeper.Sweep)) - return modifier.Modify(ctx, image) -} - -func newModifier( - logger *zap.Logger, - managedConfig *ManagedConfig, - sweeper bufimagemodify.Sweeper, -) (bufimagemodify.Modifier, error) { - modifier := bufimagemodify.NewMultiModifier( - bufimagemodify.JavaOuterClassname( - logger, - sweeper, - managedConfig.Override[bufimagemodify.JavaOuterClassNameID], - false, // preserveExistingValue - ), - bufimagemodify.PhpNamespace(logger, sweeper, managedConfig.Override[bufimagemodify.PhpNamespaceID]), - bufimagemodify.PhpMetadataNamespace(logger, sweeper, managedConfig.Override[bufimagemodify.PhpMetadataNamespaceID]), - ) - javaPackagePrefix := &JavaPackagePrefixConfig{Default: bufimagemodify.DefaultJavaPackagePrefix} - if managedConfig.JavaPackagePrefixConfig != nil { - javaPackagePrefix = managedConfig.JavaPackagePrefixConfig - } - javaPackageModifier, err := bufimagemodify.JavaPackage( - logger, - sweeper, - javaPackagePrefix.Default, - javaPackagePrefix.Except, - javaPackagePrefix.Override, - managedConfig.Override[bufimagemodify.JavaPackageID], - ) - if err != nil { - return nil, fmt.Errorf("failed to construct java_package modifier: %w", err) - } - modifier = bufimagemodify.Merge( - modifier, - javaPackageModifier, - ) - javaMultipleFilesValue := bufimagemodify.DefaultJavaMultipleFilesValue - if managedConfig.JavaMultipleFiles != nil { - javaMultipleFilesValue = *managedConfig.JavaMultipleFiles - } - javaMultipleFilesModifier, err := bufimagemodify.JavaMultipleFiles( - logger, - sweeper, - javaMultipleFilesValue, - managedConfig.Override[bufimagemodify.JavaMultipleFilesID], - false, // preserveExistingValue - ) - if err != nil { - return nil, err - } - modifier = bufimagemodify.Merge(modifier, javaMultipleFilesModifier) - if managedConfig.CcEnableArenas != nil { - ccEnableArenasModifier, err := bufimagemodify.CcEnableArenas( - logger, - sweeper, - *managedConfig.CcEnableArenas, - managedConfig.Override[bufimagemodify.CcEnableArenasID], - ) - if err != nil { - return nil, err - } - modifier = bufimagemodify.Merge(modifier, ccEnableArenasModifier) - } - if managedConfig.JavaStringCheckUtf8 != nil { - javaStringCheckUtf8, err := bufimagemodify.JavaStringCheckUtf8( - logger, - sweeper, - *managedConfig.JavaStringCheckUtf8, - managedConfig.Override[bufimagemodify.JavaStringCheckUtf8ID], - ) - if err != nil { - return nil, err - } - modifier = bufimagemodify.Merge(modifier, javaStringCheckUtf8) - } - var ( - csharpNamespaceExcept []bufmodule.ModuleFullName - csharpNamespaceOverride map[bufmodule.ModuleFullName]string - ) - if csharpNameSpaceConfig := managedConfig.CsharpNameSpaceConfig; csharpNameSpaceConfig != nil { - csharpNamespaceExcept = csharpNameSpaceConfig.Except - csharpNamespaceOverride = csharpNameSpaceConfig.Override - } - csharpNamespaceModifier := bufimagemodify.CsharpNamespace( - logger, - sweeper, - csharpNamespaceExcept, - csharpNamespaceOverride, - managedConfig.Override[bufimagemodify.CsharpNamespaceID], - ) - modifier = bufimagemodify.Merge(modifier, csharpNamespaceModifier) - if managedConfig.OptimizeForConfig != nil { - optimizeFor, err := bufimagemodify.OptimizeFor( - logger, - sweeper, - managedConfig.OptimizeForConfig.Default, - managedConfig.OptimizeForConfig.Except, - managedConfig.OptimizeForConfig.Override, - managedConfig.Override[bufimagemodify.OptimizeForID], - ) - if err != nil { - return nil, err - } - modifier = bufimagemodify.Merge( - modifier, - optimizeFor, - ) - } - if managedConfig.GoPackagePrefixConfig != nil { - goPackageModifier, err := bufimagemodify.GoPackage( - logger, - sweeper, - managedConfig.GoPackagePrefixConfig.Default, - managedConfig.GoPackagePrefixConfig.Except, - managedConfig.GoPackagePrefixConfig.Override, - managedConfig.Override[bufimagemodify.GoPackageID], - ) - if err != nil { - return nil, fmt.Errorf("failed to construct go_package modifier: %w", err) - } - modifier = bufimagemodify.Merge( - modifier, - goPackageModifier, - ) - } - var ( - objcClassPrefixDefault string - objcClassPrefixExcept []bufmodule.ModuleFullName - objcClassPrefixOverride map[bufmodule.ModuleFullName]string - ) - if objcClassPrefixConfig := managedConfig.ObjcClassPrefixConfig; objcClassPrefixConfig != nil { - objcClassPrefixDefault = objcClassPrefixConfig.Default - objcClassPrefixExcept = objcClassPrefixConfig.Except - objcClassPrefixOverride = objcClassPrefixConfig.Override - } - objcClassPrefixModifier := bufimagemodify.ObjcClassPrefix( - logger, - sweeper, - objcClassPrefixDefault, - objcClassPrefixExcept, - objcClassPrefixOverride, - managedConfig.Override[bufimagemodify.ObjcClassPrefixID], - ) - modifier = bufimagemodify.Merge( - modifier, - objcClassPrefixModifier, - ) - var ( - rubyPackageExcept []bufmodule.ModuleFullName - rubyPackageOverrides map[bufmodule.ModuleFullName]string - ) - if rubyPackageConfig := managedConfig.RubyPackageConfig; rubyPackageConfig != nil { - rubyPackageExcept = rubyPackageConfig.Except - rubyPackageOverrides = rubyPackageConfig.Override - } - rubyPackageModifier := bufimagemodify.RubyPackage( - logger, - sweeper, - rubyPackageExcept, - rubyPackageOverrides, - managedConfig.Override[bufimagemodify.RubyPackageID], - ) - modifier = bufimagemodify.Merge( - modifier, - rubyPackageModifier, - ) - return modifier, nil -} - // validateResponses verifies that a response is set for each of the // pluginConfigs, and that each generated file is generated by a single // plugin. func validateResponses( responses []*pluginpb.CodeGeneratorResponse, - pluginConfigs []*PluginConfig, + pluginConfigs []bufconfig.GeneratePluginConfig, ) error { if len(responses) != len(pluginConfigs) { return fmt.Errorf("unexpected number of responses: expected %d but got %d", len(pluginConfigs), len(responses)) @@ -596,14 +417,14 @@ func validateResponses( for i, response := range responses { pluginConfig := pluginConfigs[i] if response == nil { - return fmt.Errorf("failed to create a response for %q", pluginConfig.PluginName()) + return fmt.Errorf("failed to create a response for %q", pluginConfig.Name()) } pluginResponses = append( pluginResponses, protoplugin.NewPluginResponse( response, - pluginConfig.PluginName(), - pluginConfig.Out, + pluginConfig.Name(), + pluginConfig.Out(), ), ) } @@ -614,6 +435,7 @@ func validateResponses( } type generateOptions struct { + // plugin specific options: baseOutDirPath string includeImports bool includeWellKnownTypes bool diff --git a/private/buf/bufgen/provider.go b/private/buf/bufgen/provider.go deleted file mode 100644 index ab7d3cccb6..0000000000 --- a/private/buf/bufgen/provider.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufgen - -import ( - "context" - "io" - - "github.com/bufbuild/buf/private/pkg/encoding" - "github.com/bufbuild/buf/private/pkg/storage" - "github.com/bufbuild/buf/private/pkg/tracing" - "go.uber.org/multierr" - "go.uber.org/zap" -) - -type provider struct { - logger *zap.Logger - tracer tracing.Tracer -} - -func newProvider(logger *zap.Logger, tracer tracing.Tracer) *provider { - return &provider{ - logger: logger, - tracer: tracer, - } -} - -func (p *provider) GetConfig(ctx context.Context, readBucket storage.ReadBucket) (_ *Config, retErr error) { - ctx, span := p.tracer.Start(ctx, tracing.WithErr(&retErr)) - defer span.End() - - readObjectCloser, err := readBucket.Get(ctx, ExternalConfigFilePath) - if err != nil { - // There is no default generate template, so we propagate all errors, including - // storage.ErrNotExist. - return nil, err - } - defer func() { - retErr = multierr.Append(retErr, readObjectCloser.Close()) - }() - data, err := io.ReadAll(readObjectCloser) - if err != nil { - return nil, err - } - return getConfig( - p.logger, - encoding.UnmarshalYAMLNonStrict, - encoding.UnmarshalYAMLStrict, - data, - `File "`+readObjectCloser.ExternalPath()+`"`, - ) -} diff --git a/private/buf/bufgen/provider_test.go b/private/buf/bufgen/provider_test.go deleted file mode 100644 index 9860a0765d..0000000000 --- a/private/buf/bufgen/provider_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufgen - -import ( - "context" - "errors" - "fmt" - "io/fs" - "path/filepath" - "testing" - - "github.com/bufbuild/buf/private/pkg/storage/storagemem" - "github.com/bufbuild/buf/private/pkg/storage/storageos" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -const testConfigFileData = ` -version: %s -plugins: - - name: go - out: private/gen/proto/go - opt: paths=source_relative -` - -func TestProviderV1Beta1(t *testing.T) { - t.Parallel() - testProvider(t, "v1beta1") -} - -func TestProviderV1(t *testing.T) { - t.Parallel() - testProvider(t, "v1") -} - -func TestProviderError(t *testing.T) { - t.Parallel() - storageosProvider := storageos.NewProvider() - readWriteBucket, err := storageosProvider.NewReadWriteBucket(".") - require.NoError(t, err) - - provider := NewProvider(zap.NewNop()) - _, err = provider.GetConfig(context.Background(), readWriteBucket) - require.True(t, errors.Is(err, fs.ErrNotExist)) -} - -func testProvider(t *testing.T, version string) { - storageosProvider := storageos.NewProvider() - readWriteBucket, err := storageosProvider.NewReadWriteBucket(filepath.Join("testdata", version)) - require.NoError(t, err) - - nopLogger := zap.NewNop() - provider := NewProvider(zap.NewNop()) - actual, err := provider.GetConfig(context.Background(), readWriteBucket) - require.NoError(t, err) - - emptyBucket, err := storagemem.NewReadBucket(nil) - require.NoError(t, err) - expected, err := ReadConfig(context.Background(), nopLogger, provider, emptyBucket, ReadConfigWithOverride(fmt.Sprintf(testConfigFileData, version))) - require.NoError(t, err) - assert.Equal(t, expected, actual) -} diff --git a/private/buf/bufmigrate/bufmigrate.go b/private/buf/bufmigrate/bufmigrate.go deleted file mode 100644 index f0f27c46be..0000000000 --- a/private/buf/bufmigrate/bufmigrate.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufmigrate contains logic for migrating between different -// configuration file versions. -package bufmigrate - -// Migrator describes the interface used to migrate -// a set of files in a directory from one version to another. -type Migrator interface { - Migrate(dirPath string) error -} - -// V1Beta1MigrateOption defines the type used -// to configure the v1beta1 migrator. -type V1Beta1MigrateOption func(*v1beta1Migrator) - -// NewV1Beta1Migrator creates a new migrator that migrates files -// between version v1beta1 and v1. -func NewV1Beta1Migrator(commandName string, options ...V1Beta1MigrateOption) Migrator { - return newV1Beta1Migrator(commandName, options...) -} - -// V1Beta1MigratorWithNotifier instruments the migrator with -// a callback to call whenever an event that should notify the -// user occurs during the migration. -func V1Beta1MigratorWithNotifier(notifier func(message string) error) V1Beta1MigrateOption { - return func(migrateOptions *v1beta1Migrator) { - migrateOptions.notifier = notifier - } -} diff --git a/private/buf/bufmigrate/usage.gen.go b/private/buf/bufmigrate/usage.gen.go deleted file mode 100644 index a27acc9b2a..0000000000 --- a/private/buf/bufmigrate/usage.gen.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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. - -// Generated. DO NOT EDIT. - -package bufmigrate - -import _ "github.com/bufbuild/buf/private/usage" diff --git a/private/buf/bufmigrate/v1beta1_migrator.go b/private/buf/bufmigrate/v1beta1_migrator.go deleted file mode 100644 index 8b62262419..0000000000 --- a/private/buf/bufmigrate/v1beta1_migrator.go +++ /dev/null @@ -1,553 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufmigrate - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "sort" - "strings" - - "github.com/bufbuild/buf/private/buf/bufgen" - "github.com/bufbuild/buf/private/buf/bufwork" - "github.com/bufbuild/buf/private/bufpkg/bufcheck/bufbreaking/bufbreakingconfig" - "github.com/bufbuild/buf/private/bufpkg/bufcheck/buflint/buflintconfig" - "github.com/bufbuild/buf/private/bufpkg/bufconfig" - "github.com/bufbuild/buf/private/bufpkg/buflock" - "github.com/bufbuild/buf/private/bufpkg/bufmodule/bufmoduleconfig" - "github.com/bufbuild/buf/private/pkg/encoding" - "github.com/bufbuild/buf/private/pkg/stringutil" -) - -const ( - bufModHeaderWithName = `# Generated by %q. Edit as necessary, and -# remove this comment when you're finished. -# -# This module represents the %q root found in -# the previous configuration file for the -# %q module. -` - bufModHeaderWithoutName = `# Generated by %q. Edit as necessary, and -# remove this comment when you're finished. -# -# This module represents the %q root found in -# the previous configuration. -` - bufGenHeader = `# Generated by %q. Edit as necessary, and -# remove this comment when you're finished. -` - bufWorkHeader = `# Generated by %q. Edit as necessary, and -# remove this comment when you're finished. -# -# This workspace file points to the roots found in your -# previous %q configuration. -` -) - -type v1beta1Migrator struct { - notifier func(string) error - commandName string -} - -func newV1Beta1Migrator(commandName string, options ...V1Beta1MigrateOption) *v1beta1Migrator { - migrator := v1beta1Migrator{ - commandName: commandName, - notifier: func(string) error { return nil }, - } - for _, option := range options { - option(&migrator) - } - return &migrator -} - -func (m *v1beta1Migrator) Migrate(dirPath string) error { - migratedConfig, err := m.maybeMigrateConfig(dirPath) - if err != nil { - return fmt.Errorf("failed to migrate config: %w", err) - } - migratedGenTemplate, err := m.maybeMigrateGenTemplate(dirPath) - if err != nil { - return fmt.Errorf("failed to migrate generation template: %w", err) - } - migratedLockFile, err := m.maybeMigrateLockFile(dirPath) - if err != nil { - return fmt.Errorf("failed to migrate lock file: %w", err) - } - if !migratedConfig && !migratedGenTemplate && !migratedLockFile { - return nil - } - var migratedFiles []string - if migratedConfig { - migratedFiles = append(migratedFiles, bufconfig.ExternalConfigV1Beta1FilePath) - } - if migratedGenTemplate { - migratedFiles = append(migratedFiles, bufgen.ExternalConfigFilePath) - } - if migratedLockFile { - migratedFiles = append(migratedFiles, buflock.ExternalConfigFilePath) - } - if err := m.notifier( - fmt.Sprintf("Successfully migrated your %s to v1.\n", stringutil.SliceToHumanString(migratedFiles)), - ); err != nil { - return fmt.Errorf("failed to write success message: %w", err) - } - return nil -} - -func (m *v1beta1Migrator) maybeMigrateConfig(dirPath string) (bool, error) { - oldConfigPath := filepath.Join(dirPath, bufconfig.ExternalConfigV1Beta1FilePath) - oldConfigBytes, err := os.ReadFile(oldConfigPath) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - // OK, no old config file - return false, nil - } - return false, fmt.Errorf("failed to read file: %w", err) - } - var versionedConfig bufconfig.ExternalConfigVersion - if err := encoding.UnmarshalYAMLNonStrict(oldConfigBytes, &versionedConfig); err != nil { - return false, fmt.Errorf( - "failed to read %s version: %w", - oldConfigPath, - err, - ) - } - switch versionedConfig.Version { - case bufconfig.V1Version: - // OK, file was already v1 - return false, nil - case bufconfig.V1Beta1Version, "": - // Continue to migrate - default: - return false, fmt.Errorf("unknown config file version: %s", versionedConfig.Version) - } - var v1beta1Config bufconfig.ExternalConfigV1Beta1 - if err := encoding.UnmarshalYAMLStrict(oldConfigBytes, &v1beta1Config); err != nil { - return false, fmt.Errorf( - "failed to unmarshal %s as %s version v1beta1: %w", - oldConfigPath, - bufconfig.ExternalConfigV1Beta1FilePath, - err, - ) - } - buildConfig, err := bufmoduleconfig.NewConfigV1Beta1(v1beta1Config.Build, v1beta1Config.Deps...) - if err != nil { - return false, err - } - if excludes, ok := buildConfig.RootToExcludes["."]; len(buildConfig.RootToExcludes) == 1 && ok { - // Only "." root present, just recreate file - v1Config := bufconfig.ExternalConfigV1{ - Version: bufconfig.V1Version, - Name: v1beta1Config.Name, - Deps: v1beta1Config.Deps, - Build: bufmoduleconfig.ExternalConfigV1{ - Excludes: excludes, - }, - Breaking: bufbreakingconfig.ExternalConfigV1(v1beta1Config.Breaking), - Lint: buflintconfig.ExternalConfigV1(v1beta1Config.Lint), - } - newConfigPath := filepath.Join(dirPath, bufconfig.ExternalConfigV1FilePath) - if err := m.writeV1Config(newConfigPath, v1Config, ".", v1beta1Config.Name); err != nil { - return false, err - } - // Delete the old file once we've created the new one, - // unless it's the same file as before. - if newConfigPath != oldConfigPath { - if err := os.Remove(oldConfigPath); err != nil { - return false, fmt.Errorf("failed to delete old config file: %w", err) - } - } - return true, nil - } - // Check if we have a co-resident lock file, of any version - oldLockFilePath := filepath.Join(dirPath, buflock.ExternalConfigFilePath) - externalLockFileV1, hasLockFile, err := maybeReadLockFile(oldLockFilePath) - if err != nil { - return false, err - } - pathToProcessed := make(map[string]bool) - for root, excludes := range buildConfig.RootToExcludes { - // Convert universal settings - var name string - if v1beta1Config.Name != "" { - name = v1beta1Config.Name + "-" + strings.ReplaceAll(root, "/", "-") // Note: roots are normalized, "/" is universal - } - v1Config := bufconfig.ExternalConfigV1{ - Version: bufconfig.V1Version, - Name: name, - Deps: v1beta1Config.Deps, - Build: bufmoduleconfig.ExternalConfigV1{ - Excludes: excludes, - }, - Breaking: bufbreakingconfig.ExternalConfigV1{ - Use: v1beta1Config.Breaking.Use, - Except: v1beta1Config.Breaking.Except, - IgnoreUnstablePackages: v1beta1Config.Breaking.IgnoreUnstablePackages, - }, - Lint: buflintconfig.ExternalConfigV1{ - Use: v1beta1Config.Lint.Use, - Except: v1beta1Config.Lint.Except, - ServiceSuffix: v1beta1Config.Lint.ServiceSuffix, - EnumZeroValueSuffix: v1beta1Config.Lint.EnumZeroValueSuffix, - RPCAllowSameRequestResponse: v1beta1Config.Lint.RPCAllowSameRequestResponse, - RPCAllowGoogleProtobufEmptyRequests: v1beta1Config.Lint.RPCAllowGoogleProtobufEmptyRequests, - RPCAllowGoogleProtobufEmptyResponses: v1beta1Config.Lint.RPCAllowGoogleProtobufEmptyResponses, - AllowCommentIgnores: v1beta1Config.Lint.AllowCommentIgnores, - }, - } - - // Process Ignore's for those related to the root - v1Config.Breaking.Ignore, err = convertIgnoreSlice(v1beta1Config.Breaking.Ignore, dirPath, root, pathToProcessed) - if err != nil { - return false, err - } - v1Config.Breaking.IgnoreOnly, err = convertIgnoreMap(v1beta1Config.Breaking.IgnoreOnly, dirPath, root, pathToProcessed) - if err != nil { - return false, err - } - v1Config.Lint.Ignore, err = convertIgnoreSlice(v1beta1Config.Lint.Ignore, dirPath, root, pathToProcessed) - if err != nil { - return false, err - } - v1Config.Lint.IgnoreOnly, err = convertIgnoreMap(v1beta1Config.Lint.IgnoreOnly, dirPath, root, pathToProcessed) - if err != nil { - return false, err - } - if err := m.writeV1Config( - filepath.Join(dirPath, root, bufconfig.ExternalConfigV1FilePath), - v1Config, - root, - v1beta1Config.Name, - ); err != nil { - return false, err - } - if hasLockFile { - if err := m.writeV1LockFile( - filepath.Join(dirPath, root, buflock.ExternalConfigFilePath), - externalLockFileV1, - ); err != nil { - return false, err - } - } - } - for path, processed := range pathToProcessed { - if !processed { - if err := m.notifier( - fmt.Sprintf( - "The ignored file %q was not found in any roots and has been removed.\n", - path, - ), - ); err != nil { - return false, fmt.Errorf("failed to warn about ignored file: %w", err) - } - } - } - workConfig := bufwork.ExternalConfigV1{ - Version: bufwork.V1Version, - Directories: v1beta1Config.Build.Roots, - } - // Sort directories before marshalling for deterministic output - sort.Strings(workConfig.Directories) - workConfigBytes, err := encoding.MarshalYAML(&workConfig) - if err != nil { - return false, fmt.Errorf("failed to marshal workspace file: %w", err) - } - header := fmt.Sprintf(bufWorkHeader, m.commandName, bufconfig.ExternalConfigV1Beta1FilePath) - if err := os.WriteFile( - filepath.Join(dirPath, bufwork.ExternalConfigV1FilePath), - append([]byte(header), workConfigBytes...), - 0600, - ); err != nil { - return false, fmt.Errorf("failed to write workspace file: %w", err) - } - // Finally, delete the old `buf.yaml` and any `buf.lock`. This is safe to do unconditionally - // as we know that there can't be a new `buf.yaml` here, since the only case - // where that would be true is if the only root is ".", which is handled separately. - if err := os.Remove(oldConfigPath); err != nil { - return false, fmt.Errorf("failed to clean up old config file: %w", err) - } - if hasLockFile { - if err := os.Remove(oldLockFilePath); err != nil { - return false, fmt.Errorf("failed to clean up old lock file: %w", err) - } - } - return true, nil -} - -// writeV1Config atomically replaces the old configuration file by first writing -// the new config to a temporary file and then moving it to the old config file path. -// If we fail to marshal or write, the old config file is not touched. -func (m *v1beta1Migrator) writeV1Config( - configPath string, - config bufconfig.ExternalConfigV1, - originalRootName string, - originalModuleName string, -) (retErr error) { - v1ConfigData, err := encoding.MarshalYAML(&config) - if err != nil { - return fmt.Errorf("failed to marshal new config: %w", err) - } - header := fmt.Sprintf(bufModHeaderWithName, m.commandName, originalRootName, originalModuleName) - if originalModuleName == "" { - header = fmt.Sprintf(bufModHeaderWithoutName, m.commandName, originalRootName) - } - v1ConfigData = append([]byte(header), v1ConfigData...) - if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil { - // This happens if the user has a root specified that doesn't have a corresponding - // directory on the filesystem. - return fmt.Errorf("failed to create new directories for writing config: %w", err) - } - return os.WriteFile(configPath, v1ConfigData, 0600) -} - -func (m *v1beta1Migrator) maybeMigrateGenTemplate(dirPath string) (bool, error) { - oldConfigPath := filepath.Join(dirPath, bufgen.ExternalConfigFilePath) - oldConfigBytes, err := os.ReadFile(oldConfigPath) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - // OK, no old config file - return false, nil - } - return false, fmt.Errorf("failed to read file: %w", err) - } - var versionedConfig bufgen.ExternalConfigVersion - if err := encoding.UnmarshalYAMLNonStrict(oldConfigBytes, &versionedConfig); err != nil { - return false, fmt.Errorf( - "failed to read %s version: %w", - oldConfigPath, - err, - ) - } - switch versionedConfig.Version { - case bufgen.V1Version: - // OK, file was already v1 - return false, nil - case bufgen.V1Beta1Version, "": - // Continue to migrate - default: - return false, fmt.Errorf("unknown config file version: %s", versionedConfig.Version) - } - var v1beta1GenTemplate bufgen.ExternalConfigV1Beta1 - if err := encoding.UnmarshalYAMLStrict(oldConfigBytes, &v1beta1GenTemplate); err != nil { - return false, fmt.Errorf( - "failed to unmarshal %s as %s version v1beta1: %w", - oldConfigPath, - bufgen.ExternalConfigFilePath, - err, - ) - } - v1GenTemplate := bufgen.ExternalConfigV1{ - Version: bufgen.V1Version, - Managed: bufgen.ExternalManagedConfigV1{ - Enabled: v1beta1GenTemplate.Managed, - CcEnableArenas: v1beta1GenTemplate.Options.CcEnableArenas, - JavaMultipleFiles: v1beta1GenTemplate.Options.JavaMultipleFiles, - OptimizeFor: bufgen.ExternalOptimizeForConfigV1{Default: v1beta1GenTemplate.Options.OptimizeFor}, - }, - } - for _, plugin := range v1beta1GenTemplate.Plugins { - pluginConfig := bufgen.ExternalPluginConfigV1{ - Name: plugin.Name, - Out: plugin.Out, - Opt: plugin.Opt, - Strategy: plugin.Strategy, - } - if plugin.Path != "" { - pluginConfig.Path = plugin.Path - } - v1GenTemplate.Plugins = append(v1GenTemplate.Plugins, pluginConfig) - } - newConfigPath := filepath.Join(dirPath, bufgen.ExternalConfigFilePath) - if err := m.writeV1GenTemplate(newConfigPath, v1GenTemplate); err != nil { - return false, err - } - return true, nil -} - -// writeV1GenTemplate atomically replaces the old configuration file by first writing -// the new config to a temporary file and then moving it to the old config file path. -// If we fail to marshal or write, the old config file is not touched. -func (m *v1beta1Migrator) writeV1GenTemplate( - configPath string, - config bufgen.ExternalConfigV1, -) (retErr error) { - v1ConfigData, err := encoding.MarshalYAML(&config) - if err != nil { - return fmt.Errorf("failed to marshal new config: %w", err) - } - header := fmt.Sprintf(bufGenHeader, m.commandName) - v1ConfigData = append([]byte(header), v1ConfigData...) - return os.WriteFile(configPath, v1ConfigData, 0600) -} - -func (m *v1beta1Migrator) maybeMigrateLockFile(dirPath string) (bool, error) { - oldConfigPath := filepath.Join(dirPath, buflock.ExternalConfigFilePath) - oldConfigBytes, err := os.ReadFile(oldConfigPath) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - // OK, no old config file - return false, nil - } - return false, fmt.Errorf("failed to read file: %w", err) - } - var versionedConfig buflock.ExternalConfigVersion - if err := encoding.UnmarshalYAMLNonStrict(oldConfigBytes, &versionedConfig); err != nil { - return false, fmt.Errorf( - "failed to read %s version: %w", - oldConfigPath, - err, - ) - } - switch versionedConfig.Version { - case buflock.V1Version: - // OK, file was already v1 - return false, nil - case buflock.V1Beta1Version, "": - // Continue to migrate - default: - return false, fmt.Errorf("unknown lock file version: %s", versionedConfig.Version) - } - var v1beta1LockFile buflock.ExternalConfigV1Beta1 - if err := encoding.UnmarshalYAMLStrict(oldConfigBytes, &v1beta1LockFile); err != nil { - return false, fmt.Errorf( - "failed to unmarshal %s as %s version v1beta1: %w", - oldConfigPath, - buflock.ExternalConfigFilePath, - err, - ) - } - v1LockFile := buflock.ExternalConfigV1{ - Version: buflock.V1Version, - } - for _, dependency := range v1beta1LockFile.Deps { - v1LockFile.Deps = append(v1LockFile.Deps, buflock.ExternalConfigDependencyV1(dependency)) - } - newConfigPath := filepath.Join(dirPath, buflock.ExternalConfigFilePath) - if err := m.writeV1LockFile(newConfigPath, v1LockFile); err != nil { - return false, err - } - return true, nil -} - -// writeV1LockFile atomically replaces the old lock file by first writing -// the new lock file to a temporary file and then moving it to the old lock file path. -// If we fail to marshal or write, the old lock file is not touched. -func (m *v1beta1Migrator) writeV1LockFile( - configPath string, - config buflock.ExternalConfigV1, -) (retErr error) { - v1ConfigData, err := encoding.MarshalYAML(&config) - if err != nil { - return fmt.Errorf("failed to marshal new lock file: %w", err) - } - v1ConfigData = append([]byte(buflock.Header), v1ConfigData...) - return os.WriteFile(configPath, v1ConfigData, 0600) -} - -func maybeReadLockFile(oldLockFilePath string) (buflock.ExternalConfigV1, bool, error) { - lockFileBytes, err := os.ReadFile(oldLockFilePath) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - // OK, no old lock file - return buflock.ExternalConfigV1{}, false, nil - } - - return buflock.ExternalConfigV1{}, false, fmt.Errorf("failed to read lock file path: %w", err) - } - var versionedConfig buflock.ExternalConfigVersion - if err := encoding.UnmarshalYAMLNonStrict(lockFileBytes, &versionedConfig); err != nil { - return buflock.ExternalConfigV1{}, false, fmt.Errorf( - "failed to read %s version: %w", - oldLockFilePath, - err, - ) - } - switch versionedConfig.Version { - case "", buflock.V1Beta1Version: - var externalConfig buflock.ExternalConfigV1Beta1 - if err := encoding.UnmarshalYAMLStrict(lockFileBytes, &externalConfig); err != nil { - return buflock.ExternalConfigV1{}, false, fmt.Errorf( - "failed to unmarshal lock file at %s: %w", - buflock.V1Beta1Version, - err, - ) - } - externalLockFileV1 := buflock.ExternalConfigV1{ - Version: buflock.V1Version, - } - for _, dependency := range externalConfig.Deps { - externalLockFileV1.Deps = append(externalLockFileV1.Deps, buflock.ExternalConfigDependencyV1(dependency)) - } - return externalLockFileV1, true, nil - case buflock.V1Version: - externalLockFileV1 := buflock.ExternalConfigV1{} - if err := encoding.UnmarshalYAMLStrict(lockFileBytes, &externalLockFileV1); err != nil { - return buflock.ExternalConfigV1{}, false, fmt.Errorf("failed to unmarshal lock file at %s: %w", buflock.V1Version, err) - } - return externalLockFileV1, true, nil - default: - return buflock.ExternalConfigV1{}, false, fmt.Errorf("unknown lock file version: %s", versionedConfig.Version) - } -} - -func convertIgnoreSlice(paths []string, dirPath string, root string, pathToProcessed map[string]bool) ([]string, error) { - var ignoresForRoot []string - for _, ignoredFile := range paths { - if _, ok := pathToProcessed[ignoredFile]; !ok { - pathToProcessed[ignoredFile] = false - } - filePath := filepath.Join(dirPath, root, ignoredFile) - if _, err := os.Stat(filePath); err != nil { - if errors.Is(err, os.ErrNotExist) { - continue - } - return nil, fmt.Errorf("failed to check for presence of file %s: %w", filePath, err) - } - pathToProcessed[ignoredFile] = true - ignoresForRoot = append(ignoresForRoot, ignoredFile) - } - sort.Strings(ignoresForRoot) - return ignoresForRoot, nil -} - -func convertIgnoreMap(ruleToIgnores map[string][]string, dirPath string, root string, pathToProcessed map[string]bool) (map[string][]string, error) { - var ruleToIgnoresForRoot map[string][]string - for rule, ignores := range ruleToIgnores { - for _, ignoredFile := range ignores { - if _, ok := pathToProcessed[ignoredFile]; !ok { - pathToProcessed[ignoredFile] = false - } - filePath := filepath.Join(dirPath, root, ignoredFile) - if _, err := os.Stat(filePath); err != nil { - if errors.Is(err, os.ErrNotExist) { - continue - } - return nil, fmt.Errorf("failed to check for presence of file %s: %w", filePath, err) - } - if ruleToIgnoresForRoot == nil { - ruleToIgnoresForRoot = make(map[string][]string) - } - pathToProcessed[ignoredFile] = true - ruleToIgnoresForRoot[rule] = append( - ruleToIgnoresForRoot[rule], - ignoredFile, - ) - } - sort.Strings(ruleToIgnoresForRoot[rule]) - } - return ruleToIgnoresForRoot, nil -} diff --git a/private/buf/cmd/buf/buf.go b/private/buf/cmd/buf/buf.go index 68227ce51c..780a1ec915 100644 --- a/private/buf/cmd/buf/buf.go +++ b/private/buf/cmd/buf/buf.go @@ -62,6 +62,7 @@ import ( "github.com/bufbuild/buf/private/buf/cmd/buf/command/convert" "github.com/bufbuild/buf/private/buf/cmd/buf/command/curl" "github.com/bufbuild/buf/private/buf/cmd/buf/command/export" + "github.com/bufbuild/buf/private/buf/cmd/buf/command/generate" "github.com/bufbuild/buf/private/buf/cmd/buf/command/lint" "github.com/bufbuild/buf/private/buf/cmd/buf/command/lsfiles" "github.com/bufbuild/buf/private/buf/cmd/buf/command/mod/modclearcache" @@ -107,7 +108,7 @@ func NewRootCommand(name string) *appcmd.Command { //format.NewCommand("format", builder), lint.NewCommand("lint", builder), breaking.NewCommand("breaking", builder), - //generate.NewCommand("generate", builder), + generate.NewCommand("generate", builder), lsfiles.NewCommand("ls-files", builder), // TODO: still need to port push.NewCommand("push", builder), @@ -141,7 +142,6 @@ func NewRootCommand(name string) *appcmd.Command { graph.NewCommand("graph", builder), price.NewCommand("price", builder), stats.NewCommand("stats", builder), - //migratev1beta1.NewCommand("migrate-v1beta1", builder), studioagent.NewCommand("studio-agent", builder), { Use: "registry", diff --git a/private/buf/cmd/buf/buf_test.go b/private/buf/cmd/buf/buf_test.go index 32312d11d4..434fdba310 100644 --- a/private/buf/cmd/buf/buf_test.go +++ b/private/buf/cmd/buf/buf_test.go @@ -16,7 +16,6 @@ package buf import ( "bytes" - "context" "fmt" "io" "os" @@ -29,8 +28,6 @@ import ( "github.com/bufbuild/buf/private/buf/cmd/buf/internal/internaltesting" "github.com/bufbuild/buf/private/pkg/app/appcmd" "github.com/bufbuild/buf/private/pkg/app/appcmd/appcmdtesting" - "github.com/bufbuild/buf/private/pkg/command" - "github.com/bufbuild/buf/private/pkg/storage" "github.com/bufbuild/buf/private/pkg/storage/storageos" "github.com/bufbuild/buf/private/pkg/storage/storagetesting" "github.com/stretchr/testify/assert" @@ -1566,207 +1563,6 @@ func TestVersion(t *testing.T) { testRunStdout(t, nil, 0, bufcli.Version, "--version") } -func TestMigrateV1Beta1(t *testing.T) { - t.Parallel() - // TODO - t.Skip("TODO") - storageosProvider := storageos.NewProvider() - runner := command.NewRunner() - - // These test cases are ordered alphabetically to align with the folders in testadata. - t.Run("buf-gen-yaml-without-version", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "buf-gen-yaml-without-version", - "Successfully migrated your buf.gen.yaml to v1.", - ) - }) - t.Run("buf-yaml-without-version", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "buf-yaml-without-version", - "Successfully migrated your buf.yaml to v1.", - ) - }) - t.Run("complex", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "complex", - `The ignored file "file3.proto" was not found in any roots and has been removed. -Successfully migrated your buf.yaml and buf.gen.yaml to v1.`, - ) - }) - t.Run("deps-without-name", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "deps-without-name", - "Successfully migrated your buf.yaml to v1.", - ) - }) - t.Run("flat-deps-without-name", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "flat-deps-without-name", - "Successfully migrated your buf.yaml and buf.lock to v1.", - ) - }) - t.Run("lock-file-without-deps", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "lock-file-without-deps", - `Successfully migrated your buf.yaml and buf.lock to v1.`, - ) - }) - t.Run("nested-folder", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "nested-folder", - "Successfully migrated your buf.yaml to v1.", - ) - }) - t.Run("nested-root", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "nested-root", - "Successfully migrated your buf.yaml and buf.gen.yaml to v1.", - ) - }) - t.Run("no-deps", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "no-deps", - "Successfully migrated your buf.yaml and buf.gen.yaml to v1.", - ) - }) - t.Run("noop", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "noop", - "", - ) - }) - t.Run("only-buf-gen-yaml", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "only-buf-gen-yaml", - "Successfully migrated your buf.gen.yaml to v1.", - ) - }) - t.Run("only-buf-lock", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "only-buf-lock", - "Successfully migrated your buf.lock to v1.", - ) - }) - t.Run("only-buf-yaml", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "only-buf-yaml", - "Successfully migrated your buf.yaml to v1.", - ) - }) - t.Run("only-old-buf-gen-yaml", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "only-old-buf-gen-yaml", - "Successfully migrated your buf.gen.yaml to v1.", - ) - }) - t.Run("only-old-buf-lock", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "only-old-buf-lock", - `Successfully migrated your buf.lock to v1.`, - ) - }) - t.Run("only-old-buf-yaml", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "only-old-buf-yaml", - "Successfully migrated your buf.yaml to v1.", - ) - }) - t.Run("simple", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "simple", - "Successfully migrated your buf.yaml, buf.gen.yaml, and buf.lock to v1.", - ) - }) - t.Run("v1beta1-lock-file", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Diff( - t, - storageosProvider, - runner, - "v1beta1-lock-file", - `Successfully migrated your buf.yaml and buf.lock to v1.`, - ) - }) - - t.Run("fails-on-invalid-version", func(t *testing.T) { - t.Parallel() - testMigrateV1Beta1Failure( - t, - storageosProvider, - "invalid-version", - `failed to migrate config: unknown config file version: spaghetti`, - ) - }) -} - func TestConvertWithImage(t *testing.T) { t.Parallel() tempDir := t.TempDir() @@ -2508,55 +2304,6 @@ func TestConvertRoundTrip(t *testing.T) { }) } -func testMigrateV1Beta1Diff( - t *testing.T, - storageosProvider storageos.Provider, - runner command.Runner, - scenario string, - expectedStderr string, -) { - // Copy test setup to temporary directory to avoid writing to filesystem - inputBucket, err := storageosProvider.NewReadWriteBucket(filepath.Join("testdata", "migrate-v1beta1", "success", scenario, "input")) - require.NoError(t, err) - tempDir, readWriteBucket := internaltesting.CopyReadBucketToTempDir(context.Background(), t, storageosProvider, inputBucket) - - testRunStdoutStderrNoWarn( - t, - nil, - 0, - "", - expectedStderr, - "beta", - "migrate-v1beta1", - tempDir, - ) - - expectedOutputBucket, err := storageosProvider.NewReadWriteBucket(filepath.Join("testdata", "migrate-v1beta1", "success", scenario, "output")) - require.NoError(t, err) - - diff, err := storage.DiffBytes(context.Background(), runner, expectedOutputBucket, readWriteBucket) - require.NoError(t, err) - require.Empty(t, string(diff)) -} - -func testMigrateV1Beta1Failure(t *testing.T, storageosProvider storageos.Provider, scenario string, expectedStderr string) { - // Copy test setup to temporary directory to avoid writing to filesystem - inputBucket, err := storageosProvider.NewReadWriteBucket(filepath.Join("testdata", "migrate-v1beta1", "failure", scenario)) - require.NoError(t, err) - tempDir, _ := internaltesting.CopyReadBucketToTempDir(context.Background(), t, storageosProvider, inputBucket) - - testRunStdoutStderrNoWarn( - t, - nil, - 1, - "", - expectedStderr, - "beta", - "migrate-v1beta1", - tempDir, - ) -} - func testModInit(t *testing.T, expectedData string, document bool, name string, deps ...string) { tempDir := t.TempDir() baseArgs := []string{"mod", "init"} diff --git a/private/buf/cmd/buf/command/beta/migratev1beta1/migratev1beta1.go b/private/buf/cmd/buf/command/beta/migratev1beta1/migratev1beta1.go deleted file mode 100644 index ee00073789..0000000000 --- a/private/buf/cmd/buf/command/beta/migratev1beta1/migratev1beta1.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 migratev1beta1 - -import ( - "context" - - "github.com/bufbuild/buf/private/buf/bufmigrate" - "github.com/bufbuild/buf/private/pkg/app" - "github.com/bufbuild/buf/private/pkg/app/appcmd" - "github.com/bufbuild/buf/private/pkg/app/appext" - "github.com/spf13/pflag" -) - -// NewCommand returns a new Command. -func NewCommand( - name string, - builder appext.SubCommandBuilder, -) *appcmd.Command { - flags := newFlags() - return &appcmd.Command{ - Use: name + " ", - Short: `Migrate v1beta1 configuration to the latest version`, - Long: `Migrate any v1beta1 configuration files in the directory to the latest version. -Defaults to the current directory if not specified.`, - Args: appcmd.MaximumNArgs(1), - Run: builder.NewRunFunc( - func(ctx context.Context, container appext.Container) error { - return run(ctx, container, flags) - }, - ), - BindFlags: flags.Bind, - } -} - -type flags struct{} - -func newFlags() *flags { - return &flags{} -} - -func (f *flags) Bind(flagSet *pflag.FlagSet) {} - -func run( - ctx context.Context, - container appext.Container, - flags *flags, -) error { - dirPath, err := getDirPath(container) - if err != nil { - return err - } - return bufmigrate.NewV1Beta1Migrator( - "buf config migrate-v1beta1", - bufmigrate.V1Beta1MigratorWithNotifier(newWriteMessageFunc(container)), - ).Migrate(dirPath) -} - -func getDirPath(container app.Container) (string, error) { - switch numArgs := container.NumArgs(); numArgs { - case 0: - return ".", nil - case 1: - return container.Arg(0), nil - default: - return "", appcmd.NewInvalidArgumentErrorf("only 1 argument allowed but %d arguments specified", numArgs) - } -} - -func newWriteMessageFunc(container app.StderrContainer) func(string) error { - return func(message string) error { - _, err := container.Stderr().Write([]byte(message)) - return err - } -} diff --git a/private/buf/cmd/buf/command/beta/migratev1beta1/usage.gen.go b/private/buf/cmd/buf/command/beta/migratev1beta1/usage.gen.go deleted file mode 100644 index a3de0ecf16..0000000000 --- a/private/buf/cmd/buf/command/beta/migratev1beta1/usage.gen.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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. - -// Generated. DO NOT EDIT. - -package migratev1beta1 - -import _ "github.com/bufbuild/buf/private/usage" diff --git a/private/buf/cmd/buf/command/generate/generate.go b/private/buf/cmd/buf/command/generate/generate.go index 496d534894..1d12856415 100644 --- a/private/buf/cmd/buf/command/generate/generate.go +++ b/private/buf/cmd/buf/command/generate/generate.go @@ -17,22 +17,27 @@ package generate import ( "context" "fmt" + "os" "path/filepath" + "strings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/buf/buffetch" "github.com/bufbuild/buf/private/buf/bufgen" "github.com/bufbuild/buf/private/bufpkg/bufanalysis" + "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufimage" - "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimageutil" "github.com/bufbuild/buf/private/bufpkg/bufwasm" "github.com/bufbuild/buf/private/pkg/app/appcmd" "github.com/bufbuild/buf/private/pkg/app/appext" "github.com/bufbuild/buf/private/pkg/command" + "github.com/bufbuild/buf/private/pkg/slicesext" "github.com/bufbuild/buf/private/pkg/storage/storageos" "github.com/bufbuild/buf/private/pkg/stringutil" + "github.com/bufbuild/buf/private/pkg/tracing" "github.com/spf13/pflag" + "go.uber.org/zap" ) const ( @@ -48,6 +53,7 @@ const ( disableSymlinksFlagName = "disable-symlinks" typeFlagName = "type" typeDeprecatedFlagName = "include-types" + migrateFlagName = "migrate" ) // NewCommand returns a new Command. @@ -208,6 +214,7 @@ type flags struct { // want to find out what will break if we do. Types []string TypesDeprecated []string + Migrate bool // special InputHashtag string } @@ -276,6 +283,12 @@ func (f *flags) Bind(flagSet *pflag.FlagSet) { nil, "The types (package, message, enum, extension, service, method) that should be included in this image. When specified, the resulting image will only include descriptors to describe the requested types. Flag usage overrides buf.gen.yaml", ) + flagSet.BoolVar( + &f.Migrate, + migrateFlagName, + false, + "Migrate the generation template to the latest version", + ) _ = flagSet.MarkDeprecated(typeDeprecatedFlagName, fmt.Sprintf("Use --%s instead", typeFlagName)) _ = flagSet.MarkHidden(typeDeprecatedFlagName) } @@ -293,33 +306,26 @@ func run( // in the context of including imports. return appcmd.NewInvalidArgumentErrorf("Cannot set --%s without --%s", includeWKTFlagName, includeImportsFlagName) } - if err := bufcli.ValidateErrorFormatFlag(flags.ErrorFormat, errorFormatFlagName); err != nil { - return err - } - input, err := bufcli.GetInputValue(container, flags.InputHashtag, ".") + input, err := bufcli.GetInputValue(container, flags.InputHashtag, "") if err != nil { return err } - ref, err := buffetch.NewRefParser(container.Logger()).GetRef(ctx, input) - if err != nil { - return err + var storageosProvider storageos.Provider + if flags.DisableSymlinks { + storageosProvider = storageos.NewProvider() + } else { + storageosProvider = storageos.NewProvider(storageos.ProviderWithSymlinks()) } - storageosProvider := bufcli.NewStorageosProvider(flags.DisableSymlinks) - runner := command.NewRunner() - readWriteBucket, err := storageosProvider.NewReadWriteBucket( - ".", - storageos.ReadWriteBucketWithSymlinksIfSupported(), + controller, err := bufcli.NewController( + container, + bufctl.WithDisableSymlinks(flags.DisableSymlinks), + bufctl.WithFileAnnotationErrorFormat(flags.ErrorFormat), ) if err != nil { return err } - genConfig, err := bufgen.ReadConfig( - ctx, - logger, - bufgen.NewProvider(logger), - readWriteBucket, - bufgen.ReadConfigWithOverride(flags.Template), - ) + wasmPluginExecutor, err := bufwasm.NewPluginExecutor( + filepath.Join(container.CacheDirPath(), bufcli.WASMCompilationCacheDir)) if err != nil { return err } @@ -327,42 +333,86 @@ func run( if err != nil { return err } - imageConfigReader, err := bufcli.NewWireImageConfigReader( - container, - storageosProvider, - runner, - clientConfig, - ) - if err != nil { - return err + var bufGenYAMLFile bufconfig.BufGenYAMLFile + templatePathExtension := filepath.Ext(flags.Template) + switch { + case flags.Template == "": + bucket, err := storageosProvider.NewReadWriteBucket(".", storageos.ReadWriteBucketWithSymlinksIfSupported()) + if err != nil { + return err + } + bufGenYAMLFile, err = bufconfig.GetBufGenYAMLFileForPrefix(ctx, bucket, ".") + if err != nil { + return err + } + if flags.Migrate && bufGenYAMLFile.FileVersion() != bufconfig.FileVersionV2 { + migratedBufGenYAMLFile, err := getBufGenYAMLFileWithFlagEquivalence( + ctx, + logger, + bufGenYAMLFile, + input, + *flags, + ) + if err != nil { + return err + } + if err := bufconfig.PutBufGenYAMLFileForPrefix(ctx, bucket, ".", migratedBufGenYAMLFile); err != nil { + return err + } + // TODO: perhaps print a message + } + case templatePathExtension == ".yaml" || templatePathExtension == ".yml" || templatePathExtension == ".json": + // We should not read from a bucket at "." because this path can jump context. + configFile, err := os.Open(flags.Template) + if err != nil { + return err + } + bufGenYAMLFile, err = bufconfig.ReadBufGenYAMLFile(configFile) + if err != nil { + return err + } + if flags.Migrate && bufGenYAMLFile.FileVersion() != bufconfig.FileVersionV2 { + migratedBufGenYAMLFile, err := getBufGenYAMLFileWithFlagEquivalence( + ctx, + logger, + bufGenYAMLFile, + input, + *flags, + ) + if err != nil { + return err + } + if err := bufconfig.WriteBufGenYAMLFile(configFile, migratedBufGenYAMLFile); err != nil { + return err + } + // TODO: perhaps print a message + } + default: + bufGenYAMLFile, err = bufconfig.ReadBufGenYAMLFile(strings.NewReader(flags.Template)) + if err != nil { + return err + } + if flags.Migrate && bufGenYAMLFile.FileVersion() != bufconfig.FileVersionV2 { + return fmt.Errorf( + "invalid template: %q, migration can only apply to a file on disk with extension .yaml, .yml or .json", + flags.Template, + ) + } } - imageConfigs, fileAnnotations, err := imageConfigReader.GetImageConfigs( + images, err := getInputImages( ctx, - container, - ref, + logger, + controller, + input, + bufGenYAMLFile, flags.Config, - flags.Paths, // we filter on files - flags.ExcludePaths, // we exclude these paths - false, // input files must exist - false, // we must include source info for generation + flags.Paths, + flags.ExcludePaths, + flags.Types, ) if err != nil { return err } - if len(fileAnnotations) > 0 { - if err := bufanalysis.PrintFileAnnotations(container.Stderr(), fileAnnotations, flags.ErrorFormat); err != nil { - return err - } - return bufctl.ErrFileAnnotation - } - images := make([]bufimage.Image, 0, len(imageConfigs)) - for _, imageConfig := range imageConfigs { - images = append(images, imageConfig.Image()) - } - image, err := bufimage.MergeImages(images...) - if err != nil { - return err - } generateOptions := []bufgen.GenerateOption{ bufgen.GenerateWithBaseOutDirPath(flags.BaseOutDirPath), } @@ -388,35 +438,153 @@ func run( bufgen.GenerateWithWASMEnabled(), ) } - var includedTypes []string - if len(flags.Types) > 0 || len(flags.TypesDeprecated) > 0 { - // command-line flags take precedence - includedTypes = append(flags.Types, flags.TypesDeprecated...) - } else if genConfig.TypesConfig != nil { - includedTypes = genConfig.TypesConfig.Include - } - if len(includedTypes) > 0 { - image, err = bufimageutil.ImageFilteredByTypes(image, includedTypes...) - if err != nil { - return err - } - } - wasmPluginExecutor, err := bufwasm.NewPluginExecutor( - filepath.Join(container.CacheDirPath(), bufcli.WASMCompilationCacheDir)) - if err != nil { - return err - } return bufgen.NewGenerator( logger, + tracing.NewTracer(container.Tracer()), storageosProvider, - runner, + command.NewRunner(), wasmPluginExecutor, clientConfig, ).Generate( ctx, container, - genConfig, - image, + bufGenYAMLFile.GenerateConfig(), + images, generateOptions..., ) } + +func getInputImages( + ctx context.Context, + logger *zap.Logger, + controller bufctl.Controller, + inputSpecified string, + bufGenYAMLFile bufconfig.BufGenYAMLFile, + moduleConfigOverride string, + includePathsOverride []string, + excludePathsOverride []string, + includeTypesOverride []string, +) ([]bufimage.Image, error) { + var inputImages []bufimage.Image + // If input is specified on the command line, we use that. If input is not + // specified on the command line, but the config has no inputs, use the default input. + if inputSpecified != "" || len(bufGenYAMLFile.InputConfigs()) == 0 { + input := "." + if inputSpecified != "" { + input = inputSpecified + } + var includeTypes []string + if typesConfig := bufGenYAMLFile.GenerateConfig().GenerateTypeConfig(); typesConfig != nil { + includeTypes = typesConfig.IncludeTypes() + } + if len(includeTypesOverride) > 0 { + includeTypes = includeTypesOverride + } + inputImage, err := controller.GetImage( + ctx, + input, + bufctl.WithConfigOverride(moduleConfigOverride), + bufctl.WithTargetPaths(includePathsOverride, excludePathsOverride), + bufctl.WithImageTypes(includeTypes), + ) + if err != nil { + return nil, err + } + inputImages = []bufimage.Image{inputImage} + } else { + for _, inputConfig := range bufGenYAMLFile.InputConfigs() { + includePaths := inputConfig.IncludePaths() + if len(includePathsOverride) > 0 { + includePaths = includePathsOverride + } + excludePaths := inputConfig.ExcludePaths() + if len(excludePathsOverride) > 0 { + excludePaths = excludePathsOverride + } + // In V2 we do not need to look at generateTypeConfig.IncludeTypes() + // because it is always nil. + // TODO: document the above in godoc + includeTypes := inputConfig.IncludeTypes() + if len(includeTypesOverride) > 0 { + includeTypes = includeTypesOverride + } + inputImage, err := controller.GetImageForInputConfig( + ctx, + inputConfig, + bufctl.WithConfigOverride(moduleConfigOverride), + bufctl.WithTargetPaths(includePaths, excludePaths), + bufctl.WithImageTypes(includeTypes), + ) + if err != nil { + return nil, err + } + inputImages = append(inputImages, inputImage) + } + } + return inputImages, nil +} + +// getBufGenYAMLFileWithFlagEquivalence returns a buf gen yaml file the same as +// the given one, except that it is overriden by flags. +// This is called only for migration. Input will be used regardless if it's empty. +func getBufGenYAMLFileWithFlagEquivalence( + ctx context.Context, + logger *zap.Logger, + bufGenYAMLFile bufconfig.BufGenYAMLFile, + input string, + flags flags, +) (bufconfig.BufGenYAMLFile, error) { + if input == "" { + input = "." + } + inputConfig, err := buffetch.GetInputConfigForString( + ctx, + buffetch.NewRefParser(logger), + input, + ) + if err != nil { + return nil, err + } + var includeTypes []string + if bufGenYAMLFile.GenerateConfig().GenerateTypeConfig() != nil { + includeTypes = bufGenYAMLFile.GenerateConfig().GenerateTypeConfig().IncludeTypes() + } + if len(flags.Types) > 0 { + includeTypes = flags.Types + } + inputConfig, err = bufconfig.NewInputConfigWithTargets( + inputConfig, + flags.Paths, + flags.ExcludePaths, + includeTypes, + ) + if err != nil { + return nil, err + } + pluginConfigs, err := slicesext.MapError( + bufGenYAMLFile.GenerateConfig().GeneratePluginConfigs(), + func(pluginConfig bufconfig.GeneratePluginConfig) (bufconfig.GeneratePluginConfig, error) { + return bufconfig.NewGeneratePluginWithIncludeImportsAndWKT( + pluginConfig, + flags.IncludeImports, + flags.IncludeWKT, + ) + }, + ) + if err != nil { + return nil, err + } + generateConfig, err := bufconfig.NewGenerateConfig( + pluginConfigs, + bufGenYAMLFile.GenerateConfig().GenerateManagedConfig(), + nil, + ) + if err != nil { + return nil, err + } + return bufconfig.NewBufGenYAMLFile( + bufconfig.FileVersionV2, + generateConfig, + []bufconfig.InputConfig{inputConfig}, + ), nil +} diff --git a/private/buf/cmd/buf/command/generate/generate_test.go b/private/buf/cmd/buf/command/generate/generate_test.go index 070a6af2ea..7771e7c87f 100644 --- a/private/buf/cmd/buf/command/generate/generate_test.go +++ b/private/buf/cmd/buf/command/generate/generate_test.go @@ -24,7 +24,6 @@ import ( "path/filepath" "testing" - "github.com/bufbuild/buf/private/buf/bufgen" "github.com/bufbuild/buf/private/buf/buftesting" "github.com/bufbuild/buf/private/buf/cmd/buf/internal/internaltesting" "github.com/bufbuild/buf/private/pkg/app/appcmd" @@ -152,6 +151,8 @@ func TestOutputFlag(t *testing.T) { } func TestProtoFileRefIncludePackageFiles(t *testing.T) { + // TODO: un-skip this once proto file ref is implemented + t.Skip() t.Parallel() tempDirPath := t.TempDir() testRunSuccess( @@ -186,6 +187,8 @@ func TestGenerateDuplicatePlugins(t *testing.T) { } func TestOutputWithPathEqualToExclude(t *testing.T) { + // TODO: un-skip this once --path and --exclude-path are updated + t.Skip() t.Parallel() tempDirPath := t.TempDir() testRunStdoutStderr( @@ -517,7 +520,23 @@ func testRunStdoutStderr(t *testing.T, stdin io.Reader, expectedExitCode int, ex func(name string) *appcmd.Command { return NewCommand( name, - appext.NewBuilder(name), + appext.NewBuilder( + name, + appext.BuilderWithInterceptor( + // TODO: use the real interceptor. Currently in buf.go, NewBuilder receives appflag.BuilderWithInterceptor(newErrorInterceptor()). + // However we cannot depend on newErrorInterceptor because it would create an import cycle, not to mention it needs to be exported first. + // This can depend on newErroInterceptor when it's moved to a separate package and made public. + func(next func(context.Context, appext.Container) error) func(context.Context, appext.Container) error { + return func(ctx context.Context, container appext.Container) error { + err := next(ctx, container) + if err == nil { + return nil + } + return fmt.Errorf("Failure: %w", err) + } + }, + ), + ), ) }, expectedExitCode, @@ -530,19 +549,20 @@ func testRunStdoutStderr(t *testing.T, stdin io.Reader, expectedExitCode int, ex } func newExternalConfigV1String(t *testing.T, plugins []*testPluginInfo, out string) string { - externalConfig := bufgen.ExternalConfigV1{ - Version: "v1", - } + externalConfig := make(map[string]interface{}) + externalConfig["version"] = "v1" + pluginConfigs := []map[string]string{} for _, plugin := range plugins { - externalConfig.Plugins = append( - externalConfig.Plugins, - bufgen.ExternalPluginConfigV1{ - Name: plugin.name, - Out: out, - Opt: plugin.opt, + pluginConfigs = append( + pluginConfigs, + map[string]string{ + "name": plugin.name, + "opt": plugin.opt, + "out": out, }, ) } + externalConfig["plugins"] = pluginConfigs data, err := json.Marshal(externalConfig) require.NoError(t, err) return string(data) diff --git a/private/buf/cmd/buf/command/generate/generate_unix_test.go b/private/buf/cmd/buf/command/generate/generate_unix_test.go index aef2ea35da..58108c7606 100644 --- a/private/buf/cmd/buf/command/generate/generate_unix_test.go +++ b/private/buf/cmd/buf/command/generate/generate_unix_test.go @@ -26,6 +26,8 @@ import ( ) func TestProtoFileRef(t *testing.T) { + // TODO: un-skip this once --path and --exclude-path are updated + t.Skip() t.Parallel() tempDirPath := t.TempDir() testRunSuccess( @@ -77,6 +79,8 @@ func TestOutputWithExclude(t *testing.T) { } func TestOutputWithPathWithinExclude(t *testing.T) { + // TODO: un-skip this once --path and --exclude-path are updated + t.Skip() t.Parallel() tempDirPath := t.TempDir() testRunSuccess( @@ -124,6 +128,8 @@ func TestOutputWithExcludeWithinPath(t *testing.T) { } func TestOutputWithNestedExcludeAndTargetPaths(t *testing.T) { + // TODO: un-skip this once --path and --exclude-path are updated + t.Skip() t.Parallel() tempDirPath := t.TempDir() testRunSuccess( @@ -160,6 +166,8 @@ func TestOutputWithNestedExcludeAndTargetPaths(t *testing.T) { } func TestWorkspaceGenerateWithExcludeAndTargetPaths(t *testing.T) { + // TODO: un-skip this once --path and --exclude-path are updated + t.Skip() t.Parallel() tempDirPath := t.TempDir() testRunSuccess( diff --git a/private/bufpkg/bufconfig/buf_gen_yaml_file.go b/private/bufpkg/bufconfig/buf_gen_yaml_file.go index c27333a643..97608eb186 100644 --- a/private/bufpkg/bufconfig/buf_gen_yaml_file.go +++ b/private/bufpkg/bufconfig/buf_gen_yaml_file.go @@ -16,9 +16,12 @@ package bufconfig import ( "context" - "errors" + "encoding/json" + "fmt" "io" + "github.com/bufbuild/buf/private/pkg/encoding" + "github.com/bufbuild/buf/private/pkg/slicesext" "github.com/bufbuild/buf/private/pkg/storage" "github.com/bufbuild/buf/private/pkg/syserror" ) @@ -33,12 +36,29 @@ var ( // For v2, generation configuration has been merged into BufYAMLFiles. type BufGenYAMLFile interface { File - // Will always have empty GenerateInputConfigs. - GenerateConfig + + // GenerateConfig returns the generate config. + GenerateConfig() GenerateConfig + // InputConfigs returns the input configs, which can be empty. + InputConfigs() []InputConfig isBufGenYAMLFile() } +// NewBufGenYAMLFile returns a new BufGenYAMLFile. It is validated given each +// parameter is validated. +func NewBufGenYAMLFile( + version FileVersion, + generateConfig GenerateConfig, + inputConfigs []InputConfig, +) BufGenYAMLFile { + return newBufGenYAMLFile( + version, + generateConfig, + inputConfigs, + ) +} + // GetBufGenYAMLFileForPrefix gets the buf.gen.yaml file at the given bucket prefix. // // The buf.gen.yaml file will be attempted to be read at prefix/buf.gen.yaml. @@ -87,22 +107,36 @@ func WriteBufGenYAMLFile(writer io.Writer, bufGenYAMLFile BufGenYAMLFile) error // *** PRIVATE *** type bufGenYAMLFile struct { - GenerateConfig + generateConfig GenerateConfig + inputConfigs []InputConfig fileVersion FileVersion } -func newBufGenYAMLFile(fileVersion FileVersion, generateConfig GenerateConfig) (*bufGenYAMLFile, error) { +func newBufGenYAMLFile( + fileVersion FileVersion, + generateConfig GenerateConfig, + inputConfigs []InputConfig, +) *bufGenYAMLFile { return &bufGenYAMLFile{ - GenerateConfig: generateConfig, fileVersion: fileVersion, - }, errors.New("TODO") + generateConfig: generateConfig, + inputConfigs: inputConfigs, + } } func (g *bufGenYAMLFile) FileVersion() FileVersion { return g.fileVersion } +func (g *bufGenYAMLFile) GenerateConfig() GenerateConfig { + return g.generateConfig +} + +func (g *bufGenYAMLFile) InputConfigs() []InputConfig { + return g.inputConfigs +} + func (*bufGenYAMLFile) isBufGenYAMLFile() {} func (*bufGenYAMLFile) isFile() {} @@ -118,11 +152,54 @@ func readBufGenYAMLFile(reader io.Reader, allowJSON bool) (BufGenYAMLFile, error } switch fileVersion { case FileVersionV1Beta1: - return nil, errors.New("TODO") + var externalGenYAMLFile externalBufGenYAMLFileV1Beta1 + if err := getUnmarshalStrict(allowJSON)(data, &externalGenYAMLFile); err != nil { + return nil, fmt.Errorf("invalid as version %v: %w", fileVersion, err) + } + generateConfig, err := newGenerateConfigFromExternalFileV1Beta1(externalGenYAMLFile) + if err != nil { + return nil, err + } + return newBufGenYAMLFile( + fileVersion, + generateConfig, + nil, + ), nil case FileVersionV1: - return nil, errors.New("TODO") + var externalGenYAMLFile externalBufGenYAMLFileV1 + if err := getUnmarshalStrict(allowJSON)(data, &externalGenYAMLFile); err != nil { + return nil, fmt.Errorf("invalid as version %v: %w", fileVersion, err) + } + generateConfig, err := newGenerateConfigFromExternalFileV1(externalGenYAMLFile) + if err != nil { + return nil, err + } + return newBufGenYAMLFile( + fileVersion, + generateConfig, + nil, + ), nil case FileVersionV2: - return nil, errors.New("TODO") + var externalGenYAMLFile externalBufGenYAMLFileV2 + if err := getUnmarshalStrict(allowJSON)(data, &externalGenYAMLFile); err != nil { + return nil, fmt.Errorf("invalid as version %v: %w", fileVersion, err) + } + generateConfig, err := newGenerateConfigFromExternalFileV2(externalGenYAMLFile) + if err != nil { + return nil, err + } + inputConfigs, err := slicesext.MapError( + externalGenYAMLFile.Inputs, + newInputConfigFromExternalV2, + ) + if err != nil { + return nil, err + } + return newBufGenYAMLFile( + fileVersion, + generateConfig, + inputConfigs, + ), nil default: // This is a system error since we've already parsed. return nil, syserror.Newf("unknown FileVersion: %v", fileVersion) @@ -130,15 +207,338 @@ func readBufGenYAMLFile(reader io.Reader, allowJSON bool) (BufGenYAMLFile, error } func writeBufGenYAMLFile(writer io.Writer, bufGenYAMLFile BufGenYAMLFile) error { - switch fileVersion := bufGenYAMLFile.FileVersion(); fileVersion { - case FileVersionV1Beta1: - return errors.New("TODO") - case FileVersionV1: - return errors.New("TODO") - case FileVersionV2: - return errors.New("TODO") - default: - // This is a system error since we've already parsed. - return syserror.Newf("unknown FileVersion: %v", fileVersion) + // Regardless of version, we write the file as v2: + externalPluginConfigsV2, err := slicesext.MapError( + bufGenYAMLFile.GenerateConfig().GeneratePluginConfigs(), + newExternalGeneratePluginConfigV2FromPluginConfig, + ) + if err != nil { + return err + } + externalManagedConfigV2 := newExternalManagedConfigV2FromGenerateManagedConfig( + bufGenYAMLFile.GenerateConfig().GenerateManagedConfig(), + ) + externalInputConfigsV2, err := slicesext.MapError( + bufGenYAMLFile.InputConfigs(), + newExternalInputConfigV2FromInputConfig, + ) + if err != nil { + return err + } + externalBufGenYAMLFileV2 := externalBufGenYAMLFileV2{ + Version: FileVersionV2.String(), + Plugins: externalPluginConfigsV2, + Managed: externalManagedConfigV2, + Inputs: externalInputConfigsV2, + } + data, err := encoding.MarshalYAML(&externalBufGenYAMLFileV2) + if err != nil { + return err + } + _, err = writer.Write(data) + return err +} + +// externalBufGenYAMLFileV1Beta1 represents the v1beta buf.gen.yaml file. +type externalBufGenYAMLFileV1Beta1 struct { + Version string `json:"version,omitempty" yaml:"version,omitempty"` + // Managed is whether managed mode is enabled. + Managed bool `json:"managed,omitempty" yaml:"managed,omitempty"` + Plugins []externalGeneratePluginConfigV1Beta1 `json:"plugins,omitempty" yaml:"plugins,omitempty"` + Options externalGenerateManagedConfigV1Beta1 `json:"options,omitempty" yaml:"options,omitempty"` +} + +// externalGeneratePluginConfigV1Beta1 represents a single plugin conifg in a v1beta1 buf.gen.yaml file. +type externalGeneratePluginConfigV1Beta1 struct { + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Out string `json:"out,omitempty" yaml:"out,omitempty"` + Opt interface{} `json:"opt,omitempty" yaml:"opt,omitempty"` + Path string `json:"path,omitempty" yaml:"path,omitempty"` + Strategy string `json:"strategy,omitempty" yaml:"strategy,omitempty"` +} + +// externalGenerateManagedConfigV1Beta1 represents the options (for managed mode) config in a v1beta1 buf.gen.yaml file. +type externalGenerateManagedConfigV1Beta1 struct { + CcEnableArenas *bool `json:"cc_enable_arenas,omitempty" yaml:"cc_enable_arenas,omitempty"` + JavaMultipleFiles *bool `json:"java_multiple_files,omitempty" yaml:"java_multiple_files,omitempty"` + OptimizeFor string `json:"optimize_for,omitempty" yaml:"optimize_for,omitempty"` +} + +// externalBufGenYAMLFileV1 represents the v1 buf.gen.yaml file. +type externalBufGenYAMLFileV1 struct { + Version string `json:"version,omitempty" yaml:"version,omitempty"` + Plugins []externalGeneratePluginConfigV1 `json:"plugins,omitempty" yaml:"plugins,omitempty"` + Managed externalGenerateManagedConfigV1 `json:"managed,omitempty" yaml:"managed,omitempty"` + Types externalTypesConfigV1 `json:"types,omitempty" yaml:"types,omitempty"` +} + +// externalGeneratePluginConfigV1 represents a single plugin config in a v1 buf.gen.yaml file. +type externalGeneratePluginConfigV1 struct { + // Exactly one of Plugin and Name is required. + // Plugin is the key for a local or remote plugin. + Plugin string `json:"plugin,omitempty" yaml:"plugin,omitempty"` + // Name is the key for a local plugin. + Name string `json:"name,omitempty" yaml:"name,omitempty"` + // Remote is the key for alpha remote plugin name, which is deprecated now. + Remote string `json:"remote,omitempty" yaml:"remote,omitempty"` + // Out is required. + Out string `json:"out,omitempty" yaml:"out,omitempty"` + Revision int `json:"revision,omitempty" yaml:"revision,omitempty"` + // Opt can be one string or multiple strings. + Opt interface{} `json:"opt,omitempty" yaml:"opt,omitempty"` + // Path can be one string or multiple strings. + Path interface{} `json:"path,omitempty" yaml:"path,omitempty"` + ProtocPath string `json:"protoc_path,omitempty" yaml:"protoc_path,omitempty"` + Strategy string `json:"strategy,omitempty" yaml:"strategy,omitempty"` +} + +// externalGenerateManagedConfigV1 represents the managed mode config in a v1 buf.gen.yaml file. +type externalGenerateManagedConfigV1 struct { + Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + CcEnableArenas *bool `json:"cc_enable_arenas,omitempty" yaml:"cc_enable_arenas,omitempty"` + JavaMultipleFiles *bool `json:"java_multiple_files,omitempty" yaml:"java_multiple_files,omitempty"` + JavaStringCheckUtf8 *bool `json:"java_string_check_utf8,omitempty" yaml:"java_string_check_utf8,omitempty"` + JavaPackagePrefix externalJavaPackagePrefixConfigV1 `json:"java_package_prefix,omitempty" yaml:"java_package_prefix,omitempty"` + CsharpNamespace externalCsharpNamespaceConfigV1 `json:"csharp_namespace,omitempty" yaml:"csharp_namespace,omitempty"` + OptimizeFor externalOptimizeForConfigV1 `json:"optimize_for,omitempty" yaml:"optimize_for,omitempty"` + GoPackagePrefix externalGoPackagePrefixConfigV1 `json:"go_package_prefix,omitempty" yaml:"go_package_prefix,omitempty"` + ObjcClassPrefix externalObjcClassPrefixConfigV1 `json:"objc_class_prefix,omitempty" yaml:"objc_class_prefix,omitempty"` + RubyPackage externalRubyPackageConfigV1 `json:"ruby_package,omitempty" yaml:"ruby_package,omitempty"` + // Override maps from a file option to a file path then to the value. + Override map[string]map[string]string `json:"override,omitempty" yaml:"override,omitempty"` +} + +// externalJavaPackagePrefixConfigV1 represents the java_package_prefix config in a v1 buf.gen.yaml file. +type externalJavaPackagePrefixConfigV1 struct { + Default string `json:"default,omitempty" yaml:"default,omitempty"` + Except []string `json:"except,omitempty" yaml:"except,omitempty"` + Override map[string]string `json:"override,omitempty" yaml:"override,omitempty"` +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. This is done to maintain backward compatibility +// of accepting a plain string value for java_package_prefix. +func (e *externalJavaPackagePrefixConfigV1) UnmarshalYAML(unmarshal func(interface{}) error) error { + return e.unmarshalWith(unmarshal) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. This is done to maintain backward compatibility +// of accepting a plain string value for java_package_prefix. +func (e *externalJavaPackagePrefixConfigV1) UnmarshalJSON(data []byte) error { + unmarshal := func(v interface{}) error { + return json.Unmarshal(data, v) } + return e.unmarshalWith(unmarshal) +} + +// unmarshalWith is used to unmarshal into json/yaml. See https://abhinavg.net/posts/flexible-yaml for details. +func (e *externalJavaPackagePrefixConfigV1) unmarshalWith(unmarshal func(interface{}) error) error { + var prefix string + if err := unmarshal(&prefix); err == nil { + e.Default = prefix + return nil + } + type rawExternalJavaPackagePrefixConfigV1 externalJavaPackagePrefixConfigV1 + if err := unmarshal((*rawExternalJavaPackagePrefixConfigV1)(e)); err != nil { + return err + } + return nil +} + +// isEmpty returns true if the config is empty. +func (e externalJavaPackagePrefixConfigV1) isEmpty() bool { + return e.Default == "" && + len(e.Except) == 0 && + len(e.Override) == 0 +} + +// externalOptimizeForConfigV1 represents the optimize_for config in a v1 buf.gen.yaml file. +type externalOptimizeForConfigV1 struct { + Default string `json:"default,omitempty" yaml:"default,omitempty"` + Except []string `json:"except,omitempty" yaml:"except,omitempty"` + Override map[string]string `json:"override,omitempty" yaml:"override,omitempty"` +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. This is done to maintain backward compatibility +// of accepting a plain string value for optimize_for. +func (e *externalOptimizeForConfigV1) UnmarshalYAML(unmarshal func(interface{}) error) error { + return e.unmarshalWith(unmarshal) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. This is done to maintain backward compatibility +// of accepting a plain string value for optimize_for. +func (e *externalOptimizeForConfigV1) UnmarshalJSON(data []byte) error { + unmarshal := func(v interface{}) error { + return json.Unmarshal(data, v) + } + return e.unmarshalWith(unmarshal) +} + +// unmarshalWith is used to unmarshal into json/yaml. See https://abhinavg.net/posts/flexible-yaml for details. +func (e *externalOptimizeForConfigV1) unmarshalWith(unmarshal func(interface{}) error) error { + var optimizeFor string + if err := unmarshal(&optimizeFor); err == nil { + e.Default = optimizeFor + return nil + } + type rawExternalOptimizeForConfigV1 externalOptimizeForConfigV1 + if err := unmarshal((*rawExternalOptimizeForConfigV1)(e)); err != nil { + return err + } + return nil +} + +// isEmpty returns true if the config is empty +func (e externalOptimizeForConfigV1) isEmpty() bool { // TODO: does it need to be public? + return e.Default == "" && + len(e.Except) == 0 && + len(e.Override) == 0 +} + +// externalGoPackagePrefixConfigV1 represents the go_package_prefix config in a v1 buf.gen.yaml file. +type externalGoPackagePrefixConfigV1 struct { + Default string `json:"default,omitempty" yaml:"default,omitempty"` + Except []string `json:"except,omitempty" yaml:"except,omitempty"` + Override map[string]string `json:"override,omitempty" yaml:"override,omitempty"` +} + +// isEmpty returns true if the config is empty. +func (e externalGoPackagePrefixConfigV1) isEmpty() bool { + return e.Default == "" && + len(e.Except) == 0 && + len(e.Override) == 0 +} + +// externalCsharpNamespaceConfigV1 represents the external csharp_namespace config in a v1 buf.gen.yaml file. +type externalCsharpNamespaceConfigV1 struct { + Except []string `json:"except,omitempty" yaml:"except,omitempty"` + Override map[string]string `json:"override,omitempty" yaml:"override,omitempty"` +} + +// isEmpty returns true if the config is empty. +func (e externalCsharpNamespaceConfigV1) isEmpty() bool { + return len(e.Except) == 0 && + len(e.Override) == 0 +} + +// externalRubyPackageConfigV1 represents the ruby_package config in a v1 buf.gen.yaml file. +type externalRubyPackageConfigV1 struct { + Except []string `json:"except,omitempty" yaml:"except,omitempty"` + Override map[string]string `json:"override,omitempty" yaml:"override,omitempty"` +} + +// isEmpty returns true is the config is empty. +func (e externalRubyPackageConfigV1) isEmpty() bool { // TODO: does this need to be public? same with other IsEmpty() + return len(e.Except) == 0 && len(e.Override) == 0 +} + +// externalObjcClassPrefixConfigV1 represents the objc_class_prefix config in a v1 buf.gen.yaml file. +type externalObjcClassPrefixConfigV1 struct { + Default string `json:"default,omitempty" yaml:"default,omitempty"` + Except []string `json:"except,omitempty" yaml:"except,omitempty"` + Override map[string]string `json:"override,omitempty" yaml:"override,omitempty"` +} + +// isEmpty returns true is the config is empty. +func (e externalObjcClassPrefixConfigV1) isEmpty() bool { + return e.Default == "" && + len(e.Except) == 0 && + len(e.Override) == 0 +} + +// externalTypesConfigV1 represents the types config in a v1 buf.gen.yaml file. +type externalTypesConfigV1 struct { + Include []string `json:"include,omitempty" yaml:"include"` +} + +// externalBufGenYAMLFileV2 represents the v2 buf.gen.yaml file. +type externalBufGenYAMLFileV2 struct { + Version string `json:"version,omitempty" yaml:"version,omitempty"` + Plugins []externalGeneratePluginConfigV2 `json:"plugins,omitempty" yaml:"plugins,omitempty"` + Managed externalGenerateManagedConfigV2 `json:"managed,omitempty" yaml:"managed,omitempty"` + Inputs []externalInputConfigV2 `json:"inputs,omitempty" yaml:"inputs,omitempty"` +} + +// externalGeneratePluginConfigV2 represents a single plugin config in a v2 buf.gen.yaml file. +type externalGeneratePluginConfigV2 struct { + // Exactly one of Remote, Binary and ProtocBuiltin is required. + Remote *string `json:"remote,omitempty" yaml:"remote,omitempty"` + // Binary is the binary path, which can be one string or multiple strings. + Binary interface{} `json:"binary,omitempty" yaml:"binary,omitempty"` + // ProtocBuiltin is the protoc built-in plugin name, in the form of 'java' instead of 'protoc-gen-java'. + ProtocBuiltin *string `json:"protoc_builtin,omitempty" yaml:"protoc_builtin,omitempty"` + // Out is required. + Out string `json:"out,omitempty" yaml:"out,omitempty"` + // Revision is only valid with Remote set. + Revision *int `json:"revision,omitempty" yaml:"revision,omitempty"` + // ProtocPath is only valid with ProtocBuiltin + ProtocPath *string `json:"protoc_path,omitempty" yaml:"protoc_path,omitempty"` + // Opt can be one string or multiple strings. + Opt interface{} `json:"opt,omitempty" yaml:"opt,omitempty"` + IncludeImports bool `json:"include_imports,omitempty" yaml:"include_imports,omitempty"` + IncludeWKT bool `json:"include_wkt,omitempty" yaml:"include_wkt,omitempty"` + // Strategy 5s only valid with ProtoBuiltin and Binary + Strategy *string `json:"strategy,omitempty" yaml:"strategy,omitempty"` +} + +// externalGenerateManagedConfigV2 represents the managed mode config in a v2 buf.gen.yaml file. +type externalGenerateManagedConfigV2 struct { + Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + Disable []externalManagedDisableConfigV2 `json:"disable,omitempty" yaml:"disable,omitempty"` + Override []externalManagedOverrideConfigV2 `json:"override,omitempty" yaml:"override,omitempty"` +} + +// externalManagedDisableConfigV2 represents a disable rule in managed mode in a v2 buf.gen.yaml file. +type externalManagedDisableConfigV2 struct { + // At least one field must be set. + // At most one of FileOption and FieldOption can be set + FileOption string `json:"file_option,omitempty" yaml:"file_option,omitempty"` + FieldOption string `json:"field_option,omitempty" yaml:"field_option,omitempty"` + Module string `json:"module,omitempty" yaml:"module,omitempty"` + // Path must be normalized. + Path string `json:"path,omitempty" yaml:"path,omitempty"` + // Field must not be set if FileOption is set. + Field string `json:"field,omitempty" yaml:"field,omitempty"` +} + +// externalManagedOverrideConfigV2 represents an override rule in managed mode in a v2 buf.gen.yaml file. +type externalManagedOverrideConfigV2 struct { + // Exactly one of FileOpion and FieldOption must be set. + FileOption string `json:"file_option,omitempty" yaml:"file_option,omitempty"` + FieldOption string `json:"field_option,omitempty" yaml:"field_option,omitempty"` + Module string `json:"module,omitempty" yaml:"module,omitempty"` + // Path must be normalized. + Path string `json:"path,omitempty" yaml:"path,omitempty"` + // Field must not be set if FileOption is set. + Field string `json:"field,omitempty" yaml:"field,omitempty"` + // Value is required + Value interface{} `json:"value,omitempty" yaml:"value,omitempty"` +} + +// externalInputConfigV2 is an external input configuration. +type externalInputConfigV2 struct { + // One and only one of Module, Directory, ProtoFile, Tarball, ZipArchive, BinaryImage, + // JSONImage and GitRepo must be specified as the format. + Module *string `json:"module,omitempty" yaml:"module,omitempty"` + Directory *string `json:"directory,omitempty" yaml:"directory,omitempty"` + ProtoFile *string `json:"proto_file,omitempty" yaml:"proto_file,omitempty"` + Tarball *string `json:"tarball,omitempty" yaml:"tarball,omitempty"` + ZipArchive *string `json:"zip_archive,omitempty" yaml:"zip_archive,omitempty"` + BinaryImage *string `json:"binary_image,omitempty" yaml:"binary_image,omitempty"` + JSONImage *string `json:"json_image,omitempty" yaml:"json_image,omitempty"` + TextImage *string `json:"text_image,omitempty" yaml:"text_image,omitempty"` + GitRepo *string `json:"git_repo,omitempty" yaml:"git_repo,omitempty"` + // Types, IncludePaths and ExcludePaths are available for all formats. + Types []string `json:"types,omitempty" yaml:"types,omitempty"` + IncludePaths []string `json:"include_paths,omitempty" yaml:"include_paths,omitempty"` + ExcludePaths []string `json:"exclude_paths,omitempty" yaml:"exclude_paths,omitempty"` + // The following options are available depending on input format. + Compression *string `json:"compression,omitempty" yaml:"compression,omitempty"` + StripComponents *uint32 `json:"strip_components,omitempty" yaml:"strip_components,omitempty"` + Subdir *string `json:"subdir,omitempty" yaml:"subdir,omitempty"` + Branch *string `json:"branch,omitempty" yaml:"branch,omitempty"` + Tag *string `json:"tag,omitempty" yaml:"tag,omitempty"` + Ref *string `json:"ref,omitempty" yaml:"ref,omitempty"` + Depth *uint32 `json:"depth,omitempty" yaml:"depth,omitempty"` + RecurseSubmodules *bool `json:"recurse_submodules,omitempty" yaml:"recurse_submodules,omitempty"` + IncludePackageFiles *bool `json:"include_package_files,omitempty" yaml:"include_package_files,omitempty"` } diff --git a/private/bufpkg/bufconfig/buf_work_yaml_file.go b/private/bufpkg/bufconfig/buf_work_yaml_file.go index 5c583010e0..009a677459 100644 --- a/private/bufpkg/bufconfig/buf_work_yaml_file.go +++ b/private/bufpkg/bufconfig/buf_work_yaml_file.go @@ -178,7 +178,7 @@ func writeBufWorkYAMLFile(writer io.Writer, bufWorkYAMLFile BufWorkYAMLFile) err if err != nil { return err } - _, err = writer.Write(append(bufLockFileHeader, data...)) + _, err = writer.Write(data) return err } diff --git a/private/bufpkg/bufconfig/generate_config.go b/private/bufpkg/bufconfig/generate_config.go index 4b5bd15667..e3cd35c7b7 100644 --- a/private/bufpkg/bufconfig/generate_config.go +++ b/private/bufpkg/bufconfig/generate_config.go @@ -14,57 +14,133 @@ package bufconfig +import ( + "errors" + + "github.com/bufbuild/buf/private/pkg/slicesext" +) + // GenerateConfig is a generation configuration. -// -// TODO type GenerateConfig interface { + // GeneratePluginConfigs returns the plugin configurations. This will always be + // non-empty. Zero plugin configs will cause an error at construction time. GeneratePluginConfigs() []GeneratePluginConfig - // may be nil + // GenerateManagedConfig returns the managed mode configuration. + // This may will never be nil. GenerateManagedConfig() GenerateManagedConfig - // may be empty - // will always be empty in v2 - // TODO: we may need a way to attach inputs to make this consistent, but - // can deal with that for v2. - //GenerateInputConfigs() []GenerateInputConfig - - // may be nil - // will always be nil in v2 + // GenerateTypeConfig returns the types to generate code for. This overrides other type + // filters from input configurations, which exist in v2. + // This will always be nil in v2 GenerateTypeConfig() GenerateTypeConfig + isGenerateConfig() } -type GeneratePluginConfig interface { - Plugin() string - Revision() int - Out() string - // TODO define enum in same pattern as FileVersion - // GenerateStrategy() GenerateStrategy - // TODO finish - // TODO: figure out what to do with TypesConfig - isGeneratePluginConfig() +// NewGenerateConfig returns a validated GenerateConfig. +func NewGenerateConfig( + pluginConfigs []GeneratePluginConfig, + managedConfig GenerateManagedConfig, + typeConfig GenerateTypeConfig, +) (GenerateConfig, error) { + if len(pluginConfigs) == 0 { + return nil, newNoPluginsError() + } + return &generateConfig{ + pluginConfigs: pluginConfigs, + managedConfig: managedConfig, + typeConfig: typeConfig, + }, nil +} + +// *** PRIVATE *** + +type generateConfig struct { + pluginConfigs []GeneratePluginConfig + managedConfig GenerateManagedConfig + typeConfig GenerateTypeConfig } -type GenerateManagedConfig interface { - // second value is whether or not this was present - CCEnableArenas() (bool, bool) - // TODO finish - isGenerateManagedConfig() +func newGenerateConfigFromExternalFileV1Beta1( + externalFile externalBufGenYAMLFileV1Beta1, +) (GenerateConfig, error) { + managedConfig, err := newManagedConfigFromExternalV1Beta1(externalFile.Managed, externalFile.Options) + if err != nil { + return nil, err + } + if len(externalFile.Plugins) == 0 { + return nil, newNoPluginsError() + } + pluginConfigs, err := slicesext.MapError( + externalFile.Plugins, + newPluginConfigFromExternalV1Beta1, + ) + if err != nil { + return nil, err + } + return &generateConfig{ + pluginConfigs: pluginConfigs, + managedConfig: managedConfig, + }, nil } -//type GenerateInputConfig interface { -//isGenerateInputConfig() -//} +func newGenerateConfigFromExternalFileV1( + externalFile externalBufGenYAMLFileV1, +) (GenerateConfig, error) { + managedConfig, err := newManagedConfigFromExternalV1(externalFile.Managed) + if err != nil { + return nil, err + } + if len(externalFile.Plugins) == 0 { + return nil, newNoPluginsError() + } + pluginConfigs, err := slicesext.MapError( + externalFile.Plugins, + newPluginConfigFromExternalV1, + ) + if err != nil { + return nil, err + } + return &generateConfig{ + pluginConfigs: pluginConfigs, + managedConfig: managedConfig, + typeConfig: newGenerateTypeConfig(externalFile.Types.Include), + }, nil +} -type GenerateTypeConfig interface { - isGenerateTypeConfig() +func newGenerateConfigFromExternalFileV2( + externalFile externalBufGenYAMLFileV2, +) (GenerateConfig, error) { + managedConfig, err := newManagedConfigFromExternalV2(externalFile.Managed) + if err != nil { + return nil, err + } + pluginConfigs, err := slicesext.MapError( + externalFile.Plugins, + newPluginConfigFromExternalV2, + ) + if err != nil { + return nil, err + } + return &generateConfig{ + managedConfig: managedConfig, + pluginConfigs: pluginConfigs, + }, nil } -// *** PRIVATE *** +func (g *generateConfig) GeneratePluginConfigs() []GeneratePluginConfig { + return g.pluginConfigs +} -type generateConfig struct{} +func (g *generateConfig) GenerateManagedConfig() GenerateManagedConfig { + return g.managedConfig +} -func newGenerateConfig() *generateConfig { - return &generateConfig{} +func (g *generateConfig) GenerateTypeConfig() GenerateTypeConfig { + return g.typeConfig } func (*generateConfig) isGenerateConfig() {} + +func newNoPluginsError() error { + return errors.New("must specify at least one plugin") +} diff --git a/private/bufpkg/bufconfig/generate_config_test.go b/private/bufpkg/bufconfig/generate_config_test.go new file mode 100644 index 0000000000..cf0bfb8eb6 --- /dev/null +++ b/private/bufpkg/bufconfig/generate_config_test.go @@ -0,0 +1,798 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 bufconfig + +import ( + "testing" + + "github.com/bufbuild/buf/private/pkg/slicesext" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/descriptorpb" +) + +func TestParseConfigFromExternalV1(t *testing.T) { + t.Parallel() + testcases := []struct { + description string + externalConfig externalBufGenYAMLFileV1 + expectedConfig GenerateConfig + }{ + { + description: "name_local_plugin_strategy", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Name: "java", + Out: "java/out", + Opt: "a=b,c", + Strategy: "all", + }, + }, + }, + expectedConfig: &generateConfig{ + managedConfig: &generateManagedConfig{enabled: false}, + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeLocal, + name: "java", + out: "java/out", + // one string because it's one string in the config + opts: []string{"a=b,c"}, + strategy: toPointer(GenerateStrategyAll), + }, + }, + }, + }, + { + description: "plugin_local_plugin_strategy", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "java", + Out: "java/out", + Opt: "a", + Strategy: "all", + }, + }, + }, + expectedConfig: &generateConfig{ + managedConfig: &generateManagedConfig{enabled: false}, + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeLocal, + name: "java", + out: "java/out", + opts: []string{"a"}, + strategy: toPointer(GenerateStrategyAll), + }, + }, + }, + }, + { + description: "name_binary_plugin_with_string_slice_path_and_opts", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Name: "go", + Out: "go/out", + Path: slicesext.Map([]string{"go", "run", "goplugin"}, func(s string) interface{} { return s }), + Opt: slicesext.Map([]string{"a=b", "c"}, func(s string) interface{} { return s }), + Strategy: "directory", + }, + }, + }, + expectedConfig: &generateConfig{ + managedConfig: &generateManagedConfig{enabled: false}, + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeBinary, + name: "go", + out: "go/out", + path: []string{"go", "run", "goplugin"}, + opts: []string{"a=b", "c"}, + strategy: toPointer(GenerateStrategyDirectory), + }, + }, + }, + }, + { + description: "plugin_binary_plugin_with_string_slice_path_and_opts", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "go", + Out: "go/out", + Path: slicesext.Map([]string{"go", "run", "goplugin"}, func(s string) interface{} { return s }), + Opt: slicesext.Map([]string{"a=b", "c"}, func(s string) interface{} { return s }), + Strategy: "directory", + }, + }, + }, + expectedConfig: &generateConfig{ + managedConfig: &generateManagedConfig{enabled: false}, + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeBinary, + name: "go", + out: "go/out", + path: []string{"go", "run", "goplugin"}, + opts: []string{"a=b", "c"}, + strategy: toPointer(GenerateStrategyDirectory), + }, + }, + }, + }, + { + description: "name_binary_plugin_with_string_path", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Name: "go2", + Out: "go2/out", + Path: "protoc-gen-go", + }, + }, + }, + expectedConfig: &generateConfig{ + managedConfig: &generateManagedConfig{enabled: false}, + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeBinary, + name: "go2", + out: "go2/out", + path: []string{"protoc-gen-go"}, + }, + }, + }, + }, + { + description: "plugin_binary_plugin_with_string_path", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "go2", + Out: "go2/out", + Path: "protoc-gen-go", + }, + }, + }, + expectedConfig: &generateConfig{ + managedConfig: &generateManagedConfig{enabled: false}, + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeBinary, + name: "go2", + out: "go2/out", + path: []string{"protoc-gen-go"}, + }, + }, + }, + }, + { + description: "name_protoc_builtin_plugin", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Name: "cpp", + Out: "cpp/out", + ProtocPath: "path/to/protoc", + }, + }, + }, + expectedConfig: &generateConfig{ + managedConfig: &generateManagedConfig{enabled: false}, + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeProtocBuiltin, + name: "cpp", + out: "cpp/out", + protocPath: "path/to/protoc", + }, + }, + }, + }, + { + description: "plugin_protoc_builtin_plugin", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "cpp", + Out: "cpp/out", + ProtocPath: "path/to/protoc", + }, + }, + }, + expectedConfig: &generateConfig{ + managedConfig: &generateManagedConfig{enabled: false}, + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeProtocBuiltin, + name: "cpp", + out: "cpp/out", + protocPath: "path/to/protoc", + }, + }, + }, + }, + { + description: "remote_plugin_reference", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "buf.build/protocolbuffers/go:v1.31.0", + Out: "go/out", + Revision: 1, + }, + }, + }, + expectedConfig: &generateConfig{ + managedConfig: &generateManagedConfig{enabled: false}, + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeRemote, + remoteHost: "buf.build", + revision: 1, + name: "buf.build/protocolbuffers/go:v1.31.0", + out: "go/out", + }, + }, + }, + }, + { + description: "remote_plugin_identity", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "buf.build/protocolbuffers/go", + Out: "go/out", + Revision: 1, + }, + }, + }, + expectedConfig: &generateConfig{ + managedConfig: &generateManagedConfig{enabled: false}, + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeRemote, + remoteHost: "buf.build", + revision: 1, + name: "buf.build/protocolbuffers/go", + out: "go/out", + }, + }, + }, + }, + { + description: "managed_mode_empty", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "go", + Out: "go/out", + }, + }, + Managed: externalGenerateManagedConfigV1{ + Enabled: true, + }, + }, + expectedConfig: &generateConfig{ + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeLocal, + name: "go", + out: "go/out", + }, + }, + managedConfig: &generateManagedConfig{ + enabled: true, + }, + }, + }, + { + description: "managed_mode_bools_and_java_package", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "go", + Out: "go/out", + }, + }, + Managed: externalGenerateManagedConfigV1{ + Enabled: true, + CcEnableArenas: proto.Bool(true), + JavaMultipleFiles: proto.Bool(true), + JavaStringCheckUtf8: proto.Bool(true), + JavaPackagePrefix: externalJavaPackagePrefixConfigV1{ + Default: "foo", + Except: []string{"buf.build/acme/foo", "buf.build/acme/bar"}, + Override: map[string]string{ + "buf.build/acme/weatherapis": "weather", + "buf.build/acme/paymentapis": "payment", + "buf.build/acme/petapis": "pet", + }, + }, + }, + }, + expectedConfig: &generateConfig{ + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeLocal, + name: "go", + out: "go/out", + }, + }, + managedConfig: &generateManagedConfig{ + enabled: true, + disables: []ManagedDisableRule{ + &managedDisableRule{ + fileOption: FileOptionJavaPackage, + moduleFullName: "buf.build/acme/foo", + }, + &managedDisableRule{ + fileOption: FileOptionJavaPackage, + moduleFullName: "buf.build/acme/bar", + }, + }, + overrides: []ManagedOverrideRule{ + &managedOverrideRule{ + fileOption: FileOptionCcEnableArenas, + value: true, + }, + &managedOverrideRule{ + fileOption: FileOptionJavaMultipleFiles, + value: true, + }, + &managedOverrideRule{ + fileOption: FileOptionJavaStringCheckUtf8, + value: true, + }, + &managedOverrideRule{ + fileOption: FileOptionJavaPackagePrefix, + value: "foo", + }, + // the next three rules are ordered by their module names + &managedOverrideRule{ + fileOption: FileOptionJavaPackagePrefix, + moduleFullName: "buf.build/acme/paymentapis", + value: "payment", + }, + &managedOverrideRule{ + fileOption: FileOptionJavaPackagePrefix, + moduleFullName: "buf.build/acme/petapis", + value: "pet", + }, + &managedOverrideRule{ + fileOption: FileOptionJavaPackagePrefix, + moduleFullName: "buf.build/acme/weatherapis", + value: "weather", + }, + }, + }, + }, + }, + { + description: "managed_mode_optimize_for", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "go", + Out: "go/out", + }, + }, + Managed: externalGenerateManagedConfigV1{ + Enabled: true, + OptimizeFor: externalOptimizeForConfigV1{ + Default: "LITE_RUNTIME", + Except: []string{"buf.build/acme/foo"}, + Override: map[string]string{ + "buf.build/acme/petapis": "CODE_SIZE", + "buf.build/acme/paymentapis": "SPEED", + }, + }, + }, + }, + expectedConfig: &generateConfig{ + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeLocal, + name: "go", + out: "go/out", + }, + }, + managedConfig: &generateManagedConfig{ + enabled: true, + disables: []ManagedDisableRule{ + &managedDisableRule{ + fileOption: FileOptionOptimizeFor, + moduleFullName: "buf.build/acme/foo", + }, + }, + overrides: []ManagedOverrideRule{ + &managedOverrideRule{ + fileOption: FileOptionOptimizeFor, + value: descriptorpb.FileOptions_LITE_RUNTIME, + }, + &managedOverrideRule{ + fileOption: FileOptionOptimizeFor, + moduleFullName: "buf.build/acme/paymentapis", + value: descriptorpb.FileOptions_SPEED, + }, + &managedOverrideRule{ + fileOption: FileOptionOptimizeFor, + moduleFullName: "buf.build/acme/petapis", + value: descriptorpb.FileOptions_CODE_SIZE, + }, + }, + }, + }, + }, + { + description: "managed_mode_go_package_prefix", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "go", + Out: "go/out", + }, + }, + Managed: externalGenerateManagedConfigV1{ + Enabled: true, + GoPackagePrefix: externalGoPackagePrefixConfigV1{ + Default: "foo", + Except: []string{"buf.build/acme/foo"}, + Override: map[string]string{ + "buf.build/acme/petapis": "pet", + }, + }, + }, + }, + expectedConfig: &generateConfig{ + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeLocal, + name: "go", + out: "go/out", + }, + }, + managedConfig: &generateManagedConfig{ + enabled: true, + disables: []ManagedDisableRule{ + &managedDisableRule{ + fileOption: FileOptionGoPackage, + moduleFullName: "buf.build/acme/foo", + }, + }, + overrides: []ManagedOverrideRule{ + &managedOverrideRule{ + fileOption: FileOptionGoPackagePrefix, + value: "foo", + }, + &managedOverrideRule{ + fileOption: FileOptionGoPackagePrefix, + moduleFullName: "buf.build/acme/petapis", + value: "pet", + }, + }, + }, + }, + }, + { + description: "managed_mode_objc_class_prefix", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "go", + Out: "go/out", + }, + }, + Managed: externalGenerateManagedConfigV1{ + Enabled: true, + ObjcClassPrefix: externalObjcClassPrefixConfigV1{ + Default: "foo", + Except: []string{"buf.build/acme/foo"}, + Override: map[string]string{ + "buf.build/acme/petapis": "pet", + }, + }, + }, + }, + expectedConfig: &generateConfig{ + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeLocal, + name: "go", + out: "go/out", + }, + }, + managedConfig: &generateManagedConfig{ + enabled: true, + disables: []ManagedDisableRule{ + &managedDisableRule{ + fileOption: FileOptionObjcClassPrefix, + moduleFullName: "buf.build/acme/foo", + }, + }, + overrides: []ManagedOverrideRule{ + &managedOverrideRule{ + fileOption: FileOptionObjcClassPrefix, + value: "foo", + }, + &managedOverrideRule{ + fileOption: FileOptionObjcClassPrefix, + moduleFullName: "buf.build/acme/petapis", + value: "pet", + }, + }, + }, + }, + }, + { + description: "managed_mode_ruby_package", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "go", + Out: "go/out", + }, + }, + Managed: externalGenerateManagedConfigV1{ + Enabled: true, + RubyPackage: externalRubyPackageConfigV1{ + Except: []string{"buf.build/acme/foo"}, + Override: map[string]string{ + "buf.build/acme/petapis": "pet", + }, + }, + }, + }, + expectedConfig: &generateConfig{ + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeLocal, + name: "go", + out: "go/out", + }, + }, + managedConfig: &generateManagedConfig{ + enabled: true, + disables: []ManagedDisableRule{ + &managedDisableRule{ + fileOption: FileOptionRubyPackage, + moduleFullName: "buf.build/acme/foo", + }, + }, + overrides: []ManagedOverrideRule{ + &managedOverrideRule{ + fileOption: FileOptionRubyPackage, + moduleFullName: "buf.build/acme/petapis", + value: "pet", + }, + }, + }, + }, + }, + { + description: "managed_mode_per_file_override", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "go", + Out: "go/out", + }, + }, + Managed: externalGenerateManagedConfigV1{ + Enabled: true, + Override: map[string]map[string]string{ + "JAVA_PACKAGE": { + "foo.proto": "foo", + "bar.proto": "bar", + "baz.proto": "baz", + }, + "CC_ENABLE_ARENAS": { + "foo.proto": "false", + "baz.proto": "true", + }, + "OPTIMIZE_FOR": { + "dir/baz.proto": "SPEED", + "dir/foo.proto": "CODE_SIZE", + "dir/bar.proto": "LITE_RUNTIME", + }, + }, + }, + }, + expectedConfig: &generateConfig{ + pluginConfigs: []GeneratePluginConfig{ + &pluginConfig{ + pluginConfigType: PluginConfigTypeLocal, + name: "go", + out: "go/out", + }, + }, + managedConfig: &generateManagedConfig{ + enabled: true, + overrides: []ManagedOverrideRule{ + // ordered by file option names and then by file paths + &managedOverrideRule{ + fileOption: FileOptionCcEnableArenas, + path: "baz.proto", + value: true, + }, + &managedOverrideRule{ + fileOption: FileOptionCcEnableArenas, + path: "foo.proto", + value: false, + }, + &managedOverrideRule{ + fileOption: FileOptionJavaPackage, + path: "bar.proto", + value: "bar", + }, + &managedOverrideRule{ + fileOption: FileOptionJavaPackage, + path: "baz.proto", + value: "baz", + }, + &managedOverrideRule{ + fileOption: FileOptionJavaPackage, + path: "foo.proto", + value: "foo", + }, + &managedOverrideRule{ + fileOption: FileOptionOptimizeFor, + path: "dir/bar.proto", + value: descriptorpb.FileOptions_LITE_RUNTIME, + }, + &managedOverrideRule{ + fileOption: FileOptionOptimizeFor, + path: "dir/baz.proto", + value: descriptorpb.FileOptions_SPEED, + }, + &managedOverrideRule{ + fileOption: FileOptionOptimizeFor, + path: "dir/foo.proto", + value: descriptorpb.FileOptions_CODE_SIZE, + }, + }, + }, + }, + }, + } + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.description, func(t *testing.T) { + t.Parallel() + parsedConfig, err := newGenerateConfigFromExternalFileV1(testcase.externalConfig) + require.NoError(t, err) + require.Equal(t, testcase.expectedConfig, parsedConfig) + }) + } +} + +func TestParseConfigFromExternalV1Fail(t *testing.T) { + t.Parallel() + testcases := []struct { + description string + externalConfig externalBufGenYAMLFileV1 + }{ + { + description: "empty_out", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Name: "java", + Out: "", + Opt: "a=b,c", + Strategy: "all", + }, + }, + }, + }, + { + description: "both_plugin_and_name", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "java", + Name: "go", + Out: "java/out", + }, + }, + }, + }, + { + description: "neither_plugin_nor_name", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Out: "java/out", + }, + }, + }, + }, + { + description: "no_plugins", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: nil, + }, + }, + { + description: "invalid_strategy", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "go", + Out: "go/out", + Strategy: "invalid", + }, + }, + }, + }, + { + description: "deprecated_alpha_plugin", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Remote: "buf.build/bufbuild/plugins/connect-go:v1.3.1-1", + Out: "connect/out", + }, + }, + }, + }, + { + description: "plugin_with_deprecated_alpha_plugin_name", + externalConfig: externalBufGenYAMLFileV1{ + Version: "v1", + Plugins: []externalGeneratePluginConfigV1{ + { + Plugin: "buf.build/bufbuild/plugins/connect-go:v1.3.1-1", + Out: "connect/out", + }, + }, + }, + }, + } + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.description, func(t *testing.T) { + t.Parallel() + _, err := newGenerateConfigFromExternalFileV1(testcase.externalConfig) + require.Error(t, err) + }) + } +} diff --git a/private/bufpkg/bufconfig/generate_managed_config.go b/private/bufpkg/bufconfig/generate_managed_config.go new file mode 100644 index 0000000000..31ad9985d5 --- /dev/null +++ b/private/bufpkg/bufconfig/generate_managed_config.go @@ -0,0 +1,840 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 bufconfig + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/bufbuild/buf/private/bufpkg/bufmodule" + "github.com/bufbuild/buf/private/pkg/normalpath" + "github.com/bufbuild/buf/private/pkg/slicesext" +) + +// GenerateManagedConfig is a managed mode configuration. +type GenerateManagedConfig interface { + // Enabled returns whether managed mode is enabled. + Enabled() bool + // Disables returns the disable rules in the configuration. + Disables() []ManagedDisableRule + // Overrides returns the override rules in the configuration. + Overrides() []ManagedOverrideRule + + isGenerateManagedConfig() +} + +// NewGenerateManagedConfig returns a new GenerateManagedConfig. +func NewGenerateManagedConfig( + enabled bool, + disables []ManagedDisableRule, + overrides []ManagedOverrideRule, +) GenerateManagedConfig { + return &generateManagedConfig{ + enabled: enabled, + disables: disables, + overrides: overrides, + } +} + +// ManagedDisableRule is a disable rule. A disable rule describes: +// +// - The options to not modify. If not specified, it means all options (both +// file options and field options) are not modified. +// - The files/fields for which these options are not modified. If not specified, +// it means for all files/fields the specified options are not modified. +// +// A ManagedDisableRule is guaranteed to specify at least one of the two aspects. +// i.e. At least one of Path, ModuleFullName, FieldName, FileOption and +// FieldOption is not empty. A rule can disable all options for certain files/fields, +// disable certains options for all files/fields, or disable certain options for +// certain files/fields. To disable all options for all files/fields, turn off managed mode. +type ManagedDisableRule interface { + // Path returns the file path, relative to its module, to disable managed mode for. + Path() string + // ModuleFullName returns the full name string of the module to disable + // managed mode for. + ModuleFullName() string + // FieldName returns the fully qualified name for the field to disable managed + // mode for. This is guaranteed to be empty if FileOption is not empty. + FieldName() string + // FileOption returns the file option to disable managed mode for. This is + // guaranteed to be empty if FieldName is not empty. + FileOption() FileOption + // FieldOption returns the field option to disalbe managed mode for. + FieldOption() FieldOption + + isManagedDisableRule() +} + +// NewDisableRule returns a new ManagedDisableRule +func NewDisableRule( + path string, + moduleFullName string, + fieldName string, + fileOption FileOption, + fieldOption FieldOption, +) (ManagedDisableRule, error) { + return newDisableRule( + path, + moduleFullName, + fieldName, + fileOption, + fieldOption, + ) +} + +// ManagedOverrideRule is an override rule. An override describes: +// +// - The options to modify. Exactly one of FileOption and FieldOption is not empty. +// - The value to modify these options with. +// - The files/fields for which the options are modified. If all of Path, ModuleFullName +// - or FieldName are empty, all files/fields are modified. Otherwise, only +// file/fields that match the specified Path, ModuleFullName and FieldName +// is modified. +type ManagedOverrideRule interface { + // Path is the file path, relative to its module, to disable managed mode for. + Path() string + // ModuleFullName is the full name string of the module to disable + // managed mode for. + ModuleFullName() string + // FieldName is the fully qualified name for the field to disable managed + // mode for. This is guranteed to be empty is FileOption is not empty. + FieldName() string + // FileOption returns the file option to disable managed mode for. This is + // guaranteed to be empty (FileOptionUnspecified) if FieldName is empty. + FileOption() FileOption + // FieldOption returns the field option to disable managed mode for. + FieldOption() FieldOption + // Value returns the override value. + Value() interface{} + + isManagedOverrideRule() +} + +// NewFieldOptionOverrideRule returns an OverrideRule for a field option. +func NewFileOptionOverrideRule( + path string, + moduleFullName string, + fileOption FileOption, + value interface{}, +) (*managedOverrideRule, error) { + return newFileOptionOverrideRule( + path, + moduleFullName, + fileOption, + value, + ) +} + +// NewFieldOptionOverrideRule returns an OverrideRule for a field option. +func NewFieldOptionOverrideRule( + path string, + moduleFullName string, + fieldName string, + fieldOption FieldOption, + value interface{}, +) (ManagedOverrideRule, error) { + return newFieldOptionOverrideRule( + path, + moduleFullName, + fieldName, + fieldOption, + value, + ) +} + +// *** PRIVATE *** + +type generateManagedConfig struct { + enabled bool + disables []ManagedDisableRule + overrides []ManagedOverrideRule +} + +func newManagedConfigFromExternalV1Beta1( + enabled bool, + externalConfig externalGenerateManagedConfigV1Beta1, +) (GenerateManagedConfig, error) { + var ( + overrides []ManagedOverrideRule + ) + if externalCCEnableArenas := externalConfig.CcEnableArenas; externalCCEnableArenas != nil { + override, err := NewFileOptionOverrideRule( + "", + "", + FileOptionCcEnableArenas, + *externalCCEnableArenas, + ) + if err != nil { + return nil, err + } + overrides = append(overrides, override) + } + if externalJavaMultipleFiles := externalConfig.JavaMultipleFiles; externalJavaMultipleFiles != nil { + override, err := NewFileOptionOverrideRule( + "", + "", + FileOptionJavaMultipleFiles, + *externalJavaMultipleFiles, + ) + if err != nil { + return nil, err + } + overrides = append(overrides, override) + } + if externalOptimizeFor := externalConfig.OptimizeFor; externalOptimizeFor != "" { + defaultOverride, err := NewFileOptionOverrideRule( + "", + "", + FileOptionOptimizeFor, + externalOptimizeFor, + ) + if err != nil { + return nil, err + } + overrides = append(overrides, defaultOverride) + } + return &generateManagedConfig{ + enabled: enabled, + overrides: overrides, + }, nil +} + +func newManagedConfigFromExternalV1( + externalConfig externalGenerateManagedConfigV1, +) (GenerateManagedConfig, error) { + var ( + disables []ManagedDisableRule + overrides []ManagedOverrideRule + ) + if externalCCEnableArenas := externalConfig.CcEnableArenas; externalCCEnableArenas != nil { + override, err := NewFileOptionOverrideRule( + "", + "", + FileOptionCcEnableArenas, + *externalCCEnableArenas, + ) + if err != nil { + return nil, err + } + overrides = append(overrides, override) + } + if externalJavaMultipleFiles := externalConfig.JavaMultipleFiles; externalJavaMultipleFiles != nil { + override, err := NewFileOptionOverrideRule( + "", + "", + FileOptionJavaMultipleFiles, + *externalJavaMultipleFiles, + ) + if err != nil { + return nil, err + } + overrides = append(overrides, override) + } + if externalJavaStringCheckUtf8 := externalConfig.JavaStringCheckUtf8; externalJavaStringCheckUtf8 != nil { + override, err := NewFileOptionOverrideRule( + "", + "", + FileOptionJavaStringCheckUtf8, + *externalJavaStringCheckUtf8, + ) + if err != nil { + return nil, err + } + overrides = append(overrides, override) + } + if externalJavaPackagePrefix := externalConfig.JavaPackagePrefix; !externalJavaPackagePrefix.isEmpty() { + if externalJavaPackagePrefix.Default == "" { + return nil, errors.New("java_package_prefix requires a default value") + } + defaultOverride, err := NewFileOptionOverrideRule( + "", + "", + FileOptionJavaPackagePrefix, + externalJavaPackagePrefix.Default, + ) + if err != nil { + return nil, err + } + overrides = append(overrides, defaultOverride) + javaPackagePrefixDisables, javaPackagePrefixOverrides, err := disablesAndOverridesFromExceptAndOverrideV1( + FileOptionJavaPackage, + externalJavaPackagePrefix.Except, + FileOptionJavaPackagePrefix, + externalJavaPackagePrefix.Override, + ) + if err != nil { + return nil, err + } + disables = append(disables, javaPackagePrefixDisables...) + overrides = append(overrides, javaPackagePrefixOverrides...) + } + if externalCsharpNamespace := externalConfig.CsharpNamespace; !externalCsharpNamespace.isEmpty() { + csharpNamespaceDisables, csharpNamespaceOverrides, err := disablesAndOverridesFromExceptAndOverrideV1( + FileOptionCsharpNamespace, + externalCsharpNamespace.Except, + FileOptionCsharpNamespace, + externalCsharpNamespace.Override, + ) + if err != nil { + return nil, err + } + disables = append(disables, csharpNamespaceDisables...) + overrides = append(overrides, csharpNamespaceOverrides...) + } + if externalOptimizeFor := externalConfig.OptimizeFor; !externalOptimizeFor.isEmpty() { + if externalOptimizeFor.Default == "" { + return nil, errors.New("optimize_for requires a default value") + } + defaultOverride, err := NewFileOptionOverrideRule( + "", + "", + FileOptionOptimizeFor, + externalOptimizeFor.Default, + ) + if err != nil { + return nil, err + } + overrides = append(overrides, defaultOverride) + optimizeForDisables, optimizeForOverrides, err := disablesAndOverridesFromExceptAndOverrideV1( + FileOptionOptimizeFor, + externalOptimizeFor.Except, + FileOptionOptimizeFor, + externalOptimizeFor.Override, + ) + if err != nil { + return nil, err + } + disables = append(disables, optimizeForDisables...) + overrides = append(overrides, optimizeForOverrides...) + } + if externalGoPackagePrefix := externalConfig.GoPackagePrefix; !externalGoPackagePrefix.isEmpty() { + if externalGoPackagePrefix.Default == "" { + return nil, errors.New("go_package_prefix requires a default value") + } + defaultOverride, err := NewFileOptionOverrideRule( + "", + "", + FileOptionGoPackagePrefix, + externalGoPackagePrefix.Default, + ) + if err != nil { + return nil, err + } + overrides = append(overrides, defaultOverride) + goPackagePrefixDisables, goPackagePrefixOverrides, err := disablesAndOverridesFromExceptAndOverrideV1( + FileOptionGoPackage, + externalGoPackagePrefix.Except, + FileOptionGoPackagePrefix, + externalGoPackagePrefix.Override, + ) + if err != nil { + return nil, err + } + disables = append(disables, goPackagePrefixDisables...) + overrides = append(overrides, goPackagePrefixOverrides...) + } + if externalObjcClassPrefix := externalConfig.ObjcClassPrefix; !externalObjcClassPrefix.isEmpty() { + if externalObjcClassPrefix.Default != "" { + // objc class prefix allows empty default + defaultOverride, err := NewFileOptionOverrideRule( + "", + "", + FileOptionObjcClassPrefix, + externalObjcClassPrefix.Default, + ) + if err != nil { + return nil, err + } + overrides = append(overrides, defaultOverride) + } + objcClassPrefixDisables, objcClassPrefixOverrides, err := disablesAndOverridesFromExceptAndOverrideV1( + FileOptionObjcClassPrefix, + externalObjcClassPrefix.Except, + FileOptionObjcClassPrefix, + externalObjcClassPrefix.Override, + ) + if err != nil { + return nil, err + } + disables = append(disables, objcClassPrefixDisables...) + overrides = append(overrides, objcClassPrefixOverrides...) + } + if externalRubyPackage := externalConfig.RubyPackage; !externalRubyPackage.isEmpty() { + rubyPackageDisables, rubyPackageOverrides, err := disablesAndOverridesFromExceptAndOverrideV1( + FileOptionRubyPackage, + externalRubyPackage.Except, + FileOptionRubyPackage, + externalRubyPackage.Override, + ) + if err != nil { + return nil, err + } + disables = append(disables, rubyPackageDisables...) + overrides = append(overrides, rubyPackageOverrides...) + } + perFileOverrides, err := overrideRulesForPerFileOverridesV1(externalConfig.Override) + if err != nil { + return nil, err + } + overrides = append(overrides, perFileOverrides...) + return &generateManagedConfig{ + enabled: externalConfig.Enabled, + disables: disables, + overrides: overrides, + }, nil +} + +func newManagedConfigFromExternalV2( + externalConfig externalGenerateManagedConfigV2, +) (GenerateManagedConfig, error) { + var disables []ManagedDisableRule + var overrides []ManagedOverrideRule + for _, externalDisableConfig := range externalConfig.Disable { + var ( + fileOption FileOption + fieldOption FieldOption + err error + ) + if externalDisableConfig.FileOption != "" { + fileOption, err = parseFileOption(externalDisableConfig.FileOption) + if err != nil { + return nil, err + } + } + if externalDisableConfig.FieldOption != "" { + fieldOption, err = parseFieldOption(externalDisableConfig.FieldOption) + if err != nil { + return nil, err + } + } + disable, err := newDisableRule( + externalDisableConfig.Path, + externalDisableConfig.Module, + externalDisableConfig.Field, + fileOption, + fieldOption, + ) + if err != nil { + return nil, err + } + disables = append(disables, disable) + } + for _, externalOverrideConfig := range externalConfig.Override { + if externalOverrideConfig.FileOption == "" && externalOverrideConfig.FieldOption == "" { + return nil, errors.New("must set file_option or field_option for an override") + } + if externalOverrideConfig.FileOption != "" && externalOverrideConfig.FieldOption != "" { + return nil, errors.New("exactly one of file_option and field_option must be set for an override") + } + if externalOverrideConfig.Value == nil { + return nil, errors.New("must set value for an override") + } + if externalOverrideConfig.FieldOption != "" { + fieldOption, err := parseFieldOption(externalOverrideConfig.FieldOption) + if err != nil { + return nil, err + } + override, err := NewFieldOptionOverrideRule( + externalOverrideConfig.Path, + externalOverrideConfig.Module, + externalOverrideConfig.Field, + fieldOption, + externalOverrideConfig.Value, + ) + if err != nil { + return nil, err + } + overrides = append(overrides, override) + continue + } + fileOption, err := parseFileOption(externalOverrideConfig.FileOption) + if err != nil { + return nil, err + } + override, err := NewFileOptionOverrideRule( + externalOverrideConfig.Path, + externalOverrideConfig.Module, + fileOption, + externalOverrideConfig.Value, + ) + if err != nil { + return nil, err + } + overrides = append(overrides, override) + } + return &generateManagedConfig{ + enabled: externalConfig.Enabled, + disables: disables, + overrides: overrides, + }, nil +} + +func (g *generateManagedConfig) Enabled() bool { + return g.enabled +} + +func (g *generateManagedConfig) Disables() []ManagedDisableRule { + return g.disables +} + +func (g *generateManagedConfig) Overrides() []ManagedOverrideRule { + return g.overrides +} + +func (g *generateManagedConfig) isGenerateManagedConfig() {} + +type managedDisableRule struct { + path string + moduleFullName string + fieldName string + fileOption FileOption + fieldOption FieldOption +} + +func newDisableRule( + path string, + moduleFullName string, + fieldName string, + fileOption FileOption, + fieldOption FieldOption, +) (ManagedDisableRule, error) { + if path == "" && moduleFullName == "" && fieldName == "" && fileOption == FileOptionUnspecified && fieldOption == FieldOptionUnspecified { + return nil, errors.New("empty disable rule is not allowed") + } + if fieldName != "" && fileOption != FileOptionUnspecified { + return nil, errors.New("cannot disable a file option for a field") + } + if fileOption != FileOptionUnspecified && fieldOption != FieldOptionUnspecified { + return nil, errors.New("at most one of file_option and field_option can be specified") + } + if path != "" { + if err := validatePath(path); err != nil { + return nil, fmt.Errorf("invalid path for disable rule: %w", err) + } + } + if moduleFullName != "" { + if _, err := bufmodule.ParseModuleFullName(moduleFullName); err != nil { + return nil, err + } + } + return &managedDisableRule{ + path: path, + moduleFullName: moduleFullName, + fieldName: fieldName, + fileOption: fileOption, + fieldOption: fieldOption, + }, nil +} + +func (m *managedDisableRule) Path() string { + return m.path +} + +func (m *managedDisableRule) ModuleFullName() string { + return m.moduleFullName +} + +func (m *managedDisableRule) FieldName() string { + return m.fieldName +} + +func (m *managedDisableRule) FileOption() FileOption { + return m.fileOption +} + +func (m *managedDisableRule) FieldOption() FieldOption { + return m.fieldOption +} + +func (m *managedDisableRule) isManagedDisableRule() {} + +type managedOverrideRule struct { + path string + moduleFullName string + fieldName string + fileOption FileOption + fieldOption FieldOption + value interface{} +} + +func newFileOptionOverrideRule( + path string, + moduleFullName string, + fileOption FileOption, + value interface{}, +) (*managedOverrideRule, error) { + // All valid file options have a parse func. This lookup implicitly validates the option. + parseOverrideValueFunc, ok := fileOptionToParseOverrideValueFunc[fileOption] + if !ok { + return nil, fmt.Errorf("invalid fileOption: %v", fileOption) + } + if value == nil { + return nil, fmt.Errorf("value must be specified for override") + } + parsedValue, err := parseOverrideValueFunc(value) + if err != nil { + return nil, fmt.Errorf("invalid value %v for %v: %w", value, fileOption, err) + } + if moduleFullName != "" { + if _, err := bufmodule.ParseModuleFullName(moduleFullName); err != nil { + return nil, fmt.Errorf("invalid module name for %v override: %w", fileOption, err) + } + } + if path != "" { + if err := validatePath(path); err != nil { + return nil, fmt.Errorf("invalid path for %v override: %w", fileOption, err) + } + } + return &managedOverrideRule{ + path: path, + moduleFullName: moduleFullName, + fileOption: fileOption, + value: parsedValue, + }, nil +} + +func newFieldOptionOverrideRule( + path string, + moduleFullName string, + fieldName string, + fieldOption FieldOption, + value interface{}, +) (ManagedOverrideRule, error) { + // All valid field options have a parse func. This lookup implicitly validates the option. + parseOverrideValueFunc, ok := fieldOptionToParseOverrideValueFunc[fieldOption] + if !ok { + return nil, fmt.Errorf("invalid fieldOption: %v", fieldOption) + } + if value == nil { + return nil, fmt.Errorf("value must be specified for override") + } + parsedValue, err := parseOverrideValueFunc(value) + if err != nil { + return nil, fmt.Errorf("invalid value %v for %v: %w", value, fieldOption, err) + } + if moduleFullName != "" { + if _, err := bufmodule.ParseModuleFullName(moduleFullName); err != nil { + return nil, fmt.Errorf("invalid module name for %v override: %w", fieldOption, err) + } + } + if path != "" { + if err := validatePath(path); err != nil { + return nil, fmt.Errorf("invalid path for %v override: %w", fieldOption, err) + } + } + return &managedOverrideRule{ + path: path, + moduleFullName: moduleFullName, + fieldName: fieldName, + fieldOption: fieldOption, + value: parsedValue, + }, nil +} + +func (m *managedOverrideRule) Path() string { + return m.path +} + +func (m *managedOverrideRule) ModuleFullName() string { + return m.moduleFullName +} + +func (m *managedOverrideRule) FieldName() string { + return m.fieldName +} + +func (m *managedOverrideRule) FileOption() FileOption { + return m.fileOption +} + +func (m *managedOverrideRule) FieldOption() FieldOption { + return m.fieldOption +} + +func (m *managedOverrideRule) Value() interface{} { + return m.value +} + +func (m *managedOverrideRule) isManagedOverrideRule() {} + +func disablesAndOverridesFromExceptAndOverrideV1( + exceptFileOption FileOption, + exceptModuleFullNames []string, + overrideFileOption FileOption, + moduleFullNameToOverride map[string]string, +) ([]ManagedDisableRule, []ManagedOverrideRule, error) { + var ( + disables []ManagedDisableRule + overrides []ManagedOverrideRule + ) + seenExceptModuleFullNames := make(map[string]struct{}, len(exceptModuleFullNames)) + for _, exceptModuleFullName := range exceptModuleFullNames { + if _, err := bufmodule.ParseModuleFullName(exceptModuleFullName); err != nil { + return nil, nil, err + } + if _, ok := seenExceptModuleFullNames[exceptModuleFullName]; ok { + return nil, nil, fmt.Errorf("%q is defined multiple times in except", exceptModuleFullName) + } + seenExceptModuleFullNames[exceptModuleFullName] = struct{}{} + disable, err := newDisableRule( + "", + exceptModuleFullName, + "", + exceptFileOption, + FieldOptionUnspecified, + ) + if err != nil { + return nil, nil, err + } + disables = append(disables, disable) + } + // Sort by keys for deterministic order. + sortedModuleFullNames := slicesext.MapKeysToSortedSlice(moduleFullNameToOverride) + for _, overrideModuleFullName := range sortedModuleFullNames { + if _, err := bufmodule.ParseModuleFullName(overrideModuleFullName); err != nil { + return nil, nil, err + } + if _, ok := seenExceptModuleFullNames[overrideModuleFullName]; ok { + return nil, nil, fmt.Errorf("override %q is already defined as an except", overrideModuleFullName) + } + override, err := NewFileOptionOverrideRule( + "", + overrideModuleFullName, + overrideFileOption, + moduleFullNameToOverride[overrideModuleFullName], + ) + if err != nil { + return nil, nil, err + } + overrides = append(overrides, override) + } + return disables, overrides, nil +} + +func overrideRulesForPerFileOverridesV1( + fileOptionToFilePathToOverride map[string]map[string]string, +) ([]ManagedOverrideRule, error) { + var overrideRules []ManagedOverrideRule + sortedFileOptionStrings := slicesext.MapKeysToSortedSlice(fileOptionToFilePathToOverride) + for _, fileOptionString := range sortedFileOptionStrings { + fileOption, ok := stringToFileOption[strings.ToLower(fileOptionString)] + if !ok { + return nil, fmt.Errorf("%q is not a valid file option", fileOptionString) + } + filePathToOverride := fileOptionToFilePathToOverride[fileOptionString] + sortedFilePaths := slicesext.MapKeysToSortedSlice(filePathToOverride) + for _, filePath := range sortedFilePaths { + err := validatePath(filePath) + if err != nil { + return nil, fmt.Errorf("invalid import path for override %s: %w", fileOptionString, err) + } + overrideString := filePathToOverride[filePath] + var overrideValue interface{} = overrideString + switch fileOption { + case FileOptionCcEnableArenas, FileOptionJavaMultipleFiles, FileOptionJavaStringCheckUtf8: + overrideValue, err = strconv.ParseBool(overrideString) + if err != nil { + return nil, fmt.Errorf("") + } + } + overrideRule, err := NewFileOptionOverrideRule( + filePath, + "", + fileOption, + overrideValue, + ) + if err != nil { + return nil, err + } + overrideRules = append(overrideRules, overrideRule) + } + } + return overrideRules, nil +} + +func newExternalManagedConfigV2FromGenerateManagedConfig( + managedConfig GenerateManagedConfig, +) externalGenerateManagedConfigV2 { + if managedConfig == nil { + return externalGenerateManagedConfigV2{} + } + var externalDisables []externalManagedDisableConfigV2 + for _, disable := range managedConfig.Disables() { + var fileOptionName string + if disable.FileOption() != FileOptionUnspecified { + fileOptionName = disable.FileOption().String() + } + var fieldOptionName string + if disable.FieldOption() != FieldOptionUnspecified { + fieldOptionName = disable.FieldOption().String() + } + externalDisables = append( + externalDisables, + externalManagedDisableConfigV2{ + FileOption: fileOptionName, + FieldOption: fieldOptionName, + Module: disable.ModuleFullName(), + Path: disable.Path(), + Field: disable.FieldName(), + }, + ) + } + var externalOverrides []externalManagedOverrideConfigV2 + for _, override := range managedConfig.Overrides() { + var fileOptionName string + if override.FileOption() != FileOptionUnspecified { + fileOptionName = override.FileOption().String() + } + var fieldOptionName string + if override.FieldOption() != FieldOptionUnspecified { + fieldOptionName = override.FieldOption().String() + } + externalOverrides = append( + externalOverrides, + externalManagedOverrideConfigV2{ + FileOption: fileOptionName, + FieldOption: fieldOptionName, + Module: override.ModuleFullName(), + Path: override.Path(), + Field: override.FieldName(), + Value: override.Value(), + }, + ) + } + return externalGenerateManagedConfigV2{ + Enabled: true, + Disable: externalDisables, + Override: externalOverrides, + } +} + +func validatePath(path string) error { + normalizedPath, err := normalpath.NormalizeAndValidate(path) + if err != nil { + return err + } + if path != normalizedPath { + return fmt.Errorf( + // TODO: do we want to show the word 'normalized' to users? + "%q is not normalized, use %q instead", + path, + normalizedPath, + ) + } + return nil +} diff --git a/private/bufpkg/bufconfig/generate_managed_option.go b/private/bufpkg/bufconfig/generate_managed_option.go new file mode 100644 index 0000000000..492528e6cd --- /dev/null +++ b/private/bufpkg/bufconfig/generate_managed_option.go @@ -0,0 +1,226 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 bufconfig + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "google.golang.org/protobuf/types/descriptorpb" +) + +// FileOption is a file option. +type FileOption int + +const ( + // FileOptionUnspecified is an unspecified file option. + FileOptionUnspecified FileOption = iota + // FileOptionJavaPackage is the file option java_package. + FileOptionJavaPackage + // FileOptionJavaPackagePrefix is the file option java_package_prefix. + FileOptionJavaPackagePrefix + // FileOptionJavaPackageSuffix is the file option java_package_suffix. + FileOptionJavaPackageSuffix + // FileOptionJavaOuterClassname is the file option java_outer_classname. + FileOptionJavaOuterClassname + // FileOptionJavaMultipleFiles is the file option java_multiple_files. + FileOptionJavaMultipleFiles + // FileOptionJavaStringCheckUtf8 is the file option java_string_check_utf8. + FileOptionJavaStringCheckUtf8 + // FileOptionOptimizeFor is the file option optimize_for. + FileOptionOptimizeFor + // FileOptionGoPackage is the file option go_package. + FileOptionGoPackage + // FileOptionGoPackagePrefix is the file option go_package_prefix. + FileOptionGoPackagePrefix + // FileOptionCcEnableArenas is the file option cc_enable_arenas. + FileOptionCcEnableArenas + // FileOptionObjcClassPrefix is the file option objc_class_prefix. + FileOptionObjcClassPrefix + // FileOptionCsharpNamespace is the file option csharp_namespace. + FileOptionCsharpNamespace + // FileOptionCsharpNamespacePrefix is the file option csharp_namespace_prefix. + FileOptionCsharpNamespacePrefix + // FileOptionPhpNamespace is the file option php_namespace. + FileOptionPhpNamespace + // FileOptionPhpMetadataNamespace is the file option php_metadata_namespace. + FileOptionPhpMetadataNamespace + // FileOptionPhpMetadataNamespaceSuffix is the file option php_metadata_namespace_suffix. + FileOptionPhpMetadataNamespaceSuffix + // FileOptionRubyPackage is the file option ruby_package. + FileOptionRubyPackage + // FileOptionRubyPackageSuffix is the file option ruby_package_suffix. + FileOptionRubyPackageSuffix +) + +// String implements fmt.Stringer. +func (f FileOption) String() string { + s, ok := fileOptionToString[f] + if !ok { + return strconv.Itoa(int(f)) + } + return s +} + +// FieldOption is a field option. +type FieldOption int + +const ( + // FieldOptionUnspecified is an unspecified field option. + FieldOptionUnspecified FieldOption = iota + // FieldOptionJSType is the field option js_type. + FieldOptionJSType +) + +// String implements fmt.Stringer. +func (f FieldOption) String() string { + s, ok := fieldOptionToString[f] + if !ok { + return strconv.Itoa(int(f)) + } + return s +} + +// *** PRIVATE *** + +var ( + fileOptionToString = map[FileOption]string{ + FileOptionJavaPackage: "java_package", + FileOptionJavaPackagePrefix: "java_package_prefix", + FileOptionJavaPackageSuffix: "java_package_suffix", + FileOptionJavaOuterClassname: "java_outer_classname", + FileOptionJavaMultipleFiles: "java_multiple_files", + FileOptionJavaStringCheckUtf8: "java_string_check_utf8", + FileOptionOptimizeFor: "optimize_for", + FileOptionGoPackage: "go_package", + FileOptionGoPackagePrefix: "go_package_prefix", + FileOptionCcEnableArenas: "cc_enable_arenas", + FileOptionObjcClassPrefix: "objc_class_prefix", + FileOptionCsharpNamespace: "csharp_namespace", + FileOptionCsharpNamespacePrefix: "csharp_namespace_prefix", + FileOptionPhpNamespace: "php_namespace", + FileOptionPhpMetadataNamespace: "php_metadata_namespace", + FileOptionPhpMetadataNamespaceSuffix: "php_metadata_namespace_suffix", + FileOptionRubyPackage: "ruby_package", + FileOptionRubyPackageSuffix: "ruby_package_suffix", + } + stringToFileOption = map[string]FileOption{ + "java_package": FileOptionJavaPackage, + "java_package_prefix": FileOptionJavaPackagePrefix, + "java_package_suffix": FileOptionJavaPackageSuffix, + "java_outer_classname": FileOptionJavaOuterClassname, + "java_multiple_files": FileOptionJavaMultipleFiles, + "java_string_check_utf8": FileOptionJavaStringCheckUtf8, + "optimize_for": FileOptionOptimizeFor, + "go_package": FileOptionGoPackage, + "go_package_prefix": FileOptionGoPackagePrefix, + "cc_enable_arenas": FileOptionCcEnableArenas, + "objc_class_prefix": FileOptionObjcClassPrefix, + "csharp_namespace": FileOptionCsharpNamespace, + "csharp_namespace_prefix": FileOptionCsharpNamespacePrefix, + "php_namespace": FileOptionPhpNamespace, + "php_metadata_namespace": FileOptionPhpMetadataNamespace, + "php_metadata_namespace_suffix": FileOptionPhpMetadataNamespaceSuffix, + "ruby_package": FileOptionRubyPackage, + "ruby_package_suffix": FileOptionRubyPackageSuffix, + } + fileOptionToParseOverrideValueFunc = map[FileOption]func(interface{}) (interface{}, error){ + FileOptionJavaPackage: parseOverrideValue[string], + FileOptionJavaPackagePrefix: parseOverrideValue[string], + FileOptionJavaPackageSuffix: parseOverrideValue[string], + FileOptionOptimizeFor: parseOverrideValueOptimizeMode, + FileOptionJavaOuterClassname: parseOverrideValue[string], + FileOptionJavaMultipleFiles: parseOverrideValue[bool], + FileOptionJavaStringCheckUtf8: parseOverrideValue[bool], + FileOptionGoPackage: parseOverrideValue[string], + FileOptionGoPackagePrefix: parseOverrideValue[string], + FileOptionCcEnableArenas: parseOverrideValue[bool], + FileOptionObjcClassPrefix: parseOverrideValue[string], // objc_class_prefix is in descriptor.proto + FileOptionCsharpNamespace: parseOverrideValue[string], + FileOptionCsharpNamespacePrefix: parseOverrideValue[string], + FileOptionPhpNamespace: parseOverrideValue[string], + FileOptionPhpMetadataNamespace: parseOverrideValue[string], + FileOptionPhpMetadataNamespaceSuffix: parseOverrideValue[string], + FileOptionRubyPackage: parseOverrideValue[string], + FileOptionRubyPackageSuffix: parseOverrideValue[string], + } + fieldOptionToString = map[FieldOption]string{ + FieldOptionJSType: "jstype", + } + stringToFieldOption = map[string]FieldOption{ + "jstype": FieldOptionJSType, + } + fieldOptionToParseOverrideValueFunc = map[FieldOption]func(interface{}) (interface{}, error){ + FieldOptionJSType: parseOverrideValueJSType, + } +) + +func parseFileOption(s string) (FileOption, error) { + s = strings.ToLower(strings.TrimSpace(s)) + if s == "" { + return 0, errors.New("empty file_option") + } + f, ok := stringToFileOption[s] + if ok { + return f, nil + } + return 0, fmt.Errorf("unknown file_option: %q", s) +} + +func parseFieldOption(s string) (FieldOption, error) { + s = strings.ToLower(strings.TrimSpace(s)) + if s == "" { + return 0, errors.New("empty field_option") + } + f, ok := stringToFieldOption[s] + if ok { + return f, nil + } + return 0, fmt.Errorf("unknown field_option: %q", s) +} + +func parseOverrideValue[T string | bool](overrideValue interface{}) (interface{}, error) { + parsedValue, ok := overrideValue.(T) + if !ok { + return nil, fmt.Errorf("expected a %T, got %T", parsedValue, overrideValue) + } + return parsedValue, nil +} + +func parseOverrideValueOptimizeMode(overrideValue interface{}) (interface{}, error) { + optimizeModeName, ok := overrideValue.(string) + if !ok { + return nil, errors.New("must be one of SPEED, CODE_SIZE or LITE_RUNTIME") + } + optimizeMode, ok := descriptorpb.FileOptions_OptimizeMode_value[optimizeModeName] + if !ok { + return nil, errors.New("must be one of SPEED, CODE_SIZE or LITE_RUNTIME") + } + return descriptorpb.FileOptions_OptimizeMode(optimizeMode), nil +} + +func parseOverrideValueJSType(override interface{}) (interface{}, error) { + jsTypeName, ok := override.(string) + if !ok { + return nil, errors.New("must be one of JS_NORMAL, JS_STRING or JS_NUMBER") + } + jsTypeEnum, ok := descriptorpb.FieldOptions_JSType_value[jsTypeName] + if !ok { + return nil, errors.New("must be one of JS_NORMAL, JS_STRING or JS_NUMBER") + } + return descriptorpb.FieldOptions_JSType(jsTypeEnum), nil +} diff --git a/private/bufpkg/bufconfig/generate_plugin_config.go b/private/bufpkg/bufconfig/generate_plugin_config.go new file mode 100644 index 0000000000..f8b67aaa4e --- /dev/null +++ b/private/bufpkg/bufconfig/generate_plugin_config.go @@ -0,0 +1,622 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 bufconfig + +import ( + "errors" + "fmt" + "math" + "os/exec" + "strings" + + "github.com/bufbuild/buf/private/buf/bufpluginexec" + "github.com/bufbuild/buf/private/bufpkg/bufplugin/bufpluginref" + "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin" + "github.com/bufbuild/buf/private/pkg/encoding" + "github.com/bufbuild/buf/private/pkg/syserror" +) + +const ( + remoteAlphaPluginDeprecationMessage = "the remote field no longer works as " + + "the remote generation alpha has been deprecated, see the migration guide to " + + "now-stable remote plugins: https://buf.build/docs/migration-guides/migrate-remote-generation-alpha/#migrate-to-remote-plugins" +) + +// GenerateStrategy is the generation strategy for a protoc plugin. +type GenerateStrategy int + +const ( + // GenerateStrategyDirectory is the strategy to generate per directory. + // + // This is the default value for local plugins. + GenerateStrategyDirectory GenerateStrategy = 1 + // GenerateStrategyAll is the strategy to generate with all files at once. + // + // This is the only strategy for remote plugins. + GenerateStrategyAll GenerateStrategy = 2 +) + +// PluginConfigType is a plugin configuration type. +type PluginConfigType int + +const ( + // PluginConfigTypeRemote is the remote plugin config type. + PluginConfigTypeRemote PluginConfigType = iota + 1 + // PluginConfigTypeBinary is the binary plugin config type. + PluginConfigTypeBinary + // PluginConfigTypeProtocBuiltin is the protoc built-in plugin config type. + PluginConfigTypeProtocBuiltin + // PluginConfigTypeLocal is the local plugin config type. This type indicates + // it is to be determined whether the plugin is binary or protoc built-in. + // We defer further classification to the plugin executor. In v2 the exact + // plugin config type is always specified and it will never be just local. + PluginConfigTypeLocal +) + +// GeneratePluginConfig is a configuration for a plugin. +type GeneratePluginConfig interface { + // Type returns the plugin type. This is never the zero value. + Type() PluginConfigType + // Name returns the plugin name. This is never empty. + Name() string + // Out returns the output directory for generation. This is never empty. + Out() string + // Opt returns the plugin options as a comma seperated string. + Opt() string + // IncludeImports returns whether to generate code for imported files. This + // is always false in v1. + IncludeImports() bool + // IncludeWKT returns whether to generate code for the well-known types. + // This returns true only if IncludeImports returns true. This is always + // false in v1. + IncludeWKT() bool + // Strategy returns the generation strategy. + // + // This is not empty only when the plugin is local, binary or protoc builtin. + Strategy() GenerateStrategy + // Path returns the path, including arguments, to invoke the binary plugin. + // + // This is not empty only when the plugin is binary. + Path() []string + // ProtocPath returns a path to protoc. + // + // This is not empty only when the plugin is protoc-builtin + ProtocPath() string + // RemoteHost returns the remote host of the remote plugin. + // + // This is not empty only when the plugin is remote. + RemoteHost() string + // Revision returns the revision of the remote plugin. + // + // This is not empty only when the plugin is remote. + Revision() int + + isGeneratePluginConfig() +} + +// NewGeneratePluginWithIncludeImportsAndWKT returns a GeneratePluginConfig the +// same as the input, with include imports and include wkt overriden. +func NewGeneratePluginWithIncludeImportsAndWKT( + config GeneratePluginConfig, + includeImports bool, + includeWKT bool, +) (GeneratePluginConfig, error) { + originalConfig, ok := config.(*pluginConfig) + if !ok { + return nil, syserror.Newf("unknown implementation of GeneratePluginConfig: %T", config) + } + pluginConfig := *originalConfig + if includeImports { + pluginConfig.includeImports = true + } + if includeWKT { + pluginConfig.includeWKT = true + } + return &pluginConfig, nil +} + +// *** PRIVATE *** + +type pluginConfig struct { + pluginConfigType PluginConfigType + name string + out string + opts []string + includeImports bool + includeWKT bool + strategy *GenerateStrategy + path []string + protocPath string + remoteHost string + revision int +} + +func newPluginConfigFromExternalV1Beta1( + externalConfig externalGeneratePluginConfigV1Beta1, +) (GeneratePluginConfig, error) { + if externalConfig.Name == "" { + return nil, errors.New("plugin name is required") + } + if externalConfig.Out == "" { + return nil, fmt.Errorf("out is required for plugin %s", externalConfig.Name) + } + strategy, err := parseStrategy(externalConfig.Strategy) + if err != nil { + return nil, err + } + opt, err := encoding.InterfaceSliceOrStringToStringSlice(externalConfig.Opt) + if err != nil { + return nil, err + } + if externalConfig.Path != "" { + return newBinaryPluginConfig( + externalConfig.Name, + externalConfig.Out, + opt, + false, + false, + strategy, + []string{externalConfig.Path}, + ) + } + return newLocalPluginConfig( + externalConfig.Name, + externalConfig.Out, + opt, + false, + false, + strategy, + ) +} + +func newPluginConfigFromExternalV1( + externalConfig externalGeneratePluginConfigV1, +) (GeneratePluginConfig, error) { + if externalConfig.Remote != "" { + return nil, errors.New(remoteAlphaPluginDeprecationMessage) + } + // In v1 config, only plugin and name are allowed, since remote alpha plugin + // has been deprecated. + if externalConfig.Plugin == "" && externalConfig.Name == "" { + return nil, fmt.Errorf("one of plugin or name is required") + } + if externalConfig.Plugin != "" && externalConfig.Name != "" { + return nil, fmt.Errorf("only one of plugin or name can be set") + } + var pluginIdentifier string + switch { + case externalConfig.Plugin != "": + pluginIdentifier = externalConfig.Plugin + if _, _, _, _, err := bufremoteplugin.ParsePluginVersionPath(pluginIdentifier); err == nil { + // A remote alpha plugin name is not a valid remote plugin reference. + return nil, fmt.Errorf("invalid remote plugin reference: %s", pluginIdentifier) + } + case externalConfig.Name != "": + pluginIdentifier = externalConfig.Name + if _, _, _, _, err := bufremoteplugin.ParsePluginVersionPath(pluginIdentifier); err == nil { + return nil, fmt.Errorf("invalid plugin name %s, did you mean to use a remote plugin?", pluginIdentifier) + } + if bufpluginref.IsPluginReferenceOrIdentity(pluginIdentifier) { + // A remote alpha plugin name is not a valid local plugin name. + return nil, fmt.Errorf("invalid local plugin name: %s", pluginIdentifier) + } + } + if externalConfig.Out == "" { + return nil, fmt.Errorf("out is required for plugin %s", pluginIdentifier) + } + strategy, err := parseStrategy(externalConfig.Strategy) + if err != nil { + return nil, err + } + opt, err := encoding.InterfaceSliceOrStringToStringSlice(externalConfig.Opt) + if err != nil { + return nil, err + } + path, err := encoding.InterfaceSliceOrStringToStringSlice(externalConfig.Path) + if err != nil { + return nil, err + } + if externalConfig.Plugin != "" && bufpluginref.IsPluginReferenceOrIdentity(pluginIdentifier) { + if externalConfig.Path != nil { + return nil, fmt.Errorf("cannot specify path for remote plugin %s", externalConfig.Plugin) + } + if externalConfig.Strategy != "" { + return nil, fmt.Errorf("cannot specify strategy for remote plugin %s", externalConfig.Plugin) + } + if externalConfig.ProtocPath != "" { + return nil, fmt.Errorf("cannot specify protoc_path for remote plugin %s", externalConfig.Plugin) + } + return newRemotePluginConfig( + externalConfig.Plugin, + externalConfig.Out, + opt, + false, + false, + externalConfig.Revision, + ) + } + // At this point the plugin must be local, regardless whehter it's specified + // by key 'plugin' or 'name'. + if len(path) > 0 { + return newBinaryPluginConfig( + pluginIdentifier, + externalConfig.Out, + opt, + false, + false, + strategy, + path, + ) + } + if externalConfig.ProtocPath != "" { + return newProtocBuiltinPluginConfig( + pluginIdentifier, + externalConfig.Out, + opt, + false, + false, + strategy, + externalConfig.ProtocPath, + ) + } + // It could be either binary or protoc built-in. We defer to the plugin executor + // to decide whether the plugin is protoc-builtin or binary. + return newLocalPluginConfig( + pluginIdentifier, + externalConfig.Out, + opt, + false, + false, + strategy, + ) +} + +func newPluginConfigFromExternalV2( + externalConfig externalGeneratePluginConfigV2, +) (GeneratePluginConfig, error) { + var pluginTypeCount int + if externalConfig.Remote != nil { + pluginTypeCount++ + } + if externalConfig.Binary != nil { + pluginTypeCount++ + } + if externalConfig.ProtocBuiltin != nil { + pluginTypeCount++ + } + if pluginTypeCount == 0 { + return nil, errors.New("must specify one of remote, binary and protoc_builtin") + } + if pluginTypeCount > 1 { + return nil, errors.New("only one of remote, binary and protoc_builtin") + } + var strategy string + if externalConfig.Strategy != nil { + strategy = *externalConfig.Strategy + } + parsedStrategy, err := parseStrategy(strategy) + if err != nil { + return nil, err + } + opt, err := encoding.InterfaceSliceOrStringToStringSlice(externalConfig.Opt) + if err != nil { + return nil, err + } + switch { + case externalConfig.Remote != nil: + var revision int + if externalConfig.Revision != nil { + revision = *externalConfig.Revision + } + if externalConfig.Strategy != nil { + return nil, fmt.Errorf("cannot specify strategy for remote plugin %s", *externalConfig.Remote) + } + if externalConfig.ProtocPath != nil { + return nil, fmt.Errorf("cannot specify protoc_path for remote plugin %s", *externalConfig.Remote) + } + return newRemotePluginConfig( + *externalConfig.Remote, + externalConfig.Out, + opt, + externalConfig.IncludeImports, + externalConfig.IncludeWKT, + revision, + ) + case externalConfig.Binary != nil: + path, err := encoding.InterfaceSliceOrStringToStringSlice(externalConfig.Binary) + if err != nil { + return nil, err + } + binaryPluginName := strings.Join(path, " ") + if externalConfig.Revision != nil { + return nil, fmt.Errorf("cannot specify revision for binary plugin %s", binaryPluginName) + } + if externalConfig.ProtocPath != nil { + return nil, fmt.Errorf("cannot specify protoc_path for binary plugin %s", binaryPluginName) + } + return newBinaryPluginConfig( + strings.Join(path, " "), + externalConfig.Out, + opt, + externalConfig.IncludeImports, + externalConfig.IncludeWKT, + parsedStrategy, + path, + ) + case externalConfig.ProtocBuiltin != nil: + var protocPath string + if externalConfig.ProtocPath != nil { + protocPath = *externalConfig.ProtocPath + } + if externalConfig.Revision != nil { + return nil, fmt.Errorf("cannot specify revision for protoc built-in plugin %s", *externalConfig.ProtocBuiltin) + } + return newProtocBuiltinPluginConfig( + *externalConfig.ProtocBuiltin, + externalConfig.Out, + opt, + externalConfig.IncludeImports, + externalConfig.IncludeWKT, + parsedStrategy, + protocPath, + ) + default: + return nil, syserror.Newf("must specify one of remote, binary and protoc_builtin") + } +} + +func newRemotePluginConfig( + name string, + out string, + opt []string, + includeImports bool, + includeWKT bool, + revision int, +) (*pluginConfig, error) { + if includeWKT && !includeImports { + return nil, errors.New("cannot include well-known types without including imports") + } + remoteHost, err := parseRemoteHostName(name) + if err != nil { + return nil, err + } + if revision < 0 || revision > math.MaxInt32 { + return nil, fmt.Errorf("revision %d is out of accepted range %d-%d", revision, 0, math.MaxInt32) + } + return &pluginConfig{ + pluginConfigType: PluginConfigTypeRemote, + name: name, + remoteHost: remoteHost, + revision: revision, + out: out, + opts: opt, + includeImports: includeImports, + includeWKT: includeWKT, + }, nil +} + +func newLocalPluginConfig( + name string, + out string, + opt []string, + includeImports bool, + includeWKT bool, + strategy *GenerateStrategy, +) (*pluginConfig, error) { + if includeWKT && !includeImports { + return nil, errors.New("cannot include well-known types without including imports") + } + return &pluginConfig{ + pluginConfigType: PluginConfigTypeLocal, + name: name, + strategy: strategy, + out: out, + opts: opt, + includeImports: includeImports, + includeWKT: includeWKT, + }, nil +} + +func newBinaryPluginConfig( + name string, + out string, + opt []string, + includeImports bool, + includeWKT bool, + strategy *GenerateStrategy, + path []string, +) (*pluginConfig, error) { + if len(path) == 0 { + return nil, errors.New("must specify a path to the plugin") + } + if includeWKT && !includeImports { + return nil, errors.New("cannot include well-known types without including imports") + } + return &pluginConfig{ + pluginConfigType: PluginConfigTypeBinary, + name: name, + path: path, + strategy: strategy, + out: out, + opts: opt, + includeImports: includeImports, + includeWKT: includeWKT, + }, nil +} + +func newProtocBuiltinPluginConfig( + name string, + out string, + opt []string, + includeImports bool, + includeWKT bool, + strategy *GenerateStrategy, + protocPath string, +) (*pluginConfig, error) { + if includeWKT && !includeImports { + return nil, errors.New("cannot include well-known types without including imports") + } + return &pluginConfig{ + pluginConfigType: PluginConfigTypeProtocBuiltin, + name: name, + protocPath: protocPath, + out: out, + opts: opt, + strategy: strategy, + includeImports: includeImports, + includeWKT: includeWKT, + }, nil +} + +func (p *pluginConfig) Type() PluginConfigType { + return p.pluginConfigType +} + +func (p *pluginConfig) Name() string { + return p.name +} + +func (p *pluginConfig) Out() string { + return p.out +} + +func (p *pluginConfig) Opt() string { + return strings.Join(p.opts, ",") +} + +func (p *pluginConfig) IncludeImports() bool { + return p.includeImports +} + +func (p *pluginConfig) IncludeWKT() bool { + return p.includeWKT +} + +func (p *pluginConfig) Strategy() GenerateStrategy { + if p.strategy == nil { + return GenerateStrategyDirectory + } + return *p.strategy +} + +func (p *pluginConfig) Path() []string { + return p.path +} + +func (p *pluginConfig) ProtocPath() string { + return p.protocPath +} + +func (p *pluginConfig) RemoteHost() string { + return p.remoteHost +} + +func (p *pluginConfig) Revision() int { + return p.revision +} + +func (p *pluginConfig) isGeneratePluginConfig() {} + +func newExternalGeneratePluginConfigV2FromPluginConfig( + generatePluginConfig GeneratePluginConfig, +) (externalGeneratePluginConfigV2, error) { + pluginConfig, ok := generatePluginConfig.(*pluginConfig) + if !ok { + return externalGeneratePluginConfigV2{}, syserror.Newf("unknown implementation of GeneratePluginConfig: %T", generatePluginConfig) + } + externalPluginConfigV2 := externalGeneratePluginConfigV2{ + Out: generatePluginConfig.Out(), + IncludeImports: generatePluginConfig.IncludeImports(), + IncludeWKT: generatePluginConfig.IncludeWKT(), + } + opts := pluginConfig.opts + switch { + case len(opts) == 1: + externalPluginConfigV2.Opt = opts[0] + case len(opts) > 1: + externalPluginConfigV2.Opt = opts + } + strategy := pluginConfig.strategy + switch { + case strategy != nil && *strategy == GenerateStrategyDirectory: + externalPluginConfigV2.Strategy = toPointer("directory") + case strategy != nil && *strategy == GenerateStrategyAll: + externalPluginConfigV2.Strategy = toPointer("all") + } + switch generatePluginConfig.Type() { + case PluginConfigTypeRemote: + externalPluginConfigV2.Remote = toPointer(generatePluginConfig.Name()) + if revision := generatePluginConfig.Revision(); revision != 0 { + externalPluginConfigV2.Revision = &revision + } + case PluginConfigTypeBinary: + path := generatePluginConfig.Path() + switch { + case len(path) == 1: + externalPluginConfigV2.Binary = path[0] + case len(path) > 1: + externalPluginConfigV2.Binary = path + } + case PluginConfigTypeProtocBuiltin: + externalPluginConfigV2.ProtocBuiltin = toPointer(generatePluginConfig.Name()) + if protocPath := generatePluginConfig.ProtocPath(); protocPath != "" { + externalPluginConfigV2.ProtocPath = &protocPath + } + case PluginConfigTypeLocal: + binaryName := "protoc-gen-" + generatePluginConfig.Name() + _, err := exec.LookPath(binaryName) + if err == nil || errors.Is(err, exec.ErrDot) { + externalPluginConfigV2.Binary = binaryName + break + } + if _, isProtocBuiltin := bufpluginexec.ProtocProxyPluginNames[generatePluginConfig.Name()]; isProtocBuiltin { + externalPluginConfigV2.ProtocBuiltin = toPointer(generatePluginConfig.Name()) + break + } + return externalGeneratePluginConfigV2{}, fmt.Errorf("plugin %s is not found locally and %s is not built-in to protoc", binaryName, generatePluginConfig.Name()) + } + return externalPluginConfigV2, nil +} + +func parseStrategy(s string) (*GenerateStrategy, error) { + var strategy GenerateStrategy + switch s { + case "": + return nil, nil + case "directory": + strategy = GenerateStrategyDirectory + case "all": + strategy = GenerateStrategyAll + default: + return nil, fmt.Errorf("unknown strategy: %s", s) + } + return &strategy, nil +} + +func parseRemoteHostName(fullName string) (string, error) { + if identity, err := bufpluginref.PluginIdentityForString(fullName); err == nil { + return identity.Remote(), nil + } + reference, err := bufpluginref.PluginReferenceForString(fullName, 0) + if err == nil { + return reference.Remote(), nil + } + return "", err +} + +// TODO: where to put this? +func toPointer[T any](value T) *T { + return &value +} diff --git a/private/bufpkg/bufconfig/generate_type_config.go b/private/bufpkg/bufconfig/generate_type_config.go new file mode 100644 index 0000000000..541178e522 --- /dev/null +++ b/private/bufpkg/bufconfig/generate_type_config.go @@ -0,0 +1,45 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 bufconfig + +// GenerateTypeConfig is a type filter configuration. +type GenerateTypeConfig interface { + // If IncludeTypes returns a non-empty list, it means that only those types are + // generated. Otherwise all types are generated. + IncludeTypes() []string + + isGenerateTypeConfig() +} + +// *** PRIVATE *** + +type generateTypeConfig struct { + includeTypes []string +} + +func newGenerateTypeConfig(includeTypes []string) GenerateTypeConfig { + if len(includeTypes) == 0 { + return nil + } + return &generateTypeConfig{ + includeTypes: includeTypes, + } +} + +func (g *generateTypeConfig) IncludeTypes() []string { + return g.includeTypes +} + +func (g *generateTypeConfig) isGenerateTypeConfig() {} diff --git a/private/bufpkg/bufconfig/input_config.go b/private/bufpkg/bufconfig/input_config.go new file mode 100644 index 0000000000..9bcafccac1 --- /dev/null +++ b/private/bufpkg/bufconfig/input_config.go @@ -0,0 +1,563 @@ +// InputConfig is an input configuration. +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 bufconfig + +import ( + "errors" + "fmt" + "strconv" + + "github.com/bufbuild/buf/private/pkg/slicesext" + "github.com/bufbuild/buf/private/pkg/stringutil" + "github.com/bufbuild/buf/private/pkg/syserror" +) + +// InputConfigType is an input config's type. +type InputConfigType int + +const ( + // InputConfigTypeModule is the module input type. + InputConfigTypeModule InputConfigType = iota + 1 + // InputConfigTypeDirectory is the directory input type. + InputConfigTypeDirectory + // InputConfigTypeGitRepo is the git repository input type. + InputConfigTypeGitRepo + // InputConfigTypeProtoFile is the proto file input type. + InputConfigTypeProtoFile + // InputConfigTypeTarball is the tarball input type. + InputConfigTypeTarball + // InputConfigTypeZipArchive is the zip archive input type. + InputConfigTypeZipArchive + // InputConfigTypeBinaryImage is the binary image input type. + InputConfigTypeBinaryImage + // InputConfigTypeJSONImage is the JSON image input type. + InputConfigTypeJSONImage + // InputConfigTypeTextImage is the text image input type. + InputConfigTypeTextImage +) + +// String implements fmt.Stringer. +func (i InputConfigType) String() string { + s, ok := inputConfigTypeToString[i] + if !ok { + return strconv.Itoa(int(i)) + } + return s +} + +const ( + compressionKey = "compression" + branchKey = "branch" + tagKey = "tag" + refKey = "ref" + depthKey = "depth" + recurseSubmodulesKey = "recurse_submodules" + stripComponentsKey = "strip_components" + subDirKey = "subdir" + includePackageFilesKey = "include_package_files" +) + +var ( + allowedOptionsForFormat = map[InputConfigType](map[string]struct{}){ + InputConfigTypeGitRepo: { + branchKey: {}, + tagKey: {}, + refKey: {}, + depthKey: {}, + recurseSubmodulesKey: {}, + subDirKey: {}, + }, + InputConfigTypeModule: {}, + InputConfigTypeDirectory: {}, + InputConfigTypeProtoFile: { + includePackageFilesKey: {}, + }, + InputConfigTypeTarball: { + compressionKey: {}, + stripComponentsKey: {}, + subDirKey: {}, + }, + InputConfigTypeZipArchive: { + stripComponentsKey: {}, + subDirKey: {}, + }, + InputConfigTypeBinaryImage: { + compressionKey: {}, + }, + InputConfigTypeJSONImage: { + compressionKey: {}, + }, + InputConfigTypeTextImage: { + compressionKey: {}, + }, + } + inputConfigTypeToString = map[InputConfigType]string{ + InputConfigTypeGitRepo: "git_repo", + InputConfigTypeModule: "module", + InputConfigTypeDirectory: "directory", + InputConfigTypeProtoFile: "proto_file", + InputConfigTypeTarball: "tarball", + InputConfigTypeZipArchive: "zip_archive", + InputConfigTypeBinaryImage: "binary_image", + InputConfigTypeJSONImage: "json_image", + InputConfigTypeTextImage: "text_image", + } + allInputConfigTypeString = stringutil.SliceToHumanString( + slicesext.MapValuesToSortedSlice(inputConfigTypeToString), + ) +) + +// InputConfig is an input configuration for code generation. +type InputConfig interface { + // Type returns the input type. This is never the zero value. + Type() InputConfigType + // Location returns the location for the input. This is never empty. + Location() string + // Compression returns the compression scheme, not empty only if format is + // one of tarball, binary image, json image or text image. + Compression() string + // StripComponents returns the number of directories to strip for tar or zip + // inputs, not empty only if format is tarball or zip archive. + StripComponents() uint32 + // SubDir returns the subdirectory to use, not empty only if format is one + // git repo, tarball and zip archive. + SubDir() string + // Branch returns the git branch to checkout out, not empty only if format is git. + Branch() string + // Tag returns the git tag to checkout, not empty only if format is git. + Tag() string + // Ref returns the git ref to checkout, not empty only if format is git. + Ref() string + // Ref returns the depth to clone the git repo with, not empty only if format is git. + Depth() *uint32 + // RecurseSubmodules returns whether to clone submodules recursively. Not empty + // only if input if git. + RecurseSubmodules() bool + // IncludePackageFiles returns other files in the same package as the proto file, + // not empty only if format is proto file. + IncludePackageFiles() bool + // IncludePaths returns paths to generate for. + IncludePaths() []string + // ExcludePaths returns paths not to generate for. + ExcludePaths() []string + // IncludeTypes returns the types to generate. If GenerateConfig.GenerateTypeConfig() + // returns a non-empty list of types. + IncludeTypes() []string + + isInputConfig() +} + +// NewGitRepoInputConfig returns an input config for a git repo. +func NewGitRepoInputConfig( + location string, + subDir string, + branch string, + tag string, + ref string, + depth *uint32, + recurseSubModules bool, +) (InputConfig, error) { + if location == "" { + return nil, errors.New("empty location for git repository") + } + return &inputConfig{ + inputType: InputConfigTypeGitRepo, + location: location, + subDir: subDir, + branch: branch, + tag: tag, + ref: ref, + depth: depth, + recurseSubmodules: recurseSubModules, + }, nil +} + +// NewModuleInputConfig returns an input config for a module. +func NewModuleInputConfig( + location string, +) (InputConfig, error) { + if location == "" { + return nil, errors.New("empty location for module") + } + return &inputConfig{ + inputType: InputConfigTypeModule, + location: location, + }, nil +} + +// NewDirectoryInputConfig returns an input config for a directory. +func NewDirectoryInputConfig( + location string, +) (InputConfig, error) { + if location == "" { + return nil, errors.New("empty location for directory") + } + return &inputConfig{ + inputType: InputConfigTypeDirectory, + location: location, + }, nil +} + +// NewProtoFileInputConfig returns an input config for a proto file. +func NewProtoFileInputConfig( + location string, + includePackageFiles bool, +) (InputConfig, error) { + if location == "" { + return nil, errors.New("empty location for proto file") + } + return &inputConfig{ + inputType: InputConfigTypeProtoFile, + location: location, + includePackageFiles: includePackageFiles, + }, nil +} + +// NewTarballInputConfig returns an input config for a tarball. +func NewTarballInputConfig( + location string, + subDir string, + compression string, + stripComponents uint32, +) (InputConfig, error) { + if location == "" { + return nil, errors.New("empty location for tarball") + } + return &inputConfig{ + inputType: InputConfigTypeTarball, + location: location, + subDir: subDir, + compression: compression, + stripComponents: stripComponents, + }, nil +} + +// NewZipArchiveInputConfig returns an input config for a zip archive. +func NewZipArchiveInputConfig( + location string, + subDir string, + stripComponents uint32, +) (InputConfig, error) { + if location == "" { + return nil, errors.New("empty location for zip archive") + } + return &inputConfig{ + inputType: InputConfigTypeZipArchive, + location: location, + subDir: subDir, + stripComponents: stripComponents, + }, nil +} + +// NewBinaryImageInputConfig returns an input config for a binary image. +func NewBinaryImageInputConfig( + location string, + compression string, +) (InputConfig, error) { + if location == "" { + return nil, errors.New("empty location for binary image") + } + return &inputConfig{ + inputType: InputConfigTypeBinaryImage, + location: location, + compression: compression, + }, nil +} + +// NewJSONImageInputConfig returns an input config for a JSON image. +func NewJSONImageInputConfig( + location string, + compression string, +) (InputConfig, error) { + if location == "" { + return nil, errors.New("empty location for JSON image") + } + return &inputConfig{ + inputType: InputConfigTypeJSONImage, + location: location, + compression: compression, + }, nil +} + +// NewTextImageInputConfig returns an input config for a text image. +func NewTextImageInputConfig( + location string, + compression string, +) (InputConfig, error) { + if location == "" { + return nil, errors.New("empty location for binary image") + } + return &inputConfig{ + inputType: InputConfigTypeTextImage, + location: location, + compression: compression, + }, nil +} + +// NewInputConfigWithTargets returns an input config the same as the passed config, +// but with include paths, exclude paths and include types overriden. +func NewInputConfigWithTargets( + config InputConfig, + includePaths []string, + excludePaths []string, + includeTypes []string, +) (InputConfig, error) { + originalConfig, ok := config.(*inputConfig) + if !ok { + return nil, syserror.Newf("unknown implementation of InputConfig: %T", config) + } + // targetConfig is a copy of the original config. + targetConfig := *originalConfig + if len(includePaths) > 0 { + targetConfig.includePaths = includePaths + } + if len(excludePaths) > 0 { + targetConfig.excludePaths = excludePaths + } + if len(includeTypes) > 0 { + targetConfig.includeTypes = includeTypes + } + return &targetConfig, nil +} + +// *** PRIVATE *** + +type inputConfig struct { + inputType InputConfigType + location string + compression string + stripComponents uint32 + subDir string + branch string + tag string + ref string + depth *uint32 + recurseSubmodules bool + includePackageFiles bool + includeTypes []string + excludePaths []string + includePaths []string +} + +func newInputConfigFromExternalV2(externalConfig externalInputConfigV2) (InputConfig, error) { + inputConfig := &inputConfig{} + var inputTypes []InputConfigType + var options []string + if externalConfig.Module != nil { + inputTypes = append(inputTypes, InputConfigTypeModule) + inputConfig.location = *externalConfig.Module + } + if externalConfig.Directory != nil { + inputTypes = append(inputTypes, InputConfigTypeDirectory) + inputConfig.location = *externalConfig.Directory + } + if externalConfig.ProtoFile != nil { + inputTypes = append(inputTypes, InputConfigTypeProtoFile) + inputConfig.location = *externalConfig.ProtoFile + } + if externalConfig.BinaryImage != nil { + inputTypes = append(inputTypes, InputConfigTypeBinaryImage) + inputConfig.location = *externalConfig.BinaryImage + } + if externalConfig.Tarball != nil { + inputTypes = append(inputTypes, InputConfigTypeTarball) + inputConfig.location = *externalConfig.Tarball + } + if externalConfig.ZipArchive != nil { + inputTypes = append(inputTypes, InputConfigTypeZipArchive) + inputConfig.location = *externalConfig.ZipArchive + } + if externalConfig.JSONImage != nil { + inputTypes = append(inputTypes, InputConfigTypeJSONImage) + inputConfig.location = *externalConfig.JSONImage + } + if externalConfig.TextImage != nil { + inputTypes = append(inputTypes, InputConfigTypeTextImage) + inputConfig.location = *externalConfig.TextImage + } + if externalConfig.GitRepo != nil { + inputTypes = append(inputTypes, InputConfigTypeGitRepo) + inputConfig.location = *externalConfig.GitRepo + } + if externalConfig.Compression != nil { + options = append(options, compressionKey) + inputConfig.compression = *externalConfig.Compression + } + if externalConfig.StripComponents != nil { + options = append(options, stripComponentsKey) + inputConfig.stripComponents = *externalConfig.StripComponents + } + if externalConfig.Subdir != nil { + options = append(options, subDirKey) + inputConfig.subDir = *externalConfig.Subdir + } + if externalConfig.Branch != nil { + options = append(options, branchKey) + inputConfig.branch = *externalConfig.Branch + } + if externalConfig.Tag != nil { + options = append(options, tagKey) + inputConfig.tag = *externalConfig.Tag + } + if externalConfig.Ref != nil { + options = append(options, refKey) + inputConfig.ref = *externalConfig.Ref + } + if externalConfig.Depth != nil { + options = append(options, depthKey) + inputConfig.depth = externalConfig.Depth + } + if externalConfig.RecurseSubmodules != nil { + options = append(options, recurseSubmodulesKey) + inputConfig.recurseSubmodules = *externalConfig.RecurseSubmodules + } + if externalConfig.IncludePackageFiles != nil { + options = append(options, includePackageFilesKey) + inputConfig.includePackageFiles = *externalConfig.IncludePackageFiles + } + if len(inputTypes) == 0 { + return nil, fmt.Errorf("must specify one of %s", allInputConfigTypeString) + } + if len(inputTypes) > 1 { + return nil, fmt.Errorf("exactly one of %s must be specified", allInputConfigTypeString) + } + format := inputTypes[0] + allowedOptions, ok := allowedOptionsForFormat[format] + if !ok { + return nil, syserror.Newf("unable to find allowed options for format %v", format) + } + for _, option := range options { + if _, ok := allowedOptions[option]; !ok { + return nil, fmt.Errorf("option %s is not allowed for format %v", option, format) + } + } + return inputConfig, nil +} + +func (i *inputConfig) Type() InputConfigType { + return i.inputType +} + +func (i *inputConfig) Location() string { + return i.location +} + +func (i *inputConfig) Compression() string { + return i.compression +} + +func (i *inputConfig) StripComponents() uint32 { + return i.stripComponents +} + +func (i *inputConfig) SubDir() string { + return i.subDir +} + +func (i *inputConfig) Branch() string { + return i.branch +} + +func (i *inputConfig) Tag() string { + return i.tag +} + +func (i *inputConfig) Ref() string { + return i.ref +} + +func (i *inputConfig) Depth() *uint32 { + return i.depth +} + +func (i *inputConfig) RecurseSubmodules() bool { + return i.recurseSubmodules +} + +func (i *inputConfig) IncludePackageFiles() bool { + return i.includePackageFiles +} + +func (i *inputConfig) ExcludePaths() []string { + return i.excludePaths +} + +func (i *inputConfig) IncludePaths() []string { + return i.includePaths +} + +func (i *inputConfig) IncludeTypes() []string { + return i.includeTypes +} + +func (i *inputConfig) isInputConfig() {} + +func newExternalInputConfigV2FromInputConfig( + inputConfig InputConfig, +) (externalInputConfigV2, error) { + externalInputConfigV2 := externalInputConfigV2{} + switch inputConfig.Type() { + case InputConfigTypeGitRepo: + externalInputConfigV2.GitRepo = toPointer(inputConfig.Location()) + case InputConfigTypeDirectory: + externalInputConfigV2.Directory = toPointer(inputConfig.Location()) + case InputConfigTypeModule: + externalInputConfigV2.Module = toPointer(inputConfig.Location()) + case InputConfigTypeProtoFile: + externalInputConfigV2.ProtoFile = toPointer(inputConfig.Location()) + case InputConfigTypeZipArchive: + externalInputConfigV2.ZipArchive = toPointer(inputConfig.Location()) + case InputConfigTypeTarball: + externalInputConfigV2.Tarball = toPointer(inputConfig.Location()) + case InputConfigTypeBinaryImage: + externalInputConfigV2.BinaryImage = toPointer(inputConfig.Location()) + case InputConfigTypeJSONImage: + externalInputConfigV2.JSONImage = toPointer(inputConfig.Location()) + case InputConfigTypeTextImage: + externalInputConfigV2.TextImage = toPointer(inputConfig.Location()) + default: + return externalInputConfigV2, syserror.Newf("unknown input config type: %v", inputConfig.Type()) + } + if inputConfig.Branch() != "" { + externalInputConfigV2.Branch = toPointer(inputConfig.Branch()) + } + if inputConfig.Ref() != "" { + externalInputConfigV2.Ref = toPointer(inputConfig.Ref()) + } + if inputConfig.Tag() != "" { + externalInputConfigV2.Tag = toPointer(inputConfig.Tag()) + } + externalInputConfigV2.Depth = inputConfig.Depth() + if inputConfig.RecurseSubmodules() { + externalInputConfigV2.RecurseSubmodules = toPointer(inputConfig.RecurseSubmodules()) + } + if inputConfig.Compression() != "" { + externalInputConfigV2.Compression = toPointer(inputConfig.Compression()) + } + if inputConfig.StripComponents() != 0 { + externalInputConfigV2.StripComponents = toPointer(inputConfig.StripComponents()) + } + if inputConfig.SubDir() != "" { + externalInputConfigV2.Subdir = toPointer(inputConfig.SubDir()) + } + if inputConfig.IncludePackageFiles() { + externalInputConfigV2.IncludePackageFiles = toPointer(inputConfig.IncludePackageFiles()) + } + externalInputConfigV2.IncludePaths = inputConfig.IncludePaths() + externalInputConfigV2.ExcludePaths = inputConfig.ExcludePaths() + externalInputConfigV2.Types = inputConfig.IncludeTypes() + return externalInputConfigV2, nil +} diff --git a/private/bufpkg/bufimage/bufimagemodify/bufimagemodify.go b/private/bufpkg/bufimage/bufimagemodify/bufimagemodify.go index 28404c0215..f0d973a8dd 100644 --- a/private/bufpkg/bufimage/bufimagemodify/bufimagemodify.go +++ b/private/bufpkg/bufimage/bufimagemodify/bufimagemodify.go @@ -16,334 +16,49 @@ package bufimagemodify import ( "context" - "fmt" - "path" - "strconv" - "strings" + "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufimage" - "github.com/bufbuild/buf/private/bufpkg/bufmodule" + "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimagemodify/internal" "github.com/bufbuild/buf/private/gen/data/datawkt" - "github.com/bufbuild/buf/private/pkg/protoversion" - "go.uber.org/zap" - "google.golang.org/protobuf/types/descriptorpb" ) -// Modifier modifies Images. -type Modifier interface { - // Modify modifies the Image. - Modify(context.Context, bufimage.Image) error -} +// TODO: move this package into bufgen/internal -// NewMultiModifier returns a new Modifier for the given Modifiers. -func NewMultiModifier(modifiers ...Modifier) Modifier { - switch len(modifiers) { - case 0: +// Modify modifies the image according to the managed config. +func Modify( + ctx context.Context, + image bufimage.Image, + config bufconfig.GenerateManagedConfig, +) error { + if !config.Enabled() { return nil - case 1: - return modifiers[0] - default: - return newMultiModifier(modifiers) - } -} - -// ModifierFunc is a convenience type that implements the Modifier interface. -type ModifierFunc func(context.Context, bufimage.Image) error - -// Modify invokes the ModifierFunc with the given context and image. -func (m ModifierFunc) Modify(ctx context.Context, image bufimage.Image) error { - return m(ctx, image) -} - -// Sweeper is used to mark-and-sweep SourceCodeInfo_Locations from images. -type Sweeper interface { - // Sweep implements the ModifierFunc signature so that the Sweeper - // can be used as a Modifier. - Sweep(context.Context, bufimage.Image) error - - // mark is un-exported so that the Sweeper cannot be implemented - // outside of this package. - mark(string, []int32) -} - -// NewFileOptionSweeper constructs a new file option Sweeper that removes -// the SourceCodeInfo_Locations associated with the marks. -func NewFileOptionSweeper() Sweeper { - return newFileOptionSweeper() -} - -// Merge merges the given modifiers together so that they are run in the order -// they are provided. This is particularly useful for constructing a modifier -// from its initial 'nil' value. -// -// var modifier Modifier -// if config.JavaMultipleFiles { -// modifier = Merge(modifier, JavaMultipleFiles) -// } -func Merge(left Modifier, right Modifier) Modifier { - if left == nil { - return right - } - if right == nil { - return left - } - return NewMultiModifier(left, right) -} - -// CcEnableArenas returns a Modifier that sets the cc_enable_arenas -// file option to the given value in all of the files contained in -// the Image. -func CcEnableArenas( - logger *zap.Logger, - sweeper Sweeper, - value bool, - overrides map[string]string, -) (Modifier, error) { - validatedOverrides, err := stringOverridesToBoolOverrides(overrides) - if err != nil { - return nil, fmt.Errorf("invalid override for %s: %w", CcEnableArenasID, err) - } - return ccEnableArenas(logger, sweeper, value, validatedOverrides), nil -} - -// GoPackage returns a Modifier that sets the go_package file option -// according to the given defaultImportPathPrefix, exceptions, and -// overrides. -func GoPackage( - logger *zap.Logger, - sweeper Sweeper, - defaultImportPathPrefix string, - except []bufmodule.ModuleFullName, - moduleOverrides map[bufmodule.ModuleFullName]string, - overrides map[string]string, -) (Modifier, error) { - return goPackage( - logger, - sweeper, - defaultImportPathPrefix, - except, - moduleOverrides, - overrides, - ) -} - -// JavaMultipleFiles returns a Modifier that sets the java_multiple_files -// file option to the given value in all of the files contained in -// the Image. If the preserveExistingValue is set to true, then the -// java_multiple_files option will be preserved if set. -func JavaMultipleFiles( - logger *zap.Logger, - sweeper Sweeper, - value bool, - overrides map[string]string, - preserveExistingValue bool, -) (Modifier, error) { - validatedOverrides, err := stringOverridesToBoolOverrides(overrides) - if err != nil { - return nil, fmt.Errorf("invalid override for %s: %w", JavaMultipleFilesID, err) - } - return javaMultipleFiles(logger, sweeper, value, validatedOverrides, preserveExistingValue), nil -} - -// JavaOuterClassname returns a Modifier that sets the java_outer_classname file option -// in all of the files contained in the Image based on the PascalCase of their filename. -// If the preserveExistingValue is set to true, then the java_outer_classname option will -// be preserved if set. -func JavaOuterClassname( - logger *zap.Logger, - sweeper Sweeper, - overrides map[string]string, - preserveExistingValue bool, -) Modifier { - return javaOuterClassname(logger, sweeper, overrides, preserveExistingValue) -} - -// JavaPackage returns a Modifier that sets the java_package file option -// according to the given packagePrefix. -func JavaPackage( - logger *zap.Logger, - sweeper Sweeper, - defaultPrefix string, - except []bufmodule.ModuleFullName, - moduleOverrides map[bufmodule.ModuleFullName]string, - overrides map[string]string, -) (Modifier, error) { - return javaPackage( - logger, - sweeper, - defaultPrefix, - except, - moduleOverrides, - overrides, - ) -} - -// JavaStringCheckUtf8 returns a Modifier that sets the java_string_check_utf8 file option according -// to the given value. -func JavaStringCheckUtf8( - logger *zap.Logger, - sweeper Sweeper, - value bool, - overrides map[string]string, -) (Modifier, error) { - validatedOverrides, err := stringOverridesToBoolOverrides(overrides) - if err != nil { - return nil, fmt.Errorf("invalid override for %s: %w", JavaStringCheckUtf8ID, err) } - return javaStringCheckUtf8(logger, sweeper, value, validatedOverrides), nil -} - -// OptimizeFor returns a Modifier that sets the optimize_for file -// option to the given value in all of the files contained in -// the Image. -func OptimizeFor( - logger *zap.Logger, - sweeper Sweeper, - defaultOptimizeFor descriptorpb.FileOptions_OptimizeMode, - except []bufmodule.ModuleFullName, - moduleOverrides map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode, - overrides map[string]string, -) (Modifier, error) { - validatedOverrides, err := stringOverridesToOptimizeModeOverrides(overrides) - if err != nil { - return nil, fmt.Errorf("invalid override for %s: %w", OptimizeForID, err) - } - return optimizeFor(logger, sweeper, defaultOptimizeFor, except, moduleOverrides, validatedOverrides), nil -} - -// GoPackageImportPathForFile returns the go_package import path for the given -// ImageFile. If the package contains a version suffix, and if there are more -// than two components, concatenate the final two components. Otherwise, we -// exclude the ';' separator and adopt the default behavior from the import path. -// -// For example, an ImageFile with `package acme.weather.v1;` will include `;weatherv1` -// in the `go_package` declaration so that the generated package is named as such. -func GoPackageImportPathForFile(imageFile bufimage.ImageFile, importPathPrefix string) string { - goPackageImportPath := path.Join(importPathPrefix, path.Dir(imageFile.Path())) - packageName := imageFile.FileDescriptorProto().GetPackage() - if _, ok := protoversion.NewPackageVersionForPackage(packageName); ok { - parts := strings.Split(packageName, ".") - if len(parts) >= 2 { - goPackageImportPath += ";" + parts[len(parts)-2] + parts[len(parts)-1] + sweeper := internal.NewMarkSweeper(image) + for _, imageFile := range image.Files() { + if datawkt.Exists(imageFile.Path()) { + continue } - } - return goPackageImportPath -} - -// ObjcClassPrefix returns a Modifier that sets the objc_class_prefix file option -// according to the package name. It is set to the uppercase first letter of each package sub-name, -// not including the package version, with the following rules: -// - If the resulting abbreviation is 2 characters, add "X". -// - If the resulting abbreviation is 1 character, add "XX". -// - If the resulting abbreviation is "GPB", change it to "GPX". -// "GPB" is reserved by Google for the Protocol Buffers implementation. -func ObjcClassPrefix( - logger *zap.Logger, - sweeper Sweeper, - defaultPrefix string, - except []bufmodule.ModuleFullName, - moduleOverride map[bufmodule.ModuleFullName]string, - overrides map[string]string, -) Modifier { - return objcClassPrefix(logger, sweeper, defaultPrefix, except, moduleOverride, overrides) -} - -// CsharpNamespace returns a Modifier that sets the csharp_namespace file option -// according to the package name. It is set to the package name with each package sub-name capitalized. -func CsharpNamespace( - logger *zap.Logger, - sweeper Sweeper, - except []bufmodule.ModuleFullName, - moduleOverrides map[bufmodule.ModuleFullName]string, - overrides map[string]string, -) Modifier { - return csharpNamespace( - logger, - sweeper, - except, - moduleOverrides, - overrides, - ) -} - -// PhpNamespace returns a Modifier that sets the php_namespace file option -// according to the package name. It is set to the package name with each package sub-name capitalized -// and each "." replaced with "\\". -func PhpNamespace( - logger *zap.Logger, - sweeper Sweeper, - overrides map[string]string, -) Modifier { - return phpNamespace(logger, sweeper, overrides) -} - -// PhpMetadataNamespace returns a Modifier that sets the php_metadata_namespace file option -// according to the package name. It appends "\\GPBMetadata" to the heuristic used by PhpNamespace. -func PhpMetadataNamespace( - logger *zap.Logger, - sweeper Sweeper, - overrides map[string]string, -) Modifier { - return phpMetadataNamespace(logger, sweeper, overrides) -} - -// RubyPackage returns a Modifier that sets the ruby_package file option -// according to the given packagePrefix. It is set to the package name with each package sub-name capitalized -// and each "." replaced with "::". -func RubyPackage( - logger *zap.Logger, - sweeper Sweeper, - except []bufmodule.ModuleFullName, - moduleOverrides map[bufmodule.ModuleFullName]string, - overrides map[string]string, -) Modifier { - return rubyPackage( - logger, - sweeper, - except, - moduleOverrides, - overrides, - ) -} - -// isWellKnownType returns true if the given path is one of the well-known types. -func isWellKnownType(ctx context.Context, imageFile bufimage.ImageFile) bool { - return datawkt.Exists(imageFile.Path()) -} - -// int32SliceIsEqual returns true if x and y contain the same elements. -func int32SliceIsEqual(x []int32, y []int32) bool { - if len(x) != len(y) { - return false - } - for i, elem := range x { - if elem != y[i] { - return false + modifyFuncs := []func(internal.MarkSweeper, bufimage.ImageFile, bufconfig.GenerateManagedConfig) error{ + modifyCcEnableArenas, + modifyCsharpNamespace, + modifyGoPackage, + modifyJavaMultipleFiles, + modifyJavaOuterClass, + modifyJavaPackage, + modifyJavaStringCheckUtf8, + modifyObjcClassPrefix, + modifyOptmizeFor, + modifyPhpMetadataNamespace, + modifyPhpNamespace, + modifyRubyPackage, + modifyJsType, } - } - return true -} - -func stringOverridesToBoolOverrides(stringOverrides map[string]string) (map[string]bool, error) { - validatedOverrides := make(map[string]bool, len(stringOverrides)) - for fileImportPath, overrideString := range stringOverrides { - overrideBool, err := strconv.ParseBool(overrideString) - if err != nil { - return nil, fmt.Errorf("non-boolean override %s set for file %s", overrideString, fileImportPath) - } - validatedOverrides[fileImportPath] = overrideBool - } - return validatedOverrides, nil -} - -func stringOverridesToOptimizeModeOverrides(stringOverrides map[string]string) (map[string]descriptorpb.FileOptions_OptimizeMode, error) { - validatedOverrides := make(map[string]descriptorpb.FileOptions_OptimizeMode, len(stringOverrides)) - for fileImportPath, stringOverride := range stringOverrides { - optimizeMode, ok := descriptorpb.FileOptions_OptimizeMode_value[stringOverride] - if !ok { - return nil, fmt.Errorf("invalid optimize mode %s set for file %s", stringOverride, fileImportPath) + for _, modifyFunc := range modifyFuncs { + if err := modifyFunc(sweeper, imageFile, config); err != nil { + return err + } } - validatedOverrides[fileImportPath] = descriptorpb.FileOptions_OptimizeMode(optimizeMode) } - return validatedOverrides, nil + return nil } diff --git a/private/bufpkg/bufimage/bufimagemodify/bufimagemodify_test.go b/private/bufpkg/bufimage/bufimagemodify/bufimagemodify_test.go index 2e51e6a09c..0b8dbc7d99 100644 --- a/private/bufpkg/bufimage/bufimagemodify/bufimagemodify_test.go +++ b/private/bufpkg/bufimage/bufimagemodify/bufimagemodify_test.go @@ -16,65 +16,799 @@ package bufimagemodify import ( "context" + "path/filepath" "testing" + "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufimage" + "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimagemodify/internal" + "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimagetesting" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/bufpkg/bufmodule/bufmoduletesting" "github.com/bufbuild/buf/private/pkg/tracing" - "github.com/stretchr/testify/assert" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/descriptorpb" ) -const ( - testImportPathPrefix = "github.com/foo/bar/private/gen/proto/go" - testRemote = "buf.test" - testRepositoryOwner = "foo" - testRepositoryName = "bar" -) - -func assertFileOptionSourceCodeInfoEmpty(t *testing.T, image bufimage.Image, fileOptionPath []int32, includeSourceInfo bool) { - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - - if !includeSourceInfo { - assert.Empty(t, descriptor.SourceCodeInfo) - continue +func TestModifyImage(t *testing.T) { + t.Parallel() + testcases := []struct { + description string + dirPathToModuleFullName map[string]string + config bufconfig.GenerateManagedConfig + filePathToExpectedOptions map[string]*descriptorpb.FileOptions + }{ + { + description: "nil_config", + dirPathToModuleFullName: map[string]string{ + filepath.Join("testdata", "foo"): "buf.build/acme/foo", + filepath.Join("testdata", "bar"): "buf.build/acme/bar", + }, + config: bufconfig.NewGenerateManagedConfig(false, nil, nil), + filePathToExpectedOptions: map[string]*descriptorpb.FileOptions{ + "foo_empty/with_package.proto": nil, + "bar_all/with_package.proto": { + CcEnableArenas: proto.Bool(false), + CcGenericServices: proto.Bool(false), + CsharpNamespace: proto.String("bar"), + GoPackage: proto.String("bar"), + JavaGenericServices: proto.Bool(false), + JavaMultipleFiles: proto.Bool(false), + JavaOuterClassname: proto.String("bar"), + JavaPackage: proto.String("bar"), + JavaStringCheckUtf8: proto.Bool(false), + ObjcClassPrefix: proto.String("bar"), + OptimizeFor: descriptorpb.FileOptions_SPEED.Enum(), + PhpClassPrefix: proto.String("bar"), + PhpGenericServices: proto.Bool(false), + PhpMetadataNamespace: proto.String("bar"), + PhpNamespace: proto.String("bar"), + PyGenericServices: proto.Bool(false), + RubyPackage: proto.String("bar"), + SwiftPrefix: proto.String("bar"), + }, + }, + }, + { + description: "empty_config", + dirPathToModuleFullName: map[string]string{ + filepath.Join("testdata", "foo"): "buf.build/acme/foo", + filepath.Join("testdata", "bar"): "buf.build/acme/bar", + }, + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{}, + ), + filePathToExpectedOptions: map[string]*descriptorpb.FileOptions{ + "foo_empty/with_package.proto": { + // CcEnableArena's default value is true + CsharpNamespace: proto.String("Foo.Empty"), + // GoPackage is not modified by default + JavaMultipleFiles: proto.Bool(true), + JavaOuterClassname: proto.String("WithPackageProto"), + JavaPackage: proto.String("com.foo.empty"), + // JavaStringCheckUtf8 is not modified by default + ObjcClassPrefix: proto.String("FEX"), + // OptimizeFor tries to modifiy this value to SPEED, which is already the default + // Empty is a keyword in php + PhpMetadataNamespace: proto.String(`Foo\Empty_\GPBMetadata`), + PhpNamespace: proto.String(`Foo\Empty_`), + RubyPackage: proto.String("Foo::Empty"), + }, + "foo_empty/without_package.proto": { + // CcEnableArena's default value is true + // GoPackage is not modified by default + JavaMultipleFiles: proto.Bool(true), + JavaOuterClassname: proto.String("WithoutPackageProto"), + // JavaStringCheckUtf8 is not modified by default + // OptimizeFor tries to modifiy this value to SPEED, which is already the default + }, + "bar_all/with_package.proto": { + CcEnableArenas: proto.Bool(true), + CcGenericServices: proto.Bool(false), + CsharpNamespace: proto.String("Bar.All"), + GoPackage: proto.String("bar"), + JavaGenericServices: proto.Bool(false), + JavaMultipleFiles: proto.Bool(true), + JavaOuterClassname: proto.String("WithPackageProto"), + JavaPackage: proto.String("com.bar.all"), + JavaStringCheckUtf8: proto.Bool(false), + ObjcClassPrefix: proto.String("BAX"), + OptimizeFor: descriptorpb.FileOptions_SPEED.Enum(), + PhpClassPrefix: proto.String("bar"), + PhpGenericServices: proto.Bool(false), + PhpMetadataNamespace: proto.String(`Bar\All\GPBMetadata`), + PhpNamespace: proto.String(`Bar\All`), + PyGenericServices: proto.Bool(false), + RubyPackage: proto.String("Bar::All"), + SwiftPrefix: proto.String("bar"), + }, + "bar_all/without_package.proto": { + CcEnableArenas: proto.Bool(true), + CcGenericServices: proto.Bool(false), + CsharpNamespace: proto.String("bar"), + GoPackage: proto.String("bar"), + JavaGenericServices: proto.Bool(false), + JavaMultipleFiles: proto.Bool(true), + JavaOuterClassname: proto.String("WithoutPackageProto"), + JavaPackage: proto.String("bar"), + JavaStringCheckUtf8: proto.Bool(false), + ObjcClassPrefix: proto.String("bar"), + OptimizeFor: descriptorpb.FileOptions_SPEED.Enum(), + PhpClassPrefix: proto.String("bar"), + PhpGenericServices: proto.Bool(false), + PhpMetadataNamespace: proto.String(`bar`), + PhpNamespace: proto.String(`bar`), + PyGenericServices: proto.Bool(false), + RubyPackage: proto.String("bar"), + SwiftPrefix: proto.String("bar"), + }, + }, + }, + } + for _, testcase := range testcases { + testcase := testcase + for _, includeSourceInfo := range []bool{true, false} { + includeSourceInfo := includeSourceInfo + t.Run(testcase.description, func(t *testing.T) { + t.Parallel() + image := testGetImageFromDirs(t, testcase.dirPathToModuleFullName, includeSourceInfo) + err := Modify( + context.Background(), + image, + testcase.config, + ) + require.NoError(t, err) + for filePath, expectedOptions := range testcase.filePathToExpectedOptions { + imageFile := image.GetFile(filePath) + require.NotNil(t, imageFile) + require.Empty( + t, + cmp.Diff(expectedOptions, imageFile.FileDescriptorProto().GetOptions(), protocmp.Transform()), + imageFile.FileDescriptorProto().GetOptions(), + ) + } + }) } + } +} - var hasFileOption bool - for _, location := range descriptor.SourceCodeInfo.Location { - if len(location.Path) > 0 && int32SliceIsEqual(location.Path, fileOptionPath) { - hasFileOption = true - break - } +func TestModifyImageFile( + t *testing.T, +) { + t.Parallel() + testcases := []struct { + description string + dirPathToModuleFullName map[string]string + config bufconfig.GenerateManagedConfig + modifyFunc func(internal.MarkSweeper, bufimage.ImageFile, bufconfig.GenerateManagedConfig) error + filePathToExpectedOptions map[string]*descriptorpb.FileOptions + filePathToExpectedMarkedLocationPaths map[string][][]int32 + }{ + { + description: "cc_enable_arena", + dirPathToModuleFullName: map[string]string{ + filepath.Join("testdata", "foo"): "buf.build/acme/foo", + filepath.Join("testdata", "bar"): "buf.build/acme/bar", + }, + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "buf.build/acme/bar", bufconfig.FileOptionCcEnableArenas, false), + }, + ), + modifyFunc: modifyCcEnableArenas, + filePathToExpectedOptions: map[string]*descriptorpb.FileOptions{ + "foo_empty/without_package.proto": nil, + "bar_empty/without_package.proto": { + CcEnableArenas: proto.Bool(false), + }, + }, + filePathToExpectedMarkedLocationPaths: map[string][][]int32{ + "bar_empty/without_package.proto": {ccEnableArenasPath}, + }, + }, + { + description: "csharp_namespace", + dirPathToModuleFullName: map[string]string{ + filepath.Join("testdata", "foo"): "buf.build/acme/foo", + filepath.Join("testdata", "bar"): "buf.build/acme/bar", + }, + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{ + newTestManagedDisableRule(t, "foo_empty/with_package.proto", "", "", bufconfig.FileOptionCsharpNamespacePrefix, bufconfig.FieldOptionUnspecified), + }, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "bar_empty", "buf.build/acme/bar", bufconfig.FileOptionCsharpNamespacePrefix, "BarPrefix"), + newTestFileOptionOverrideRule(t, "bar_empty/without_package.proto", "buf.build/acme/bar", bufconfig.FileOptionCsharpNamespace, "BarValue"), + newTestFileOptionOverrideRule(t, "", "buf.build/acme/foo", bufconfig.FileOptionCsharpNamespace, "FooValue"), + newTestFileOptionOverrideRule(t, "foo_empty", "buf.build/acme/foo", bufconfig.FileOptionCsharpNamespacePrefix, "FooPrefix"), + }, + ), + modifyFunc: modifyCsharpNamespace, + filePathToExpectedOptions: map[string]*descriptorpb.FileOptions{ + "bar_empty/with_package.proto": { + CsharpNamespace: proto.String("BarPrefix.Bar.Empty"), + }, + "bar_empty/without_package.proto": { + CsharpNamespace: proto.String("BarValue"), + }, + "foo_empty/with_package.proto": { + CsharpNamespace: proto.String("FooValue"), + }, + "foo_empty/without_package.proto": nil, + }, + filePathToExpectedMarkedLocationPaths: map[string][][]int32{ + "bar_empty/with_package.proto": {csharpNamespacePath}, + "bar_empty/without_package.proto": {csharpNamespacePath}, + "foo_empty/with_package.proto": {csharpNamespacePath}, + }, + }, + { + description: "go_package", + dirPathToModuleFullName: map[string]string{ + filepath.Join("testdata", "foo"): "buf.build/acme/foo", + filepath.Join("testdata", "bar"): "buf.build/acme/bar", + }, + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{ + newTestManagedDisableRule(t, "foo_empty/with_package.proto", "", "", bufconfig.FileOptionGoPackagePrefix, bufconfig.FieldOptionUnspecified), + }, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "bar_empty", "buf.build/acme/bar", bufconfig.FileOptionGoPackagePrefix, "barprefix"), + newTestFileOptionOverrideRule(t, "bar_empty/without_package.proto", "buf.build/acme/bar", bufconfig.FileOptionGoPackage, "barvalue"), + newTestFileOptionOverrideRule(t, "foo_empty/with_package.proto", "buf.build/acme/foo", bufconfig.FileOptionGoPackage, "foovalue"), + newTestFileOptionOverrideRule(t, "foo_empty", "buf.build/acme/foo", bufconfig.FileOptionGoPackagePrefix, "fooprefix"), + }, + ), + modifyFunc: modifyGoPackage, + filePathToExpectedOptions: map[string]*descriptorpb.FileOptions{ + "bar_empty/with_package.proto": { + GoPackage: proto.String("barprefix/bar_empty"), + }, + "bar_empty/without_package.proto": { + GoPackage: proto.String("barvalue"), + }, + "foo_empty/with_package.proto": { + GoPackage: proto.String("foovalue"), + }, + "foo_empty/without_package.proto": { + GoPackage: proto.String("fooprefix/foo_empty"), + }, + }, + filePathToExpectedMarkedLocationPaths: map[string][][]int32{ + "bar_empty/with_package.proto": {goPackagePath}, + "bar_empty/without_package.proto": {goPackagePath}, + "foo_empty/with_package.proto": {goPackagePath}, + "foo_empty/without_package.proto": {goPackagePath}, + }, + }, + { + description: "java_package_prefix", + dirPathToModuleFullName: map[string]string{ + filepath.Join("testdata", "foo"): "buf.build/acme/foo", + filepath.Join("testdata", "bar"): "buf.build/acme/bar", + }, + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{ + newTestManagedDisableRule(t, "bar_empty", "", "", bufconfig.FileOptionJavaPackagePrefix, bufconfig.FieldOptionUnspecified), + }, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "buf.build/acme/bar", bufconfig.FileOptionJavaPackagePrefix, "barprefix"), + newTestFileOptionOverrideRule(t, "", "buf.build/acme/foo", bufconfig.FileOptionJavaPackageSuffix, "foosuffix"), + }, + ), + modifyFunc: modifyJavaPackage, + filePathToExpectedOptions: map[string]*descriptorpb.FileOptions{ + "foo_empty/with_package.proto": { + // default prefix and override suffix + JavaPackage: proto.String("com.foo.empty.foosuffix"), + }, + // prefix is disabled + "bar_empty/with_package.proto": nil, + // prefix is overridden + "bar_all/with_package.proto": { + JavaPackage: proto.String("barprefix.bar.all"), + // below this point are the values from the file + CcEnableArenas: proto.Bool(false), + CcGenericServices: proto.Bool(false), + CsharpNamespace: proto.String("bar"), + GoPackage: proto.String("bar"), + JavaGenericServices: proto.Bool(false), + JavaMultipleFiles: proto.Bool(false), + JavaOuterClassname: proto.String("bar"), + JavaStringCheckUtf8: proto.Bool(false), + ObjcClassPrefix: proto.String("bar"), + OptimizeFor: descriptorpb.FileOptions_SPEED.Enum(), + PhpClassPrefix: proto.String("bar"), + PhpGenericServices: proto.Bool(false), + PhpMetadataNamespace: proto.String("bar"), + PhpNamespace: proto.String("bar"), + PyGenericServices: proto.Bool(false), + RubyPackage: proto.String("bar"), + SwiftPrefix: proto.String("bar"), + }, + // not modified because it doesn't have a package + "foo_empty/without_package.proto": nil, + "bar_empty/without_package.proto": nil, + "foo_all/without_package.proto": { + // values are from the file + CcEnableArenas: proto.Bool(true), + CcGenericServices: proto.Bool(true), + CsharpNamespace: proto.String("foo"), + GoPackage: proto.String("foo"), + JavaGenericServices: proto.Bool(true), + JavaMultipleFiles: proto.Bool(true), + JavaOuterClassname: proto.String("foo"), + JavaPackage: proto.String("foo"), + JavaStringCheckUtf8: proto.Bool(true), + ObjcClassPrefix: proto.String("foo"), + OptimizeFor: descriptorpb.FileOptions_CODE_SIZE.Enum(), + PhpClassPrefix: proto.String("foo"), + PhpGenericServices: proto.Bool(true), + PhpMetadataNamespace: proto.String("foo"), + PhpNamespace: proto.String("foo"), + PyGenericServices: proto.Bool(true), + RubyPackage: proto.String("foo"), + SwiftPrefix: proto.String("foo"), + }, + "bar_all/without_package.proto": { + // values are from the file + CcEnableArenas: proto.Bool(false), + CcGenericServices: proto.Bool(false), + CsharpNamespace: proto.String("bar"), + GoPackage: proto.String("bar"), + JavaGenericServices: proto.Bool(false), + JavaMultipleFiles: proto.Bool(false), + JavaOuterClassname: proto.String("bar"), + JavaPackage: proto.String("bar"), + JavaStringCheckUtf8: proto.Bool(false), + ObjcClassPrefix: proto.String("bar"), + OptimizeFor: descriptorpb.FileOptions_SPEED.Enum(), + PhpClassPrefix: proto.String("bar"), + PhpGenericServices: proto.Bool(false), + PhpMetadataNamespace: proto.String("bar"), + PhpNamespace: proto.String("bar"), + PyGenericServices: proto.Bool(false), + RubyPackage: proto.String("bar"), + SwiftPrefix: proto.String("bar"), + }, + }, + filePathToExpectedMarkedLocationPaths: map[string][][]int32{ + "foo_empty/with_package.proto": {javaPackagePath}, + "bar_all/with_package.proto": {javaPackagePath}, + }, + }, + { + description: "java_package_suffix", + dirPathToModuleFullName: map[string]string{ + filepath.Join("testdata", "foo"): "buf.build/acme/foo", + filepath.Join("testdata", "bar"): "buf.build/acme/bar", + }, + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{ + newTestManagedDisableRule(t, "bar_empty", "", "", bufconfig.FileOptionJavaPackageSuffix, bufconfig.FieldOptionUnspecified), + }, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackageSuffix, "suffix"), + }, + ), + modifyFunc: modifyJavaPackage, + filePathToExpectedOptions: map[string]*descriptorpb.FileOptions{ + "foo_empty/with_package.proto": { + // only suffix matches, but apply both prefix and suffix + JavaPackage: proto.String("com.foo.empty.suffix"), + }, + "bar_empty/with_package.proto": { + // only prefix because suffix is disabled + JavaPackage: proto.String("com.bar.empty"), + }, + "bar_all/with_package.proto": { + JavaPackage: proto.String("com.bar.all.suffix"), + // below this point are the values from the file + CcEnableArenas: proto.Bool(false), + CcGenericServices: proto.Bool(false), + CsharpNamespace: proto.String("bar"), + GoPackage: proto.String("bar"), + JavaGenericServices: proto.Bool(false), + JavaMultipleFiles: proto.Bool(false), + JavaOuterClassname: proto.String("bar"), + JavaStringCheckUtf8: proto.Bool(false), + ObjcClassPrefix: proto.String("bar"), + OptimizeFor: descriptorpb.FileOptions_SPEED.Enum(), + PhpClassPrefix: proto.String("bar"), + PhpGenericServices: proto.Bool(false), + PhpMetadataNamespace: proto.String("bar"), + PhpNamespace: proto.String("bar"), + PyGenericServices: proto.Bool(false), + RubyPackage: proto.String("bar"), + SwiftPrefix: proto.String("bar"), + }, + // not modified + "foo_empty/without_package.proto": nil, + }, + filePathToExpectedMarkedLocationPaths: map[string][][]int32{ + "foo_empty/with_package.proto": {javaPackagePath}, + "bar_empty/with_package.proto": {javaPackagePath}, + "bar_all/with_package.proto": {javaPackagePath}, + }, + }, + { + description: "java_package", + dirPathToModuleFullName: map[string]string{ + filepath.Join("testdata", "foo"): "buf.build/acme/foo", + filepath.Join("testdata", "bar"): "buf.build/acme/bar", + }, + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{ + newTestManagedDisableRule(t, "bar_empty", "", "", bufconfig.FileOptionJavaPackage, bufconfig.FieldOptionUnspecified), + }, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "buf.build/acme/bar", bufconfig.FileOptionJavaPackage, "bar.value"), + newTestFileOptionOverrideRule(t, "", "buf.build/acme/foo", bufconfig.FileOptionJavaPackage, "foo.value"), + }, + ), + modifyFunc: modifyJavaPackage, + filePathToExpectedOptions: map[string]*descriptorpb.FileOptions{ + // bar_empty disabled + "bar_empty/with_package.proto": nil, + "bar_empty/without_package.proto": nil, + "bar_all/with_package.proto": { + JavaPackage: proto.String("bar.value"), + CcEnableArenas: proto.Bool(false), + CcGenericServices: proto.Bool(false), + CsharpNamespace: proto.String("bar"), + GoPackage: proto.String("bar"), + JavaGenericServices: proto.Bool(false), + JavaMultipleFiles: proto.Bool(false), + JavaOuterClassname: proto.String("bar"), + JavaStringCheckUtf8: proto.Bool(false), + ObjcClassPrefix: proto.String("bar"), + OptimizeFor: descriptorpb.FileOptions_SPEED.Enum(), + PhpClassPrefix: proto.String("bar"), + PhpGenericServices: proto.Bool(false), + PhpMetadataNamespace: proto.String("bar"), + PhpNamespace: proto.String("bar"), + PyGenericServices: proto.Bool(false), + RubyPackage: proto.String("bar"), + SwiftPrefix: proto.String("bar"), + }, + "foo_empty/with_package.proto": { + JavaPackage: proto.String("foo.value"), + }, + "foo_empty/without_package.proto": { + JavaPackage: proto.String("foo.value"), + }, + }, + filePathToExpectedMarkedLocationPaths: map[string][][]int32{ + "foo_empty/with_package.proto": {javaPackagePath}, + "foo_empty/without_package.proto": {javaPackagePath}, + "bar_all/with_package.proto": {javaPackagePath}, + }, + }, + { + description: "objc_class_prefix", + dirPathToModuleFullName: map[string]string{ + filepath.Join("testdata", "foo"): "buf.build/acme/foo", + filepath.Join("testdata", "bar"): "buf.build/acme/bar", + }, + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{ + newTestManagedDisableRule(t, "foo_empty/with_package.proto", "", "", bufconfig.FileOptionObjcClassPrefix, bufconfig.FieldOptionUnspecified), + }, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "buf.build/acme/bar", bufconfig.FileOptionObjcClassPrefix, "BAR"), + newTestFileOptionOverrideRule(t, "", "buf.build/acme/foo", bufconfig.FileOptionObjcClassPrefix, "FOO"), + newTestFileOptionOverrideRule(t, "foo_all", "buf.build/acme/foo", bufconfig.FileOptionObjcClassPrefix, "FOOALL"), + }, + ), + modifyFunc: modifyObjcClassPrefix, + filePathToExpectedOptions: map[string]*descriptorpb.FileOptions{ + "bar_empty/with_package.proto": { + ObjcClassPrefix: proto.String("BAR"), + }, + "bar_empty/without_package.proto": { + ObjcClassPrefix: proto.String("BAR"), + }, + // disabled + "foo_empty/with_package.proto": nil, + // no package + "foo_empty/without_package.proto": { + ObjcClassPrefix: proto.String("FOO"), + }, + "foo_all/with_package.proto": { + ObjcClassPrefix: proto.String("FOOALL"), + CcEnableArenas: proto.Bool(true), + CcGenericServices: proto.Bool(true), + CsharpNamespace: proto.String("foo"), + GoPackage: proto.String("foo"), + JavaGenericServices: proto.Bool(true), + JavaMultipleFiles: proto.Bool(true), + JavaOuterClassname: proto.String("foo"), + JavaPackage: proto.String("foo"), + JavaStringCheckUtf8: proto.Bool(true), + OptimizeFor: descriptorpb.FileOptions_CODE_SIZE.Enum(), + PhpClassPrefix: proto.String("foo"), + PhpGenericServices: proto.Bool(true), + PhpMetadataNamespace: proto.String("foo"), + PhpNamespace: proto.String("foo"), + PyGenericServices: proto.Bool(true), + RubyPackage: proto.String("foo"), + SwiftPrefix: proto.String("foo"), + }, + }, + filePathToExpectedMarkedLocationPaths: map[string][][]int32{ + "bar_empty/with_package.proto": {objcClassPrefixPath}, + "bar_empty/without_package.proto": {objcClassPrefixPath}, + "foo_empty/without_package.proto": {objcClassPrefixPath}, + "foo_all/without_package.proto": {objcClassPrefixPath}, + "foo_all/with_package.proto": {objcClassPrefixPath}, + }, + }, + } + for _, testcase := range testcases { + testcase := testcase + for _, includeSourceInfo := range []bool{true, false} { + // TODO: we are only testing sweep here, no need to test both include and exclude source info + includeSourceInfo := includeSourceInfo + t.Run(testcase.description, func(t *testing.T) { + t.Parallel() + image := testGetImageFromDirs(t, testcase.dirPathToModuleFullName, includeSourceInfo) + sweeper := internal.NewMarkSweeper(image) + // TODO: check include source code info + for filePath, expectedOptions := range testcase.filePathToExpectedOptions { + imageFile := image.GetFile(filePath) + testcase.modifyFunc( + sweeper, + imageFile, + testcase.config, + ) + require.NotNil(t, imageFile) + require.Empty( + t, + cmp.Diff(expectedOptions, imageFile.FileDescriptorProto().GetOptions(), protocmp.Transform()), + "incorrect options result for %s", + filePath, + ) + // TODO: sweep and check paths gone + } + }) } - assert.False(t, hasFileOption) } } -func assertFileOptionSourceCodeInfoNotEmpty(t *testing.T, image bufimage.Image, fileOptionPath []int32) { - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - - var hasFileOption bool - for _, location := range descriptor.SourceCodeInfo.Location { - if len(location.Path) > 0 && int32SliceIsEqual(location.Path, fileOptionPath) { - hasFileOption = true - break - } - } - assert.True(t, hasFileOption) +// TODO: add default values +func TestGetStringOverrideFromConfig(t *testing.T) { + t.Parallel() + testcases := []struct { + description string + config bufconfig.GenerateManagedConfig + imageFile bufimage.ImageFile + defaultOverrideOptions stringOverrideOptions + expectedOverride stringOverrideOptions + expectedDisable bool + }{ + { + description: "only_value", + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackage, "value"), + }, + ), + imageFile: testGetImageFile(t, "a.proto", "buf.build/foo/bar"), + expectedOverride: stringOverrideOptions{value: "value"}, + }, + { + description: "only_prefix", + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackagePrefix, "prefix"), + }, + ), + imageFile: testGetImageFile(t, "a.proto", "buf.build/foo/bar"), + expectedOverride: stringOverrideOptions{prefix: "prefix"}, + }, + { + description: "only_suffix", + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackageSuffix, "suffix"), + }, + ), + imageFile: testGetImageFile(t, "a.proto", "buf.build/foo/bar"), + expectedOverride: stringOverrideOptions{suffix: "suffix"}, + }, + { + description: "prefix_then_value", + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackagePrefix, "prefix"), + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackage, "value"), + }, + ), + imageFile: testGetImageFile(t, "a.proto", "buf.build/foo/bar"), + expectedOverride: stringOverrideOptions{value: "value"}, + }, + { + description: "value_then_prefix", + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackage, "value"), + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackagePrefix, "prefix"), + }, + ), + imageFile: testGetImageFile(t, "a.proto", "buf.build/foo/bar"), + expectedOverride: stringOverrideOptions{prefix: "prefix"}, + }, + { + description: "prefix_then_suffix", + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackagePrefix, "prefix"), + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackageSuffix, "suffix"), + }, + ), + imageFile: testGetImageFile(t, "a.proto", "buf.build/foo/bar"), + expectedOverride: stringOverrideOptions{ + prefix: "prefix", + suffix: "suffix", + }, + }, + { + description: "value_prefix_then_suffix", + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackage, "value"), + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackagePrefix, "prefix"), + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackageSuffix, "suffix"), + }, + ), + imageFile: testGetImageFile(t, "a.proto", "buf.build/foo/bar"), + expectedOverride: stringOverrideOptions{ + prefix: "prefix", + suffix: "suffix", + }, + }, + { + description: "prefix_value_then_suffix", + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackagePrefix, "prefix"), + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackage, "value"), + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackageSuffix, "suffix"), + }, + ), + imageFile: testGetImageFile(t, "a.proto", "buf.build/foo/bar"), + expectedOverride: stringOverrideOptions{suffix: "suffix"}, + }, + { + description: "prefix_then_prefix", + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackagePrefix, "prefix"), + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackagePrefix, "prefix2"), + }, + ), + imageFile: testGetImageFile(t, "a.proto", "buf.build/foo/bar"), + expectedOverride: stringOverrideOptions{prefix: "prefix2"}, + }, + { + description: "suffix_then_suffix", + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackageSuffix, "suffix"), + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackageSuffix, "suffix2"), + }, + ), + imageFile: testGetImageFile(t, "a.proto", "buf.build/foo/bar"), + expectedOverride: stringOverrideOptions{suffix: "suffix2"}, + }, + { + description: "value_then_value", + config: bufconfig.NewGenerateManagedConfig( + true, + []bufconfig.ManagedDisableRule{}, + []bufconfig.ManagedOverrideRule{ + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackage, "value"), + newTestFileOptionOverrideRule(t, "", "", bufconfig.FileOptionJavaPackage, "value2"), + }, + ), + imageFile: testGetImageFile(t, "a.proto", "buf.build/foo/bar"), + expectedOverride: stringOverrideOptions{value: "value2"}, + }, + } + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.description, func(t *testing.T) { + t.Parallel() + override, err := stringOverrideFromConfig( + testcase.imageFile, + testcase.config, + testcase.defaultOverrideOptions, + bufconfig.FileOptionJavaPackage, + bufconfig.FileOptionJavaPackagePrefix, + bufconfig.FileOptionJavaPackageSuffix, + ) + require.NoError(t, err) + require.Equal(t, testcase.expectedOverride, override) + }) } } -func testGetImage(t *testing.T, dirPath string, includeSourceInfo bool) bufimage.Image { - moduleSet, err := bufmoduletesting.NewModuleSet( - bufmoduletesting.ModuleData{ - Name: testRemote + "/" + testRepositoryOwner + "/" + testRepositoryName, - DirPath: dirPath, +func TestModifyFieldOption(t *testing.T) { + t.Parallel() + // TODO in v2 +} + +func testGetImageFile( + t *testing.T, + path string, + moduleFullName string, +) bufimage.ImageFile { + parsedModuleFullName, err := bufmodule.ParseModuleFullName(moduleFullName) + require.NoError(t, err) + return bufimagetesting.NewImageFile( + t, + &descriptorpb.FileDescriptorProto{ + Name: proto.String(path), + Syntax: proto.String("proto3"), }, + parsedModuleFullName, + "", + path, + false, + false, + nil, ) +} + +func testGetImageFromDirs( + t *testing.T, + dirPathToModuleFullName map[string]string, + includeSourceInfo bool, +) bufimage.Image { + moduleDatas := make([]bufmoduletesting.ModuleData, 0, len(dirPathToModuleFullName)) + for dirPath, moduleFullName := range dirPathToModuleFullName { + moduleDatas = append( + moduleDatas, + bufmoduletesting.ModuleData{ + Name: moduleFullName, + DirPath: dirPath, + }, + ) + } + moduleSet, err := bufmoduletesting.NewModuleSet(moduleDatas...) require.NoError(t, err) var options []bufimage.BuildImageOption if !includeSourceInfo { @@ -90,3 +824,58 @@ func testGetImage(t *testing.T, dirPath string, includeSourceInfo bool) bufimage require.Empty(t, annotations) return image } + +func newTestManagedDisableRule( + t *testing.T, + path string, + moduleFullName string, + fieldName string, + fileOption bufconfig.FileOption, + fieldOption bufconfig.FieldOption, +) bufconfig.ManagedDisableRule { + disable, err := bufconfig.NewDisableRule( + path, + moduleFullName, + fieldName, + fileOption, + fieldOption, + ) + require.NoError(t, err) + return disable +} + +func newTestFileOptionOverrideRule( + t *testing.T, + path string, + moduleFullName string, + fileOption bufconfig.FileOption, + value interface{}, +) bufconfig.ManagedOverrideRule { + fileOptionOverride, err := bufconfig.NewFileOptionOverrideRule( + path, + moduleFullName, + fileOption, + value, + ) + require.NoError(t, err) + return fileOptionOverride +} + +func newTestFieldOptionOverrideRule( + t *testing.T, + path string, + moduleFullName string, + fieldName string, + fieldOption bufconfig.FieldOption, + value interface{}, +) bufconfig.ManagedOverrideRule { + fieldOptionOverrid, err := bufconfig.NewFieldOptionOverrideRule( + path, + moduleFullName, + bufconfig.FileOptionPhpMetadataNamespace.String(), + fieldOption, + value, + ) + require.NoError(t, err) + return fieldOptionOverrid +} diff --git a/private/bufpkg/bufimage/bufimagemodify/cc_enable_arenas.go b/private/bufpkg/bufimage/bufimagemodify/cc_enable_arenas.go deleted file mode 100644 index d4e52ac1d2..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/cc_enable_arenas.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/descriptorpb" -) - -// CcEnableArenasID is the ID of the cc_enable_arenas modifier. -const CcEnableArenasID = "CC_ENABLE_ARENAS" - -// ccEnableArenas is the SourceCodeInfo path for the cc_enable_arenas option. -// https://github.com/protocolbuffers/protobuf/blob/29152fbc064921ca982d64a3a9eae1daa8f979bb/src/google/protobuf/descriptor.proto#L420 -var ccEnableArenasPath = []int32{8, 31} - -func ccEnableArenas( - logger *zap.Logger, - sweeper Sweeper, - value bool, - overrides map[string]bool, -) Modifier { - return ModifierFunc( - func(ctx context.Context, image bufimage.Image) error { - seenOverrideFiles := make(map[string]struct{}, len(overrides)) - for _, imageFile := range image.Files() { - modifierValue := value - if overrideValue, ok := overrides[imageFile.Path()]; ok { - modifierValue = overrideValue - seenOverrideFiles[imageFile.Path()] = struct{}{} - } - if err := ccEnableArenasForFile(ctx, sweeper, imageFile, modifierValue); err != nil { - return err - } - } - for overrideFile := range overrides { - if _, ok := seenOverrideFiles[overrideFile]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", CcEnableArenasID, overrideFile) - } - } - return nil - }, - ) -} - -func ccEnableArenasForFile( - ctx context.Context, - sweeper Sweeper, - imageFile bufimage.ImageFile, - value bool, -) error { - descriptor := imageFile.FileDescriptorProto() - options := descriptor.GetOptions() - switch { - case isWellKnownType(ctx, imageFile): - // The file is a well-known type, don't do anything. - return nil - case options != nil && options.GetCcEnableArenas() == value: - // The option is already set to the same value, don't do anything. - return nil - case options == nil && descriptorpb.Default_FileOptions_CcEnableArenas == value: - // The option is not set, but the value we want to set is the - // same as the default, don't do anything. - return nil - } - if options == nil { - descriptor.Options = &descriptorpb.FileOptions{} - } - descriptor.Options.CcEnableArenas = proto.Bool(value) - if sweeper != nil { - sweeper.mark(imageFile.Path(), ccEnableArenasPath) - } - return nil -} diff --git a/private/bufpkg/bufimage/bufimagemodify/cc_enable_arenas_test.go b/private/bufpkg/bufimage/bufimagemodify/cc_enable_arenas_test.go deleted file mode 100644 index 5bcc60e885..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/cc_enable_arenas_test.go +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestCcEnableArenasEmptyOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, true) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, false, nil) - require.NoError(t, err) - modifier := NewMultiModifier( - ccEnableArenasModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.False(t, descriptor.GetOptions().GetCcEnableArenas()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, false) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, false, nil) - require.NoError(t, err) - err = ccEnableArenasModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.False(t, descriptor.GetOptions().GetCcEnableArenas()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, true) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, false, map[string]string{"a.proto": "true"}) - require.NoError(t, err) - modifier := NewMultiModifier( - ccEnableArenasModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.True(t, descriptor.GetOptions().GetCcEnableArenas()) - continue - } - assert.False(t, descriptor.GetOptions().GetCcEnableArenas()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, false) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, false, map[string]string{"a.proto": "true"}) - require.NoError(t, err) - err = ccEnableArenasModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.True(t, descriptor.GetOptions().GetCcEnableArenas()) - continue - } - assert.False(t, descriptor.GetOptions().GetCcEnableArenas()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, false) - }) -} - -func TestCcEnableArenasAllOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "alloptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, ccEnableArenasPath) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, true, nil) - require.NoError(t, err) - modifier := NewMultiModifier( - ccEnableArenasModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetCcEnableArenas()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, false) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, true, nil) - require.NoError(t, err) - err = ccEnableArenasModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetCcEnableArenas()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, ccEnableArenasPath) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, true, map[string]string{"a.proto": "false"}) - require.NoError(t, err) - modifier := NewMultiModifier( - ccEnableArenasModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.False(t, descriptor.GetOptions().GetCcEnableArenas()) - continue - } - assert.True(t, descriptor.GetOptions().GetCcEnableArenas()) - } - assertFileOptionSourceCodeInfoNotEmpty(t, image, ccEnableArenasPath) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, false) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, true, map[string]string{"a.proto": "false"}) - require.NoError(t, err) - err = ccEnableArenasModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.False(t, descriptor.GetOptions().GetCcEnableArenas()) - continue - } - assert.True(t, descriptor.GetOptions().GetCcEnableArenas()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, false) - }) -} - -func TestCcEnableArenasCcOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "ccoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, ccEnableArenasPath) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, true, nil) - require.NoError(t, err) - modifier := NewMultiModifier( - ccEnableArenasModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetCcEnableArenas()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, false) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, true, nil) - require.NoError(t, err) - err = ccEnableArenasModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetCcEnableArenas()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, ccEnableArenasPath) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, true, map[string]string{"a.proto": "false"}) - require.NoError(t, err) - modifier := NewMultiModifier( - ccEnableArenasModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.False(t, descriptor.GetOptions().GetCcEnableArenas()) - continue - } - assert.True(t, descriptor.GetOptions().GetCcEnableArenas()) - } - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, false) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, true, map[string]string{"a.proto": "false"}) - require.NoError(t, err) - err = ccEnableArenasModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.False(t, descriptor.GetOptions().GetCcEnableArenas()) - continue - } - assert.True(t, descriptor.GetOptions().GetCcEnableArenas()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, ccEnableArenasPath, false) - }) -} - -func TestCcEnableArenasWellKnownTypes(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "wktimport") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, true, nil) - require.NoError(t, err) - modifier := NewMultiModifier( - ccEnableArenasModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - assert.True(t, imageFile.FileDescriptorProto().GetOptions().GetCcEnableArenas()) - } - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - - sweeper := NewFileOptionSweeper() - ccEnableArenasModifier, err := CcEnableArenas(zap.NewNop(), sweeper, true, nil) - require.NoError(t, err) - err = ccEnableArenasModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - assert.True(t, imageFile.FileDescriptorProto().GetOptions().GetCcEnableArenas()) - } - }) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/csharp_namespace.go b/private/bufpkg/bufimage/bufimagemodify/csharp_namespace.go deleted file mode 100644 index 6bdca80e0b..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/csharp_namespace.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "strings" - - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "github.com/bufbuild/buf/private/pkg/stringutil" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/descriptorpb" -) - -// CsharpNamespaceID is the ID of the csharp_namespace modifier. -const CsharpNamespaceID = "CSHARP_NAMESPACE" - -// csharpNamespacePath is the SourceCodeInfo path for the csharp_namespace option. -// https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L428 -var csharpNamespacePath = []int32{8, 37} - -func csharpNamespace( - logger *zap.Logger, - sweeper Sweeper, - except []bufmodule.ModuleFullName, - moduleOverrides map[bufmodule.ModuleFullName]string, - overrides map[string]string, -) Modifier { - // Convert the bufmodule.ModuleFullName types into - // strings so that they're comparable. - exceptModuleFullNameStrings := make(map[string]struct{}, len(except)) - for _, moduleFullName := range except { - exceptModuleFullNameStrings[moduleFullName.String()] = struct{}{} - } - overrideModuleFullNameStrings := make(map[string]string, len(moduleOverrides)) - for moduleFullName, csharpNamespace := range moduleOverrides { - overrideModuleFullNameStrings[moduleFullName.String()] = csharpNamespace - } - return ModifierFunc( - func(ctx context.Context, image bufimage.Image) error { - seenModuleFullNameStrings := make(map[string]struct{}, len(overrideModuleFullNameStrings)) - seenOverrideFiles := make(map[string]struct{}, len(overrides)) - for _, imageFile := range image.Files() { - csharpNamespaceValue := csharpNamespaceValue(imageFile) - if moduleFullName := imageFile.ModuleFullName(); moduleFullName != nil { - moduleFullNameString := moduleFullName.String() - if moduleNamespaceOverride, ok := overrideModuleFullNameStrings[moduleFullNameString]; ok { - seenModuleFullNameStrings[moduleFullNameString] = struct{}{} - csharpNamespaceValue = moduleNamespaceOverride - } - } - if overrideValue, ok := overrides[imageFile.Path()]; ok { - csharpNamespaceValue = overrideValue - seenOverrideFiles[imageFile.Path()] = struct{}{} - } - if err := csharpNamespaceForFile( - ctx, - sweeper, - imageFile, - csharpNamespaceValue, - exceptModuleFullNameStrings, - ); err != nil { - return err - } - } - for moduleFullNameString := range overrideModuleFullNameStrings { - if _, ok := seenModuleFullNameStrings[moduleFullNameString]; !ok { - logger.Sugar().Warnf("csharp_namespace_prefix override for %q was unused", moduleFullNameString) - } - } - for overrideFile := range overrides { - if _, ok := seenOverrideFiles[overrideFile]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", CsharpNamespaceID, overrideFile) - } - } - return nil - }, - ) -} - -func csharpNamespaceForFile( - ctx context.Context, - sweeper Sweeper, - imageFile bufimage.ImageFile, - csharpNamespaceValue string, - exceptModuleFullNameStrings map[string]struct{}, -) error { - if shouldSkipCsharpNamespaceForFile(ctx, imageFile, csharpNamespaceValue, exceptModuleFullNameStrings) { - // This is a well-known type or we could not resolve a non-empty csharp_namespace - // value, so this is a no-op. - return nil - } - descriptor := imageFile.FileDescriptorProto() - if descriptor.Options == nil { - descriptor.Options = &descriptorpb.FileOptions{} - } - descriptor.Options.CsharpNamespace = proto.String(csharpNamespaceValue) - if sweeper != nil { - sweeper.mark(imageFile.Path(), csharpNamespacePath) - } - return nil -} - -func shouldSkipCsharpNamespaceForFile( - ctx context.Context, - imageFile bufimage.ImageFile, - csharpNamespaceValue string, - exceptModuleFullNameStrings map[string]struct{}, -) bool { - if isWellKnownType(ctx, imageFile) || csharpNamespaceValue == "" { - // This is a well-known type or we could not resolve a non-empty csharp_namespace - // value, so this is a no-op. - return true - } - - if moduleFullName := imageFile.ModuleFullName(); moduleFullName != nil { - if _, ok := exceptModuleFullNameStrings[moduleFullName.String()]; ok { - return true - } - } - return false -} - -// csharpNamespaceValue returns the csharp_namespace for the given ImageFile based on its -// package declaration. If the image file doesn't have a package declaration, an -// empty string is returned. -func csharpNamespaceValue(imageFile bufimage.ImageFile) string { - pkg := imageFile.FileDescriptorProto().GetPackage() - if pkg == "" { - return "" - } - packageParts := strings.Split(pkg, ".") - for i, part := range packageParts { - packageParts[i] = stringutil.ToPascalCase(part) - } - return strings.Join(packageParts, ".") -} diff --git a/private/bufpkg/bufimage/bufimagemodify/csharp_namespace_test.go b/private/bufpkg/bufimage/bufimagemodify/csharp_namespace_test.go deleted file mode 100644 index 27a9921fbd..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/csharp_namespace_test.go +++ /dev/null @@ -1,627 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "path/filepath" - "strings" - "testing" - - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/bufbuild/buf/private/pkg/normalpath" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestCsharpNamespaceEmptyOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, true) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, nil) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, true) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, map[string]string{"a.proto": "foo"}) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - // Overwritten with "foo" in the namespace - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, true) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetCsharpNamespace()) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, map[string]string{"a.proto": "foo"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - // Overwritten with "foo" in the namespace - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetCsharpNamespace()) - }) -} - -func TestCsharpNamespaceAllOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "alloptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, csharpNamespacePath) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, nil) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetCsharpNamespace()) - } - assertFileOptionSourceCodeInfoNotEmpty(t, image, csharpNamespacePath) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetCsharpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, csharpNamespacePath) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, map[string]string{"a.proto": "bar"}) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - // Overwritten with "bar" in the namespace - assert.Equal(t, "bar", descriptor.GetOptions().GetCsharpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, map[string]string{"a.proto": "bar"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - // Overwritten with "bar" in the namespace - assert.Equal(t, "bar", descriptor.GetOptions().GetCsharpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - }) -} - -func TestCsharpNamespaceOptions(t *testing.T) { - t.Parallel() - testCsharpNamespaceOptions(t, filepath.Join("testdata", "csharpoptions", "single"), "Acme.V1") - testCsharpNamespaceOptions(t, filepath.Join("testdata", "csharpoptions", "double"), "Acme.Weather.V1") - testCsharpNamespaceOptions(t, filepath.Join("testdata", "csharpoptions", "triple"), "Acme.Weather.Data.V1") - testCsharpNamespaceOptions(t, filepath.Join("testdata", "csharpoptions", "underscore"), "Acme.Weather.FooBar.V1") -} - -func testCsharpNamespaceOptions(t *testing.T, dirPath string, classPrefix string) { - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, csharpNamespacePath) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, nil) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, classPrefix, descriptor.GetOptions().GetCsharpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, classPrefix, descriptor.GetOptions().GetCsharpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - }) - - t.Run("with SourceCodeInfo and per-file options", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, csharpNamespacePath) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, map[string]string{"override.proto": "Acme.Override.V1"}) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "Acme.Override.V1", descriptor.GetOptions().GetCsharpNamespace()) - continue - } - assert.Equal(t, classPrefix, descriptor.GetOptions().GetCsharpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, map[string]string{"override.proto": "Acme.Override.V1"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "Acme.Override.V1", descriptor.GetOptions().GetCsharpNamespace()) - continue - } - assert.Equal(t, classPrefix, descriptor.GetOptions().GetCsharpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - }) -} - -func TestCsharpNamespaceWellKnownTypes(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "wktimport") - modifiedCsharpNamespace := "Acme.Weather.V1alpha1" - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, nil) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - assert.NotEmpty(t, descriptor.GetOptions().GetCsharpNamespace()) - assert.NotEqual(t, modifiedCsharpNamespace, descriptor.GetOptions().GetCsharpNamespace()) - continue - } - assert.Equal(t, - modifiedCsharpNamespace, - descriptor.GetOptions().GetCsharpNamespace(), - ) - } - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - - sweeper := NewFileOptionSweeper() - modifier := CsharpNamespace(zap.NewNop(), sweeper, nil, nil, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - assert.NotEmpty(t, descriptor.GetOptions().GetCsharpNamespace()) - assert.NotEqual(t, modifiedCsharpNamespace, descriptor.GetOptions().GetCsharpNamespace()) - continue - } - assert.Equal(t, - modifiedCsharpNamespace, - descriptor.GetOptions().GetCsharpNamespace(), - ) - } - }) -} - -func TestCsharpNamespaceWithExcept(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - testModuleFullName, err := bufmodule.NewModuleFullName( - testRemote, - testRepositoryOwner, - testRepositoryName, - ) - require.NoError(t, err) - - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, true) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace( - zap.NewNop(), - sweeper, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - nil, - ) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace( - zap.NewNop(), - sweeper, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - nil, - ) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, true) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace( - zap.NewNop(), - sweeper, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - map[string]string{"a.proto": "override"}, - ) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "", descriptor.GetOptions().GetCsharpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace( - zap.NewNop(), - sweeper, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - map[string]string{"a.proto": "override"}, - ) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "", descriptor.GetOptions().GetCsharpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) -} - -func TestCsharpNamespaceWithOverride(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - overrideCsharpNamespacePrefix := "x.y.z" - testModuleFullName, err := bufmodule.NewModuleFullName( - testRemote, - testRepositoryOwner, - testRepositoryName, - ) - require.NoError(t, err) - - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, true) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace( - zap.NewNop(), - sweeper, - nil, - map[bufmodule.ModuleFullName]string{ - testModuleFullName: overrideCsharpNamespacePrefix, - }, - nil, - ) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - strings.ReplaceAll(normalpath.Dir(overrideCsharpNamespacePrefix+"/"+imageFile.Path()), "/", "."), - descriptor.GetOptions().GetCsharpNamespace(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace( - zap.NewNop(), - sweeper, - nil, - map[bufmodule.ModuleFullName]string{ - testModuleFullName: overrideCsharpNamespacePrefix, - }, - nil, - ) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - strings.ReplaceAll(normalpath.Dir(overrideCsharpNamespacePrefix+"/"+imageFile.Path()), "/", "."), - descriptor.GetOptions().GetCsharpNamespace(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, true) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace( - zap.NewNop(), - sweeper, - nil, - map[bufmodule.ModuleFullName]string{ - testModuleFullName: overrideCsharpNamespacePrefix, - }, - map[string]string{"a.proto": "override"}, - ) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.FileDescriptorProto().Name != nil && *imageFile.FileDescriptorProto().Name == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetCsharpNamespace()) - continue - } - assert.Equal(t, - strings.ReplaceAll(normalpath.Dir(imageFile.Path()), "/", "."), - descriptor.GetOptions().GetCsharpNamespace(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, csharpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - csharpNamespaceModifier := CsharpNamespace( - zap.NewNop(), - sweeper, - nil, - map[bufmodule.ModuleFullName]string{ - testModuleFullName: overrideCsharpNamespacePrefix, - }, - map[string]string{"a.proto": "override"}, - ) - - modifier := NewMultiModifier(csharpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.FileDescriptorProto().Name != nil && *imageFile.FileDescriptorProto().Name == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetCsharpNamespace()) - continue - } - assert.Equal(t, - strings.ReplaceAll(normalpath.Dir(imageFile.Path()), "/", "."), - descriptor.GetOptions().GetCsharpNamespace(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/field_option.go b/private/bufpkg/bufimage/bufimagemodify/field_option.go new file mode 100644 index 0000000000..c275662dbd --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/field_option.go @@ -0,0 +1,124 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 bufimagemodify + +import ( + "fmt" + + "github.com/bufbuild/buf/private/bufpkg/bufconfig" + "github.com/bufbuild/buf/private/bufpkg/bufimage" + "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimagemodify/internal" + "github.com/bufbuild/buf/private/gen/data/datawkt" + "github.com/bufbuild/buf/private/pkg/slicesext" + "github.com/bufbuild/protocompile/walk" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/descriptorpb" +) + +// jsTypeSubPath is the SourceCodeInfo sub path for the jstype field option. +// https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L567 +// TODO: where does the 8 come from????? +var jsTypeSubPath = []int32{8, 6} + +func modifyJsType( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + overrideRules := slicesext.Filter( + config.Overrides(), + func(override bufconfig.ManagedOverrideRule) bool { + return override.FieldOption() == bufconfig.FieldOptionJSType + }, + ) + // Unless specified, js type is not modified. + if len(overrideRules) == 0 { + return nil + } + for _, disable := range config.Disables() { + // If the entire file is disabled, skip. + if fileMatchConfig(imageFile, disable.Path(), disable.ModuleFullName()) && + disable.FieldName() == "" && + (disable.FieldOption() == bufconfig.FieldOptionUnspecified || disable.FieldOption() == bufconfig.FieldOptionJSType) { + return nil + } + } + if datawkt.Exists(imageFile.Path()) { + return nil + } + return walk.DescriptorProtosWithPath( + imageFile.FileDescriptorProto(), + func( + fullName protoreflect.FullName, + path protoreflect.SourcePath, + message proto.Message, + ) error { + fieldDescriptor, ok := message.(*descriptorpb.FieldDescriptorProto) + if !ok { + return nil + } + for _, disable := range config.Disables() { + // If the entire file is disabled, skip. + if fileMatchConfig(imageFile, disable.Path(), disable.ModuleFullName()) && + (disable.FieldName() == "" || disable.FieldName() == string(fullName)) && + (disable.FieldOption() == bufconfig.FieldOptionUnspecified || disable.FieldOption() == bufconfig.FieldOptionJSType) { + return nil + } + } + var jsType *descriptorpb.FieldOptions_JSType + for _, override := range config.Overrides() { + if fileMatchConfig(imageFile, override.Path(), override.ModuleFullName()) && + (override.FieldName() == "" || override.FieldName() == string(fullName)) && + override.FieldOption() == bufconfig.FieldOptionJSType { + jsTypeValue, ok := override.Value().(descriptorpb.FieldOptions_JSType) + if !ok { + return fmt.Errorf("invalid js_type override value of type %T", override.Value()) + } + jsType = &jsTypeValue + } + } + if jsType == nil { + return nil + } + if fieldDescriptor.Type == nil || !isJsTypePermittedForType(*fieldDescriptor.Type) { + return nil + } + if options := fieldDescriptor.Options; options != nil { + if existingJSTYpe := options.Jstype; existingJSTYpe != nil && *existingJSTYpe == *jsType { + return nil + } + } + if fieldDescriptor.Options == nil { + fieldDescriptor.Options = &descriptorpb.FieldOptions{} + } + fieldDescriptor.Options.Jstype = jsType + if len(path) > 0 { + jsTypeOptionPath := append(path, jsTypeSubPath...) + sweeper.Mark(imageFile, jsTypeOptionPath) + } + return nil + }, + ) +} + +func isJsTypePermittedForType(fieldType descriptorpb.FieldDescriptorProto_Type) bool { + // https://github.com/protocolbuffers/protobuf/blob/d4db41d395dcbb2c79b7fb1f109086fa04afd8aa/src/google/protobuf/descriptor.proto#L622 + return fieldType == descriptorpb.FieldDescriptorProto_TYPE_INT64 || + fieldType == descriptorpb.FieldDescriptorProto_TYPE_UINT64 || + fieldType == descriptorpb.FieldDescriptorProto_TYPE_SINT64 || + fieldType == descriptorpb.FieldDescriptorProto_TYPE_FIXED64 || + fieldType == descriptorpb.FieldDescriptorProto_TYPE_SFIXED64 +} diff --git a/private/bufpkg/bufimage/bufimagemodify/file_option.go b/private/bufpkg/bufimage/bufimagemodify/file_option.go new file mode 100644 index 0000000000..34276551a2 --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/file_option.go @@ -0,0 +1,466 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 bufimagemodify + +import ( + "github.com/bufbuild/buf/private/bufpkg/bufconfig" + "github.com/bufbuild/buf/private/bufpkg/bufimage" + "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimagemodify/internal" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/descriptorpb" +) + +var ( + // ccEnableArenas is the SourceCodeInfo path for the cc_enable_arenas option. + // https://github.com/protocolbuffers/protobuf/blob/29152fbc064921ca982d64a3a9eae1daa8f979bb/src/google/protobuf/descriptor.proto#L420 + ccEnableArenasPath = []int32{8, 31} + // csharpNamespacePath is the SourceCodeInfo path for the csharp_namespace option. + // https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L428 + csharpNamespacePath = []int32{8, 37} + // goPackagePath is the SourceCodeInfo path for the go_package option. + // https://github.com/protocolbuffers/protobuf/blob/ee04809540c098718121e092107fbc0abc231725/src/google/protobuf/descriptor.proto#L392 + goPackagePath = []int32{8, 11} + // javaMultipleFilesPath is the SourceCodeInfo path for the java_multiple_files option. + // https://github.com/protocolbuffers/protobuf/blob/ee04809540c098718121e092107fbc0abc231725/src/google/protobuf/descriptor.proto#L364 + javaMultipleFilesPath = []int32{8, 10} + // javaOuterClassnamePath is the SourceCodeInfo path for the java_outer_classname option. + // https://github.com/protocolbuffers/protobuf/blob/87d140f851131fb8a6e8a80449cf08e73e568259/src/google/protobuf/descriptor.proto#L356 + javaOuterClassnamePath = []int32{8, 8} + // javaPackagePath is the SourceCodeInfo path for the java_package option. + // https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L348 + javaPackagePath = []int32{8, 1} + // javaStringCheckUtf8Path is the SourceCodeInfo path for the java_string_check_utf8 option. + // https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L375 + javaStringCheckUtf8Path = []int32{8, 27} + // objcClassPrefixPath is the SourceCodeInfo path for the objc_class_prefix option. + // https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L425 + objcClassPrefixPath = []int32{8, 36} + // optimizeFor is the SourceCodeInfo path for the optimize_for option. + // https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L385 + optimizeForPath = []int32{8, 9} + // phpMetadataNamespacePath is the SourceCodeInfo path for the php_metadata_namespace option. + // Ref: https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L448 + phpMetadataNamespacePath = []int32{8, 44} + // phpNamespacePath is the SourceCodeInfo path for the php_namespace option. + // Ref: https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L443 + phpNamespacePath = []int32{8, 41} + + // rubyPackagePath is the SourceCodeInfo path for the ruby_package option. + // https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L453 + rubyPackagePath = []int32{8, 45} +) + +func modifyJavaOuterClass( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + return modifyStringOption( + sweeper, + imageFile, + config, + bufconfig.FileOptionJavaOuterClassname, + bufconfig.FileOptionUnspecified, + bufconfig.FileOptionUnspecified, + func(bufimage.ImageFile) stringOverrideOptions { + return stringOverrideOptions{value: javaOuterClassnameValue(imageFile)} + }, + func(imageFile bufimage.ImageFile, _ stringOverrideOptions) string { + return javaOuterClassnameValue(imageFile) + }, + func(options *descriptorpb.FileOptions) string { + return options.GetJavaOuterClassname() + }, + func(options *descriptorpb.FileOptions, value string) { + options.JavaOuterClassname = proto.String(value) + }, + javaOuterClassnamePath, + ) +} + +func modifyJavaPackage( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + return modifyStringOption( + sweeper, + imageFile, + config, + bufconfig.FileOptionJavaPackage, + bufconfig.FileOptionJavaPackagePrefix, + bufconfig.FileOptionJavaPackageSuffix, + func(bufimage.ImageFile) stringOverrideOptions { + return stringOverrideOptions{prefix: "com"} + }, + getJavaPackageValue, + func(options *descriptorpb.FileOptions) string { + return options.GetJavaPackage() + }, + func(options *descriptorpb.FileOptions, value string) { + options.JavaPackage = proto.String(value) + }, + javaPackagePath, + ) +} + +func modifyGoPackage( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + return modifyStringOption( + sweeper, + imageFile, + config, + bufconfig.FileOptionGoPackage, + bufconfig.FileOptionGoPackagePrefix, + bufconfig.FileOptionUnspecified, + func(bufimage.ImageFile) stringOverrideOptions { + return stringOverrideOptions{} + }, + func(imageFile bufimage.ImageFile, stringOverrideOptions stringOverrideOptions) string { + if stringOverrideOptions.prefix == "" { + return "" + } + return goPackageImportPathForFile(imageFile, stringOverrideOptions.prefix) + }, + func(options *descriptorpb.FileOptions) string { + return options.GetGoPackage() + }, + func(options *descriptorpb.FileOptions, value string) { + options.GoPackage = proto.String(value) + }, + goPackagePath, + ) +} + +func modifyObjcClassPrefix( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + return modifyStringOption( + sweeper, + imageFile, + config, + bufconfig.FileOptionObjcClassPrefix, + bufconfig.FileOptionUnspecified, + bufconfig.FileOptionUnspecified, + func(bufimage.ImageFile) stringOverrideOptions { + return stringOverrideOptions{value: objcClassPrefixValue(imageFile)} + }, + func(imageFile bufimage.ImageFile, _ stringOverrideOptions) string { + return objcClassPrefixValue(imageFile) + }, + func(options *descriptorpb.FileOptions) string { + return options.GetObjcClassPrefix() + }, + func(options *descriptorpb.FileOptions, value string) { + options.ObjcClassPrefix = proto.String(value) + }, + objcClassPrefixPath, + ) +} + +func modifyCsharpNamespace( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + return modifyStringOption( + sweeper, + imageFile, + config, + bufconfig.FileOptionCsharpNamespace, + bufconfig.FileOptionCsharpNamespacePrefix, + bufconfig.FileOptionUnspecified, + func(bufimage.ImageFile) stringOverrideOptions { + return stringOverrideOptions{value: csharpNamespaceValue(imageFile)} + }, + func(imageFile bufimage.ImageFile, stringOverrideOptions stringOverrideOptions) string { + return getCsharpNamespaceValue(imageFile, stringOverrideOptions.prefix) + }, + func(options *descriptorpb.FileOptions) string { + return options.GetCsharpNamespace() + }, + func(options *descriptorpb.FileOptions, value string) { + options.CsharpNamespace = proto.String(value) + }, + csharpNamespacePath, + ) +} + +func modifyPhpNamespace( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + return modifyStringOption( + sweeper, + imageFile, + config, + bufconfig.FileOptionPhpNamespace, + bufconfig.FileOptionUnspecified, + bufconfig.FileOptionUnspecified, + func(bufimage.ImageFile) stringOverrideOptions { + return stringOverrideOptions{value: phpNamespaceValue(imageFile)} + }, + func(imageFile bufimage.ImageFile, _ stringOverrideOptions) string { + return phpNamespaceValue(imageFile) + }, + func(options *descriptorpb.FileOptions) string { + return options.GetPhpNamespace() + }, + func(options *descriptorpb.FileOptions, value string) { + options.PhpNamespace = proto.String(value) + }, + phpNamespacePath, + ) +} + +func modifyPhpMetadataNamespace( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + return modifyStringOption( + sweeper, + imageFile, + config, + bufconfig.FileOptionPhpMetadataNamespace, + bufconfig.FileOptionUnspecified, + bufconfig.FileOptionPhpMetadataNamespaceSuffix, + func(bufimage.ImageFile) stringOverrideOptions { + return stringOverrideOptions{value: phpMetadataNamespaceValue(imageFile)} + }, + func(imageFile bufimage.ImageFile, stringOverrideOptions stringOverrideOptions) string { + return getPhpMetadataNamespaceValue(imageFile, stringOverrideOptions.suffix) + }, + func(options *descriptorpb.FileOptions) string { + return options.GetPhpMetadataNamespace() + }, + func(options *descriptorpb.FileOptions, value string) { + options.PhpMetadataNamespace = proto.String(value) + }, + phpMetadataNamespacePath, + ) +} + +func modifyRubyPackage( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + return modifyStringOption( + sweeper, + imageFile, + config, + bufconfig.FileOptionRubyPackage, + bufconfig.FileOptionUnspecified, + bufconfig.FileOptionRubyPackageSuffix, + func(bufimage.ImageFile) stringOverrideOptions { + return stringOverrideOptions{value: rubyPackageValue(imageFile)} + }, + func(imageFile bufimage.ImageFile, stringOverrideOptions stringOverrideOptions) string { + return getRubyPackageValue(imageFile, stringOverrideOptions.suffix) + }, + func(options *descriptorpb.FileOptions) string { + return options.GetRubyPackage() + }, + func(options *descriptorpb.FileOptions, value string) { + options.RubyPackage = proto.String(value) + }, + rubyPackagePath, + ) +} + +func modifyCcEnableArenas( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + return modifyFileOption[bool]( + sweeper, + imageFile, + config, + bufconfig.FileOptionCcEnableArenas, + true, + func(options *descriptorpb.FileOptions) bool { + return options.GetCcEnableArenas() + }, + func(options *descriptorpb.FileOptions, value bool) { + options.CcEnableArenas = proto.Bool(value) + }, + ccEnableArenasPath, + ) +} + +func modifyJavaMultipleFiles( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + return modifyFileOption[bool]( + sweeper, + imageFile, + config, + bufconfig.FileOptionJavaMultipleFiles, + true, + func(options *descriptorpb.FileOptions) bool { + return options.GetJavaMultipleFiles() + }, + func(options *descriptorpb.FileOptions, value bool) { + options.JavaMultipleFiles = proto.Bool(value) + }, + javaMultipleFilesPath, + ) +} + +func modifyJavaStringCheckUtf8( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + return modifyFileOption[bool]( + sweeper, + imageFile, + config, + bufconfig.FileOptionJavaStringCheckUtf8, + false, + func(options *descriptorpb.FileOptions) bool { + return options.GetJavaStringCheckUtf8() + }, + func(options *descriptorpb.FileOptions, value bool) { + options.JavaStringCheckUtf8 = proto.Bool(value) + }, + javaStringCheckUtf8Path, + ) +} + +func modifyOptmizeFor( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, +) error { + return modifyFileOption[descriptorpb.FileOptions_OptimizeMode]( + sweeper, + imageFile, + config, + bufconfig.FileOptionOptimizeFor, + descriptorpb.FileOptions_SPEED, + func(options *descriptorpb.FileOptions) descriptorpb.FileOptions_OptimizeMode { + return options.GetOptimizeFor() + }, + func(options *descriptorpb.FileOptions, value descriptorpb.FileOptions_OptimizeMode) { + options.OptimizeFor = value.Enum() + }, + optimizeForPath, + ) +} + +func modifyFileOption[T bool | descriptorpb.FileOptions_OptimizeMode]( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, + fileOption bufconfig.FileOption, + // You can set this value to the same as protobuf default, in order to not modify a value by default. + defaultValue T, + getOptionFunc func(*descriptorpb.FileOptions) T, + setOptionFunc func(*descriptorpb.FileOptions, T), + sourceLocationPath []int32, +) error { + value := defaultValue + if isFileOptionDisabledForFile( + imageFile, + fileOption, + config, + ) { + return nil + } + override, err := overrideFromConfig[T]( + imageFile, + config, + fileOption, + ) + if err != nil { + return err + } + if override != nil { + value = *override + } + descriptor := imageFile.FileDescriptorProto() + if getOptionFunc(descriptor.Options) == value { + // The option is already set to the same value, don't modify or mark it. + return nil + } + if descriptor.Options == nil { + descriptor.Options = &descriptorpb.FileOptions{} + } + setOptionFunc(descriptor.Options, value) + sweeper.Mark(imageFile, sourceLocationPath) + return nil +} + +func modifyStringOption( + sweeper internal.MarkSweeper, + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, + valueOption bufconfig.FileOption, + prefixOption bufconfig.FileOption, + suffixOption bufconfig.FileOption, + defaultOptionsFunc func(bufimage.ImageFile) stringOverrideOptions, + valueFunc func(bufimage.ImageFile, stringOverrideOptions) string, + getOptionFunc func(*descriptorpb.FileOptions) string, + setOptionFunc func(*descriptorpb.FileOptions, string), + sourceLocationPath []int32, +) error { + overrideOptions, err := stringOverrideFromConfig( + imageFile, + config, + defaultOptionsFunc(imageFile), + valueOption, + prefixOption, + suffixOption, + ) + if err != nil { + return err + } + var emptyOverrideOptions stringOverrideOptions + // This means the options are all disabled. + if overrideOptions == emptyOverrideOptions { + return nil + } + // Now either value is set or prefix and/or suffix is set. + value := overrideOptions.value + if value == "" { + // TODO: pass in prefix and suffix, instead of just override options + value = valueFunc(imageFile, overrideOptions) + } + if value == "" { + return nil + } + descriptor := imageFile.FileDescriptorProto() + if getOptionFunc(descriptor.Options) == value { + // The option is already set to the same value, don't modify or mark it. + return nil + } + if descriptor.Options == nil { + descriptor.Options = &descriptorpb.FileOptions{} + } + setOptionFunc(descriptor.Options, value) + sweeper.Mark(imageFile, sourceLocationPath) + return nil +} diff --git a/private/bufpkg/bufimage/bufimagemodify/file_option_sweeper.go b/private/bufpkg/bufimage/bufimagemodify/file_option_sweeper.go deleted file mode 100644 index 9844af3952..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/file_option_sweeper.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "fmt" - - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "google.golang.org/protobuf/types/descriptorpb" -) - -// fileOptionPath is the path prefix used for FileOptions. -// All file option locations are preceded by a location -// with a path set to the fileOptionPath. -// https://github.com/protocolbuffers/protobuf/blob/053966b4959bdd21e4a24e657bcb97cb9de9e8a4/src/google/protobuf/descriptor.proto#L80 -var fileOptionPath = []int32{8} - -type fileOptionSweeper struct { - // Filepath -> SourceCodeInfo_Location.Path keys. - sourceCodeInfoPaths map[string]map[string]struct{} -} - -func newFileOptionSweeper() *fileOptionSweeper { - return &fileOptionSweeper{ - sourceCodeInfoPaths: make(map[string]map[string]struct{}), - } -} - -// mark is used to mark the given SourceCodeInfo_Location indices for -// deletion. This method should be called in each of the file option -// modifiers. -func (s *fileOptionSweeper) mark(imageFilePath string, path []int32) { - paths, ok := s.sourceCodeInfoPaths[imageFilePath] - if !ok { - paths = make(map[string]struct{}) - s.sourceCodeInfoPaths[imageFilePath] = paths - } - paths[getPathKey(path)] = struct{}{} -} - -// Sweep applies all of the marks and sweeps the file option SourceCodeInfo_Locations. -func (s *fileOptionSweeper) Sweep(ctx context.Context, image bufimage.Image) error { - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if descriptor.SourceCodeInfo == nil { - continue - } - paths, ok := s.sourceCodeInfoPaths[imageFile.Path()] - if !ok { - continue - } - // We can't just match on an exact path match because the target - // file option's parent path elements would remain (i.e [8]). - // Instead, we perform an initial pass to validate that the paths - // are structured as expect, and collect all of the indices that - // we need to delete. - indices := make(map[int]struct{}, len(paths)*2) - for i, location := range descriptor.SourceCodeInfo.Location { - if _, ok := paths[getPathKey(location.Path)]; !ok { - continue - } - if i == 0 { - return fmt.Errorf("path %v must have a preceding parent path", location.Path) - } - if !int32SliceIsEqual(descriptor.SourceCodeInfo.Location[i-1].Path, fileOptionPath) { - return fmt.Errorf("path %v must have a preceding parent path equal to %v", location.Path, fileOptionPath) - } - // Add the target path and its parent. - indices[i-1] = struct{}{} - indices[i] = struct{}{} - } - // Now that we know exactly which indices to exclude, we can - // filter the SourceCodeInfo_Locations as needed. - locations := make( - []*descriptorpb.SourceCodeInfo_Location, - 0, - len(descriptor.SourceCodeInfo.Location)-len(indices), - ) - for i, location := range descriptor.SourceCodeInfo.Location { - if _, ok := indices[i]; ok { - continue - } - locations = append(locations, location) - } - descriptor.SourceCodeInfo.Location = locations - } - return nil -} - -// getPathKey returns a unique key for the given path. -func getPathKey(path []int32) string { - key := make([]byte, len(path)*4) - j := 0 - for _, elem := range path { - key[j] = byte(elem) - key[j+1] = byte(elem >> 8) - key[j+2] = byte(elem >> 16) - key[j+3] = byte(elem >> 24) - j += 4 - } - return string(key) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/go_package.go b/private/bufpkg/bufimage/bufimagemodify/go_package.go deleted file mode 100644 index 9bc9915d74..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/go_package.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "fmt" - - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/descriptorpb" -) - -// GoPackageID is the ID of the go_package modifier. -const GoPackageID = "GO_PACKAGE" - -// goPackagePath is the SourceCodeInfo path for the go_package option. -// https://github.com/protocolbuffers/protobuf/blob/ee04809540c098718121e092107fbc0abc231725/src/google/protobuf/descriptor.proto#L392 -var goPackagePath = []int32{8, 11} - -func goPackage( - logger *zap.Logger, - sweeper Sweeper, - defaultImportPathPrefix string, - except []bufmodule.ModuleFullName, - moduleOverrides map[bufmodule.ModuleFullName]string, - overrides map[string]string, -) (Modifier, error) { - if defaultImportPathPrefix == "" { - return nil, fmt.Errorf("a non-empty import path prefix is required") - } - // Convert the bufmodule.ModuleFullName types into - // strings so that they're comparable. - exceptModuleFullNameStrings := make(map[string]struct{}, len(except)) - for _, moduleFullName := range except { - exceptModuleFullNameStrings[moduleFullName.String()] = struct{}{} - } - overrideModuleFullNameStrings := make(map[string]string, len(moduleOverrides)) - for moduleFullName, goPackagePrefix := range moduleOverrides { - overrideModuleFullNameStrings[moduleFullName.String()] = goPackagePrefix - } - seenModuleFullNameStrings := make(map[string]struct{}, len(overrideModuleFullNameStrings)) - seenOverrideFiles := make(map[string]struct{}, len(overrides)) - return ModifierFunc( - func(ctx context.Context, image bufimage.Image) error { - for _, imageFile := range image.Files() { - importPathPrefix := defaultImportPathPrefix - if moduleFullName := imageFile.ModuleFullName(); moduleFullName != nil { - moduleFullNameString := moduleFullName.String() - if modulePrefixOverride, ok := overrideModuleFullNameStrings[moduleFullNameString]; ok { - importPathPrefix = modulePrefixOverride - seenModuleFullNameStrings[moduleFullNameString] = struct{}{} - } - } - goPackageValue := GoPackageImportPathForFile(imageFile, importPathPrefix) - if overrideValue, ok := overrides[imageFile.Path()]; ok { - goPackageValue = overrideValue - seenOverrideFiles[imageFile.Path()] = struct{}{} - } - if err := goPackageForFile( - ctx, - sweeper, - imageFile, - goPackageValue, - exceptModuleFullNameStrings, - ); err != nil { - return err - } - } - for moduleFullNameString := range overrideModuleFullNameStrings { - if _, ok := seenModuleFullNameStrings[moduleFullNameString]; !ok { - logger.Sugar().Warnf("go_package_prefix override for %q was unused", moduleFullNameString) - } - } - for overrideFile := range overrides { - if _, ok := seenOverrideFiles[overrideFile]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", GoPackageID, overrideFile) - } - } - return nil - }, - ), nil -} - -func goPackageForFile( - ctx context.Context, - sweeper Sweeper, - imageFile bufimage.ImageFile, - goPackageValue string, - exceptModuleFullNameStrings map[string]struct{}, -) error { - if shouldSkipGoPackageForFile(ctx, imageFile, exceptModuleFullNameStrings) { - return nil - } - descriptor := imageFile.FileDescriptorProto() - if descriptor.Options == nil { - descriptor.Options = &descriptorpb.FileOptions{} - } - descriptor.Options.GoPackage = proto.String(goPackageValue) - if sweeper != nil { - sweeper.mark(imageFile.Path(), goPackagePath) - } - return nil -} - -func shouldSkipGoPackageForFile( - ctx context.Context, - imageFile bufimage.ImageFile, - exceptModuleFullNameStrings map[string]struct{}, -) bool { - if isWellKnownType(ctx, imageFile) && imageFile.FileDescriptorProto().GetOptions().GetGoPackage() != "" { - // The well-known type defines the go_package option, so this is a no-op. - // If a well-known type ever omits the go_package option, we make sure - // to include it. - return true - } - if moduleFullName := imageFile.ModuleFullName(); moduleFullName != nil { - if _, ok := exceptModuleFullNameStrings[moduleFullName.String()]; ok { - return true - } - } - return false -} diff --git a/private/bufpkg/bufimage/bufimagemodify/go_package_test.go b/private/bufpkg/bufimage/bufimagemodify/go_package_test.go deleted file mode 100644 index 9d0ad9ddd0..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/go_package_test.go +++ /dev/null @@ -1,687 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "fmt" - "path/filepath" - "testing" - - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/bufbuild/buf/private/pkg/normalpath" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestGoPackageError(t *testing.T) { - t.Parallel() - _, err := GoPackage(zap.NewNop(), NewFileOptionSweeper(), "", nil, nil, nil) - require.Error(t, err) -} - -func TestGoPackageEmptyOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - - sweeper := NewFileOptionSweeper() - goPackageModifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, nil) - require.NoError(t, err) - - modifier := NewMultiModifier(goPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - normalpath.Dir(testImportPathPrefix+"/"+imageFile.Path()), - descriptor.GetOptions().GetGoPackage(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, nil) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - normalpath.Dir(testImportPathPrefix+"/"+imageFile.Path()), - descriptor.GetOptions().GetGoPackage(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - - sweeper := NewFileOptionSweeper() - goPackageModifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, map[string]string{"a.proto": "override"}) - require.NoError(t, err) - - modifier := NewMultiModifier(goPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetGoPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, map[string]string{"a.proto": "override"}) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetGoPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) -} - -func TestGoPackageAllOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "alloptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, goPackagePath) - - sweeper := NewFileOptionSweeper() - goPackageModifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, map[string]string{}) - require.NoError(t, err) - - modifier := NewMultiModifier(goPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - normalpath.Dir(testImportPathPrefix+"/"+imageFile.Path()), - descriptor.GetOptions().GetGoPackage(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, nil) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - normalpath.Dir(testImportPathPrefix+"/"+imageFile.Path()), - descriptor.GetOptions().GetGoPackage(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, goPackagePath) - - sweeper := NewFileOptionSweeper() - goPackageModifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, map[string]string{"a.proto": "override"}) - require.NoError(t, err) - - modifier := NewMultiModifier(goPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetGoPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, map[string]string{"a.proto": "override"}) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetGoPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) -} - -func TestGoPackagePackageVersion(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "packageversion") - packageSuffix := "weatherv1alpha1" - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, goPackagePath) - - sweeper := NewFileOptionSweeper() - goPackageModifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, nil) - require.NoError(t, err) - - modifier := NewMultiModifier(goPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - fmt.Sprintf("%s;%s", - normalpath.Dir(testImportPathPrefix+"/"+imageFile.Path()), - packageSuffix, - ), - descriptor.GetOptions().GetGoPackage(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, nil) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - fmt.Sprintf("%s;%s", - normalpath.Dir(testImportPathPrefix+"/"+imageFile.Path()), - packageSuffix, - ), - descriptor.GetOptions().GetGoPackage(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, goPackagePath) - - sweeper := NewFileOptionSweeper() - goPackageModifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, map[string]string{"a.proto": "override"}) - require.NoError(t, err) - - modifier := NewMultiModifier(goPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetGoPackage()) - continue - } - assert.Equal(t, - fmt.Sprintf("%s;%s", - normalpath.Dir(testImportPathPrefix+"/"+imageFile.Path()), - packageSuffix, - ), - descriptor.GetOptions().GetGoPackage(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, map[string]string{"a.proto": "override"}) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetGoPackage()) - continue - } - assert.Equal(t, - fmt.Sprintf("%s;%s", - normalpath.Dir(testImportPathPrefix+"/"+imageFile.Path()), - packageSuffix, - ), - descriptor.GetOptions().GetGoPackage(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) -} - -func TestGoPackageWellKnownTypes(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "wktimport") - packageSuffix := "weatherv1alpha1" - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - - sweeper := NewFileOptionSweeper() - goPackageModifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, nil) - require.NoError(t, err) - - modifier := NewMultiModifier(goPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - modifiedGoPackage := fmt.Sprintf("%s;%s", - normalpath.Dir(testImportPathPrefix+"/"+imageFile.Path()), - packageSuffix, - ) - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - assert.NotEmpty(t, descriptor.GetOptions().GetGoPackage()) - assert.NotEqual(t, modifiedGoPackage, descriptor.GetOptions().GetGoPackage()) - continue - } - assert.Equal(t, - modifiedGoPackage, - descriptor.GetOptions().GetGoPackage(), - ) - } - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := GoPackage(zap.NewNop(), sweeper, testImportPathPrefix, nil, nil, nil) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - modifiedGoPackage := fmt.Sprintf("%s;%s", - normalpath.Dir(testImportPathPrefix+"/"+imageFile.Path()), - packageSuffix, - ) - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - assert.NotEmpty(t, descriptor.GetOptions().GetGoPackage()) - assert.NotEqual(t, modifiedGoPackage, descriptor.GetOptions().GetGoPackage()) - continue - } - assert.Equal(t, - modifiedGoPackage, - descriptor.GetOptions().GetGoPackage(), - ) - } - }) -} - -func TestGoPackageWithExcept(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - testModuleFullName, err := bufmodule.NewModuleFullName( - testRemote, - testRepositoryOwner, - testRepositoryName, - ) - require.NoError(t, err) - - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - - sweeper := NewFileOptionSweeper() - goPackageModifier, err := GoPackage( - zap.NewNop(), - sweeper, - testImportPathPrefix, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - nil, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(goPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := GoPackage( - zap.NewNop(), - sweeper, - testImportPathPrefix, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - nil, - ) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - - sweeper := NewFileOptionSweeper() - goPackageModifier, err := GoPackage( - zap.NewNop(), - sweeper, - testImportPathPrefix, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - map[string]string{"a.proto": "override"}, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(goPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := GoPackage( - zap.NewNop(), - sweeper, - testImportPathPrefix, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - map[string]string{"a.proto": "override"}, - ) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) -} - -func TestGoPackageWithOverride(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - overrideGoPackagePrefix := "github.com/foo/bar/private/private/gen/proto/go" - testModuleFullName, err := bufmodule.NewModuleFullName( - testRemote, - testRepositoryOwner, - testRepositoryName, - ) - require.NoError(t, err) - - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - - sweeper := NewFileOptionSweeper() - goPackageModifier, err := GoPackage( - zap.NewNop(), - sweeper, - testImportPathPrefix, - nil, - map[bufmodule.ModuleFullName]string{ - testModuleFullName: overrideGoPackagePrefix, - }, - nil, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(goPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - normalpath.Dir(overrideGoPackagePrefix+"/"+imageFile.Path()), - descriptor.GetOptions().GetGoPackage(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := GoPackage( - zap.NewNop(), - sweeper, - testImportPathPrefix, - nil, - map[bufmodule.ModuleFullName]string{ - testModuleFullName: overrideGoPackagePrefix, - }, - nil, - ) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - normalpath.Dir(overrideGoPackagePrefix+"/"+imageFile.Path()), - descriptor.GetOptions().GetGoPackage(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - - sweeper := NewFileOptionSweeper() - goPackageModifier, err := GoPackage( - zap.NewNop(), - sweeper, - testImportPathPrefix, - nil, - map[bufmodule.ModuleFullName]string{ - testModuleFullName: overrideGoPackagePrefix, - }, - map[string]string{"a.proto": "override"}, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(goPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetGoPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := GoPackage( - zap.NewNop(), - sweeper, - testImportPathPrefix, - nil, - map[bufmodule.ModuleFullName]string{ - testModuleFullName: overrideGoPackagePrefix, - }, - map[string]string{"a.proto": "override"}, - ) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetGoPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/internal/field_options_trie.go b/private/bufpkg/bufimage/bufimagemodify/internal/field_options_trie.go new file mode 100644 index 0000000000..e2de1c8962 --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/internal/field_options_trie.go @@ -0,0 +1,113 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 internal + +import ( + "sort" + + "golang.org/x/exp/slices" +) + +// fieldOptionsTrie stores paths to FieldOptions (tag 8 of a FieldDescriptorProto). +type fieldOptionsTrie []*fieldOptionsTrieNode + +type fieldOptionsTrieNode struct { + value int32 + isPathEnd bool + children fieldOptionsTrie + // locationIndex is the data attached that we are interested in retrieving. + // This field is irrelevant to traversal or the trie structure. + locationIndex int + // registeredDescendantCount records how many times a descendant of this node + // is registered. This field is irrelevant to traversal or the trie structure. + registeredDescendantCount int +} + +// insert inserts a path into the trie. The caller should +// ensure that the path is for a FieldOptions. +func (p *fieldOptionsTrie) insert(path []int32, locationIndex int) { + trie := p + for index, element := range path { + isLastElement := index == len(path)-1 + nodes := *trie + pos, found := sort.Find(len(nodes), func(i int) int { + return int(element - nodes[i].value) + }) + if found { + if isLastElement { + nodes[pos].isPathEnd = true + nodes[pos].locationIndex = locationIndex + return + } + trie = &nodes[pos].children + continue + } + newNode := &fieldOptionsTrieNode{ + value: element, + children: fieldOptionsTrie{}, + } + if isLastElement { + newNode.isPathEnd = true + newNode.locationIndex = locationIndex + } + nodes = slices.Insert(nodes, pos, newNode) + *trie = nodes + trie = &nodes[pos].children + } +} + +// registerDescendant finds if there is an ancestor of the provided +// path and increments this ancestor's counter if it exists. +func (p *fieldOptionsTrie) registerDescendant(descendant []int32) { + trie := p + for i, element := range descendant { + nodes := *trie + pos, found := sort.Find(len(nodes), func(i int) int { + return int(element - nodes[i].value) + }) + if !found { + return + } + ancestor := nodes[pos] + descendantContinues := i != len(descendant)-1 + if ancestor.isPathEnd && descendantContinues { + ancestor.registeredDescendantCount += 1 + return + } + trie = &ancestor.children + } +} + +// indicesWithoutDescendant returns the location indices of +func (p *fieldOptionsTrie) indicesWithoutDescendant() []int { + locationIndices := []int{} + walkTrie(*p, func(node *fieldOptionsTrieNode) { + if node.isPathEnd && node.registeredDescendantCount == 0 { + locationIndices = append(locationIndices, node.locationIndex) + } + }) + return locationIndices +} + +func walkTrie(trie fieldOptionsTrie, enter func(node *fieldOptionsTrieNode)) { + for _, node := range trie { + walkTrieNode(node, enter) + } +} + +func walkTrieNode(node *fieldOptionsTrieNode, enter func(node *fieldOptionsTrieNode)) { + enter(node) + walkTrie(node.children, enter) +} diff --git a/private/bufpkg/bufimage/bufimagemodify/internal/internal.go b/private/bufpkg/bufimage/bufimagemodify/internal/internal.go new file mode 100644 index 0000000000..e2098ca932 --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/internal/internal.go @@ -0,0 +1,33 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 internal + +import "github.com/bufbuild/buf/private/bufpkg/bufimage" + +// fileOptionPath is the path prefix used for FileOptions. +// All file option locations are preceded by a location +// with a path set to the fileOptionPath. +// https://github.com/protocolbuffers/protobuf/blob/053966b4959bdd21e4a24e657bcb97cb9de9e8a4/src/google/protobuf/descriptor.proto#L80 +var fileOptionPath = []int32{8} + +// MarkSweeper is a mark sweeper for an image. +type MarkSweeper interface { + Mark(imageFile bufimage.ImageFile, path []int32) + Sweep() error +} + +func NewMarkSweeper(image bufimage.Image) MarkSweeper { + return newMarkSweeper(image) +} diff --git a/private/bufpkg/bufimage/bufimagemodify/internal/location_path_dfa.go b/private/bufpkg/bufimage/bufimagemodify/internal/location_path_dfa.go new file mode 100644 index 0000000000..2381fbe6f9 --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/internal/location_path_dfa.go @@ -0,0 +1,101 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 internal + +const ( + pathTypeNotFieldOption pathType = iota + 1 + pathTypeFieldOptionsRoot + pathTypeFieldOption + // https://github.com/protocolbuffers/protobuf/blob/29152fbc064921ca982d64a3a9eae1daa8f979bb/src/google/protobuf/descriptor.proto#L75 + messageTypeTagInFile int32 = 4 + // https://github.com/protocolbuffers/protobuf/blob/29152fbc064921ca982d64a3a9eae1daa8f979bb/src/google/protobuf/descriptor.proto#L78 + extensionTagInFile int32 = 7 + // https://github.com/protocolbuffers/protobuf/blob/29152fbc064921ca982d64a3a9eae1daa8f979bb/src/google/protobuf/descriptor.proto#L97 + fieldTagInMessage int32 = 2 + // https://github.com/protocolbuffers/protobuf/blob/29152fbc064921ca982d64a3a9eae1daa8f979bb/src/google/protobuf/descriptor.proto#L100 + nestedTypeTagInMessage int32 = 3 + // https://github.com/protocolbuffers/protobuf/blob/29152fbc064921ca982d64a3a9eae1daa8f979bb/src/google/protobuf/descriptor.proto#L98 + extensionTagInMessage int32 = 6 + // https://github.com/protocolbuffers/protobuf/blob/29152fbc064921ca982d64a3a9eae1daa8f979bb/src/google/protobuf/descriptor.proto#L215 + optionsTagInField int32 = 8 +) + +// pathType is the type of a message pointed to by a location path. +type pathType int + +// locationPathDFAState takes an input and returns the next state and the path type +// that ends with the input, which is consistent with the returned next state. The next +// state is nil if the DFA has finished running. +type locationPathDFAState func(int32) (locationPathDFAState, pathType) + +// getPathType returns the type of the path. It only accepts one of: +// empty, messages, message, fields, field, field options and field option. +func getPathType(path []int32) pathType { + pathType := pathTypeNotFieldOption + currentState := start + for _, element := range path { + if currentState == nil { + break + } + currentState, pathType = currentState(element) + } + return pathType +} + +func start(input int32) (locationPathDFAState, pathType) { + switch input { + case messageTypeTagInFile: + return messages, pathTypeNotFieldOption + case extensionTagInFile: + return fields, pathTypeNotFieldOption + default: + return nil, pathTypeNotFieldOption + } +} + +func messages(input int32) (locationPathDFAState, pathType) { + // we are not checking index >= 0, the caller must ensure this + return message, pathTypeNotFieldOption +} + +func message(input int32) (locationPathDFAState, pathType) { + switch input { + case nestedTypeTagInMessage: + return messages, pathTypeNotFieldOption + case fieldTagInMessage, extensionTagInMessage: + return fields, pathTypeNotFieldOption + } + return nil, pathTypeNotFieldOption +} + +func fields(input int32) (locationPathDFAState, pathType) { + // we are not checking index >= 0, the caller must ensure this + return field, pathTypeNotFieldOption +} + +func field(input int32) (locationPathDFAState, pathType) { + switch input { + case optionsTagInField: + return fieldOptions, pathTypeFieldOptionsRoot + default: + return nil, pathTypeNotFieldOption + } +} + +func fieldOptions(input int32) (locationPathDFAState, pathType) { + // We are done here: after this point the path will be for a FieldOption. + // No need for the DFA to keep running. + return nil, pathTypeFieldOption +} diff --git a/private/bufpkg/bufimage/bufimagemodify/internal/marksweeper.go b/private/bufpkg/bufimage/bufimagemodify/internal/marksweeper.go new file mode 100644 index 0000000000..513bcab1f6 --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/internal/marksweeper.go @@ -0,0 +1,176 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 internal + +import ( + "fmt" + + "github.com/bufbuild/buf/private/bufpkg/bufimage" + "github.com/bufbuild/buf/private/pkg/slicesext" + "google.golang.org/protobuf/types/descriptorpb" +) + +type markSweeper struct { + image bufimage.Image + // Filepath -> SourceCodeInfo_Location.Path keys. + sourceCodeInfoPaths map[string]map[string]struct{} +} + +func newMarkSweeper(image bufimage.Image) *markSweeper { + return &markSweeper{ + image: image, + sourceCodeInfoPaths: make(map[string]map[string]struct{}), + } +} + +func (s *markSweeper) Mark(imageFile bufimage.ImageFile, path []int32) { + paths, ok := s.sourceCodeInfoPaths[imageFile.Path()] + if !ok { + paths = make(map[string]struct{}) + s.sourceCodeInfoPaths[imageFile.Path()] = paths + } + paths[getPathKey(path)] = struct{}{} +} + +func (s *markSweeper) Sweep() error { + for _, imageFile := range s.image.Files() { + descriptor := imageFile.FileDescriptorProto() + if descriptor.SourceCodeInfo == nil { + continue + } + paths, ok := s.sourceCodeInfoPaths[imageFile.Path()] + if !ok { + continue + } + err := removeLocationsFromSourceCodeInfo(descriptor.SourceCodeInfo, paths) + if err != nil { + return err + } + } + return nil +} + +// removeLocationsFromSourceCodeInfo removes paths from the given sourceCodeInfo. +// Each path must be for either a file option or a field option. +func removeLocationsFromSourceCodeInfo( + sourceCodeInfo *descriptorpb.SourceCodeInfo, + pathsToRemove map[string]struct{}, +) error { + // TODO: in v1 there is no need to check for field options, maybe v1 and v2 + // don't need to share this function. + + // We can't just match on an exact path match because the target + // file option's parent path elements would remain (i.e [8]), + // or the target field option's parent path has no other child left. + // Instead, we perform an initial pass to validate that the paths + // are structured as expected, and collect all of the indices that + // we need to delete. + indices := make(map[int]struct{}, len(pathsToRemove)*2) + // each path in this trie is for a FieldOptions message (not for a singular option) + var fieldOptionsPaths fieldOptionsTrie + for i, location := range sourceCodeInfo.Location { + path := location.Path + pathType := getPathType(path) + if pathType == pathTypeFieldOptionsRoot { + fieldOptionsPaths.insert(path, i) + } + if _, ok := pathsToRemove[getPathKey(path)]; !ok { + if pathType == pathTypeFieldOption { + // This field option path will not be removed, register it to its + // parent FieldOptions. + fieldOptionsPaths.registerDescendant(path) + } + continue + } + if i == 0 { + return fmt.Errorf("path %v must have a preceding parent path", location.Path) + } + if isPathForFileOption(location.Path) { + if !slicesext.ElementsEqual(sourceCodeInfo.Location[i-1].Path, fileOptionPath) { + return fmt.Errorf("file option path %v must have a preceding parent path equal to %v", location.Path, fileOptionPath) + } + // Add the target path and its parent. + indices[i-1] = struct{}{} + indices[i] = struct{}{} + continue + } + if pathType == pathTypeFieldOption { + // Note that there is a difference between the generated file option paths and field options paths. + // For example, for: + // ... + // option java_package = "com.example"; + // option go_package = "github.com/hello/world"; + // ... + // the generated paths are + // [8], [8,1], [8], [8,11] + // where each file option declared has a parent. + // However, for different field options of the same field, they share the same parent. For + // ... + // optional string id2 = 2 [jstype = JS_STRING, ctype = CORD]; + // required fixed64 first = 1 [ + // (foo.bar.baz.aaa).foo = "hello", + // (foo.bar.baz.bbb).a.foo = "hey", + // (foo.bar.baz.ccc) = 123, // ccc is a repeated option + // jstype = JS_STRING + // ]; + // ... + // the generated paths are + // [4,0,2,0,8],[4,0,2,0,8,50000,1],[4,0,2,0,8,50002,1,1],[4,0,2,0,8,50003,0],[4,0,2,0,8,6] + // where two field options share the same parent. + // Therefore, do not remove the parent path yet. + indices[i] = struct{}{} + continue + } + return fmt.Errorf("path %v is neither a file option path nor a field option path", location.Path) + } + for _, emptyFieldOptions := range fieldOptionsPaths.indicesWithoutDescendant() { + indices[emptyFieldOptions] = struct{}{} + } + // Now that we know exactly which indices to exclude, we can + // filter the SourceCodeInfo_Locations as needed. + locations := make( + []*descriptorpb.SourceCodeInfo_Location, + 0, + len(sourceCodeInfo.Location)-len(indices), + ) + for i, location := range sourceCodeInfo.Location { + if _, ok := indices[i]; ok { + continue + } + locations = append(locations, location) + } + sourceCodeInfo.Location = locations + return nil +} + +func isPathForFileOption(path []int32) bool { + // a file option's path is {8, x} + fileOptionPathLen := 2 + return len(path) == fileOptionPathLen && path[0] == fileOptionPath[0] +} + +// getPathKey returns a unique key for the given path. +func getPathKey(path []int32) string { + key := make([]byte, len(path)*4) + j := 0 + for _, elem := range path { + key[j] = byte(elem) + key[j+1] = byte(elem >> 8) + key[j+2] = byte(elem >> 16) + key[j+3] = byte(elem >> 24) + j += 4 + } + return string(key) +} diff --git a/private/bufpkg/bufimage/bufimagemodify/java_multiple_files.go b/private/bufpkg/bufimage/bufimagemodify/java_multiple_files.go deleted file mode 100644 index f0385ae109..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/java_multiple_files.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/descriptorpb" -) - -const ( - // DefaultJavaMultipleFilesValue is the default value for the java_multiple_files modifier. - DefaultJavaMultipleFilesValue = true - - // JavaMultipleFilesID is the ID of the java_multiple_files modifier. - JavaMultipleFilesID = "JAVA_MULTIPLE_FILES" -) - -// javaMultipleFilesPath is the SourceCodeInfo path for the java_multiple_files option. -// https://github.com/protocolbuffers/protobuf/blob/ee04809540c098718121e092107fbc0abc231725/src/google/protobuf/descriptor.proto#L364 -var javaMultipleFilesPath = []int32{8, 10} - -func javaMultipleFiles( - logger *zap.Logger, - sweeper Sweeper, - value bool, - overrides map[string]bool, - preserveExistingValue bool, -) Modifier { - return ModifierFunc( - func(ctx context.Context, image bufimage.Image) error { - seenOverrideFiles := make(map[string]struct{}, len(overrides)) - for _, imageFile := range image.Files() { - modifierValue := value - if overrideValue, ok := overrides[imageFile.Path()]; ok { - modifierValue = overrideValue - seenOverrideFiles[imageFile.Path()] = struct{}{} - } - if err := javaMultipleFilesForFile(ctx, sweeper, imageFile, modifierValue, preserveExistingValue); err != nil { - return err - } - } - for overrideFile := range overrides { - if _, ok := seenOverrideFiles[overrideFile]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", JavaMultipleFilesID, overrideFile) - } - } - return nil - }, - ) -} - -func javaMultipleFilesForFile( - ctx context.Context, - sweeper Sweeper, - imageFile bufimage.ImageFile, - value bool, - preserveExistingValue bool, -) error { - descriptor := imageFile.FileDescriptorProto() - options := descriptor.GetOptions() - switch { - case isWellKnownType(ctx, imageFile): - // The file is a well-known type, don't do anything. - return nil - case options != nil: - if preserveExistingValue && options.JavaMultipleFiles != nil { - // This option is explicitly set to a value - preserve existing value. - return nil - } - if options.GetJavaMultipleFiles() == value { - // The option is already set to the same value, don't do anything. - return nil - } - case options == nil && descriptorpb.Default_FileOptions_JavaMultipleFiles == value: - // The option is not set, but the value we want to set is the - // same as the default, don't do anything. - return nil - } - if options == nil { - descriptor.Options = &descriptorpb.FileOptions{} - } - descriptor.Options.JavaMultipleFiles = proto.Bool(value) - if sweeper != nil { - sweeper.mark(imageFile.Path(), javaMultipleFilesPath) - } - return nil -} diff --git a/private/bufpkg/bufimage/bufimagemodify/java_multiple_files_test.go b/private/bufpkg/bufimage/bufimagemodify/java_multiple_files_test.go deleted file mode 100644 index 059e8f78d3..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/java_multiple_files_test.go +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestJavaMultipleFilesEmptyOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, true) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, true, nil, false) - require.NoError(t, err) - modifier := NewMultiModifier( - javaMultipleFilesModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, true, nil, false) - require.NoError(t, err) - err = javaMultipleFilesModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, true) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, true, map[string]string{"a.proto": "false"}, false) - require.NoError(t, err) - modifier := NewMultiModifier( - javaMultipleFilesModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.False(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, true, map[string]string{"a.proto": "false"}, false) - require.NoError(t, err) - err = javaMultipleFilesModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.False(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - }) -} - -func TestJavaMultipleFilesAllOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "alloptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaMultipleFilesPath) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, true, nil, false) - require.NoError(t, err) - modifier := NewMultiModifier( - javaMultipleFilesModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, true, nil, false) - require.NoError(t, err) - err = javaMultipleFilesModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaMultipleFilesPath) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, true, map[string]string{"a.proto": "false"}, false) - require.NoError(t, err) - modifier := NewMultiModifier( - javaMultipleFilesModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.False(t, descriptor.GetOptions().GetJavaMultipleFiles()) - continue - } - assert.True(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaMultipleFilesPath) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, true, map[string]string{"a.proto": "false"}, false) - require.NoError(t, err) - err = javaMultipleFilesModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.False(t, descriptor.GetOptions().GetJavaMultipleFiles()) - continue - } - assert.True(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - }) - - t.Run("with preserveExistingValue", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, true, nil, true) - require.NoError(t, err) - err = javaMultipleFilesModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.False(t, descriptor.GetOptions().GetJavaMultipleFiles()) - continue - } - assert.True(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - }) -} - -func TestJavaMultipleFilesJavaOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "javaoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaMultipleFilesPath) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, false, nil, false) - require.NoError(t, err) - modifier := NewMultiModifier( - javaMultipleFilesModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.False(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, false, nil, false) - require.NoError(t, err) - err = javaMultipleFilesModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.True(t, descriptor.GetOptions().GetJavaMultipleFiles()) - continue - } - assert.False(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaMultipleFilesPath) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, false, map[string]string{"override.proto": "true"}, false) - require.NoError(t, err) - modifier := NewMultiModifier( - javaMultipleFilesModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.True(t, descriptor.GetOptions().GetJavaMultipleFiles()) - continue - } - assert.False(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, false, map[string]string{"override.proto": "true"}, false) - require.NoError(t, err) - err = javaMultipleFilesModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.True(t, descriptor.GetOptions().GetJavaMultipleFiles()) - continue - } - assert.False(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - }) - - t.Run("with preserveExistingValue", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, false, nil, true) - require.NoError(t, err) - err = javaMultipleFilesModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetJavaMultipleFiles()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaMultipleFilesPath, false) - }) -} - -func TestJavaMultipleFilesWellKnownTypes(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "wktimport") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, true, nil, false) - require.NoError(t, err) - modifier := NewMultiModifier( - javaMultipleFilesModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - assert.True(t, imageFile.FileDescriptorProto().GetOptions().GetJavaMultipleFiles()) - } - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - - sweeper := NewFileOptionSweeper() - javaMultipleFilesModifier, err := JavaMultipleFiles(zap.NewNop(), sweeper, true, nil, false) - require.NoError(t, err) - err = javaMultipleFilesModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - assert.True(t, imageFile.FileDescriptorProto().GetOptions().GetJavaMultipleFiles()) - } - }) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/java_outer_classname.go b/private/bufpkg/bufimage/bufimagemodify/java_outer_classname.go deleted file mode 100644 index f46ae5a83d..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/java_outer_classname.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "github.com/bufbuild/buf/private/pkg/normalpath" - "github.com/bufbuild/buf/private/pkg/stringutil" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/descriptorpb" -) - -// JavaOuterClassNameID is the ID for the java_outer_classname modifier. -const JavaOuterClassNameID = "JAVA_OUTER_CLASSNAME" - -// javaOuterClassnamePath is the SourceCodeInfo path for the java_outer_classname option. -// https://github.com/protocolbuffers/protobuf/blob/87d140f851131fb8a6e8a80449cf08e73e568259/src/google/protobuf/descriptor.proto#L356 -var javaOuterClassnamePath = []int32{8, 8} - -func javaOuterClassname( - logger *zap.Logger, - sweeper Sweeper, - overrides map[string]string, - preserveExistingValue bool, -) Modifier { - return ModifierFunc( - func(ctx context.Context, image bufimage.Image) error { - seenOverrideFiles := make(map[string]struct{}, len(overrides)) - for _, imageFile := range image.Files() { - javaOuterClassnameValue := javaOuterClassnameValue(imageFile) - if overrideValue, ok := overrides[imageFile.Path()]; ok { - javaOuterClassnameValue = overrideValue - seenOverrideFiles[imageFile.Path()] = struct{}{} - } - if err := javaOuterClassnameForFile(ctx, sweeper, imageFile, javaOuterClassnameValue, preserveExistingValue); err != nil { - return err - } - } - for overrideFile := range overrides { - if _, ok := seenOverrideFiles[overrideFile]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", JavaOuterClassNameID, overrideFile) - } - } - return nil - }, - ) -} - -func javaOuterClassnameForFile( - ctx context.Context, - sweeper Sweeper, - imageFile bufimage.ImageFile, - javaOuterClassnameValue string, - preserveExistingValue bool, -) error { - if isWellKnownType(ctx, imageFile) { - // The file is a well-known type - don't override the value. - return nil - } - descriptor := imageFile.FileDescriptorProto() - options := descriptor.GetOptions() - if options != nil && options.JavaOuterClassname != nil && preserveExistingValue { - // The option is explicitly set in the file - don't override it if we want to preserve the existing value. - return nil - } - if options.GetJavaOuterClassname() == javaOuterClassnameValue { - // The file already defines the java_outer_classname option with the given value, so this is a no-op. - return nil - } - if descriptor.Options == nil { - descriptor.Options = &descriptorpb.FileOptions{} - } - descriptor.Options.JavaOuterClassname = proto.String(javaOuterClassnameValue) - if sweeper != nil { - sweeper.mark(imageFile.Path(), javaOuterClassnamePath) - } - return nil -} - -func javaOuterClassnameValue(imageFile bufimage.ImageFile) string { - return stringutil.ToPascalCase(normalpath.Base(imageFile.Path())) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/java_outer_classname_test.go b/private/bufpkg/bufimage/bufimagemodify/java_outer_classname_test.go deleted file mode 100644 index 1532177fd0..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/java_outer_classname_test.go +++ /dev/null @@ -1,409 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "path/filepath" - "testing" - - "github.com/bufbuild/buf/private/pkg/normalpath" - "github.com/bufbuild/buf/private/pkg/stringutil" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestJavaOuterClassnameEmptyOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, true) - - sweeper := NewFileOptionSweeper() - modifier := NewMultiModifier( - JavaOuterClassname(zap.NewNop(), sweeper, nil, false), - ModifierFunc(sweeper.Sweep), - ) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "AProto", descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, false) - - sweeper := NewFileOptionSweeper() - err := JavaOuterClassname(zap.NewNop(), sweeper, nil, false).Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "AProto", descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, true) - - sweeper := NewFileOptionSweeper() - modifier := NewMultiModifier( - JavaOuterClassname(zap.NewNop(), sweeper, map[string]string{"a.proto": "override"}, false), - ModifierFunc(sweeper.Sweep), - ) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, false) - - sweeper := NewFileOptionSweeper() - err := JavaOuterClassname(zap.NewNop(), sweeper, map[string]string{"a.proto": "override"}, false).Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, false) - }) - - t.Run("with preserveExistingValue", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, true) - - sweeper := NewFileOptionSweeper() - modifier := NewMultiModifier( - JavaOuterClassname(zap.NewNop(), sweeper, nil, true), - ModifierFunc(sweeper.Sweep), - ) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "AProto", descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, true) - }) -} - -func TestJavaOuterClassnameAllOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "alloptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaOuterClassnamePath) - - sweeper := NewFileOptionSweeper() - modifier := NewMultiModifier( - JavaOuterClassname(zap.NewNop(), sweeper, nil, false), - ModifierFunc(sweeper.Sweep), - ) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "AProto", descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, false) - - sweeper := NewFileOptionSweeper() - err := JavaOuterClassname(zap.NewNop(), sweeper, nil, false).Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "AProto", descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaOuterClassnamePath) - - sweeper := NewFileOptionSweeper() - modifier := NewMultiModifier( - JavaOuterClassname(zap.NewNop(), sweeper, map[string]string{"a.proto": "override"}, false), - ModifierFunc(sweeper.Sweep), - ) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, false) - - sweeper := NewFileOptionSweeper() - err := JavaOuterClassname(zap.NewNop(), sweeper, map[string]string{"a.proto": "override"}, false).Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, false) - }) - - t.Run("with preserveExistingValue", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaOuterClassnamePath) - - sweeper := NewFileOptionSweeper() - modifier := NewMultiModifier( - JavaOuterClassname(zap.NewNop(), sweeper, nil, true), - ModifierFunc(sweeper.Sweep), - ) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaOuterClassnamePath) - }) -} - -func TestJavaOuterClassnameJavaOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "javaoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaOuterClassnamePath) - - sweeper := NewFileOptionSweeper() - modifier := NewMultiModifier( - JavaOuterClassname(zap.NewNop(), sweeper, nil, false), - ModifierFunc(sweeper.Sweep), - ) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, stringutil.ToPascalCase(normalpath.Base(imageFile.Path())), descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, false) - - sweeper := NewFileOptionSweeper() - err := JavaOuterClassname(zap.NewNop(), sweeper, nil, false).Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, stringutil.ToPascalCase(normalpath.Base(imageFile.Path())), descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaOuterClassnamePath) - - sweeper := NewFileOptionSweeper() - modifier := NewMultiModifier( - JavaOuterClassname(zap.NewNop(), sweeper, map[string]string{"override.proto": "override"}, false), - ModifierFunc(sweeper.Sweep), - ) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetJavaOuterClassname()) - continue - } - assert.Equal(t, "JavaFileProto", descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, false) - - sweeper := NewFileOptionSweeper() - err := JavaOuterClassname(zap.NewNop(), sweeper, map[string]string{"override.proto": "override"}, false).Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetJavaOuterClassname()) - continue - } - assert.Equal(t, "JavaFileProto", descriptor.GetOptions().GetJavaOuterClassname()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaOuterClassnamePath, false) - }) -} - -func TestJavaOuterClassnameWellKnownTypes(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "wktimport") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - - sweeper := NewFileOptionSweeper() - modifier := NewMultiModifier( - JavaOuterClassname(zap.NewNop(), sweeper, nil, false), - ModifierFunc(sweeper.Sweep), - ) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - assert.Equal(t, javaOuterClassnameValue(imageFile), descriptor.GetOptions().GetJavaOuterClassname()) - continue - } - assert.Equal(t, - "AProto", - descriptor.GetOptions().GetJavaOuterClassname(), - ) - } - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - - sweeper := NewFileOptionSweeper() - err := JavaOuterClassname(zap.NewNop(), sweeper, nil, false).Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - assert.Equal(t, javaOuterClassnameValue(imageFile), descriptor.GetOptions().GetJavaOuterClassname()) - continue - } - assert.Equal(t, - "AProto", - descriptor.GetOptions().GetJavaOuterClassname(), - ) - } - }) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/java_package.go b/private/bufpkg/bufimage/bufimagemodify/java_package.go deleted file mode 100644 index 97820b9c9c..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/java_package.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "fmt" - - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/descriptorpb" -) - -const ( - // DefaultJavaPackagePrefix is the default java_package prefix used in the java_package modifier. - DefaultJavaPackagePrefix = "com" - - // JavaPackageID is the ID of the java_package modifier. - JavaPackageID = "JAVA_PACKAGE" -) - -// javaPackagePath is the SourceCodeInfo path for the java_package option. -// https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L348 -var javaPackagePath = []int32{8, 1} - -func javaPackage( - logger *zap.Logger, - sweeper Sweeper, - defaultPackagePrefix string, - except []bufmodule.ModuleFullName, - moduleOverrides map[bufmodule.ModuleFullName]string, - overrides map[string]string, -) (Modifier, error) { - if defaultPackagePrefix == "" { - return nil, fmt.Errorf("a non-empty package prefix is required") - } - // Convert the bufmodule.ModuleFullName types into - // strings so that they're comparable. - exceptModuleFullNameStrings := make(map[string]struct{}, len(except)) - for _, moduleFullName := range except { - exceptModuleFullNameStrings[moduleFullName.String()] = struct{}{} - } - overrideModuleFullNameStrings := make(map[string]string, len(moduleOverrides)) - for moduleFullName, javaPackagePrefix := range moduleOverrides { - overrideModuleFullNameStrings[moduleFullName.String()] = javaPackagePrefix - } - seenModuleFullNameStrings := make(map[string]struct{}, len(overrideModuleFullNameStrings)) - seenOverrideFiles := make(map[string]struct{}, len(overrides)) - return ModifierFunc( - func(ctx context.Context, image bufimage.Image) error { - for _, imageFile := range image.Files() { - packagePrefix := defaultPackagePrefix - if moduleFullName := imageFile.ModuleFullName(); moduleFullName != nil { - moduleFullNameString := moduleFullName.String() - if modulePrefixOverride, ok := overrideModuleFullNameStrings[moduleFullNameString]; ok { - packagePrefix = modulePrefixOverride - seenModuleFullNameStrings[moduleFullNameString] = struct{}{} - } - } - javaPackageValue := javaPackageValue(imageFile, packagePrefix) - if overridePackagePrefix, ok := overrides[imageFile.Path()]; ok { - javaPackageValue = overridePackagePrefix - seenOverrideFiles[imageFile.Path()] = struct{}{} - } - if err := javaPackageForFile( - ctx, - sweeper, - imageFile, - javaPackageValue, - exceptModuleFullNameStrings, - ); err != nil { - return err - } - } - for moduleFullNameString := range overrideModuleFullNameStrings { - if _, ok := seenModuleFullNameStrings[moduleFullNameString]; !ok { - logger.Sugar().Warnf("java_package_prefix override for %q was unused", moduleFullNameString) - } - } - for overrideFile := range overrides { - if _, ok := seenOverrideFiles[overrideFile]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", JavaPackageID, overrideFile) - } - } - return nil - }, - ), nil -} - -func javaPackageForFile( - ctx context.Context, - sweeper Sweeper, - imageFile bufimage.ImageFile, - javaPackageValue string, - exceptModuleFullNameStrings map[string]struct{}, -) error { - if shouldSkipJavaPackageForFile(ctx, imageFile, javaPackageValue, exceptModuleFullNameStrings) { - return nil - } - descriptor := imageFile.FileDescriptorProto() - if descriptor.Options == nil { - descriptor.Options = &descriptorpb.FileOptions{} - } - descriptor.Options.JavaPackage = proto.String(javaPackageValue) - if sweeper != nil { - sweeper.mark(imageFile.Path(), javaPackagePath) - } - return nil -} - -func shouldSkipJavaPackageForFile( - ctx context.Context, - imageFile bufimage.ImageFile, - javaPackageValue string, - exceptModuleFullNameStrings map[string]struct{}, -) bool { - if isWellKnownType(ctx, imageFile) || javaPackageValue == "" { - // This is a well-known type or we could not resolve a non-empty java_package - // value, so this is a no-op. - return true - } - - if moduleFullName := imageFile.ModuleFullName(); moduleFullName != nil { - if _, ok := exceptModuleFullNameStrings[moduleFullName.String()]; ok { - return true - } - } - return false -} - -// javaPackageValue returns the java_package for the given ImageFile based on its -// package declaration. If the image file doesn't have a package declaration, an -// empty string is returned. -func javaPackageValue(imageFile bufimage.ImageFile, packagePrefix string) string { - if pkg := imageFile.FileDescriptorProto().GetPackage(); pkg != "" { - return packagePrefix + "." + pkg - } - return "" -} diff --git a/private/bufpkg/bufimage/bufimagemodify/java_package_test.go b/private/bufpkg/bufimage/bufimagemodify/java_package_test.go deleted file mode 100644 index b89d46b12e..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/java_package_test.go +++ /dev/null @@ -1,632 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "path/filepath" - "testing" - - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -const testJavaPackagePrefix = "com" - -func TestJavaPackageError(t *testing.T) { - t.Parallel() - _, err := JavaPackage(zap.NewNop(), NewFileOptionSweeper(), "", nil, nil, nil) - require.Error(t, err) -} - -func TestJavaPackageEmptyOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - - sweeper := NewFileOptionSweeper() - javaPackageModifier, err := JavaPackage(zap.NewNop(), sweeper, testJavaPackagePrefix, nil, nil, nil) - require.NoError(t, err) - - modifier := NewMultiModifier(javaPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaPackage(zap.NewNop(), sweeper, testJavaPackagePrefix, nil, nil, nil) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - - sweeper := NewFileOptionSweeper() - javaPackageModifier, err := JavaPackage(zap.NewNop(), sweeper, testJavaPackagePrefix, nil, nil, map[string]string{"a.proto": "override"}) - require.NoError(t, err) - - modifier := NewMultiModifier(javaPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetJavaPackage()) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaPackage(zap.NewNop(), sweeper, testJavaPackagePrefix, nil, nil, map[string]string{"a.proto": "override"}) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetJavaPackage()) - }) -} - -func TestJavaPackageAllOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "alloptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaPackagePath) - - sweeper := NewFileOptionSweeper() - javaPackageModifier, err := JavaPackage(zap.NewNop(), sweeper, testJavaPackagePrefix, nil, nil, nil) - require.NoError(t, err) - - modifier := NewMultiModifier(javaPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetJavaPackage()) - } - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaPackagePath) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaPackage(zap.NewNop(), sweeper, testJavaPackagePrefix, nil, nil, nil) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetJavaPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaPackagePath) - - sweeper := NewFileOptionSweeper() - javaPackageModifier, err := JavaPackage(zap.NewNop(), sweeper, testJavaPackagePrefix, nil, nil, map[string]string{"a.proto": "override"}) - require.NoError(t, err) - - modifier := NewMultiModifier(javaPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetJavaPackage()) - continue - } - assert.Equal(t, "foo", descriptor.GetOptions().GetJavaPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaPackage(zap.NewNop(), sweeper, testJavaPackagePrefix, nil, nil, map[string]string{"a.proto": "override"}) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetJavaPackage()) - continue - } - assert.Equal(t, "foo", descriptor.GetOptions().GetJavaPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - }) -} - -func TestJavaPackageJavaOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "javaoptions") - modifiedJavaPackage := "com.acme.weather" - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaPackagePath) - - sweeper := NewFileOptionSweeper() - javaPackageModifier, err := JavaPackage(zap.NewNop(), sweeper, testJavaPackagePrefix, nil, nil, nil) - require.NoError(t, err) - - modifier := NewMultiModifier(javaPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, modifiedJavaPackage, descriptor.GetOptions().GetJavaPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaPackage(zap.NewNop(), sweeper, testJavaPackagePrefix, nil, nil, nil) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, modifiedJavaPackage, descriptor.GetOptions().GetJavaPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaPackagePath) - - sweeper := NewFileOptionSweeper() - javaPackageModifier, err := JavaPackage(zap.NewNop(), sweeper, testJavaPackagePrefix, nil, nil, map[string]string{"override.proto": "override"}) - require.NoError(t, err) - - modifier := NewMultiModifier(javaPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetJavaPackage()) - continue - } - assert.Equal(t, modifiedJavaPackage, descriptor.GetOptions().GetJavaPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaPackage(zap.NewNop(), sweeper, testJavaPackagePrefix, nil, nil, map[string]string{"override.proto": "override"}) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetJavaPackage()) - continue - } - assert.Equal(t, modifiedJavaPackage, descriptor.GetOptions().GetJavaPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - }) -} - -func TestJavaPackageWellKnownTypes(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "wktimport") - javaPackagePrefix := "org" - modifiedJavaPackage := "org.acme.weather.v1alpha1" - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - - sweeper := NewFileOptionSweeper() - javaPackageModifier, err := JavaPackage(zap.NewNop(), sweeper, javaPackagePrefix, nil, nil, nil) - require.NoError(t, err) - - modifier := NewMultiModifier(javaPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - assert.NotEmpty(t, descriptor.GetOptions().GetJavaPackage()) - assert.NotEqual(t, modifiedJavaPackage, descriptor.GetOptions().GetJavaPackage()) - continue - } - assert.Equal(t, - modifiedJavaPackage, - descriptor.GetOptions().GetJavaPackage(), - ) - } - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaPackage(zap.NewNop(), sweeper, javaPackagePrefix, nil, nil, nil) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - assert.NotEmpty(t, descriptor.GetOptions().GetJavaPackage()) - assert.NotEqual(t, modifiedJavaPackage, descriptor.GetOptions().GetJavaPackage()) - continue - } - assert.Equal(t, - modifiedJavaPackage, - descriptor.GetOptions().GetJavaPackage(), - ) - } - }) -} - -func TestJavaPackageWithExcept(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "javaemptyoptions") - testModuleFullName, err := bufmodule.NewModuleFullName( - testRemote, - testRepositoryOwner, - testRepositoryName, - ) - require.NoError(t, err) - - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - - sweeper := NewFileOptionSweeper() - javaPackageModifier, err := JavaPackage( - zap.NewNop(), - sweeper, - testJavaPackagePrefix, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - nil, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(javaPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaPackage( - zap.NewNop(), - sweeper, - testJavaPackagePrefix, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - nil, - ) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - - sweeper := NewFileOptionSweeper() - javaPackageModifier, err := JavaPackage( - zap.NewNop(), - sweeper, - testJavaPackagePrefix, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - map[string]string{"a.proto": "override"}, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(javaPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaPackage( - zap.NewNop(), - sweeper, - testJavaPackagePrefix, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - map[string]string{"a.proto": "override"}, - ) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - }) -} - -func TestJavaPackageWithOverride(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "javaemptyoptions") - overrideJavaPackagePrefix := "foo.bar" - testModuleFullName, err := bufmodule.NewModuleFullName( - testRemote, - testRepositoryOwner, - testRepositoryName, - ) - require.NoError(t, err) - - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - - sweeper := NewFileOptionSweeper() - javaPackageModifier, err := JavaPackage( - zap.NewNop(), - sweeper, - testJavaPackagePrefix, - nil, - map[bufmodule.ModuleFullName]string{ - testModuleFullName: overrideJavaPackagePrefix, - }, - nil, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(javaPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - overrideJavaPackagePrefix+"."+descriptor.GetPackage(), - descriptor.GetOptions().GetJavaPackage(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaPackage( - zap.NewNop(), - sweeper, - testJavaPackagePrefix, - nil, - map[bufmodule.ModuleFullName]string{ - testModuleFullName: overrideJavaPackagePrefix, - }, - nil, - ) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - overrideJavaPackagePrefix+"."+descriptor.GetPackage(), - descriptor.GetOptions().GetJavaPackage(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - - sweeper := NewFileOptionSweeper() - javaPackageModifier, err := JavaPackage( - zap.NewNop(), - sweeper, - testJavaPackagePrefix, - nil, - map[bufmodule.ModuleFullName]string{ - testModuleFullName: overrideJavaPackagePrefix, - }, - map[string]string{"a.proto": "override"}, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(javaPackageModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetJavaPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaPackage( - zap.NewNop(), - sweeper, - testJavaPackagePrefix, - nil, - map[bufmodule.ModuleFullName]string{ - testModuleFullName: overrideJavaPackagePrefix, - }, - map[string]string{"a.proto": "override"}, - ) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetJavaPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaPackagePath, false) - }) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/java_string_check_utf8.go b/private/bufpkg/bufimage/bufimagemodify/java_string_check_utf8.go deleted file mode 100644 index 767455d69a..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/java_string_check_utf8.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/descriptorpb" -) - -// JavaStringCheckUtf8ID is the ID of the java_string_check_utf8 modifier. -const JavaStringCheckUtf8ID = "JAVA_STRING_CHECK_UTF8" - -// javaStringCheckUtf8Path is the SourceCodeInfo path for the java_string_check_utf8 option. -// https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L375 -var javaStringCheckUtf8Path = []int32{8, 27} - -func javaStringCheckUtf8( - logger *zap.Logger, - sweeper Sweeper, - value bool, - overrides map[string]bool, -) Modifier { - return ModifierFunc( - func(ctx context.Context, image bufimage.Image) error { - seenOverrideFiles := make(map[string]struct{}, len(overrides)) - for _, imageFile := range image.Files() { - modifierValue := value - if overrideValue, ok := overrides[imageFile.Path()]; ok { - modifierValue = overrideValue - seenOverrideFiles[imageFile.Path()] = struct{}{} - } - if err := javaStringCheckUtf8ForFile(ctx, sweeper, imageFile, modifierValue); err != nil { - return err - } - } - for overrideFile := range overrides { - if _, ok := seenOverrideFiles[overrideFile]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", JavaStringCheckUtf8ID, overrideFile) - } - } - return nil - }, - ) -} - -func javaStringCheckUtf8ForFile( - ctx context.Context, - sweeper Sweeper, - imageFile bufimage.ImageFile, - value bool, -) error { - descriptor := imageFile.FileDescriptorProto() - options := descriptor.GetOptions() - switch { - case isWellKnownType(ctx, imageFile): - // The file is a well-known type, don't do anything. - return nil - case options != nil && options.GetJavaStringCheckUtf8() == value: - // The option is already set to the same value, don't do anything. - return nil - case options == nil && descriptorpb.Default_FileOptions_JavaStringCheckUtf8 == value: - // The option is not set, but the value we want to set is the - // same as the default, don't do anything. - return nil - } - if options == nil { - descriptor.Options = &descriptorpb.FileOptions{} - } - descriptor.Options.JavaStringCheckUtf8 = proto.Bool(value) - if sweeper != nil { - sweeper.mark(imageFile.Path(), javaStringCheckUtf8Path) - } - return nil -} diff --git a/private/bufpkg/bufimage/bufimagemodify/java_string_check_utf8_test.go b/private/bufpkg/bufimage/bufimagemodify/java_string_check_utf8_test.go deleted file mode 100644 index 63f3e99a2b..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/java_string_check_utf8_test.go +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestJavaStringCheckUtf8EmptyOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, true) - - sweeper := NewFileOptionSweeper() - JavaStringCheckUtf8Modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, false, nil) - require.NoError(t, err) - modifier := NewMultiModifier(JavaStringCheckUtf8Modifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, false, nil) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, true) - - sweeper := NewFileOptionSweeper() - JavaStringCheckUtf8Modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, false, map[string]string{"a.proto": "true"}) - require.NoError(t, err) - modifier := NewMultiModifier(JavaStringCheckUtf8Modifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, false, map[string]string{"a.proto": "true"}) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - }) -} - -func TestJavaStringCheckUtf8AllOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "alloptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaStringCheckUtf8Path) - - sweeper := NewFileOptionSweeper() - JavaStringCheckUtf8Modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, false, nil) - require.NoError(t, err) - modifier := NewMultiModifier(JavaStringCheckUtf8Modifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.False(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - } - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaStringCheckUtf8Path) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, false, nil) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.False(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaStringCheckUtf8Path) - - sweeper := NewFileOptionSweeper() - JavaStringCheckUtf8Modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, false, map[string]string{"a.proto": "true"}) - require.NoError(t, err) - modifier := NewMultiModifier(JavaStringCheckUtf8Modifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.True(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - continue - } - assert.False(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, false, map[string]string{"a.proto": "true"}) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.True(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - continue - } - assert.False(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, false) - }) -} - -func TestJavaStringCheckUtf8JavaOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "javaoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaStringCheckUtf8Path) - - sweeper := NewFileOptionSweeper() - JavaStringCheckUtf8Modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, true, nil) - require.NoError(t, err) - modifier := NewMultiModifier(JavaStringCheckUtf8Modifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, true, nil) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.True(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, javaStringCheckUtf8Path) - - sweeper := NewFileOptionSweeper() - JavaStringCheckUtf8Modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, true, map[string]string{"override.proto": "false"}) - require.NoError(t, err) - modifier := NewMultiModifier(JavaStringCheckUtf8Modifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.False(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - continue - } - assert.True(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - } - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, javaStringCheckUtf8Path, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, true, map[string]string{"override.proto": "false"}) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.False(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - continue - } - assert.True(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - } - }) -} - -func TestJavaStringCheckUtf8WellKnownTypes(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "wktimport") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - - sweeper := NewFileOptionSweeper() - JavaStringCheckUtf8Modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, true, nil) - require.NoError(t, err) - modifier := NewMultiModifier(JavaStringCheckUtf8Modifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - assert.False(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - continue - } - assert.True(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - } - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - - sweeper := NewFileOptionSweeper() - modifier, err := JavaStringCheckUtf8(zap.NewNop(), sweeper, true, nil) - require.NoError(t, err) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - assert.False(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - continue - } - assert.True(t, descriptor.GetOptions().GetJavaStringCheckUtf8()) - } - }) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/multi_modifier.go b/private/bufpkg/bufimage/bufimagemodify/multi_modifier.go deleted file mode 100644 index 4d1064ab04..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/multi_modifier.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - - "github.com/bufbuild/buf/private/bufpkg/bufimage" -) - -type multiModifier struct { - delegates []Modifier -} - -func newMultiModifier( - delegates []Modifier, -) *multiModifier { - return &multiModifier{ - delegates: delegates, - } -} - -func (m *multiModifier) Modify( - ctx context.Context, - image bufimage.Image, -) error { - for _, delegate := range m.delegates { - if err := delegate.Modify(ctx, image); err != nil { - return err - } - } - return nil -} diff --git a/private/bufpkg/bufimage/bufimagemodify/objc_class_prefix.go b/private/bufpkg/bufimage/bufimagemodify/objc_class_prefix.go deleted file mode 100644 index be45df05e9..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/objc_class_prefix.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "strings" - "unicode" - - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/bufbuild/buf/private/pkg/protoversion" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/descriptorpb" -) - -// ObjcClassPrefixID is the ID of the objc_class_prefix modifier. -const ObjcClassPrefixID = "OBJC_CLASS_PREFIX" - -// objcClassPrefixPath is the SourceCodeInfo path for the objc_class_prefix option. -// https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L425 -var objcClassPrefixPath = []int32{8, 36} - -func objcClassPrefix( - logger *zap.Logger, - sweeper Sweeper, - defaultPrefix string, - except []bufmodule.ModuleFullName, - moduleOverrides map[bufmodule.ModuleFullName]string, - overrides map[string]string, -) Modifier { - // Convert the bufmodule.ModuleFullName types into - // strings so that they're comparable. - exceptModuleFullNameStrings := make(map[string]struct{}, len(except)) - for _, moduleFullName := range except { - exceptModuleFullNameStrings[moduleFullName.String()] = struct{}{} - } - overrideModuleFullNameStrings := make(map[string]string, len(moduleOverrides)) - for moduleFullName, goPackagePrefix := range moduleOverrides { - overrideModuleFullNameStrings[moduleFullName.String()] = goPackagePrefix - } - return ModifierFunc( - func(ctx context.Context, image bufimage.Image) error { - seenModuleFullNameStrings := make(map[string]struct{}, len(overrideModuleFullNameStrings)) - seenOverrideFiles := make(map[string]struct{}, len(overrides)) - for _, imageFile := range image.Files() { - objcClassPrefixValue := objcClassPrefixValue(imageFile) - if defaultPrefix != "" { - objcClassPrefixValue = defaultPrefix - } - if moduleFullName := imageFile.ModuleFullName(); moduleFullName != nil { - moduleFullNameString := moduleFullName.String() - if modulePrefixOverride, ok := overrideModuleFullNameStrings[moduleFullNameString]; ok { - objcClassPrefixValue = modulePrefixOverride - seenModuleFullNameStrings[moduleFullNameString] = struct{}{} - } - } - if overrideValue, ok := overrides[imageFile.Path()]; ok { - objcClassPrefixValue = overrideValue - seenOverrideFiles[imageFile.Path()] = struct{}{} - } - if err := objcClassPrefixForFile(ctx, sweeper, imageFile, objcClassPrefixValue, exceptModuleFullNameStrings); err != nil { - return err - } - } - for moduleFullNameString := range overrideModuleFullNameStrings { - if _, ok := seenModuleFullNameStrings[moduleFullNameString]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", ObjcClassPrefixID, moduleFullNameString) - } - } - for overrideFile := range overrides { - if _, ok := seenOverrideFiles[overrideFile]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", ObjcClassPrefixID, overrideFile) - } - } - return nil - }, - ) -} - -func objcClassPrefixForFile( - ctx context.Context, - sweeper Sweeper, - imageFile bufimage.ImageFile, - objcClassPrefixValue string, - exceptModuleFullNameStrings map[string]struct{}, -) error { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(ctx, imageFile) || objcClassPrefixValue == "" { - // This is a well-known type or we could not resolve a non-empty objc_class_prefix - // value, so this is a no-op. - return nil - } - if moduleFullName := imageFile.ModuleFullName(); moduleFullName != nil { - if _, ok := exceptModuleFullNameStrings[moduleFullName.String()]; ok { - return nil - } - } - if descriptor.Options == nil { - descriptor.Options = &descriptorpb.FileOptions{} - } - descriptor.Options.ObjcClassPrefix = proto.String(objcClassPrefixValue) - if sweeper != nil { - sweeper.mark(imageFile.Path(), objcClassPrefixPath) - } - return nil -} - -// objcClassPrefixValue returns the objc_class_prefix for the given ImageFile based on its -// package declaration. If the image file doesn't have a package declaration, an -// empty string is returned. -func objcClassPrefixValue(imageFile bufimage.ImageFile) string { - pkg := imageFile.FileDescriptorProto().GetPackage() - if pkg == "" { - return "" - } - _, hasPackageVersion := protoversion.NewPackageVersionForPackage(pkg) - packageParts := strings.Split(pkg, ".") - var prefixParts []rune - for i, part := range packageParts { - // Check if last part is a version before appending. - if i == len(packageParts)-1 && hasPackageVersion { - continue - } - // Probably should never be a non-ASCII character, - // but why not support it just in case? - runeSlice := []rune(part) - prefixParts = append(prefixParts, unicode.ToUpper(runeSlice[0])) - } - for len(prefixParts) < 3 { - prefixParts = append(prefixParts, 'X') - } - prefix := string(prefixParts) - if prefix == "GPB" { - prefix = "GPX" - } - return prefix -} diff --git a/private/bufpkg/bufimage/bufimagemodify/objc_class_prefix_test.go b/private/bufpkg/bufimage/bufimagemodify/objc_class_prefix_test.go deleted file mode 100644 index 49da4ab054..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/objc_class_prefix_test.go +++ /dev/null @@ -1,718 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "path/filepath" - "testing" - - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestObjcClassPrefixEmptyOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, true) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, nil) - - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - - sweeper := NewFileOptionSweeper() - modifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, true) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, map[string]string{"a.proto": "override"}) - - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - require.Equal(t, "override", descriptor.GetOptions().GetObjcClassPrefix()) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - - sweeper := NewFileOptionSweeper() - modifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, map[string]string{"a.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - require.Equal(t, "override", descriptor.GetOptions().GetObjcClassPrefix()) - }) -} - -func TestObjcClassPrefixAllOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "alloptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, nil) - - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - - sweeper := NewFileOptionSweeper() - modifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, map[string]string{"a.proto": "override"}) - - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetObjcClassPrefix()) - continue - } - assert.Equal(t, "foo", descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - - sweeper := NewFileOptionSweeper() - modifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, map[string]string{"a.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetObjcClassPrefix()) - continue - } - assert.Equal(t, "foo", descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - }) -} - -func TestObjcClassPrefixObjcOptions(t *testing.T) { - t.Parallel() - testObjcClassPrefixOptions(t, filepath.Join("testdata", "objcoptions", "single"), "AXX") - testObjcClassPrefixOptions(t, filepath.Join("testdata", "objcoptions", "double"), "AWX") - testObjcClassPrefixOptions(t, filepath.Join("testdata", "objcoptions", "triple"), "AWD") - testObjcClassPrefixOptions(t, filepath.Join("testdata", "objcoptions", "unversioned"), "AWD") - testObjcClassPrefixOptions(t, filepath.Join("testdata", "objcoptions", "gpb"), "GPX") -} - -func testObjcClassPrefixOptions(t *testing.T, dirPath string, classPrefix string) { - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, nil) - - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, classPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - - sweeper := NewFileOptionSweeper() - modifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, classPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, map[string]string{"override.proto": "override"}) - - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetObjcClassPrefix()) - continue - } - assert.Equal(t, classPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - - sweeper := NewFileOptionSweeper() - modifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, map[string]string{"override.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetObjcClassPrefix()) - continue - } - assert.Equal(t, classPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - }) -} - -func TestObjcClassPrefixWellKnownTypes(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "wktimport") - modifiedObjcClassPrefix := "AWX" - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, nil) - - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - assert.NotEmpty(t, descriptor.GetOptions().GetObjcClassPrefix()) - assert.NotEqual(t, modifiedObjcClassPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - continue - } - assert.Equal(t, - modifiedObjcClassPrefix, - descriptor.GetOptions().GetObjcClassPrefix(), - ) - } - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - - sweeper := NewFileOptionSweeper() - modifier := ObjcClassPrefix(zap.NewNop(), sweeper, "", nil, nil, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - assert.NotEmpty(t, descriptor.GetOptions().GetObjcClassPrefix()) - assert.NotEqual(t, modifiedObjcClassPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - continue - } - assert.Equal(t, - modifiedObjcClassPrefix, - descriptor.GetOptions().GetObjcClassPrefix(), - ) - } - }) -} - -func TestObjcClassPrefixWithDefault(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "objcoptions", "single") - defaultClassPrefix := "DEFAULT" - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix( - zap.NewNop(), - sweeper, - defaultClassPrefix, - nil, - nil, - nil, - ) - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, defaultClassPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix( - zap.NewNop(), - sweeper, - defaultClassPrefix, - nil, - nil, - nil, - ) - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, defaultClassPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix( - zap.NewNop(), - sweeper, - defaultClassPrefix, - nil, - nil, - map[string]string{"override.proto": "override"}, - ) - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetObjcClassPrefix()) - continue - } - assert.Equal(t, defaultClassPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix( - zap.NewNop(), - sweeper, - defaultClassPrefix, - nil, - nil, - map[string]string{"override.proto": "override"}, - ) - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetObjcClassPrefix()) - continue - } - assert.Equal(t, defaultClassPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - }) -} - -func TestObjcClassPrefixWithExcept(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "objcoptions", "single") - defaultClassPrefix := "DEFAULT" - testModuleFullName, err := bufmodule.NewModuleFullName( - testRemote, - testRepositoryOwner, - testRepositoryName, - ) - require.NoError(t, err) - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix( - zap.NewNop(), - sweeper, - defaultClassPrefix, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - nil, - ) - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - // Should still be non-empty because the module is skipped. - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix( - zap.NewNop(), - sweeper, - defaultClassPrefix, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - nil, - ) - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix( - zap.NewNop(), - sweeper, - defaultClassPrefix, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - map[string]string{"override.proto": "override"}, - ) - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - // Should still be non-empty because the module is skipped. - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix( - zap.NewNop(), - sweeper, - defaultClassPrefix, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - map[string]string{"override.proto": "override"}, - ) - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - }) -} - -func TestObjcClassPrefixWithOverride(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "objcoptions", "single") - testModuleFullName, err := bufmodule.NewModuleFullName( - testRemote, - testRepositoryOwner, - testRepositoryName, - ) - require.NoError(t, err) - defaultClassPrefix := "DEFAULT" - overrideClassPrefix := "MODULE_OVERRIDE" - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix( - zap.NewNop(), - sweeper, - defaultClassPrefix, - nil, - map[bufmodule.ModuleFullName]string{testModuleFullName: overrideClassPrefix}, - nil, - ) - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, overrideClassPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix( - zap.NewNop(), - sweeper, - defaultClassPrefix, - nil, - map[bufmodule.ModuleFullName]string{testModuleFullName: overrideClassPrefix}, - nil, - ) - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, overrideClassPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, objcClassPrefixPath) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix( - zap.NewNop(), - sweeper, - defaultClassPrefix, - nil, - map[bufmodule.ModuleFullName]string{testModuleFullName: overrideClassPrefix}, - map[string]string{"override.proto": "override"}, - ) - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetObjcClassPrefix()) - continue - } - assert.Equal(t, overrideClassPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - - sweeper := NewFileOptionSweeper() - objcClassPrefixModifier := ObjcClassPrefix( - zap.NewNop(), - sweeper, - defaultClassPrefix, - nil, - map[bufmodule.ModuleFullName]string{testModuleFullName: overrideClassPrefix}, - map[string]string{"override.proto": "override"}, - ) - modifier := NewMultiModifier(objcClassPrefixModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetObjcClassPrefix()) - continue - } - assert.Equal(t, overrideClassPrefix, descriptor.GetOptions().GetObjcClassPrefix()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, objcClassPrefixPath, false) - }) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/optimize_for.go b/private/bufpkg/bufimage/bufimagemodify/optimize_for.go deleted file mode 100644 index b0e477b8e3..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/optimize_for.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "go.uber.org/zap" - "google.golang.org/protobuf/types/descriptorpb" -) - -// OptimizeForID is the ID for the optimize_for modifier. -const OptimizeForID = "OPTIMIZE_FOR" - -// optimizeFor is the SourceCodeInfo path for the optimize_for option. -// https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L385 -var optimizeForPath = []int32{8, 9} - -func optimizeFor( - logger *zap.Logger, - sweeper Sweeper, - defaultOptimizeFor descriptorpb.FileOptions_OptimizeMode, - except []bufmodule.ModuleFullName, - moduleOverrides map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode, - overrides map[string]descriptorpb.FileOptions_OptimizeMode, -) Modifier { - // Convert the bufmodule.ModuleFullName types into - // strings so that they're comparable. - exceptModuleFullNameStrings := make(map[string]struct{}, len(except)) - for _, moduleFullName := range except { - exceptModuleFullNameStrings[moduleFullName.String()] = struct{}{} - } - overrideModuleFullNameStrings := make( - map[string]descriptorpb.FileOptions_OptimizeMode, - len(moduleOverrides), - ) - for moduleFullName, optimizeFor := range moduleOverrides { - overrideModuleFullNameStrings[moduleFullName.String()] = optimizeFor - } - return ModifierFunc( - func(ctx context.Context, image bufimage.Image) error { - seenModuleFullNameStrings := make(map[string]struct{}, len(overrideModuleFullNameStrings)) - seenOverrideFiles := make(map[string]struct{}, len(overrides)) - for _, imageFile := range image.Files() { - modifierValue := defaultOptimizeFor - if moduleFullName := imageFile.ModuleFullName(); moduleFullName != nil { - moduleFullNameString := moduleFullName.String() - if optimizeForOverrdie, ok := overrideModuleFullNameStrings[moduleFullNameString]; ok { - modifierValue = optimizeForOverrdie - seenModuleFullNameStrings[moduleFullNameString] = struct{}{} - } - } - if overrideValue, ok := overrides[imageFile.Path()]; ok { - modifierValue = overrideValue - seenOverrideFiles[imageFile.Path()] = struct{}{} - } - if err := optimizeForForFile( - ctx, - sweeper, - imageFile, - modifierValue, - exceptModuleFullNameStrings, - ); err != nil { - return err - } - } - for moduleFullNameString := range overrideModuleFullNameStrings { - if _, ok := seenModuleFullNameStrings[moduleFullNameString]; !ok { - logger.Sugar().Warnf("optimize_for override for %q was unused", moduleFullNameString) - } - } - for overrideFile := range overrides { - if _, ok := seenOverrideFiles[overrideFile]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", OptimizeForID, overrideFile) - } - } - return nil - }, - ) -} - -func optimizeForForFile( - ctx context.Context, - sweeper Sweeper, - imageFile bufimage.ImageFile, - value descriptorpb.FileOptions_OptimizeMode, - exceptModuleFullNameStrings map[string]struct{}, -) error { - descriptor := imageFile.FileDescriptorProto() - options := descriptor.GetOptions() - switch { - case isWellKnownType(ctx, imageFile): - // The file is a well-known type, don't do anything. - return nil - case options != nil && options.GetOptimizeFor() == value: - // The option is already set to the same value, don't do anything. - return nil - case options == nil && descriptorpb.Default_FileOptions_OptimizeFor == value: - // The option is not set, but the value we want to set is the - // same as the default, don't do anything. - return nil - } - if moduleFullName := imageFile.ModuleFullName(); moduleFullName != nil { - if _, ok := exceptModuleFullNameStrings[moduleFullName.String()]; ok { - return nil - } - } - if options == nil { - descriptor.Options = &descriptorpb.FileOptions{} - } - descriptor.Options.OptimizeFor = &value - if sweeper != nil { - sweeper.mark(imageFile.Path(), optimizeForPath) - } - return nil -} diff --git a/private/bufpkg/bufimage/bufimagemodify/optimize_for_test.go b/private/bufpkg/bufimage/bufimagemodify/optimize_for_test.go deleted file mode 100644 index 08793c2b42..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/optimize_for_test.go +++ /dev/null @@ -1,664 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "path/filepath" - "testing" - - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" - "google.golang.org/protobuf/types/descriptorpb" -) - -func TestOptimizeForEmptyOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, true) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_LITE_RUNTIME, nil, nil, nil) - require.NoError(t, err) - modifier := NewMultiModifier( - optimizeForModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, descriptorpb.FileOptions_LITE_RUNTIME, descriptor.GetOptions().GetOptimizeFor()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, false) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_LITE_RUNTIME, nil, nil, nil) - require.NoError(t, err) - err = optimizeForModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, descriptorpb.FileOptions_LITE_RUNTIME, descriptor.GetOptions().GetOptimizeFor()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, true) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_LITE_RUNTIME, nil, nil, map[string]string{"a.proto": "SPEED"}) - require.NoError(t, err) - modifier := NewMultiModifier( - optimizeForModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, descriptorpb.FileOptions_SPEED, descriptor.GetOptions().GetOptimizeFor()) - continue - } - assert.Equal(t, descriptorpb.FileOptions_LITE_RUNTIME, descriptor.GetOptions().GetOptimizeFor()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, false) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_LITE_RUNTIME, nil, nil, map[string]string{"a.proto": "SPEED"}) - require.NoError(t, err) - err = optimizeForModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, descriptorpb.FileOptions_SPEED, descriptor.GetOptions().GetOptimizeFor()) - continue - } - assert.Equal(t, descriptorpb.FileOptions_LITE_RUNTIME, descriptor.GetOptions().GetOptimizeFor()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, false) - }) -} - -func TestOptimizeForAllOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "alloptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, optimizeForPath) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_LITE_RUNTIME, nil, nil, nil) - require.NoError(t, err) - modifier := NewMultiModifier( - optimizeForModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, descriptorpb.FileOptions_LITE_RUNTIME, descriptor.GetOptions().GetOptimizeFor()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, false) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_LITE_RUNTIME, nil, nil, nil) - require.NoError(t, err) - err = optimizeForModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, descriptorpb.FileOptions_LITE_RUNTIME, descriptor.GetOptions().GetOptimizeFor()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, optimizeForPath) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_LITE_RUNTIME, nil, nil, map[string]string{"a.proto": "SPEED"}) - require.NoError(t, err) - modifier := NewMultiModifier( - optimizeForModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, descriptorpb.FileOptions_SPEED, descriptor.GetOptions().GetOptimizeFor()) - continue - } - assert.Equal(t, descriptorpb.FileOptions_LITE_RUNTIME, descriptor.GetOptions().GetOptimizeFor()) - } - assertFileOptionSourceCodeInfoNotEmpty(t, image, optimizeForPath) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, false) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_LITE_RUNTIME, nil, nil, map[string]string{"a.proto": "SPEED"}) - require.NoError(t, err) - err = optimizeForModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, descriptorpb.FileOptions_SPEED, descriptor.GetOptions().GetOptimizeFor()) - continue - } - assert.Equal(t, descriptorpb.FileOptions_LITE_RUNTIME, descriptor.GetOptions().GetOptimizeFor()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, false) - }) -} - -func TestOptimizeForCcOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "ccoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, optimizeForPath) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_SPEED, nil, nil, nil) - require.NoError(t, err) - modifier := NewMultiModifier( - optimizeForModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, descriptorpb.FileOptions_SPEED, descriptor.GetOptions().GetOptimizeFor()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, false) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_SPEED, nil, nil, nil) - require.NoError(t, err) - err = optimizeForModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, descriptorpb.FileOptions_SPEED, descriptor.GetOptions().GetOptimizeFor()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, optimizeForPath) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_SPEED, nil, nil, map[string]string{"a.proto": "LITE_RUNTIME"}) - require.NoError(t, err) - modifier := NewMultiModifier( - optimizeForModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, descriptorpb.FileOptions_LITE_RUNTIME, descriptor.GetOptions().GetOptimizeFor()) - continue - } - assert.Equal(t, descriptorpb.FileOptions_SPEED, descriptor.GetOptions().GetOptimizeFor()) - } - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, false) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_SPEED, nil, nil, map[string]string{"a.proto": "LITE_RUNTIME"}) - require.NoError(t, err) - err = optimizeForModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, descriptorpb.FileOptions_LITE_RUNTIME, descriptor.GetOptions().GetOptimizeFor()) - continue - } - assert.Equal(t, descriptorpb.FileOptions_SPEED, descriptor.GetOptions().GetOptimizeFor()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, optimizeForPath, false) - }) -} - -func TestOptimizeForWellKnownTypes(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "wktimport") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_SPEED, nil, nil, nil) - require.NoError(t, err) - modifier := NewMultiModifier( - optimizeForModifier, - ModifierFunc(sweeper.Sweep), - ) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, descriptorpb.FileOptions_SPEED, descriptor.GetOptions().GetOptimizeFor()) - } - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor(zap.NewNop(), sweeper, descriptorpb.FileOptions_SPEED, nil, nil, nil) - require.NoError(t, err) - err = optimizeForModifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, descriptorpb.FileOptions_SPEED, descriptor.GetOptions().GetOptimizeFor()) - } - }) -} - -func TestOptimizeForWithExcept(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - testModuleFullName, err := bufmodule.NewModuleFullName( - testRemote, - testRepositoryOwner, - testRepositoryName, - ) - require.NoError(t, err) - - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor( - zap.NewNop(), - sweeper, - descriptorpb.FileOptions_CODE_SIZE, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - nil, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(optimizeForModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor( - zap.NewNop(), - sweeper, - descriptorpb.FileOptions_CODE_SIZE, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - nil, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(optimizeForModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor( - zap.NewNop(), - sweeper, - descriptorpb.FileOptions_CODE_SIZE, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - map[string]string{ - "a.proto": "LITE_RUNTIME", - }, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(optimizeForModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor( - zap.NewNop(), - sweeper, - descriptorpb.FileOptions_CODE_SIZE, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - map[string]string{ - "a.proto": "SPEED", - }, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(optimizeForModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) -} - -func TestOptimizeForWithOverride(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - overrideOptimizeFor := descriptorpb.FileOptions_LITE_RUNTIME - testModuleFullName, err := bufmodule.NewModuleFullName( - testRemote, - testRepositoryOwner, - testRepositoryName, - ) - require.NoError(t, err) - - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor( - zap.NewNop(), - sweeper, - descriptorpb.FileOptions_CODE_SIZE, - nil, - map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode{ - testModuleFullName: overrideOptimizeFor, - }, - nil, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(optimizeForModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - overrideOptimizeFor, - descriptor.GetOptions().GetOptimizeFor(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor( - zap.NewNop(), - sweeper, - descriptorpb.FileOptions_CODE_SIZE, - nil, - map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode{ - testModuleFullName: overrideOptimizeFor, - }, - nil, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(optimizeForModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - overrideOptimizeFor, - descriptor.GetOptions().GetOptimizeFor(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor( - zap.NewNop(), - sweeper, - descriptorpb.FileOptions_CODE_SIZE, - nil, - map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode{ - testModuleFullName: overrideOptimizeFor, - }, - map[string]string{ - "a.proto": "CODE_SIZE", - }, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(optimizeForModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - descriptorpb.FileOptions_CODE_SIZE, - descriptor.GetOptions().GetOptimizeFor(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - - sweeper := NewFileOptionSweeper() - optimizeForModifier, err := OptimizeFor( - zap.NewNop(), - sweeper, - descriptorpb.FileOptions_CODE_SIZE, - nil, - map[bufmodule.ModuleFullName]descriptorpb.FileOptions_OptimizeMode{ - testModuleFullName: overrideOptimizeFor, - }, - map[string]string{ - "a.proto": "CODE_SIZE", - }, - ) - require.NoError(t, err) - - modifier := NewMultiModifier(optimizeForModifier, ModifierFunc(sweeper.Sweep)) - err = modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, - descriptorpb.FileOptions_CODE_SIZE, - descriptor.GetOptions().GetOptimizeFor(), - ) - } - assertFileOptionSourceCodeInfoEmpty(t, image, goPackagePath, false) - }) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/override.go b/private/bufpkg/bufimage/bufimagemodify/override.go new file mode 100644 index 0000000000..813c05e839 --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/override.go @@ -0,0 +1,421 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// 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 bufimagemodify + +import ( + "fmt" + "path" + "strings" + "unicode" + + "github.com/bufbuild/buf/private/bufpkg/bufconfig" + "github.com/bufbuild/buf/private/bufpkg/bufimage" + "github.com/bufbuild/buf/private/pkg/normalpath" + "github.com/bufbuild/buf/private/pkg/protoversion" + "github.com/bufbuild/buf/private/pkg/stringutil" + "google.golang.org/protobuf/types/descriptorpb" +) + +// Keywords and classes that could be produced by our heuristic. +// They must not be used in a php_namespace. +// Ref: https://www.php.net/manual/en/reserved.php +var phpReservedKeywords = map[string]struct{}{ + // Reserved classes as per above. + "directory": {}, + "exception": {}, + "errorexception": {}, + "closure": {}, + "generator": {}, + "arithmeticerror": {}, + "assertionerror": {}, + "divisionbyzeroerror": {}, + "error": {}, + "throwable": {}, + "parseerror": {}, + "typeerror": {}, + // Keywords avoided by protoc. + // Ref: https://github.com/protocolbuffers/protobuf/blob/66d749188ff2a2e30e932110222d58da7c6a8d49/src/google/protobuf/compiler/php/php_generator.cc#L50-L66 + "abstract": {}, + "and": {}, + "array": {}, + "as": {}, + "break": {}, + "callable": {}, + "case": {}, + "catch": {}, + "class": {}, + "clone": {}, + "const": {}, + "continue": {}, + "declare": {}, + "default": {}, + "die": {}, + "do": {}, + "echo": {}, + "else": {}, + "elseif": {}, + "empty": {}, + "enddeclare": {}, + "endfor": {}, + "endforeach": {}, + "endif": {}, + "endswitch": {}, + "endwhile": {}, + "eval": {}, + "exit": {}, + "extends": {}, + "final": {}, + "finally": {}, + "fn": {}, + "for": {}, + "foreach": {}, + "function": {}, + "global": {}, + "goto": {}, + "if": {}, + "implements": {}, + "include": {}, + "include_once": {}, + "instanceof": {}, + "insteadof": {}, + "interface": {}, + "isset": {}, + "list": {}, + "match": {}, + "namespace": {}, + "new": {}, + "or": {}, + "print": {}, + "private": {}, + "protected": {}, + "public": {}, + "require": {}, + "require_once": {}, + "return": {}, + "static": {}, + "switch": {}, + "throw": {}, + "trait": {}, + "try": {}, + "unset": {}, + "use": {}, + "var": {}, + "while": {}, + "xor": {}, + "yield": {}, + "int": {}, + "float": {}, + "bool": {}, + "string": {}, + "true": {}, + "false": {}, + "null": {}, + "void": {}, + "iterable": {}, +} + +type stringOverrideOptions struct { + value string + prefix string + suffix string +} + +func stringOverrideFromConfig( + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, + defaultOverrideOptions stringOverrideOptions, + valueFileOption bufconfig.FileOption, + prefixFileOption bufconfig.FileOption, + suffixFileOption bufconfig.FileOption, +) (stringOverrideOptions, error) { + if isFileOptionDisabledForFile( + imageFile, + valueFileOption, + config, + ) { + return stringOverrideOptions{}, nil + } + overrideOptions := defaultOverrideOptions + ignorePrefix := prefixFileOption == bufconfig.FileOptionUnspecified || isFileOptionDisabledForFile(imageFile, prefixFileOption, config) + ignoreSuffix := suffixFileOption == bufconfig.FileOptionUnspecified || isFileOptionDisabledForFile(imageFile, suffixFileOption, config) + if ignorePrefix { + overrideOptions.prefix = "" + } + if ignoreSuffix { + overrideOptions.suffix = "" + } + for _, overrideRule := range config.Overrides() { + if !fileMatchConfig(imageFile, overrideRule.Path(), overrideRule.ModuleFullName()) { + continue + } + switch overrideRule.FileOption() { + case valueFileOption: + valueString, ok := overrideRule.Value().(string) + if !ok { + // This should never happen, since the override rule has been validated. + return stringOverrideOptions{}, fmt.Errorf("invalid value type for %v override: %T", valueFileOption, overrideRule.Value()) + } + // If the latest override matched is a value override (java_package as opposed to java_package_prefix), use the value. + overrideOptions = stringOverrideOptions{value: valueString} + case prefixFileOption: + if ignorePrefix { + continue + } + prefix, ok := overrideRule.Value().(string) + if !ok { + // This should never happen, since the override rule has been validated. + return stringOverrideOptions{}, fmt.Errorf("invalid value type for %v override: %T", prefixFileOption, overrideRule.Value()) + } + // Keep the suffix if the last two overrides are suffix and prefix. + overrideOptions = stringOverrideOptions{ + prefix: prefix, + suffix: overrideOptions.suffix, + } + case suffixFileOption: + if ignoreSuffix { + continue + } + suffix, ok := overrideRule.Value().(string) + if !ok { + // This should never happen, since the override rule has been validated. + return stringOverrideOptions{}, fmt.Errorf("invalid value type for %v override: %T", suffixFileOption, overrideRule.Value()) + } + // Keep the prefix if the last two overrides are suffix and prefix. + overrideOptions = stringOverrideOptions{ + prefix: overrideOptions.prefix, + suffix: suffix, + } + } + } + return overrideOptions, nil +} + +// returns the override value and whether managed mode is DISABLED for this file for this file option. +func overrideFromConfig[T bool | descriptorpb.FileOptions_OptimizeMode]( + imageFile bufimage.ImageFile, + config bufconfig.GenerateManagedConfig, + fileOption bufconfig.FileOption, +) (*T, error) { + var override *T + for _, overrideRule := range config.Overrides() { + if !fileMatchConfig(imageFile, overrideRule.Path(), overrideRule.ModuleFullName()) { + continue + } + if overrideRule.FileOption() != fileOption { + continue + } + value, ok := overrideRule.Value().(T) + if !ok { + // This should never happen, since the override rule has been validated. + return nil, fmt.Errorf("invalid value type for %v override: %T", fileOption, overrideRule.Value()) + } + override = &value + } + return override, nil +} + +func isFileOptionDisabledForFile( + imageFile bufimage.ImageFile, + fileOption bufconfig.FileOption, + config bufconfig.GenerateManagedConfig, +) bool { + for _, disableRule := range config.Disables() { + if disableRule.FileOption() != bufconfig.FileOptionUnspecified && disableRule.FileOption() != fileOption { + continue + } + if !fileMatchConfig(imageFile, disableRule.Path(), disableRule.ModuleFullName()) { + continue + } + return true + } + return false +} + +func fileMatchConfig( + imageFile bufimage.ImageFile, + requiredPath string, + requiredModuleFullName string, +) bool { + if requiredPath != "" && !normalpath.EqualsOrContainsPath(requiredPath, imageFile.Path(), normalpath.Relative) { + return false + } + if requiredModuleFullName != "" && (imageFile.ModuleFullName() == nil || imageFile.ModuleFullName().String() != requiredModuleFullName) { + return false + } + return true +} + +// TODO: unify naming of these helpers +func getJavaPackageValue(imageFile bufimage.ImageFile, stringOverrideOptions stringOverrideOptions) string { + if pkg := imageFile.FileDescriptorProto().GetPackage(); pkg != "" { + if stringOverrideOptions.prefix != "" { + pkg = stringOverrideOptions.prefix + "." + pkg + } + if stringOverrideOptions.suffix != "" { + pkg = pkg + "." + stringOverrideOptions.suffix + } + return pkg + } + return "" +} + +func getCsharpNamespaceValue(imageFile bufimage.ImageFile, prefix string) string { + namespace := csharpNamespaceValue(imageFile) + if namespace == "" { + return "" + } + if prefix == "" { + return namespace + } + return prefix + "." + namespace +} + +func getPhpMetadataNamespaceValue(imageFile bufimage.ImageFile, suffix string) string { + namespace := phpNamespaceValue(imageFile) + if namespace == "" { + return "" + } + if suffix == "" { + return namespace + } + return namespace + `\` + suffix +} + +func getRubyPackageValue(imageFile bufimage.ImageFile, suffix string) string { + rubyPackage := rubyPackageValue(imageFile) + if rubyPackage == "" { + return "" + } + if suffix == "" { + return rubyPackage + } + return rubyPackage + "::" + suffix +} + +// TODO: is this needed? +// csharpNamespaceValue returns the csharp_namespace for the given ImageFile based on its +// package declaration. If the image file doesn't have a package declaration, an +// empty string is returned. +func csharpNamespaceValue(imageFile bufimage.ImageFile) string { + pkg := imageFile.FileDescriptorProto().GetPackage() + if pkg == "" { + return "" + } + packageParts := strings.Split(pkg, ".") + for i, part := range packageParts { + packageParts[i] = stringutil.ToPascalCase(part) + } + return strings.Join(packageParts, ".") +} + +// goPackageImportPathForFile returns the go_package import path for the given +// ImageFile. If the package contains a version suffix, and if there are more +// than two components, concatenate the final two components. Otherwise, we +// exclude the ';' separator and adopt the default behavior from the import path. +// +// For example, an ImageFile with `package acme.weather.v1;` will include `;weatherv1` +// in the `go_package` declaration so that the generated package is named as such. +func goPackageImportPathForFile(imageFile bufimage.ImageFile, importPathPrefix string) string { + goPackageImportPath := path.Join(importPathPrefix, path.Dir(imageFile.Path())) + packageName := imageFile.FileDescriptorProto().GetPackage() + if _, ok := protoversion.NewPackageVersionForPackage(packageName); ok { + parts := strings.Split(packageName, ".") + if len(parts) >= 2 { + goPackageImportPath += ";" + parts[len(parts)-2] + parts[len(parts)-1] + } + } + return goPackageImportPath +} + +func javaOuterClassnameValue(imageFile bufimage.ImageFile) string { + return stringutil.ToPascalCase(normalpath.Base(imageFile.Path())) +} + +// objcClassPrefixValue returns the objc_class_prefix for the given ImageFile based on its +// package declaration. If the image file doesn't have a package declaration, an +// empty string is returned. +func objcClassPrefixValue(imageFile bufimage.ImageFile) string { + pkg := imageFile.FileDescriptorProto().GetPackage() + if pkg == "" { + return "" + } + _, hasPackageVersion := protoversion.NewPackageVersionForPackage(pkg) + packageParts := strings.Split(pkg, ".") + var prefixParts []rune + for i, part := range packageParts { + // Check if last part is a version before appending. + if i == len(packageParts)-1 && hasPackageVersion { + continue + } + // Probably should never be a non-ASCII character, + // but why not support it just in case? + runeSlice := []rune(part) + prefixParts = append(prefixParts, unicode.ToUpper(runeSlice[0])) + } + for len(prefixParts) < 3 { + prefixParts = append(prefixParts, 'X') + } + prefix := string(prefixParts) + if prefix == "GPB" { + prefix = "GPX" + } + return prefix +} + +// phpMetadataNamespaceValue returns the php_metadata_namespace for the given ImageFile based on its +// package declaration. If the image file doesn't have a package declaration, an +// empty string is returned. +func phpMetadataNamespaceValue(imageFile bufimage.ImageFile) string { + phpNamespace := phpNamespaceValue(imageFile) + if phpNamespace == "" { + return "" + } + return phpNamespace + `\GPBMetadata` +} + +// phpNamespaceValue returns the php_namespace for the given ImageFile based on its +// package declaration. If the image file doesn't have a package declaration, an +// empty string is returned. +func phpNamespaceValue(imageFile bufimage.ImageFile) string { + pkg := imageFile.FileDescriptorProto().GetPackage() + if pkg == "" { + return "" + } + packageParts := strings.Split(pkg, ".") + for i, part := range packageParts { + packagePart := stringutil.ToPascalCase(part) + if _, ok := phpReservedKeywords[strings.ToLower(part)]; ok { + // Append _ to the package part if it is a reserved keyword. + packagePart += "_" + } + packageParts[i] = packagePart + } + return strings.Join(packageParts, `\`) +} + +// rubyPackageValue returns the ruby_package for the given ImageFile based on its +// package declaration. If the image file doesn't have a package declaration, an +// empty string is returned. +func rubyPackageValue(imageFile bufimage.ImageFile) string { + pkg := imageFile.FileDescriptorProto().GetPackage() + if pkg == "" { + return "" + } + packageParts := strings.Split(pkg, ".") + for i, part := range packageParts { + packageParts[i] = stringutil.ToPascalCase(part) + } + return strings.Join(packageParts, "::") +} diff --git a/private/bufpkg/bufimage/bufimagemodify/php_metadata_namespace.go b/private/bufpkg/bufimage/bufimagemodify/php_metadata_namespace.go deleted file mode 100644 index 8f3b205687..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/php_metadata_namespace.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/descriptorpb" -) - -// PhpMetadataNamespaceID is the ID of the php_metadata_namespace modifier. -const PhpMetadataNamespaceID = "PHP_METADATA_NAMESPACE" - -var ( - // phpMetadataNamespacePath is the SourceCodeInfo path for the php_metadata_namespace option. - // Ref: https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L448 - phpMetadataNamespacePath = []int32{8, 44} -) - -func phpMetadataNamespace( - logger *zap.Logger, - sweeper Sweeper, - overrides map[string]string, -) Modifier { - return ModifierFunc( - func(ctx context.Context, image bufimage.Image) error { - seenOverrideFiles := make(map[string]struct{}, len(overrides)) - for _, imageFile := range image.Files() { - phpMetadataNamespaceValue := phpMetadataNamespaceValue(imageFile) - if overrideValue, ok := overrides[imageFile.Path()]; ok { - phpMetadataNamespaceValue = overrideValue - seenOverrideFiles[imageFile.Path()] = struct{}{} - } - if err := phpMetadataNamespaceForFile(ctx, sweeper, imageFile, phpMetadataNamespaceValue); err != nil { - return err - } - } - for overrideFile := range overrides { - if _, ok := seenOverrideFiles[overrideFile]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", PhpMetadataNamespaceID, overrideFile) - } - } - return nil - }, - ) -} - -func phpMetadataNamespaceForFile( - ctx context.Context, - sweeper Sweeper, - imageFile bufimage.ImageFile, - phpMetadataNamespaceValue string, -) error { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(ctx, imageFile) || phpMetadataNamespaceValue == "" { - // This is a well-known type or we could not resolve a non-empty php_metadata_namespace - // value, so this is a no-op. - return nil - } - if descriptor.Options == nil { - descriptor.Options = &descriptorpb.FileOptions{} - } - descriptor.Options.PhpMetadataNamespace = proto.String(phpMetadataNamespaceValue) - if sweeper != nil { - sweeper.mark(imageFile.Path(), phpMetadataNamespacePath) - } - return nil -} - -// phpMetadataNamespaceValue returns the php_metadata_namespace for the given ImageFile based on its -// package declaration. If the image file doesn't have a package declaration, an -// empty string is returned. -func phpMetadataNamespaceValue(imageFile bufimage.ImageFile) string { - phpNamespace := phpNamespaceValue(imageFile) - if phpNamespace == "" { - return "" - } - return phpNamespace + `\GPBMetadata` -} diff --git a/private/bufpkg/bufimage/bufimagemodify/php_metadata_namespace_test.go b/private/bufpkg/bufimage/bufimagemodify/php_metadata_namespace_test.go deleted file mode 100644 index 928415a13d..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/php_metadata_namespace_test.go +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestPhpMetadataNamespaceEmptyOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, true) - - sweeper := NewFileOptionSweeper() - phpMetadataNamespaceModifier := PhpMetadataNamespace(zap.NewNop(), sweeper, nil) - - modifier := NewMultiModifier(phpMetadataNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpMetadataNamespace(zap.NewNop(), sweeper, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, true) - - sweeper := NewFileOptionSweeper() - phpMetadataNamespaceModifier := PhpMetadataNamespace(zap.NewNop(), sweeper, map[string]string{"a.proto": "override"}) - - modifier := NewMultiModifier(phpMetadataNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - require.Equal(t, "override", descriptor.GetOptions().GetPhpMetadataNamespace()) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpMetadataNamespace(zap.NewNop(), sweeper, map[string]string{"a.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - require.Equal(t, "override", descriptor.GetOptions().GetPhpMetadataNamespace()) - }) -} - -func TestPhpMetadataNamespaceAllOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "alloptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, phpMetadataNamespacePath) - - sweeper := NewFileOptionSweeper() - phpMetadataNamespaceModifier := PhpMetadataNamespace(zap.NewNop(), sweeper, nil) - - modifier := NewMultiModifier(phpMetadataNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetPhpMetadataNamespace()) - } - assertFileOptionSourceCodeInfoNotEmpty(t, image, phpMetadataNamespacePath) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpMetadataNamespace(zap.NewNop(), sweeper, map[string]string{"a.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetPhpMetadataNamespace()) - continue - } - assert.Equal(t, "foo", descriptor.GetOptions().GetPhpMetadataNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, phpMetadataNamespacePath) - - sweeper := NewFileOptionSweeper() - phpMetadataNamespaceModifier := PhpMetadataNamespace(zap.NewNop(), sweeper, nil) - - modifier := NewMultiModifier(phpMetadataNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetPhpMetadataNamespace()) - } - assertFileOptionSourceCodeInfoNotEmpty(t, image, phpMetadataNamespacePath) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpMetadataNamespace(zap.NewNop(), sweeper, map[string]string{"a.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetPhpMetadataNamespace()) - continue - } - assert.Equal(t, "foo", descriptor.GetOptions().GetPhpMetadataNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, false) - }) -} - -func TestPhpMetadataNamespaceOptions(t *testing.T) { - t.Parallel() - testPhpMetadataNamespaceOptions(t, filepath.Join("testdata", "phpoptions", "single"), `Acme\V1\GPBMetadata`) - testPhpMetadataNamespaceOptions(t, filepath.Join("testdata", "phpoptions", "double"), `Acme\Weather\V1\GPBMetadata`) - testPhpMetadataNamespaceOptions(t, filepath.Join("testdata", "phpoptions", "triple"), `Acme\Weather\Data\V1\GPBMetadata`) - testPhpMetadataNamespaceOptions(t, filepath.Join("testdata", "phpoptions", "reserved"), `Acme\Error_\V1\GPBMetadata`) -} - -func testPhpMetadataNamespaceOptions(t *testing.T, dirPath string, classPrefix string) { - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, phpMetadataNamespacePath) - - sweeper := NewFileOptionSweeper() - phpMetadataNamespaceModifier := PhpMetadataNamespace(zap.NewNop(), sweeper, nil) - - modifier := NewMultiModifier(phpMetadataNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, classPrefix, descriptor.GetOptions().GetPhpMetadataNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpMetadataNamespace(zap.NewNop(), sweeper, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, classPrefix, descriptor.GetOptions().GetPhpMetadataNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, phpMetadataNamespacePath) - - sweeper := NewFileOptionSweeper() - phpMetadataNamespaceModifier := PhpMetadataNamespace(zap.NewNop(), sweeper, map[string]string{"override.proto": "override"}) - - modifier := NewMultiModifier(phpMetadataNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetPhpMetadataNamespace()) - continue - } - assert.Equal(t, classPrefix, descriptor.GetOptions().GetPhpMetadataNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpMetadataNamespace(zap.NewNop(), sweeper, map[string]string{"override.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetPhpMetadataNamespace()) - continue - } - assert.Equal(t, classPrefix, descriptor.GetOptions().GetPhpMetadataNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpMetadataNamespacePath, false) - }) -} - -func TestPhpMetadataNamespaceWellKnownTypes(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "wktimport") - modifiedPhpMetadataNamespace := `Acme\Weather\V1alpha1\GPBMetadata` - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - - sweeper := NewFileOptionSweeper() - phpMetadataNamespaceModifier := PhpMetadataNamespace(zap.NewNop(), sweeper, nil) - - modifier := NewMultiModifier(phpMetadataNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - // php_namespace is unset for the well-known types - assert.Empty(t, descriptor.GetOptions().GetPhpMetadataNamespace()) - continue - } - assert.Equal(t, - modifiedPhpMetadataNamespace, - descriptor.GetOptions().GetPhpMetadataNamespace(), - ) - } - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpMetadataNamespace(zap.NewNop(), sweeper, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - // php_namespace is unset for the well-known types - assert.Empty(t, descriptor.GetOptions().GetPhpMetadataNamespace()) - continue - } - assert.Equal(t, - modifiedPhpMetadataNamespace, - descriptor.GetOptions().GetPhpMetadataNamespace(), - ) - } - }) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/php_namespace.go b/private/bufpkg/bufimage/bufimagemodify/php_namespace.go deleted file mode 100644 index 2e5bf64e33..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/php_namespace.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "strings" - - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "github.com/bufbuild/buf/private/pkg/stringutil" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/descriptorpb" -) - -// PhpNamespaceID is the ID of the php_namespace modifier. -const PhpNamespaceID = "PHP_NAMESPACE" - -var ( - // phpNamespacePath is the SourceCodeInfo path for the php_namespace option. - // Ref: https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L443 - phpNamespacePath = []int32{8, 41} - - // Keywords and classes that could be produced by our heuristic. - // They must not be used in a php_namespace. - // Ref: https://www.php.net/manual/en/reserved.php - phpReservedKeywords = map[string]struct{}{ - // Reserved classes as per above. - "directory": {}, - "exception": {}, - "errorexception": {}, - "closure": {}, - "generator": {}, - "arithmeticerror": {}, - "assertionerror": {}, - "divisionbyzeroerror": {}, - "error": {}, - "throwable": {}, - "parseerror": {}, - "typeerror": {}, - // Keywords avoided by protoc. - // Ref: https://github.com/protocolbuffers/protobuf/blob/66d749188ff2a2e30e932110222d58da7c6a8d49/src/google/protobuf/compiler/php/php_generator.cc#L50-L66 - "abstract": {}, - "and": {}, - "array": {}, - "as": {}, - "break": {}, - "callable": {}, - "case": {}, - "catch": {}, - "class": {}, - "clone": {}, - "const": {}, - "continue": {}, - "declare": {}, - "default": {}, - "die": {}, - "do": {}, - "echo": {}, - "else": {}, - "elseif": {}, - "empty": {}, - "enddeclare": {}, - "endfor": {}, - "endforeach": {}, - "endif": {}, - "endswitch": {}, - "endwhile": {}, - "eval": {}, - "exit": {}, - "extends": {}, - "final": {}, - "finally": {}, - "fn": {}, - "for": {}, - "foreach": {}, - "function": {}, - "global": {}, - "goto": {}, - "if": {}, - "implements": {}, - "include": {}, - "include_once": {}, - "instanceof": {}, - "insteadof": {}, - "interface": {}, - "isset": {}, - "list": {}, - "match": {}, - "namespace": {}, - "new": {}, - "or": {}, - "print": {}, - "private": {}, - "protected": {}, - "public": {}, - "require": {}, - "require_once": {}, - "return": {}, - "static": {}, - "switch": {}, - "throw": {}, - "trait": {}, - "try": {}, - "unset": {}, - "use": {}, - "var": {}, - "while": {}, - "xor": {}, - "yield": {}, - "int": {}, - "float": {}, - "bool": {}, - "string": {}, - "true": {}, - "false": {}, - "null": {}, - "void": {}, - "iterable": {}, - } -) - -func phpNamespace( - logger *zap.Logger, - sweeper Sweeper, - overrides map[string]string, -) Modifier { - return ModifierFunc( - func(ctx context.Context, image bufimage.Image) error { - seenOverrideFiles := make(map[string]struct{}, len(overrides)) - for _, imageFile := range image.Files() { - phpNamespaceValue := phpNamespaceValue(imageFile) - if overrideValue, ok := overrides[imageFile.Path()]; ok { - phpNamespaceValue = overrideValue - seenOverrideFiles[imageFile.Path()] = struct{}{} - } - if err := phpNamespaceForFile(ctx, sweeper, imageFile, phpNamespaceValue); err != nil { - return err - } - } - for overrideFile := range overrides { - if _, ok := seenOverrideFiles[overrideFile]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", PhpNamespaceID, overrideFile) - } - } - return nil - }, - ) -} - -func phpNamespaceForFile( - ctx context.Context, - sweeper Sweeper, - imageFile bufimage.ImageFile, - phpNamespaceValue string, -) error { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(ctx, imageFile) || phpNamespaceValue == "" { - // This is a well-known type or we could not resolve a non-empty php_namespace - // value, so this is a no-op. - return nil - } - if descriptor.Options == nil { - descriptor.Options = &descriptorpb.FileOptions{} - } - descriptor.Options.PhpNamespace = proto.String(phpNamespaceValue) - if sweeper != nil { - sweeper.mark(imageFile.Path(), phpNamespacePath) - } - return nil -} - -// phpNamespaceValue returns the php_namespace for the given ImageFile based on its -// package declaration. If the image file doesn't have a package declaration, an -// empty string is returned. -func phpNamespaceValue(imageFile bufimage.ImageFile) string { - pkg := imageFile.FileDescriptorProto().GetPackage() - if pkg == "" { - return "" - } - packageParts := strings.Split(pkg, ".") - for i, part := range packageParts { - packagePart := stringutil.ToPascalCase(part) - if _, ok := phpReservedKeywords[strings.ToLower(part)]; ok { - // Append _ to the package part if it is a reserved keyword. - packagePart += "_" - } - packageParts[i] = packagePart - } - return strings.Join(packageParts, `\`) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/php_namespace_test.go b/private/bufpkg/bufimage/bufimagemodify/php_namespace_test.go deleted file mode 100644 index f1c6b9c7bb..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/php_namespace_test.go +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestPhpNamespaceEmptyOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, true) - - sweeper := NewFileOptionSweeper() - phpNamespaceModifier := PhpNamespace(zap.NewNop(), sweeper, nil) - - modifier := NewMultiModifier(phpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpNamespace(zap.NewNop(), sweeper, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, true) - - sweeper := NewFileOptionSweeper() - phpNamespaceModifier := PhpNamespace(zap.NewNop(), sweeper, map[string]string{"a.proto": "override"}) - - modifier := NewMultiModifier(phpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetPhpNamespace()) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpNamespace(zap.NewNop(), sweeper, map[string]string{"a.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetPhpNamespace()) - }) -} - -func TestPhpNamespaceAllOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "alloptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, phpNamespacePath) - - sweeper := NewFileOptionSweeper() - phpNamespaceModifier := PhpNamespace(zap.NewNop(), sweeper, nil) - - modifier := NewMultiModifier(phpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetPhpNamespace()) - } - assertFileOptionSourceCodeInfoNotEmpty(t, image, phpNamespacePath) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpNamespace(zap.NewNop(), sweeper, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetPhpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, phpNamespacePath) - - sweeper := NewFileOptionSweeper() - phpNamespaceModifier := PhpNamespace(zap.NewNop(), sweeper, map[string]string{"a.proto": "override"}) - - modifier := NewMultiModifier(phpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetPhpNamespace()) - continue - } - assert.Equal(t, "foo", descriptor.GetOptions().GetPhpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpNamespace(zap.NewNop(), sweeper, map[string]string{"a.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetPhpNamespace()) - continue - } - assert.Equal(t, "foo", descriptor.GetOptions().GetPhpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, false) - }) -} - -func TestPhpNamespaceOptions(t *testing.T) { - t.Parallel() - testPhpNamespaceOptions(t, filepath.Join("testdata", "phpoptions", "single"), `Acme\V1`) - testPhpNamespaceOptions(t, filepath.Join("testdata", "phpoptions", "double"), `Acme\Weather\V1`) - testPhpNamespaceOptions(t, filepath.Join("testdata", "phpoptions", "triple"), `Acme\Weather\Data\V1`) - testPhpNamespaceOptions(t, filepath.Join("testdata", "phpoptions", "reserved"), `Acme\Error_\V1`) - testPhpNamespaceOptions(t, filepath.Join("testdata", "phpoptions", "underscore"), `Acme\Weather\FooBar\V1`) -} - -func testPhpNamespaceOptions(t *testing.T, dirPath string, classPrefix string) { - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, phpNamespacePath) - - sweeper := NewFileOptionSweeper() - phpNamespaceModifier := PhpNamespace(zap.NewNop(), sweeper, nil) - - modifier := NewMultiModifier(phpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, classPrefix, descriptor.GetOptions().GetPhpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpNamespace(zap.NewNop(), sweeper, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, classPrefix, descriptor.GetOptions().GetPhpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, phpNamespacePath) - - sweeper := NewFileOptionSweeper() - phpNamespaceModifier := PhpNamespace(zap.NewNop(), sweeper, map[string]string{"override.proto": "override"}) - - modifier := NewMultiModifier(phpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetPhpNamespace()) - continue - } - assert.Equal(t, classPrefix, descriptor.GetOptions().GetPhpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpNamespace(zap.NewNop(), sweeper, map[string]string{"override.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetPhpNamespace()) - continue - } - assert.Equal(t, classPrefix, descriptor.GetOptions().GetPhpNamespace()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, phpNamespacePath, false) - }) -} - -func TestPhpNamespaceWellKnownTypes(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "wktimport") - modifiedPhpNamespace := `Acme\Weather\V1alpha1` - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - - sweeper := NewFileOptionSweeper() - phpNamespaceModifier := PhpNamespace(zap.NewNop(), sweeper, nil) - - modifier := NewMultiModifier(phpNamespaceModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - // php_namespace is unset for the well-known types - assert.Empty(t, descriptor.GetOptions().GetPhpNamespace()) - continue - } - assert.Equal(t, - modifiedPhpNamespace, - descriptor.GetOptions().GetPhpNamespace(), - ) - } - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - - sweeper := NewFileOptionSweeper() - modifier := PhpNamespace(zap.NewNop(), sweeper, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - // php_namespace is unset for the well-known types - assert.Empty(t, descriptor.GetOptions().GetPhpNamespace()) - continue - } - assert.Equal(t, - modifiedPhpNamespace, - descriptor.GetOptions().GetPhpNamespace(), - ) - } - }) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/ruby_package.go b/private/bufpkg/bufimage/bufimagemodify/ruby_package.go deleted file mode 100644 index 3f813873fc..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/ruby_package.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "strings" - - "github.com/bufbuild/buf/private/bufpkg/bufimage" - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/bufbuild/buf/private/pkg/stringutil" - "go.uber.org/zap" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/descriptorpb" -) - -// RubyPackageID is the ID of the ruby_package modifier. -const RubyPackageID = "RUBY_PACKAGE" - -// rubyPackagePath is the SourceCodeInfo path for the ruby_package option. -// https://github.com/protocolbuffers/protobuf/blob/61689226c0e3ec88287eaed66164614d9c4f2bf7/src/google/protobuf/descriptor.proto#L453 -var rubyPackagePath = []int32{8, 45} - -func rubyPackage( - logger *zap.Logger, - sweeper Sweeper, - except []bufmodule.ModuleFullName, - moduleOverrides map[bufmodule.ModuleFullName]string, - overrides map[string]string, -) Modifier { - // Convert the bufmodule.ModuleFullName types into - // strings so that they're comparable. - exceptModuleFullNameStrings := make(map[string]struct{}, len(except)) - for _, moduleFullName := range except { - exceptModuleFullNameStrings[moduleFullName.String()] = struct{}{} - } - overrideModuleFullNameStrings := make(map[string]string, len(moduleOverrides)) - for moduleFullName, rubyPackage := range moduleOverrides { - overrideModuleFullNameStrings[moduleFullName.String()] = rubyPackage - } - return ModifierFunc( - func(ctx context.Context, image bufimage.Image) error { - seenModuleFullNameStrings := make(map[string]struct{}, len(overrideModuleFullNameStrings)) - seenOverrideFiles := make(map[string]struct{}, len(overrides)) - for _, imageFile := range image.Files() { - rubyPackageValue := rubyPackageValue(imageFile) - if moduleFullName := imageFile.ModuleFullName(); moduleFullName != nil { - moduleFullNameString := moduleFullName.String() - if moduleNamespaceOverride, ok := overrideModuleFullNameStrings[moduleFullNameString]; ok { - seenModuleFullNameStrings[moduleFullNameString] = struct{}{} - rubyPackageValue = moduleNamespaceOverride - } - } - if overrideValue, ok := overrides[imageFile.Path()]; ok { - rubyPackageValue = overrideValue - seenOverrideFiles[imageFile.Path()] = struct{}{} - } - if err := rubyPackageForFile( - ctx, - sweeper, - imageFile, - rubyPackageValue, - exceptModuleFullNameStrings, - ); err != nil { - return err - } - } - for moduleFullNameString := range overrideModuleFullNameStrings { - if _, ok := seenModuleFullNameStrings[moduleFullNameString]; !ok { - logger.Sugar().Warnf("ruby_package override for %q was unused", moduleFullNameString) - } - } - for overrideFile := range overrides { - if _, ok := seenOverrideFiles[overrideFile]; !ok { - logger.Sugar().Warnf("%s override for %q was unused", RubyPackageID, overrideFile) - } - } - return nil - }, - ) -} - -func rubyPackageForFile( - ctx context.Context, - sweeper Sweeper, - imageFile bufimage.ImageFile, - rubyPackageValue string, - exceptModuleFullNameStrings map[string]struct{}, -) error { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(ctx, imageFile) || rubyPackageValue == "" { - // This is a well-known type or we could not resolve a non-empty ruby_package - // value, so this is a no-op. - return nil - } - if moduleFullName := imageFile.ModuleFullName(); moduleFullName != nil { - if _, ok := exceptModuleFullNameStrings[moduleFullName.String()]; ok { - return nil - } - } - if descriptor.Options == nil { - descriptor.Options = &descriptorpb.FileOptions{} - } - descriptor.Options.RubyPackage = proto.String(rubyPackageValue) - if sweeper != nil { - sweeper.mark(imageFile.Path(), rubyPackagePath) - } - return nil -} - -// rubyPackageValue returns the ruby_package for the given ImageFile based on its -// package declaration. If the image file doesn't have a package declaration, an -// empty string is returned. -func rubyPackageValue(imageFile bufimage.ImageFile) string { - pkg := imageFile.FileDescriptorProto().GetPackage() - if pkg == "" { - return "" - } - packageParts := strings.Split(pkg, ".") - for i, part := range packageParts { - packageParts[i] = stringutil.ToPascalCase(part) - } - return strings.Join(packageParts, "::") -} diff --git a/private/bufpkg/bufimage/bufimagemodify/ruby_package_test.go b/private/bufpkg/bufimage/bufimagemodify/ruby_package_test.go deleted file mode 100644 index 177fa8d911..0000000000 --- a/private/bufpkg/bufimage/bufimagemodify/ruby_package_test.go +++ /dev/null @@ -1,587 +0,0 @@ -// Copyright 2020-2023 Buf Technologies, Inc. -// -// 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 bufimagemodify - -import ( - "context" - "path/filepath" - "testing" - - "github.com/bufbuild/buf/private/bufpkg/bufmodule" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestRubyPackageEmptyOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "emptyoptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, true) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, nil) - - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, true) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, map[string]string{"a.proto": "override"}) - - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetRubyPackage()) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, map[string]string{"a.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - require.Equal(t, 1, len(image.Files())) - descriptor := image.Files()[0].FileDescriptorProto() - assert.Equal(t, "override", descriptor.GetOptions().GetRubyPackage()) - }) -} - -func TestRubyPackageAllOptions(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "alloptions") - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, rubyPackagePath) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, nil) - - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetRubyPackage()) - } - assertFileOptionSourceCodeInfoNotEmpty(t, image, rubyPackagePath) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, "foo", descriptor.GetOptions().GetRubyPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, rubyPackagePath) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, map[string]string{"a.proto": "override"}) - - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetRubyPackage()) - continue - } - assert.Equal(t, "foo", descriptor.GetOptions().GetRubyPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, true) - }) - - t.Run("without SourceCodeInfo and with per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, map[string]string{"a.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "a.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetRubyPackage()) - continue - } - assert.Equal(t, "foo", descriptor.GetOptions().GetRubyPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - }) -} - -func TestRubyPackageOptions(t *testing.T) { - t.Parallel() - testRubyPackageOptions(t, filepath.Join("testdata", "rubyoptions", "single"), `Acme::V1`) - testRubyPackageOptions(t, filepath.Join("testdata", "rubyoptions", "double"), `Acme::Weather::V1`) - testRubyPackageOptions(t, filepath.Join("testdata", "rubyoptions", "triple"), `Acme::Weather::Data::V1`) - testRubyPackageOptions(t, filepath.Join("testdata", "rubyoptions", "underscore"), `Acme::Weather::FooBar::V1`) -} - -func testRubyPackageOptions(t *testing.T, dirPath string, classPrefix string) { - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, rubyPackagePath) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, nil) - - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, classPrefix, descriptor.GetOptions().GetRubyPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, classPrefix, descriptor.GetOptions().GetRubyPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - }) - - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, rubyPackagePath) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, map[string]string{"override.proto": "override"}) - - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetRubyPackage()) - continue - } - assert.Equal(t, classPrefix, descriptor.GetOptions().GetRubyPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, true) - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - - sweeper := NewFileOptionSweeper() - modifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, map[string]string{"override.proto": "override"}) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetRubyPackage()) - continue - } - assert.Equal(t, classPrefix, descriptor.GetOptions().GetRubyPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - }) -} - -func TestRubyPackageWellKnownTypes(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "wktimport") - modifiedRubyPackage := `Acme::Weather::V1alpha1` - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, nil) - - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - // php_namespace is unset for the well-known types - assert.Empty(t, descriptor.GetOptions().GetRubyPackage()) - continue - } - assert.Equal(t, - modifiedRubyPackage, - descriptor.GetOptions().GetRubyPackage(), - ) - } - }) - - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - - sweeper := NewFileOptionSweeper() - modifier := RubyPackage(zap.NewNop(), sweeper, nil, nil, nil) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if isWellKnownType(context.Background(), imageFile) { - // php_namespace is unset for the well-known types - assert.Empty(t, descriptor.GetOptions().GetRubyPackage()) - continue - } - assert.Equal(t, - modifiedRubyPackage, - descriptor.GetOptions().GetRubyPackage(), - ) - } - }) -} - -func TestRubyPackageExcept(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "rubyoptions", "single") - testModuleFullName, err := bufmodule.NewModuleFullName( - testRemote, - testRepositoryOwner, - testRepositoryName, - ) - require.NoError(t, err) - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, rubyPackagePath) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage( - zap.NewNop(), - sweeper, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - nil, - ) - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, true), image) - // Still not empty, because the module is in except - assertFileOptionSourceCodeInfoNotEmpty(t, image, rubyPackagePath) - }) - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage( - zap.NewNop(), - sweeper, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - nil, - ) - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - }) - t.Run("with SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, rubyPackagePath) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage( - zap.NewNop(), - sweeper, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - map[string]string{"override.proto": "override"}, - ) - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - // not modified even though a file has override, because the module is in except - assert.Equal(t, testGetImage(t, dirPath, true), image) - // Still not empty, because the module is in except - assertFileOptionSourceCodeInfoNotEmpty(t, image, rubyPackagePath) - }) - t.Run("without SourceCodeInfo and per-file overrides", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage( - zap.NewNop(), - sweeper, - []bufmodule.ModuleFullName{testModuleFullName}, - nil, - map[string]string{"override.proto": "override"}, - ) - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.Equal(t, testGetImage(t, dirPath, false), image) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - }) -} - -func TestRubyPackageOverride(t *testing.T) { - t.Parallel() - dirPath := filepath.Join("testdata", "rubyoptions", "single") - overrideRubyPackage := "MODULE" - testModuleFullName, err := bufmodule.NewModuleFullName( - testRemote, - testRepositoryOwner, - testRepositoryName, - ) - require.NoError(t, err) - t.Run("with SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, rubyPackagePath) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage( - zap.NewNop(), - sweeper, - nil, - map[bufmodule.ModuleFullName]string{testModuleFullName: overrideRubyPackage}, - nil, - ) - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, overrideRubyPackage, descriptor.GetOptions().GetRubyPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, true) - }) - t.Run("without SourceCodeInfo", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage( - zap.NewNop(), - sweeper, - nil, - map[bufmodule.ModuleFullName]string{testModuleFullName: overrideRubyPackage}, - nil, - ) - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - assert.Equal(t, overrideRubyPackage, descriptor.GetOptions().GetRubyPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - }) - t.Run("with SourceCodeInfo with per-file override", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, true) - assertFileOptionSourceCodeInfoNotEmpty(t, image, rubyPackagePath) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage( - zap.NewNop(), - sweeper, - nil, - map[bufmodule.ModuleFullName]string{testModuleFullName: overrideRubyPackage}, - map[string]string{"override.proto": "override"}, - ) - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, true), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetRubyPackage()) - continue - } - assert.Equal(t, overrideRubyPackage, descriptor.GetOptions().GetRubyPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, true) - }) - t.Run("without SourceCodeInfo and per-file override", func(t *testing.T) { - t.Parallel() - image := testGetImage(t, dirPath, false) - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - - sweeper := NewFileOptionSweeper() - rubyPackageModifier := RubyPackage( - zap.NewNop(), - sweeper, - nil, - map[bufmodule.ModuleFullName]string{testModuleFullName: overrideRubyPackage}, - map[string]string{"override.proto": "override"}, - ) - modifier := NewMultiModifier(rubyPackageModifier, ModifierFunc(sweeper.Sweep)) - err := modifier.Modify( - context.Background(), - image, - ) - require.NoError(t, err) - assert.NotEqual(t, testGetImage(t, dirPath, false), image) - - for _, imageFile := range image.Files() { - descriptor := imageFile.FileDescriptorProto() - if imageFile.Path() == "override.proto" { - assert.Equal(t, "override", descriptor.GetOptions().GetRubyPackage()) - continue - } - assert.Equal(t, overrideRubyPackage, descriptor.GetOptions().GetRubyPackage()) - } - assertFileOptionSourceCodeInfoEmpty(t, image, rubyPackagePath, false) - }) -} diff --git a/private/bufpkg/bufimage/bufimagemodify/testdata/bar/bar_all/with_package.proto b/private/bufpkg/bufimage/bufimagemodify/testdata/bar/bar_all/with_package.proto new file mode 100644 index 0000000000..40912a91a5 --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/testdata/bar/bar_all/with_package.proto @@ -0,0 +1,20 @@ +package bar.all; + +option cc_enable_arenas = false; +option cc_generic_services = false; +option csharp_namespace = "bar"; +option go_package = "bar"; +option java_generic_services = false; +option java_multiple_files = false; +option java_outer_classname = "bar"; +option java_package = "bar"; +option java_string_check_utf8 = false; +option objc_class_prefix = "bar"; +option optimize_for = SPEED; +option php_class_prefix = "bar"; +option php_generic_services = false; +option php_metadata_namespace = "bar"; +option php_namespace = "bar"; +option py_generic_services = false; +option ruby_package = "bar"; +option swift_prefix = "bar"; diff --git a/private/bufpkg/bufimage/bufimagemodify/testdata/bar/bar_all/without_package.proto b/private/bufpkg/bufimage/bufimagemodify/testdata/bar/bar_all/without_package.proto new file mode 100644 index 0000000000..76f2451093 --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/testdata/bar/bar_all/without_package.proto @@ -0,0 +1,18 @@ +option cc_enable_arenas = false; +option cc_generic_services = false; +option csharp_namespace = "bar"; +option go_package = "bar"; +option java_generic_services = false; +option java_multiple_files = false; +option java_outer_classname = "bar"; +option java_package = "bar"; +option java_string_check_utf8 = false; +option objc_class_prefix = "bar"; +option optimize_for = SPEED; +option php_class_prefix = "bar"; +option php_generic_services = false; +option php_metadata_namespace = "bar"; +option php_namespace = "bar"; +option py_generic_services = false; +option ruby_package = "bar"; +option swift_prefix = "bar"; diff --git a/private/bufpkg/bufimage/bufimagemodify/testdata/bar/bar_empty/with_package.proto b/private/bufpkg/bufimage/bufimagemodify/testdata/bar/bar_empty/with_package.proto new file mode 100644 index 0000000000..cf4a9e547d --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/testdata/bar/bar_empty/with_package.proto @@ -0,0 +1 @@ +package bar.empty; diff --git a/private/bufpkg/bufimage/bufimagemodify/testdata/bar/bar_empty/without_package.proto b/private/bufpkg/bufimage/bufimagemodify/testdata/bar/bar_empty/without_package.proto new file mode 100644 index 0000000000..e69de29bb2 diff --git a/private/bufpkg/bufimage/bufimagemodify/testdata/foo/foo_all/with_package.proto b/private/bufpkg/bufimage/bufimagemodify/testdata/foo/foo_all/with_package.proto new file mode 100644 index 0000000000..dec813d807 --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/testdata/foo/foo_all/with_package.proto @@ -0,0 +1,20 @@ +package foo.all; + +option cc_enable_arenas = true; +option cc_generic_services = true; +option csharp_namespace = "foo"; +option go_package = "foo"; +option java_generic_services = true; +option java_multiple_files = true; +option java_outer_classname = "foo"; +option java_package = "foo"; +option java_string_check_utf8 = true; +option objc_class_prefix = "foo"; +option optimize_for = CODE_SIZE; +option php_class_prefix = "foo"; +option php_generic_services = true; +option php_metadata_namespace = "foo"; +option php_namespace = "foo"; +option py_generic_services = true; +option ruby_package = "foo"; +option swift_prefix = "foo"; diff --git a/private/bufpkg/bufimage/bufimagemodify/testdata/foo/foo_all/without_package.proto b/private/bufpkg/bufimage/bufimagemodify/testdata/foo/foo_all/without_package.proto new file mode 100644 index 0000000000..aab870f526 --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/testdata/foo/foo_all/without_package.proto @@ -0,0 +1,18 @@ +option cc_enable_arenas = true; +option cc_generic_services = true; +option csharp_namespace = "foo"; +option go_package = "foo"; +option java_generic_services = true; +option java_multiple_files = true; +option java_outer_classname = "foo"; +option java_package = "foo"; +option java_string_check_utf8 = true; +option objc_class_prefix = "foo"; +option optimize_for = CODE_SIZE; +option php_class_prefix = "foo"; +option php_generic_services = true; +option php_metadata_namespace = "foo"; +option php_namespace = "foo"; +option py_generic_services = true; +option ruby_package = "foo"; +option swift_prefix = "foo"; diff --git a/private/bufpkg/bufimage/bufimagemodify/testdata/foo/foo_empty/with_package.proto b/private/bufpkg/bufimage/bufimagemodify/testdata/foo/foo_empty/with_package.proto new file mode 100644 index 0000000000..af978aa59f --- /dev/null +++ b/private/bufpkg/bufimage/bufimagemodify/testdata/foo/foo_empty/with_package.proto @@ -0,0 +1 @@ +package foo.empty; diff --git a/private/bufpkg/bufimage/bufimagemodify/testdata/foo/foo_empty/without_package.proto b/private/bufpkg/bufimage/bufimagemodify/testdata/foo/foo_empty/without_package.proto new file mode 100644 index 0000000000..e69de29bb2 diff --git a/private/pkg/slicesext/slicesext.go b/private/pkg/slicesext/slicesext.go index 13642ad1dc..78db644f69 100644 --- a/private/pkg/slicesext/slicesext.go +++ b/private/pkg/slicesext/slicesext.go @@ -237,6 +237,22 @@ func MapKeysToSlice[K comparable, V any](m map[K]V) []K { return s } +// MapValuesToSlice converts the map's values to a sorted slice. +// +// Duplicate values will be added. This should generally be used +// in cases where you know there is a 1-1 mapping from K to V. +func MapValuesToSortedSlice[K comparable, V Ordered](m map[K]V) []V { + s := MapValuesToSlice(m) + // TODO: Replace with slices.Sort when we only support Go versions >= 1.21. + sort.Slice( + s, + func(i int, j int) bool { + return s[i] < s[j] + }, + ) + return s +} + // MapValuesToSlice converts the map's values to a slice. // // Duplicate values will be added. This should generally be used