Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions internal/adapters/secondary/repository/repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"io"
"os"
"path/filepath"
"testing"
"time"

Expand All @@ -27,13 +28,15 @@ func NewMockFileSystem() *MockFileSystem {
}

func (m *MockFileSystem) ReadFile(name string) ([]byte, error) {
name = filepath.ToSlash(name)
if data, ok := m.files[name]; ok {
return data, nil
}
return nil, errors.New("file not found")
}

func (m *MockFileSystem) WriteFile(name string, data []byte, _ os.FileMode) error {
name = filepath.ToSlash(name)
m.files[name] = data
return nil
}
Expand All @@ -43,6 +46,7 @@ func (m *MockFileSystem) MkdirAll(_ string, _ os.FileMode) error {
}

func (m *MockFileSystem) Stat(name string) (os.FileInfo, error) {
name = filepath.ToSlash(name)
if _, ok := m.files[name]; ok {
//nolint:nilnil // Mock for testing
return nil, nil
Expand All @@ -51,6 +55,7 @@ func (m *MockFileSystem) Stat(name string) (os.FileInfo, error) {
}

func (m *MockFileSystem) Remove(name string) error {
name = filepath.ToSlash(name)
delete(m.files, name)
return nil
}
Expand All @@ -60,6 +65,8 @@ func (m *MockFileSystem) RemoveAll(_ string) error {
}

func (m *MockFileSystem) Rename(oldpath, newpath string) error {
oldpath = filepath.ToSlash(oldpath)
newpath = filepath.ToSlash(newpath)
if data, ok := m.files[oldpath]; ok {
m.files[newpath] = data
delete(m.files, oldpath)
Expand All @@ -82,6 +89,7 @@ func (m *MockFileSystem) Create(_ string) (io.WriteCloser, error) {
}

func (m *MockFileSystem) Exists(name string) bool {
name = filepath.ToSlash(name)
_, ok := m.files[name]
return ok
}
Expand Down
52 changes: 52 additions & 0 deletions internal/core/services/compiler/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,60 @@ func buildSearchPath(dep *domain.Dependency) string {
return searchPath
}

func compileLazarus(lazarusPath string, tracker *BuildTracker) bool {
if tracker == nil || !tracker.IsEnabled() {
msg.Info(" 🔨 Building Lazarus project/package: " + filepath.Base(lazarusPath))
}

_, err := exec.LookPath("lazbuild")
if err != nil {
if tracker == nil || !tracker.IsEnabled() {
msg.Err(" ❌ 'lazbuild' compiler not found on PATH. Please install Lazarus/lazbuild to compile.")
}
return false
}

absPath, _ := filepath.Abs(lazarusPath)
absDir := filepath.Dir(absPath)

cmd := exec.Command("lazbuild", "--build-mode=Debug", absPath)
cmd.Dir = absDir

buildLog := filepath.Join(absDir, "build_boss_" + strings.TrimSuffix(filepath.Base(lazarusPath), filepath.Ext(lazarusPath)) + ".log")
logFile, err := os.OpenFile(buildLog, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
if err != nil {
if tracker == nil || !tracker.IsEnabled() {
msg.Warn(" ⚠️ Error creating build log file: %v", err)
}
return false
}
defer logFile.Close()

cmd.Stdout = logFile
cmd.Stderr = logFile

if err := cmd.Run(); err != nil {
if tracker == nil || !tracker.IsEnabled() {
msg.Err(" ❌ Failed to compile, see " + buildLog + " for more information: %v", err)
}
return false
}

if tracker == nil || !tracker.IsEnabled() {
msg.Info(" ✅️ Success!")
}

_ = os.Remove(buildLog)
return true
}

//nolint:funlen,gocognit,lll // Complex compilation orchestration with long function signature
func compile(dprojPath string, dep *domain.Dependency, rootLock domain.PackageLock, tracker *BuildTracker, selectedCompiler *compilerselector.SelectedCompiler) bool {
ext := strings.ToLower(filepath.Ext(dprojPath))
if ext == ".lpi" || ext == ".lpk" {
return compileLazarus(dprojPath, tracker)
}

if tracker == nil || !tracker.IsEnabled() {
msg.Info(" 🔨 Building " + filepath.Base(dprojPath))
}
Expand Down
6 changes: 4 additions & 2 deletions internal/core/services/installer/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,11 @@ func DoInstall(config env.ConfigProvider, options InstallOptions, pkg *domain.Pa
msg.SetProgressTracker(nil)
progress.Stop()

paths.EnsureCleanModulesDir(dependencies, pkg.Lock)
paths.EnsureCleanModulesDir(dependencies, pkg.Lock, len(options.Args) == 0)

pkg.Lock.CleanRemoved(dependencies)
if len(options.Args) == 0 {
pkg.Lock.CleanRemoved(dependencies)
}
if err := pkgmanager.SavePackageCurrent(pkg); err != nil {
msg.Warn("⚠️ Failed to save package: %v", err)
}
Expand Down
24 changes: 24 additions & 0 deletions internal/core/services/installer/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,27 @@ func TestAddWarning(t *testing.T) {
t.Errorf("Expected warning 'Test warning', got %q", ctx.warnings[0])
}
}

func TestCollectDependenciesToInstall_WithFilter(t *testing.T) {
pkg := &domain.Package{
Dependencies: map[string]string{
"github.com/hashload/horse": "^1.0.0",
"github.com/hashload/boss": "^2.0.0",
},
}

// Test empty filter (should return all)
all := collectDependenciesToInstall(pkg, []string{})
if len(all) != 2 {
t.Errorf("Expected 2 dependencies, got %d", len(all))
}

// Test with filter (should return only matching)
filtered := collectDependenciesToInstall(pkg, []string{"horse"})
if len(filtered) != 1 {
t.Errorf("Expected 1 dependency, got %d", len(filtered))
}
if filtered[0].Repository != "github.com/hashload/horse" {
t.Errorf("Expected horse dependency, got %q", filtered[0].Repository)
}
}
4 changes: 2 additions & 2 deletions internal/core/services/paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
// EnsureCleanModulesDir ensures that the modules directory is clean and contains only the required dependencies.
//
//nolint:gocognit // Refactoring would reduce readability
func EnsureCleanModulesDir(dependencies []domain.Dependency, lock domain.PackageLock) {
func EnsureCleanModulesDir(dependencies []domain.Dependency, lock domain.PackageLock, cleanAll bool) {
cacheDir := env.GetModulesDir()
cacheDirInfo, err := os.Stat(cacheDir)
if os.IsNotExist(err) {
Expand Down Expand Up @@ -47,7 +47,7 @@ func EnsureCleanModulesDir(dependencies []domain.Dependency, lock domain.Package
continue
}

if !utils.Contains(dependenciesNames, info.Name()) {
if cleanAll && !utils.Contains(dependenciesNames, info.Name()) {
remove:
if err = os.RemoveAll(filepath.Join(cacheDir, info.Name())); err != nil {
msg.Warn("⚠️ Failed to remove old cache: %s", err.Error())
Expand Down
55 changes: 53 additions & 2 deletions internal/core/services/paths/paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestEnsureCleanModulesDir_CreatesDir(t *testing.T) {
}

// EnsureCleanModulesDir should create the modules directory
paths.EnsureCleanModulesDir(deps, lock)
paths.EnsureCleanModulesDir(deps, lock, true)

// Verify modules directory was created
modulesDir := filepath.Join(tempDir, consts.FolderDependencies)
Expand Down Expand Up @@ -114,7 +114,7 @@ func TestEnsureCleanModulesDir_RemovesOldDependencies(t *testing.T) {
}

// EnsureCleanModulesDir should remove old dependency
paths.EnsureCleanModulesDir(deps, lock)
paths.EnsureCleanModulesDir(deps, lock, true)

// Verify old dependency was removed
if _, err := os.Stat(oldDepDir); !os.IsNotExist(err) {
Expand All @@ -126,3 +126,54 @@ func TestEnsureCleanModulesDir_RemovesOldDependencies(t *testing.T) {
t.Error("EnsureCleanModulesDir() should keep current dependency directories")
}
}

func TestEnsureCleanModulesDir_KeepOldDependenciesOnSelective(t *testing.T) {
// Create a temp directory for workspace
tempDir := t.TempDir()

// Save original state and set not global
originalGlobal := env.GetGlobal()
defer env.SetGlobal(originalGlobal)
env.SetGlobal(false)

// Change to temp directory
t.Chdir(tempDir)

// Create modules directory
modulesDir := filepath.Join(tempDir, consts.FolderDependencies)
if err := os.MkdirAll(modulesDir, 0755); err != nil {
t.Fatalf("Failed to create modules dir: %v", err)
}

// Create an old dependency directory that should NOT be removed because cleanAll is false
oldDepDir := filepath.Join(modulesDir, "old-dependency")
if err := os.MkdirAll(oldDepDir, 0755); err != nil {
t.Fatalf("Failed to create old dependency dir: %v", err)
}

// Create a current dependency directory that should be kept
currentDepDir := filepath.Join(modulesDir, "horse")
if err := os.MkdirAll(currentDepDir, 0755); err != nil {
t.Fatalf("Failed to create current dependency dir: %v", err)
}

// Define current dependencies
dep := domain.ParseDependency("github.com/hashload/horse", "^1.0.0")
deps := []domain.Dependency{dep}
lock := domain.PackageLock{
Installed: map[string]domain.LockedDependency{},
}

// EnsureCleanModulesDir with cleanAll=false should keep the old dependency
paths.EnsureCleanModulesDir(deps, lock, false)

// Verify old dependency was NOT removed
if _, err := os.Stat(oldDepDir); os.IsNotExist(err) {
t.Error("EnsureCleanModulesDir() with cleanAll=false should NOT remove old dependency directories")
}

// Verify current dependency was kept
if _, err := os.Stat(currentDepDir); os.IsNotExist(err) {
t.Error("EnsureCleanModulesDir() should keep current dependency directories")
}
}
1 change: 1 addition & 0 deletions pkg/consts/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
FileExtensionDpr = ".dpr"
FileExtensionDproj = ".dproj"
FileExtensionLpi = ".lpi"
FileExtensionLpk = ".lpk"

FilePackageLockOld = "boss.lock"
FolderDependencies = "modules"
Expand Down
33 changes: 23 additions & 10 deletions utils/librarypath/dproj_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import (

var (
//nolint:lll // Regex pattern readability is important
reProjectFile = regexp.MustCompile(`.*` + regexp.QuoteMeta(consts.FileExtensionDproj) + `|.*` + regexp.QuoteMeta(consts.FileExtensionLpi) + `$`)
reLazarusFile = regexp.MustCompile(`.*` + regexp.QuoteMeta(consts.FileExtensionLpi) + `$`)
reProjectFile = regexp.MustCompile(`.*` + regexp.QuoteMeta(consts.FileExtensionDproj) + `|.*` + regexp.QuoteMeta(consts.FileExtensionLpi) + `|.*` + regexp.QuoteMeta(consts.FileExtensionLpk) + `$`)
reLazarusFile = regexp.MustCompile(`.*` + regexp.QuoteMeta(consts.FileExtensionLpi) + `|.*` + regexp.QuoteMeta(consts.FileExtensionLpk) + `$`)
)

// updateDprojLibraryPath updates the library path in the project file.
Expand All @@ -40,28 +40,41 @@ func updateOtherUnitFilesProject(lpiName string) {
doc := etree.NewDocument()
info, err := os.Stat(lpiName)
if os.IsNotExist(err) || info.IsDir() {
msg.Err("❌ .lpi not found.")
msg.Err("❌ Lazarus project/package file not found.")
return
}
err = doc.ReadFromFile(lpiName)
if err != nil {
msg.Err("❌ Error on read lpi: %s", err)
msg.Err("❌ Error on read lazarus file: %s", err)
return
}

root := doc.Root()

compilerOptions := root.SelectElement(consts.XMLTagNameCompilerOptions)
processCompilerOptions(compilerOptions)
if compilerOptions != nil {
processCompilerOptions(compilerOptions)
}

projectOptions := root.SelectElement(consts.XMLTagNameProjectOptions)
if projectOptions != nil {
buildModes := projectOptions.SelectElement(consts.XMLTagNameBuildModes)
if buildModes != nil {
for _, item := range buildModes.SelectElements(consts.XMLTagNameItem) {
attribute := item.SelectAttr(consts.XMLNameAttribute)
compilerOptions = item.SelectElement(consts.XMLTagNameCompilerOptions)
if compilerOptions != nil {
msg.Info(" 🔁 Updating %s mode", attribute.Value)
processCompilerOptions(compilerOptions)
}
}
}
}

buildModes := projectOptions.SelectElement(consts.XMLTagNameBuildModes)
for _, item := range buildModes.SelectElements(consts.XMLTagNameItem) {
attribute := item.SelectAttr(consts.XMLNameAttribute)
compilerOptions = item.SelectElement(consts.XMLTagNameCompilerOptions)
packageOptions := root.SelectElement("Package")
if packageOptions != nil {
compilerOptions = packageOptions.SelectElement(consts.XMLTagNameCompilerOptions)
if compilerOptions != nil {
msg.Info(" 🔁 Updating %s mode", attribute.Value)
processCompilerOptions(compilerOptions)
}
}
Expand Down
Loading
Loading