Skip to content

fix(librarypath): resolve a subdirectory project's paths against its own directory - #275

Merged
viniciussanchez merged 1 commit into
HashLoad:mainfrom
vBaggio:fix/dproj-rootpath
Aug 12, 2026
Merged

fix(librarypath): resolve a subdirectory project's paths against its own directory#275
viniciussanchez merged 1 commit into
HashLoad:mainfrom
vBaggio:fix/dproj-rootpath

Conversation

@vBaggio

@vBaggio vBaggio commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

O problema

Um projeto que não fica ao lado do boss.json — um projeto de testes numa subpasta, por exemplo — recebe os paths das dependências relativos à raiz do boss.json, e não à pasta dele. O que vai parar no DCC_UnitSearchPath aponta para lugar nenhum, e a compilação morre mesmo com o boss reportando instalação bem-sucedida:

[dcc32 Fatal Error] ProjectTests.pas(7): F2613 Unit 'SomeDependency' not found.

Com o .dproj em <raiz>/tests/ e as dependências em <raiz>/modules/, o boss escreve modules\..., quando o correto a partir de tests/ é ..\modules\....

Não é o comportamento pretendido: os dois trechos afetados nasceram justamente para resolver o path por projeto — 14b5102 "Setting search paths relative to project path" e ac7fe16 "Adding browsing path by project relative root folder". Se a ideia fosse fixar tudo na raiz do boss.json, bastaria rootPath := env.GetCurrentDir(); a presença de path.Dir(dprojName) mostra a intenção de usar a pasta do próprio projeto. O suporte a projeto fora da raiz também é explícito em getExplicitProjectNames, que aceita caminho absoluto e resolve caminho relativo contra a raiz.

Causa

rootPath := filepath.Join(env.GetCurrentDir(), path.Dir(dprojName))
if _, err = os.Stat(rootPath); os.IsNotExist(err) {
    rootPath = env.GetCurrentDir()
}

dprojName é sempre um caminho absoluto e nativo do SO — GetProjectNames monta toda entrada com filepath.Join. Mas path.Dir só entende /, então os dois sistemas perdem o diretório do projeto por caminhos diferentes:

  • Windows: não há / num caminho com barra invertida, então path.Dir responde ".", o Join volta para o diretório de trabalho, e a guarda os.Stat nunca dispara — porque esse diretório existe de verdade.
  • POSIX: path.Dir responde certo, mas juntar um resultado absoluto sobre o diretório de trabalho duplica o caminho (/home/user/proj/home/user/proj/tests). Aí o os.Stat falha e a guarda cai no diretório de trabalho.

Os dois terminam na raiz do boss.json. Não é específico de plataforma nem de versão do Delphi — é a API de path errada.

A correção

filepath.Dir responde certo nos dois sistemas, então o rootPath sai direto dele. Os dois call sites (o .dproj e o browsing path do Windows) passam a compartilhar um helper, dprojRootPath, que documenta a armadilha num lugar só.

A guarda os.Stat foi junto: updateLibraryPathProject já faz os.Stat(dprojName) e retorna quando o arquivo não existe, e um arquivo que existe sempre tem um pai que existe — o ramo tinha virado inalcançável.

Quem muda de comportamento

Projeto na raiz do boss.json — o caso comum — não muda em nada, nos dois sistemas:

antes depois
raiz, windows path.Dir".", Join→cwd filepath.Dir→cwd
raiz, posix path duplicado → guarda → cwd filepath.Dir→cwd
subpasta cwd (paths quebrados) pasta do projeto

Só muda quem já estava quebrado. E nada muda em quais projetos são escritos: isso é decidido por GetProjectNames, que este PR não toca — a auto-descoberta continua lendo só a raiz, sem recursão, e uma subpasta só é tocada se estiver listada em projects.

A profundidade é livre, porque quem calcula o caminho é o filepath.Rel: tests/app.dproj recebe ..\modules\..., a/b/c/app.dproj recebe ..\..\..\modules\....

Vale registrar um caso: se a subpasta for ela mesma um projeto boss, com boss.json e modules/ próprios, o comportamento antigo escrevia modules\..., que resolvido a partir da subpasta caía no modules/ dela — acertando por acidente quando as duas tinham uma dependência de mesmo nome. Agora aponta para o modules/ da raiz, que é o que a raiz pediu ao listar aquele projeto. Quem gerencia o projeto aninhado rodando boss install dentro dele não sente diferença: esse é o caso da primeira linha da tabela. Um boss.json aninhado segue invisível para a execução da raiz, que varre apenas <cwd>/modules.

Testes

TestUpdateLibraryPathProject_SubdirectoryProject monta uma dependência em modules/mydep/src e um .dproj em app/, e cobra o path relativo à pasta do projeto. Verificado como regressão de verdade — revertendo só a linha do fix:

--- FAIL: TestUpdateLibraryPathProject_SubdirectoryProject
    expected DCC_UnitSearchPath to contain "..\modules\mydep\src", got
    "$(DCC_UnitSearchPath);modules\mydep\src;modules\.dcp;modules\.dcu"
  • go test ./... verde em todos os pacotes.
  • go build ./... e go vet ./... limpos.
  • golangci-lint: nenhum achado novo. Medido contra o main num worktree com LF: 9 achados no main, 9 nesta branch, mesmo detalhamento — todos pré-existentes em global_util_win.go, que o lint do CI não analisa por causa do build tag.

Validado também de ponta a ponta num projeto real com o .dproj de testes numa subpasta: antes do fix o arquivo recebia modules\... e o compilador não achava as units; depois recebe ..\modules\... e todas resolvem. O .dproj da raiz do mesmo projeto continuou byte a byte igual, como esperado pela tabela acima. Rodando o install três vezes seguidas o resultado é idêntico — a de-duplicação continua vindo do utils.Contains, então não há crescimento da lista.

Uma ressalva sobre o browsing path

O browsing path é um valor único no registro, e updateGlobalBrowsingPath percorre todos os projetos escrevendo nele. Com projetos em profundidades diferentes, o último processado decide a base do caminho relativo — antes todos escreviam relativo à raiz, agora cada um escreve relativo a si. Nenhum valor único serve aos dois casos, então isso é limitação do desenho e não algo introduzido aqui; o que muda é qual projeto acaba favorecido. Para o caso de um projeto só, que é o comum, não há diferença. Se preferirem, dá para restringir o fix ao .dproj — onde a semântica é inequívoca, já que o MSBuild resolve caminho relativo contra o próprio arquivo de projeto — e tratar o browsing path à parte.

Fora de escopo

Dois problemas vizinhos que apareceram na investigação e não entram aqui:

  • cleanPath monta o prefixo contra env.GetCurrentDir(), então num projeto em subpasta ele nunca reconhece os paths que o próprio boss escreveu (..\modules\...) e não remove o que sobrou de uma dependência desinstalada. O path fica órfão no .dproj, sem quebrar a compilação.
  • processCompilerOptions, no caminho do Lazarus, usa env.GetCurrentDir() fixo e tem a mesma limitação para um .lpi/.lpk em subpasta. Corrigir exige passar o nome do arquivo adiante, o que muda mais superfície do que este bug pede.

🤖 Generated with Claude Code

…own directory

A project that does not sit next to boss.json -- a test project under tests/, for
example -- received dependency paths relative to the boss.json root instead of to
its own folder. The paths written into DCC_UnitSearchPath then pointed nowhere and
the compiler could not find the units, failing with "F2613 Unit '<name>' not found"
even though boss reported a successful install.

rootPath came from filepath.Join(env.GetCurrentDir(), path.Dir(dprojName)), and
dprojName is always an absolute, OS-native path because GetProjectNames builds
every entry with filepath.Join. path.Dir only understands "/", so the two platforms
lost the project's directory by different routes:

  windows: no "/" in a backslash path, so path.Dir answers ".", the Join collapses
           back to the working directory, and the os.Stat guard never fires because
           that directory does exist

  posix:   path.Dir answers correctly, but joining an absolute result onto the
           working directory doubles the path, so os.Stat fails and the guard
           silently falls back to the working directory

Both ended at the boss.json root. filepath.Dir answers correctly on either platform,
so rootPath is now taken straight from it, shared by the .dproj writer and the
Windows browsing path writer through one helper.

The os.Stat fallback went with it: updateLibraryPathProject already stats dprojName
and returns when it is missing, and a file that exists always has a parent that
exists, so the branch could no longer be reached.

Only projects outside the boss.json directory change behaviour. A project sitting at
the root resolves to the same directory it always did.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 12, 2026 00:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@807f017). Learn more about missing BASE report.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #275   +/-   ##
=======================================
  Coverage        ?   28.94%           
=======================================
  Files           ?       90           
  Lines           ?     5700           
  Branches        ?        0           
=======================================
  Hits            ?     1650           
  Misses          ?     3907           
  Partials        ?      143           
Flag Coverage Δ
unittests 28.94% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@viniciussanchez
viniciussanchez merged commit a9dc3c8 into HashLoad:main Aug 12, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants