MSBuild技术问题:如何将%(Compile.Identity)转换为obj文件名?
Priority Solution: Fix Metadata Reference & Use Transform Syntax
The MSB4095 error you're hitting happens because your metadata reference is ambiguous. In your original Transform expression %(Compile.Identity->'%(Filename).obj'), the inner %(Filename) isn't tied to the Compile item collection—MSBuild can't tell which item's filename you're referring to.
1. Correct the Exec Command
If you want to stick with the Exec task, explicitly specify the Compile item's Filename metadata in the Transform, and use @ to reference the entire transformed list (this avoids running echo once per file, which is inefficient):
<Exec Command="echo @(Compile->'%(Compile.Filename).obj', '
') > $(OutputPath)\$(ExecutableName).link" />
@(Compile->'%(Compile.Filename).obj', '
'): Converts eachCompileitem tofilename.obj, using a carriage return + line feed (XML-escaped as
) to separate entries- Use
>to overwrite the file (prevents duplicate entries on re-runs); keep>>if you need to append instead
2. More Efficient Native MSBuild Approach: Use WriteLinesToFile
Instead of spawning a cmd process with Exec, use MSBuild's built-in WriteLinesToFile task—it's cleaner and faster:
<WriteLinesToFile File="$(OutputPath)\$(ExecutableName).link" Lines="@(Compile->'%(Filename).obj')" Overwrite="true" Encoding="UTF-8" />
Lines="@(Compile->'%(Filename).obj'): Directly uses the transformed.objpaths as the file contentOverwrite="true"replaces the file on each run; set tofalseif you need to append
Pure MSBuild Implementation (No Dependency on Microsoft.Cpp.targets)
If you want a fully self-contained build script without relying on C++-specific Targets files, here's a complete framework:
1. Define Base Properties & Item Collections
<PropertyGroup> <OutputPath>bin\$(Configuration)</OutputPath> <ExecutableName>HelloWorld</ExecutableName> <!-- Update these paths to match your MSVC installation if they're not in PATH --> <CLPath>cl.exe</CLPath> <LinkPath>link.exe</LinkPath> </PropertyGroup> <ItemGroup> <Compile Include="src\*.cpp" /> <!-- Your C++ source files --> </ItemGroup>
2. Compile Target: Generate .obj Files
<Target Name="Compile"> <!-- Create output directory if it doesn't exist --> <MakeDir Directories="$(OutputPath)" /> <!-- Compile each .cpp file to .obj --> <Exec Command="$(CLPath) /c %(Compile.Identity) /Fo$(OutputPath)\%(Compile.Filename).obj" /> <!-- Generate the linker input file (using the recommended WriteLinesToFile) --> <WriteLinesToFile File="$(OutputPath)\$(ExecutableName).link" Lines="@(Compile->'$(OutputPath)\%(Filename).obj')" Overwrite="true" /> </Target>
3. Link Target: Build the Executable
<Target Name="Link" DependsOnTargets="Compile"> <!-- Use the generated link file to pass .obj list to linker --> <Exec Command="$(LinkPath) @$(OutputPath)\$(ExecutableName).link /OUT:$(OutputPath)\$(ExecutableName).exe" /> </Target> <!-- Default build target --> <Target Name="Build" DependsOnTargets="Link" />
Notes
- This script manually calls
cl.exeandlink.exe—no external Targets files required - Ensure MSVC's toolchain is in your system PATH, or specify full paths for
CLPathandLinkPath - The
@filenamesyntax is natively supported bylink.exeto read input files from a text list
内容的提问来源于stack exchange,提问作者dmitry_bond

