Manage Cross-Platform Projects with the make Utility

Manage Cross-Platform Projects with the make Utility

ong before Windows became the de facto operating system on the desktop?even before IBM released the first Personal Computer with 16KB of memory, software engineers had realized the need for automation tools to facilitate large-scale development efforts. In 1960s and 70s, when software projects began growing in complexity, developers found it increasingly difficult to work in unity with each other. Unforeseen problems during integration often forced the team back to coding. Furthermore, in some extreme cases, two programmers’ combined efforts could be producing less reliable code when compared to any one of them working alone. It started to become clear that two programmers were not necessarily twice as productive as one. These observations contradicted the conventional wisdom of the industrial age. The software industry definitely needed new methods to make development efforts more predictable. Most of the tools and concepts we take for granted today flourished out of this crisis: source code control systems, structured programming, and even object oriented programming, to name just a few.

The Basics of make
One of the important products of the period is the make utility, which consists of a simple, yet expandable rule evaluation engine geared towards producing binary executables from source files and libraries along with a makefile consisting of the rules. The difference between make implementations in UNIX and Windows is considerable, but the underlying concepts are the same:

Suppose you have a VB form named Startup.vb, and a VB class named Helper.vb, which are used to build your application, VBApp.exe. The dependency between these three files is represented in the makefile as follows:

VBApp.exe: Startup.vb Helper.vb	vbc /out:VBApp.exe Startup.vb Helper.vb

The first line establishes Startup.vb and Helper.vb as two prerequisites for VBApp.exe, whereas the second line tells the utility how to produce VBApp.exe using the two. Note that the second line is indented with a tab. This marks it as a command line, rather than a dependency line. You can add as many command lines as you wish to produce VBApp.exe, just make sure to start each line with a tab.

The command line to invoke make is simple:

nmake VBApp.exe

When invoked, make looks for makefile in the directory in which it’s been invoked. make then parses the makefile looking for instructions on how to build the target, VBApp.exe. Here, the target depends on Startup.vb and Helper.vb, resulting in the invocation of the Visual Basic compiler.

You can add as many levels of dependencies as possible. For a slightly more complicated project, VBApp.exe may depend on Startup.vb and Helper.dll, which could be a C# DLL consisting of one class, in file Helper.cs. In this case, there would be two inference rules in your file:

VBApp.exe: Startup.vb Helper.dll	vbc /out:VBApp.exe /r:Helper.dll Startup.vbHelper.dll: Helper.cs	csc /out:Helper.dll /t:library Helper.cs

To produce the Helper.dll without worrying about VBApp.exe, simply type nmake Helper.dll. Make evaluates rules that pertain only to the specific, desired target. It leaves out unrelated portions of the makefile.

By now, the idea and the opportunities that make presents should be clear: Every dependency is processed recursively until the topmost level is satisfied; and the evaluation tree can be as deep or wide as you like.

Using make with Visual Studio .NET
Although the command line tool provided with Visual Studio is ample for many projects, its integration with Visual Studio is, at best, minimal. You are given the option to map three core actions within the GUI environment to commands by using the Project Properties page, as shown in Figure 1.

Figure 1: Mapping Visual Studio GUI Actions to Build Commands
  • Build: corresponds to Build Project in the Build menu. The action should result in creating the target file entered in the Output box.
  • Clean: triggered when you choose Clean Project from the Build menu, resulting in the removal of all changes produced by Build.
  • Rebuild: is equivalent to a clean followed by a build. It handles the to Rebuild Project command in the Build menu.

What if you receive a make project that you want to port to Visual Studio? The short answer: there is no general way. Although make can use any program to produce the target you require, Visual Studio is a static environment, so there may not always be a suitable transformation. However, you can use the following rules of thumb:

  • Each executable produced by make corresponds to a separate project in Visual Studio. You can mimic dependencies between executables by using the Project Dependencies dialog box.
  • Supporting entities, such as automatic documentation generators or database creation scripts, are good matches for the Pre-Build, Pre-Link, and Post-Build steps in a Visual C++ project.
  • Any set of rules that cannot be translated, because they require a third party utility or they may effect the course of the build process, can be kept in a separate make project.

Can Your Visual Studio Do That?
A significant limitation of Visual Studio is its inability to handle more than one language in a single project. This is because of the way VB and C# compilers are designed;although both compilers produce IL binaries, their output is limited to standalone executables and libraries, rather than object modules that can later be consolidated into an executable. Consequently, Visual Studio has no built-in mechanism to mix multiple CLR languages into a single executable.

Figure 2: Creating Your Project

