Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support dotnet tools #337

Open
Place1 opened this issue Feb 6, 2023 · 7 comments
Open

Support dotnet tools #337

Place1 opened this issue Feb 6, 2023 · 7 comments

Comments

@Place1
Copy link
Contributor

Place1 commented Feb 6, 2023

I'd like to try and use rules_dotnet for an aspnetcore app that uses entity framework.

I can get the project to build but I'll need to be able to use the dotnet-ef cli tool for creating migrations.

Here's the command I need run; is it possible to create a bazel run target for this?

dotnet exec \
  --runtimeconfig MyApp.runtimeconfig.json \
  --depsfile MyApp.deps.json \
  --additionalprobingpath ~/.nuget/packages/ \
    bazel-myapp/external/nuget.dotnet-ef.v7.0.2/tools/net6.0/any/tools/netcoreapp2.0/any/ef.dll \
      migrations add \
        --assembly MyApp.dll \
        --startup-assembly MyApp.dll \
          my-migration-name

I was thinking to create a sh_binary target that depends on:

  • @dotnet_toolchains//:resolved_toolchain for the dotnet executable
  • :myapp which would be a csharp_library to get the built assembly, deps.json and runtimeconfig.json files.
  • @deps//dotnet-ef for the external/nuget.dotnet-ef.v7.0.2/tools/net6.0/any/tools/netcoreapp2.0/any/ef.dll
  • and something for the --additionalprobingpath flag which needs to point to the nuget packages used by the assembly (I think it just looks for Microsoft.EntityFrameworkCore.Design).

Currently I found that the nuget_archive rule doesn't expose any of the nuget package's tools/ folder so I can't access the ef.dll.

I also found that the :resolved_toolchain target didn't expose the dotnet runtime files which I submitted a PR for here: #336 (this fixed allows me to use dotnet exec)

Does anyone know how to proceed here? I'm also wondering if anyone else uses rules_dotnet with any dotnet-tools today?

@purkhusid
Copy link
Collaborator

I've been meaning to add support for this at some point and I wanted to follow a similar approach to how rules_js does this. The docs for rules_js can be seen here: https://github.com/aspect-build/rules_js/tree/main/docs#using-binaries-published-to-npm

I haven't had the time to look at the implementation in rules_js yet so I haven't really fleshed out any details though.
If you are interested in contributing this then I would recommend looking into how rules_js does this.

@Place1
Copy link
Contributor Author

Place1 commented Feb 9, 2023

Yeah that'd be great. I'm not skilled enough with bazel to figure it out unfortunately.

I think support for dotnet-tools alongside some support for generating a csproj for IDE intellisense (seperate topic) is all that's missing from rules_dotnet for my team to adopt it.

@sin-ack
Copy link
Contributor

sin-ack commented Apr 30, 2024

