2

I'm building a mod for Unity based game RimWorld. I have the following assembly references in my .csproj file, using a variable so other people can build the project regardless of where RimWorld is installed:

        <Reference Include="Assembly-CSharp">
            <HintPath>$(RimWorldManagedDir)\Assembly-CSharp.dll</HintPath>
        </Reference>
        <Reference Include="UnityEngine.CoreModule">
            <HintPath>$(RimWorldManagedDir)\UnityEngine.CoreModule.dll</HintPath>
        </Reference>

At build time, I can define $(RimWorldManagedDir) on the command line, and it builds just fine:

msbuild Source/HGAPatchset.csproj -p:Configuration=Release -p:RimWorldManagedDir=/home/nicole/.local/share/Steam/steamapps/common/RimWorld/RimWorldLinux_Data/Managed/

But VSCode can't properly feed this into the language server, likely because I can't define this variable anywhere, and as a result I get autocomplete errors and the following message in the output window of the C# extension:

2025-11-22 13:53:16.735 [warning] [solution/open] [LanguageServerProjectLoader] Project /home/nicole/.local/share/Steam/steamapps/common/RimWorld/Mods/HGAPatchset/Source/HGAPatchset.csproj has unresolved dependencies

I'm very certain I can define this variable somewhere, but I can't seem to find any information on it. How do I define this variable somewhere the C# extension (any completion service) can see it?

1 Answer 1

1

I'm guessing/hoping that the completion service is doing a proper evaluation of the project by using the MSBuild SDK to load and evaluate the project. An example of not doing a proper project evaluation would be treating the project as an XML file and querying for Reference elements. The problem with doing this is that Properties (which you have referred to as variables) will not be evaluated.

Environment variables are mapped to Properties. You could, at your command prompt or in a shell script, define a RimWorldManagedDir environment variable and then start VSCode with code.

The RimWorldManagedDir property could have a default value. e.g.

    <PropertyGroup>
        <!-- assumes a RimWorld subdirectory of the project directory -->
        <RimWorldManagedDir Condition="'$(RimWorldManagedDir)' == ''">RimWorld/RimWorldLinux_Data/Managed/</RimWorldManagedDir>
        <!-- normalize the directory path to always end with a slash -->
        <RimWorldManagedDir>$([MSBuild]::EnsureTrailingSlash('$(RimWorldManagedDir)'))</RimWorldManagedDir>
    </PropertyGroup>
    <Reference Include="Assembly-CSharp">
        <HintPath>$(RimWorldManagedDir)Assembly-CSharp.dll</HintPath>
    </Reference>
    <Reference Include="UnityEngine.CoreModule">
        <HintPath>$(RimWorldManagedDir)UnityEngine.CoreModule.dll</HintPath>
    </Reference>

When there is a RimWorldManagedDir environment variable, the RimWorldManagedDir property will have a value and the Condition of '$(RimWorldManagedDir)' == '' will be false.

MSBuild supports textual includes via the Import element. An Import can have a Condition.

    <Import Project="user.targets" Condition="Exists('user.targets')" />
    <PropertyGroup>
        <!-- assumes a RimWorld subdirectory of the project directory -->
        <RimWorldManagedDir Condition="'$(RimWorldManagedDir)' == ''">RimWorld/RimWorldLinux_Data/Managed/</RimWorldManagedDir>
        <!-- normalize the directory path to always end with a slash -->
        <RimWorldManagedDir>$([MSBuild]::EnsureTrailingSlash('$(RimWorldManagedDir)'))</RimWorldManagedDir>
    </PropertyGroup>
    ...

The user.targets file might contain

<Project>
   <PropertyGroup>
        <RimWorldManagedDir Condition="'$(RimWorldManagedDir)' == ''">/home/nicole/.local/share/Steam/steamapps/common/RimWorld/RimWorldLinux_Data/Managed/</RimWorldManagedDir>
   </PropertyGroup>
</Project>

If there are some common conventions for install locations, you could test for those locations.

    <Import Project="user.targets" Condition="Exists('user.targets')" />
    <PropertyGroup>
        <_localSteamApps>$([System.Environment]::GetFolderPath(SpecialFolder.UserProfile)).local/share/Steam/steamapps/common/RimWorld/RimWorldLinux_Data/Managed/</_localSteamApps>
        <RimWorldManagedDir Condition="'$(RimWorldManagedDir)' == '' and Exists('$(_localSteamApps)')">$(_localSteamApps)</RimWorldManagedDir>
        <!-- assumes a RimWorld subdirectory of the project directory -->
        <RimWorldManagedDir Condition="'$(RimWorldManagedDir)' == ''">RimWorld/RimWorldLinux_Data/Managed/</RimWorldManagedDir>
        <!-- normalize the directory path to always end with a slash -->
        <RimWorldManagedDir>$([MSBuild]::EnsureTrailingSlash('$(RimWorldManagedDir)'))</RimWorldManagedDir>
    </PropertyGroup>
    ...

Now you have a cascade in the following order of precedence:

  1. Use the RimWorldManagedDir environment variable if it exists.
  2. Otherwise use the property value on the command line (the -p or -property switch) if it exists.
  3. Import the user.targets file if it exists. In the file a user can set a specific value for the property. In the example user.targets file, the property is set with a value when it doesn't already have a value (from the environment or command line).
  4. Otherwise test for a conventional location.
  5. Otherwise use a default value.
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.