| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You 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 bufpluginconfig |
| |
| import ( |
| "errors" |
| "fmt" |
| "strings" |
| ) |
| |
| import ( |
| "golang.org/x/mod/modfile" |
| "golang.org/x/mod/semver" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-kubernetes/pkg/bufman/bufpkg/bufplugin/bufpluginref" |
| "github.com/apache/dubbo-kubernetes/pkg/bufman/gen/data/dataspdx" |
| ) |
| |
| func newConfig(externalConfig ExternalConfig, options []ConfigOption) (*Config, error) { |
| opts := &configOptions{} |
| for _, option := range options { |
| option(opts) |
| } |
| pluginIdentity, err := pluginIdentityForStringWithOverrideRemote(externalConfig.Name, opts.overrideRemote) |
| if err != nil { |
| return nil, err |
| } |
| pluginVersion := externalConfig.PluginVersion |
| if pluginVersion == "" { |
| return nil, errors.New("a plugin_version is required") |
| } |
| if !semver.IsValid(pluginVersion) { |
| return nil, fmt.Errorf("plugin_version %q must be a valid semantic version", externalConfig.PluginVersion) |
| } |
| var dependencies []bufpluginref.PluginReference |
| if len(externalConfig.Deps) > 0 { |
| existingDeps := make(map[string]struct{}) |
| for _, dependency := range externalConfig.Deps { |
| reference, err := pluginReferenceForStringWithOverrideRemote(dependency.Plugin, dependency.Revision, opts.overrideRemote) |
| if err != nil { |
| return nil, err |
| } |
| if reference.Remote() != pluginIdentity.Remote() { |
| return nil, fmt.Errorf("plugin dependency %q must use same remote as plugin %q", dependency, pluginIdentity.Remote()) |
| } |
| if _, ok := existingDeps[reference.IdentityString()]; ok { |
| return nil, fmt.Errorf("plugin dependency %q was specified more than once", dependency) |
| } |
| existingDeps[reference.IdentityString()] = struct{}{} |
| dependencies = append(dependencies, reference) |
| } |
| } |
| registryConfig, err := newRegistryConfig(externalConfig.Registry) |
| if err != nil { |
| return nil, err |
| } |
| spdxLicenseID := externalConfig.SPDXLicenseID |
| if spdxLicenseID != "" { |
| if licenseInfo, ok := dataspdx.GetLicenseInfo(spdxLicenseID); ok { |
| spdxLicenseID = licenseInfo.ID() |
| } else { |
| return nil, fmt.Errorf("unknown SPDX License ID %q", spdxLicenseID) |
| } |
| } |
| return &Config{ |
| Name: pluginIdentity, |
| PluginVersion: pluginVersion, |
| Dependencies: dependencies, |
| Registry: registryConfig, |
| SourceURL: externalConfig.SourceURL, |
| Description: externalConfig.Description, |
| OutputLanguages: externalConfig.OutputLanguages, |
| SPDXLicenseID: spdxLicenseID, |
| LicenseURL: externalConfig.LicenseURL, |
| }, nil |
| } |
| |
| func newRegistryConfig(externalRegistryConfig ExternalRegistryConfig) (*RegistryConfig, error) { |
| var ( |
| isGoEmpty = externalRegistryConfig.Go == nil |
| isNPMEmpty = externalRegistryConfig.NPM == nil |
| isMavenEmpty = externalRegistryConfig.Maven == nil |
| isSwiftEmpty = externalRegistryConfig.Swift == nil |
| ) |
| var registryCount int |
| for _, isEmpty := range []bool{ |
| isGoEmpty, |
| isNPMEmpty, |
| isMavenEmpty, |
| isSwiftEmpty, |
| } { |
| if !isEmpty { |
| registryCount++ |
| } |
| if registryCount > 1 { |
| // We might eventually want to support multiple runtime configuration, |
| // but it's safe to start with an error for now. |
| return nil, fmt.Errorf("%s configuration contains multiple registry configurations", ExternalConfigFilePath) |
| } |
| } |
| if registryCount == 0 { |
| // It's possible that the plugin doesn't have any runtime dependencies. |
| return nil, nil |
| } |
| options := OptionsSliceToPluginOptions(externalRegistryConfig.Opts) |
| switch { |
| case !isGoEmpty: |
| goRegistryConfig, err := newGoRegistryConfig(externalRegistryConfig.Go) |
| if err != nil { |
| return nil, err |
| } |
| return &RegistryConfig{ |
| Go: goRegistryConfig, |
| Options: options, |
| }, nil |
| case !isNPMEmpty: |
| npmRegistryConfig, err := newNPMRegistryConfig(externalRegistryConfig.NPM) |
| if err != nil { |
| return nil, err |
| } |
| return &RegistryConfig{ |
| NPM: npmRegistryConfig, |
| Options: options, |
| }, nil |
| case !isMavenEmpty: |
| mavenRegistryConfig, err := newMavenRegistryConfig(externalRegistryConfig.Maven) |
| if err != nil { |
| return nil, err |
| } |
| return &RegistryConfig{ |
| Maven: mavenRegistryConfig, |
| Options: options, |
| }, nil |
| case !isSwiftEmpty: |
| swiftRegistryConfig, err := newSwiftRegistryConfig(externalRegistryConfig.Swift) |
| if err != nil { |
| return nil, err |
| } |
| return &RegistryConfig{ |
| Swift: swiftRegistryConfig, |
| Options: options, |
| }, nil |
| default: |
| return nil, errors.New("unknown registry configuration") |
| } |
| } |
| |
| func newNPMRegistryConfig(externalNPMRegistryConfig *ExternalNPMRegistryConfig) (*NPMRegistryConfig, error) { |
| if externalNPMRegistryConfig == nil { |
| return nil, nil |
| } |
| var dependencies []*NPMRegistryDependencyConfig |
| for _, dep := range externalNPMRegistryConfig.Deps { |
| if dep.Package == "" { |
| return nil, errors.New("npm runtime dependency requires a non-empty package name") |
| } |
| if dep.Version == "" { |
| return nil, errors.New("npm runtime dependency requires a non-empty version name") |
| } |
| // TODO: Note that we don't have NPM-specific validation yet - any |
| // non-empty string will work for the package and version. |
| // |
| // For a complete set of the version syntax we need to support, see |
| // https://docs.npmjs.com/cli/v6/using-npm/semver |
| // |
| // https://github.com/Masterminds/semver might be a good candidate for |
| // this, but it might not support all of the constraints supported |
| // by NPM. |
| dependencies = append( |
| dependencies, |
| &NPMRegistryDependencyConfig{ |
| Package: dep.Package, |
| Version: dep.Version, |
| }, |
| ) |
| } |
| switch externalNPMRegistryConfig.ImportStyle { |
| case "module", "commonjs": |
| default: |
| return nil, errors.New(`npm registry config import_style must be one of: "module" or "commonjs"`) |
| } |
| return &NPMRegistryConfig{ |
| RewriteImportPathSuffix: externalNPMRegistryConfig.RewriteImportPathSuffix, |
| Deps: dependencies, |
| ImportStyle: externalNPMRegistryConfig.ImportStyle, |
| }, nil |
| } |
| |
| func newGoRegistryConfig(externalGoRegistryConfig *ExternalGoRegistryConfig) (*GoRegistryConfig, error) { |
| if externalGoRegistryConfig == nil { |
| return nil, nil |
| } |
| if externalGoRegistryConfig.MinVersion != "" && !modfile.GoVersionRE.MatchString(externalGoRegistryConfig.MinVersion) { |
| return nil, fmt.Errorf("the go minimum version %q must be a valid semantic version in the form of <major>.<minor>", externalGoRegistryConfig.MinVersion) |
| } |
| var dependencies []*GoRegistryDependencyConfig |
| for _, dep := range externalGoRegistryConfig.Deps { |
| if dep.Module == "" { |
| return nil, errors.New("go runtime dependency requires a non-empty module name") |
| } |
| if dep.Version == "" { |
| return nil, errors.New("go runtime dependency requires a non-empty version name") |
| } |
| if !semver.IsValid(dep.Version) { |
| return nil, fmt.Errorf("go runtime dependency %s:%s does not have a valid semantic version", dep.Module, dep.Version) |
| } |
| dependencies = append( |
| dependencies, |
| &GoRegistryDependencyConfig{ |
| Module: dep.Module, |
| Version: dep.Version, |
| }, |
| ) |
| } |
| return &GoRegistryConfig{ |
| MinVersion: externalGoRegistryConfig.MinVersion, |
| Deps: dependencies, |
| }, nil |
| } |
| |
| func newMavenRegistryConfig(externalMavenRegistryConfig *ExternalMavenRegistryConfig) (*MavenRegistryConfig, error) { |
| if externalMavenRegistryConfig == nil { |
| return nil, nil |
| } |
| var dependencies []MavenDependencyConfig |
| for _, externalDep := range externalMavenRegistryConfig.Deps { |
| dep, err := mavenExternalDependencyToDependencyConfig(externalDep) |
| if err != nil { |
| return nil, err |
| } |
| dependencies = append(dependencies, dep) |
| } |
| var additionalRuntimes []MavenRuntimeConfig |
| for _, runtime := range externalMavenRegistryConfig.AdditionalRuntimes { |
| var deps []MavenDependencyConfig |
| for _, externalDep := range runtime.Deps { |
| dep, err := mavenExternalDependencyToDependencyConfig(externalDep) |
| if err != nil { |
| return nil, err |
| } |
| deps = append(deps, dep) |
| } |
| config := MavenRuntimeConfig{ |
| Name: runtime.Name, |
| Deps: deps, |
| Options: runtime.Opts, |
| } |
| additionalRuntimes = append(additionalRuntimes, config) |
| } |
| return &MavenRegistryConfig{ |
| Compiler: MavenCompilerConfig{ |
| Java: MavenCompilerJavaConfig{ |
| Encoding: externalMavenRegistryConfig.Compiler.Java.Encoding, |
| Release: externalMavenRegistryConfig.Compiler.Java.Release, |
| Source: externalMavenRegistryConfig.Compiler.Java.Source, |
| Target: externalMavenRegistryConfig.Compiler.Java.Target, |
| }, |
| Kotlin: MavenCompilerKotlinConfig{ |
| APIVersion: externalMavenRegistryConfig.Compiler.Kotlin.APIVersion, |
| JVMTarget: externalMavenRegistryConfig.Compiler.Kotlin.JVMTarget, |
| LanguageVersion: externalMavenRegistryConfig.Compiler.Kotlin.LanguageVersion, |
| Version: externalMavenRegistryConfig.Compiler.Kotlin.Version, |
| }, |
| }, |
| Deps: dependencies, |
| AdditionalRuntimes: additionalRuntimes, |
| }, nil |
| } |
| |
| func newSwiftRegistryConfig(externalSwiftRegistryConfig *ExternalSwiftRegistryConfig) (*SwiftRegistryConfig, error) { |
| if externalSwiftRegistryConfig == nil { |
| return nil, nil |
| } |
| var dependencies []SwiftRegistryDependencyConfig |
| for _, externalDependency := range externalSwiftRegistryConfig.Deps { |
| dependency, err := swiftExternalDependencyToDependencyConfig(externalDependency) |
| if err != nil { |
| return nil, err |
| } |
| dependencies = append(dependencies, dependency) |
| } |
| return &SwiftRegistryConfig{ |
| Dependencies: dependencies, |
| }, nil |
| } |
| |
| func swiftExternalDependencyToDependencyConfig(externalDep ExternalSwiftRegistryDependencyConfig) (SwiftRegistryDependencyConfig, error) { |
| if externalDep.Source == "" { |
| return SwiftRegistryDependencyConfig{}, errors.New("swift runtime dependency requires a non-empty source") |
| } |
| if externalDep.Package == "" { |
| return SwiftRegistryDependencyConfig{}, errors.New("swift runtime dependency requires a non-empty package name") |
| } |
| if externalDep.Version == "" { |
| return SwiftRegistryDependencyConfig{}, errors.New("swift runtime dependency requires a non-empty version name") |
| } |
| // Swift SemVers are typically not prefixed with a "v". The Golang semver library requires a "v" prefix. |
| if !semver.IsValid(fmt.Sprintf("v%s", externalDep.Version)) { |
| return SwiftRegistryDependencyConfig{}, fmt.Errorf("swift runtime dependency %s:%s does not have a valid semantic version", externalDep.Package, externalDep.Version) |
| } |
| return SwiftRegistryDependencyConfig{ |
| Source: externalDep.Source, |
| Package: externalDep.Package, |
| Version: externalDep.Version, |
| Products: externalDep.Products, |
| SwiftVersions: externalDep.SwiftVersions, |
| Platforms: SwiftRegistryDependencyPlatformConfig{ |
| MacOS: externalDep.Platforms.MacOS, |
| IOS: externalDep.Platforms.IOS, |
| TVOS: externalDep.Platforms.TVOS, |
| WatchOS: externalDep.Platforms.WatchOS, |
| }, |
| }, nil |
| } |
| |
| func pluginIdentityForStringWithOverrideRemote(identityStr string, overrideRemote string) (bufpluginref.PluginIdentity, error) { |
| identity, err := bufpluginref.PluginIdentityForString(identityStr) |
| if err != nil { |
| return nil, err |
| } |
| if len(overrideRemote) == 0 { |
| return identity, nil |
| } |
| return bufpluginref.NewPluginIdentity(overrideRemote, identity.Owner(), identity.Plugin()) |
| } |
| |
| func pluginReferenceForStringWithOverrideRemote( |
| referenceStr string, |
| revision int, |
| overrideRemote string, |
| ) (bufpluginref.PluginReference, error) { |
| reference, err := bufpluginref.PluginReferenceForString(referenceStr, revision) |
| if err != nil { |
| return nil, err |
| } |
| if len(overrideRemote) == 0 { |
| return reference, nil |
| } |
| overrideIdentity, err := pluginIdentityForStringWithOverrideRemote(reference.IdentityString(), overrideRemote) |
| if err != nil { |
| return nil, err |
| } |
| return bufpluginref.NewPluginReference(overrideIdentity, reference.Version(), reference.Revision()) |
| } |
| |
| func mavenExternalDependencyToDependencyConfig(dependency string) (MavenDependencyConfig, error) { |
| // <groupId>:<artifactId>:<version>[:<classifier>][@<type>] |
| dependencyWithoutExtension, extension, _ := strings.Cut(dependency, "@") |
| components := strings.Split(dependencyWithoutExtension, ":") |
| if len(components) < 3 { |
| return MavenDependencyConfig{}, fmt.Errorf("invalid dependency %q: missing required groupId:artifactId:version fields", dependency) |
| } |
| if len(components) > 4 { |
| return MavenDependencyConfig{}, fmt.Errorf("invalid dependency %q: maximum 4 fields before optional type", dependency) |
| } |
| config := MavenDependencyConfig{ |
| GroupID: components[0], |
| ArtifactID: components[1], |
| Version: components[2], |
| Extension: extension, |
| } |
| if len(components) == 4 { |
| config.Classifier = components[3] |
| } |
| return config, nil |
| } |