Dynamic files include on Visual Studio build process - visual-studio

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.

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 make my project depend on other projects via msbuild?

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.

Getting T4-generated .resx files to build

In our web application, I'm co-opting the iu-Cans-CA locale as a pseudo-locale where string resource keys are used as the values as well. This is to provide an aid for automation testing. To generate the .resx for this locale, I'm using a T4 template.
The .resx is generated correctly, but something's wrong: the resource file isn't being compiled into the assembly like the other real translations are. I finally figured out the cause. Because the T4 template is used to generate the .resx, the .csproj contains this:
<EmbeddedResource Include="Strings.iu-Cans-CA.resx">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Strings.iu-Cans-CA.tt</DependentUpon>
</EmbeddedResource>
For some reason, this is preventing the .resx from compiling. If I update the .csproj to this, it does compile:
<EmbeddedResource Include="Strings.iu-Cans-CA.resx">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</EmbeddedResource>
I was hoping this would be the end of it, but now whenever the .csproj is loaded in Visual Studio, the project file is once again updated to the former structure, breaking the compilation of the resource file again.
Is there a way to fix things so that the generated/dependent .resx properly compiles as an embedded resource? Or is there a way to permanently break the dependency relationship so it doesn't keep getting restored? I realize I can rewrite the .tt so it generates a file with a different name, but I'd prefer finding a solution that doesn't require this since that is an atypical usage of T4 and I want to avoid too many WTFs.
The workaround is to strip the DependentUpon metadata from the T4 generated EmbeddedResource elements before the build really kicks off. The following modification of the BeforeBuild target in my project file works for me:
<Target Name="BeforeBuild">
<!-- Fix MSBuild issue where T4 generated .RESX files do not get compiled properly into satellite assemblies -->
<ItemGroup>
<GeneratedEmbeddedResource Include="#(EmbeddedResource)" Condition="'%(EmbeddedResource.DependentUpon)' != ''">
<DependentUpon></DependentUpon>
</GeneratedEmbeddedResource>
<EmbeddedResource Remove="#(EmbeddedResource)" Condition="'%(EmbeddedResource.DependentUpon)' != ''"/>
<EmbeddedResource Include="#(GeneratedEmbeddedResource)" />
</ItemGroup>
</Target>
You need to specify an output path for the .resx file.
If you leave Output.Project empty and don't specify a directory/path in Output.File, the generated file will be DependentUpon the .tt file itself.

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.

Using Microsoft AJAX Minifier with Visual Studio 2010 1-click publish

