core/providers/dotnet/dotnet.go
core/providers/dotnet/dotnet.goBrowse 1970 files
1,677 tokens
6,082 bytes
Token encoding: o200k_base
Snapshot 21a254f
← Back to SKILL.md
1package dotnet2 3import (4 "encoding/json"5 "encoding/xml"6 "fmt"7 "maps"8 "path"9 "regexp"10 "strings"11 12 "github.com/railwayapp/railpack/core/generate"13 "github.com/railwayapp/railpack/core/plan"14 "github.com/railwayapp/railpack/internal/utils"15)16 17const (18 DEFAULT_DOTNET_VERSION = "8.0"19 DOTNET_ROOT = "/mise/installs/dotnet"20 DOTNET_DEPENDENCIES_ROOT = "/root/.nuget/packages"21)22 23type DotnetProvider struct {24}25 26func (p *DotnetProvider) Name() string {27 return "Dotnet"28}29 30func (p *DotnetProvider) Detect(ctx *generate.GenerateContext) (bool, error) {31 return ctx.App.HasMatch("*.csproj"), nil32}33 34func (p *DotnetProvider) Initialize(ctx *generate.GenerateContext) error {35 return nil36}37 38func (p *DotnetProvider) Plan(ctx *generate.GenerateContext) error {39 miseStep := ctx.GetMiseStepBuilder()40 p.InstallMisePackages(ctx, miseStep)41 42 install := ctx.NewCommandStep("install")43 install.AddInput(plan.NewStepLayer(miseStep.Name()))44 p.Install(ctx, install)45 46 build := ctx.NewCommandStep("build")47 build.AddInput(plan.NewStepLayer(miseStep.Name()))48 build.AddInput(plan.NewStepLayer(install.Name(), plan.Filter{49 Include: []string{"obj/", DOTNET_DEPENDENCIES_ROOT},50 }))51 p.Build(ctx, build)52 53 envVars := p.GetEnvVars(ctx)54 // Required for internationalization55 ctx.Deploy.AddAptPackages([]string{"libicu-dev"})56 ctx.Deploy.AddInputs([]plan.Layer{57 plan.NewStepLayer(miseStep.Name(), plan.Filter{58 // Need to include the dotnet runtime for the binary to run59 Include: []string{envVars["DOTNET_ROOT"]},60 }),61 plan.NewStepLayer(build.Name(), plan.Filter{62 Include: []string{"out"},63 }),64 })65 ctx.Deploy.StartCmd = p.GetStartCommand(ctx)66 maps.Copy(ctx.Deploy.Variables, envVars)67 68 return nil69}70 71func (p *DotnetProvider) CleansePlan(buildPlan *plan.BuildPlan) {}72 73func (p *DotnetProvider) StartCommandHelp() string {74 return "To start your Dotnet application, Railpack will look for:\n\n" +75 "1. A .csproj file in your project root\n\n" +76 "The project will be run with `./out`"77}78 79func (p *DotnetProvider) GetStartCommand(ctx *generate.GenerateContext) string {80 projFiles, err := ctx.App.FindFiles("*.csproj")81 if err != nil || len(projFiles) == 0 {82 return ""83 }84 projFile := projFiles[0]85 projName := strings.TrimSuffix(projFile, ".csproj")86 return fmt.Sprintf("ASPNETCORE_URLS=http://0.0.0.0:${PORT:-3000} ./out/%s", projName)87}88 89func (p *DotnetProvider) Install(ctx *generate.GenerateContext, install *generate.CommandStepBuilder) {90 maps.Copy(install.Variables, p.GetEnvVars(ctx))91 install.AddCommands([]plan.Command{92 plan.NewCopyCommand("nuget.config*"),93 plan.NewCopyCommand("*.csproj"),94 plan.NewCopyCommand("global.json*"),95 plan.NewExecCommand(fmt.Sprintf("mkdir -p %s", DOTNET_DEPENDENCIES_ROOT)),96 plan.NewExecCommand(`dotnet restore`),97 })98}99 100func (p *DotnetProvider) Build(ctx *generate.GenerateContext, build *generate.CommandStepBuilder) {101 maps.Copy(build.Variables, p.GetEnvVars(ctx))102 build.AddInput(plan.NewLocalLayer())103 build.AddCommands([]plan.Command{104 plan.NewExecCommand("dotnet publish --no-restore -c Release -o out"),105 })106}107 108func (p *DotnetProvider) GetEnvVars(ctx *generate.GenerateContext) map[string]string {109 version := p.getDotnetVersion(ctx)110 return map[string]string{111 "ASPNETCORE_ENVIRONMENT": "Production",112 "ASPNETCORE_CONTENTROOT": "/app/out",113 "DOTNET_CLI_TELEMETRY_OPTOUT": "1",114 "DOTNET_ROOT": path.Join(DOTNET_ROOT, version),115 }116}117 118func (p *DotnetProvider) InstallMisePackages(ctx *generate.GenerateContext, miseStep *generate.MiseStepBuilder) {119 dotnet := miseStep.Default("dotnet", DEFAULT_DOTNET_VERSION)120 121 if files, err := ctx.App.FindFiles("*.csproj"); err == nil && len(files) > 0 {122 if data, err := ctx.App.ReadFile(files[0]); err == nil {123 var project *Project124 err = xml.Unmarshal([]byte(data), &project)125 if err != nil {126 fmt.Printf("Error parsing XML: %v\n", err)127 return128 }129 130 for _, pg := range project.PropertyGroups {131 if pg.TargetFramework != "" {132 version := extractVersionFromCsproj(pg.TargetFramework)133 if version != "" {134 miseStep.Version(dotnet, version, "csproj")135 break136 }137 }138 139 if pg.TargetFrameworks != "" {140 frameworks := strings.Split(pg.TargetFrameworks, ";")141 if len(frameworks) > 0 {142 version := extractVersionFromCsproj(frameworks[0])143 if version != "" {144 miseStep.Version(dotnet, version, "csproj")145 break146 }147 }148 }149 }150 }151 }152 153 if globalJSON, err := ctx.App.ReadFile("global.json"); err == nil {154 var global *CSharpGlobalJSON155 if err := json.Unmarshal([]byte(globalJSON), &global); err == nil && global.SDK.Version != "" {156 version := utils.ExtractSemverVersion(global.SDK.Version)157 semver, err := utils.ParseSemver(version)158 159 // The global.json file needs to be respected, unlike csproj which can be fuzzy160 if err == nil {161 version := fmt.Sprintf("%d.%d.%d", semver.Major, semver.Minor, semver.Patch)162 miseStep.Version(dotnet, version, "global.json")163 }164 }165 }166 167 if envVersion, varName := ctx.Env.GetConfigVariable("DOTNET_VERSION"); envVersion != "" {168 miseStep.Version(dotnet, envVersion, varName)169 }170 171 miseStep.UseMiseVersions(ctx, []string{"dotnet"})172}173 174func (p *DotnetProvider) getDotnetVersion(ctx *generate.GenerateContext) string {175 miseStepBuilder := ctx.GetMiseStepBuilder()176 pkg := miseStepBuilder.Resolver.Get("dotnet")177 if pkg != nil && pkg.Version != "" {178 return pkg.Version179 }180 return DEFAULT_DOTNET_VERSION181}182 183type Project struct {184 PropertyGroups []PropertyGroup `xml:"PropertyGroup"`185}186 187type PropertyGroup struct {188 TargetFramework string `xml:"TargetFramework"`189 TargetFrameworks string `xml:"TargetFrameworks"`190}191 192func extractVersionFromCsproj(framework string) string {193 // Match patterns like net6.0, netcoreapp3.1, net5.0-windows, etc.194 re := regexp.MustCompile(`(?:net|netcoreapp)(\d+\.\d+)`)195 matches := re.FindStringSubmatch(framework)196 if len(matches) > 1 {197 return matches[1]198 }199 200 return ""201}202 203type CSharpGlobalJSON struct {204 SDK SDK `json:"sdk"`205}206 207type SDK struct {208 Version string `json:"version"`209}210