Open verelpode opened 3 years ago
@drewnoakes There's a lot to look at here, but we should consider this feedback as we work on the new property pages.
@tmeschter -- Thanks for considering it. Here's some details about the storage aspect:
To store what the suggested new symbols GUI needs, here is an example of a backwards-compatible extension to the .csproj format:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<ConditionalCompilationSymbols>
<!-- This section determines the symbol names that should appear in the symbol list in the VS GUI, but NOT the value or tick status of each symbol. -->
<ConditionalCompilationSymbol Name="DEBUG" Comment="" />
<ConditionalCompilationSymbol Name="TRACE" Comment="" />
<ConditionalCompilationSymbol Name="EXPERIMENTAL" Comment="The program might crash if you enable this symbol." />
<ConditionalCompilationSymbol Name="UNFINISHED" Comment="The project might not compile if you enable this symbol." />
<ConditionalCompilationSymbol Name="TEMP_TESTING" Comment="Never enable this in the Release config!" />
<ConditionalCompilationSymbol Name="EXAMPLE_SYM_1" Comment="Blah blah blah." />
<ConditionalCompilationSymbol Name="EXAMPLE_SYM_2" Comment="Blah blah." />
<ConditionalCompilationSymbol Name="EXAMPLE_SYM_3" Comment="" />
</ConditionalCompilationSymbols>
<!-- If desired, the following could potentially be supported to instruct the GUI to use symbols from shared symbol files used in multiple .csproj files: -->
<ConditionalCompilationSymbols Include="..\Unity\Unity-Symbols.xml" />
<ConditionalCompilationSymbols Include="..\ExampleFolder\Our-Common-Symbols.xml" />
</PropertyGroup>
<!-- The following format is unchanged (the same format as currently used in .csproj files). -->
<!-- The following determines which symbols are ticked (or Value=true) in the VS GUI. -->
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG; TRACE; EXPERIMENTAL; EXAMPLE_SYM_2</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE; EXAMPLE_SYM_2</DefineConstants>
</PropertyGroup>
</Project>
If desired, the above also includes an example of a potential feature that could allow symbol names to be loaded from separate XML files shared between multiple projects, in addition to storing symbol names directly inside each .csproj file. For example, where it says Unity-Symbols.xml
above, this file could contain a list of the symbols described in the "Platform dependent compilation" page of the Unity3D manual.
Thus the file Unity-Symbols.xml
could contain:
<ConditionalCompilationSymbols>
<ConditionalCompilationSymbol Name="UNITY_EDITOR" Comment="directive to call Unity Editor scripts from your game code." />
<ConditionalCompilationSymbol Name="UNITY_EDITOR_WIN" Comment="directive for Editor code on Windows." />
<ConditionalCompilationSymbol Name="UNITY_EDITOR_OSX" Comment="directive for Editor code on Mac OS X." />
<!-- ... and so forth ... -->
</ConditionalCompilationSymbols>
Note the shared Unity-Symbols.xml
file would NOT store the value/boolean/tick/integer of each symbol, rather it should only store the name of each symbol (and optionally a comment/description). The value/tick of each symbol remains stored in the same place as it is currently stored (the <DefineConstants>
inside each .csproj file).
Unity3D is just a good common example although I don't use it myself. In my case, I'd still be happy if the final decision is to skip the extended idea of loading symbols from separate/shared files. Instead of a shared file, I could copy-and-paste the same <ConditionalCompilationSymbols>
section to multiple .csproj files, because the symbol values/ticks are stored outside of the <ConditionalCompilationSymbols>
section. Thus it is beneficial to store the full list of symbol names in a separate part of the .csproj file than where the symbol values(ticks) are stored (<DefineConstants>
).
For reference and terminology, see also: "#define (C# Reference)"
@mckennabarlow This is something that we should investigate and see what design we will want to do here.
If the additional idea of shared symbol files will also be supported, then here is a potential way of doing it using the preexisting <Import>
element in MSBuild. Firstly, insert one or more <Import>
elements into a ".csproj" file like this:
<Import Project="..\ExampleFolder\Unity-Symbols.props" />
And the "Unity-Symbols.props" file would contain, for example:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ConditionalCompilationSymbols>
<!-- This section determines the symbol names that should appear in the symbol list in the VS GUI, but NOT the value or tick status of each symbol. -->
<ConditionalCompilationSymbol Name="UNITY_EDITOR" Comment="directive to call Unity Editor scripts from your game code." />
<ConditionalCompilationSymbol Name="UNITY_EDITOR_WIN" Comment="directive for Editor code on Windows." />
<ConditionalCompilationSymbol Name="UNITY_EDITOR_OSX" Comment="directive for Editor code on Mac OS X." />
<!-- ... and so forth ... -->
</ConditionalCompilationSymbols>
</PropertyGroup>
</Project>
That's all preexisting stuff except for the <ConditionalCompilationSymbols>
and <ConditionalCompilationSymbol>
elements.
Thus in the same manner as how MSBuild already operates, the new <ConditionalCompilationSymbols>
element could be used in the .csproj file AND in any imported .props files. According to MSBuild, as I understand it, any properties such as the suggested <ConditionalCompilationSymbols>
must be inside an MSBuild <PropertyGroup>
element regardless of whether it is in a .props or .csproj file.
Importantly, the contents of the effective/final symbol list (displayed in VS GUI) should not be obliterated by any subsequent or imported <ConditionalCompilationSymbols>
elements, rather they should be concatenated. Thus if both the .csproj and one or more imported .props files contain <ConditionalCompilationSymbols>
, then the symbols in the .props file(s) should supplement not replace the symbols in the .csproj file.
Re the preexisting <DefineConstants>
property/element, the behavior of <DefineConstants>
would remain unchanged, thus backwards-compatibility is preserved, but luckily this isn't any sacrifice for compatibillity reasons because in any event it's beneficial to store the symbol values/ticks in a different place/element than where the full list of symbol names will be stored.
By the way, currently, any second or third <DefineConstants>
does obliterate/replace all prior <DefineConstants>
elements, and this behavior could remain as-is, but the suggested new <ConditionalCompilationSymbols>
should supplement-not-obliterate any prior <ConditionalCompilationSymbols>
elements.
@verelpode I have a couple of questions, and the answers will help us better understand your needs.
The current UI is based on the assumption that the number of conditional compilation symbols is generally small, and rarely changed.
@tmeschter wrote:
"The current UI is based on the assumption that the number of conditional compilation symbols is generally small, and rarely changed."
I disagree that the current UI assumes that the symbols are rarely changed. The current UI has a tickbox "Define TRACE constant" -- this tickbox implies that TRACE
is frequently changed (or at least more than rarely changed).
In my opinion, the current UI's attitude toward frequent changes is a self-contradictory "Yes but also No" attitude. The TRACE tickbox implies frequent changes but the symbols textbox implies rare changes, thus it's currently self-contradictory.
I'll answer your question anyway. In my opinion, the assumption you mentioned was only valid in the past, or only valid with small simple projects, or perhaps it was never valid even for small projects (considering TRACE
). For example, the Unity3D manual lists 40 symbols, and the MS documentation page "#if (C# reference)" lists 33 symbols (an incomplete list; more than 33 MS symbols exist), and other projects likewise involve 10-40 symbols, and my work also uses approx 20 symbols, yet the current UI is only practical for approx 3 symbols.
EXPERIMENTAL
symbolRe frequency of changes, for example in my previous screenshots I made a symbol named EXPERIMENTAL
, and I realize people might suggest that I create a new Configuration named "Experimental" that defines the symbol EXPERIMENTAL
, and I realize that I could then use the preexisting Config chooser control to frequently and easily switch between Configs named "Experimental", "Debug", and "Release", but this technique is broken. Should a new Config named "Experimental" be configured with settings suitable for debugging or for release? In order to test the symbol EXPERIMENTAL
with both debug and release settings, I'd be forced to create 4 Configurations:
Config Name | Symbols Defined |
---|---|
Debug | DEBUG; TRACE; |
Release | TRACE; |
Experimental Debug | EXPERIMENTAL; DEBUG; TRACE; |
Experimental Release | EXPERIMENTAL; TRACE; |
But now, for example, I also need to test EXPERIMENTAL
with and without WebGL -- the UNITY_WEBGL
symbol described in the Unity3D manual (actually I don't use Unity3D in real life, rather I have a bunch of other symbols, but I'll use Unity3D as an example). So now I'm forced to create 8 Configs:
Config Name | Symbols Defined |
---|---|
Debug | DEBUG; TRACE; |
Release | TRACE; |
Experimental Debug | EXPERIMENTAL; DEBUG; TRACE; |
Experimental Release | EXPERIMENTAL; TRACE; |
Debug with WebGL | UNITY_WEBGL; DEBUG; TRACE; |
Release with WebGL | UNITY_WEBGL; TRACE; |
Experimental Debug with WebGL | UNITY_WEBGL; EXPERIMENTAL; DEBUG; TRACE; |
Experimental Release with WebGL | UNITY_WEBGL; EXPERIMENTAL; TRACE; |
But wait, I also need to test the program with and without the IL2CPP-based script-compiler -- the ENABLE_IL2CPP
symbol also described in the Unity3D manual. So now I'm forced to create 16 Configs -- this is highly unmanageable and messy:
Config Name | Symbols Defined |
---|---|
Debug | DEBUG; TRACE; |
Release | TRACE; |
Experimental Debug | EXPERIMENTAL; DEBUG; TRACE; |
Experimental Release | EXPERIMENTAL; TRACE; |
Debug with WebGL | UNITY_WEBGL; DEBUG; TRACE; |
Release with WebGL | UNITY_WEBGL; TRACE; |
Experimental Debug with WebGL | UNITY_WEBGL; EXPERIMENTAL; DEBUG; TRACE; |
Experimental Release with WebGL | UNITY_WEBGL; EXPERIMENTAL; TRACE; |
Debug with IL2CPP | ENABLE_IL2CPP; DEBUG; TRACE; |
Release with IL2CPP | ENABLE_IL2CPP; TRACE; |
Experimental Debug and IL2CPP | ENABLE_IL2CPP; EXPERIMENTAL; DEBUG; TRACE; |
Experimental Release and IL2CPP | ENABLE_IL2CPP; EXPERIMENTAL; TRACE; |
Debug with WebGL and IL2CPP | ENABLE_IL2CPP; UNITY_WEBGL; DEBUG; TRACE; |
Release with WebGL and IL2CPP | ENABLE_IL2CPP; UNITY_WEBGL; TRACE; |
Experimental Debug with WebGL and IL2CPP | ENABLE_IL2CPP; UNITY_WEBGL; EXPERIMENTAL; DEBUG; TRACE; |
Experimental Release with WebGL and IL2CPP | ENABLE_IL2CPP; UNITY_WEBGL; EXPERIMENTAL; TRACE; |
Add one more symbol and you need 32 Configs.
But wait, I work with multiple .csproj's in real life. I need these 32 Config's in all of the .csproj's. So I'd be forced to spend hours recreating 32 Config's in multiple different .csproj's that all need the same list of 32 Config's (alternatively I could manually edit the XML inside each .csproj file and hope I don't make any mistakes).
See, that doesn't work. What's needed is a listbox of symbols with tickboxes or a "Value" column, so each symbol can be individually enabled or disabled independently of other symbols, frequently and easily. Even better would be if the symbol names list (without values) could be shared between multiple .csproj's (such as via the preexisting MSBuild <Import>
element).
UNFINISHED
symbolIn my previous screenshots I also made a symbol named UNFINISHED
. Often I need to compile and run (with or without VS debugger) the very latest dev-build of the program, but it cannot be compiled because of some unfinished new classes or methods. So I wrap the unfinished portions with #if UNFINISHED
and undefine the UNFINISHED
symbol, and then the app successfully compiles and runs. Later the same day, I need to redefine the UNFINISHED
symbol in order to resume work on the unfinished code sections. Thus frequent changes occur to the enable/disable status of the UNFINISHED
symbol.
I believe it's also important to consider the difference in usage characteristics of class libraries (or nuget packages) versus apps. Developers who develop class libraries and/or nuget packages may well need to change the symbols more frequently than app developers, in order to support multiple variations of the same library or nuget package.
Before publishing a new version of a nuget package, the developer(s) want to test that every variation still works correctly, and this means enabling and disabling symbols each time a new version of the library will be published, i.e. frequently. Again using Unity3D as an example, Unity3D is a class library, so the Unity3D developers would want to frequently enable and disable various combinations of the following symbols, to test Unity3D prior to publishing each new version:
Conditionally-Compiled Feature | Symbol Name |
---|---|
Whether WebGL is supported | UNITY_WEBGL |
Whether Analytics are performed | UNITY_ANALYTICS |
Whether the IL2CPP script-compiler is supported | ENABLE_IL2CPP |
Whether "Unity Editor scripts" scripts are supported | UNITY_EDITOR |
Unity library for .NET Standard | NET_STANDARD_2_0 |
Unity library for .NET Framework 4.6 | NET_4_6 |
Unity library for .NET Legacy | NET_LEGACY |
etc | etc |
etc | etc |
etc | etc |
Whether the UI is excluded (for Console and Server programs) | ... |
Whether the UI is compiled for WPF | ... |
Whether the UI is compiled for WinUI 3 | ... |
Whether the UI is compiled for UWP/WinRT | ... |
Whether the UI is compiled for Xamarin UI | ... |
Whether the UI is compiled for Mono-WinForms | ... |
Visual Studio Version: 16.8.2
Summary
Visual Studio is a truly wonderful product, but some aspects of it cause hassles every week on average. Currently the GUI for Conditional Compilation Symbols in Project Properties window in Visual Studio (16.8.2) is inadequate and a hassle to use, and inconsistent with other parts of Visual Studio. It fails to support large projects that need a larger quantity of symbols. It is unfortunately only a textbox (and narrow), despite the fact that it actually represents a list of values. The current textbox suggests that only a single value should be entered, but in reality it's a case of a textbox being misused as a substitute for a list GUI. Screenshot:
To solve this problem, I suggest that the symbols textbox be replaced with a list GUI like the following mockup for C# projects:
For comparison, see also the preexisting list GUI for "Environment variables:" in the "Debug" panel in Project Properties of an ASP.NET Core project:
C# versus C++ Differences/Considerations
In C#, a symbol can only be enabled or disabled (boolean), whereas in C++, a value can be assigned to a symbol. I began my career using C++ but I mostly stopped using C++ long ago because I noticed that my productivity was much better when I used C#, thus the GUI for C++ projects is unimportant for me, but anyway, for people who still use a lot of C++ code, remember that C++ symbols allow a value to be assigned to each symbol. Thus the GUI for C++ symbols should be like the above "Environment variables" screenshot, whereas the GUI for C# symbols can be a list of tickboxes/checkboxes. Here is a mockup for C++:
Alternatively, you may wish to use the same symbol list GUI for both C# and C++ projects. This is also a good idea. Thus the symbol list would look like the above two-column mockup regardless of whether it is for a C# or C++ project. However, if it is a C# project, Visual Studio would limit the user to entering only a boolean true or false value for each symbol (not an integer value). If it is a C++ project, Visual Studio would allow the symbol value to be true or false or an integer.
#define EXPERIMENTAL
#undef EXPERIMENTAL
(or otherwise not defined)#define XYZ_VERSION 5001
Re Multiple Configurations in a project
Regarding multiple Configurations in a .csproj (such as the "Debug" and "Release" Configurations), the list/names of Conditional Compilation Symbols should be the same for all Configurations, except that the tick/value of the symbols should be different for each Configuration. For example, the symbols
DEBUG
andTRACE
should appear in the symbol list regardless of whether the current Configuration is "Debug" or "Release", but when the "Release" configuration is chosen, theDEBUG
symbol would be shown as unticked or Value=false. Thus when you use the GUI to view a different configuration, only the tick or value of each symbol should change, not the symbol names nor the quantity of symbols.Alternatively, if compatibility reasons or other reasons lead to a final decision to store a completely separate symbol list for each Configuration, then this would still be OK provided it has the ability to enable and disable each symbol without deleting symbols from the list.
Steps to Reproduce
Expected Behavior
Actual Current Behavior
User Impact