The Microsoft AJAX Minifier provides a build task which can be used in TFS or local build definitions.
I have succsfully used this in both a local project file (building to seperate files) and in TFS build definitions (overwriting the existing JS files).
I'd like to move to using Visual Studio 2010's 1-click publish feature rather than a TFS build definition, however adding the minification task as an AfterBuild target in the project file doesn't appear to effect the 1-click publish feature.
Using information found in this thread and these articles, I tried creating a file named '[ProjectName].wpp.targets in my WAP root directory, and used the following XML:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\Microsoft\MicrosoftAjax\ajaxmin.tasks" />
<Target Name="Minify" BeforeTargets="MSDeployPublish">
<ItemGroup>
<JS Include="**\*.js" Exclude="**\*.min.js;**\*vsddoc.js;**\*debug.js" />
</ItemGroup>
<ItemGroup>
<CSS Include="**\*.css" Exclude="**\*.min.css" />
</ItemGroup>
<AjaxMin JsSourceFiles="#(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".min.js" CssSourceFiles="#(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".min.css" />
</Target>
</Project>
This doesn't appear to have any effect, and unfortunately Visual Studio doesn't give much in the way of feedback or debugging info for these tools.
Has anyone had any success minifying JavaScript / CSS using the Visual Studio 2010 1-click publish feature?
I just wrote a detailed blog entry on how to do this at
http://sedodream.com/2011/02/25/HowToCompressCSSJavaScriptBeforePublishpackage.aspx and http://blogs.msdn.com/b/webdevtools/archive/2011/02/24/how-to-compress-css-javascript-before-publish-package.aspx.
Here are the contents
Today I saw a post on stackoverflow.com asking Using Microsoft AJAX Minifier with Visual Studio 2010 1-click publish. This is a response to that question. The Web Publishing Pipeline is pretty extensive so it is easy for us to hook in to it in order to perform operation such as these. One of those extension points, as we’ve blogged about before, is creating a .wpp.targets file. If you create a file in the same directory of your project with the name {ProjectName}.wpp.targets then that file will automatically be imported and included in the build/publish process. This makes it easy to edit your build/publish process without always having to edit the project file itself. I will use this technique to demonstrate how to compress the CSS & JavaScript files a project contains before it is published/packaged.
Eventhough the question specifically states Microsoft AJAX Minifier I decided to use the compressor contained in Packer.NET (link in resources section). I did this because when I looked at the MSBuild task for the AJAX Minifier it didn’t look like I could control the output location of the compressed files. Instead it would simply write to the same folder with an extension like .min.cs or .min.js. In any case, when you publish/package your Web Application Project (WAP) the files are copied to a temporary location before the publish/package occurs. The default value for this location is obj{Configuration}\Package\PackageTmp\ where {Configuration} is the build configuration that you are currently using for your WAP. So what we need to do is to allow the WPP to copy all the files to that location and then after that we can compress the CSS and JavaScript that goes in that folder. The target which copies the files to that location is CopyAllFilesToSingleFolderForPackage. (To learn more about these targets take a look at the file %Program Files (x86)%\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets.) To make our target run after this target we can use the MSBuild AfterTargets attribute. The project that I created to demonstrate this is called CompressBeforePublish, because of that I create a new file named CompressBeforePublish.wpp.targets to contain my changes.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="SmallSharpTools.Packer.MSBuild.Packer"
AssemblyFile="$(MSBuildThisFileDirectory)..\Contrib\SmallSharpTools.Packer\SmallSharpTools.Packer.dll" />
<!-- This target will run after the files are copied to PackageTmp folder -->
<Target Name="CompressJsAndCss" AfterTargets="CopyAllFilesToSingleFolderForPackage">
<!-- Discover files to compress -->
<ItemGroup>
<_JavaScriptFiles Include="$(_PackageTempDir)\Scripts\**\*.js" />
<_CssFiles Include="$(_PackageTempDir)\Content\**\*.css" />
</ItemGroup>
<Message Text="Compressing JavaScript files" Importance="high" />
<!--
Compress the JavaScript files.
Not the usage of %(JavaScript.Identity which causes this task to run once per
.js file in the JavaScriptFiles item list.
For more info on batching: http://sedotech.com/resources#Batching
-->
<Packer InputFiles="%(_JavaScriptFiles.Identity)"
OutputFileName="#(_JavaScriptFiles->'$(_PackageTempDir)\Scripts\%(RecursiveDir)%(Filename)%(Extension)')"
Mode="JSMin"
Verbose="false"
Condition=" '#(_JavaScriptFiles)' != ''" />
<Message Text="Compressing CSS files" Importance="high" />
<Packer InputFiles="%(_CssFiles.Identity)"
OutputFileName="#(_CssFiles->'$(_PackageTempDir)\Content\%(RecursiveDir)%(Filename)%(Extension)')"
Mode="CSSMin"
Verbose="false"
Condition=" '#(_CssFiles)' != '' "/>
</Target>
</Project>
Here I’ve created one target, CompressJsAndCss, and I have included AfterTargets=”CopyAllFilesToSingleFolderForPackage” which causes it to be executed after CopyAllFilesToSingleFolderForPackage. Inside this target I do two things, gather the files which need to be compressed and then I compress them.
1. Gather files to be compressed
<ItemGroup>
<_JavaScriptFiles Include="$(_PackageTempDir)\Scripts\**\*.js" />
<_CssFiles Include="$(_PackageTempDir)\Content\**\*.css" />
</ItemGroup>
Here I use an item list for both JavaScript files as well as CSS files. Notice that I am using the _PackageTempDir property to pickup .js & .css files inside the temporary folder where the files are written to be packaged. The reason that I’m doing that instead of picking up source files is because my build may be outputting other .js & .css files and which are going to be published. Note: since the property _PackageTempDir starts with an underscore it is not guaranteed to behave (or even exist) in future versions.
2. Compress files
I use the Packer task to compress the .js and .css files. For both sets of files the usage is pretty similar so I will only look at the first usage.
<Packer InputFiles="%(_JavaScriptFiles.Identity)"
OutputFileName="#(_JavaScriptFiles->'$(_PackageTempDir)\Scripts\%(RecursiveDir)%(Filename)%(Extension)')"
Mode="JSMin"
Verbose="false"
Condition=" '#(_JavaScriptFiles)' != ''" />
Here the task is fed all the .js files for compression. Take a note how I passed the files into the task using, %(_JavaScriptFiles.Identity), in this case what that does is to cause this task to be executed once per .js file. The %(abc.def) syntax invokes batching, if you are not familiar with batching please see below. For the value of the output file I use the _PackageTempDir property again. In this case since the item already resides there I could have simplified that to be #(_JavaScriptFiles->’%(FullPath)’) but I thought you might find that expression helpful in the case that you are compressing files which do not already exist in the _PackageTempDir folder.
Now that we have added this target to the .wpp.targets file we can publish/package our web project and it the contained .js & .css files will be compressed. Note: Whenever you modify the .wpp.targets file you will have to unload/reload the web project so that the changes are picked up, Visual Studio caches your projects.
In the image below you can see the difference that compressing these files made.
You can download the entire project below, as well as take a look at some other resources that I have that you might be interested in.
Resources
http://sedotech.com/content/samples/CompressBeforePublish.zip
http://sedotech.com/resources#batching
MSBuild BeforeTargets/AfterTargets
WebDeploymentToolMSDeployHowToExcludeFilesFromPackageBasedOnConfiguration.aspx
Packer.NET
For this to work in visual studio 2015, we have to change the "AfterTarget" from
<Target Name="CompressJsAndCss" AfterTargets="CopyAllFilesToSingleFolderForPackage">
to the following
<Target Name="CompressJsAndCss" AfterTargets="PipelineCopyAllFilesToOneFolderForMsdeploy">
enjoy!!

Resources