Skip to content

Commit ae068b1

Browse files
committed
Adds .net csharp sample
1 parent 9ab2a44 commit ae068b1

30 files changed

+2124
-56
lines changed

cli/azd/extensions/microsoft.azd.extensions/build.ps1

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ if ($env:EXTENSION_PLATFORM) {
3535

3636
$APP_PATH = "github.com/azure/azure-dev/cli/azd/extensions/$env:EXTENSION_ID/internal/cmd"
3737

38+
# Check if the build type is specified
39+
if (-not $env:EXTENSION_LANGUAGE) {
40+
Write-Host "Error: BUILD_TYPE environment variable is required (go or dotnet)"
41+
exit 1
42+
}
43+
3844
# Loop through platforms and build
3945
foreach ($PLATFORM in $PLATFORMS) {
4046
$OS, $ARCH = $PLATFORM -split '/'
@@ -46,14 +52,54 @@ foreach ($PLATFORM in $PLATFORMS) {
4652
}
4753

4854
Write-Host "Building for $OS/$ARCH..."
49-
$env:GOOS = $OS
50-
$env:GOARCH = $ARCH
51-
go build `
52-
-ldflags="-X '$APP_PATH.Version=$env:EXTENSION_VERSION' -X '$APP_PATH.Commit=$COMMIT' -X '$APP_PATH.BuildDate=$BUILD_DATE'" `
53-
-o $OUTPUT_NAME
54-
55-
if ($LASTEXITCODE -ne 0) {
56-
Write-Host "An error occurred while building for $OS/$ARCH"
55+
56+
# Delete the output file if it already exists
57+
if (Test-Path -Path $OUTPUT_NAME) {
58+
Remove-Item -Path $OUTPUT_NAME -Force
59+
}
60+
61+
if ($env:EXTENSION_LANGUAGE -eq "dotnet") {
62+
# Set runtime identifier for .NET
63+
$RUNTIME = if ($OS -eq "windows") { "win-$ARCH" } elseif ($OS -eq "darwin") { "osx-$ARCH" } else { "linux-$ARCH" }
64+
$PROJECT_FILE = "$EXTENSION_ID_SAFE.csproj"
65+
66+
# Run dotnet publish for single file executable
67+
dotnet publish `
68+
-c Release `
69+
-r $RUNTIME `
70+
-o $OUTPUT_DIR `
71+
/p:PublishTrimmed=true `
72+
$PROJECT_FILE
73+
74+
if ($LASTEXITCODE -ne 0) {
75+
Write-Host "An error occurred while building for $OS/$ARCH"
76+
exit 1
77+
}
78+
79+
$EXPECTED_OUTPUT_NAME = $EXTENSION_ID_SAFE
80+
if ($OS -eq "windows") {
81+
$EXPECTED_OUTPUT_NAME += ".exe"
82+
}
83+
84+
Rename-Item -Path "$OUTPUT_DIR/$EXPECTED_OUTPUT_NAME" -NewName $OUTPUT_NAME
85+
} elseif ($env:EXTENSION_LANGUAGE -eq "go") {
86+
# Set environment variables for Go build
87+
$env:GOOS = $OS
88+
$env:GOARCH = $ARCH
89+
90+
go build `
91+
-ldflags="-X '$APP_PATH.Version=$env:EXTENSION_VERSION' -X '$APP_PATH.Commit=$COMMIT' -X '$APP_PATH.BuildDate=$BUILD_DATE'" `
92+
-o $OUTPUT_NAME
93+
94+
if ($LASTEXITCODE -ne 0) {
95+
Write-Host "An error occurred while building for $OS/$ARCH"
96+
exit 1
97+
}
98+
} else {
99+
Write-Host "Error: Unsupported BUILD_TYPE '$env:BUILD_TYPE'. Use 'go' or 'dotnet'."
57100
exit 1
58101
}
59102
}
103+
104+
Write-Host "Build completed successfully!"
105+
Write-Host "Binaries are located in the $OUTPUT_DIR directory."

cli/azd/extensions/microsoft.azd.extensions/extension.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# yaml-language-server: $schema=../extension.schema.json
22
id: microsoft.azd.extensions
33
namespace: x
4+
language: go
45
displayName: AZD Extensions Developer Kit
56
description: This extension provides a set of tools for AZD extension developers to test and debug their extensions.
67
usage: azd x <command> [options]

cli/azd/extensions/microsoft.azd.extensions/internal/cmd/build.go

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os/exec"
1111
"path/filepath"
1212
"runtime"
13+
"strings"
1314

1415
"github.com/azure/azure-dev/cli/azd/extensions/microsoft.azd.extensions/internal"
1516
"github.com/azure/azure-dev/cli/azd/extensions/microsoft.azd.extensions/internal/models"
@@ -95,23 +96,35 @@ func runBuildAction(flags *buildFlags) error {
9596
taskList.AddTask(ux.TaskOptions{
9697
Title: "Building extension artifacts",
9798
Action: func(progress ux.SetProgressFunc) (ux.TaskState, error) {
98-
// Build the binaries
99-
buildScript := filepath.Join(flags.extensionPath, "build.sh")
100-
if _, err := os.Stat(buildScript); err == nil {
101-
var cmd *exec.Cmd
102-
if runtime.GOOS == "windows" {
103-
cmd = exec.Command("pwsh", "build.ps1")
104-
} else {
105-
cmd = exec.Command("bash", "build.sh")
99+
// Create output directory if it doesn't exist
100+
if _, err := os.Stat(absOutputPath); os.IsNotExist(err) {
101+
if err := os.MkdirAll(absOutputPath, os.ModePerm); err != nil {
102+
return ux.Error, common.NewDetailedError("Failed to create output directory", err)
106103
}
104+
}
107105

106+
var command string
107+
var scriptFile string
108+
if runtime.GOOS == "windows" {
109+
command = "pwsh"
110+
scriptFile = "build.ps1"
111+
} else {
112+
command = "bash"
113+
scriptFile = "build.sh"
114+
}
115+
116+
// Build the binaries
117+
buildScript := filepath.Join(flags.extensionPath, scriptFile)
118+
if _, err := os.Stat(buildScript); err == nil {
119+
cmd := exec.Command(command, scriptFile)
108120
cmd.Dir = absExtensionPath
109121

110122
envVars := map[string]string{
111-
"OUTPUT_DIR": absOutputPath,
112-
"EXTENSION_DIR": absExtensionPath,
113-
"EXTENSION_ID": schema.Id,
114-
"EXTENSION_VERSION": schema.Version,
123+
"OUTPUT_DIR": absOutputPath,
124+
"EXTENSION_DIR": absExtensionPath,
125+
"EXTENSION_ID": schema.Id,
126+
"EXTENSION_VERSION": schema.Version,
127+
"EXTENSION_LANGUAGE": schema.Language,
115128
}
116129

117130
// By default builds for current os/arch
@@ -147,7 +160,9 @@ func runBuildAction(flags *buildFlags) error {
147160
}
148161

149162
extensionInstallDir := filepath.Join(azdConfigDir, "extensions", schema.Id)
150-
if err := copyFiles(absOutputPath, extensionInstallDir); err != nil {
163+
extensionBinaryPrefix := strings.ReplaceAll(schema.Id, ".", "-")
164+
165+
if err := copyBinaryFiles(extensionBinaryPrefix, absOutputPath, extensionInstallDir); err != nil {
151166
return ux.Error, common.NewDetailedError(
152167
"Install failed",
153168
fmt.Errorf("failed to copy files to install directory: %w", err),
@@ -165,7 +180,7 @@ func runBuildAction(flags *buildFlags) error {
165180
return nil
166181
}
167182

168-
func copyFiles(sourcePath, destPath string) error {
183+
func copyBinaryFiles(extensionId, sourcePath, destPath string) error {
169184
if _, err := os.Stat(destPath); os.IsNotExist(err) {
170185
if err := os.MkdirAll(destPath, os.ModePerm); err != nil {
171186
return fmt.Errorf("failed to create install directory: %w", err)
@@ -177,20 +192,22 @@ func copyFiles(sourcePath, destPath string) error {
177192
return err
178193
}
179194

180-
relPath, err := filepath.Rel(sourcePath, path)
181-
if err != nil {
182-
return err
195+
// Only process files in the root of the source path
196+
if filepath.Dir(path) != sourcePath {
197+
return nil
183198
}
184199

185-
destPath := filepath.Join(destPath, relPath)
186-
187-
if info.IsDir() {
188-
if err := os.MkdirAll(destPath, internal.PermissionDirectory); err != nil {
189-
return fmt.Errorf("failed to create directory %s: %w", destPath, err)
190-
}
191-
} else {
192-
if err := copyFile(path, destPath); err != nil {
193-
return fmt.Errorf("failed to copy file %s to %s: %w", path, destPath, err)
200+
// Check if the file name starts with the extensionId and is either .exe or has no extension
201+
if info.Mode().IsRegular() {
202+
fileName := info.Name()
203+
if strings.HasPrefix(fileName, extensionId) {
204+
ext := filepath.Ext(fileName)
205+
if ext == ".exe" || ext == "" {
206+
destFilePath := filepath.Join(destPath, fileName)
207+
if err := copyFile(path, destFilePath); err != nil {
208+
return fmt.Errorf("failed to copy file %s to %s: %w", path, destFilePath, err)
209+
}
210+
}
194211
}
195212
}
196213

cli/azd/extensions/microsoft.azd.extensions/internal/cmd/init.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"io/fs"
1212
"os"
1313
"os/exec"
14+
"path"
1415
"path/filepath"
1516
"strings"
1617
"text/template"
@@ -248,6 +249,30 @@ func collectExtensionMetadata(ctx context.Context, azdClient *azdext.AzdClient)
248249
return nil, fmt.Errorf("failed to prompt for capabilities: %w", err)
249250
}
250251

252+
languageChoices := []*azdext.SelectChoice{
253+
{
254+
Label: "Go",
255+
Value: "go",
256+
},
257+
{
258+
Label: "C#",
259+
Value: "dotnet",
260+
},
261+
}
262+
263+
programmingLanguagePrompt, err := azdClient.Prompt().Select(ctx, &azdext.SelectRequest{
264+
Options: &azdext.SelectOptions{
265+
Message: "Select a programming language for your extension",
266+
Choices: languageChoices,
267+
EnableFiltering: internal.ToPtr(false),
268+
DisplayNumbers: internal.ToPtr(false),
269+
HelpMessage: "Programming language is used to define the language in which your extension is written. You can select one programming language.",
270+
},
271+
})
272+
if err != nil {
273+
return nil, fmt.Errorf("failed to prompt for programming language: %w", err)
274+
}
275+
251276
capabilities := make([]extensions.CapabilityType, len(capabilitiesPrompt.Values))
252277
for i, capability := range capabilitiesPrompt.Values {
253278
capabilities[i] = extensions.CapabilityType(capability.Value)
@@ -273,6 +298,7 @@ func collectExtensionMetadata(ctx context.Context, azdClient *azdext.AzdClient)
273298
Description: descriptionPrompt.Value,
274299
Namespace: namespacePrompt.Value,
275300
Capabilities: capabilities,
301+
Language: languageChoices[*programmingLanguagePrompt.Value].Value,
276302
Tags: tags,
277303
Usage: fmt.Sprintf("azd %s <command> [options]", namespacePrompt.Value),
278304
Version: "0.0.1",
@@ -313,7 +339,16 @@ func createExtensionDirectory(
313339
}
314340

315341
// Create project from template.
316-
err = copyAndProcessTemplates(resources.Languages, "languages/go", extensionPath, extensionMetadata)
342+
templateMetadata := &ExtensionTemplate{
343+
Metadata: extensionMetadata,
344+
DotNet: &DotNetTemplate{
345+
Namespace: internal.ToPascalCase(extensionMetadata.Id),
346+
ExeName: strings.ReplaceAll(extensionMetadata.Id, ".", "-"),
347+
},
348+
}
349+
350+
templatePath := path.Join("languages", extensionMetadata.Language)
351+
err = copyAndProcessTemplates(resources.Languages, templatePath, extensionPath, templateMetadata)
317352
if err != nil {
318353
return fmt.Errorf("failed to copy and process templates: %w", err)
319354
}
@@ -452,3 +487,13 @@ func createLocalRegistry(ctx context.Context, azdClient *azdext.AzdClient) error
452487

453488
return nil
454489
}
490+
491+
type ExtensionTemplate struct {
492+
Metadata *models.ExtensionSchema
493+
DotNet *DotNetTemplate
494+
}
495+
496+
type DotNetTemplate struct {
497+
Namespace string
498+
ExeName string
499+
}

cli/azd/extensions/microsoft.azd.extensions/internal/cmd/package.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ func processExtension(
192192
artifactMap := map[string]extensions.ExtensionArtifact{}
193193
if err == nil {
194194
targetPath := filepath.Join(outputPath, extensionMetadata.Id, extensionMetadata.Version)
195+
extensionYamlSourcePath := filepath.Join(extensionMetadata.Path, "extension.yaml")
195196

196197
// Ensure target directory exists
197198
if err := os.MkdirAll(targetPath, osutil.PermissionDirectory); err != nil {
@@ -200,14 +201,29 @@ func processExtension(
200201

201202
// Map and copy artifacts
202203
for _, artifact := range artifacts {
203-
extensionYamlSourcePath := filepath.Join(extensionMetadata.Path, "extension.yaml")
204-
artifactSourcePath := filepath.Join(artifactsPath, artifact.Name())
204+
if artifact.IsDir() {
205+
continue
206+
}
207+
208+
artifactSafePrefix := strings.ReplaceAll(extensionMetadata.Id, ".", "-")
209+
210+
// Only process files that match the extension ID
211+
artifactName := artifact.Name()
212+
if !strings.HasPrefix(artifactName, artifactSafePrefix) {
213+
continue
214+
}
215+
216+
ext := filepath.Ext(artifactName)
217+
if !(ext == ".exe" || ext == "") {
218+
continue
219+
}
205220

206-
fileWithoutExt := getFileNameWithoutExt(artifact.Name())
221+
fileWithoutExt := getFileNameWithoutExt(artifactName)
207222
zipFileName := fmt.Sprintf("%s.zip", fileWithoutExt)
208223
targetFilePath := filepath.Join(targetPath, zipFileName)
209224

210225
// Create a ZIP archive for the artifact
226+
artifactSourcePath := filepath.Join(artifactsPath, artifact.Name())
211227
zipFiles := []string{extensionYamlSourcePath, artifactSourcePath}
212228

213229
if err := zipSource(zipFiles, targetFilePath); err != nil {

cli/azd/extensions/microsoft.azd.extensions/internal/cmd/watch.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,15 @@ func runWatchAction(flags *watchFlags) error {
5757

5858
ignoredFolders := map[string]struct{}{
5959
"bin": {},
60+
"obj": {},
6061
}
6162

6263
// Define glob patterns for ignored paths
6364
globIgnorePaths := []string{
6465
"bin", // Matches the "bin" folder itself
6566
"bin/**/*", // Matches all files and subdirectories inside "bin"
67+
"obj", // Matches the "obj" folder itself
68+
"obj/**/*", // Matches all files and subdirectories inside "obj"
6669
}
6770

6871
if err := watchRecursive(flags.extensionPath, watcher, ignoredFolders); err != nil {

cli/azd/extensions/microsoft.azd.extensions/internal/models/extension_schema.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type ExtensionSchema struct {
1919

2020
Id string `yaml:"id" json:"id"`
2121
Namespace string `yaml:"namespace" json:"namespace,omitempty"`
22+
Language string `yaml:"language" json:"language,omitempty"`
2223
EntryPoint string `yaml:"entryPoint" json:"entryPoint,omitempty"`
2324
Version string `yaml:"version" json:"version"`
2425
Capabilities []extensions.CapabilityType `yaml:"capabilities" json:"capabilities"`
@@ -29,7 +30,7 @@ type ExtensionSchema struct {
2930
Tags []string `yaml:"tags" json:"tags,omitempty"`
3031
Dependencies []extensions.ExtensionDependency `yaml:"dependencies" json:"dependencies,omitempty"`
3132
Platforms map[string]map[string]any `yaml:"platforms" json:"platforms,omitempty"`
32-
Path string `yaml:"-" json:"-"` // Path to the extension directory
33+
Path string `yaml:"-" json:"-"`
3334
}
3435

3536
func LoadExtension(extensionPath string) (*ExtensionSchema, error) {

0 commit comments

Comments
 (0)