Skip to content

Commit

Permalink
Generalize the dotnet wrapper a bit
Browse files Browse the repository at this point in the history
csharp_binary needs to become a rule which emits a cc_binary that
invokes "dotnet <foo>", where "<foo>" is the actual *.dll that we
compile, to solve #71. Eventually <foo> will become a wrapper C# exe
that tweaks assembly loading to solve #9. So all of this requires the
wrapper to provide a default argv[1].

For stuff like dotnet vstest (#51) we will have argv[1] = vstest, so we
also need support for baking in argv[2].
  • Loading branch information
j3parker committed Mar 7, 2020
1 parent a1b1eae commit a3564d6
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 21 deletions.
36 changes: 36 additions & 0 deletions csharp/private/actions/wrapper.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
An action that generates code for the C++ dotnet wrapper.
"""

def write_wrapper_main_cc(ctx, name, template, dotnetexe, argv1 = None, argv2 = None):
"""Create the *.main.cc file which wraps dotnet.exe.
Args:
ctx: Rule context
name: The name of the associated target for this actione
template: The template .cc file
dotnetexe: The File object for dotnet.exe
argv1: (Optional) a value to inject as argv1
argv2: (Optional) a value to inject as argv2
Returns:
A File object to be used in a cc_binary target.
"""

main_cc = ctx.actions.declare_file("%s.main.cc" % name)

# Trim leading "../"
# e.g. ../netcore-sdk-osx/dotnet
dotnetexe_path = dotnetexe.short_path[3:]

ctx.actions.expand_template(
template = template,
output = main_cc,
substitutions = {
"{DotnetExe}": dotnetexe_path,
"{Argv1}": argv1 or "",
"{Argv2}": argv2 or "",
},
)

return main_cc
17 changes: 4 additions & 13 deletions csharp/private/rules/wrapper.bzl
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
"""
A wrapper around `dotnet` for Bazel.
"""
_TEMPLATE = "//csharp/private:wrappers/dotnet.cc"

def _dotnet_wrapper_impl(ctx):
cc_file = ctx.actions.declare_file("%s.cc" % (ctx.attr.name))
if len(ctx.files.src) < 1:
fail("No dotnet executable found in the SDK")
load("//csharp/private:actions/wrapper.bzl", "write_wrapper_main_cc")

ctx.actions.expand_template(
template = ctx.file.template,
output = cc_file,
substitutions = {
"{DotnetExe}": ctx.files.src[0].short_path[3:],
},
)
def _dotnet_wrapper_impl(ctx):
cc_file = write_wrapper_main_cc(ctx, ctx.attr.name, ctx.file.template, ctx.files.src[0])

files = depset(direct = [cc_file])
return [
Expand All @@ -29,7 +20,7 @@ dotnet_wrapper = rule(
"template": attr.label(
doc = """Path to the program that will wrap the dotnet executable.
This program will be compiled and used instead of directly calling the dotnet executable.""",
default = Label(_TEMPLATE),
default = Label("//csharp/private:wrappers/dotnet.cc"),
allow_single_file = True,
),
"src": attr.label_list(
Expand Down
49 changes: 41 additions & 8 deletions csharp/private/wrappers/dotnet.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <cstring>
#include <iostream>
#include <sstream>
#include <string>
Expand All @@ -21,17 +22,29 @@ std::string evprintf(std::string name, std::string path) {
return ss.str();
}

// Bazel template strings. Optional args may == "".
// kDotnetExe: path to dotnet.exe
// kArgv1: (optional) first arg to dotnet.exe
// kArgv2: (optional) second arg to dotnet.exe
constexpr const char* kDotnetExe = "{DotnetExe}";
constexpr const char* kArgv1 = "{Argv1}";
constexpr const char* kArgv2 = "{Argv2}";

static_assert(
!(kArgv1[0] == '\0' && kArgv2[0] != '\0'),
"kArgv2 can only be specified if kArgv1 is too"
);

int main(int argc, char** argv) {
std::string error;

auto runfiles = Runfiles::Create(argv[0], &error);

if (runfiles == nullptr) {
std::cerr << "Couldn't load runfiles: " << error << std::endl;
return 101;
}

auto dotnet = runfiles->Rlocation("{DotnetExe}");
auto dotnet = runfiles->Rlocation(kDotnetExe);
if (dotnet.empty()) {
std::cerr << "Couldn't find the .NET runtime" << std::endl;
return 404;
Expand All @@ -50,14 +63,34 @@ int main(int argc, char** argv) {
evprintf("DOTNET_CLI_TELEMETRY_OPTOUT", "1"), // disable telemetry
};

auto extra_count =
!!(std::strlen(kArgv1) != 0) +
!!(std::strlen(kArgv2) != 0);

auto dotnet_argv = new char*[extra_count + argc + 1];

// dotnet wants this to either be dotnet or dotnet.exe but doesn't have a
// preference otherwise.
auto dotnet_argv = new char*[argc + 1];
dotnet_argv[0] = (char*)"dotnet";
dotnet_argv[0] = const_cast<char*>("dotnet");

// Make sure to hold a reference to these std::string.
auto argv1 = runfiles->Rlocation(kArgv1);
auto argv2 = runfiles->Rlocation(kArgv2);

if (std::strlen(kArgv1) != 0) {
dotnet_argv[1] = const_cast<char*>(argv1.c_str());
}

if (std::strlen(kArgv2) != 0) {
dotnet_argv[2] = const_cast<char*>(argv2.c_str());
}

// Copy the rest of our arguments in
for (int i = 1; i < argc; i++) {
dotnet_argv[i] = argv[i];
dotnet_argv[extra_count + i] = argv[i];
}
dotnet_argv[argc] = nullptr;

dotnet_argv[extra_count + argc] = nullptr;

#ifdef _WIN32
// _spawnve has a limit on the size of the environment variables
Expand All @@ -83,9 +116,9 @@ int main(int argc, char** argv) {
auto result = execve(dotnet.c_str(), const_cast<char**>(dotnet_argv), envp);
#endif // _WIN32
if (result != 0) {
std::cout << "dotnet failed: " << errno << std::endl;
perror("dotnet failed");
return -1;
}

return result;
}
}

0 comments on commit a3564d6

Please sign in to comment.