I have some content files that I would like to share between a number of projects in Visual Studio.
I have put these files in their own project, set the build action to "Content", and the copy to output directory to "Copy if newer". I would like all these files to be copied to the bin/debug directory of the projects that reference them.
I can get it to work by including a reference to the "contents" project in each of the projects that need the files, but that requires that a minimal assembly be generated (3K). I assume there is a way, using MSBuild, to make this all work without creating the empty assembly?
Thanks to everone who took the time to make a suggestion about how to solve this problem.
It turns out that if I want my compiled content files to be treated like content files (in that they get copied to the output directory of any other project that references my project), I need to create a target which runs before GetCopyToOutputDirectoryItems, and add the full path of the compiled content files to the AllItemsFullPathWithTargetPath ItemGroup. MSBuild calls GetCopyToOutputDirectoryItems for projects on which the current project depends, and uses the resulting file list to determine the files that are copied along with the assembly.dll. Here is the XML from my .csproj, just in case someone else has a similar problem.
I have a custom task called "ZipDictionary", and I accumulate all the files that I am going to compile in an ItemGroup called DictionaryCompile. My target, "FixGetCopyToOutputDirectoryItems" is executed before "GetCopyToOutputDirectoryItems". I don't do the actual compilation there, since this target can be called multiple times by referencing projects, and it would hurt performance. The target does some transforms to get the post-compilation file names, and then returns the full paths to all the files, since relative paths will not work when copy is called from the referencing project.
<ItemGroup>
<DictionaryCompile Include="Dictionaries\it-IT.dic">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</DictionaryCompile>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<UsingTask TaskName="ZipDictionary" AssemblyFile="..\LogicTree.DictionaryCompiler\bin\Debug\LogicTree.DictionaryCompiler.dll"/>
<Target Name="BeforeCompile">
<Message Text="Files #(DictionaryCompile)" Importance="high" />
<ZipDictionary DictionaryFiles="#(DictionaryCompile)" OutputDirectory="$(OutputPath)">
<Output TaskParameter="OutputFiles" ItemName="DictionaryOutputFiles" />
</ZipDictionary>
</Target>
<Target Name="FixGetCopyToOutputDirectoryItems" BeforeTargets="GetCopyToOutputDirectoryItems">
<ItemGroup>
<_DictionaryCompile Include="#(DictionaryCompile->'$(OutputPath)Dictionaries\%(FileName).ltdic')" />
</ItemGroup>
<AssignTargetPath Files="#(_DictionaryCompile)" RootFolder="$(MSBuildProjectDirectory)\$(OutputPath)">
<Output TaskParameter="AssignedFiles" ItemName="_DictionaryCompileWithTargetPath" />
</AssignTargetPath>
<ItemGroup>
<AllItemsFullPathWithTargetPath Include="#(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'" />
<_SourceItemsToCopyToOutputDirectoryAlways Include="#(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='Always'" />
<_SourceItemsToCopyToOutputDirectory Include="#(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'" />
</ItemGroup>
</Target>
A better possible solution would be to
place a common directory in the solution dir and place your common content files there.
in VS, in each project that should share this content, right-click add existing item, browse to the desired item(s), select, click the down-arrow on the add button and select add as link. In the project, you will notice the files are added with a 'shortcut' overlay.
In the project, select the newly added links and right-click->properties and select Build Action: content, Copy To Output Directory: Copy Always.
This is a simple solution to the problem given.
I use this technique for things like SQL scripts and partial config files (using configSource) with great success. This allows me to make changes to these files in a single location with the assurance that they will be propigated throughout the solution.
A more robust solution would be to create a project with embedded resources. This requires a bit more work to manage the content on the receiving end but may be worth it in the long run as having a bunch of loose artifacts flying about can become problematic.
Hope that helps.
A similar solution like the one Sky suggested can be found in my answer to "Is there a way to automatically include content files into asp.net project file?".
It allows to share your content but you must not touch the folder or its content inside VS because this breaks the recursive path.
This approach works best for auto-generated content - you don't have to bother about including new content files to your solution.
And of course you can reuse this in multiple solutions/projects.
We do something similar where we have "...ReleaseBuilds" that reference dlls and content we require for specific projects. Compiling copies everything to the bin debug folder and indeed creates the empty assembly.
Within Visual Studio we have a post-build event in the "...RealeaseBuild" (in project properties) that copies/deletes or run batch files to make sure we have all the files (configs, services etc etc) required and to delete the empty assembly.
HTH
Related
I apologize if this is trivial, but I'm not a regular VS user and my Google-Fu is turning up nothing obvious or simple.
I have inherited responsibility for a large (500k+ LOC, a dozen solutions, hundreds of projects) repository that's been forked a number of times in the past. The solution/project structure is... spaghetti-esque, in that the filesystem folder structure and the solution/project structure are only weakly correlated, and many projects import/reference other projects outside the filesystem folder hierarchy of their containing solution, and that are not even part of the containing solution.
For example:
c:\SolutionA\SolutionA.sln contains c:\SolutionA\ProjectB.csproj and c:\SolutionA\ProjectC.csproj. But C:\solution\ProjectC.csproj contains a <Import Project="..\SomeOtherRandomSolutionDir\ProjectD.csproj" /> reference.
I know there are a lot of projects/files/resources in this repo that are not used by any of the solutions I'm actually building and I don't need them, but the tentacular nature of the project imports/references makes it hard to determine what's actually necessary for the builds and what's superfluous.
So: is there any relatively simple way to run a solution build in Visual Studio (or MSBuild) and obtain a list of every single file used by the build process? I've tried creating a diagnostic-level build log and grepping[1] it for the repo base path; will that get me what I want? (Narrator: it won't)
EDIT: Assume that all file operations are done entirely by default Visual Studio solution project handling and there's no custom targets or shelling out to copy or move files, in the way Perry Qian describes below
[1] Well, Get-Content | Select-String-ing it, but that's clunkier to say
is there any relatively simple way to run a solution build in Visual
Studio (or MSBuild) and obtain a list of every single file used by the
build process? I've tried creating a diagnostic-level build log and
grepping[1] it for the repo base path; will that get me what I want?
Sorry but I'm afraid this is not supported scenario. You cannot obtain a list of every single files that are used in a project or a solution during build process.
Let me explain it more detailed:
Usually the files which are in the solution explorer are all useful in the project. Since your solution is too large and logically complex, we do not recommend deleting any of the files, and I think they all work.
We can obtain a list of files which are parts of the input items of the projects by MSBuild(usually in <Itemgroup> node of the xxx.csproj file).This is the only way I can think of to get a set of project files through MSBuild. We can add this target into xxx.csproj to list all of them like this:
<Target Name="ShowSingleProjectItemList" AfterTargets="Build">
<Message Importance="high" Text="None file:#(None)---Compile files:#(Compile)---Content files:#(Content)---Embedded Resource files:#(EmbeddedResource)---CodeAnalysisDictionary files:#(CodeAnalysisDictionary)---ApplicationDefinition files:#(ApplicationDefinition)---Page files:#(Page)---Resource files:#(Resource)---SplashScreen files:#(SplashScreen)---DesignData files: #(DesignData) Reference dlls :#(Reference)">
</Target>
Note that this method can only be used for each project and not for the entire solution so if you want to use, add it into every xxx.csproj file.
But for other files which are not as the input items of the projects and added or referenced in the projects by some CMD commands or powershell scripts, build events(Right-click on Project-->Properties-->Build Events)(You can refer to this) and any other custom target in the xxx.csproj,we cannot list all of them by a function.
For example, if you use powershell to do some copy operation like coping some dlls from the path outside of your solution into projects,they can't stay in the project as an item of the project. So we cannot obtain them by MSBuild.
For this situation, we can only manually view all of them that are imported into the projects in whatever way in the diagnostic-level build log.
Conclusion
As input items of the projects, we can get the required files for each project by MSBuild, but for some other operations(powershell,build events,etc) to add files from other path outside into the current project,we cannot retrieve all of their information by a method. You can only look it up one by one by diagnostible-level build log.
Besides,we don't know the structure and logic of the entire solution, so we can't guarantee that every file is an item element, so for now you have to look at it manually.
Update 1
To avoid adding every target into your xxx.csproj(since you have a lot of projects under a solution), you can try to use Directory.Build.props. You just write the custom target into this file and then put the file under your solution. After that, when you build the solution, the build will execute into every project so that you just have to write it once.
Solution
1) create a file namedDirectory.Build.props under the solution
2) write these info into the file
<Target Name="ShowSingleProjectItemList" AfterTargets="Build">
<Message Importance="high" Text="None file:#(None)---Compile files:#(Compile)---Content files:#(Content)---Embedded Resource files:#(EmbeddedResource)---CodeAnalysisDictionary files:#(CodeAnalysisDictionary)---ApplicationDefinition files:#(ApplicationDefinition)---Page files:#(Page)---Resource files:#(Resource)---SplashScreen files:#(SplashScreen)---DesignData files: #(DesignData) Reference dlls :#(Reference)">
</Target>
3) build your solution and you will find the files in the build output window.
We have a very large number of solutions spread across a wide number of repositories which do not always share a directory hierarchy in a way that makes it easy for us to update an .editorconfig such that it applies to all projects/solutions in the organization. We currently apply all of our code analysis configuration via an internal NuGet package and I was hoping we could include our organization-wide .editorconfig settings in this way as well?
I tried a quick experiment adding the following to a project to see if linked files would be honored (since we could simply add this to a props file we already have in the NuGet package), but it does not appear to be honored currently.
<ItemGroup>
<None Include="C:\SomeAlternatePath\ECTest\.editorconfig" Link=".editorconfig" />
</ItemGroup>
Is there some other MSBuild property or mechanism we could use to better facilitate this without literally writing a duplicate file to every solution/project/repo?
Is there some other MSBuild property or mechanism we could use to
better facilitate this without literally writing a duplicate file to
every solution/project/repo?
I'm afraid the answer is negative. Cause the .editorconfig file have nothing to do with msbuild or xx.csproj. Only file hierarchy can affect the behavior how the config file works. More details please check this document.
Some tests:
When I right-click project=>add .editorconfig to add this file in current project, there's one line added to the xx.csproj: <None Include=".editorconfig"/>.
If we set the indent_size = 32, it works for current project. Now we can right-click that file=>Exclude from Project to remove that file from current project system. (This action will remove the <None Include=".editorconfig"/> in xx.csproj, but the file is still in the same folder where xx.csproj exists)
Now reload the project, the settings(indent_size=32) still works. So it's obvious if we place this file in project directory, then it will take effect, no matter if we have definitions about it in project file(xx.csproj).
Suggestions:
According to your description, all your projects use the same .editorconfig file. Since this file's working scope is affected by file hierarchy, you can reduce some meaningless work by:
1.Place that file in Solution folder, it will work for all projects under that solution folder
2.Place that file in repos(C:\Users\xxx\source\repos) folder, it will work for all solutions and projects under this folder.
3.So if most of your solutions are under path C:\somepath, place that file here, all projects under that path will benefit from that. And about precedence in file hierarchy please see this one.
Hope all above makes some help :)
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.
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!!
I have two versions of a project. One for Silverlight and one for .NET. The SL project has the vast majority of the code base in it. I want to globally add all files from the SL project into the .NET version as linked files. I've managed to do so successfully like this in the csproj file for the .NET version:
<Compile Include="..\MyProj.Common.SL\**\*.cs" Exclude="..\MyProj.Common.SL\Properties\**">
Unfortunately, this adds all the files right to the root of my project... so I end up with a long unreadable list of linked files in the .NET project. I really really really don't want to have to maintain an entire duplicate directory structure by hand and deal with directory name changes and file name changes and whatnot.
So, is there any way to have Visual Studio preserve the directory structure when adding linked files in the wildcard manner above? Or is there at least a way of making it group all the linked files together under a directory in the .NET project like MyProj.Common.SL.Links?
The very closest I've come is to set the <Visible>false</Visible> under the <Compile> tag, which effectively removes the long unreadable list of 300+ files....but unfortunately this screws up Resharper, which no longer sees those files as valid and it goes crazy on all the projects that reference the .NET project. If I could figure out a way of making Resharper not get all messed up, that would be an acceptable solution too...
Any suggestions?
Thanks.
I think I found a way of getting this to work:
<Compile Include="..\MyProj.Common.SL\**\*.cs" Exclude="..\MyProj.Common.SL\Properties\**">
<Link>MyProj.Common.SL.LinkedFiles\MyProj.Common.SL.LinkedFiles</Link>
</Compile>
It will create a MyProj.Common.SL.LinkedFiles folder and group all the linked files under that folder.
I think I would do this:
Copy the existing project's <Compile> items, which presumably have e.g. Include="foo.cs" and Include="Folder\bar.cs"
Paste them into the new project
Search and replace <Compile Include="(.*?)" /> with <Compile Include="..\Other\$1" ><Link>$1</Link></Compile>
I don't know if I got the regular expression search and replace syntax exactly right, but the point is, you already have a good project, you should be able to cut, paste, regex-replace it to get the same set of files, only referenced from a different folder, and with the same directory structure.
You'll still have two .csproj's to maintain at this point, but this is also easily fixed. Now take this new list of compile items, and put it in a file named e.g. "Common.csproj" that just contains the property group with those compile items, and then have both projects <Import Include="..\Common.csproj" /> and not include any Compile items of their own.
Basically, a little manual labor to refactor the .csproj file for sharing once, and then I think you'll be set. I am not sure if this is the 'easiest' way to unblock you, but I think this sounds approximately like what you may want for an 'ideal' structure.