diff --git a/dotnet/private/launcher.bat.tpl b/dotnet/private/launcher.bat.tpl index 29f6b198..77bd3365 100644 --- a/dotnet/private/launcher.bat.tpl +++ b/dotnet/private/launcher.bat.tpl @@ -62,4 +62,4 @@ if defined args ( set args=!args:\=\\\\! set args=!args:"=\"! ) -"!dotnet_executable!" exec "!run_script!" !args! +"!dotnet_executable!" exec --additionalprobingpath "%RUNFILES_DIR%" "!run_script!" !args! diff --git a/dotnet/private/launcher.sh.tpl b/dotnet/private/launcher.sh.tpl index 710318c5..fc3cf426 100644 --- a/dotnet/private/launcher.sh.tpl +++ b/dotnet/private/launcher.sh.tpl @@ -32,4 +32,4 @@ export DOTNET_NOLOGO="1" export DOTNET_CLI_TELEMETRY_OPTOUT="1" export DOTNET_ROOT="$(dirname $(rlocation TEMPLATED_dotnet))" -exec $(rlocation TEMPLATED_dotnet) exec $(rlocation TEMPLATED_executable) "$@" +exec $(rlocation TEMPLATED_dotnet) exec --additionalprobingpath "${RUNFILES_DIR}" $(rlocation TEMPLATED_executable) "$@" diff --git a/dotnet/private/rules/common/binary.bzl b/dotnet/private/rules/common/binary.bzl index 52a78185..0adf16ac 100644 --- a/dotnet/private/rules/common/binary.bzl +++ b/dotnet/private/rules/common/binary.bzl @@ -44,7 +44,8 @@ def _collect_native_dlls(assembly_runtime_info, deps): return result def _create_launcher(ctx, runfiles, executable): - runtime = get_toolchain(ctx).runtime + toolchain = get_toolchain(ctx) + runtime = toolchain.target_runtime windows_constraint = ctx.attr._windows_constraint[platform_common.ConstraintValueInfo] launcher = ctx.actions.declare_file("{}.{}".format(executable.basename, "bat" if ctx.target_platform_has_constraint(windows_constraint) else "sh"), sibling = executable) @@ -70,7 +71,7 @@ def _create_launcher(ctx, runfiles, executable): is_executable = True, ) - runfiles.extend(get_toolchain(ctx).dotnetinfo.runtime_files) + runfiles.extend(toolchain.dotnetinfo.target_runtime_files) return launcher diff --git a/dotnet/private/tests/target_runtime/BUILD.bazel b/dotnet/private/tests/target_runtime/BUILD.bazel new file mode 100644 index 00000000..7cefa086 --- /dev/null +++ b/dotnet/private/tests/target_runtime/BUILD.bazel @@ -0,0 +1,64 @@ +load("//dotnet:toolchain.bzl", "dotnet_toolchain") +load(":target_runtime_test.bzl", "fake_executable", "resolved_target_runtime_test", "target_runtime_test") + +platform( + name = "windows_x64", + constraint_values = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +) + +config_setting( + name = "target_windows", + constraint_values = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +) + +fake_executable( + name = "runtime_host", +) + +fake_executable( + name = "runtime_windows", +) + +filegroup( + name = "host_model", + srcs = ["host_model.dll"], +) + +dotnet_toolchain( + name = "fake_dotnet_toolchain", + runtime = ":runtime_host", + target_runtime = select({ + ":target_windows": ":runtime_windows", + "//conditions:default": ":runtime_host", + }), + csharp_compiler = ":runtime_host", + fsharp_compiler = ":runtime_host", + host_model = ":host_model", + sdk_version = "0.0.0", + runtime_version = "0.0.0", + runtime_tfm = "net0.0", + csharp_default_version = "0.0", + fsharp_default_version = "0.0", +) + +toolchain( + name = "fake_registered_toolchain", + toolchain = ":fake_dotnet_toolchain", + toolchain_type = "//dotnet:toolchain_type", +) + +target_runtime_test( + name = "target_runtime_uses_target_platform", + toolchain_under_test = ":fake_dotnet_toolchain", +) + +resolved_target_runtime_test( + name = "resolved_target_runtime_uses_target_platform", + tags = ["manual"], +) diff --git a/dotnet/private/tests/target_runtime/host_model.dll b/dotnet/private/tests/target_runtime/host_model.dll new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotnet/private/tests/target_runtime/host_model.dll @@ -0,0 +1 @@ + diff --git a/dotnet/private/tests/target_runtime/target_runtime_test.bzl b/dotnet/private/tests/target_runtime/target_runtime_test.bzl new file mode 100644 index 00000000..5c8dd7ae --- /dev/null +++ b/dotnet/private/tests/target_runtime/target_runtime_test.bzl @@ -0,0 +1,72 @@ +def _fake_executable_impl(ctx): + out = ctx.actions.declare_file(ctx.label.name + ".sh") + ctx.actions.write( + output = out, + content = "#!/usr/bin/env bash\nexit 0\n", + is_executable = True, + ) + return [DefaultInfo(executable = out, files = depset([out]))] + +fake_executable = rule( + implementation = _fake_executable_impl, + executable = True, +) + +def _windows_platform_transition_impl(_settings, _attr): + return { + "//command_line_option:platforms": "//dotnet/private/tests/target_runtime:windows_x64", + } + +_windows_platform_transition = transition( + implementation = _windows_platform_transition_impl, + inputs = [], + outputs = ["//command_line_option:platforms"], +) + +def _target_runtime_test_impl(ctx): + toolchain_under_test = ctx.attr.toolchain_under_test + if type(toolchain_under_test) == "list": + toolchain_under_test = toolchain_under_test[0] + toolchain = toolchain_under_test[platform_common.ToolchainInfo] + return _assert_windows_runtime(ctx, toolchain) + +def _resolved_target_runtime_test_impl(ctx): + toolchain = ctx.toolchains["//dotnet:toolchain_type"] + return _assert_windows_runtime(ctx, toolchain) + +def _assert_windows_runtime(ctx, toolchain): + target_runtime_files = [ + file.basename + for file in toolchain.dotnetinfo.target_runtime_files + ] + + if "runtime_windows.sh" not in target_runtime_files: + fail("Expected target runtime files to contain runtime_windows.sh, got: {}".format(target_runtime_files)) + + out = ctx.actions.declare_file(ctx.label.name + ".sh") + ctx.actions.write( + output = out, + content = "#!/usr/bin/env bash\nexit 0\n", + is_executable = True, + ) + return [DefaultInfo(executable = out)] + +target_runtime_test = rule( + implementation = _target_runtime_test_impl, + attrs = { + "toolchain_under_test": attr.label( + cfg = _windows_platform_transition, + providers = [platform_common.ToolchainInfo], + ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + }, + test = True, +) + +resolved_target_runtime_test = rule( + implementation = _resolved_target_runtime_test_impl, + test = True, + toolchains = ["//dotnet:toolchain_type"], +) diff --git a/dotnet/private/tests/use_as_tool/fsharp/BUILD.bazel b/dotnet/private/tests/use_as_tool/fsharp/BUILD.bazel index 5eaa45ab..05ff39b3 100644 --- a/dotnet/private/tests/use_as_tool/fsharp/BUILD.bazel +++ b/dotnet/private/tests/use_as_tool/fsharp/BUILD.bazel @@ -54,6 +54,13 @@ assert_contains( expected = "Hello World!", ) +sh_test( + name = "relocated_runfiles_test", + srcs = ["relocated_runfiles_test.sh"], + args = ["$(rootpath :main)"], + data = [":main"], +) + # Also test that self contained binaries are also usable as tools in custom rules publish_binary( name = "publish_self_contained", diff --git a/dotnet/private/tests/use_as_tool/fsharp/relocated_runfiles_test.sh b/dotnet/private/tests/use_as_tool/fsharp/relocated_runfiles_test.sh new file mode 100755 index 00000000..31bca012 --- /dev/null +++ b/dotnet/private/tests/use_as_tool/fsharp/relocated_runfiles_test.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +launcher="$(find "$TEST_SRCDIR" -path "*/$1" -print -quit)" +if [[ -z "$launcher" ]]; then + echo "Could not find launcher matching $1 under $TEST_SRCDIR" >&2 + exit 1 +fi +tmp="${TEST_TMPDIR}/relocated" +mkdir -p "$tmp" + +cp "$launcher" "$tmp/app" +cp -R "$TEST_SRCDIR" "$tmp/app.runfiles" + +"$tmp/app" "$tmp/output" +grep -F "Hello World!" "$tmp/output" diff --git a/dotnet/repositories.bzl b/dotnet/repositories.bzl index 572a76ff..0bf2eb94 100644 --- a/dotnet/repositories.bzl +++ b/dotnet/repositories.bzl @@ -12,10 +12,33 @@ _DOC = "Fetch external tools needed for dotnet toolchain" _ATTRS = { "dotnet_version": attr.string(mandatory = True, values = TOOL_VERSIONS.keys()), "platform": attr.string(mandatory = True, values = PLATFORMS.keys()), + "user_repository_name": attr.string(), } def _dotnet_repo_impl(repository_ctx): url = TOOL_VERSIONS[repository_ctx.attr.dotnet_version][repository_ctx.attr.platform]["url"] + user_repository_name = repository_ctx.attr.user_repository_name + if user_repository_name == "": + current_repo_suffix = "_" + repository_ctx.attr.platform + if not repository_ctx.name.endswith(current_repo_suffix): + fail("Expected repository name {} to end with {}".format(repository_ctx.name, current_repo_suffix)) + user_repository_name = repository_ctx.name[:-len(current_repo_suffix)] + + platform_config_settings = "" + target_runtime_select_entries = [] + for platform, meta in PLATFORMS.items(): + config_setting_name = "target_" + platform + platform_config_settings += """ +config_setting( + name = "{config_setting_name}", + constraint_values = {compatible_with}, +) +""".format( + config_setting_name = config_setting_name, + compatible_with = meta.compatible_with, + ) + target_runtime_select_entries.append("\":{}\": \"@{}_{}//:runtime\"".format(config_setting_name, user_repository_name, platform)) + repository_ctx.download_and_extract( url = url, integrity = TOOL_VERSIONS[repository_ctx.attr.dotnet_version][repository_ctx.attr.platform]["hash"], @@ -24,6 +47,8 @@ def _dotnet_repo_impl(repository_ctx): load("@rules_dotnet//dotnet:toolchain.bzl", "dotnet_toolchain") load("@rules_dotnet//dotnet:defs.bzl", "import_dll") +{platform_config_settings} + filegroup( name = "csc_binary", srcs = [ @@ -80,6 +105,10 @@ filegroup( dotnet_toolchain( name = "dotnet_toolchain", runtime = ":runtime", + target_runtime = select({{ + {target_runtime_select}, + "//conditions:default": ":runtime", + }}), csharp_compiler = ":csc_binary", fsharp_compiler = ":fsc_binary", host_model = ":host_model", @@ -91,6 +120,8 @@ dotnet_toolchain( visibility = ["//visibility:public"], ) """.format( + platform_config_settings = platform_config_settings, + target_runtime_select = ",\n ".join(target_runtime_select_entries), sdk_version = repository_ctx.attr.dotnet_version, runtime_version = TOOL_VERSIONS[repository_ctx.attr.dotnet_version]["runtimeVersion"], runtime_tfm = TOOL_VERSIONS[repository_ctx.attr.dotnet_version]["runtimeTfm"], @@ -129,6 +160,7 @@ def dotnet_register_toolchains(name, dotnet_version, register = True, **kwargs): name = name + "_" + platform, platform = platform, dotnet_version = dotnet_version, + user_repository_name = name, **kwargs ) if register: diff --git a/dotnet/toolchain.bzl b/dotnet/toolchain.bzl index 45b446e9..6e13be35 100644 --- a/dotnet/toolchain.bzl +++ b/dotnet/toolchain.bzl @@ -9,6 +9,10 @@ DotnetInfo = provider( "runtime_files": """Files required in runfiles to make the dotnet executable available. May be empty if the runtime_path points to a locally installed tool binary.""", + "target_runtime_path": "Path to the dotnet executable for target binaries", + "target_runtime_files": """Files required in runfiles to make the target dotnet executable available. + +May be empty if target_runtime_path points to a locally installed tool binary.""", "csharp_compiler_path": "Path to the C# compiler executable", "csharp_compiler_files": """Files required in runfiles to make the C# compiler executable available. @@ -41,6 +45,8 @@ def _dotnet_toolchain_impl(ctx): fail("Can only set one of runtime or runtime_path but both were set.") if not ctx.attr.runtime and not ctx.attr.runtime_path: fail("Must set one of runtime or runtime_path.") + if ctx.attr.target_runtime and ctx.attr.target_runtime_path: + fail("Can only set one of target_runtime or target_runtime_path but both were set.") if ctx.attr.csharp_compiler and ctx.attr.csharp_compiler_path: fail("Can only set one of csharp_compiler or csharp_compiler_path but both were set.") @@ -54,6 +60,8 @@ def _dotnet_toolchain_impl(ctx): runtime_files = [] runtime_path = ctx.attr.runtime_path + target_runtime_files = [] + target_runtime_path = ctx.attr.target_runtime_path csharp_compiler_files = [] csharp_compiler_path = ctx.attr.csharp_compiler_path @@ -65,6 +73,13 @@ def _dotnet_toolchain_impl(ctx): runtime_files = ctx.attr.runtime.files.to_list() + ctx.attr.runtime.default_runfiles.files.to_list() runtime_path = _to_manifest_path(ctx, runtime_files[0]) + if ctx.attr.target_runtime: + target_runtime_files = ctx.attr.target_runtime.files.to_list() + ctx.attr.target_runtime.default_runfiles.files.to_list() + target_runtime_path = _to_manifest_path(ctx, target_runtime_files[0]) + else: + target_runtime_files = runtime_files + target_runtime_path = runtime_path + if ctx.attr.csharp_compiler: csharp_compiler_files = ctx.attr.csharp_compiler.files.to_list() + ctx.attr.csharp_compiler.default_runfiles.files.to_list() csharp_compiler_path = _to_manifest_path(ctx, csharp_compiler_files[0]) @@ -92,6 +107,8 @@ def _dotnet_toolchain_impl(ctx): dotnetinfo = DotnetInfo( runtime_path = runtime_path, runtime_files = runtime_files, + target_runtime_path = target_runtime_path, + target_runtime_files = target_runtime_files, csharp_compiler_path = csharp_compiler_path, csharp_compiler_files = csharp_compiler_files, fsharp_compiler_path = fsharp_compiler_path, @@ -110,6 +127,7 @@ def _dotnet_toolchain_impl(ctx): dotnetinfo = dotnetinfo, template_variables = template_variables, runtime = ctx.attr.runtime, + target_runtime = ctx.attr.target_runtime or ctx.attr.runtime, csharp_compiler = ctx.attr.csharp_compiler, fsharp_compiler = ctx.attr.fsharp_compiler, host_model = ctx.attr.host_model, @@ -134,6 +152,16 @@ dotnet_toolchain = rule( doc = "Path to the dotnet CLI. Do not set if `runtime` is set", mandatory = False, ), + "target_runtime": attr.label( + doc = "The dotnet CLI used by target binaries. Defaults to runtime.", + mandatory = False, + executable = True, + cfg = "target", + ), + "target_runtime_path": attr.string( + doc = "Path to the dotnet CLI used by target binaries. Defaults to runtime_path.", + mandatory = False, + ), "csharp_compiler": attr.label( doc = "The C# compiler binary", mandatory = False,