Building and configuring OpenSSL in Visual Studio (MSBuild). - CodeProject

:

Introduction

In this article I will show how OpenSSL library could be configured and built using Visual Studio and MSBuild system. The goal of this writing is to provide solution for following problems:

  • Provide specialized UI Property Pages specific to openssl library so only valid options could be set
  • Provide easy way to fine tune build and pass compiler options
  • Nothing is hard-coded
  • Build multiple platforms in sequence (x86, x64...) 
  • Build outside of source tree
  • Integrate with Visual Studio to allow project references to and from OpenSSL
  • Seamless debugging and symbols loading

Background

The OpenSSL Project is open source effort implementing Secure Sockets Layer (SSL v2/v3) and Transport Layer Security (TLS) protocols as well as a general purpose cryptography library. It is cross platform library and as such is written to accommodate multiple build tools and operating systems.

Build instructions are pretty straightforward:

perl Configure VC-WIN32 --prefix=C:\Build-OpenSSL-VC-32
ms\do_ms
nmake -f ms\nt.mak
nmake -f ms\nt.mak install

Seems to be simple, but there is a catch, as always:

  • It builds all binaries as well as object files inside root directory of OpenSSL distribution.
  • If more than one configuration (Release, Debug, etc.) require build it has to be done one by one.
  • It does not integrate with Visual Studio in any way

Here we'll try to fix all of these.

Configuration

In my other articles I've described how to create custom property pages for third party tools. For more information see here and here. Long story short, we create following property pages:

General page allows setting of normal project attributes such as Output and Intermediate directories as well as most of OpenSSL options. It also provides UI to add any arbitrary parameters in Command Line control.
Output, Intermediate and Include directories will be used during build to place respective files. If left empty build will use default location in OpenSSL root folder (for example inc32 if Include is empty).

Expanding Options in left pane produces following view:

This page allows disabling ciphers. If cipher is missing on this page it could still be disabled by providing
no-missing_cither option in Command Line on General page.

Kerberos 5 page provides a way to add Kerberos 5 libraries to the build.

C++ page allows to fine tune build and send additional switches and definitions to the compiler. 

With these property pages we can configure all OpenSSL options as well as pass some additional parameters to the compiler. 

Getting correct values from property pages and passing them to configuration is described in details here. Configuration is processed by GetXmlConfig target and returned a string of parameters which passed to appropriate script.

Now we are ready to build.

Build

Examining Configure script and batch files revealed following steps:

perl Configure VC-WIN32 --prefix=C:\Build-OpenSSL-VC-32

Creates Makefile at the root of OpenSSL directory.

ms\do_ms

Creates MINFO file
Creates Module Definition and Resource files
Creates consolidated Makefile

nmake -f ms\nt.mak

Builds the library.

Wrapping these tasks into native MSBuild targets allows more control during build. We create following targets:

<Target Name="Configure" ... />
<Target Name="MINFO" ... />
<Target Name="ModuleDefinitions" ... />
<Target Name="CreateMakefile" ... />
<Target Name="CompileAsm" ... />
<Target Name="Build" ... />
<Target Name="Rebuild" ... />
<Target Name="Clean" ... />

Add required dependencies between targets, insert <exec> tasks for respective Perls scripts, add configuration for Configure and Makefile scripts and we are done. Everything is straightforward and follows OpenSSL normal build flow exactly. Specifying Inputs and Outputs allows incremental builds to be performed if nothing is changed in source tree.

Configuration Options

OpenSSL build is configured in two steps: in Configure target and CreateMakefile target.

Configure target processes parameters from UI Property pages and pass them to Configure script. Values for configuration are retrieved by executing GetXmlConfig target implemented in ConfGen.targets. For detailed explanation of the process see this post

CreateMakefile target executes mk1mf.pl script and sets locations of Output, Include and Intermediate directories. It also directs the script to create static or shared libraries and use assembler or not.

Clean

Cleaning up after the build is a bit tricky. Executing nmake -f ms\nt.mak clean wipes out everything by running del *.* 

For Visual Studio this behavior is not acceptable. There might be other project's files in the same directory. In order to avoid deleting incorrect files Makefile is parsed for list of targets it builds. Format of Makefile is well defined so it is relatively easy to find all of these (note, script is modified for simplicity):

<ReadLinesFromFile File="$(BuildDir)\openssl.mak" Condition="Exists('$(BuildDir)\openssl.mak')">
  <Output ItemName="ConfigLines" TaskParameter="Lines"/>
</ReadLinesFromFile>

<ItemGroup>
  <BuildDirSelected  Include="$([Regex]::Match('%(ConfigLines)', (?&lt;=\\)(.*?\b)(?&lt;=\.obj) ))" />
  <BuildDirSelected  Include="$([Regex]::Match('%(ConfigLines)', (?&lt;=\\)(.*?\b)(?&lt;=\.asm) ))" />
  <BuildDirLibrary   Include="$([Regex]::Match('%(ConfigLines)', (?&lt;=\\)(.*?\b)(?&lt;=\.lib) ))" />
  ...
  <OutputDirLibrary  Include="$([Regex]::Match('%(ConfigLines)', (?&lt;=\\)(.*?\b)(?&lt;=\.dll) ))" />
  <OutputDirLibrary  Include="openssl.exe;libeay32.dll;ssleay32.dll" />
