Sharpmake Documentation

repository·main·Indexed 22 days ago

https://github.com/ubisoft/sharpmake

A high-performance C#-based project and solution generator for Visual Studio (.vcxproj, .csproj, .sln), makefiles, and Xcode projects. Designed for large-scale, multi-platform development, it allows configuration via C# scripting and provides an extensibility system for custom platforms. Includes guidelines on project definition hierarchies, dependency management, and integration with vcpkg.

Tokens
13K
Snippets
30
Records
42
Agent score
77%

What's inside Sharpmake

  1. Overview of the CLRCPPProj sample project structure

    main

    The CLRCPPProj sample is a Dynamic Link Library (DLL) generated by AppWizard. It demonstrates a C++/CLI project structure consisting of the following key files:

    • CLRCPPProj.vcxproj: The main Visual C++ project file containing platform, configuration, and feature metadata.
    • CLRCPPProj.vcxproj.filters: An IDE-specific file that manages file grouping (e.g., associating .cpp files with the "Source Files" node).
    • CLRCPPProj.cpp: The primary source file for the DLL implementation.
    • CLRCPPProj.h: The header file containing class declarations.
    • AssemblyInfo.cpp: A file used for defining custom attributes to modify assembly metadata.
  2. What is Sharpmake

    main

    Sharpmake is a high-performance generator for Visual Studio projects (.vcxproj, .csproj) and solutions (.sln). It is designed for speed and scale, capable of generating thousands of project files in seconds.

    Key features include:

    • C# Scripting: Configuration is written in C#, allowing you to use standard IDE features like auto-completion, refactoring, and debugging.
    • Multi-platform Support: Ideal for complex game development involving multiple platforms, optimization levels, and rendering APIs.
    • Cross-platform Execution: Can generate makefiles and Xcode projects and runs on any modern OS supporting a recent .dotnet runtime.
    • Extensibility: Uses an extension system to add support for platforms or features that are not part of the core open-source repository.
  3. Understand the Samples definition format

    main

    Samples in Sharpmake are managed via a data-driven approach using the SamplesDef.json file. This file defines how samples are executed in CI pipelines (GitHub/GitLab) and via the RunSample.ps1 script.

    Each sample entry in SamplesDef.json is an object that specifies its metadata, target environments, and the sequence of commands required to run it. Commands are executed using the PowerShell Invoke-Expression cmdlet, meaning they share the same execution context; variables set in one command are available to subsequent commands.

    {
        "Name": "HelloWorld",
        "CIs": [ "github", "gitlab" ],
        "OSs": [ "windows-2019", "windows-2022" ],
        "Frameworks": [ "net6.0" ],
        "Configurations": [ "debug", "release" ],
        "TestFolder": "samples/HelloWorld",
        "Commands":
        [
            "./RunSharpmake.ps1 -workingDirectory {testFolder} -sharpmakeFile \"HelloWorld.sharpmake.cs\" -framework {framework}",
            "./Compile.ps1 -slnOrPrjFile \"helloworld_vs2019_win32.sln\" -configuration {configuration} -platform \"Win32\" -WorkingDirectory \"{testFolder}/projects\" -VsVersion {os} -compiler MsBuild",
            "&'./{testFolder}/projects/output/win32/{configuration}/helloWorld.exe'",
            "./Compile.ps1 -slnOrPrjFile \"helloworld_vs2019_win64.sln\" -configuration {configuration} -platform \"x64\" -WorkingDirectory \"{testFolder}/projects\" -VsVersion {os} -compiler MsBuild",
            "&'./{testFolder}/projects/output/win64/{configuration}/helloWorld.exe'"
        ]
    }
  4. Use Smart File Granularity with reflection strings

    main

    Sharpmake allows you to generate multiple project files from a single Project class by using reflection-based strings in the ProjectFileName property. This enables automatic file granularity based on target properties.

    Use the [property.Name] syntax to ensure values are evaluated late during the generation phase.

    [Configure]
    public void Configure(Configuration conf, Target target)
    {
        // Generates one file per platform and dev environment
        conf.ProjectFileName = "[project.Name].[target.Platform].[target.DevEnv]";
    }
  5. Available platform types in Sharpmake

    main

    Sharpmake categorizes platforms into two main types:

    1. Open Platforms: Provided via the Sharpmake.CommonPlatforms.dll extension. This includes support for generating Visual Studio solutions (C++, C#, C++/CLI) for Windows, as well as Xcode and GNU Make-based projects for Mac and Linux.
    2. NDA Platforms: These include video game consoles (e.g., Microsoft, Nintendo, Sony) which require private SDKs. These are not included in the open-source distribution but can be added at runtime via the extension mechanism if you are an authorized developer.
  6. Choose between target approaches for project definitions

    main

    When defining targets in Sharpmake, you can use one of two approaches depending on your needs:

    1. Different Target Types: Use distinct ITarget types for different projects (e.g., a library's internal target vs. a consumer's target). This makes the library's .sharpmake.cs file independent and reusable, and allows the library to expose a minimal set of targets. This requires explicit conversion methods (e.g., GetSomeLibTarget()) when adding dependencies.

    2. Same Target Type: Use the same ITarget type across all projects. This is simpler to implement and reduces code complexity, which is a common pattern used to minimize different target types in large codebases.

    Warning: Avoid using the same target type for different meanings. A Target type should be a compile-time tool, not a run-time object. Do not implement methods that return the same target type as a way to perform a conversion; instead, use explicit conversion methods to ensure type safety during dependency resolution.

    // Approach 1: Different Target Types (Better for reusable libraries)
    namespace SomeLib
    {
        class Target : ITarget { ... }
        class SomeLib : Project 
        { 
            SomeLib() : Project(typeof(Target)) { ... }
        }
    }
    namespace MyNamespace
    {
        class Target : ITarget
        {
            SomeLib.Target GetSomeLibTarget() { return ...; }
        }
        [Generate]
        class MyProject : Project
        {
            [Configure]
            void Configure(Configuration conf, Target target)
            {
                conf.AddPublicDependency<SomeLib.SomeLib>(target.GetSomeLibTarget());
            }
        }
    }
    
    // Approach 2: Same Target Type (Simpler for internal projects)
    namespace MyNamespace
    {
        class SomeLib : Project 
        { 
            SomeLib() : Project(typeof(Target)) { ... }
        }
        class Target : ITarget { ... }
        [Generate]
        class MyProject : Project
        {
            [Configure]
            void Configure(Configuration conf, Target target)
            {
                conf.AddPublicDependency<SomeLib>(target);
            }
        }
    }
  7. What are Fragments and Targets in Sharpmake

    main

    Sharpmake uses Fragments and Targets to manage build variations efficiently.

    • Fragments: These are enum types decorated with [Fragment, Flags] attributes. Each enum value must use unique bits (e.g., 0x1, 0x2, 0x4) so they can be combined using bitwise OR operations.
    • Targets: A Target is a class (implementing Sharpmake.ITarget) that holds properties of different fragment types.

    By combining fragment bits, you can define a single Target instance that represents a list of multiple targets, allowing you to scale build configurations easily.

    [Fragment, Flags]
    enum Optimization
    {
        Debug = 0x1,
        Release = 0x2,
        Retail = 0x4
    }
    
    // Combining fragments to create a target list
    new Target(
        BuildSystem.MSBuild | BuildSystem.FastBuild, 
        Optimization.Debug | Optimization.Release | Optimization.Retail, 
        ...);
  8. Define a Solution

    main

    A Solution class is used to generate .sln files. It works similarly to the Project class:

    1. Inherit from Solution.
    2. In the constructor, call AddTargets(...).
    3. Use a [Configure] method to add projects to the solution using conf.AddProject<T>(target).
    [Generate]
    class MySolution : Solution
    {
        public MySolution() : base(typeof(Target))
        {
            AddTargets(new Target(
                BuildSystem.MSBuild | BuildSystem.FastBuild,
                Optimization.Debug | Optimization.Release | Optimization.Retail,
                ...);
        }
    
        [Configure]
        public void Configure(Configuration conf, Target target)
        {
            conf.AddProject<MyProject>(target);
        }
    }
  9. Manage snapshots with Verify.Terminal

    main

    If a test fails due to an API change, you must reconcile the 'received' file with the 'verified' snapshot. Verify.Terminal is a dotnet CLI tool that allows you to manage these snapshots directly in the terminal.

    First, ensure the tool is restored:

    dotnet tool restore

    Then use the following commands to manage pending snapshots:

    # Restore the tool
    dotnet tool restore
    
    # Interactively review pending diffs
    dotnet verify review
    
    # Accept all pending snapshots (overwrites verified files)
    dotnet verify accept
    
    # Reject all pending snapshots (deletes received files)
    dotnet verify reject
  10. Extend Sharpmake with custom platforms or extensions

    main

    Sharpmake is designed to be extended for additional features or platform support (e.g., game consoles). The recommended way to extend Sharpmake is to create a separate project structure called SharpmakeExtended that wraps the core repository.

    SharpmakeExtended:
     - 📁 Sharpmake
     - 📁 Sharpmake.Platforms
     - 📁 Sharpmake.Extensions
        - Directory.build.props
        - SharpmakeExtended.sln

    Implementation Details

    1. Sharpmake (Core): This folder contains the files from the main Sharpmake Git repository. You can include it as a Git submodule in your SharpmakeExtended project.

    2. Sharpmake.Platforms / Sharpmake.Extensions: Use these folders to house your custom platform logic. Each platform should have its own folder and .csproj file:

      📁 Sharpmake.Platforms
       - 📁 Sharpmake.Platform_A
            - *.cs
            - Sharpmake.Platform_A.csproj

      The Sharpmake.Application.csproj in the core repository automatically detects and adds .csproj files from these folders to its dependency list. This ensures they are built, copied to the output folder, and ready for debugging automatically.

    3. Directory.build.props: Use this file in your extension projects to inherit the core build configuration (like target frameworks). Import the core file and then apply your overrides:

      <Project>
        <Import Project="Sharpmake/Directory.Build.props" />
        <!-- Add customization/override here -->
      </Project>
    4. SharpmakeExtended.sln: Create a custom solution file to manage both the core projects and your extensions in a single IDE instance.

    <Project>
      <!-- Rely on Sharpmake build setup -->
      <Import Project="Sharpmake/Directory.Build.props" />
    
      <!-- Add customization/override here -->
      <!-- ... -->
    </Project>
  11. Add a new sample to Sharpmake

    main
    To add a new sample to the Sharpmake ecosystem, you only need to add a new entry to the SamplesDef.json file. Once this file is committed, CI systems will automatically detect the new entry and dynamically inject a new job into the pipelines. No manual CI configuration is required.