I find that I am always tuning and tweaking our CI setup as we add new projects. While there is NO question that the benefits are awesome for existing code that seldom changes, new projects or volitile ones seem to require more work as I have to configure each project to be "intergrated" as well as maintain an ever-growing CCNET.config file. Is there a better strategy short of building an utility to manage adding and modifying a CI setup?
I do a few things to try keep it under control:
1) Split the config file into two. I have one file that mostly stays the same and contains a set of constants e.g.
<?xml version="1.0" encoding="utf-8"?>
<cruisecontrol xmlns:cb="urn:ccnet.config.builder">
<!-- Constant definition used by the projecct config to prevent changes being required for each iteration -->
<cb:define branch="branch name for source control"/>
<cb:define ciserver="Server name in here"/>
<cb:define devenv="Path to DEVENV"/>
<cb:define nunit="Path to NUNIT"/>
<cb:define cruisecontrol="Cruisecontrol Path"/>
<!-- Include file to the standard CI project definitions. This file is kept under source control -->
<cb:include href="config\CCProjects.config"/>
</cruisecontrol>
The use of constants allows you to make a single change and have it propagate through each task in the config file.
See docs
2) Keep the file with the projects in under source control. The project file gets updated as part of the SVN checkout. This helps track changes that get made and let you rollback without too much hassle.
Maybe it has got to the point where CC.Net is working against you rather than for you. I've heard good things about the ease of configuration of other CI servers, like Hudson, but it may not be a good fit with your build environment.
1/ Split your config file as you want
For example, I have a constants section, and each project is an include, so I can update each project quite independently and use constants across projects.
ccnet.config
<cruisecontrol xmlns:cb="urn:ccnet.config.builder">
<!-- Shared constants -->
<cb:define WorkingFolderBase="D:\dev\ContinuousIntegration\WC" />
<cb:define ArtifactFolderBase="D:\dev\ContinuousIntegration\Artifact" />
<cb:define ConfigFolder="projects" />
<cb:define SvnBasePath="http://myserver.com/svn" />
<cb:define SvnUsername="Myusername" />
<cb:define SvnPassword="MyPassword" />
<!-- MyProject1 -->
<cb:include href="projects/MyProject1.config"/>
<!-- MyProject2 -->
<cb:include href="projects/MyProject2.config"/>
</cruisecontrol>
MyProject1.config
<project name="MyProject1" queue="Q1" queuePriority="1">
<artifactDirectory>$(ArtifactFolderBase)\MyProject1</artifactDirectory>
<workingDirectory>$(WorkingFolderBase)\MyProject1</workingDirectory>
<!-- SVN implementation -->
<sourcecontrol type="svn" username="$(SvnUsername)" password="$(SvnPassword)">
<trunkUrl>$(SvnBasePath)/MyProject1/trunk/</trunkUrl>
<workingDirectory>$(WorkingFolderBase)\MyProject1</workingDirectory>
</sourcecontrol>
[...]
</project>
2/ Use version control on CC.NET (I recommand on the whole installation) or on config files.
3/ Keep it simple! have all actions executed by a batch file (Compile applciation, compile tests, run tests, get coverage, static analyser, generate reports ...).
Related
I'm trying to build a solution using Azure DevOps and publish the result as a nuGet package in a private repository.
A project in the solution contains a localized resource Language.resx containing the English texts.
The localized versions are:
Language.da.resx, Language.se.resx and Language.no.resx yet none of these are included in the resulting nuget package.
I tried adding /target:Resource,Compile to the MSBuild arguments property of the Build Solution task in DevOps, but it just resulted in an error saying no 'Resource' target was found.
I'm sure I'm just missing something obvious, but I just can't see it.
I must be close, the nuGet package gets published after all, and works, except for the localization resources.
I inspected the resulting nuGet Package and extracted the DLL from the project in question. Opening the DLL in .Net Reflector 10 shows me that it does indeed contain the text strings in English, but no other languages.
It appears the localized ressources were indeed built by default, though not visible in .Net Reflector 10.
The reason is that they weren't included in the nuGet package in the Packaging task.
A bit of research lead me to add a .nuspec file to the project.
In this file, I specified the relevant files etc. and they are now included in the nuGet package.
Not ideal though, since the list of files and dependencies now have to be maintained manually. But I was unable to find a way of making it dynamic.
Most of the details in the .nuspec file was possible to be made dynamic though, using variables referring to the AssemblyInfo.cs file.
I ended up with the following content in the .nuspec file (some details cleaned though, since this is displayed in public)
Note: The word Release in the src attribute of the files section is the name of my build profile. Your solution might use a different name.
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<!-- The identifier that must be unique within the hosting gallery -->
<id>$id$</id>
<!-- The package version number that is used when resolving dependencies -->
<version>$version$</version>
<!-- Authors contain text that appears directly on the gallery -->
<authors>$author$</authors>
<!--
Owners are typically nuget.org identities that allow gallery
users to easily find other packages by the same owners.
-->
<owners>$author$</owners>
<!-- License and project URLs provide links for the gallery -->
<!--<licenseUrl></licenseUrl>-->
<!--<projectUrl></projectUrl>-->
<!-- The icon is used in Visual Studio's package manager UI -->
<!--<iconUrl></iconUrl>-->
<!--
If true, this value prompts the user to accept the license when
installing the package.
-->
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<!-- Any details about this particular release -->
<!--<releaseNotes></releaseNotes>-->
<!--
The description can be used in package manager UI. Note that the
nuget.org gallery uses information you add in the portal.
Must be included, and must never be empty.
-->
<description>$description$</description>
<!-- Copyright information -->
<copyright>$copyright$</copyright>
<!-- Tags appear in the gallery and can be used for tag searches -->
<!--<tags></tags>-->
<!-- Dependencies are automatically installed when the package is installed -->
<dependencies>
<dependency id="MicrosoftOfficeCore" version="15.0.0" />
<dependency id="Microsoft.Office.Interop.Word" version="15.0.4797.1003" />
</dependencies>
</metadata>
<!-- Files to include in the package -->
<files>
<file src="bin\Release\$id$.dll" target="lib\net462\$id$.dll" />
<file src="bin\Release\da\$id$.resources.dll" target="lib\net462\da\$id$.resources.dll" />
<file src="bin\Release\no\$id$.resources.dll" target="lib\net462\no\$id$.resources.dll" />
<file src="bin\Release\sv\$id$.resources.dll" target="lib\net462\sv\$id$.resources.dll" />
</files>
</package>
I'm trying to enable perf4j annotations in intellij but I'm struggling to configure correctly AspectJ. More specifically the log file is created correctly but lacks of any data from the annotated method.
These are the relevant extracts of configuration:
logback.xml
<configuration debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="statistics" class="ch.qos.logback.core.FileAppender">
<file>./target/statisticsLogback.log</file>
<append>false</append>
<layout>
<pattern>%msg%n</pattern>
</layout>
</appender>
<appender name="coalescingStatistics" class="org.perf4j.logback.AsyncCoalescingStatisticsAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<timeSlice>1000</timeSlice>
<appender-ref ref="statistics"/>
</appender>
<appender name="listAppender" class="ch.qos.logback.core.read.ListAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<timeSlice>1000</timeSlice>
</appender>
<logger name="org.perf4j.TimingLogger" level="info">
<appender-ref ref="coalescingStatistics" />
<appender-ref ref="listAppender"/>
</logger>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
aop.xml
<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
<!--
We only want to weave in the log4j TimingAspect into the #Profiled classes.
Note that Perf4J provides TimingAspects for the most popular Java logging
frameworks and facades: log4j, java.util.logging, Apache Commons Logging
and SLF4J. The TimingAspect you specify here will depend on which logging
framework you wish to use in your code.
-->
<aspects>
<aspect name="org.perf4j.slf4j.aop.TimingAspect"/>
<!-- if SLF4J/logback use org.perf4j.slf4j.aop.TimingAspect instead -->
</aspects>
<weaver options="-verbose -showWeaveInfo">
<!--
Here is where we specify the classes to be woven. You can specify package
names like com.company.project.*
-->
<include within="com.mycode.myproject.mypackage.*"/>
<include within="org.perf4j.slf4j.aop.*"/>
</weaver>
</aspectj>
Finally the related test method is tagged with the #Profiled annotation, this is part of the package defined in the aop.xml.
This configuration results in the log file being produced (which suggests that the logback.xml is configured correctly, however it only contains headers and no statistics from the tagged method.
The main question I have is where the AspectJ configuration should go within Intellij, I have included the aop.xml under a manually created META-INF folder in the src folder but I'm not sure this is detected by AspectJ at all.
Thanks in advance
UPDATE
I have made some progress on this since my initial post, specifically introducing two changes:
i) included -javaagent:lib\aspectjweaver.jar
ii) moved the aop.xml into a META-INF folder.
The aop configuration is now being picked up as it logs the configuration details and it also mentions the method being profiled.
The issue is now that the thread being profiled crashes, it doesn't log any exceptions but via debug the issue seems to be related to a ClassNotFoundException in org.aspectj.runtime.reflect.Factory when trying to instantiate org.aspectj.runtime.reflect.JoinPointImpl.
To isolate the issue i have removed all the maven imports of aspectJ and used the jars provided by the installation package but the issue persists, also the fact that the application crashes without any logging makes the issue tracking harder.
UPDATE
To clarify:
After reading more about this including the manual in the wayback link (thanks for that) I realised I was mixing up load-time / compile-time approach. Since then I tried both methods as described in the guide but with the same results described in my earlier update.
As per above, I do start the application with aspectj weaver option (-javaagent)
The build is done via IDE, as per above at the moment I have removed the aspectj / perf4j dependencies from Maven and linked to local jars
As mentioned the aop.xml does get picked up as mentioned in the update with no errors or warning, just confirmation of the weaved method
Okay, I have added a full Maven example to a GitHub repo which you can just clone and play around with.
Some basic things to consider:
For compile-time weaving (CTW) you need aspectjrt.jar on the classpath when compiling and running the code. You also need to use the AspectJ compiler to build the project, a normal Java compiler is not enough.
For load-time weaving (LTW) you need aspectjweaver.jar as a Java agent on the command line when running the code: -javaagent:/path/to/aspectjweaver.jar. You also need to add it as a VM argument to your LTW run configuration in IDEA.
For LTW you also need META-INF/aop.xml in your resources folder. Please also note that in order to encompass subpackages you should use the ..* notation, not just .*, e.g. <include within="de.scrum_master..*"/>.
You find more information in my project's read-me file.
P.S.: The Perf4J documentation is outdated and the project unmaintained. Thus, it still mentions AspectJ 1.6.x as necessary dependencies. I built and ran everything with the latest AspectJ 1.8.10 and it runs just fine, both from Maven and IDEA.
I'm pretty new to build servers but have been asked by my employer to do some testing (because F5 is not a build process, as the excellent article by Jeff Atwood says). At this stage, I'm working on getting some sample builds and test reports up and running on a CruiseControl.NET server. So far, I've gotten a build up and running (the configuration file will need some tidying up before adding new builds/projects but the proof of concept is there) but the reporting is causing something of a headache.
The main report I'm looking for is for out NUnit tests and SpecFlow integration tests. The tests run fine (as I'm getting a sensible looking xml file generated) and am looking to merge that in to the main build results so that I can show the results of the NUnit/SpecFlowtests.
Whenever the build completes, the following is reported in the messages (in ViewFarmReport.aspx): "Failing Tasks : XmlLogPublisher "
This combined with the following error reported in the Windows application log (source - CC.Net)
2015-03-24 08:36:52,987 [Initech.SuperCrm-DEV] ERROR CruiseControl.NET [(null)] - Publisher threw exception: ThoughtWorks.CruiseControl.Core.CruiseControlException: Unable to read the contents of the file: C:\CCNet\BuildArtifacts\Initech.SuperCrm-DEV\msbuild-results-7c657954-2c3e-405f-b0f1-7da1299788fd.xml ---> System.IO.FileNotFoundException: Could not find file 'C:\CCNet\BuildArtifacts\Initech.SuperCrm-DEV\msbuild-results-7c657954-2c3e-405f-b0f1-7da1299788fd.xml'.
(company/application name "censored")
This leads me to suspect that the failure to merge in the msbuild results (which I believe CruiseControl.NET automatically scrapes since version... 1.5 or 1.6?) is preventing the NUnit results from being merged in.
There is no msbuild-results file in the BuildArtifacts folder, which does not surprise me as I do not believe my current msbuild configuration allows for xml based logging as I am using the ThoughtWorks.CruiseControl.MsBuild.dll logger.
According to the online documentation for CruiseControl.NET there is XML enabled custom logger: ThoughtWorks.CruiseControl.MsBuild.XmlLogger which can be used, however the download location for this logger: here
appears not to exist any more.
Can anyone say whether I'm thinking along the right lines here and what my options are?
For reference, here is my complete configuration:
<cruisecontrol xmlns:cb="urn:ccnet.config.builder">
<cb:define MSBuildPath="C:\Windows\Microsoft.NET\Framework\v4.0.30319" />
<cb:define WorkingBaseDir="C:\CCNet\Builds" />
<cb:define ArtifactBaseDir="C:\CCNet\BuildArtifacts" />
<cb:define MSBuildLogger="C:\Program Files (x86)\CruiseControl.NET\server
\ThoughtWorks.CruiseControl.MsBuild.dll" />
<cb:define NUnitExe="C:\Jenkins\Nunit\nunit-console.exe" />
<cb:define name="vsts_ci">
<executable>C:\Jenkins\tf.exe</executable>
<server>http://tfs-srv:8080/tfs/LEEDS/</server>
<domain>CONTOSO</domain>
<autoGetSource>true</autoGetSource>
<cleanCopy>true</cleanCopy>
<force>true</force>
<deleteWorkspace>true</deleteWorkspace>
</cb:define>
<project name="Initech.Libraries" description="Shared libraries used in all Initech projects"
queue="Q1">
<state type="state" directory="C:\CCNet\State"/>
<artifactDirectory>$(ArtifactBaseDir)\Initech.Libraries</artifactDirectory>
<workingDirectory>$(WorkingBaseDir)\Initech.Libraries</workingDirectory>
<triggers>
<intervalTrigger
name="continuous"
seconds="30"
buildCondition="IfModificationExists"
initialSeconds="5"/>
</triggers>
<sourcecontrol type="vsts">
<cb:vsts_ci/>
<workspace>CCNET_Initech.Libraries</workspace>
<project>$/InitechLibraries/Initech.Libraries</project>
</sourcecontrol>
</project>
<project name="Initech.SuperCrm-DEV" description="Initech.SuperCrm Application, Development
Version" queue="Q1">
<cb:define ArtifactDirectory="$(ArtifactBaseDir)\Initech.SuperCrm-DEV" />
<cb:define WorkingDirectory="$(WorkingBaseDir)\Initech.SuperCrm-DEV" />
<cb:define OutputDirectory="$(WorkingDirectory)\Initech.SuperCrm\bin\Debug" />
<cb:define ProjectFile="Initech.SuperCrm.sln" />
<cb:define NUnitLog="$(WorkingDirectory)\NunitResults.xml" />
<state type="state" directory="C:\CCNet\State"/>
<artifactDirectory>$(ArtifactDirectory)</artifactDirectory>
<workingDirectory>$(WorkingDirectory)</workingDirectory>
<triggers>
<!-- check the source control every X time for changes,
and run the tasks if changes are found -->
<intervalTrigger
name="continuous"
seconds="30"
buildCondition="IfModificationExists"
initialSeconds="5"/>
</triggers>
<sourcecontrol type="vsts">
<cb:vsts_ci/>
<workspace>CCNET_Initech.SuperCrm-DEV</workspace>
<project>$/InitechSuperCrm/SuperCrm/Initech.SuperCrm-DEV</project>
</sourcecontrol>
<tasks>
<exec>
<executable>C:\Program Files (x86)\DXperience 12.1\Tools\DXperience
\ProjectConverter-console.exe</executable>
<buildArgs>$(WorkingDirectory)</buildArgs>
</exec>
<msbuild>
<executable>$(MSBuildPath)\MSBuild.exe</executable>
<workingDirectory>$(WorkingDirectory)</workingDirectory>
<projectFile>$(ProjectFile)</projectFile>
<timeout>900</timeout>
<logger>$(MSBuildLogger)</logger>
</msbuild>
<exec>
<executable>$(NUnitExe)</executable>
<buildArgs>/xml=$(NUnitLog) /nologo $(WorkingDirectory)\$(ProjectFile)
</buildArgs>
</exec>
</tasks>
<publishers>
<buildpublisher>
<sourceDir>$(OutputDirectory)</sourceDir>
<useLabelSubDirectory>true</useLabelSubDirectory>
<alwaysPublish>false</alwaysPublish>
<cleanPublishDirPriorToCopy>true</cleanPublishDirPriorToCopy>
</buildpublisher>
<merge>
<files>
<file>$(NUnitLog)</file>
</files>
</merge>
<xmllogger logDir="C:\CCNet\BuildArtifacts\Initech.SuperCrm-DEV\buildlogs" />
<artifactcleanup cleanUpMethod="KeepLastXBuilds"
cleanUpValue="50" />
</publishers>
</project>
</cruisecontrol>
I've been tearing my hair while trying to figure this out, and I don't have much to begin with, so any help would be greatly appreciated.
After a prolonged period of banging my head against the wall, I seem to have finally found the solution (well solutions).
1) Kobush.Build.dll (https://www.nuget.org/packages/Kobush.Build/) can be used as the logger for MSBuild. Looking at the attributions in CruiseControl.NET's documentation, it appears to have been written by the same developer (but extended).
2) Some tweaks were needed due to the default location of the msbuild-report output. Because, by default, it was dumped to the buildartifacts folder then it is susceptible to being prematurely deleted.
I no longer clean the publish directory prior to copying (in the buildpublisher) and perform the merge and xmllogger portions of the publisher before artifact cleanup.
As a result, I now have msbuild and nunit output/results integrated in to the main build log and these can be consumed through the CruiseControl.NET dashboard.
There's probably a tidier way of handling this, but at the moment I'm just getting a proof of concept going.
If so, how?
If not, what is the best workaround? Particularly, I'm trying to automate running unit tests in the GUI test runner on arbitrary developers' machines.
The best solution is to make the classes you want to unit test accept configuration settings in their constructors rather than read them from configuration files.
Make callers responsible for obtaining configuration data: decouple your classes from specific configuration sources and eliminate hidden dependencies in one fell swoop.
Yes, you can configure NUnit to do this. If you specify domainUsage="Multiple" in your project's settings, it will load .dll.config for each assembly in your NUnit project.
Here's a sample project file with this setting specified:
<NUnitProject>
<Settings activeConfig="Debug" domainUsage="Multiple" />
<Config name="Debug" binpathtype="Auto" >
<assembly path="Assembly1.dll" />
<assembly path="Assembly2.dll" />
</Config>
<Config name="Release" binpathtype="Auto" />
</NUnitProject>
In this case, NUnit will load Assembly1.dll.config and Assembly2.dll.config automatically.
I'm trying to setup CruiseControl.net at the moment. So far it works nice, but I have a Problem with the MSBuild Task.
According to the Documentation, it passes CCNetArtifactDirectory to MSBuild. But how do I use it?
I tried this:
<buildArgs>
/noconsolelogger /p:OutputPath=$(CCNetArtifactDirectory)\test
</buildArgs>
But that does not work. In fact, it kills the service with this error:
ThoughtWorks.CruiseControl.Core.Config.Preprocessor.EvaluationException: Reference to unknown symbol CCNetArtifactDirectory
Documentation is rather sparse, and google und mainly offers modifying the .sln Project file, which is what I want to avoid in order to be able to manually build this project later - I would really prefer /p:OutputPath.
The CCNetArtifactDirectory is passed to the MSBuild by default, so you dont need to worry about it. MSBuild will place the build output in the "bin location" relevant to the working directory that you have specified.
<executable>c:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe</executable>
<workingDirectory>C:\data\projects\FooSolution\</workingDirectory>
<projectFile>FooSolution.sln</projectFile>
<buildArgs>/noconsolelogger /p:Configuration=Debug </buildArgs>
So in the above example your build output will be put in C:\data\projects\FooSolution[ProjectName]\bin\Debug. Should you want to output to a different location you may want to look at of the tag in CCNET.
<publishers>
<xmllogger />
<buildpublisher>
<sourceDir>C:\data\projects\FooSolution\FooProject\bin\Debug</sourceDir>
<publishDir>C:\published\FooSolution\</publishDir>
<useLabelSubDirectory>false</useLabelSubDirectory>
</buildpublisher>
</publishers>
This will allow you to publish your output to a different location.
You can use the artifact directory variable inside the MSBuild script itself. Here's an example of how I'm running FxCop right now from my CC.Net MSBuild script (this script is what CC.Net points to - there is also a "Build" target in the script that includes an MSBuild task against the SLN to do the actual compilation):
<Exec
Command='FxCopCmd.exe /project:"$(MSBuildProjectDirectory)\FXCopRules.FxCop" /out:"$(CCNetArtifactDirectory)\ProjectName.FxCop.xml"'
WorkingDirectory="C:\Program Files\Microsoft FxCop 1.35"
ContinueOnError="true"
IgnoreExitCode="true"
/>
Parameters like CCNetArtifactDirectory are passed to external programs using environment variables. They are available in the external program but they aren't inside CCNET configuration. This often leads to confusion.
You can use a preprocessor constant instead:
<cb:define project.artifactDirectory="C:\foo">
<project>
<!-- [...] -->
<artifactDirectory>$(project.artifactDirectory)</artifactDirectory>
<!-- [...] -->
<tasks>
<!-- [...] -->
<msbuild>
<!-- [...] -->
<buildArgs>/noconsolelogger /p:OutputPath=$(project.artifactDirectory)\test</buildArgs>
<!-- [...] -->
</msbuild>
<!-- [...] -->
</tasks>
<!-- [...] -->
</project>