Hi, I would also really like to see this. The build system I'm working on at work requires running a .NET tool on the project before it can be built. Right now I'm trying to hackily implement it just to get it work for our particular case, using the package repository directly (@foo.bar.v1.2.3//:tools/net8.0/any/Foo.Bar.dll), but would love to see a proper implementation.

@njlr
Copy link
Contributor

njlr commented Apr 30, 2024

I will share my hacky work-around to run Fantomas (F# format checker) as a Bazel test:

in WORKSPACE:

http_archive(
  name = "fantomas",
  type = "zip",
  sha256 = "ddb7c3dd40d7b8892a2c16f0ac79a7b2bd1edd22099c356725a9cc92547ab188",
  urls = [ "https://www.nuget.org/api/v2/package/fantomas/5.0.0-beta-010" ],
  build_file = "@//:BUILD.fantomas",
)

BUILD.fantomas:

filegroup(
  name = "srcs",
  srcs = glob([ "**/*" ]),
  visibility = [ "//visibility:public" ],
)

fantomas.bzl:

def fantomas_check(name, editor_config, srcs, size = None):
  check = [
    native.package_name() + "/" + x for x in srcs
  ]

  native.sh_test(
    name = name,
    srcs = [ "@//:fantomas.sh" ],
    args = [
      "--check",
    ] + check,
    data = srcs + [
      editor_config,
      "//:fantomas",
    ],
    size = size,
  )

  return name

fantomas.sh:

#!/bin/bash

set -e

export HOME=$(mktemp -d || mktemp -d -t bazel-tmp)

trap "rm -rf $HOME" EXIT

export DOTNET_NOLOGO=1
export DOTNET_SKIP_FIRST_RUN_EXPERIENCE=1
export DOTNET_CLI_TELEMETRY_OPTOUT=1

EXEC_ROOT=$(pwd)

if test "${BUILD_WORKING_DIRECTORY+x}"; then
  cd $BUILD_WORKING_DIRECTORY
fi

dotnet $EXEC_ROOT/external/fantomas/tools/net6.0/any/fantomas.dll ${@:1}

Usage:

load("//:fantomas.bzl", "fantomas_check")

fantomas_check(
  name = "fantomas",
  editor_config = "//:.editorconfig",
  srcs = glob([
    "*.fs",
  ]),
)

Note this uses the system dotnet so it's not truly hermetic. If anyone knows how to wire up the rules_dotnet toolchain, that would be a great improvement!

@sin-ack
Copy link
Contributor

sin-ack commented Apr 30, 2024

The way I'm currently hooking up the rules_dotnet toolchain is doing it the way rules_dotnet does it internally, i.e. creating a new rule and consuming @rules_dotnet//dotnet:toolchain_type. The toolchain will have toolchain.dotnetinfo.runtime, which is the dotnet command you can execute. Then I just feed that into a ctx.actions.run with the manual assembly path I set up. Obviously more work than your solution, but allows me to not have to put dotnet on the CI machine.

@leryss
Copy link

leryss commented Apr 30, 2024

we implemented tool support by getting the dotnet binary out of the DotnetInfo provider of the toolchain and essentially doing a dotnet tool install .... we use the --tool-path argument for dotnet to create a local installation, the tool can be executed as long as there are some specific dotnet env variable set

this is a rule doing that:

def _dotnet_tool_binary_impl(ctx):
    info = ctx.attr._toolchain[DotnetInfo]
    dotnet_bin = info.runtime_path

    out_log = ctx.actions.declare_file(ctx.attr.name + ".log")
    out_store = ctx.actions.declare_directory(ctx.attr.name + "/.store")
    out_exec = ctx.actions.declare_file(ctx.attr.name + "/" + ctx.attr.tool_exec_name)
    out_run_exec = ctx.actions.declare_file(ctx.attr.name + "/run.sh")

    # See https://github.com/dotnet/sdk/issues/27761
    arch = ""
    if ctx.attr.is_darwin_arm64:
        arch = "--arch arm64 "

    install_cmd_parts = [
        "ROOTDIR=$(pwd)",
        "cd $(dirname {output})",
        "DOTNET_CLI_HOME=\"$ROOTDIR/$(dirname {dotnet_bin})\" \"$ROOTDIR/{dotnet_bin}\" tool install {package} {arch} --tool-path {tool_path} > $(basename {output})",
    ]

    install_cmd = ";".join(install_cmd_parts).format(
        dotnet_bin = dotnet_bin,
        package = ctx.attr.tool_install_name,
        arch = arch,
        tool_path = ctx.attr.name,
        output = out_log.path,
    )

    ctx.actions.run_shell(
        outputs = [out_log, out_store, out_exec],
        command = install_cmd,
        tools = info.runtime_files,
        toolchain = "@rules_dotnet//dotnet:resolved_toolchain",
        use_default_shell_env = True,
    )

    run_script = """#!/bin/bash
        DOTNETBIN=$(readlink \"{dotnet}\")
        cd $BUILD_WORKSPACE_DIRECTORY
        DOTNET_ROOT=$(dirname $DOTNETBIN) {tool_bin} "$@"
    """

    run_script = run_script.format(
        dotnet = dotnet_bin,
        tool_bin = out_exec.path,
    )

    ctx.actions.write(
        output = out_run_exec,
        content = run_script,
    )

    runfiles = ctx.runfiles(files = info.runtime_files + [out_exec, out_log, out_store])

    return [DefaultInfo(executable = out_run_exec, runfiles = runfiles, files = depset([out_exec, out_log, out_store]))]

_dotnet_tool_binary = rule(
    implementation = _dotnet_tool_binary_impl,
    executable = True,
    attrs = {
        "tool_exec_name": attr.string(),
        "tool_install_name": attr.string(),
        "is_darwin_arm64": attr.bool(mandatory = True),
        "_toolchain": attr.label(default = "@rules_dotnet//dotnet:resolved_toolchain"),
    },
)```

@sin-ack
Copy link
Contributor

sin-ack commented Apr 30, 2024

Cool solution, and very close to mine! The only difference for me was using the paket2bazel output for downloading the Nuget archive. That has its own downsides for the time being, of course.

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

No branches or pull requests

5 participants