How do I make my project depend on other projects via msbuild? - visual-studio

I only have the names/locations of my other projects but I dont want to require the developer to add project dependencies himself.
So my prebuild event needs to add "references" (=anything that makes the current project depend on the other one) via some kind of msbuild magic.
Is that possible?
Desired pseudo code:
<Task Name="MyOwnPrebuild">
<AddProjectDependencies ItemGroup="#MyProjectPaths" />
</Task>
Where I would fill the #MyProjectPaths array by iteratating over my windows folders recursively in some other task before calling this one.

In my specific case there is (luckily!) a stupidly simple, yet beautiful answer:
<ItemGroup>
<ProjectReference Include="..\**\*.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>
This includes all my projects from all(?) subfolders! Exactly what I want in this particular case. If I want to restrict to a specific type of project, I could easily use a more specific regex.

Related

Tooling or method to re-add 'content' to projects utilizing "Package References"

A project (original VS format) is converted to use Package References. Great.
Project References "don't support content". Questionable choice, although not debated here.
Some used packages contain "content" and cannot or will not be updated to use "content files". Hmm..
What semi-automated tooling / method can be used to copy the "content" from NuGet dependencies that "don't support contentFiles"+? Only applying to direct packages is fine.
+Naturally, one could manually open up each individual NuGet file and copy the contents. This question isn't about "why" the switch was made and/or any merits or trade-offs and/or how how packages "should" be authored. The question is about an automated or semi-automated method to be able to restore the "content" into a project's source tree.
It is possible to use "content"-based NuGet packages with original Visual Studio / MSBuild projects that have been converted to use Package References.
The presented solution can likely be amended for SDK-style projects as well. The feat is accomplished by utilizing the GeneratePathProperty attribute of PackageReference+. Using the generated Pkg* path properties ensures valid paths to the referenced packages. (Note: if a package name contains ., replace it with _ in the Pkg* property name.)
First, add a GeneratePathProperty to all the packages with content to copy or link.
<PackageReference Include="bootstrap" GeneratePathProperty="true">
<Version>Don't ask..</Version>
</PackageReference>
<PackageReference Include="AngularJS.Core" GeneratePathProperty="true">
<Version>..it's not great</Version>
</PackageReference>
"Get the Content"
Depending on needs there are two different approaches presented here. While these approaches can be used together, neither approach should be applied to "content files"-based NuGet packages.
Approach #1: Copy Content to Project Source
With this approach it's expected that the files are added to source control. The following lines in the project can thus be un-commented (and re-commented) when updating packages to ensure that the newest NuGet package content.
It uses the Copy Task to, well, copy the the content.
<ItemGroup>
<NugetContentToRestore Include="
$(Pkgbootstrap)\Content\**\*.*;
$(PkgAngularJS_Core)\Content\**\*.*;
" />
</ItemGroup>
<Target Name="BeforeBuild">
<Copy SourceFiles="#(NugetContentToRestore)"
DestinationFiles="#(NugetContentToRestore->'$(ProjectDir)\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
If the ItemGroup is not commented out during normal development, the items will be included in the the project’s solution explorer root, which is a bit ugly. It’s also not possible to detect when "content" has been removed.
Approach #2: Link in Content
If none of the content is to be copied and/or checked into source control along with the project, it can also be linked in. This approach has the benefit of always linking in the latest content and does not pollute the solution explorer.
A local file explicitly included in the project will take precedence over the linked resources.
For a Normal Project (and bin-deployed files)
For a normal project, <CopyToOutputDirectory> is sufficient and the linked files will go in the 'bin' output. However, this does not work for a Web Project which only understands files which are physically present in the project tree.
<ItemGroup>
<Content Include="$(Pkgbootstrap)\Content\**\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(PkgAngularJS_Core)\Content\**\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
For a Web Project (requires local files)
For a Web Project, these links must be copied to local files: <CopyToOutputDirectory> is not sufficient or required. Packages with content that should go into the bin output should use the approach above.
<ItemGroup>
<Content Include="$(Pkgbootstrap)\Content\**\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Content>
<Content Include="$(PkgAngularJS_Core)\Content\**\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Content>
</ItemGroup>
Then add a target to copy the linked files so they are picked up by the Web Project tooling. See Copying linked content files at each build using MSBuild. It may be relevant so skip certain links and add source control ignore entries.
<Target Name="CopyLinkedContentFiles" BeforeTargets="Build">
<Copy SourceFiles="%(Content.Identity)"
DestinationFiles="%(Content.Link)"
SkipUnchangedFiles='true'
OverwriteReadOnlyFiles='true'
Condition="'%(Content.Link)' != ''" />
</Target>
+This works in an up-to-date Visual Studio 2019 environment. It may not work in certain older MSBuild/NuGet configurations as Pkg* property generation is required.
Tooling or method to re-add 'content' to projects utilizing “Package
References”
I am afraid that there is no such tool to make files from content folder into projects with PackageReference nuget management format.
As the user of the package, there is currently no tool to copy the files of the content folder to the projects with PackageReference.
And actually, if there is such a tool, it also violates the mechanism of nuget. All of them are depended on the author of the nuget package rather than the users unless the package is authored by us
Suggestion
So you have to manually unzip the nuget package and copy the files into your project.
And it seems to be a bit complex and if you still want to get a easy way to get what you want, I suggest you could suggest a feature on our User Voice Forum.
And after that, you could share the link here and anyone who is interested in it will vote it so that it will get more Microsoft's attention. All of these will help get what you want as soon as possible.

