- Written by Robert
- Created: 25 September 2017
In the last build of X# we have introduced a small problem in some of our project templates (more about that later) and when trying to instruct one of our customers how to fix that I realized that it might be a good idea to explain our project file format a little bit.
But first let’s look back at the past.
Some of us have started development in the DOS days and may remember how the build process worked in the DOS/Clipper time:
You had a folder full of source code, an RMake file with instructions on which files needed to be compiled and what the compiler commandline needed to be and a Link file with instructions on how to link the object files produced by the compiler with the standard Clipper libraries and sometimes also with 3rd party libraries.
The Rmake file contained rules that described the dependencies: The Exe file depended on a list of OBJ files, which in their turn depended on PRG and CH files. When one of the PRG or CH files was newer than the OBJ file (or the OBJ file was missing) then the compiler would be called and a new OBJ file was created. After that the linker was called because the OBJ file was newer than the EXE file.
Btw (x)Harbour uses a similar way to compile.
Visual Objects works a bit different: all the source code and object code is stored in a repository (a database). The Adam component inside Visual Objects manages this database and marks object code from an entity as ‘dead’ whenever the matching source code is updated or touched.
At compile time only the ‘dead’ entities are recompiled (which may lead to other entities being marked for recompilation) and finally the object code from all relevant entities is linked in an EXE or DLL. Normally this results in fast compilation time, because only changed entities have to be recompiled.
When this works, this is a great, but most of us remember the problems in the earlier versions of Visual Objects with corrupted repositories.
With X# we are back to the model when source code is stored in files on the disk. However, there is a difference with the Clipper model. Unlike Clipper (and C++, Harbour etc) no longer OBJ files are produced. The compiler receives a list of sourcefiles and compiles these and produces a EXE or DLL.
The project file (.xsproj file) contains the information that is needed to build the program. It also contains (additional) information used by Visual Studio (VS) to aid in building the project tree inside the Solution Explorer, as well as information that helps VS to decide which editor is needed for a certain file in the project file.
Let’s start with a simple project, a Console Application.
When you create a console application in Visual Studio it will create a folder with 2 files and a subfolder with one file:
The PRG files obviously contain the source to the application. The xsproj file contains the instructions on how to build the application. Let’s have a look at this file. In short it contains:
- A xml tag describing the file format
- A Project node with multiple child nodes.
Inside the project node we see
- An Import
- Various PropertyGroups with properties
- Another Import
- Various ItemGroups with items
- A ProjectExtensions tag
- More PropertyGroups
The most important elements in the project file are:
- The properties in the project groups
- The items in the itemgroups
First the itemgroups
The two files on disk are included in the itemgroups as items of type Compile. As you will suspect this indicated that they are meant for the compiler.
Other items are of type Reference. They describe the external dependencies that the compiler must use to look for types and methods not defined in the source code. In this example that is for example the System.Console class.
Other project files may have items of other types. For example: you may see items of type NativeResource (RC files) , VOBinary (XsFrm) , None (VH,XH, Settings), EmbeddedResource (ResX) ApplicationDefinition (XAML) , Page (XAML) and more.
In a second article I will describe how the build system “knows” what to do with each of the items.
If you look at the ProjectGroups you will see that some of them have a Condition attribute and others not.
The first PropertyGroup in the file has no condition and contains settings for the project that are used always. The other project groups have a condition that may looks like Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"
The properties defined in these sections can (and will) be used together with the unconditioned properties to construct a command line for the compiler and for example to instruct the build system where the output of the build process must be stored.
Some properties needed during the build process are not defined in the project file, but passed to the build process from the command line. Configuration and Platform are examples of this. VS passes these values based on the selected configuration and platform in the toolbar.
Default values for these properties are defined in the XSharp.Default.Props files which is imported by the first import line. These defaults are used when the properties are not passed from the command line.
Most managed languages in VS have at least 2 different configurations (Debug and Release) and at least 1 Platform (AnyCPU). We have chosen to follow the naming scheme from Microsoft. But these names are not mandatory. You can also create your own Configurations and Platforms. You can do that from the Build – Configuration Manager menupoint inside Visual Studio. The configuration and Platform comboboxes on this window contain the option to edit (rename) each of these or to add a new.
One of the confusing things to many people is that next to the Platform property there is also a PlatformTarget property. Inside 2nd and 3rd property group you will find a line <PlatformTarget>AnyCPU</PlatformTarget>
This property matches the “Platform Target” property on the Build page of the Project Properties for X# projects.
The Project Properties dialog “knows” which properties are used for all configurations. These properties (for example the General and the Language pages) are written to the first Propertygroup.
Other properties (Build, Build Event, Debug) can be different for different configurations. When changing these values they will be written to the matching section in the project file.
And there lies the problem in the current build
The properties dialog (or actually the project configuration manager) looks at the text of the condition and expects that there are spaces around the == operator in the condition. And the 2nd and 3rd group in the file did not have these spaces. For that reason, it was writing the properties to the 4th and 5th group.
Existing values for properties in the groups 2 and 3 were not updated.
The fix for the problem in the current build is to add the spaces with a text editor.
The build process works in a certain order. When building the command line for the compiler it takes into account the properties that are defined BEFORE the items in the project file. It does not look at properties defined after the project file.
The task that creates the command line for the compiler is defined in the file that is imported in the 2nd import line in the file. It simply does not “see” the properties that are defined after that.
Moving the 4th and 5th sections before the Itemgroups will not work. Often people use the $(TargetPath) or similar macros in a post build step to copy files from te output folder to a new location. These properties/ macros are defined in the XSharp.targets file. So if you move the lines with the PreBuild and PostBuild steps before the line that imports XSharp.targets then these macros will be empty.
To build an X# VS project you can use the menu options inside visual studio. However you can also open a Visual Studio command prompt and use MsBuild.
If you type MsBuild <ProjectFileName> on the commandline then the project will also be built.
There are many options that you can pass to MsBuild from the command line, such as values for the Configuration and Platform properties. If you omit these then there will be default values in the XSharp.Default.Props file.
If you do that then you will see that MsBuild calls the xsc compiler and that the references and files defined in the project file are passed to the compiler as command line arguments.
The references in the file are expanded to include a complete path. For example, on my machine I had selected the framework 4.5.2. The system DLLs will then be included from the folder
C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2
In a next article, I will elaborate the imported files and on how to debug this process in case you are experiencing problems.