Skip to content
Merged
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
19 changes: 13 additions & 6 deletions utils/librarypath/dproj_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package librarypath

import (
"os"
"path"
"path/filepath"
"regexp"
"strings"
Expand Down Expand Up @@ -128,6 +127,18 @@ func updateGlobalBrowsingPath(pkg *domain.Package) {
}
}

// dprojRootPath returns the directory a project file's own paths are relative to.
// dprojName is always an absolute, OS-native path (GetProjectNames builds every entry
// with filepath.Join), so its parent has to be resolved with filepath.Dir. path.Dir
// only understands "/": on Windows it finds no separator in a backslash path and
// answers ".", and on POSIX it answers correctly but the caller then joined that
// absolute result onto the working directory, doubling the path. Both ways the
// project's real directory was lost. Kept in this file, rather than inlined, so the
// Windows-only browsing path caller shares one definition with the .dproj caller.
func dprojRootPath(dprojName string) string {
return filepath.Dir(dprojName)
}

// updateLibraryPathProject updates the library path in the project file.
func updateLibraryPathProject(dprojName string) {
doc := etree.NewDocument()
Expand All @@ -151,11 +162,7 @@ func updateLibraryPathProject(dprojName string) {
if child == nil {
child = createTagLibraryPath(children)
}
rootPath := filepath.Join(env.GetCurrentDir(), path.Dir(dprojName))
if _, err = os.Stat(rootPath); os.IsNotExist(err) {
rootPath = env.GetCurrentDir()
}
processCurrentPath(child, rootPath)
processCurrentPath(child, dprojRootPath(dprojName))
}
}

Expand Down
92 changes: 92 additions & 0 deletions utils/librarypath/dproj_util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//nolint:testpackage // Testing internal dproj utility functions
package librarypath

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/beevik/etree"
"github.com/hashload/boss/internal/adapters/secondary/filesystem"
"github.com/hashload/boss/internal/adapters/secondary/repository"
"github.com/hashload/boss/internal/core/services/packages"
"github.com/hashload/boss/pkg/pkgmanager"
)

const mockDprojContent = `<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Base)'!=''">
<DCC_UnitSearchPath>$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
</PropertyGroup>
</Project>
`

// TestUpdateLibraryPathProject_SubdirectoryProject verifies that a .dproj located in a
// subdirectory of the boss.json root gets paths relative to its OWN directory, not to
// the root. dprojName is always an absolute, OS-native path (built via filepath.Join),
// so resolving its parent directory must use filepath.Dir rather than path.Dir -- the
// latter only understands "/" and silently collapses to "." on a Windows backslash path.
func TestUpdateLibraryPathProject_SubdirectoryProject(t *testing.T) {
tempDir := t.TempDir()
t.Chdir(tempDir)

fs := filesystem.NewOSFileSystem()
packageRepo := repository.NewFilePackageRepository(fs)
lockRepo := repository.NewFileLockRepository(fs)
packageService := packages.NewPackageService(packageRepo, lockRepo)
pkgmanager.SetInstance(packageService)

depSrcDir := filepath.Join(tempDir, "modules", "mydep", "src")
if err := os.MkdirAll(depSrcDir, 0755); err != nil {
t.Fatalf("Failed to create dependency src dir: %v", err)
}
if err := os.WriteFile(filepath.Join(depSrcDir, "dummy.pas"), []byte("unit dummy;"), 0600); err != nil {
t.Fatalf("Failed to write dummy.pas: %v", err)
}
depBossJSON := `{"name": "mydep", "mainsrc": "src"}`
depBossJSONPath := filepath.Join(tempDir, "modules", "mydep", "boss.json")
if err := os.WriteFile(depBossJSONPath, []byte(depBossJSON), 0600); err != nil {
t.Fatalf("Failed to write dependency boss.json: %v", err)
}

projectDir := filepath.Join(tempDir, "app")
if err := os.MkdirAll(projectDir, 0755); err != nil {
t.Fatalf("Failed to create project dir: %v", err)
}
dprojPath := filepath.Join(projectDir, "project.dproj")
if err := os.WriteFile(dprojPath, []byte(mockDprojContent), 0600); err != nil {
t.Fatalf("Failed to write mock dproj: %v", err)
}

updateLibraryPathProject(dprojPath)

doc := etree.NewDocument()
if err := doc.ReadFromFile(dprojPath); err != nil {
t.Fatalf("Failed to read updated dproj: %v", err)
}

var searchPath string
for _, group := range doc.Root().FindElements("PropertyGroup") {
if el := group.SelectElement("DCC_UnitSearchPath"); el != nil {
searchPath = el.Text()
}
}
if searchPath == "" {
t.Fatal("DCC_UnitSearchPath not found in updated dproj")
}

expected := filepath.Join("..", "modules", "mydep", "src")
if !strings.Contains(filepath.Clean(searchPath), expected) {
t.Errorf("expected DCC_UnitSearchPath to contain %q (relative to the project's own directory), got %q",
expected, searchPath)
}

wrong := filepath.Join("modules", "mydep", "src")
for _, entry := range strings.Split(searchPath, ";") {
if filepath.Clean(entry) == wrong {
t.Errorf("DCC_UnitSearchPath contains %q, which is relative to the boss.json root instead of "+
"the project's own directory -- rootPath was computed wrong", entry)
}
}
}
5 changes: 1 addition & 4 deletions utils/librarypath/global_util_win.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
package librarypath

import (
"path"
"path/filepath"
"strings"

"github.com/hashload/boss/pkg/consts"
Expand Down Expand Up @@ -103,8 +101,7 @@ func updateGlobalBrowsingByProject(dprojName string, setReadOnly bool) {
}

splitPaths := strings.Split(paths, ";")
rootPath := filepath.Join(env.GetCurrentDir(), path.Dir(dprojName))
newSplitPaths := GetNewBrowsingPaths(splitPaths, false, rootPath, setReadOnly)
newSplitPaths := GetNewBrowsingPaths(splitPaths, false, dprojRootPath(dprojName), setReadOnly)
newPaths := strings.Join(newSplitPaths, ";")
err = delphiPlatform.SetStringValue(BrowsingPathRegistry, newPaths)
if err != nil {
Expand Down
Loading