</ItemGroup>
    
<CombinePath BasePath="$(BuildDir)" Paths="@(BuildDirSelected)">
  <Output ItemName="DelFiles" TaskParameter="CombinedPaths"/>
</CombinePath>
<CombinePath BasePath="$(OutputDir)" Paths="@(OutputDirSelected)">
  <Output ItemName="DelFiles" TaskParameter="CombinedPaths"/>
</CombinePath>
    
<Delete Files="@(DelFiles)" ContinueOnError="true" />

In this code we read Makefile, parse all target files by matching patterns, add full path to these files and delete them.

Building Multiple Platforms

Building multiple platforms in sequence is possible if Intermediate, Include and Output directories are all different. This  could be achieved by using macros such as $(Platform) and $(Configuration). For example of configuration see x64 platform of the sample project.

Build could be executed both from VisualStudio and command prompt. 

References

One of the goals of this project was to integrate it into Visual Studio project reference system.

Referencing dependencies

OpenSSL depends on two external libraries: ZLib and Kerberos 5. Kerberos does not provide any support for MSBuild so it cannot be integrated into native reference system. ZLib, on the other hand, has MSBuild project distributed with the library and can be easily integrated.
ZLib also has well-known ID associated with the project which could be used to identify it to the build:

<MSBuild Projects="@(ProjectReference)" Targets="GetResolvedLinkLibs"

         Condition="'%(ProjectReference.Project)'=='{8fd826f8-3739-44e6-8cc8-997122e53b8d}'" >
  <Output ItemName="ZLibPath" TaskParameter="TargetOutputs"/>
</MSBuild>

This gets the path to ZLib library.

Build also requires path to the Include directory of ZLib library. Normally it is manually set as Additional Include Directories field in C++ compiler settings. We could eliminate manual step if that location could be inferred from location of zlib.h file. This code gets that location:

<MSBuild Projects="@(ProjectReference)" Targets="SourceFilesProjectOutputGroup"

         Condition="'%(ProjectReference.Project)'=='{8fd826f8-3739-44e6-8cc8-997122e53b8d}'" >
  <Output ItemName="SourceFiles" TaskParameter="TargetOutputs"/>
</MSBuild>

<PropertyGroup>
  <LibEnvVar Condition="'@(SourceFiles)'!='' And 
                        '%(SourceFiles.Identity)'!='' And 
                        '@(SourceFiles->Contains(&#34;zlib.h&#34;))'=='True'">INCLUDE=%(RelativeDir)/>
</PropertyGroup>

Both of these paremeters are passed as environment variables LIB & INCLUDE to the build.

Providing References

In order to be referenced in upstream projects several targets are required:

<Target Name="GetNativeTargetPath"/ >
<Target Name="GetTargetPath" />
<Target Name="GetNativeManifest" />
<Target Name="GetResolvedLinkLibs" />
<Target Name="GetCopyToOutputDirectoryItems" />

For detailed explanation of project referencing and what is required see Referencing native projects in Visual Studio. OpenSSL project follows guidelines as described in the article.

Test Application

Test application has been created to demonstrate OpenSSL integration and referencing. The sample comes from old IBM article published here

Sample requires adding reference to OpenSSL to project dependencies and setting path to Include directory. Once it is done test application could be built and debugged.

Debugging

One of the biggest benefits of Visual Studio is its powerful debugger. This integrated project allows you to utilize all features of this tool without any extra effort. You can step into the code from your application or you could just debug openssl.exe. Stepping into OpenSSL code is working without any additional steps. Location of pdb files is inferred from location of lib files provided by GetResolvedLinkLibs

To debug openssl.exe right click openssl project in solution explorer, select Debug and select new instance:

It will start new debugger session, load executable and step into main method:

From this point on you could step through the code, set break points and etc.

Testing

This project allows execution of unit tests normally executed by nmake -f makefile test command. Tests are run by Test target. Visual Studio does not provide support for this target so it should be executed from command prompt like this: MSBuild /target:Test openssl.vcxproj

If all is well it will report success as above.

Perl

OpenSSL requires Perl to configure build and create Makefile. Windows does not have any built-in support for the language, nor does Visual Studio. OpenSSL organization recommends ActivePerl for build.

Using the code

Source archive contains project file, targets and xml files required to build OpenSSL. Sample has Visual Studio solution and other projects.

You need to download OpenSSL and ZLib source and expand it into respective directories before you can build solution.

The code provided meant to be an example and was not exhaustively tested. If you find any issues with it let me know. If you know how to fix it, send me a pull request at GitHub.

History

03/30/2015 - Published
03/31/2015 - Expanded on native references, added Debug and Test sections