Skip to content

Add csharp support#7398

Merged
waruqi merged 55 commits intodevfrom
csharp
Mar 17, 2026
Merged

Add csharp support#7398
waruqi merged 55 commits intodevfrom
csharp

Conversation

@waruqi
Copy link
Member

@waruqi waruqi commented Mar 13, 2026

#7342

It also works on macOS.

ruki:console ruki$ xmake f -c
checking for platform ... macosx (x86_64)
checking for Xcode SDK ... /Applications/Xcode.app (sdk: 15.2, x86_64-apple-macos15.2)
ruki:console ruki$ xmake -rv
checking for dotnet ... /usr/local/share/dotnet/dotnet
checking for unknown toolkind cs (cs) ... dotnet
checking for /usr/local/share/dotnet/dotnet ... ok
checking for flags (-p:Optimize=true) ... ok
[ 31%]: <test> linking.release test
/usr/local/share/dotnet/dotnet build build/.gens/test/macosx/x86_64/release/rules/csharp/test.csproj --nologo --configuration Release --verbosity quiet --output build/macosx/x86_64/release -p:Optimize
=true
[100%]: build ok, spent 6.122s
ruki:console ruki$ xmake run
hello xmake!

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands Xmake's capabilities by introducing first-class support for C# projects. It enables developers to define, build, and manage C# applications and libraries using Xmake's declarative syntax, seamlessly integrating with the .NET ecosystem. The changes encompass automatic project file generation, robust dotnet CLI integration, and comprehensive configuration options, making Xmake a more versatile build system for multi-language projects.