Another limitation of Visual Studio is that it does not support modules at all (generated by the /target:module option in VB and C# compilers). However, with the help of make, you can bypass both of these shortcomings.

Say you have a project, sample.exe that consists of three source files in three different languages: C#, VB, and IL assembly. With the standard mechanisms of Visual Studio .NET, it is not possible to manage such a diverse project. But by using the make utility and doing a little bit of homework, you can use the VS.NET development environment to deal with this exotic project.

Start by creating a new project. Visual Studio supports make only within C++ projects, so your only recourse is to create a new C++ project. Name your project ” sample,” and click on Finish.

Figure 3: Project Settings

Next, fill in your application settings as shown. As mentioned earlier, you have to provide handlers for three distinct cases: Build, rebuild, and clean. Fill in boxes as in Figure 3.

  • Build: nmake Debugsample.exe
  • Output: Debugsample.exe
  • Clean: nmake clean
  • Rebuild: nmake clean Debugsample.exe

Add the source files to the project by right clicking on the Source Files folder in Solution Explorer, and selecting Add New Item from the menu. In the dialog box, replace with VBClass.vb, and click Open. In the text window that appears, paste in the following code:

Public Class VBClass	Public Sub Method		System.Console.WriteLine("Hello from VBClass")	End SubEnd Class

Do the same thing with CSClass.cs:

public class CSClass {	public void Method() {		System.Console.WriteLine("Hello from CSClass");	}}

Do the same thing for If you are not familiar with IL, dont worry: it will seem cryptic at first, but this code snippet is really simple. It merely creates instances of CSClass and VBClass, and then calls their methods:

// Current assembly.assembly Sample {}// We use the following modules in Main.module extern CSClass.netmodule.file CSClass.netmodule.module extern VBClass.netmodule.file VBClass.netmodule// CLR will start here at .entrypoint.method public static void Main() cil managed {	.entrypoint	.maxstack  1	// Declare local variables	.locals init([0] class [.module CSClass.netmodule]CSClass cs,	             [1] class [.module VBClass.netmodule]VBClass vb)	// Create new instance of CSClass	newobj instance void [.module CSClass.netmodule]CSClass::.ctor()	stloc.0	ldloc.0	// Call its Method	callvirt instance void [.module CSClass.netmodule]CSClass::Method()	// Create new instance of VBClass	newobj instance void [.module VBClass.netmodule]VBClass::.ctor()	stloc.1	ldloc.1	// Call its Method	callvirt instance void [.module VBClass.netmodule]VBClass::Method()	ret}

Now that your source files are ready, you can work on your make file. Ironically, there is no straightforward way to add a makefile to a make project, so you have to follow these steps:

  • Right click on the sample project in Solution Explorer and select Add Existing Item.
  • In the Add Existing Item dialog box, change Files of Type to All Files (*.*).
  • Create a new text file by right clicking on the file listbox, and clicking on New Text Document.
  • Rename New Text Document.txt as makefile.
  • Double-click on makefile.
Figure 4: Project Settings

Now your Solution Explorer should resemble Figure 4. Double-click on makefile to edit its contents inside the development environment.

You need only a few rules: One should teach make how to generate a .netmodule file from a .vb file. You’ll need a similar rule for .cs files, like the following:

## VB and C# modules used by sample.exe#DebugVBClass.netmodule: VBClass.vb	vbc /debug /t:module /out:DebugVBClass.netmodule VBClass.vbDebugCSClass.netmodule: CSClass.cs	csc /debug /t:module /out:DebugCSClass.netmodule CSClass.cs

Finally, the executable is dependent on the two modules you previously defined, and to the IL file

## Compile into sample.exe#Debugsample.exe: DebugVBClass.netmodule DebugCSClass.netmodule	ilasm /debug /out:Debugsample.exe

As stated previously, Visual Studio needs to know three commands to integrate its environment to make: Build, rebuild, and clean. The rules thus far provide the means to build and rebuild the solution. Cleaning, on the other hand, is easily implemented by deleting everything under the Debug directory:

## Clean up for rebuild#clean:	del /q Debug*

Save all four files and choose Build Solution from the Build menu. Visual Studio will call make to generate your executable.

You can even debug, set breakpoints, and step through IL, C#, and VB code at the same time. To do that, go to Project Properties, and change the Debugger Type from Auto to Managed Only, as pictured in Figure 5. When you hit F11, the instruction pointer should be on the line where the C# class is created. Three lines later, when Method is called, you’ll be stepping into C# code, and six lines later into VB code.

Figure 5: Changing Debugger Type to Managed Only

Caveat Emptor
It is apparent from these tricks and workarounds that Microsoft support for this utility is minimal. You may already have noticed that IntelliSense, which is one of the primary reasons developers use Visual Studio as opposed to a text editor, does not work with make based projects.

Perhaps the reason for this lies in the different approaches taken by the Microsoft and UNIX camps. Historically, Microsofts approach has always been product-centric. Developers are provided with the best tools to produce what they want to produce, whether it’s a Windows executable in Visual Basic or an ATL DLL in Visual C++. On the other hand, the philosophy in the UNIX camp is process-centric. Developers are provided with utilities that perform the simplest tasks best, but it is up to them to use such tools effectively. The assumption is that the ingenuity of developers will provide the best usage patterns.


Share the Post: