How to avoid repeated constant string in .NET AssemblyVersion and AssemblyFileVersion - c#

In a C# project, we increment versions manually and have a top-level file MyAssemblyInfo.cs containing
[assembly: AssemblyVersion("31.1.13")]
[assembly: AssemblyFileVersion("31.1.13")]
[assembly: AssemblyInformationalVersion("31.1.13-V1.0.0")]
I'd like to avoid repeating the version. For example, with the C preprocessor, one could write this as
#define VERSION "31.1.13"
[assembly: AssemblyVersion(VERSION)]
[assembly: AssemblyFileVersion(VERSION)]
[assembly: AssemblyInformationalVersion(VERSION "-V1.0.0")]
Is there a way to achieve this in C# without using any external tools?

[assembly: AssemblyFileVersion(AppVersion.Version)]
[assembly: AssemblyInformationalVersion(AppVersion.Version)]
// you can use + here but the format you use shows as invalid version for me
[assembly: AssemblyVersion(AppVersion.Version)]
// this must be at the end of the global declarations
internal class AppVersion
{
public const string Version = "31.1.13";
}

The short answer to your question is no you cannot use pre-processor directives in this manner. One work-around is stated in the other answer and involves defining a constant. This would be perhaps the only solution for old-style projects.
If you are using the SDK-style project format however, AssemblyInfo attributes can be set in the project file. The following properties correspond to the attributes defined above:
InformationalVersion -> AssemblyInformationalAttribute
AssemblyVersion -> AssemblyVersion
FileVersion -> AssemblyFileVersion
Version -> Can map to any/all of the above if they are omitted
Has special logic to remove suffix patterns when setting File/Assembly version
All other Assembly attributes can be set via properties so long as GenerateAssemblyInfo is true. This requires you to remove your AssemblyInfo.cs file to prevent duplication of those attributes.*
The way I have typically solved this is to define my own set of version-related properties in my project file (see notes below for why). For example, you might have the following:
<PropertyGroup>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<BaseVersion>31.1.13</BaseVersion>
<InfoSuffix>v1.0.0</InfoSuffix>
</PropertyGroup>
<PropertyGroup>
<!-- note: "-v1" is invalid in an Assembly Version; I assumed you meant this on the Informational one -->
<InformationalVersion>$(BaseVersion)-$(InfoSuffix)</InformationalVersion>
<AssemblyVersion>$(BaseVersion)</AssemblyVersion>
<FileVersion>$(BaseVersion)</FileVersion>
</PropertyGroup>
You could pass any of these properties on the command line to MSBuild or dotnet build if you wanted to explicitly override them:
dotnet build -p:InfoSuffix=v2.0+1234
# alternatively, specify the MSBuild properties directly
dotnet build -p:FileVersion=1.2.3.4 -p:InformationalVersion=5.6.7.8
If you have multiple projects you could set all of these properties inside of a Directory.Build.props file so that they apply to all of your projects at once.
Some Notes
First, if you are using the Directory.Build.props mechanism and running any sort of post-processing tasks or something like SourceLink (which appends info to the Informational version) you will need to move the second property group into a Directory.Build.targets file instead. You'd also need to do this if you wanted to pass the properties explicitly to other tooling, such as dotnet pack.
Second, MSBuild has a series of other version-related properties**. These include (but are not limited to):
SourceRevisionId - appended to the Informational version (our InfoSuffix)
VersionPrefix - A Base version
VersionSuffix - Sets prerelease label
PackageVersion - generates nuget version
You can use these in combination with those specified above instead. You can even pass them as properties on the command-line if you wanted to override the values in the project/props/targets files.
Now you might wonder why I bother defining my own properties instead of using the built-in ones. The reason is that MSBuild has some strange and sometimes unexpected behavior depending on how combinations of these properties are set. Anecdotally, I noticed different behavior depending on which values I was setting and whether I was building in VS, on the command line, or using dotnet pack. It is because of this I prefer defining my own properties and then using them to explicitly set the others. It also allowed me to use conditional MSBuild logic to set certain parts of the version easier. Though I do admit, your mileage may vary.
* If you want to keep the AssemblyInfo.cs file for other attributes, you'd have to disable their automatic generation by leveraging the individual GenerateX MSBuild properties.
** For a good explanation of some of these properties, see this blog post.

