From 211711c4c339158f3c0b0c985ddf52bd22374ec5 Mon Sep 17 00:00:00 2001 From: Isaque Pinheiro Date: Thu, 25 Jun 2026 13:15:26 -0300 Subject: [PATCH] fix(compiler): respect platform in toolchain settings when compiler version is omitted (issue #229) --- .../services/compilerselector/selector.go | 58 ++++-- .../compilerselector/selector_test.go | 172 ++++++++++++++++++ 2 files changed, 215 insertions(+), 15 deletions(-) create mode 100644 internal/core/services/compilerselector/selector_test.go diff --git a/internal/core/services/compilerselector/selector.go b/internal/core/services/compilerselector/selector.go index eaa56414..62a43cc9 100644 --- a/internal/core/services/compilerselector/selector.go +++ b/internal/core/services/compilerselector/selector.go @@ -67,39 +67,67 @@ func (s *Service) SelectCompiler(ctx SelectionContext) (*SelectedCompiler, error return nil, errors.New("no Delphi installation found") } - if ctx.CliCompilerVersion != "" { - return findCompiler(installations, ctx.CliCompilerVersion, ctx.CliPlatform) + // Determine target platform + var targetPlatform string + if ctx.CliPlatform != "" { + targetPlatform = ctx.CliPlatform + } else if ctx.Package != nil && ctx.Package.Toolchain != nil && ctx.Package.Toolchain.Platform != "" { + targetPlatform = ctx.Package.Toolchain.Platform + } else { + targetPlatform = consts.PlatformWin32.String() } - if ctx.Package != nil && ctx.Package.Toolchain != nil { - tc := ctx.Package.Toolchain - - platform := tc.Platform - if platform == "" { - platform = consts.PlatformWin32.String() - } + // 1. If CLI compiler version is specified + if ctx.CliCompilerVersion != "" { + return findCompiler(installations, ctx.CliCompilerVersion, targetPlatform) + } - if tc.Compiler != "" { - return findCompiler(installations, tc.Compiler, platform) - } + // 2. If toolchain compiler version is specified + if ctx.Package != nil && ctx.Package.Toolchain != nil && ctx.Package.Toolchain.Compiler != "" { + return findCompiler(installations, ctx.Package.Toolchain.Compiler, targetPlatform) } + // 3. Fallback to global path globalPath := s.config.GetDelphiPath() if globalPath != "" { + // Try to find a matching installation for the target platform in the global path for _, inst := range installations { instDir := filepath.Dir(inst.Path) - if strings.EqualFold(instDir, globalPath) { + if strings.EqualFold(instDir, globalPath) && strings.EqualFold(inst.Arch, targetPlatform) { return createSelectedCompiler(inst), nil } } + // Fallback if not found in registered installations + var compilerBinary string + switch targetPlatform { + case consts.PlatformWin64.String(): + compilerBinary = "dcc64.exe" + default: + compilerBinary = "dcc32.exe" + } return &SelectedCompiler{ - Path: filepath.Join(globalPath, "dcc32.exe"), + Path: filepath.Join(globalPath, compilerBinary), BinDir: globalPath, - Arch: consts.PlatformWin32.String(), + Arch: targetPlatform, }, nil } + // 4. Fallback to the latest installation that matches the target platform + var bestMatch *registryadapter.DelphiInstallation + for _, inst := range installations { + if strings.EqualFold(inst.Arch, targetPlatform) { + if bestMatch == nil || inst.Version > bestMatch.Version { + instCopy := inst + bestMatch = &instCopy + } + } + } + if bestMatch != nil { + return createSelectedCompiler(*bestMatch), nil + } + + // If no installation matching the target platform is found, fallback to the latest overall installation if len(installations) > 0 { latest := installations[0] for _, inst := range installations[1:] { diff --git a/internal/core/services/compilerselector/selector_test.go b/internal/core/services/compilerselector/selector_test.go new file mode 100644 index 00000000..f60d22da --- /dev/null +++ b/internal/core/services/compilerselector/selector_test.go @@ -0,0 +1,172 @@ +package compilerselector_test + +import ( + "path/filepath" + "testing" + + registryadapter "github.com/hashload/boss/internal/adapters/secondary/registry" + "github.com/hashload/boss/internal/core/domain" + "github.com/hashload/boss/internal/core/services/compilerselector" + "github.com/hashload/boss/pkg/env" +) + +// mockRegistryAdapter is a mock for compilerselector.RegistryAdapter +type mockRegistryAdapter struct { + installations []registryadapter.DelphiInstallation +} + +func (m *mockRegistryAdapter) GetDetectedDelphis() []registryadapter.DelphiInstallation { + return m.installations +} + +// mockConfigProvider embeds env.ConfigProvider to mock only GetDelphiPath +type mockConfigProvider struct { + env.ConfigProvider + delphiPath string +} + +func (m *mockConfigProvider) GetDelphiPath() string { + return m.delphiPath +} + +func TestSelectCompiler_CliOverrides(t *testing.T) { + registry := &mockRegistryAdapter{ + installations: []registryadapter.DelphiInstallation{ + {Version: "35.0", Path: filepath.Join("C:", "Delphi35", "bin", "dcc32.exe"), Arch: "Win32"}, + {Version: "35.0", Path: filepath.Join("C:", "Delphi35", "bin", "dcc64.exe"), Arch: "Win64"}, + {Version: "36.0", Path: filepath.Join("C:", "Delphi36", "bin", "dcc32.exe"), Arch: "Win32"}, + {Version: "36.0", Path: filepath.Join("C:", "Delphi36", "bin", "dcc64.exe"), Arch: "Win64"}, + }, + } + config := &mockConfigProvider{} + service := compilerselector.NewService(registry, config) + + // CLI version and platform specified + ctx := compilerselector.SelectionContext{ + CliCompilerVersion: "35.0", + CliPlatform: "Win64", + Package: &domain.Package{ + Toolchain: &domain.PackageToolchain{ + Compiler: "36.0", + Platform: "Win32", + }, + }, + } + + selected, err := service.SelectCompiler(ctx) + if err != nil { + t.Fatalf("Failed to select compiler: %v", err) + } + + if selected.Version != "35.0" { + t.Errorf("Expected version 35.0, got %s", selected.Version) + } + if selected.Arch != "Win64" { + t.Errorf("Expected arch Win64, got %s", selected.Arch) + } +} + +func TestSelectCompiler_ToolchainCompilerAndPlatform(t *testing.T) { + registry := &mockRegistryAdapter{ + installations: []registryadapter.DelphiInstallation{ + {Version: "35.0", Path: filepath.Join("C:", "Delphi35", "bin", "dcc32.exe"), Arch: "Win32"}, + {Version: "35.0", Path: filepath.Join("C:", "Delphi35", "bin", "dcc64.exe"), Arch: "Win64"}, + {Version: "36.0", Path: filepath.Join("C:", "Delphi36", "bin", "dcc32.exe"), Arch: "Win32"}, + {Version: "36.0", Path: filepath.Join("C:", "Delphi36", "bin", "dcc64.exe"), Arch: "Win64"}, + }, + } + config := &mockConfigProvider{} + service := compilerselector.NewService(registry, config) + + // Toolchain compiler and platform specified + ctx := compilerselector.SelectionContext{ + Package: &domain.Package{ + Toolchain: &domain.PackageToolchain{ + Compiler: "36.0", + Platform: "Win64", + }, + }, + } + + selected, err := service.SelectCompiler(ctx) + if err != nil { + t.Fatalf("Failed to select compiler: %v", err) + } + + if selected.Version != "36.0" { + t.Errorf("Expected version 36.0, got %s", selected.Version) + } + if selected.Arch != "Win64" { + t.Errorf("Expected arch Win64, got %s", selected.Arch) + } +} + +func TestSelectCompiler_ToolchainPlatformOnly(t *testing.T) { + registry := &mockRegistryAdapter{ + installations: []registryadapter.DelphiInstallation{ + {Version: "35.0", Path: filepath.Join("C:", "Delphi35", "bin", "dcc32.exe"), Arch: "Win32"}, + {Version: "35.0", Path: filepath.Join("C:", "Delphi35", "bin", "dcc64.exe"), Arch: "Win64"}, + {Version: "36.0", Path: filepath.Join("C:", "Delphi36", "bin", "dcc32.exe"), Arch: "Win32"}, + {Version: "36.0", Path: filepath.Join("C:", "Delphi36", "bin", "dcc64.exe"), Arch: "Win64"}, + }, + } + config := &mockConfigProvider{} + service := compilerselector.NewService(registry, config) + + // Only platform Win64 specified in toolchain + ctx := compilerselector.SelectionContext{ + Package: &domain.Package{ + Toolchain: &domain.PackageToolchain{ + Platform: "Win64", + }, + }, + } + + selected, err := service.SelectCompiler(ctx) + if err != nil { + t.Fatalf("Failed to select compiler: %v", err) + } + + // Should select the latest version (36.0) matching platform Win64 + if selected.Version != "36.0" { + t.Errorf("Expected version 36.0, got %s", selected.Version) + } + if selected.Arch != "Win64" { + t.Errorf("Expected arch Win64, got %s", selected.Arch) + } +} + +func TestSelectCompiler_FallbackToGlobalPath(t *testing.T) { + registry := &mockRegistryAdapter{ + installations: []registryadapter.DelphiInstallation{ + {Version: "35.0", Path: filepath.Join("C:", "Delphi35", "bin", "dcc32.exe"), Arch: "Win32"}, + }, + } + globalPath := filepath.Join("C:", "CustomDelphi", "bin") + config := &mockConfigProvider{ + delphiPath: globalPath, + } + service := compilerselector.NewService(registry, config) + + // Win64 requested, should fallback to dcc64.exe in globalPath + ctx := compilerselector.SelectionContext{ + Package: &domain.Package{ + Toolchain: &domain.PackageToolchain{ + Platform: "Win64", + }, + }, + } + + selected, err := service.SelectCompiler(ctx) + if err != nil { + t.Fatalf("Failed to select compiler: %v", err) + } + + expectedPath := filepath.Join(globalPath, "dcc64.exe") + if selected.Path != expectedPath { + t.Errorf("Expected path %s, got %s", expectedPath, selected.Path) + } + if selected.Arch != "Win64" { + t.Errorf("Expected arch Win64, got %s", selected.Arch) + } +}