Sunday, 10 July 2011

Continuous Integration for your Delphi projects using Jenkins CI

Going on with the utilization of Msbuild, I'm putting forward this post using Jenkins CI (Continuous Integration) previously known as Hudson, for your Delphi projects under Windows Vista. In a nutshell, Jenkins provides an easy-to-use so-called continuous integration system, making it easier for developers to integrate changes to the project, and making it easier for users to obtain a fresh build. The automated, continuous build increases the productivity. (Source: What is Jenkins?). With these simple steps I'll lead you throughout the whole installation and configuration of your first Delphi built project using Jenkins CI and MsBuild.

1 . Download the latest version of Jenkins CI.
Use the following link to download the latest windows installer, in my case v1.419. The default installation path is C:\Program Files\Jenkins and there you'll find the service wrapper jenkins.exe and the Java web archive (jenkins.war) that is very useful to start the Jenkins if you have difficulties installing it with a container. It's very easy to run Jenkins by itself using the command: java -jar jenkins.war (which uses Winstone as a container).

2. Setting up environment variables.
To make sure that Jenkins runs perfectly, we need to set up the JENKINS_HOME environment variable:

3. Adding administrator privileges to Jenkins windows service.
We need to be sure that the service has enough permissions to be running. To do this, go to console and type down "services.msc". Then look for Jenkins service and add the log on information using an administrator account.
I had a lot of troubles trying to execute MSBuild from Jenkins until I realised that the service didn't have enough permissions.

4. Installing Jenkins CI plug-ins.
Once everything is done, we can go on with Jenkins and open a web explorer and type http://localhost:8080. Now we need to add the following plug-ins: Jenkins MSBuild Plugin and Hudson Setenv Plugin. To install them go to "Manage Jenkins"-> "Manage Pluggins" -> Available and look for them. Then, save the changes at the end of the page and reload Jenkins.
Once installed, you'll find them under the Installed section:

5. Configure MSBuild.
Now the plug-in is ready, and we need to supply the path of our MSBuild. Go to "Manage Jenkins" -> "Configure Jenkins" and add the Msbuild information in the MSbuild section:

6. Adding environment variables.
As we are trying to build Delphi projects we need to set up Jenkins with the same environment variables included in rsvars.bat batch file. Go to "Manage Jenkins" -> "Configure Jenkins" and add the following environment variables according to your Delphi version:

7. Setting up your project.
Start a new job with your project description and choose the "Build a free-style software project" type. And add the following MSbuild information to build your project (dproj file):

8. Build your Project.
Everything is ready to do the build. Now execute the "Build now" option and you'll see your project being compiled and built using MSBuild.

Now, you can add your Delphi projects and improve the quality of your software.

Related Links:

Monday, 4 July 2011

Delphi Msbuild

Msbuild has been included since Delphi 2007 to automate our Delphi builds and manage our projects in a better way. I have developed a custom application to provide automated Builds for Delphi (2007 / 2009 / 2010 / XE -Win32) using MSBuild. The app called Thundax Delphi Builder is available here for free. This little tool generates a batch file to call the list of projects and it displays the information using a TMemo getting the info from the console window using a pipe with the capture method from my previous post.
If you take a look at my application, you will see a output section and a configuration section. In the configuration section we need to add the list of projects to compile/build, the path of our Delphi binaries and the different MSBuild commands we want to execute.

As soon as all the parameters are correct, we can hit the "Build" button. The Build button generates a batch file called build.bat. This batch file contains the list of all our projects with the correct commands for MSBuild. Just take a look at the generated batch file and you will understand what is this doing.

First of all, it sets the environment variables for the target compiler. It means that if you enter in the "Bin" section  the path to your DelphiXE binaries, it will set MSBuild for DelphiXE. To do this, we only need to run rsvars.bat batch file which contains:

@SET BDS=C:\Program Files (x86)\Embarcadero\RAD Studio\7.0
@SET BDSCOMMONDIR=C:\Users\Public\Documents\RAD Studio\7.0
@SET FrameworkDir=C:\Windows\Microsoft.NET\Framework\v2.0.50727
@SET FrameworkVersion=v2.0.50727
@SET FrameworkSDKDir=
@SET PATH=%FrameworkDir%;%FrameworkSDKDir%;%PATH%

Of course this tools is just a "little tool" to help you understand how Msbuild works and how to invoke it from the command line. I use continuous integration tools and some day I will talk about one of them, for example Hudson continuous integration.

PS: If you want to download the app, download my Calc Hash to make sure that the MD5 hash is correct. To check it, download the app, remove the " (0c3c3ab27a6000d8881974a411959828)." hash and check the file with the calc. If the app is correct, you should get the same hash.

Related links:

Capturing console output with Delphi 2010/XE

This new method supersedes the previous one using TDosCommand. It's been tested and it works with Delphi 2010 and Delphi XE, so it's worth to give it a try. It's really easy to use and I'm preparing a little tool with it.

//Anonymous procedure approach by Lars Fosdal
    TArg<T> = reference to procedure(const Arg: T);

procedure TForm1.CaptureConsoleOutput(const ACommand, AParameters: String; CallBack: TArg<PAnsiChar>);
    CReadBuffer = 2400;
    saSecurity: TSecurityAttributes;
    hRead: THandle;
    hWrite: THandle;
    suiStartup: TStartupInfo;
    piProcess: TProcessInformation;
    pBuffer: array [0 .. CReadBuffer] of AnsiChar;
    dBuffer: array [0 .. CReadBuffer] of AnsiChar;
    dRead: DWord;
    dRunning: DWord;
    saSecurity.nLength := SizeOf(TSecurityAttributes);
    saSecurity.bInheritHandle := True;
    saSecurity.lpSecurityDescriptor := nil;

    if CreatePipe(hRead, hWrite, @saSecurity, 0) then
        FillChar(suiStartup, SizeOf(TStartupInfo), #0);
        suiStartup.cb := SizeOf(TStartupInfo);
        suiStartup.hStdInput := hRead;
        suiStartup.hStdOutput := hWrite;
        suiStartup.hStdError := hWrite;
        suiStartup.wShowWindow := SW_HIDE;

        if CreateProcess(nil, pChar(ACommand + ' ' + AParameters), @saSecurity, @saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess) then
                dRunning := WaitForSingleObject(piProcess.hProcess, 100);
                    dRead := 0;
                    ReadFile(hRead, pBuffer[0], CReadBuffer, dRead, nil);
                    pBuffer[dRead] := #0;

                    //OemToAnsi(pBuffer, pBuffer);
                    //Unicode support by Lars Fosdal
                    OemToCharA(pBuffer, dBuffer);
                until (dRead < CReadBuffer);
            until (dRunning <> WAIT_TIMEOUT);

    CaptureConsoleOutput('java -version', '', 
                procedure(const Line: PAnsiChar) 

With all the help I got from Lars, I've released a version of the function for test purposes. You can download the app from here (Thundax Output).


Updated 13/12/2012:
Check out the latest algorithm in the most updated and revised post:

Related links: