Walkthrough: Writing a DScript SDK
September 22, 2025 · View on GitHub
In this walkthrough, we will write a DScript SDK for building a small C++ project consisting of more than one module. We will also see how one module consumes the result of another module. The example in this walkthrough can be found in Writing SDK.
Prerequisites
The example in this walkthrough runs both on Linux and Windows.
For Windows, since we are going to use g++ compiler tool to compile our example C++ program,
you need to install the latest version of Mingw-w64 via MSYS2, which provides up-to-date native builds of GNU C++ tools
and libraries. You can download the latest installer from the MSYS2 page, and then follow
the installation instructions on the MSYS2 website to install Mingw-w64 install the GNU compiler toolset.
The example in this walkthrough will use concepts that have been explained in Hello World and Using Input/Output Directories walkthroughs, so we recommend that you should to read those walkthroughs first.
A small C++ project
The project that we are going to build has the following structure:
Examples/Walkthrough/SimpleSDK/
├── Include
│ └── hello.h
└── Src
├── HelloApp
│ └── main.cpp
└── HelloLib
├── greetings.h
└── hello.cpp
The project has 2 modules, HelloLib and HelloApp. The former one creates a library that will be used to create
the application in the latter module. The Include folder contains headers needed by the .cpp files in both modules.
The following are the files in the HelloLib module:
// greetings.h
char* Greetings();
// hello.cpp
#include "hello.h"
#include "greetings.h"
#include <iostream>
void SayHello()
{
std::cout << Greetings() << std::endl;
}
char* Greetings()
{
return "Hello, World!";
}
And the following is the main.cpp file in the HelloApp module:
// main.cpp
#include "hello.h"
int main()
{
SayHello();
return 0;
}
Both modules #include the hello.h header file from the Include folder:
// hello.h
void SayHello();
We are going to write our DScript SDK in the Sdk folder adjacent to the Src and Include folders. Thus, our build
specification has the following organization:
Examples/Walkthrough/SimpleSDK/
├── config.dsc
├── Include
│ └── hello.h
├── Sdk
│ ├── module.config.dsc
│ └── MySdk.dsc
└── Src
├── HelloApp
│ ├── HelloApp.dsc
│ ├── main.cpp
│ └── module.config.dsc
└── HelloLib
├── greetings.h
├── hello.cpp
├── HelloLib.dsc
└── module.config.dsc
Simple SDK
To build the project, we need to
- compile
hello.cpptohello.o, - compile
main.cpptomain.o, and - link
hello.oandmain.otoapp.exeOne can callTransformer.executein the DScript specsHelloLib.dscandHelloApp.dscto create process pips for compilation and linking. However, this approach is not sustainable. For example, if we need to uniformly change the compiler flag, then we need to modify all DScript specs.
We write an SDK to as an abstraction of the compilation and linking processes. First, the SDK declares an interface used for compilation:
@@public
export interface CompileArgs
{
cFile: File;
includes?: File[];
includeSearchDirs?: StaticDirectory[];
optimize?: boolean;
}
This interface abstracts away the details of compiler options. For our purpose, we only need to specify the .cpp file,
the .h header files (optional), the search directories (optional), and whether or not we want to enable optimization.
The SDK also determines the compiler tool used for compilation and linking:
const compilerTool = {
exe: Context.isWindowsOS() ? f`C:/msys64/ucrt64/bin/g++.exe` : f`/usr/bin/g++`,
dependsOnCurrentHostOSDirectories: true,
prepareTempDirectory: true,
untrackedDirectoryScopes: Context.isWindowsOS()
? [d`C:/msys64/ucrt64`]
: undefined
};
Note that the compiler tool is not exposed out of the SDK. In this way, the author of the SDK can change the compiler tool
easily (e.g., g++ to clang++) without worrying that the consumer of the SDK making assumption about the compiler tool.
For compilation, we write the following compile function:
@@public
export function compile(args: CompileArgs) : File
{
const outDir = Context.getNewOutputDirectory("compile");
const objFile = p`${outDir}/${args.cFile.name.changeExtension(".o")}`;
const result = Transformer.execute({
tool: compilerTool,
arguments: [
Cmd.argument("-c"),
Cmd.flag("-O3", args.optimize),
Cmd.argument(Artifact.input(args.cFile)),
Cmd.options("-I ", Artifact.inputs(args.includeSearchDirs)),
Cmd.option("-o ", Artifact.output(objFile)),
],
dependencies: args.includes || [],
workingDirectory: d`${args.cFile.parent}`,
environmentVariables: Context.isWindowsOS()
? [{ name: "PATH", value: p`C:/msys64/ucrt64/bin` }]
: undefined
});
return result.getOutputFile(objFile);
}
The function takes a CompileArgs object as an input and returns an object file. For the compile pip, we establish
dependencies on the .cpp file by calling Artifact.input(args.cFile). We also establish directory dependencies
on all the include search directories. The output file is an object file whose location is determined by BuildXL.
The interface and function for linking object files can be written in a similar manner.
Using the SDK
To use the SDK, one simply import it. Let's take a look at the HelloApp.dsc spec file:
import * as MySdk from "MySdk";
import * as HelloLib from "HelloLib";
const includeDir = Transformer.sealSourceDirectory(d`../../Include`, Transformer.SealSourceDirectoryOption.allDirectories);
const obj = MySdk.compile({
cFile: f`main.cpp`,
includeSearchDirs: [includeDir],
optimize: true
});
const exe = MySdk.link({
objFiles: [obj, HelloLib.obj],
output: p`${Context.getMount("ObjectRoot").path}/app.exe`
});
First, we import our SDK (named "MySdk" in this case). Then, to compile main.cpp, we simply call MySdk.compile. Note
that we specify the Include directory as an input directory, so that we need to seal it first.
Next, we link main.o and hello.o resulting from compiling hello.cpp in HelloLib module. To access the value of
hello.o in HelloLib module, we import the module and select its obj declaration.
Running the example
You can run the example by invoking this command:
> ./bxl /c:config.dsc
The output of the run will be like:
Microsoft (R) Build Accelerator.
Copyright (C) Microsoft Corporation. All rights reserved.
[0:00] -- Telemetry is enabled. SessionId: bb41ed1d-0000-0000-0000-03040fecfe28
[0:05] 100.00% Processes:[3 done (0 hit), 0 executing, 0 waiting]
[0:05] -- Cache savings: 0.000% of 3 included processes. 0 excluded via filtering.
Build Succeeded
Log Directory: /BuildXL/Examples/Walkthrough/SimpleSDK/Out/Logs
There are 3 process pips: 2 compile pips (for main.cpp and hello.cpp), and 1 link pip (for linking main.o
and hello.o). After the run, the folder Out/Objects will have the app.exe file that you can run:
> ./Out/Objects/app.exe
Hello, World!