Related

Can I define constants based on the runtime identifier in .NET Core?

I have a .NET Core Console application. My goal here is to be able to conditionally DLLImport a function and call it, but only on Windows runtimes.
I thought maybe if I could access the runtime identifier in the csproj file, I could conditionally define a constant for that runtime, then in my c# I could surround the DLLImport and calls in #if/#endif blocks.
Is it possible to set compilation constants within a csproj based on the runtime the project is being built for? This is specifically for an SDK-style Project format (that starts with <Project Sdk="Microsoft.NET.Sdk">) that is targeting .NET Core.
Note: this question gets close, but is for project.json style projects.
Alternately, is there a better approach to accomplish my goal?
If you are building and publishing for different runtimes by passing different --runtime options (MSBuild property RuntimeIdentifier), you can condition on that property in the csproj file (allowing you to use #if BUILT_FOR_WINDOWS in your C# code):
<PropertyGroup>
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'win-x64'">$(DefineConstants);BUILT_FOR_WINDOWS</DefineConstants>
</PropertyGroup>
However you can also test the current OS at run time using:
using System.Runtime.InteropServices;
…
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// call windows function here
}
else
{
// do something else here
}
As long as a function marked with [DllImport(…)] is not called on an OS where the library / method cannot be found, there shouldn't be any problems. Do note that DllImport() can also probe for different libraries depending on the os - so DllImport("foo") would check for foo.dll, foo.dylib, libfoo.so etc.
Adding to a Martin Ullrich's answer: if you want to define constants based on RuntimeIdentifier in a referenced library project as opposed to a project with application entry point make sure that you include the list of identifiers which you use in a RuntimeIdentifiers property in the project's .csproj file, for example:
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RuntimeIdentifiers>linux-x64;linux-arm</RuntimeIdentifiers>
</PropertyGroup>
If you don't do it then the constants will not be defined as RuntimeIdentifier property will not be passed to the csproj, as was in my case.
Source: https://github.com/dotnet/core/issues/2678#issuecomment-498967871

AssemblyInformationalVersion attribute behaviour change: deliberate or a bug?

In a C# project built with VS2013, I could put this in the AssemblyInfo.cs file:
[assembly: AssemblyInformationalVersion("7.1.0.0 Private (Debug build)")]
When I used the FileVersionInfo.GetVersionInfo .NET API against the executable from within another project, I found these values reported:
Product version: 7.1.0.0 Private (Debug build)
ProductMajorPart: 7
ProductMinorPart: 1
When I use the same attribute and string value in a C# project built with VS2015, the ProductMajorPart and ProductMinorPart properties are reported as zero!
Does anyone know if the behaviour change is intentional?
I have examined the binary file version information found within the executables, and whilst the string values within the version information are as expected in both files, the VS2015 executable has zero values within the VS_FIXEDFILEINFO.dwProductVersionMS and VS_FIXEDFILEINFO.dwProductVersionLS fields.
I can confirm the change in behavior for this case, with the observation that if the AssemblyInformationalVersion is in canonical format, for example "7.1.0.0" then it works as expected in all versions i.e. the Product version major/minor/build/revision fields are filled in.
For background, the docs for AssemblyInformationalVersion do in fact specify that:
The attribute defined by this class attaches additional version information to an assembly. If this attribute is applied to an assembly, the string it specifies can be obtained at run time by using the Application.ProductVersion property.
[...] Although you can specify any text, a warning message appears on compilation if the string is not in the format used by the assembly version number [...]
From the above:
there is no formal guarantee other than that the string itself can be retrieved;
there is a warning against using free-format strings.
You may, or even should, file a bug report on VS connect though my feeling is that MS sees free-format strings in AssemblyInformationalVersion as an unsupported "accidental" feature, and might not consider a change in undocumented behavior to be a "bug" proper.
Not directly related, but this VS 2010 bug report Localized build with free form AssemblyInformationalVersion causes ALINK warning AL1053 has been closed by MS as won't fix.
Also the accepted answer at Why is warning CS1607 “The version specified for the 'product version' is not in the normal 'major.minor.build.revision' format” generated? basically advises that once you deviate from the standard major.minor.build.revision format, you are pretty much on your own.

Assemblies and Little More In C# (part 1)