Highlights

  • C# Language Support: Introduced comprehensive support for building C# projects using a new csharp rule, enabling Xmake to manage C# applications and libraries.
  • Automatic .csproj Generation: Implemented automatic generation of .csproj files from Xmake configurations, allowing users to define C# projects without manually creating project files, while also supporting existing .csproj files.
  • dotnet CLI Integration: Integrated with the dotnet command-line interface for building, publishing, and cleaning C# targets, leveraging the native .NET build system.
  • NuGet Package Management: Added support for resolving and including NuGet package references in C# projects, enhancing dependency management for .NET ecosystems.
  • .NET SDK and Runtime Detection: Improved detection of .NET SDKs and runtimes, including detailed information from dotnet --list-sdks and dotnet --list-runtimes, and enhanced .NET Framework SDK detection on Windows.
  • Extensive C# Project Property Mapping: Provided a wide range of configurable C# project properties (e.g., TargetFramework, OutputType, ImplicitUsings, Optimize, DebugSymbols, RuntimeIdentifier) that map directly to .csproj XML elements.
  • New C# Test Projects: Added multiple test projects demonstrating various C# scenarios, including console applications, libraries, NuGet package usage, web projects, and projects with existing .csproj files.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • .gitignore
    • Added '.claude' to the ignore list for LLM-related files.
  • tests/projects/csharp/console/.gitignore
    • Added common C# build artifacts to the ignore list.
  • tests/projects/csharp/console/src/Program.cs
    • Added a basic C# console application source file.
  • tests/projects/csharp/console/test.lua
    • Added a Lua test script for building the C# console project.
  • tests/projects/csharp/console/xmake.lua
    • Added an Xmake configuration for a C# console application.
  • tests/projects/csharp/console_with_runtime_json/.gitignore
    • Added common C# build artifacts to the ignore list.
  • tests/projects/csharp/console_with_runtime_json/src/Program.cs
    • Added a C# program that reads a runtime configuration file.
  • tests/projects/csharp/console_with_runtime_json/src/runtime.json
    • Added a sample runtime configuration JSON file.
  • tests/projects/csharp/console_with_runtime_json/test.lua
    • Added a Lua test script for building the C# console project with runtime JSON.
  • tests/projects/csharp/console_with_runtime_json/xmake.lua
    • Added an Xmake configuration for a C# console application with runtime JSON.
  • tests/projects/csharp/exe_with_library/.gitignore
    • Added common C# build artifacts to the ignore list.
  • tests/projects/csharp/exe_with_library/src/app/Program.cs
    • Added a C# executable source file that uses a local library.
  • tests/projects/csharp/exe_with_library/src/lib/Greeter.cs
    • Added a C# library source file.
  • tests/projects/csharp/exe_with_library/test.lua
    • Added a Lua test script for building the C# executable with a library.
  • tests/projects/csharp/exe_with_library/xmake.lua
    • Added Xmake configurations for a C# executable and its dependent library.
  • tests/projects/csharp/exe_with_package/.gitignore
    • Added common C# build artifacts to the ignore list.
  • tests/projects/csharp/exe_with_package/src/Program.cs
    • Added a C# executable source file that uses a NuGet package.
  • tests/projects/csharp/exe_with_package/test.lua
    • Added a Lua test script for building the C# executable with a NuGet package.
  • tests/projects/csharp/exe_with_package/xmake.lua
    • Added an Xmake configuration for a C# executable with a NuGet package dependency.
  • tests/projects/csharp/existing_csproj/.gitignore
    • Added common C# build artifacts to the ignore list.
  • tests/projects/csharp/existing_csproj/Program.cs
    • Added a basic C# console application source file.
  • tests/projects/csharp/existing_csproj/existing_csproj.csproj
    • Added an existing C# project file.
  • tests/projects/csharp/existing_csproj/test.lua
    • Added a Lua test script for building the C# project using an existing .csproj.
  • tests/projects/csharp/existing_csproj/xmake.lua
    • Added an Xmake configuration for a C# project using an existing .csproj file.
  • tests/projects/csharp/multiple_library/.gitignore
    • Added common C# build artifacts to the ignore list.
  • tests/projects/csharp/multiple_library/src/libalpha/Alpha.cs
    • Added a C# library source file for 'libalpha'.
  • tests/projects/csharp/multiple_library/src/libbeta/Beta.cs
    • Added a C# library source file for 'libbeta' that depends on 'libalpha'.
  • tests/projects/csharp/multiple_library/src/sample/Program.cs
    • Added a C# program source file that uses 'libbeta'.
  • tests/projects/csharp/multiple_library/test.lua
    • Added a Lua test script for building the C# project with multiple libraries.
  • tests/projects/csharp/multiple_library/xmake.lua
    • Added Xmake configurations for a C# project with multiple library dependencies.
  • tests/projects/csharp/web_project/.gitignore
    • Added common C# build artifacts to the ignore list.
  • tests/projects/csharp/web_project/src/Program.cs
    • Added a basic C# web application source file.
  • tests/projects/csharp/web_project/test.lua
    • Added a Lua test script for building the C# web project.
  • tests/projects/csharp/web_project/xmake.lua
    • Added an Xmake configuration for a C# web application.
  • xmake/languages/csharp/load.lua
    • Defined C# specific APIs and functions for Xmake.
  • xmake/languages/csharp/xmake.lua
    • Registered the C# language with Xmake, specifying source/target kinds and configuration options.
  • xmake/modules/core/tools/dotnet.lua
    • Implemented a Lua module for dotnet CLI interactions, including flag mappings for build properties.
  • xmake/modules/core/tools/dotnet/has_flags.lua
    • Added logic to detect dotnet CLI flags for various argument patterns.
  • xmake/modules/detect/sdks/find_dotnet.lua
    • Updated .NET SDK detection to use dotnet --list-sdks and dotnet --list-runtimes, and improved .NET Framework SDK detection.
  • xmake/modules/detect/tools/find_dotnet.lua
    • Added a utility to find the dotnet program and its version.
  • xmake/modules/package/manager/nuget/find_package.lua
    • Enhanced NuGet package resolution, including finding versions from project dependencies and improved path matching.
  • xmake/rules/csharp/buildcmd.lua
    • Implemented the build command for C# targets using dotnet build or dotnet publish.
  • xmake/rules/csharp/clean.lua
    • Implemented the clean command for C# targets, removing build outputs and generated project files.
  • xmake/rules/csharp/install.lua
    • Implemented the install command for C# targets using dotnet publish.
  • xmake/rules/csharp/installcmd.lua
    • Implemented the install command for C# targets using dotnet publish in batch mode.
  • xmake/rules/csharp/load.lua
    • Implemented the load phase logic for C# targets, handling .csproj file resolution and generation.
  • xmake/rules/csharp/modules/csharp_common.lua
    • Added common utility functions for C# rules, including .csproj handling, build mode mapping, and dotnet CLI options.
  • xmake/rules/csharp/modules/csproj_generator.lua
    • Implemented the core logic for generating .csproj files from Xmake target configurations.
  • xmake/rules/csharp/modules/itemgroups.lua
    • Defined how item groups (Compile, ProjectReference, PackageReference) are collected for .csproj generation.
  • xmake/rules/csharp/modules/properties.lua
    • Defined the mapping of Xmake target values to C# .csproj XML properties.
  • xmake/rules/csharp/xmake.lua
    • Registered the csharp rule, defining its extensions, source kinds, and hooks for various build phases.
  • xmake/toolchains/dotnet/xmake.lua
    • Defined the dotnet toolchain for Xmake, including its detection and configuration.