How do I specify a custom code analysis ruleset for multiple projects in a solution using Visual Studio 2019?

I've got a custom code analysis ruleset that I want to apply to all configurations of multiple projects in my solution but can't see how I can do it.
To be clear, I'm looking for a way (if any) of doing this in a single step rather then editing the properties of each project from the IDE.
I found this guidance so far: https://learn.microsoft.com/en-us/visualstudio/code-quality/how-to-configure-code-analysis-for-a-managed-code-project?view=vs-2019#specify-rule-sets-for-multiple-projects-in-a-solution
But it doesn't seem to be correct. In Visual Studio 2019, if I go to Analyze > Configure Code Analysis > For Solution I get a blank property page with the message:
NOTE: This property page has been deprecated and will be removed in a future product release.
Is there another way I can do this? I have lots of projects :(
Thanks.
I've had no answers about if there's a way to do this in Visual Studio so I've had to resort to altering .csproj files directly in a batch fashion.
This script I found by John Robbins is excellent:
https://www.wintellect.com/batch-updating-changing-visual-studio-projects-with-powershell/
After installation, my usage was like this in case anyone is interested:
dir -recurse *.csproj | Set-ProjectProperties -OverrideDefaultProperties -CustomConfigurationProperties #{ "CodeAnalysisRuleSet" = ".\SystemStandard.ruleset" }
dir -recurse *.csproj | Set-ProjectProperties -OverrideDefaultProperties -CustomConfigurationProperties #{ "RunCodeAnalysis" = "false" }
dir -recurse *.csproj | Set-ProjectProperties -OverrideDefaultProperties -CustomConfigurationProperties #{ "TreatWarningsAsErrors" = "true" }
To specify a rule set for a project, use the CodeAnalysisRuleSet MSBuild property.
To do that, there are several ways you can customize your build
If I understand the question correctly, this can be done quite easily by following the steps outlined in this blog post.
The Directory.Build.props approach
The guide is written with StyleCop in mind but the same step should work with any analyzer.
Create a file named Directory.Build.props (use this exact casing) along with your .sln file, i.e. at the top-level of your project. Its content should be something like this:
<Project>
<PropertyGroup>
<!-- This part specifies the ruleset file name. Change to something
more appropriate if not using StyleCop. -->
<CodeAnalysisRuleSet>$(SolutionDir)StyleCop.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<!-- This part adds StyleCop as a reference in all projects + makes the
top-level stylecop.json file be used by all projects. Skip this
altogether if you are not spefically using StyleCop. -->
<ItemGroup>
<PackageReference Include=”StyleCop.Analyzers” Version=”1.1.1-rc.108" PrivateAssets=”all” />
<AdditionalFiles Include=”$(SolutionDir)stylecop.json” Link=”stylecop.json” />
</ItemGroup>
</Project>
Create a StyleCop.ruleset file with your analyzer configuration.
That's it. The next time you run dotnet build or build your project in Visual Studio (you might have to close/reopen the solution if you have it open), these rules should apply.
For reference, here is the Directory.Build.props file in a project of mine: https://github.com/perlun/perlang/blob/master/Directory.Build.props
If projects in your solution have a hierarchical structure, meaning they reference each other and have a common, most abstract base that is root in the structure similar to this:
Common
├── Worker
└── Persistence
└── API
...then you can reference StyleCop.Analyzers package in your root project and set the value of <PrivateAssets> tag to none:
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>none</PrivateAssets>
</PackageReference>
</ItemGroup>
all is set by default when you add the StyleCop.Analyzers package reference to your project. What does it mean?
You might be using a dependency purely as a development harness and might not want to expose that to projects that will consume your package. In this scenario, you can use the PrivateAssets metadata to control this behavior. ~ Docs
So, by default StyleCop.Analyzers will only be enabled in the project in which you explicitly reference the package. But in almost all code bases I've been part of, enforcing the StyleCode rules in all projects in solution was desired (almost all, except for projects with auto-generated code e.g. EF migrations). Changing all to none in the package reference metadata will result in passing down the styling rules to all projects that depend on it.
Solution
Summarizing, the root project Common will have to reference StyleCop.Analyzers package while setting <PrivateAssets> to none:
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>none</PrivateAssets>
</PackageReference>
</ItemGroup>
and Worker or Persistence or any other dependent project will only have to reference the previous layer, which is common practice in any layered architecture anyway:
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
</ItemGroup>

inject version to DLLs during build process

this is my situation:
I have VS2010 solution with X projects included.
Wix project that can create msi from all compiled artifacts.
I have build machine \ Jenkins that first compile (MSBuild .Net 4) all the solution, then compile the wix to package it to msi.
What\how can I inject to all artifacts\dlls the number of the product (e.g 11.2.0.4789) - as simple as possible?
Is there and command line arguments that can be passed while compiling the solution?
There are tools, such as several extensions for MSBuild, that do version stamping but each assumes a particular workflow. You might find one that works for you but a DIY method would help you evaluate them, even if it isn't your final solution.
You can add a property to the MSBuild command-line like this:
msbuild /p:VersionStamp=11.2.0.4789
Note: I assume you are going to parameterize the Jenkins build in some way or generate the number during a preceding build step. Here is a simulation of that:
echo 11.2.0.4789 >version.txt
set /p version=reading from pipe <version.txt
msbuild /p:VersionStamp=%version%
Now, the work is in getting each project to use it. That would depend on the project type and where you want VersionStamp to appear.
For a csproj, you might want to use it as the AssemblyVersion. The simplest way is to move the attribute to a cs file by itself and rewrite it every time. I would leave a comment in AssemblyInfo.cs as a clue to where it now comes from. You can include the cs file in your project either dynamically or permanently. I prefer dynamically since it is effectively an intermediate file for the build. So, in your .csproj add the following in a text editor (e.g. Visual Studio. Unload and Edit project):
<Target Name="BeforeBuild">
<PropertyGroup>
<AssemblyVersionPath>$(IntermediateOutputDir)AssemblyVersion.cs</AssemblyVersionPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(AssemblyVersionPath)" />
</ItemGroup>
<WriteLinesToFile
File='$(AssemblyVersionPath)'
Overwrite="true"
Condition="'$(ProductVersion)' != ''"
Lines='using System.Reflection%3b;
[assembly: AssemblyVersion("$(VersionStamp)")]' />
</Target>
This is sufficient but a more thorough solution would include adding the file to a list so it is cleaned with other files and only writing the file if the version changed to prevent unnecessary rebuilds, etc.
Use a similar technique for other project types.

Dynamic files include on Visual Studio build process

I got a question about the build process on VS.
I have a DLL Project with 3 .cs files inside. I would like to be able to define which files to include in the compilation process. So I can build a dll file only with file1.cs. Or file2.cs. Or file1.cs and file3.cs.
For now, the only way I know, is to create as many proj file I need and choose manually which one to build.
I would like to know if there are any other way to do it, maybe in the prebuild event you can redefine the files included or not. Maybe some addin exists ?
Thanks.
You can conditionally include files in a project file by putting a condition on the itemgroup.
Open up your csproj in a text editor:
<ItemGroup>
<Compile Include="Program.cs" Condition="$(BuildtypeA)!='True'" />
<Compile Include="Program1.cs" Condition="$(BuildtypeA)!='True'" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
Use whatever condition you like- in this case BuildtypeA. You can pass this in as aproperty on the commandline to msbuild. (or my creating.modifying a build configuration)
You can also use wild cards on a given folder and it will include in the project file what ever is at a given location.
<ItemGroup>
<Compile Include="..\dynamiccontent\*.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
No add-in required.
Not exactly sure why you couldn't just compiles the project with all 3 files all the time, but if you really need to do something like this, you can check out the MSBuild extensions. They support the ability to manipulate an Xml document (which is what a csproj file is) and you can dynamically add/remove the reference to the files as needed. Let me know if you need an example of doing something like that.

MSBuild, include files based on a group of paths

I'm trying to create a build script which would allow me to specify a list of paths to "module projects" that is included in a specific web site.
Hence, I have this layout:
customer folder
|_MainProject
|_ModuleProject1
|_ModuleProject2
So, basically I want to be able to specify an ItemGroup that would contain ModuleProject1 and 2, and copy relevant files from it into the MainProject.
<ItemGroup>
<CustomModule Include="ModuleProject1\*.csproj" />
<CustomModule Include="ModuleProjec2\*.csproj" />
</ItemGroup>
In my main build script I then want to find out the paths to my satellite assemblies, as well as all dlls that reside inside the obj\Release*.dll folder of the project.
I already have item groups for the project file (.csproj), so basically I just want to add some more file references. But I can't figure out how I would do that. This is what I have today:
<Satellites Include="$(ReferencesFolder)\??\*.dll" />
<Satellites Include="$(SiteRoot)\bin\??\*.dll" />
<Satellites Include="%(CustomModule.RelativeDir)obj\$(Configuration)\??\*.dll" />
How would I go about making the last line work based on the facts that I have?
When doing this:
<Message Text="%(CustomModule.RelativeDir)obj\$(Configuration)\??\*.dll" />
It outputs this:
ModuleProject1\obj\Release\??\*.dll
ModuleProject2\obj\Release\??\*.dll
And if anyone has some links to the fundamentals of MSBuild with good examples, I would really appreciate it. I always end up at msdn with some really cryptic examples that doesn't really explain a lot.
EDIT: I revised my plan and almost have it working, however the include doesn't really work as I expect it to. Nothing is included, but there are files mathing the path.
If I manually add this:
<Satellites Include="ModuleProject1\obj\Release\??\*.dll" />
It is actually included in the "Satellites" item group.
Found a solution myself, seems like I needed a workaround for it to work explicitly.
Since I wasn't able to include items based on another item list, this was what I finally came up with:
<Target Name="BuildModules">
<!-- We do this because we need a property with the correct wildcards, otherwise it won't work -->
<PropertyGroup>
<CustomModuleSatellites>#(CustomModule->'%(RelativeDir)obj\$(Configuration)\??\*.dll')</CustomModuleSatellites>
</PropertyGroup>
<ItemGroup>
<Satellites Include="$(CustomModuleSatellites)" />
</ItemGroup>
</Target>
I needed to create a property within a Target (outside of it it didn't fly because it still contained the wild cards), and then use that property ti include the files using wildcards in my item list, otherwise it would be paths with wildcards in it and then the copy command didn't work either.

Resources