Summary
RelaxVersioner 3.21.0 can fail in CI with a file-lock error while running pack-related targets.
The failing file is typically:
obj/Release/RelaxVersioner_Properties.xml
Example error:
RelaxVersioner.targets(327,5): error MSB4018: The "DumpPropertiesTask" task failed unexpectedly.
System.IO.IOException: The process cannot access the file
'obj/Release/RelaxVersioner_Properties.xml'
because it is being used by another process.
at System.IO.File.Create(String path)
at RelaxVersioner.DumpPropertiesTask.Execute()
Expected behavior
RelaxVersioner should work correctly when the same project is evaluated or built multiple times in CI, including scenarios such as:
- inner builds
- graph builds
- pack-related build requests
- multi-target SDK-style builds
Actual behavior
DumpPropertiesTask may attempt to create the same RelaxVersioner_Properties.xml file concurrently, which results in an MSB4018 failure.
This behavior was observed consistently when pack-related targets were executed in parallel.
Why this seems to happen
From the 3.21.0 sources:
- RelaxVersionerPackCoreGenerate calls DumpPropertiesTask.
- In the pack path, RelaxVersionerOutputDir is derived from $(NuspecOutputPath).
As a result, the dump path becomes something like:
obj/Release/RelaxVersioner_Properties.xml
This path is shared and does not include a TFM-specific directory.
In contrast, normal build-time outputs usually go under:
$(IntermediateOutputPath)
which for SDK-style multi-target builds typically resolves to:
obj/<Configuration>/<TFM>/
Based on observation:
- pack-related targets may resolve multiple TFMs to the same shared path
- normal build-time targets resolve to separate TFM-scoped directories
Important detail
In 3.21.0, DumpPropertiesTask.Execute() uses:
without retry or locking, and the exception is not handled in that release.
As a result, the IOException propagates as MSB4018.
Broader concern
The issue may affect more than DumpPropertiesTask.
The same dump base path is used for files such as:
- RelaxVersioner_Result.xml
- RelaxVersioner_Version.txt
- RelaxVersioner_ShortVersion.txt
- RelaxVersioner_SafeVersion.txt
- RelaxVersioner_CommitId.txt
- RelaxVersioner_Branch.txt
- RelaxVersioner_Tags.txt
Therefore, protecting only DumpPropertiesTask might prevent the immediate exception but still leave race conditions where one build request observes another request's files.
Example scenario:
- Build request A writes RelaxVersioner_Properties.xml
- Build request B overwrites it
- Build request A runs
rv --propertiesPath ...
- A reads B's data instead of its own
Suggested direction
A robust fix might be one of the following:
-
Preferred
Make the properties/result/helper output paths unique per build request or inner build.
-
Alternative
Serialize the whole pipeline per dump base path:
- dump properties
- run rv
- read back results
- write helper files
-
Minimal mitigation
Add exception handling or retry logic around DumpPropertiesTask.
This would mitigate the current MSB4018, but it would not fully solve the shared-path race.
Environment
- RelaxVersioner: 3.21.0
- CI: GitHub Actions
- OS: Linux runner
- Project type: SDK-style .NET project
- Observed during pack-related processing
Due to repository constraints I cannot share a minimal reproduction project,
but the behavior was consistently observed in CI when pack-related targets
were executed in parallel in a multi-target SDK-style build.
Summary
RelaxVersioner 3.21.0 can fail in CI with a file-lock error while running pack-related targets.
The failing file is typically:
obj/Release/RelaxVersioner_Properties.xmlExample error:
Expected behavior
RelaxVersioner should work correctly when the same project is evaluated or built multiple times in CI, including scenarios such as:
Actual behavior
DumpPropertiesTask may attempt to create the same RelaxVersioner_Properties.xml file concurrently, which results in an MSB4018 failure.
This behavior was observed consistently when pack-related targets were executed in parallel.
Why this seems to happen
From the 3.21.0 sources:
As a result, the dump path becomes something like:
obj/Release/RelaxVersioner_Properties.xmlThis path is shared and does not include a TFM-specific directory.
In contrast, normal build-time outputs usually go under:
$(IntermediateOutputPath)which for SDK-style multi-target builds typically resolves to:
obj/<Configuration>/<TFM>/Based on observation:
Important detail
In 3.21.0, DumpPropertiesTask.Execute() uses:
without retry or locking, and the exception is not handled in that release.
As a result, the IOException propagates as MSB4018.
Broader concern
The issue may affect more than DumpPropertiesTask.
The same dump base path is used for files such as:
Therefore, protecting only DumpPropertiesTask might prevent the immediate exception but still leave race conditions where one build request observes another request's files.
Example scenario:
rv --propertiesPath ...Suggested direction
A robust fix might be one of the following:
Preferred
Make the properties/result/helper output paths unique per build request or inner build.
Alternative
Serialize the whole pipeline per dump base path:
Minimal mitigation
Add exception handling or retry logic around DumpPropertiesTask.
This would mitigate the current MSB4018, but it would not fully solve the shared-path race.
Environment
Due to repository constraints I cannot share a minimal reproduction project,
but the behavior was consistently observed in CI when pack-related targets
were executed in parallel in a multi-target SDK-style build.