Activity
  • The pull request description provides a link to the GitHub issue, but no human activity (comments, reviews, etc.) is recorded in the provided context.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces comprehensive support for C# projects in xmake. It adds a new csharp rule, a dotnet toolchain, and integrates with the dotnet CLI for building, publishing, and managing dependencies via NuGet. The implementation is well-structured, with clear separation of concerns into modules for project generation, property mapping, and dependency detection. The test suite is also expanded with various C# project types. My review includes a few suggestions for improvement, such as fixing an invalid target framework in a test project, correcting a typo in a user-facing menu, refactoring some duplicated code, and optimizing the install rules for better performance.


<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The TargetFramework net10.0 is not a valid Target Framework Moniker (TFM). This will cause the build to fail. Please use a valid TFM, for example net8.0 if you intend to use .NET 8.

    <TargetFramework>net8.0</TargetFramework>

Comment on lines +63 to +75
{category = "Cross Complation Configuration/Compiler Configuration" }
, {nil, "cs", "kv", nil, "The C# Compiler" }

, {category = "Cross Complation Configuration/Linker Configuration" }
, {nil, "ld", "kv", nil, "The Linker" }
, {nil, "sh", "kv", nil, "The Shared Library Linker" }

, {category = "Cross Complation Configuration/Compiler Flags Configuration" }
, {nil, "csflags", "kv", nil, "The C# Compiler Flags" }

