github.com/replit/upm@v0.0.0-20240423230255-9ce4fc3ea24c/internal/backends/backends.go (about) 1 // Package backends contains the language-specific UPM backends, and 2 // logic for selecting amongst them. 3 package backends 4 5 import ( 6 "context" 7 "strings" 8 9 "github.com/replit/upm/internal/api" 10 "github.com/replit/upm/internal/backends/dart" 11 "github.com/replit/upm/internal/backends/dotnet" 12 "github.com/replit/upm/internal/backends/elisp" 13 "github.com/replit/upm/internal/backends/java" 14 "github.com/replit/upm/internal/backends/nodejs" 15 "github.com/replit/upm/internal/backends/php" 16 "github.com/replit/upm/internal/backends/python" 17 "github.com/replit/upm/internal/backends/rlang" 18 "github.com/replit/upm/internal/backends/ruby" 19 "github.com/replit/upm/internal/backends/rust" 20 "github.com/replit/upm/internal/util" 21 "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" 22 ) 23 24 // languageBackends is a slice of language backends which may be used 25 // from the command line. 26 // 27 // If more than one backend might match the same project, then one 28 // that comes first in this list will be used. 29 var languageBackends = []api.LanguageBackend{ 30 python.PythonPoetryBackend, 31 python.PythonPipBackend, 32 nodejs.BunBackend, 33 nodejs.NodejsNPMBackend, 34 nodejs.NodejsPNPMBackend, 35 nodejs.NodejsYarnBackend, 36 ruby.RubyBackend, 37 elisp.ElispBackend, 38 dart.DartPubBackend, 39 java.JavaBackend, 40 rlang.RlangBackend, 41 dotnet.DotNetBackend, 42 rust.RustBackend, 43 php.PhpComposerBackend, 44 } 45 46 // matchesLanguage checks if a language backend matches a value for 47 // the --lang argument. For example, the python-python3-poetry backend 48 // will match --lang=python-poetry and --lang=python3 but not 49 // --lang=python2. This is used to filter the available language 50 // backends before trying to guess which one should be used. 51 func matchesLanguage(b api.LanguageBackend, language string) bool { 52 bParts := map[string]bool{} 53 for _, bPart := range strings.Split(b.Name, "-") { 54 bParts[bPart] = true 55 } 56 checkAlias := false 57 for _, lPart := range strings.Split(language, "-") { 58 if !bParts[lPart] { 59 checkAlias = true 60 break 61 } 62 } 63 if checkAlias { 64 bParts = map[string]bool{} 65 for _, bPart := range strings.Split(b.Alias, "-") { 66 bParts[bPart] = true 67 } 68 for _, lPart := range strings.Split(language, "-") { 69 if !bParts[lPart] { 70 return false 71 } 72 } 73 } 74 return true 75 } 76 77 // GetBackend returns the language backend for a given --lang argument 78 // value. If none is applicable, it exits the process. 79 func GetBackend(ctx context.Context, language string) api.LanguageBackend { 80 //nolint:ineffassign,wastedassign,staticcheck 81 span, ctx := tracer.StartSpanFromContext(ctx, "GetBackend") 82 defer span.Finish() 83 backends := languageBackends 84 if language != "" { 85 filteredBackends := []api.LanguageBackend{} 86 for _, b := range backends { 87 if matchesLanguage(b, language) { 88 filteredBackends = append(filteredBackends, b) 89 } 90 } 91 switch len(filteredBackends) { 92 case 0: 93 util.DieConsistency("no such language: %s", language) 94 case 1: 95 return filteredBackends[0] 96 default: 97 backends = filteredBackends 98 } 99 100 } 101 for _, b := range backends { 102 if util.Exists(b.Specfile) && 103 util.Exists(b.Lockfile) { 104 if b.IsSpecfileCompatible != nil { 105 isValid, err := b.IsSpecfileCompatible(b.Specfile) 106 if err != nil { 107 panic(err) 108 } 109 if !isValid { 110 continue 111 } 112 } 113 return b 114 } 115 } 116 for _, b := range backends { 117 if util.Exists(b.Specfile) { 118 if b.IsSpecfileCompatible != nil { 119 isValid, err := b.IsSpecfileCompatible(b.Specfile) 120 if err != nil { 121 panic(err) 122 } 123 if !isValid { 124 continue 125 } 126 } 127 128 return b 129 } 130 if util.Exists(b.Lockfile) { 131 return b 132 } 133 } 134 for _, b := range backends { 135 for _, p := range b.FilenamePatterns { 136 if util.PatternExists(p) { 137 return b 138 } 139 } 140 } 141 if language == "" { 142 util.DieInitializationError("could not autodetect a language for your project") 143 } 144 return backends[0] 145 } 146 147 type BackendInfo struct { 148 Name string 149 Available bool 150 } 151 152 // GetBackendNames returns a slice of the canonical names (e.g. 153 // python-python3-poetry, not just python3) for all the backends 154 // listed in languageBackends. 155 func GetBackendNames() []BackendInfo { 156 var backendNames []BackendInfo 157 for _, b := range languageBackends { 158 backendNames = append(backendNames, BackendInfo{Name: b.Name, Available: b.IsAvailable()}) 159 } 160 return backendNames 161 } 162 163 // SetupAll panics if any registered language backend does not 164 // implement its mandatory fields. It also assigns defaults for all 165 // registered language backends. 166 func SetupAll() { 167 for i := range languageBackends { 168 // Make sure that the Setup function can make changes 169 // to the struct. 170 (&languageBackends[i]).Setup() 171 } 172 }