Skip to content

Commit

Permalink
[codegen/dotnet] Allow generated SDKs to have a global namespace othe…
Browse files Browse the repository at this point in the history
…r then `Pulumi` (#8735)

* Allow .NET packages alternate owners

Specifically, this allows a generated package with a enclosing namespace
different then "Pulumi."

* New test case files

* Remove unused import

* Update CHANGELOG_PENDING.md

* Update codegen from master merge

* Use rootNamespace instead of namespace

* publisher overrides Author and "Company" in .NET

* Add publisher propagation to CHANGELOG_PENDING.md

* Fix nits

* Use fmt.Sprintf over +

* Restore sdk_driver.go (mostly)

* Add docs to allLanguages add documentation.

* Update tests from master

* Add rootNamespace to docs
  • Loading branch information
iwahbe authored Jan 21, 2022
1 parent ccd00c3 commit 90913de
Show file tree
Hide file tree
Showing 58 changed files with 4,129 additions and 78 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
### Improvements

- [codegen/dotnet] - Add C# extension `rootNamespace`, allowing the user to
replace `Pulumi` as the default C# global namespace in generated programs.
The `Company` and `Author` fields of the .csproj file are now driven by
`schema.publisher`.
[#8735](https://github.com/pulumi/pulumi/pull/8735)

- [cli] Download provider plugins from GitHub Releases
[#8785](https://github.com/pulumi/pulumi/pull/8785)

Expand All @@ -16,6 +22,6 @@

### Bug Fixes

- [codegen/nodejs] - Generate an install script that runs `pulumi plugin install` with
- [codegen/nodejs] - Generate an install script that runs `pulumi plugin install` with
the `--server` flag when necessary.
[#8730](https://github.com/pulumi/pulumi/pull/8730)
22 changes: 14 additions & 8 deletions pkg/codegen/dotnet/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,17 @@ func (d DocLanguageHelper) GetDocLinkForFunctionInputOrOutputType(pkg *schema.Pa

// GetLanguageTypeString returns the DotNet-specific type given a Pulumi schema type.
func (d DocLanguageHelper) GetLanguageTypeString(pkg *schema.Package, moduleName string, t schema.Type, input bool) string {
info, ok := pkg.Language["csharp"].(CSharpPackageInfo)
if !ok {
info = CSharpPackageInfo{}
}
typeDetails := map[*schema.ObjectType]*typeDetails{}
mod := &modContext{
pkg: pkg,
mod: moduleName,
typeDetails: typeDetails,
namespaces: d.Namespaces,
pkg: pkg,
mod: moduleName,
typeDetails: typeDetails,
namespaces: d.Namespaces,
rootNamespace: info.GetRootNamespace(),
}
qualifier := "Inputs" // nolint: goconst
if !input {
Expand Down Expand Up @@ -109,10 +114,11 @@ func (d DocLanguageHelper) GetMethodResultName(pkg *schema.Package, modName stri
if info.LiftSingleValueMethodReturns && m.Function.Outputs != nil && len(m.Function.Outputs.Properties) == 1 {
typeDetails := map[*schema.ObjectType]*typeDetails{}
mod := &modContext{
pkg: pkg,
mod: modName,
typeDetails: typeDetails,
namespaces: d.Namespaces,
pkg: pkg,
mod: modName,
typeDetails: typeDetails,
namespaces: d.Namespaces,
rootNamespace: info.GetRootNamespace(),
}
return mod.typeString(m.Function.Outputs.Properties[0].Type, "", false, false, false)
}
Expand Down
54 changes: 35 additions & 19 deletions pkg/codegen/dotnet/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ type modContext struct {

// Determine whether to lift single-value method return values
liftSingleValueMethodReturns bool

// The root namespace to use, if any.
rootNamespace string
}

func (mod *modContext) RootNamespace() string {
if mod.rootNamespace != "" {
return mod.rootNamespace
}
return "Pulumi"
}

func (mod *modContext) propertyName(p *schema.Property) string {
Expand Down Expand Up @@ -202,7 +212,7 @@ func (mod *modContext) tokenToNamespace(tok string, qualifier string) string {
components := strings.Split(tok, ":")
contract.Assertf(len(components) == 3, "malformed token %v", tok)

pkg, nsName := "Pulumi."+namespaceName(mod.namespaces, components[0]), mod.pkg.TokenToModule(tok)
pkg, nsName := mod.RootNamespace()+"."+namespaceName(mod.namespaces, components[0]), mod.pkg.TokenToModule(tok)

if mod.isK8sCompatMode() {
if qualifier != "" {
Expand Down Expand Up @@ -381,6 +391,7 @@ func (mod *modContext) typeString(t schema.Type, qualifier string, input, state,
namingCtx = &modContext{
pkg: extPkg,
namespaces: info.Namespaces,
rootNamespace: info.GetRootNamespace(),
compatibility: info.Compatibility,
}
}
Expand All @@ -398,7 +409,7 @@ func (mod *modContext) typeString(t schema.Type, qualifier string, input, state,
case *schema.ResourceType:
if strings.HasPrefix(t.Token, "pulumi:providers:") {
pkgName := strings.TrimPrefix(t.Token, "pulumi:providers:")
return fmt.Sprintf("Pulumi.%s.Provider", namespaceName(mod.namespaces, pkgName))
return fmt.Sprintf("%s.%s.Provider", mod.RootNamespace(), namespaceName(mod.namespaces, pkgName))
}

namingCtx := mod
Expand All @@ -414,6 +425,7 @@ func (mod *modContext) typeString(t schema.Type, qualifier string, input, state,
namingCtx = &modContext{
pkg: extPkg,
namespaces: info.Namespaces,
rootNamespace: info.GetRootNamespace(),
compatibility: info.Compatibility,
}
}
Expand Down Expand Up @@ -1288,7 +1300,7 @@ func (mod *modContext) genFunctionFileCode(f *schema.Function) (string, error) {
imports := map[string]codegen.StringSet{}
mod.getImports(f, imports)
buffer := &bytes.Buffer{}
importStrings := pulumiImports
importStrings := mod.pulumiImports()

// True if the function has a non-standard namespace.
nonStandardNamespace := mod.namespaceName != mod.tokenToNamespace(f.Token, "")
Expand All @@ -1299,9 +1311,6 @@ func (mod *modContext) genFunctionFileCode(f *schema.Function) (string, error) {
for _, i := range imports {
importStrings = append(importStrings, i.SortedValues()...)
}
if f.NeedsOutputVersion() {
importStrings = append(importStrings, "Pulumi.Utilities")
}

// We need to qualify input types when we are not in the same module as them.
if nonStandardNamespace {
Expand Down Expand Up @@ -1649,12 +1658,18 @@ func (mod *modContext) genType(w io.Writer, obj *schema.ObjectType, propertyType
}

// pulumiImports is a slice of common imports that are used with the genHeader method.
var pulumiImports = []string{
"System",
"System.Collections.Generic",
"System.Collections.Immutable",
"System.Threading.Tasks",
"Pulumi.Serialization",
func (mod *modContext) pulumiImports() []string {
var pulumiImports = []string{
"System",
"System.Collections.Generic",
"System.Collections.Immutable",
"System.Threading.Tasks",
"Pulumi.Serialization",
}
if mod.RootNamespace() != "Pulumi" {
pulumiImports = append(pulumiImports, "Pulumi")
}
return pulumiImports
}

func (mod *modContext) getTypeImports(t schema.Type, recurse bool, imports map[string]codegen.StringSet, seen codegen.Set) {
Expand Down Expand Up @@ -2032,7 +2047,7 @@ func (mod *modContext) gen(fs fs) error {
additionalImports = append(additionalImports, i.SortedValues()...)
}
sort.Strings(additionalImports)
importStrings := pulumiImports
importStrings := mod.pulumiImports()
importStrings = append(importStrings, additionalImports...)
mod.genHeader(buffer, importStrings)

Expand Down Expand Up @@ -2066,7 +2081,7 @@ func (mod *modContext) gen(fs fs) error {

if mod.details(t).inputType {
buffer := &bytes.Buffer{}
mod.genHeader(buffer, pulumiImports)
mod.genHeader(buffer, mod.pulumiImports())

fmt.Fprintf(buffer, "namespace %s\n", mod.tokenToNamespace(t.Token, "Inputs"))
fmt.Fprintf(buffer, "{\n")
Expand All @@ -2085,7 +2100,7 @@ func (mod *modContext) gen(fs fs) error {
}
if mod.details(t).stateType {
buffer := &bytes.Buffer{}
mod.genHeader(buffer, pulumiImports)
mod.genHeader(buffer, mod.pulumiImports())

fmt.Fprintf(buffer, "namespace %s\n", mod.tokenToNamespace(t.Token, "Inputs"))
fmt.Fprintf(buffer, "{\n")
Expand All @@ -2097,7 +2112,7 @@ func (mod *modContext) gen(fs fs) error {
}
if mod.details(t).outputType {
buffer := &bytes.Buffer{}
mod.genHeader(buffer, pulumiImports)
mod.genHeader(buffer, mod.pulumiImports())

fmt.Fprintf(buffer, "namespace %s\n", mod.tokenToNamespace(t.Token, "Outputs"))
fmt.Fprintf(buffer, "{\n")
Expand Down Expand Up @@ -2272,7 +2287,7 @@ func generateModuleContextMap(tool string, pkg *schema.Package) (map[string]*mod
mod, ok := modules[modName]
if !ok {
info := getPackageInfo(p)
ns := "Pulumi." + namespaceName(info.Namespaces, pkg.Name)
ns := info.GetRootNamespace() + "." + namespaceName(info.Namespaces, pkg.Name)
if modName != "" {
ns += "." + namespaceName(info.Namespaces, modName)
}
Expand All @@ -2282,6 +2297,7 @@ func generateModuleContextMap(tool string, pkg *schema.Package) (map[string]*mod
tool: tool,
namespaceName: ns,
namespaces: info.Namespaces,
rootNamespace: info.GetRootNamespace(),
typeDetails: details,
propertyNames: propertyNames,
compatibility: info.Compatibility,
Expand Down Expand Up @@ -2314,7 +2330,7 @@ func generateModuleContextMap(tool string, pkg *schema.Package) (map[string]*mod
// Create the config module if necessary.
if len(pkg.Config) > 0 {
cfg := getMod("config", pkg)
cfg.namespaceName = "Pulumi." + namespaceName(infos[pkg].Namespaces, pkg.Name)
cfg.namespaceName = fmt.Sprintf("%s.%s", cfg.RootNamespace(), namespaceName(infos[pkg].Namespaces, pkg.Name))
}

visitObjectTypes(pkg.Config, func(t *schema.ObjectType) {
Expand Down Expand Up @@ -2434,7 +2450,7 @@ func GeneratePackage(tool string, pkg *schema.Package, extraFiles map[string][]b
return nil, err
}

assemblyName := "Pulumi." + namespaceName(info.Namespaces, pkg.Name)
assemblyName := info.GetRootNamespace() + "." + namespaceName(info.Namespaces, pkg.Name)

// Generate each module.
files := fs{}
Expand Down
11 changes: 11 additions & 0 deletions pkg/codegen/dotnet/importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ type CSharpPackageInfo struct {
ProjectReferences []string `json:"projectReferences,omitempty"`
// Determines whether to make single-return-value methods return an output object or the single value.
LiftSingleValueMethodReturns bool `json:"liftSingleValueMethodReturns,omitempty"`

// The root namespace used for the package. This defaults to `Pulumi`.
RootNamespace string `json:"rootNamespace,omitempty"`
}

// Returns the root namespace, or "Pulumi" if not provided.
func (info *CSharpPackageInfo) GetRootNamespace() string {
if r := info.RootNamespace; r != "" {
return r
}
return "Pulumi"
}

// Importer implements schema.Language for .NET.
Expand Down
4 changes: 2 additions & 2 deletions pkg/codegen/dotnet/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ const csharpProjectFileTemplateText = `<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Pulumi Corp.</Authors>
<Company>Pulumi Corp.</Company>
<Authors>{{or .Package.Publisher "Pulumi Corp."}}</Authors>
<Company>{{or .Package.Publisher "Pulumi Corp."}}</Company>
<Description>{{.Package.Description}}</Description>
<PackageLicenseExpression>{{.Package.License}}</PackageLicenseExpression>
<PackageProjectUrl>{{.Package.Homepage}}</PackageProjectUrl>
Expand Down
75 changes: 46 additions & 29 deletions pkg/codegen/internal/test/sdk_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,45 @@ type sdkTest struct {
SkipCompileCheck codegen.StringSet
}

// ShouldSkipTest indicates if a given test for a given language should be run.
func (tt sdkTest) ShouldSkipTest(language, test string) bool {

// Only language-specific checks.
if !strings.HasPrefix(test, language+"/") {
return true
}

// Obey SkipCompileCheck to skip compile and test targets.
if tt.SkipCompileCheck != nil &&
tt.SkipCompileCheck.Has(language) &&
(test == fmt.Sprintf("%s/compile", language) ||
test == fmt.Sprintf("%s/test", language)) {
return true
}

// Obey Skip.
if tt.Skip != nil && tt.Skip.Has(test) {
return true
}

return false
}

// ShouldSkipCodegen determines if codegen should be run. ShouldSkipCodegen=true
// further implies no other tests will be run.
func (tt sdkTest) ShouldSkipCodegen(language string) bool {
return tt.Skip.Has(language + "/any")
}

const (
python = "python"
nodejs = "nodejs"
dotnet = "dotnet"
golang = "go"
)

var allLanguages = codegen.NewStringSet("python/any", "nodejs/any", "dotnet/any", "go/any", "docs/any")

var sdkTests = []sdkTest{
{
Directory: "naming-collisions",
Expand Down Expand Up @@ -184,7 +216,7 @@ var sdkTests = []sdkTest{
{
Directory: "different-package-name-conflict",
Description: "different packages with the same resource",
Skip: codegen.NewStringSet("dotnet/any", "nodejs/any", "python/any", "go/any", "docs/any"),
Skip: allLanguages,
},
{
Directory: "different-enum",
Expand All @@ -198,7 +230,17 @@ var sdkTests = []sdkTest{
{
Directory: "regress-go-8664",
Description: "Regress pulumi/pulumi#8664 affecting Go",
Skip: codegen.NewStringSet("dotnet/any", "python/any", "nodejs/any", "docs/any"),
Skip: allLanguages.Except("go/any"),
},
{
Directory: "other-owned",
Description: "CSharp rootNamespaces",
// We only test in dotnet, because we are testing a change in a dotnet
// language property. Other tests should pass, but do not put the
// relevant feature under test. To save time, we skip them.
//
// We need to see dotnet changes (paths) in the docs too.
Skip: allLanguages.Except("dotnet/any").Except("docs/any"),
},
}

Expand Down Expand Up @@ -312,8 +354,7 @@ func TestSDKCodegen(t *testing.T, opts *SDKCodegenOptions) { // revive:disable-l
schemaPath = filepath.Join(dirPath, "schema.yaml")
}

// Any takes place before codegen.
if tt.Skip.Has(opts.Language + "/any") {
if tt.ShouldSkipCodegen(opts.Language) {
t.Logf("Skipping generation + tests for %s", tt.Directory)
return
}
Expand Down Expand Up @@ -347,30 +388,6 @@ func TestSDKCodegen(t *testing.T, opts *SDKCodegenOptions) { // revive:disable-l
allChecks[k] = v
}

// Define check filter.
shouldSkipCheck := func(check string) bool {

// Only language-specific checks.
if !strings.HasPrefix(check, opts.Language+"/") {
return true
}

// Obey SkipCompileCheck to skip compile and test targets.
if tt.SkipCompileCheck != nil &&
tt.SkipCompileCheck.Has(opts.Language) &&
(check == fmt.Sprintf("%s/compile", opts.Language) ||
check == fmt.Sprintf("%s/test", opts.Language)) {
return true
}

// Obey Skip.
if tt.Skip != nil && tt.Skip.Has(check) {
return true
}

return false
}

// Sort the checks in alphabetical order.
var checkOrder []string
for check := range allChecks {
Expand All @@ -384,7 +401,7 @@ func TestSDKCodegen(t *testing.T, opts *SDKCodegenOptions) { // revive:disable-l
for _, checkVar := range checkOrder {
check := checkVar
t.Run(check, func(t *testing.T) {
if shouldSkipCheck(check) {
if tt.ShouldSkipTest(opts.Language, check) {
t.Skip()
}
checkFun := allChecks[check]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Pulumi Corp.</Authors>
<Company>Pulumi Corp.</Company>
<Authors>Pulumi</Authors>
<Company>Pulumi</Company>
<Description>A native Pulumi package for creating and managing Azure resources.</Description>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://pulumi.com</PackageProjectUrl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Collections.Immutable;
using System.Threading.Tasks;
using Pulumi.Serialization;
using Pulumi.Utilities;

namespace Pulumi.Example
{
Expand Down
Loading

0 comments on commit 90913de

Please sign in to comment.