, {category = "Cross Complation Configuration/Linker Flags Configuration" }
, {nil, "ldflags", "kv", nil, "The Binary Linker Flags" }
, {nil, "shflags", "kv", nil, "The Shared Library Linker Flags" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a typo "Complation" in the category names for the configuration menu. It should be "Compilation". This is a user-facing string that appears in xmake f --menu.

                    {category = "Cross Compilation Configuration/Compiler Configuration"       }
                ,   {nil, "cs",         "kv", nil,          "The C# Compiler"                 }

                ,   {category = "Cross Compilation Configuration/Linker Configuration"         }
                ,   {nil, "ld",         "kv", nil,          "The Linker"                      }
                ,   {nil, "sh",         "kv", nil,          "The Shared Library Linker"       }

                ,   {category = "Cross Compilation Configuration/Compiler Flags Configuration" }
                ,   {nil, "csflags",    "kv", nil,          "The C# Compiler Flags"           }

                ,   {category = "Cross Compilation Configuration/Linker Flags Configuration"   }
                ,   {nil, "ldflags",    "kv", nil,          "The Binary Linker Flags"         }
                ,   {nil, "shflags",    "kv", nil,          "The Shared Library Linker Flags" }

--

-- attempt to check it from known flags
function _check_from_knownargs(flags, opt)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The opt parameter in the _check_from_knownargs function is declared but not used. It can be removed to improve code clarity.

function _check_from_knownargs(flags)

Comment on lines +31 to +63
function _find_target_root(targets, name)
local namelower = name:lower()
for targetname in pairs(targets or {}) do
local targetpkg = targetname:match("^([^/]+)") or targetname
local targetpkglower = targetpkg:lower()
local targetnamelower = targetname:lower()
local normalized_targetpkg = targetpkglower:gsub("[._%-]", "")
local normalized_name = namelower:gsub("[._%-]", "")
if targetnamelower == namelower
or targetnamelower:startswith(namelower .. "/")
or targetpkglower == namelower
or normalized_targetpkg == normalized_name then
return targetname
end
end
end

function _find_library_root(libraries, name)
local namelower = name:lower()
for libraryname in pairs(libraries or {}) do
local librarypkg = libraryname:match("^([^/]+)") or libraryname
local librarypkglower = librarypkg:lower()
local librarynamelower = libraryname:lower()
local normalized_librarypkg = librarypkglower:gsub("[._%-]", "")
local normalized_name = namelower:gsub("[._%-]", "")
if librarynamelower == namelower
or librarynamelower:startswith(namelower .. "/")
or librarypkglower == namelower
or normalized_librarypkg == normalized_name then
return libraryname
end
end
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The functions _find_target_root and _find_library_root are nearly identical. They can be refactored into a single generic function to reduce code duplication and improve maintainability.

For example, you could create a generic function _find_root(collection, name) and call it from both places:

function _find_root(collection, name)
    local namelower = name:lower()
    for itemname in pairs(collection or {}) do
        local itempkg = itemname:match("^([^"]+)") or itemname
        local itempkglower = itempkg:lower()
        local itemnamelower = itemname:lower()
        local normalized_itempkg = itempkglower:gsub("[._%-]", "")
        local normalized_name = namelower:gsub("[._%-]", "")
        if itemnamelower == namelower
           or itemnamelower:startswith(namelower .. "/")
           or itempkglower == namelower
           or normalized_itempkg == normalized_name then
            return itemname
        end
    end
end

function _find_target_root(targets, name)
    return _find_root(targets, name)
end

function _find_library_root(libraries, name)
    return _find_root(libraries, name)
end

Comment on lines +23 to +95
function main(target, opt)
local function _q(arg)
arg = tostring(arg)
if arg:find("[%s\"]") then
arg = "\"" .. arg:gsub("\"", "\\\"") .. "\""
end
return arg
end

local csprojfile = assert(csharp_common.find_or_generate_csproj(target), "target(%s): missing csharp .csproj file!", target:name())
local dotnet = csharp_common.get_dotnet_program(target)
local configuration = csharp_common.build_mode_to_configuration()
local verbosity = csharp_common.get_dotnet_verbosity()

local install_path = target:installdir()
if target:is_binary() then
install_path = target:installdir("bin")
elseif target:is_static() or target:is_shared() then
install_path = target:installdir("lib")
end
if not install_path or #install_path == 0 then
return
end

local install_abs = path.is_absolute(install_path) and install_path or path.absolute(install_path, os.projectdir())
os.mkdir(install_abs)

local rid = csharp_common.get_runtime_identifier(target)
local argv = {
"publish", csprojfile,
"--nologo",
"--configuration", configuration,
"--verbosity", verbosity,
"--output", install_abs
}
if rid and target:is_binary() then
table.join2(argv, {"--runtime", rid})
end
csharp_common.append_target_flags(target, argv)

local runopt = csharp_common.get_dotnet_runopt(csprojfile)
if os.vrunv then
os.vrunv(dotnet, argv, runopt)
elseif os.runv then
os.runv(dotnet, argv, runopt)
elseif os.execv then
os.execv(dotnet, argv, runopt)
elseif os.vrun then
local cmd = _q(dotnet)
for _, arg in ipairs(argv) do
cmd = cmd .. " " .. _q(arg)
end
os.vrun(cmd, runopt)
elseif os.run then
local cmd = _q(dotnet)
for _, arg in ipairs(argv) do
cmd = cmd .. " " .. _q(arg)
end
os.run(cmd, runopt)
else
local targetdir = target:targetdir()
if targetdir and os.isdir(targetdir) then
os.cp(path.join(targetdir, "**"), install_abs, {rootdir = targetdir})
end
end

