After looking all over the Google I found a good way to build a solution. However the solution I want to build also contains unit test projects, which I don't want to include in the build, or if I can't prevent that at least put those binaries in a separate folder. The code is as follows:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
public class BuildSolution
{
private readonly string _solutionPath;
private readonly string _outputPath = "C:\\Temp\\TestBuild\\";
public BuildSolution(string solutionPath, string outputPath = null)
{
if (!string.IsNullOrEmpty(outputPath))
_outputPath = outputPath;
_solutionPath = solutionPath;
Directory.EnumerateFiles(_outputPath, "*", SearchOption.AllDirectories)
.Select(x => new FileInfo(x))
.ToList()
.ForEach(x => x.Delete());
}
public void Build()
{
var pc = new ProjectCollection();
var globalProps = new Dictionary<string, string>()
{
{ ProjectPropertyNames.Configuration, "Debug" },
{ ProjectPropertyNames.OutputPath, _outputPath },
{ ProjectPropertyNames.EnableNuGetPackageRestore, "true"},
};
var targetsToBuild = new[] { "Build" };
var buildRequest = new BuildRequestData(_solutionPath, globalProps, null, targetsToBuild, null);
var buildParams = new BuildParameters(pc);
buildParams.Loggers = new List<ILogger>() { new ConsoleLogger(LoggerVerbosity.Minimal) };
var buildManager = BuildManager.DefaultBuildManager;
buildManager.BeginBuild(buildParams);
var buildSubmission = buildManager.PendBuildRequest(buildRequest);
buildSubmission.ExecuteAsync(BuildCompleted, null);
while (!done)
{
Thread.Sleep(10);
}
buildManager.EndBuild();
Console.WriteLine("OverallResult:{0}", buildSubmission.BuildResult.OverallResult);
}
bool done = false;
private void BuildCompleted(BuildSubmission submission)
{
done = submission.IsCompleted;
}
/// <summary>
/// Unused, but I tried it and it gives me back the correct projects but the build fails because of dependant nuget packages
/// </summary>
/// <param name="path">path of solution</param>
/// <returns></returns>
private IEnumerable<FileInfo> GetFirstLevelProjects(string path)
{
foreach (var dir in Directory.EnumerateDirectories(path))
{
foreach (var file in Directory.EnumerateFiles(dir, "*.csproj"))
{
if (!file.Contains("Test"))
yield return new FileInfo(file);
}
}
}
}
nothing fancy about it. (I'm playing with the idea of making the build async so I can update status...we'll see about that, I might switch it back to sync). One thing I tried was that instead of putting the solution in the build request, I would build the project collection using the first level projects (I use git with sub-modules, so I don't want to build all the non-relevant sub-modules). The problem with that route was that the build would fail because of nuget packages (not sure why or how to get around that). When I build the solution it builds successfully, but my outputPath also includes the test binaries. My end game is that the output can get copied to a specific folder of mine. I wouldn't mind having the test binaries if I knew I could filter ALL the binaries that are in the test projects... So how? What options do I have?
Why Not MsBuild?
The easiest way that I can think of to do this is to use MsBuild to do it.
msbuild C:\myFolder\mySolution.sln /p:Configuration=Release
As for not building the tests, this could now be easily changed from within visual studio
Right Click on Solution > Properties > Configuration Properties (on left side)
From there you could switch to release mode and uncheck the box next to your test projects. This will tell visual studio that when it does a release build it can skip these projects.
Without MsBuild
However, if you wanted to keep building them in the manner that you showed, I would change the assembly name (the dll name) so that you could identify them easily in your favorite scripting language.
Right Click on Project > Application Tab (on left side) > Assembly Name Box
I would call them something like SolutionNameSpace.Tests.ProjectUnderTest.dll.
Then in your build process you can filter out SolutionNameSpace.Tests.*.dll. Just be carefull, if you reference testing libraries they could get copied to your output folder also.
Related
Is it possible, from code, to get some project's output path from its vb/csproj alone? I am using .Net framework (4.8). I could not find anything exactly matching what I am looking for. There is https://learn.microsoft.com/en-us/dotnet/api/microsoft.build.evaluation.project.getpropertyvalue?view=msbuild-17-netcore but this isn't available in the .Net framework.
EDIT
I am coding an output builder, which takes targeted assemblies of a solution projects and copy them in a specified location.
The code below uses MSBuild to get the properties of a csproj file specified as a string, and retrieves the current TargetPath. If you create a Console App in .NET Framework 4.8 it will get the output library path of a .NET Framework project.
using Microsoft.Build.Evaluation;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PathGetter
{
internal class Program
{
static void Main(string[] args)
{
string testCsproj = #"C:\Dotnet\WpfControlLibrary1\WpfControlLibrary1\WpfControlLibrary1.csproj";
string result = GetProperty(testCsproj, "TargetPath");
Console.WriteLine(result);
}
public static string GetProperty(string csproj, string propertyName)
{
using (var collection = new ProjectCollection())
{
var project = new Project(csproj, new Dictionary<string, string>(),
null, collection, ProjectLoadSettings.Default);
return project.Properties.Where(p => p.Name == propertyName)
.Select(p => p.EvaluatedValue).SingleOrDefault();
}
}
}
}
I'm trying to build a simple program to static code analysis with provided package from Microsoft. So I wan to open solution then open projects via code then get all the document and analyze with my methods.
This is my method, I'm calling somewhere else, and I double checked solution path is correct. You can see my namespaces that I used.
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.MSBuild;
public class Fixer
{
public Fixer(string solutionPath)
{
using (var workspace = MSBuildWorkspace.Create())
{
_solution = workspace.OpenSolutionAsync(_solutionPath).Result;
}
}
}
After that I want to
public IEnumerable<Document> GetDocuments(Solution solution)
{
foreach (var projectId in solution.ProjectIds)
{
var project = solution.GetProject(projectId);
foreach (Document document in project.Documents)
{
if (document.SupportsSyntaxTree)
yield return document;
}
}
}
public IEnumerable<MethodDeclarationSyntax> GetMethods(IEnumerable<Document> documents)
{
return documents.SelectMany(p => p.GetSyntaxRootAsync().Result.DescendantNodes().OfType<MethodDeclarationSyntax>());
}
Main problem is its not open solution at all. When I checked workspace object. There is diagnostic property and says
{[Failure] Cannot open project '.\RunningDiagnostics.csproj' because the language 'C#' is not supported.}
For all project in that solution.
I tried to update nuget packages that I used. Nothing happened. What is the main problem ?
And guidance will be appreciated.
Thank you.
I have TeamCity running for a C# project. The Unit tests are written using MSTest and they include an external JSON file. They are loaded in because they're large and I don't want to have to escape them in C#.
I import them like this:
[TestInitialize]
public void Setup()
{
using (StreamReader r = new StreamReader(#".\currency2.json"))
{
_json = r.ReadToEnd();
}
...
They run fine locally. I have 'Copy always set' but when the tests are ran using Teamcity I get an error saying that it can't find them in a temp folder. They are copied over to the build server but they're not in this temp folder.
Could not find file 'E:\TeamCity\buildAgent\temp\buildTmp\SYSTEM_SERVER 2016-07-18 15_28_19\Out\currency2.json'
I have **\bin\release\*test*.dll setup as my Test File Names in the test build step.
Any help appreciated.
I had a similar problem.
I changed the properties of the test file to this
Build Action = Content
Copy to Output Directory = Copy always
Teamcity will copy the file to the build folder, but it does not seem to maintain the same structure you'd expect.
So I created a file lookup loop. That will step down the expected folder until it finds the text file in question.
var pathLookups = new string[]
{
"2ndFolder\\3rdFolder\\test.json", // folder that normally workes
"3rdFolder\\test.json",
"test.json"
};
foreach (var pathLookup in pathLookups)
{
try
{
jsonFileCollection = JsonFileLoader<TestJsonType>.LoadJson(pathLooksup);
if (jsonFileCollection!= null)
{
break;
}
}
catch (Exception)
{
Console.WriteLine("Attempted to load test json from path:" + pathLooksup);
}
}
It's not the cleanest solution, but it will get the job done. You could refactor this to look a little nicer.
You might pass the full pass by argument to your program (and value defined in TeamCity).
Something like this (this is a pseudo-code example only) :
string[] programArgs;
static void Main(string[] args)
{
programArgs = args
}
[TestInitialize]
public void Setup()
{
using (StreamReader r = new StreamReader(programArgs[1]))
{
_json = r.ReadToEnd();
}
...
}
We would like the use the bundling mechanism of System.Web.Optimization in combination with the Less transformer.
The problem is that the same application/server serves pages for different branded websites. So depending on the 'SiteContext' the same .less files are used but different values should be used by the .less variables. So we want the (re)use the same less files but with different variables depending on the context of the request.
I tried a couple of different theories:
In all 3 cases I setup different bundles depending on the SiteContext.
1 inject an #import directive with the themed variables by using a custom VirtualPathProvider that intercepts the variables.less file.
So I have:
the styling file eg: header.less (imports the variables file)
the variables file: variables.less
a themed variables file: variables-theme.less (injected in variables.less via the VirtualPathProvider)
This is not working because the BundleTransformer cache sees this as the same file and doesn't know about the SiteContext. The cache key is based on the Url of the IAsset and we cannot influence this behavior.
2 Replace the variables.less import by variables-themed.less with an custom transformer that runs before the Less transformer.
Again no luck, same caching issues.
And as a side effect, the extra transformer was not called in debug because the assets are not bundled but called individually by the LessAssetHandler. This could be solved by writing your own AssetHandler that calls all required transformers.
3 create themed Asset names that are resolved by a custom VirtualPathProvider
Eg. Add header-themeX.less to the bundle, this file doesn't exist but you resolve this file to header.less and use method 2 to set the correct variables file import. (replace the import of the variables.less to the themed version).
Once again no luck. I think this could solve the caching issue if it wasn't for the Bundle.Include(string virtualPath) that does a File.Exists(path) internally. It doesn't pass via the CustomVirtualPathProvider.
Am I looking to deep to solve this?
All ideas are welcome, I can imagine that this will become a problem to more and more people as the System.Web.Optimization library gets more popular...
Keep in mind that:
we have a lot of .less/css files
we will have 5 or so themes
we like to keep things working in visual studio (that is why header.less has a ref. to variables.less)
Thanks for any feedback.
Michael!
You use the Microsoft ASP.NET Web Optimization Framework and the Bundle Transformer in multi-tenant environment, so you need to replace some components of the System.Web.Optimization and create own versions of the debugging HTTP-handlers (see «Problem: LESS file imports are added to BundleResponse.Files collection» discussion). As far as I know, Murat Cakir solve all these problems in the SmartStore.NET project.
In the Bundle Transformer there are 2 ways to inject of LESS-variables:
Look a properties GlobalVariables and ModifyVariables of LESS-translator:
using System.Collections.Generic;
using System.Web.Optimization;
using BundleTransformer.Core.Builders;
using BundleTransformer.Core.Orderers;
using BundleTransformer.Core.Transformers;
using BundleTransformer.Core.Translators;
using BundleTransformer.Less.Translators;
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
var nullBuilder = new NullBuilder();
var nullOrderer = new NullOrderer();
var lessTranslator = new LessTranslator
{
GlobalVariables = "my-variable='Hurrah!'",
ModifyVariables = "font-family-base='Comic Sans MS';body-bg=lime;font-size-h1=50px"
};
var cssTransformer = new CssTransformer(new List<ITranslator>{ lessTranslator });
var commonStylesBundle = new Bundle("~/Bundles/BootstrapStyles");
commonStylesBundle.Include(
"~/Content/less/bootstrap-3.1.1/bootstrap.less");
commonStylesBundle.Builder = nullBuilder;
commonStylesBundle.Transforms.Add(cssTransformer);
commonStylesBundle.Orderer = nullOrderer;
bundles.Add(commonStylesBundle);
}
}
Create a custom item transformation:
using System.Text;
using System.Web.Optimization;
public sealed class InjectContentItemTransform : IItemTransform
{
private readonly string _beforeContent;
private readonly string _afterContent;
public InjectContentItemTransform(string beforeContent, string afterContent)
{
_beforeContent = beforeContent ?? string.Empty;
_afterContent = afterContent ?? string.Empty;
}
public string Process(string includedVirtualPath, string input)
{
if (_beforeContent.Length == 0 && _afterContent.Length == 0)
{
return input;
}
var contentBuilder = new StringBuilder();
if (_beforeContent.Length > 0)
{
contentBuilder.AppendLine(_beforeContent);
}
contentBuilder.AppendLine(input);
if (_afterContent.Length > 0)
{
contentBuilder.AppendLine(_afterContent);
}
return contentBuilder.ToString();
}
}
And register this transformation as follows:
using System.Web.Optimization;
using BundleTransformer.Core.Orderers;
using BundleTransformer.Core.Bundles;
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
var nullOrderer = new NullOrderer();
const string beforeLessCodeToInject = #"#my-variable: 'Hurrah!';";
const string afterLessCodeToInject = #"#font-family-base: 'Comic Sans MS';
#body-bg: lime;
#font-size-h1: 50px;";
var commonStylesBundle = new CustomStyleBundle("~/Bundles/BootstrapStyles");
commonStylesBundle.Include(
"~/Content/less/bootstrap-3.1.1/bootstrap.less",
new InjectContentItemTransform(beforeLessCodeToInject, afterLessCodeToInject));
commonStylesBundle.Orderer = nullOrderer;
bundles.Add(commonStylesBundle);
}
}
Both ways have disadvantage: the injection of LESS-variables does not work in debug mode.
I need to be able to publish an SSDT project programmatically. I am looking at using Microsoft.Build to do so but can not find any documentation. It seems pretty simple to create the .dacpac, but how would I either publish to an existing database or at the very least to a .sql file. The idea is to have it do what it does when I right click on the project and select publish. It should compare with a selected database and generate an upgrade script.
This is what I have so far to create the .dacpac:
partial class DBDeploy
{
Project project;
internal void publishChanges()
{
Console.WriteLine("Building project " + ProjectPath);
Stopwatch sw = new Stopwatch();
sw.Start();
project = ProjectCollection.GlobalProjectCollection.LoadProject(ProjectPath);
project.Build();
//at this point the .dacpac is built and put in the debug folder for the project
sw.Stop();
Console.WriteLine("Project build Complete. Total time: {0}", sw.Elapsed.ToString());
}
}
Essentially I am trying to do what this MSBuild Example shows but in code.
Sorry that this is all I have. The doecumentation on the Build classes is very poor. Any help would be appreciated.
Thanks.
I had to do something similar to this because VSDBCMD which we previously used does not deploy to SQL Server 2012 and we needed to support it. What I found was the Microsoft.SqlServer.Dac assembly which seems to come as part of the SQL Server data tools (http://msdn.microsoft.com/en-us/data/tools.aspx)
When you run this on the client machine you will need the full version of the .NET 4 framework and the SQL CLR types and SQL T-SQL ScriptDOM pack found here: http://www.microsoft.com/en-us/download/details.aspx?id=29065
Code below is from a mockup I made for testing the new deployment method and deploys a given .dacpac file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Dac;
using System.IO;
namespace ConsoleApplication3
{
class Program
{
private static TextWriter output = new StreamWriter("output.txt", false);
static void Main(string[] args)
{
Console.Write("Connection String:");
//Class responsible for the deployment. (Connection string supplied by console input for now)
DacServices dbServices = new DacServices(Console.ReadLine());
//Wire up events for Deploy messages and for task progress (For less verbose output, don't subscribe to Message Event (handy for debugging perhaps?)
dbServices.Message += new EventHandler<DacMessageEventArgs>(dbServices_Message);
dbServices.ProgressChanged += new EventHandler<DacProgressEventArgs>(dbServices_ProgressChanged);
//This Snapshot should be created by our build process using MSDeploy
Console.WriteLine("Snapshot Path:");
DacPackage dbPackage = DacPackage.Load(Console.ReadLine());
DacDeployOptions dbDeployOptions = new DacDeployOptions();
//Cut out a lot of options here for configuring deployment, but are all part of DacDeployOptions
dbDeployOptions.SqlCommandVariableValues.Add("debug", "false");
dbServices.Deploy(dbPackage, "trunk", true, dbDeployOptions);
output.Close();
}
static void dbServices_Message(object sender, DacMessageEventArgs e)
{
output.WriteLine("DAC Message: {0}", e.Message);
}
static void dbServices_ProgressChanged(object sender, DacProgressEventArgs e)
{
output.WriteLine(e.Status + ": " + e.Message);
}
}
}
This seems to work on all versions of SQL Server from 2005 and up. There is a similar set of objects available in Microsoft.SqlServer.Management.Dac, however I believe this is in the previous version of DACFx and is not included in the latest version. So use the latest version if you can.
We need a way tell msbuild how and where to publish. Open your project in Visual Studio and begin to Publish it. Enter all needed info in the dialog, including your DB connection info and any custom SQLCMD variable values. Save Profile As... to a file, e.g. Northwind.publish.xml. (You may then Cancel.) Now we can use this and the project file to build and publish:
// Create a logger.
FileLogger logger = new FileLogger();
logger.Parameters = #"logfile=Northwind.msbuild.log";
// Set up properties.
var projects = ProjectCollection.GlobalProjectCollection;
projects.SetGlobalProperty("Configuration", "Debug");
projects.SetGlobalProperty("SqlPublishProfilePath", #"Northwind.publish.xml");
// Load and build project.
var dbProject = ProjectCollection.GlobalProjectCollection.LoadProject(#"Northwind.sqlproj");
dbProject.Build(new[]{"Build", "Publish"}, new[]{logger});
This can take awhile and may appear to get stuck. Be patient. :)
You should use SqlPackage.exe to publish your dacpac.
SqlPackage.exe
/Action:Publish
/SourceFile:C:/file.dacpac
/TargetConnectionString:[Connection string]
Also instead of passing too many parameters you could save your settings into DAC Publish Profile (this can be done from visual studio)
I wanted to build and publish a database based on a sqlproj file and log helpful information to console. Here's what I arrived at:
using Microsoft.Build.Framework;
using Microsoft.Build.Execution;
public void UpdateSchema() {
var props = new Dictionary<string, string> {
{ "UpdateDatabase", "True" },
{ "PublishScriptFileName", "schema-update.sql" },
{ "SqlPublishProfilePath", "path/to/publish.xml") }
};
var projPath = "path/to/database.sqlproj";
var result = BuildManager.DefaultBuildManager.Build(
new BuildParameters { Loggers = new[] { new ConsoleLogger() } },
new BuildRequestData(new ProjectInstance(projPath, props, null), new[] { "Publish" }));
if (result.OverallResult == BuildResultCode.Success) {
Console.WriteLine("Schema update succeeded!");
}
else {
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Schema update failed!");
Console.ResetColor();
}
}
private class ConsoleLogger : ILogger
{
public void Initialize(IEventSource eventSource) {
eventSource.ErrorRaised += (sender, e) => {
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(e.Message);
Console.ResetColor();
};
eventSource.MessageRaised += (sender, e) => {
if (e.Importance != MessageImportance.Low)
Console.WriteLine(e.Message);
};
}
public void Shutdown() { }
public LoggerVerbosity Verbosity { get; set; }
public string Parameters { get; set; }
}
This is for .NET 4 and above. Be sure and include assembly references to Microsoft.Build and Microsoft.Build.Framework.