From d8f68e4bbaaca09eb09a20f39aaca4a8a242dc4a Mon Sep 17 00:00:00 2001 From: "Philip K. Warren" Date: Thu, 11 Jul 2024 11:30:59 -0500 Subject: [PATCH] Add bufremoteplugin support for Nuget registries (#3150) --- .../bufpkg/bufremoteplugin/bufremoteplugin.go | 116 ++++++++++++++---- .../bufremoteplugin/bufremoteplugin_test.go | 19 +++ .../bufremotepluginconfig.go | 41 +++++++ .../bufremotepluginconfig_test.go | 41 +++++++ .../bufremotepluginconfig/config.go | 83 +++++++++++++ .../testdata/success/nuget/buf.plugin.yaml | 25 ++++ .../bufremoteplugin/nuget_target_framework.go | 57 +++++++++ .../nuget_target_platform_test.go | 63 ++++++++++ 8 files changed, 422 insertions(+), 23 deletions(-) create mode 100644 private/bufpkg/bufremoteplugin/bufremotepluginconfig/testdata/success/nuget/buf.plugin.yaml create mode 100644 private/bufpkg/bufremoteplugin/nuget_target_framework.go create mode 100644 private/bufpkg/bufremoteplugin/nuget_target_platform_test.go diff --git a/private/bufpkg/bufremoteplugin/bufremoteplugin.go b/private/bufpkg/bufremoteplugin/bufremoteplugin.go index 5663e2c83c..5f7efb56a5 100644 --- a/private/bufpkg/bufremoteplugin/bufremoteplugin.go +++ b/private/bufpkg/bufremoteplugin/bufremoteplugin.go @@ -22,6 +22,7 @@ import ( "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin/bufremotepluginconfig" "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin/bufremotepluginref" registryv1alpha1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1" + "github.com/bufbuild/buf/private/pkg/slicesext" ) // Plugin represents a plugin defined by a buf.plugin.yaml. @@ -67,19 +68,22 @@ func NewPlugin( // PluginToProtoPluginRegistryType determines the appropriate registryv1alpha1.PluginRegistryType for the plugin. func PluginToProtoPluginRegistryType(plugin Plugin) registryv1alpha1.PluginRegistryType { registryType := registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_UNSPECIFIED - if plugin.Registry() != nil { - if plugin.Registry().Go != nil { + if registry := plugin.Registry(); registry != nil { + switch { + case registry.Go != nil: registryType = registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_GO - } else if plugin.Registry().NPM != nil { + case registry.NPM != nil: registryType = registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_NPM - } else if plugin.Registry().Maven != nil { + case registry.Maven != nil: registryType = registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_MAVEN - } else if plugin.Registry().Swift != nil { + case registry.Swift != nil: registryType = registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_SWIFT - } else if plugin.Registry().Python != nil { + case registry.Python != nil: registryType = registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_PYTHON - } else if plugin.Registry().Cargo != nil { + case registry.Cargo != nil: registryType = registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_CARGO + case registry.Nuget != nil: + registryType = registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_NUGET } } return registryType @@ -123,7 +127,8 @@ func PluginRegistryToProtoRegistryConfig(pluginRegistry *bufremotepluginconfig.R registryConfig := ®istryv1alpha1.RegistryConfig{ Options: bufremotepluginconfig.PluginOptionsToOptionsSlice(pluginRegistry.Options), } - if pluginRegistry.Go != nil { + switch { + case pluginRegistry.Go != nil: goConfig := ®istryv1alpha1.GoConfig{} goConfig.MinimumVersion = pluginRegistry.Go.MinVersion if pluginRegistry.Go.Deps != nil { @@ -133,7 +138,7 @@ func PluginRegistryToProtoRegistryConfig(pluginRegistry *bufremotepluginconfig.R } } registryConfig.RegistryConfig = ®istryv1alpha1.RegistryConfig_GoConfig{GoConfig: goConfig} - } else if pluginRegistry.NPM != nil { + case pluginRegistry.NPM != nil: importStyle, err := npmImportStyleToNPMProtoImportStyle(pluginRegistry.NPM.ImportStyle) if err != nil { return nil, err @@ -149,7 +154,7 @@ func PluginRegistryToProtoRegistryConfig(pluginRegistry *bufremotepluginconfig.R } } registryConfig.RegistryConfig = ®istryv1alpha1.RegistryConfig_NpmConfig{NpmConfig: npmConfig} - } else if pluginRegistry.Maven != nil { + case pluginRegistry.Maven != nil: mavenConfig := ®istryv1alpha1.MavenConfig{} var javaCompilerConfig *registryv1alpha1.MavenConfig_CompilerJavaConfig if compiler := pluginRegistry.Maven.Compiler.Java; compiler != (bufremotepluginconfig.MavenCompilerJavaConfig{}) { @@ -188,21 +193,27 @@ func PluginRegistryToProtoRegistryConfig(pluginRegistry *bufremotepluginconfig.R } } registryConfig.RegistryConfig = ®istryv1alpha1.RegistryConfig_MavenConfig{MavenConfig: mavenConfig} - } else if pluginRegistry.Swift != nil { + case pluginRegistry.Swift != nil: swiftConfig := SwiftRegistryConfigToProtoSwiftConfig(pluginRegistry.Swift) registryConfig.RegistryConfig = ®istryv1alpha1.RegistryConfig_SwiftConfig{SwiftConfig: swiftConfig} - } else if pluginRegistry.Python != nil { + case pluginRegistry.Python != nil: pythonConfig, err := PythonRegistryConfigToProtoPythonConfig(pluginRegistry.Python) if err != nil { return nil, err } registryConfig.RegistryConfig = ®istryv1alpha1.RegistryConfig_PythonConfig{PythonConfig: pythonConfig} - } else if pluginRegistry.Cargo != nil { + case pluginRegistry.Cargo != nil: cargoConfig, err := CargoRegistryConfigToProtoCargoConfig(pluginRegistry.Cargo) if err != nil { return nil, err } registryConfig.RegistryConfig = ®istryv1alpha1.RegistryConfig_CargoConfig{CargoConfig: cargoConfig} + case pluginRegistry.Nuget != nil: + nugetConfig, err := NugetRegistryConfigToProtoNugetConfig(pluginRegistry.Nuget) + if err != nil { + return nil, err + } + registryConfig.RegistryConfig = ®istryv1alpha1.RegistryConfig_NugetConfig{NugetConfig: nugetConfig} } return registryConfig, nil } @@ -226,7 +237,8 @@ func ProtoRegistryConfigToPluginRegistry(config *registryv1alpha1.RegistryConfig registryConfig := &bufremotepluginconfig.RegistryConfig{ Options: bufremotepluginconfig.OptionsSliceToPluginOptions(config.Options), } - if config.GetGoConfig() != nil { + switch { + case config.GetGoConfig() != nil: goConfig := &bufremotepluginconfig.GoRegistryConfig{} goConfig.MinVersion = config.GetGoConfig().GetMinimumVersion() runtimeLibraries := config.GetGoConfig().GetRuntimeLibraries() @@ -237,7 +249,7 @@ func ProtoRegistryConfigToPluginRegistry(config *registryv1alpha1.RegistryConfig } } registryConfig.Go = goConfig - } else if config.GetNpmConfig() != nil { + case config.GetNpmConfig() != nil: importStyle, err := npmProtoImportStyleToNPMImportStyle(config.GetNpmConfig().GetImportStyle()) if err != nil { return nil, err @@ -254,30 +266,36 @@ func ProtoRegistryConfigToPluginRegistry(config *registryv1alpha1.RegistryConfig } } registryConfig.NPM = npmConfig - } else if protoMavenConfig := config.GetMavenConfig(); protoMavenConfig != nil { - mavenConfig, err := ProtoMavenConfigToMavenRegistryConfig(protoMavenConfig) + case config.GetMavenConfig() != nil: + mavenConfig, err := ProtoMavenConfigToMavenRegistryConfig(config.GetMavenConfig()) if err != nil { return nil, err } registryConfig.Maven = mavenConfig - } else if protoSwiftConfig := config.GetSwiftConfig(); protoSwiftConfig != nil { - swiftConfig, err := ProtoSwiftConfigToSwiftRegistryConfig(protoSwiftConfig) + case config.GetSwiftConfig() != nil: + swiftConfig, err := ProtoSwiftConfigToSwiftRegistryConfig(config.GetSwiftConfig()) if err != nil { return nil, err } registryConfig.Swift = swiftConfig - } else if protoPythonConfig := config.GetPythonConfig(); protoPythonConfig != nil { - pythonConfig, err := ProtoPythonConfigToPythonRegistryConfig(protoPythonConfig) + case config.GetPythonConfig() != nil: + pythonConfig, err := ProtoPythonConfigToPythonRegistryConfig(config.GetPythonConfig()) if err != nil { return nil, err } registryConfig.Python = pythonConfig - } else if protoCargoConfig := config.GetCargoConfig(); protoCargoConfig != nil { - cargoConfig, err := ProtoCargoConfigToCargoRegistryConfig(protoCargoConfig) + case config.GetCargoConfig() != nil: + cargoConfig, err := ProtoCargoConfigToCargoRegistryConfig(config.GetCargoConfig()) if err != nil { return nil, err } registryConfig.Cargo = cargoConfig + case config.GetNugetConfig() != nil: + nugetConfig, err := ProtoNugetConfigToNugetRegistryConfig(config.GetNugetConfig()) + if err != nil { + return nil, err + } + registryConfig.Nuget = nugetConfig } return registryConfig, nil } @@ -298,6 +316,32 @@ func ProtoCargoConfigToCargoRegistryConfig(protoCargoConfig *registryv1alpha1.Ca return cargoConfig, nil } +// ProtoNugetConfigToNugetRegistryConfig converts protoConfig to an equivalent [*bufremotepluginconfig.NugetRegistryConfig]. +func ProtoNugetConfigToNugetRegistryConfig(protoConfig *registryv1alpha1.NugetConfig) (*bufremotepluginconfig.NugetRegistryConfig, error) { + targetFrameworks, err := slicesext.MapError(protoConfig.TargetFrameworks, dotnetTargetFrameworkToString) + if err != nil { + return nil, err + } + config := &bufremotepluginconfig.NugetRegistryConfig{ + TargetFrameworks: targetFrameworks, + } + for _, dependency := range protoConfig.RuntimeLibraries { + var depTargetFrameworks []string + if len(dependency.TargetFrameworks) > 0 { + depTargetFrameworks, err = slicesext.MapError(dependency.TargetFrameworks, dotnetTargetFrameworkToString) + if err != nil { + return nil, err + } + } + config.Deps = append(config.Deps, bufremotepluginconfig.NugetDependencyConfig{ + Name: dependency.Name, + Version: dependency.Version, + TargetFrameworks: depTargetFrameworks, + }) + } + return config, err +} + // CargoRegistryConfigToProtoCargoConfig converts cargoConfig to an equivalent [*registryv1alpha1.CargoConfig]. func CargoRegistryConfigToProtoCargoConfig(cargoConfig *bufremotepluginconfig.CargoRegistryConfig) (*registryv1alpha1.CargoConfig, error) { protoCargoConfig := ®istryv1alpha1.CargoConfig{ @@ -314,6 +358,32 @@ func CargoRegistryConfigToProtoCargoConfig(cargoConfig *bufremotepluginconfig.Ca return protoCargoConfig, nil } +// NugetRegistryConfigToProtoNugetConfig converts nugetConfig to an equivalent [*registryv1alpha1.NugetConfig]. +func NugetRegistryConfigToProtoNugetConfig(nugetConfig *bufremotepluginconfig.NugetRegistryConfig) (*registryv1alpha1.NugetConfig, error) { + targetFrameworks, err := slicesext.MapError(nugetConfig.TargetFrameworks, dotnetTargetFrameworkFromString) + if err != nil { + return nil, err + } + protoNugetConfig := ®istryv1alpha1.NugetConfig{ + TargetFrameworks: targetFrameworks, + } + for _, dependency := range nugetConfig.Deps { + var depTargetFrameworks []registryv1alpha1.DotnetTargetFramework + if len(dependency.TargetFrameworks) > 0 { + depTargetFrameworks, err = slicesext.MapError(dependency.TargetFrameworks, dotnetTargetFrameworkFromString) + if err != nil { + return nil, err + } + } + protoNugetConfig.RuntimeLibraries = append(protoNugetConfig.RuntimeLibraries, ®istryv1alpha1.NugetConfig_RuntimeLibrary{ + Name: dependency.Name, + Version: dependency.Version, + TargetFrameworks: depTargetFrameworks, + }) + } + return protoNugetConfig, nil +} + // ProtoPythonConfigToPythonRegistryConfig converts protoPythonConfig to an equivalent [*bufremotepluginconfig.PythonRegistryConfig]. func ProtoPythonConfigToPythonRegistryConfig(protoPythonConfig *registryv1alpha1.PythonConfig) (*bufremotepluginconfig.PythonRegistryConfig, error) { pythonConfig := &bufremotepluginconfig.PythonRegistryConfig{ diff --git a/private/bufpkg/bufremoteplugin/bufremoteplugin_test.go b/private/bufpkg/bufremoteplugin/bufremoteplugin_test.go index 26511a7288..a1e23b0bf2 100644 --- a/private/bufpkg/bufremoteplugin/bufremoteplugin_test.go +++ b/private/bufpkg/bufremoteplugin/bufremoteplugin_test.go @@ -30,6 +30,9 @@ func TestPluginToProtoPluginRegistryType(t *testing.T) { assertPluginToPluginRegistryType(t, &bufremotepluginconfig.RegistryConfig{NPM: &bufremotepluginconfig.NPMRegistryConfig{}}, registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_NPM) assertPluginToPluginRegistryType(t, &bufremotepluginconfig.RegistryConfig{Maven: &bufremotepluginconfig.MavenRegistryConfig{}}, registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_MAVEN) assertPluginToPluginRegistryType(t, &bufremotepluginconfig.RegistryConfig{Swift: &bufremotepluginconfig.SwiftRegistryConfig{}}, registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_SWIFT) + assertPluginToPluginRegistryType(t, &bufremotepluginconfig.RegistryConfig{Python: &bufremotepluginconfig.PythonRegistryConfig{}}, registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_PYTHON) + assertPluginToPluginRegistryType(t, &bufremotepluginconfig.RegistryConfig{Cargo: &bufremotepluginconfig.CargoRegistryConfig{}}, registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_CARGO) + assertPluginToPluginRegistryType(t, &bufremotepluginconfig.RegistryConfig{Nuget: &bufremotepluginconfig.NugetRegistryConfig{}}, registryv1alpha1.PluginRegistryType_PLUGIN_REGISTRY_TYPE_NUGET) } func assertPluginToPluginRegistryType(t testing.TB, config *bufremotepluginconfig.RegistryConfig, registryType registryv1alpha1.PluginRegistryType) { @@ -187,6 +190,22 @@ func TestPluginRegistryRoundTrip(t *testing.T) { }, }, }) + assertPluginRegistryRoundTrip(t, &bufremotepluginconfig.RegistryConfig{ + Nuget: &bufremotepluginconfig.NugetRegistryConfig{ + TargetFrameworks: []string{"netstandard2.0", "netstandard2.1"}, + Deps: []bufremotepluginconfig.NugetDependencyConfig{ + { + Name: "Grpc.Core.Api", + Version: "1.2.3", + }, + { + Name: "Grpc.Other.Api", + Version: "4.5.6", + TargetFrameworks: []string{"netstandard2.1"}, + }, + }, + }, + }) } func assertPluginRegistryRoundTrip(t testing.TB, config *bufremotepluginconfig.RegistryConfig) { diff --git a/private/bufpkg/bufremoteplugin/bufremotepluginconfig/bufremotepluginconfig.go b/private/bufpkg/bufremoteplugin/bufremotepluginconfig/bufremotepluginconfig.go index beea640359..d60cd44f2b 100644 --- a/private/bufpkg/bufremoteplugin/bufremotepluginconfig/bufremotepluginconfig.go +++ b/private/bufpkg/bufremoteplugin/bufremotepluginconfig/bufremotepluginconfig.go @@ -93,6 +93,7 @@ type RegistryConfig struct { Swift *SwiftRegistryConfig Python *PythonRegistryConfig Cargo *CargoRegistryConfig + Nuget *NugetRegistryConfig // Options is the set of options passed into the plugin for the // remote registry. // @@ -267,6 +268,27 @@ type CargoRegistryConfig struct { Deps []CargoRegistryDependency } +// NugetDependencyConfig defines a runtime dependency for a generated nuget package. +type NugetDependencyConfig struct { + // Name specifies the name of the dependency. + Name string + // Version specifies the version of the dependency. + // This can be an exact version or a version range. + Version string + // TargetFrameworks specify the optional target frameworks for the dependency. + // If specified, the dependency will be added only for the specified framework. + TargetFrameworks []string +} + +// NugetRegistryConfig defines the configuration for a nuget registry. +type NugetRegistryConfig struct { + // TargetFrameworks specify the frameworks to build. + // At least one target framework must be specified. + TargetFrameworks []string + // Deps specifies the dependencies for the generated SDK. + Deps []NugetDependencyConfig +} + // ConfigOption is an optional option used when loading a Config. type ConfigOption func(*configOptions) @@ -398,6 +420,7 @@ type ExternalRegistryConfig struct { Swift *ExternalSwiftRegistryConfig `json:"swift,omitempty" yaml:"swift,omitempty"` Python *ExternalPythonRegistryConfig `json:"python,omitempty" yaml:"python,omitempty"` Cargo *ExternalCargoRegistryConfig `json:"cargo,omitempty" yaml:"cargo,omitempty"` + Nuget *ExternalNugetRegistryConfig `json:"nuget,omitempty" yaml:"nuget,omitempty"` Opts []string `json:"opts,omitempty" yaml:"opts,omitempty"` } @@ -539,6 +562,24 @@ type ExternalCargoRegistryConfig struct { Deps []ExternalCargoDependency `json:"deps,omitempty" yaml:"deps,omitempty"` } +// ExternalNugetDependency defines a nuget package dependency. +type ExternalNugetDependency struct { + // Name specifies the name of the dependency. + Name string `json:"name,omitempty" yaml:"name,omitempty"` + // Version specifies the version of the dependency. + Version string `json:"version,omitempty" yaml:"version,omitempty"` + // TargetFrameworks specify the optional target frameworks for the dependency. + TargetFrameworks []string `json:"target_frameworks" yaml:"target_frameworks,omitempty"` +} + +// ExternalNugetRegistryConfig defines the configuration for a nuget registry. +type ExternalNugetRegistryConfig struct { + // TargetFrameworks specify the frameworks to build. + TargetFrameworks []string `json:"target_frameworks" yaml:"target_frameworks,omitempty"` + // Deps specifies the dependencies for the generated SDK. + Deps []ExternalNugetDependency `json:"deps,omitempty" yaml:"deps,omitempty"` +} + type externalConfigVersion struct { Version string `json:"version,omitempty" yaml:"version,omitempty"` } diff --git a/private/bufpkg/bufremoteplugin/bufremotepluginconfig/bufremotepluginconfig_test.go b/private/bufpkg/bufremoteplugin/bufremotepluginconfig/bufremotepluginconfig_test.go index a0d3d6d93c..7e37e3771d 100644 --- a/private/bufpkg/bufremoteplugin/bufremotepluginconfig/bufremotepluginconfig_test.go +++ b/private/bufpkg/bufremoteplugin/bufremotepluginconfig/bufremotepluginconfig_test.go @@ -363,6 +363,47 @@ func TestParsePluginConfigCargoYAML(t *testing.T) { ) } +func TestParsePluginConfigNugetYAML(t *testing.T) { + t.Parallel() + pluginConfig, err := ParseConfig(filepath.Join("testdata", "success", "nuget", "buf.plugin.yaml")) + require.NoError(t, err) + pluginIdentity, err := bufremotepluginref.PluginIdentityForString("buf.build/grpc/csharp") + require.NoError(t, err) + depPluginRef, err := bufremotepluginref.PluginReferenceForString("buf.build/protocolbuffers/csharp:v26.1", 0) + require.NoError(t, err) + require.Equal( + t, + &Config{ + Name: pluginIdentity, + PluginVersion: "v1.65.0", + Dependencies: []bufremotepluginref.PluginReference{depPluginRef}, + SourceURL: "https://github.com/grpc/grpc", + Description: "Generates C# client and server stubs for the gRPC framework.", + SPDXLicenseID: "Apache-2.0", + LicenseURL: "https://github.com/grpc/grpc/blob/v1.65.0/LICENSE", + OutputLanguages: []string{"csharp"}, + Registry: &RegistryConfig{ + Nuget: &NugetRegistryConfig{ + TargetFrameworks: []string{"netstandard2.0", "netstandard2.1"}, + Deps: []NugetDependencyConfig{ + { + Name: "Grpc.Core.Api", + Version: "2.63.0", + }, + { + Name: "Grpc.Other.Api", + Version: "1.0.31", + TargetFrameworks: []string{"netstandard2.1"}, + }, + }, + }, + Options: map[string]string{"base_namespace": ""}, + }, + }, + pluginConfig, + ) +} + func TestParsePluginConfigOptionsYAML(t *testing.T) { t.Parallel() pluginConfig, err := ParseConfig(filepath.Join("testdata", "success", "options", "buf.plugin.yaml")) diff --git a/private/bufpkg/bufremoteplugin/bufremotepluginconfig/config.go b/private/bufpkg/bufremoteplugin/bufremotepluginconfig/config.go index c42b316b1c..5103ef84e6 100644 --- a/private/bufpkg/bufremoteplugin/bufremotepluginconfig/config.go +++ b/private/bufpkg/bufremoteplugin/bufremotepluginconfig/config.go @@ -21,6 +21,7 @@ import ( "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin/bufremotepluginref" "github.com/bufbuild/buf/private/gen/data/dataspdx" + "github.com/bufbuild/buf/private/pkg/slicesext" "golang.org/x/mod/modfile" "golang.org/x/mod/semver" ) @@ -93,6 +94,7 @@ func newRegistryConfig(externalRegistryConfig ExternalRegistryConfig) (*Registry isSwiftEmpty = externalRegistryConfig.Swift == nil isPythonEmpty = externalRegistryConfig.Python == nil isCargoEmpty = externalRegistryConfig.Cargo == nil + isNugetEmpty = externalRegistryConfig.Nuget == nil ) var registryCount int for _, isEmpty := range []bool{ @@ -102,6 +104,7 @@ func newRegistryConfig(externalRegistryConfig ExternalRegistryConfig) (*Registry isSwiftEmpty, isPythonEmpty, isCargoEmpty, + isNugetEmpty, } { if !isEmpty { registryCount++ @@ -172,6 +175,15 @@ func newRegistryConfig(externalRegistryConfig ExternalRegistryConfig) (*Registry Cargo: cargoRegistryConfig, Options: options, }, nil + case !isNugetEmpty: + nugetRegistryConfig, err := newNugetRegistryConfig(externalRegistryConfig.Nuget) + if err != nil { + return nil, err + } + return &RegistryConfig{ + Nuget: nugetRegistryConfig, + Options: options, + }, nil default: return nil, errors.New("unknown registry configuration") } @@ -387,6 +399,77 @@ func newCargoRegistryConfig(externalCargoRegistryConfig *ExternalCargoRegistryCo return config, nil } +func newNugetRegistryConfig(externalConfig *ExternalNugetRegistryConfig) (*NugetRegistryConfig, error) { + if len(externalConfig.TargetFrameworks) == 0 { + return nil, errors.New("nuget registry target frameworks required") + } + targetFrameworks, err := validateNugetTargetFrameworks(externalConfig.TargetFrameworks) + if err != nil { + return nil, fmt.Errorf("nuget registry: %w", err) + } + var deps []NugetDependencyConfig + if len(externalConfig.Deps) > 0 { + deps = make([]NugetDependencyConfig, 0, len(externalConfig.Deps)) + for i, externalDep := range externalConfig.Deps { + if externalDep.Name == "" { + return nil, fmt.Errorf("nuget registry dependency %d: empty name", i) + } + if externalDep.Version == "" { + return nil, fmt.Errorf("nuget registry dependency %d: empty version", i) + } + depTargetFrameworks, err := validateNugetTargetFrameworks(externalDep.TargetFrameworks) + if err != nil { + return nil, fmt.Errorf("nuget registry dependency %d: %w", i, err) + } + deps = append(deps, NugetDependencyConfig{ + Name: externalDep.Name, + Version: externalDep.Version, + TargetFrameworks: depTargetFrameworks, + }) + } + } + return &NugetRegistryConfig{ + TargetFrameworks: targetFrameworks, + Deps: deps, + }, nil +} + +func validateNugetTargetFrameworks(targetFrameworks []string) ([]string, error) { + if len(targetFrameworks) == 0 { + return nil, nil + } + if dups := slicesext.Duplicates(targetFrameworks); len(dups) > 0 { + return nil, fmt.Errorf("duplicate target frameworks: %v", dups) + } + for i, targetFramework := range targetFrameworks { + if err := validateNugetTargetFramework(targetFramework); err != nil { + return nil, fmt.Errorf("target framework %d: %w", i, err) + } + } + return slicesext.Copy(targetFrameworks), nil +} + +func validateNugetTargetFramework(targetFramework string) error { + switch targetFramework { + case "netstandard1.0": + case "netstandard1.1": + case "netstandard1.2": + case "netstandard1.3": + case "netstandard1.4": + case "netstandard1.5": + case "netstandard1.6": + case "netstandard2.0": + case "netstandard2.1": + case "net5.0": + case "net6.0": + case "net7.0": + case "net8.0": + default: + return fmt.Errorf("invalid target framework: %q", targetFramework) + } + return nil +} + func pluginIdentityForStringWithOverrideRemote(identityStr string, overrideRemote string) (bufremotepluginref.PluginIdentity, error) { identity, err := bufremotepluginref.PluginIdentityForString(identityStr) if err != nil { diff --git a/private/bufpkg/bufremoteplugin/bufremotepluginconfig/testdata/success/nuget/buf.plugin.yaml b/private/bufpkg/bufremoteplugin/bufremotepluginconfig/testdata/success/nuget/buf.plugin.yaml new file mode 100644 index 0000000000..84e4f32749 --- /dev/null +++ b/private/bufpkg/bufremoteplugin/bufremotepluginconfig/testdata/success/nuget/buf.plugin.yaml @@ -0,0 +1,25 @@ +version: v1 +name: buf.build/grpc/csharp +plugin_version: v1.65.0 +source_url: https://github.com/grpc/grpc +description: Generates C# client and server stubs for the gRPC framework. +deps: + - plugin: buf.build/protocolbuffers/csharp:v26.1 +output_languages: + - csharp +spdx_license_id: Apache-2.0 +license_url: https://github.com/grpc/grpc/blob/v1.65.0/LICENSE +registry: + nuget: + target_frameworks: + - netstandard2.0 + - netstandard2.1 + deps: + - name: Grpc.Core.Api + version: 2.63.0 + - name: Grpc.Other.Api + version: 1.0.31 + target_frameworks: + - netstandard2.1 + opts: + - base_namespace= diff --git a/private/bufpkg/bufremoteplugin/nuget_target_framework.go b/private/bufpkg/bufremoteplugin/nuget_target_framework.go new file mode 100644 index 0000000000..2a8f6930d9 --- /dev/null +++ b/private/bufpkg/bufremoteplugin/nuget_target_framework.go @@ -0,0 +1,57 @@ +// Copyright 2020-2024 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 bufremoteplugin + +import ( + "fmt" + + registryv1alpha1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1" +) + +var ( + stringToDotnetTargetFramework = map[string]registryv1alpha1.DotnetTargetFramework{ + "netstandard1.0": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NETSTANDARD_1_0, + "netstandard1.1": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NETSTANDARD_1_1, + "netstandard1.2": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NETSTANDARD_1_2, + "netstandard1.3": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NETSTANDARD_1_3, + "netstandard1.4": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NETSTANDARD_1_4, + "netstandard1.5": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NETSTANDARD_1_5, + "netstandard1.6": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NETSTANDARD_1_6, + "netstandard2.0": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NETSTANDARD_2_0, + "netstandard2.1": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NETSTANDARD_2_1, + "net5.0": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NET_5_0, + "net6.0": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NET_6_0, + "net7.0": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NET_7_0, + "net8.0": registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_NET_8_0, + } +) + +func dotnetTargetFrameworkFromString(framework string) (registryv1alpha1.DotnetTargetFramework, error) { + frameworkEnum, ok := stringToDotnetTargetFramework[framework] + if !ok { + return 0, fmt.Errorf("unknown target framework %q", framework) + } + return frameworkEnum, nil +} + +func dotnetTargetFrameworkToString(framework registryv1alpha1.DotnetTargetFramework) (string, error) { + // This isn't performance critical code - just scan the existing mapping instead of storing in both directions. + for frameworkStr, frameworkEnum := range stringToDotnetTargetFramework { + if frameworkEnum == framework { + return frameworkStr, nil + } + } + return "", fmt.Errorf("unknown target framework %v", framework) +} diff --git a/private/bufpkg/bufremoteplugin/nuget_target_platform_test.go b/private/bufpkg/bufremoteplugin/nuget_target_platform_test.go new file mode 100644 index 0000000000..55e5a1154c --- /dev/null +++ b/private/bufpkg/bufremoteplugin/nuget_target_platform_test.go @@ -0,0 +1,63 @@ +// Copyright 2020-2024 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 bufremoteplugin + +import ( + "context" + "fmt" + "testing" + + "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin/bufremotepluginconfig" + registryv1alpha1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDotnetTargetPlatformMapping(t *testing.T) { + t.Parallel() + assert.Len(t, stringToDotnetTargetFramework, len(registryv1alpha1.DotnetTargetFramework_name)-1) + for value := range registryv1alpha1.DotnetTargetFramework_name { + targetFramework := registryv1alpha1.DotnetTargetFramework(value) + if targetFramework == registryv1alpha1.DotnetTargetFramework_DOTNET_TARGET_FRAMEWORK_UNSPECIFIED { + continue + } + // Verify round trip + strTargetFramework, err := dotnetTargetFrameworkToString(targetFramework) + require.NoErrorf(t, err, "missing mapping for target framework %v", targetFramework) + targetFrameworkFromStr, err := dotnetTargetFrameworkFromString(strTargetFramework) + require.NoError(t, err) + assert.Equal(t, targetFramework, targetFrameworkFromStr) + } +} + +func TestDotnetTargetPlatformExternalConfigMapping(t *testing.T) { + // We validate the string values for target frameworks in bufremoteconfig. + // This test will fail if we add a new target framework to the proto and didn't update the validation. + t.Parallel() + ctx := context.Background() + for targetFrameworkStr := range stringToDotnetTargetFramework { + externalCfg := fmt.Sprintf( + `version: v1 +name: buf.build/grpc/csharp +plugin_version: v1.0.0 +registry: + nuget: + target_frameworks: + - %s +`, targetFrameworkStr) + _, err := bufremotepluginconfig.GetConfigForData(ctx, []byte(externalCfg)) + require.NoErrorf(t, err, "bufremotepluginconfig.validateNugetTargetFramework needs updating for %s", targetFrameworkStr) + } +}