Projects

While scripted builds can offer a sense of straightforward control and can give the impression of quicker compile times for small changes, there are several compelling reasons why programmers should consider using structured build systems like MSBuild.

Consistency and standardization

Structured approach: MSBuild provides a consistent and standardized approach to building projects. It ensures that every build follows the same steps and rules, reducing the chances of discrepancies that can often arise with custom scripts.

Team collaboration: In a team environment, standardization is crucial. MSBuild allows different team members to work on the same project with a unified understanding of the build process, minimizing conflicts and confusion that can arise from individualized scripts.

Complexity management

Handling large projects: As projects grow in size and complexity, maintaining custom scripts for building can become increasingly cumbersome and error-prone. MSBuild is designed to handle complex dependency trees and project structures efficiently, something that’s hard to maintain manually in scripts.

Automated dependency tracking: MSBuild automatically handles dependencies between files. This means that it intelligently recompiles only what is necessary, reducing the manual effort of tracking changed files, a process that is prone to human error. Going a step further, DBL actually checks the external signatures of your dependencies to prevent a small change to a core library turning into a full rebuild of your entire codebase.

Integration with tools and ecosystems

IDE integration: MSBuild is tightly integrated with Visual Studio and other development tools. This integration provides developers with seamless experiences, such as detailed build diagnostics, easy configuration management, and immediate feedback on build errors and warnings.

Ecosystem compatibility: Using MSBuild ensures compatibility with a wide range of tools and plugins in the .NET ecosystem, including continuous integration systems.

Advanced features and flexibility

Customization and extensibility: While MSBuild provides a structured approach, it also offers extensive customization and extensibility options. Developers can define custom build steps, specify conditional builds, and integrate other tools as needed, all within a structured framework.

Cross-platform builds: Builds produced by MSBuild can be executed on Windows or Linux for both the Traditional runtime and the .NET runtime. However, for DBL code, your MSBuild currently still needs to be run on Windows.

Maintainability and future-proofing

Easier maintenance: A structured build system is generally easier to maintain and update. Changes in the build process or project structure can be implemented more systematically, without the need to rewrite scripts from scratch.

Community and support: MSBuild, being a widely used and Microsoft-supported build system, benefits from a large community, regular updates, and professional support. This status ensures that the build system remains up-to-date with the latest technology trends and best practices.

Introduction to MSBuild’s XML format

MSBuild projects are defined using XML. At the heart of an MSBuild file, with a .synproj extension for DBL projects, are various elements that describe how to build a project. For DBL, you might have custom source file extensions, but the structure remains consistent with MSBuild standards.

Older MSBuild-style projects

Traditional DBL and DBL targeting the .NET Framework is currently built using the older MSBuild-style projects.

Verbose XML schema: Traditional MSBuild project files are known for their verbosity. They contain detailed specifications for every file in the project, along with numerous property and target definitions. This verbosity often made the project files large and cumbersome to edit and maintain.

Package management: In older MSBuild projects, NuGet package references were typically managed in separate packages.config files. This approach required additional synchronization between the package configuration and the project file.

Framework targeting: Targeting multiple frameworks required more manual setup. Developers had to carefully manage conditional statements within the project file to accommodate different frameworks, making the process error-prone and complex.

Build process customization: Customizing the build process involved manually editing the project file to include various MSBuild tasks and targets. This required a deep understanding of MSBuild’s inner workings.

SDK-style projects

With the introduction of .NET Core, SDK-style projects became the standard. These projects are designed to be simpler, more concise, and easier to work with. When targeting .NET with DBL, you’ll be using SDK-style projects.

Simplified and lean structure: SDK-style project files are much leaner and more readable. They use a simplified XML schema and often require only a minimal set of elements to work. Files are included implicitly, so there’s no need to list each file individually.

Integrated package management: SDK-style projects integrate NuGet package references directly within the project file using the PackageReference node. This eliminates the need for packages.config and simplifies the management of dependencies.

Multi-targeting simplified: SDK-style projects make it easier to target multiple frameworks. Developers can specify multiple target frameworks in a single property (TargetFrameworks), greatly simplifying the process.

Cross-platform and modern tooling: These projects are designed with cross-platform support in mind and are built to work seamlessly with modern tools like the .NET CLI. This makes them more adaptable to different environments and toolchains.

Enhanced project Sdk attribute: The Sdk attribute in the project file header specifies which SDK will be used (e.g., Microsoft.NET.Sdk for .NET Core projects). This attribute abstracts much of the complexity and allows the project file to focus on the specifics of the project itself.

Knowing that there is a difference between the project styles, we’re going to try to explain the common elements that don’t really change between the two styles.

Selecting the output type

The output type of a project is specified within the <PropertyGroup> element. For a DBL application, you might be building a console app or a library. This is specified using the <OutputType> tag. For example:

<PropertyGroup>
  <OutputType>Exe</OutputType>
  <!-- TODO: Other properties -->
</PropertyGroup>