If you go to:
Solution Explorer -- > Properties -- >(double click) Assembly Info
you will see some information about assemblies for your project. At the end of it there are some different versions for each assemblies that they are:
Major Version
Minor Version
Build Number
Revision
And I Understood that These are numbers of these:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
but I don't understand the meaning.
When the project is built those values are baked into the dll so that when you view the dll's properties via the Windows File System you will see that version number.
Managing those numbers is a bit of a pain in the ass. In older projects you'll often see some ugly build scripts that do things like check out the files then increment the number then check them in then continue with the build... The most elegant solution I've seen is implemented by TeamCity (though there are probably similar products); it basically copies all of the projects files to your build server, then it edits it's local copy with values it maintains (you can alter or reset them in UI), then builds the project. This allows it to never touch source control while giving you good control over dll versioning.
They are whatever you want them to mean. You are free to use your own definitions for each section of the version number; beyond the fact that different numbers are different, there is no functionality driven off of this by the language.
AssemblyVersion is used in the strong name of the assembly(signing).
AssemblyFileVersion is displayed by Windows in the Version tab on the file properties.
AssemblyInformationalVersion is used in the assembly manifest for things like NuGet.
As far as how to version, I recommend Semantic Versioning, which uses a 3-part version number:
Given a version number MAJOR.MINOR.PATCH, increment the:
1. MAJOR version when you make incompatible API changes,
2. MINOR version when you add functionality in a backwards-compatible manner, and
3. PATCH version when you make backwards-compatible bug fixes.
AssemblyVersion used for strong name of the assembly with sn.exe, but
AssemblyFileVersion display the version on the file properties.

Changing Conditional Compilation in project reference in Visual Studio

I have a class library A that is used in other projects in my solution like B and C.
Class library A behaves differently based on the presence of a pre-processor directive, for example :
#if some_directive
// some code
#else
// some other code
#end
How can I use class library A in project B with enabled some_directive but use in project C with disabled some_directive?
You can do something like this using the ConditionalAttribute
This is how Debug.WriteLine() is present or non-present depending on presence of the "DEBUG" symbol.
You will be able to define your own name for the conditional symbol that you use to control the presence or absence of the code.
You can then put that symbol into the list in the "Conditional compilation symbols" settings on the project properties "Build" tab for the project where you want the code.
This doesn't allow you to have the "some other code" part unfortunately, and also it only applies to entire methods.
It seems that currently this feature is not supported. According to this post:
The language doesn't support the notion of references via preprocessor
macros.
What you can do is use a msbuild file and alter the set of references
added based on msbuild parameters.
Another workaround which I used was using solution configuration in "Configuration Manager". I created two configurations for building each projects B or C which preprocessor directive is enabled in only one of these configurations.
I Know this topic is old - for me the following approach works nicely and might fit as well:
comments of (dis)advantages of this approach are welcome.
If you add your .cs-file as existing Item as a link to each project - you can compile it with different directives
For adding an existing item as a linked file see screenshots at this post
Use folders to organize linked files.
// file ClassA.cs
namespace HelperClasses
{
public ClassA
{
#if some_directive
// some code
#else
// some other code
#end
// ....
}
}
// using statement in Project B and C
using HelperClasses
// Add ClassA.cs in both Projects B and C
// as exiting, linked File -- not as a Reference
// set the compiler-Directives according your needs

Build error under Mono when moving AssemblyVersion attribute to a separate file

I have a C# library project with a build task that writes the current SVN revision into a file named Version.cs. The resulting file contains just this single line:
[assembly: System.Reflection.AssemblyVersion(0.0.0.1325)]
I've removed the pre-existing AssemblyVersion attribute from the AssemblyInfo.cs file.
When compiling using Mono, the gmcs compiler doesn't like this - it throws the following parse error:
'value' is invalid attribute target. All attributes in this attribute section will be ignored
Any ideas why this is happening? Surely AssemblyInfo.cs isn't some sort of "special" file - I'd rather not have to write the version attribute into that...
I haven't tried it under Windows/VS.NET yet (it needs to be able to compile on both platforms).
Crap, nevermind, I hadn't quoted the version string (should be [assembly: System.Reflection.AssemblyVersion("0.0.0.1325")]...

Categories

Resources