if target:is_binary() then
local targetdir = target:targetdir()
if targetdir and os.isdir(targetdir) then
os.cp(path.join(targetdir, "**"), install_abs, {rootdir = targetdir})
end
end
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The install rule re-runs dotnet publish, which is inefficient because the build step already produces the necessary artifacts. The install step should copy artifacts from the build output directory (target:targetdir()) to the installation directory. This avoids redundant work and aligns with the typical xmake build lifecycle.

function main(target, opt)
    local install_path = target:installdir()
    if target:is_binary() then
        install_path = target:installdir("bin")
    elseif target:is_static() or target:is_shared() then
        install_path = target:installdir("lib")
    end
    if not install_path or #install_path == 0 then
        return
    end

    local install_abs = path.is_absolute(install_path) and install_path or path.absolute(install_path, os.projectdir())
    os.mkdir(install_abs)

    local targetdir = target:targetdir()
    if targetdir and os.isdir(targetdir) then
        os.cp(path.join(targetdir, "**"), install_abs, {rootdir = targetdir})
    end
end

Comment on lines +23 to +59
function main(target, batchcmds, opt)
local csprojfile = assert(csharp_common.find_or_generate_csproj(target), "target(%s): missing csharp .csproj file!", target:name())
local dotnet = csharp_common.get_dotnet_program(target)
local configuration = csharp_common.build_mode_to_configuration()
local verbosity = csharp_common.get_dotnet_verbosity()

local install_path = target:installdir()
if target:is_binary() then
install_path = target:installdir("bin")
elseif target:is_static() or target:is_shared() then
install_path = target:installdir("lib")
end

local install_abs
local argv = {
"publish", csprojfile,
"--nologo",
"--configuration", configuration,
"--verbosity", verbosity
}
if install_path and #install_path > 0 then
install_abs = path.is_absolute(install_path) and install_path or path.absolute(install_path, os.projectdir())
table.join2(argv, {"--output", install_abs})
end

local rid = csharp_common.get_runtime_identifier(target)
if rid and target:is_binary() then
table.join2(argv, {"--runtime", rid})
end
csharp_common.append_target_flags(target, argv)

batchcmds:show_progress(opt.progress, "${color.build.target}publishing.csharp.$(mode) %s", target:name())
if install_abs then
batchcmds:mkdir(install_abs)
end
batchcmds:vrunv(dotnet, argv, csharp_common.get_dotnet_runopt(csprojfile))
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the install rule, this installcmd rule re-runs dotnet publish. For efficiency, it should instead generate batch commands to copy the build artifacts from target:targetdir() to the installation directory.

function main(target, batchcmds, opt)
    local install_path = target:installdir()
    if target:is_binary() then
        install_path = target:installdir("bin")
    elseif target:is_static() or target:is_shared() then
        install_path = target:installdir("lib")
    end

    if not install_path or #install_path == 0 then
        return
    end

    local install_abs = path.is_absolute(install_path) and install_path or path.absolute(install_path, os.projectdir())
    batchcmds:mkdir(install_abs)

    local targetdir = target:targetdir()
    if targetdir and os.isdir(targetdir) then
        batchcmds:show_progress(opt.progress, "${color.build.target}installing.csharp.$(mode) %s", target:name())
        batchcmds:cp(path.join(targetdir, "**"), install_abs, {rootdir = targetdir})
    end
end

@waruqi waruqi added this to the v3.0.8 milestone Mar 16, 2026
@KaivnD
Copy link

KaivnD commented Mar 17, 2026

wow!这个可以,看了之后在想有没有可能结合SourceGenerator 编译时生成 C# 源代码(*.g.cs)来做P/Invoke的胶水代码生成,打通C#项目和C/C++的交互性。

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically.


wow! This works. After reading it, I wondered if it would be possible to combine SourceGenerator to generate C# source code (*.g.cs) during compilation to generate glue code for P/Invoke and open up the interactivity between C# projects and C/C++.

@waruqi waruqi merged commit 42dec94 into dev Mar 17, 2026
74 checks passed
@waruqi waruqi deleted the csharp branch March 17, 2026 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants