fix(librarypath): resolve a subdirectory project's paths against its own directory - #275
Merged
Merged
Conversation
…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>
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #275 +/- ##
=======================================
Coverage ? 28.94%
=======================================
Files ? 90
Lines ? 5700
Branches ? 0
=======================================
Hits ? 1650
Misses ? 3907
Partials ? 143
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 doboss.json, e não à pasta dele. O que vai parar noDCC_UnitSearchPathaponta para lugar nenhum, e a compilação morre mesmo com o boss reportando instalação bem-sucedida:Com o
.dprojem<raiz>/tests/e as dependências em<raiz>/modules/, o boss escrevemodules\..., quando o correto a partir detests/é..\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"eac7fe16 "Adding browsing path by project relative root folder". Se a ideia fosse fixar tudo na raiz doboss.json, bastariarootPath := env.GetCurrentDir(); a presença depath.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 emgetExplicitProjectNames, que aceita caminho absoluto e resolve caminho relativo contra a raiz.Causa
dprojNameé sempre um caminho absoluto e nativo do SO —GetProjectNamesmonta toda entrada comfilepath.Join. Maspath.Dirsó entende/, então os dois sistemas perdem o diretório do projeto por caminhos diferentes:/num caminho com barra invertida, entãopath.Dirresponde".", oJoinvolta para o diretório de trabalho, e a guardaos.Statnunca dispara — porque esse diretório existe de verdade.path.Dirresponde certo, mas juntar um resultado absoluto sobre o diretório de trabalho duplica o caminho (/home/user/proj/home/user/proj/tests). Aí oos.Statfalha 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.Dirresponde certo nos dois sistemas, então orootPathsai direto dele. Os dois call sites (o.dproje o browsing path do Windows) passam a compartilhar um helper,dprojRootPath, que documenta a armadilha num lugar só.A guarda
os.Statfoi junto:updateLibraryPathProjectjá fazos.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:path.Dir→".",Join→cwdfilepath.Dir→cwdfilepath.Dir→cwdSó 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 emprojects.A profundidade é livre, porque quem calcula o caminho é o
filepath.Rel:tests/app.dprojrecebe..\modules\...,a/b/c/app.dprojrecebe..\..\..\modules\....Vale registrar um caso: se a subpasta for ela mesma um projeto boss, com
boss.jsonemodules/próprios, o comportamento antigo escreviamodules\..., que resolvido a partir da subpasta caía nomodules/dela — acertando por acidente quando as duas tinham uma dependência de mesmo nome. Agora aponta para omodules/da raiz, que é o que a raiz pediu ao listar aquele projeto. Quem gerencia o projeto aninhado rodandoboss installdentro dele não sente diferença: esse é o caso da primeira linha da tabela. Umboss.jsonaninhado segue invisível para a execução da raiz, que varre apenas<cwd>/modules.Testes
TestUpdateLibraryPathProject_SubdirectoryProjectmonta uma dependência emmodules/mydep/srce um.dprojemapp/, e cobra o path relativo à pasta do projeto. Verificado como regressão de verdade — revertendo só a linha do fix:go test ./...verde em todos os pacotes.go build ./...ego vet ./...limpos.golangci-lint: nenhum achado novo. Medido contra omainnum worktree com LF: 9 achados nomain, 9 nesta branch, mesmo detalhamento — todos pré-existentes emglobal_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
.dprojde testes numa subpasta: antes do fix o arquivo recebiamodules\...e o compilador não achava as units; depois recebe..\modules\...e todas resolvem. O.dprojda 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 doutils.Contains, então não há crescimento da lista.Uma ressalva sobre o browsing path
O browsing path é um valor único no registro, e
updateGlobalBrowsingPathpercorre 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:
cleanPathmonta o prefixo contraenv.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, usaenv.GetCurrentDir()fixo e tem a mesma limitação para um.lpi/.lpkem subpasta. Corrigir exige passar o nome do arquivo adiante, o que muda mais superfície do que este bug pede.🤖 Generated with Claude Code