|
4 | 4 | "errors" |
5 | 5 | "fmt" |
6 | 6 | "os" |
7 | | - "os/exec" |
8 | 7 | "path/filepath" |
9 | 8 | "strings" |
10 | 9 |
|
@@ -41,11 +40,6 @@ var miseStableChecksums = map[string]string{ |
41 | 40 | "macos-arm64": "0b5893de7c8c274736867b7c4c7ed565b4429f4d6272521ace802f8a21422319", |
42 | 41 | } |
43 | 42 |
|
44 | | -const ( |
45 | | - nixpkgsPluginGitURL = "https://github.com/bitrise-io/mise-nixpkgs-plugin.git" |
46 | | - nixpkgsPluginName = "mise-nixpkgs-plugin" |
47 | | -) |
48 | | - |
49 | 43 | type MiseToolProvider struct { |
50 | 44 | ExecEnv execenv.ExecEnv |
51 | 45 | } |
@@ -108,49 +102,51 @@ func (m *MiseToolProvider) Bootstrap() error { |
108 | 102 | } |
109 | 103 |
|
110 | 104 | func (m *MiseToolProvider) InstallTool(tool provider.ToolRequest) (provider.ToolInstallResult, error) { |
111 | | - useNix := m.shouldUseNixPkgs(tool) |
| 105 | + useNix, err := m.canBeInstalledWithNix(tool) |
| 106 | + if err != nil { |
| 107 | + return provider.ToolInstallResult{}, err |
| 108 | + } |
112 | 109 |
|
113 | | - if useNix { |
114 | | - tool.ToolName = provider.ToolID(fmt.Sprintf("nixpkgs:%s", tool.ToolName)) |
115 | | - } else { |
116 | | - err := m.InstallPlugin(tool) |
| 110 | + if !useNix { |
| 111 | + err = m.InstallPlugin(tool) |
117 | 112 | if err != nil { |
118 | 113 | return provider.ToolInstallResult{}, fmt.Errorf("install tool plugin %s: %w", tool.ToolName, err) |
119 | 114 | } |
120 | | - } |
| 115 | + } // else: nixpkgs plugin is already installed in ShouldInstallWithNix() |
121 | 116 |
|
122 | | - isAlreadyInstalled, err := isAlreadyInstalled(tool, m.resolveToLatestInstalled) |
| 117 | + installRequest := installRequest(tool, useNix) |
| 118 | + isAlreadyInstalled, err := isAlreadyInstalled(installRequest, m.resolveToLatestInstalled) |
123 | 119 | if err != nil { |
124 | 120 | return provider.ToolInstallResult{}, err |
125 | 121 | } |
126 | 122 |
|
127 | | - if !useNix { |
128 | | - // Nix already checks version existence previously |
129 | | - versionExists, err := m.versionExists(tool.ToolName, tool.UnparsedVersion) |
130 | | - if err != nil { |
131 | | - return provider.ToolInstallResult{}, fmt.Errorf("check if version exists: %w", err) |
132 | | - } |
133 | | - if !versionExists { |
134 | | - return provider.ToolInstallResult{}, provider.ToolInstallError{ |
135 | | - ToolName: tool.ToolName, |
136 | | - RequestedVersion: tool.UnparsedVersion, |
137 | | - Cause: fmt.Sprintf("no match for requested version %s", tool.UnparsedVersion), |
138 | | - } |
| 123 | + versionExists, err := m.versionExists(tool.ToolName, tool.UnparsedVersion) |
| 124 | + if err != nil { |
| 125 | + return provider.ToolInstallResult{}, fmt.Errorf("check if version exists: %w", err) |
| 126 | + } |
| 127 | + if !versionExists { |
| 128 | + return provider.ToolInstallResult{}, provider.ToolInstallError{ |
| 129 | + ToolName: tool.ToolName, |
| 130 | + RequestedVersion: tool.UnparsedVersion, |
| 131 | + Cause: fmt.Sprintf("no match for requested version %s", tool.UnparsedVersion), |
139 | 132 | } |
140 | 133 | } |
141 | 134 |
|
142 | | - err = m.installToolVersion(tool) |
| 135 | + err = m.installToolVersion(installRequest) |
143 | 136 | if err != nil { |
144 | 137 | return provider.ToolInstallResult{}, err |
145 | 138 | } |
146 | 139 |
|
147 | | - concreteVersion, err := m.resolveToConcreteVersionAfterInstall(tool) |
| 140 | + concreteVersion, err := m.resolveToConcreteVersionAfterInstall(installRequest) |
148 | 141 | if err != nil { |
149 | 142 | return provider.ToolInstallResult{}, fmt.Errorf("resolve exact version after install: %w", err) |
150 | 143 | } |
151 | 144 |
|
152 | 145 | return provider.ToolInstallResult{ |
153 | | - ToolName: tool.ToolName, |
| 146 | + // Note: we return installRequest.ToolName instead of the original tool.ToolName. |
| 147 | + // This is because installRequest might use a custom backend plugin and the value returned here |
| 148 | + // is what gets used in ActivateEnv(), the two should be consistent. |
| 149 | + ToolName: installRequest.ToolName, |
154 | 150 | IsAlreadyInstalled: isAlreadyInstalled, |
155 | 151 | ConcreteVersion: concreteVersion, |
156 | 152 | }, nil |
@@ -194,112 +190,3 @@ func GetMiseChecksums() map[string]string { |
194 | 190 | // Fallback to stable version for non-edge stacks |
195 | 191 | return miseStableChecksums |
196 | 192 | } |
197 | | - |
198 | | -func useNixPkgs(tool provider.ToolRequest) bool { |
199 | | - // Note: Add other tools here if needed |
200 | | - if tool.ToolName != "ruby" { |
201 | | - log.Debugf("[TOOLPROVIDER] Nix packages are only supported for ruby tool, current tool: %s", tool.ToolName) |
202 | | - return false |
203 | | - } |
204 | | - |
205 | | - if value, variablePresent := os.LookupEnv("BITRISEIO_MISE_LEGACY_INSTALL"); variablePresent && strings.Contains(value, "1") { |
206 | | - log.Debugf("[TOOLPROVIDER] Using legacy install (non-nix) for tool: %s", tool.ToolName) |
207 | | - return false |
208 | | - } |
209 | | - |
210 | | - return true |
211 | | -} |
212 | | - |
213 | | -// shouldUseNixPkgs checks if Nix packages should be used for the tool installation. |
214 | | -// It validates that the tool is eligible for Nix, the plugin is available, and the version exists in the index. |
215 | | -// Returns false and logs a warning if any validation fails, falling back to legacy installation. |
216 | | -func (m *MiseToolProvider) shouldUseNixPkgs(tool provider.ToolRequest) bool { |
217 | | - if !useNixPkgs(tool) { |
218 | | - return false |
219 | | - } |
220 | | - |
221 | | - if err := m.getNixpkgsPlugin(); err != nil { |
222 | | - log.Warnf("Failed to link nixpkgs plugin: %v. Falling back to legacy installation.", err) |
223 | | - return false |
224 | | - } |
225 | | - |
226 | | - nameWithBackend := provider.ToolID(fmt.Sprintf("nixpkgs:%s", tool.ToolName)) |
227 | | - |
228 | | - available, err := m.versionExists(nameWithBackend, tool.UnparsedVersion) |
229 | | - if err != nil || !available { |
230 | | - log.Warnf("Failed to check nixpkgs index for %s@%s: %v. Falling back to legacy installation.", tool.ToolName, tool.UnparsedVersion, err) |
231 | | - return false |
232 | | - } |
233 | | - |
234 | | - return true |
235 | | -} |
236 | | - |
237 | | -// findPluginPath finds the nixpkgs plugin directory relative to the bitrise executable. |
238 | | -// It first checks next to the binary, then tries a dev location one directory up. |
239 | | -// Returns the path if found, otherwise returns an error. |
240 | | -func findPluginPath() (string, error) { |
241 | | - execPath, err := os.Executable() |
242 | | - if err != nil { |
243 | | - return "", fmt.Errorf("get executable path: %w", err) |
244 | | - } |
245 | | - |
246 | | - execDir := filepath.Dir(execPath) |
247 | | - pluginPath := filepath.Join(execDir, nixpkgsPluginName) |
248 | | - |
249 | | - // Check if the plugin exists besides the binary |
250 | | - if _, err := os.Stat(pluginPath); os.IsNotExist(err) { |
251 | | - // Try dev location |
252 | | - pluginPath = filepath.Join(execDir, "..", nixpkgsPluginName) |
253 | | - if _, err := os.Stat(pluginPath); os.IsNotExist(err) { |
254 | | - return "", fmt.Errorf("%s not found", nixpkgsPluginName) |
255 | | - } |
256 | | - } |
257 | | - |
258 | | - return pluginPath, nil |
259 | | -} |
260 | | - |
261 | | -// getNixpkgsPlugin clones or updates the nixpkgs backend plugin and links it to mise. |
262 | | -// If the plugin directory doesn't exist, it clones from the git URL. |
263 | | -// If it exists, it checks out the specified commit/branch. |
264 | | -func (m *MiseToolProvider) getNixpkgsPlugin() error { |
265 | | - pluginPath, err := findPluginPath() |
266 | | - needsClone := false |
267 | | - if err != nil { |
268 | | - // Plugin doesn't exist, we need to clone it |
269 | | - needsClone = true |
270 | | - execPath, execErr := os.Executable() |
271 | | - if execErr != nil { |
272 | | - return fmt.Errorf("get executable path: %w", execErr) |
273 | | - } |
274 | | - pluginPath = filepath.Join(filepath.Dir(execPath), nixpkgsPluginName) |
275 | | - } |
276 | | - |
277 | | - if needsClone { |
278 | | - if err := cloneGitRepo(nixpkgsPluginGitURL, pluginPath); err != nil { |
279 | | - return fmt.Errorf("clone nixpkgs plugin: %w", err) |
280 | | - } |
281 | | - } |
282 | | - |
283 | | - // Link the plugin using mise plugin link |
284 | | - _, err = m.ExecEnv.RunMisePlugin("link", "--force", "nixpkgs", pluginPath) |
285 | | - if err != nil { |
286 | | - return fmt.Errorf("link nixpkgs plugin: %w", err) |
287 | | - } |
288 | | - |
289 | | - // Enable experimental settings for custom backend |
290 | | - if _, err := m.ExecEnv.RunMise("settings", "experimental=true"); err != nil { |
291 | | - return fmt.Errorf("enable experimental settings: %w", err) |
292 | | - } |
293 | | - |
294 | | - return nil |
295 | | -} |
296 | | - |
297 | | -// cloneGitRepo clones a git repository to the specified path with minimal history. |
298 | | -func cloneGitRepo(repoURL, destPath string) error { |
299 | | - cmd := exec.Command("git", "clone", "--depth", "1", "--no-tags", repoURL, destPath) |
300 | | - output, err := cmd.CombinedOutput() |
301 | | - if err != nil { |
302 | | - return fmt.Errorf("git clone failed: %w: %s", err, string(output)) |
303 | | - } |
304 | | - return nil |
305 | | -} |
0 commit comments