This snippet sets the output type to an executable. The following table lists the various output types available for DBL projects:


  
  
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
  
  
  OutputType
  Produces
  Traditional
  DBL
  output
  types
  application
  A
  single
  .dbr
  application
  olb
  A
  static
  library
  
  .olb
  
  elb
  A
  dynamic
  library
  (.elb
  
  mainline
  Multiple
  .dbr
  applications,
  one
  per
  source
  file
  DBL
  for
  .NET
  output
  types
  exe
  A
  single
  .NET
  executable
  library
  A
  .NET
  DLL
  
    
    
    
    
    
    
    
    
    
    
    
  

Referencing other projects

To reference other projects, such as libraries or dependencies, use the <ItemGroup> element with <ProjectReference> tags. Each reference includes the path to the other project file:

<ItemGroup>
  <ProjectReference Include="..\Library\MyLibrary.synproj" />
  <!-- TODO: Additional project references -->
</ItemGroup>

This structure allows your DBL project to integrate and use functionalities from other projects within your solution.

Adding source files

Source files are included in the project through the <ItemGroup> element, using the <Compile> tag. For DBL, you would specify each source file (.dbl) you want to include:

<ItemGroup>
  <Compile Include="src\MyProgram.dbl" />
  <!-- TODO: Other source files -->
</ItemGroup>

This ensures that MSBuild recognizes and compiles all the necessary DBL source files.

Adding include files

Include files, which might contain shared code or definitions, are also added via the <ItemGroup> element. However, because you don’t want to hand these to the DBL compiler as though they were source, you might use the <None> or <Content> tag:

<ItemGroup>
  <None Include="includes\MyIncludeFile.dbl" />
  <!-- TODO: Other include files -->
</ItemGroup>

This inclusion ensures that these files are part of the project and can be easily navigated and searched within Visual Studio but won’t be treated as top-level source files by the compiler.

Managing access to a Synergy repository

Managing common build settings

There are a few ways to manage build settings that need to be common across multiple projects. The first is to use a Directory.Build.props file. This file can be placed in the root of your repository and will be automatically included in all projects within the repository. This is a good place to put settings that are common across all projects in the repository. For example, if you want to set the default target framework for all projects in the repository to .NET 6.0, you can add the following to the Directory.Build.props file:

<Project>
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

The second way to manage common build settings is to use Common.props files. The Visual Studio integration for DBL offers direct GUI access to managing build time environment variables. Because of the particulars of how environment variables are commonly used in DBL build systems, it’s best to use a Common.props file to manage these settings. We aren’t going to cover Visual Studio instructions here as there is a wealth of YouTube videos, articles, and documentation. Knowing that you don’t need to do this manually, you can use the following snippet to add a Common.props file to your project at the top of your <Project element:

<Import Project="$(SolutionDir)Common.props" />

This snippet will import the Common.props file from the root of your solution. You can then add the following to your Common.props file to set environment variables:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <CommonEnvVars>EXEDIR=$(ProjectDir)..\$(Configuration)\$(Platform)\;SOMEOTHER_ENVVAR=blablabla</CommonEnvVars>
  </PropertyGroup>
</Project>

This code will set the EXEDIR and SOMEOTHER_ENVVAR environment variables for all projects in your solution. You can then use these environment variables in your build scripts. For example, you can use the EXEDIR environment variable to set the output directory for your build by adding/updating the following inside an active <PropertyGroup> element:

<UnevaluatedOutputPath>EXEDIR:</UnevaluatedOutputPath>

Grouping projects into solutions

Using a solution file (.sln) in an MSBuild-based build system is required to effectively manage multiple projects. A .sln file is a text file that lists the projects that make up your solution, essentially serving as a project aggregator. It allows developers to organize, build, and manage a group of related projects as a single entity. This is particularly useful when your application consists of multiple components, such as a library, a user interface, and various service modules. Each project can be developed and maintained separately, with its own set of files, resources, and dependencies. The .sln file keeps track of these projects and their dependencies, ensuring that when you build the solution, MSBuild will compile the projects in the correct order. The flexibility of solution files extends to allowing custom-named solution configurations, within which developers can selectively determine which projects to build and can specify project-level configurations (such as Debug or Release) for each, offering a tailored and granular control over the build process. This second level of configuration is powerful, but it’s very easy to get confused if you’re not careful with your naming conventions.

Creating a solution file

You likely already have a solution file for your project. If you don’t, you can create one by using the dotnet new sln command. This command creates a new solution file with the same name as the current directory. You can also specify a name for the solution file by using the -n or --name option. For example, to create a solution file named MySolution.sln, you would use the following command:

dotnet new sln -n MySolution

Adding projects to a solution file

Once you have a solution file, you can add projects to it using the dotnet sln add command. First, you will need to make sure your project file has explicitly specified its project type. This is going to feel a little bit like boilerplate, and it is, but doing things this way will ensure you know every part of your build system and will make it easier to maintain in the long run. You can check to see if you already have the required project type GUID by opening your project file and looking for something like the following structure:

<Project>
...
<PropertyGroup>
...
<ProjectTypeGuids>{BBD0F5D1-1CC4-42FD-BA4C-A96779C64378}</ProjectTypeGuids>
</PropertyGroup>
...

Here’s a list of the project type GUIDs and their meanings:


  
  
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
  
  
  Project
  Type
  GUID
  Meaning
  {BBD0F5D1
  
  1CC4
  
  42FD
  
  BA4C
  
  A96779C64378}
  DBL
  base
  project
  {7B8CF543
  
  378A
  
  4EC1
  
  BB1B
  
  98E4DC6E6820}
  Traditional
  DBL
  flavor
  
    
    
    
    
    
    
  

If you have a Traditional DBL project, you would combine the two project type GUIDs like this:

<ProjectTypeGuids>
{7B8CF543-378A-4EC1-BB1B-98E4DC6E6820};{BBD0F5D1-1CC4-42fd-BA4C-A96779C64378}
</ProjectTypeGuids>

Now that you have your project type GUIDs sorted out, in order to add a project named MyProject to the solution file, you would use the following command from the folder where the solution file is located:

dotnet sln add path/to/MyProject.synproj

If you’re missing the project type GUIDs, you’ll get an error like this:

dotnet sln add HelloWorld.synproj
Project 'D:\repos\HelloWorld\HelloWorld.synproj' has an unknown project type 
and cannot be added to the solution file. Contact your SDK provider for support.

Otherwise, you’ll see a message like this:

dotnet sln add HelloWorld.synproj
Project `HelloWorld.synproj` added to the solution.