diff --git a/prelude/csharp/csharp.bzl b/prelude/csharp/csharp.bzl index 8ca3b6e1efff6..fef897d804d22 100644 --- a/prelude/csharp/csharp.bzl +++ b/prelude/csharp/csharp.bzl @@ -9,42 +9,39 @@ load(":csharp_providers.bzl", "DllDepTSet", "DllReference", "DotNetLibraryInfo", "generate_target_tset_children") load(":toolchain.bzl", "CSharpToolchainInfo") -def csharp_library_impl(ctx: AnalysisContext) -> list[Provider]: +def _csharp_library_or_exe_artifact(ctx: AnalysisContext, library_or_exe_name: str, target_type: str) -> (Artifact, list[DllDepTSet]): toolchain = ctx.attrs._csharp_toolchain[CSharpToolchainInfo] - # Automatically set the output dll_name to this target's name if the caller did not specify a - # custom name. - dll_name = "{}.dll".format(ctx.attrs.name) if not ctx.attrs.dll_name else ctx.attrs.dll_name - - # Declare that this rule will produce a dll. - library = ctx.actions.declare_output(dll_name, has_content_based_path = False) + # Declare that this rule will produce a dll or exe. + library_or_exe_artifact = ctx.actions.declare_output(library_or_exe_name, has_content_based_path = False) - # Create a command invoking a wrapper script that calls csc.exe to compile the .dll. + # Create a command invoking a wrapper script that calls csc.exe to compile the .dll or the .exe. cmd = [toolchain.csc] # Add caller specified compiler flags. cmd.append(ctx.attrs.compiler_flags) # Set the output target as a .NET library. - cmd.append("/target:library") + cmd.append("/target:" + target_type) cmd.append( cmd_args( - library.as_output(), + library_or_exe_artifact.as_output(), format = "/out:{}", ) ) - # Don't include any default .NET framework assemblies like "mscorlib" or "System" unless - # explicitly requested with `/reference:{}`. This flag also stops injection of other - # default compiler flags. - cmd.append("/noconfig") + if ctx.attrs.add_hermetic_arguments: + # Don't include any default .NET framework assemblies like "mscorlib" or "System" unless + # explicitly requested with `/reference:{}`. This flag also stops injection of other + # default compiler flags. + cmd.append("/noconfig") - # Don't reference mscorlib.dll unless asked for. This is required for targets that target - # embedded platforms such as Silverlight or WASM. (Originally for Buck1 compatibility.) - cmd.append("/nostdlib") + # Don't reference mscorlib.dll unless asked for. This is required for targets that target + # embedded platforms such as Silverlight or WASM. (Originally for Buck1 compatibility.) + cmd.append("/nostdlib") - # Don't search any paths for .NET libraries unless explicitly referenced with `/lib:{}`. - cmd.append("/nosdkpath") + # Don't search any paths for .NET libraries unless explicitly referenced with `/lib:{}`. + cmd.append("/nosdkpath") # Let csc know the directory path where it can find system assemblies. This is the path # that is searched by `/reference:{libname}` if `libname` is just a DLL name. @@ -64,15 +61,38 @@ def csharp_library_impl(ctx: AnalysisContext) -> list[Provider]: # Run the C# compiler to produce the output artifact. ctx.actions.run(cmd, category = "csharp_compile") + return library_or_exe_artifact, child_deps + +def csharp_library_impl(ctx: AnalysisContext) -> list[Provider]: + # Automatically set the output dll_name to this target's name if the caller did not specify a + # custom name. + dll_name = "{}.dll".format(ctx.attrs.name) if not ctx.attrs.dll_name else ctx.attrs.dll_name + + library_or_exe_artifact, child_deps = _csharp_library_or_exe_artifact(ctx, dll_name, "library") + return [ - DefaultInfo(default_output = library), + DefaultInfo(default_output = library_or_exe_artifact), DotNetLibraryInfo( - name = ctx.attrs.dll_name, - object = library, - dll_deps = ctx.actions.tset(DllDepTSet, value = DllReference(reference = library), children = child_deps), + name = library_or_exe_artifact.basename, + object = library_or_exe_artifact, + dll_deps = ctx.actions.tset(DllDepTSet, value = DllReference(reference = library_or_exe_artifact), children = child_deps), ), ] +def csharp_binary_impl(ctx: AnalysisContext) -> list[Provider]: + exe_name = "{}.exe".format(ctx.attrs.name) if not ctx.attrs.exe_name else ctx.attrs.exe_name + + library_or_exe_artifact, child_deps = _csharp_library_or_exe_artifact(ctx, exe_name, "exe") + + new_runtime_files = [] + for child in child_deps: + new_runtime_files.extend([ctx.actions.symlink_file(dll_dep.reference.basename, dll_dep.reference) for dll_dep in child.traverse()]) + + return [ + DefaultInfo(default_output = library_or_exe_artifact, other_outputs = new_runtime_files), + RunInfo(args = cmd_args(library_or_exe_artifact, hidden = new_runtime_files)), + ] + def prebuilt_dotnet_library_impl(ctx: AnalysisContext) -> list[Provider]: # Prebuilt libraries are just passed through since they are already built. return [ diff --git a/prelude/decls/dotnet_common.bzl b/prelude/decls/dotnet_common.bzl new file mode 100644 index 0000000000000..55a1f4d6786e8 --- /dev/null +++ b/prelude/decls/dotnet_common.bzl @@ -0,0 +1,14 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is dual-licensed under either the MIT license found in the +# LICENSE-MIT file in the root directory of this source tree or the Apache +# License, Version 2.0 found in the LICENSE-APACHE file in the root directory +# of this source tree. You may select, at your option, one of the +# above-listed licenses. + +# TODO(cjhopman): This was generated by scripts/hacks/rules_shim_with_docs.py, +# but should be manually edited going forward. There may be some errors in +# the generated docs, and so those should be verified to be accurate and +# well-formatted (and then delete this TODO) + +FrameworkVersion = ["net35", "net40", "net45", "net46"] diff --git a/prelude/decls/dotnet_rules.bzl b/prelude/decls/dotnet_rules.bzl index 074a1fa26cb4c..327b5317c6779 100644 --- a/prelude/decls/dotnet_rules.bzl +++ b/prelude/decls/dotnet_rules.bzl @@ -12,8 +12,32 @@ # well-formatted (and then delete this TODO) load(":common.bzl", "buck", "prelude_rule") +load(":dotnet_common.bzl", "FrameworkVersion") +load("@prelude//csharp:csharp_providers.bzl", "DotNetLibraryInfo") -FrameworkVersion = ["net35", "net40", "net45", "net46"] +_CSHARP_LIBRARY_OR_EXE_ATTRIBUTES = { + "srcs": attrs.list(attrs.source(), default = [], doc = """ + The set of C# source files to be compiled, and assembled by this rule. + Each element may be either a literal string (representing the path within this package), or a target. + """), + "framework_ver": attrs.enum(FrameworkVersion, doc = """ + The version of the .Net framework that this library targets. This is + one of """ + ", ".join(FrameworkVersion) + """. + """), + "deps": attrs.list(attrs.one_of(attrs.dep(providers = [DotNetLibraryInfo]), attrs.string()), default = [], doc = """ + The set of targets or system-provided assemblies this target depends on. Any + values that are targets must be either csharp\\_library or `prebuilt_dotnet_library` + instances. + """), + "compiler_flags": attrs.list(attrs.arg(), default = [], doc = """ + The set of additional compiler flags. + """), + "add_hermetic_arguments": attrs.bool(default = True, doc = """ + If true, the following arguments are passed to the compiler: "/noconfig", "/nostdlib" and "/nosdkpath". + These attributes prevent loading of default assemblies by the compiler so that they can be explicitly + controlled in Buck2. + """), +} csharp_library = prelude_rule( name = "csharp_library", @@ -22,9 +46,7 @@ csharp_library = prelude_rule( and dependencies by invoking csc. """, examples = """ - For more examples, check out our [integration tests](https://github.com/facebook/buck/tree/dev/test/com/facebook/buck/rust/testdata/). - - ``` + ```python csharp_library( name = 'simple', dll_name = 'Cake.dll', @@ -32,9 +54,6 @@ csharp_library = prelude_rule( srcs = [ 'Hello.cs', ], - resources = { - 'greeting.txt': '//some:target', - }, deps=[ ':other', 'System.dll', @@ -57,57 +76,59 @@ csharp_library = prelude_rule( The output name of the dll. This allows you to specify the name of the dll exactly. When this is not set, the dll will be named after the short name of the target. - """, - ), - "srcs": attrs.list( - attrs.source(), - default = [], - doc = """ - The collection of source files to compile. - """, - ), - "resources": attrs.dict( - key = attrs.string(), - value = attrs.source(), - sorted = False, - default = {}, - doc = """ - Resources that should be embedded within the built DLL. The format - is the name of the resource once mapped into the DLL as the key, and - the value being the resource that should be merged. This allows - non-unique keys to be identified quickly. - """, - ), - "framework_ver": attrs.enum( - FrameworkVersion, - doc = """ - The version of the .Net framework that this library targets. This is - one of 'net35', 'net40', 'net45' and 'net46'. - """, - ), - "deps": attrs.list( - attrs.one_of(attrs.dep(), attrs.string()), - default = [], - doc = """ - The set of targets or system-provided assemblies to rely on. Any - values that are targets must be either csharp\\_library or `prebuilt_dotnet_library` - instances. - """, - ), - "compiler_flags": attrs.list( - attrs.string(), - default = [], - doc = """ - The set of additional compiler flags to pass to the compiler. - """, - ), + """), } + | _CSHARP_LIBRARY_OR_EXE_ATTRIBUTES | buck.licenses_arg() | buck.labels_arg() | buck.contacts_arg() ), ) +csharp_binary = prelude_rule( + name = "csharp_binary", + docs = """ + A csharp\\_binary() rule builds a .Net library from the supplied set of C# source files + and dependencies by invoking csc. + """, + examples = """ + ```python + csharp_binary( + name = 'simple', + exe_name = 'Cake.exe', + framework_ver = 'net46', + srcs = [ + 'Hello.cs', + ], + deps=[ + ':other', + 'System.dll', + ], + ) + + prebuilt_dotnet_library( + name = 'other', + assembly = 'other-1.0.dll', + ) + ``` + """, + further = None, + attrs = ( + # @unsorted-dict-items + { + "exe_name": attrs.string(default = "", doc = """ + The output name of the executable. This allows you to specify the name of + the executable exactly. When this is not set, the executable will be named after + the short name of the target. + """), + } | + _CSHARP_LIBRARY_OR_EXE_ATTRIBUTES | + buck.licenses_arg() | + buck.labels_arg() | + buck.contacts_arg() + ), +) + prebuilt_dotnet_library = prelude_rule( name = "prebuilt_dotnet_library", docs = """ @@ -115,7 +136,7 @@ prebuilt_dotnet_library = prelude_rule( prebuilt .Net assembles into your .Net code. """, examples = """ - ``` + ```python prebuilt_dotnet_library( name = 'log4net', assembly = 'log4net.dll', @@ -152,5 +173,6 @@ prebuilt_dotnet_library = prelude_rule( dotnet_rules = struct( csharp_library = csharp_library, + csharp_binary = csharp_binary, prebuilt_dotnet_library = prebuilt_dotnet_library, ) diff --git a/prelude/rules_impl.bzl b/prelude/rules_impl.bzl index 7c890e96891ae..809c69d3bac03 100644 --- a/prelude/rules_impl.bzl +++ b/prelude/rules_impl.bzl @@ -22,7 +22,7 @@ load("@prelude//apple:apple_common.bzl", "apple_common") load("@prelude//apple:apple_rules_decls.bzl", "apple_rules") load("@prelude//apple:apple_rules_impl.bzl", _apple_extra_attributes = "extra_attributes", _apple_implemented_rules = "implemented_rules") load("@prelude//configurations:rules.bzl", _config_extra_attributes = "extra_attributes", _config_implemented_rules = "implemented_rules") -load("@prelude//csharp:csharp.bzl", "csharp_library_impl", "prebuilt_dotnet_library_impl") +load("@prelude//csharp:csharp.bzl", "csharp_library_impl", "csharp_binary_impl", "prebuilt_dotnet_library_impl") load("@prelude//cxx:bitcode.bzl", "llvm_link_bitcode_impl") load("@prelude//cxx:cuda.bzl", "CudaCompileStyle") load("@prelude//cxx:cxx.bzl", "cxx_binary_impl", "cxx_library_impl", "cxx_precompiled_header_impl", "cxx_test_impl", "prebuilt_cxx_library_impl") @@ -174,6 +174,7 @@ extra_implemented_rules = struct( worker_tool = worker_tool, # c# csharp_library = csharp_library_impl, + csharp_binary = csharp_binary_impl, prebuilt_dotnet_library = prebuilt_dotnet_library_impl, # c++ cxx_binary = cxx_binary_impl, @@ -266,6 +267,9 @@ _dotnet_extra_attributes = { "csharp_library": { "_csharp_toolchain": toolchains_common.csharp(), }, + "csharp_binary": { + "_csharp_toolchain": toolchains_common.csharp(), + }, } _cxx_extra_library_attrs = ( diff --git a/prelude/toolchains/csharp.bzl b/prelude/toolchains/csharp.bzl index b48d7b65c3948..52d5a508b49ae 100644 --- a/prelude/toolchains/csharp.bzl +++ b/prelude/toolchains/csharp.bzl @@ -12,10 +12,12 @@ def _system_csharp_toolchain_impl(ctx): if not host_info().os.is_windows: fail("csharp toolchain only supported on windows for now") + csc = ctx.attrs.csc or ctx.attrs._csharp_tools_info[CSharpToolchainInfo].csc + return [ DefaultInfo(), CSharpToolchainInfo( - csc = RunInfo(args = ctx.attrs.csc), + csc = RunInfo(args = csc), framework_dirs = { "net35": "C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v3.5\\Profile\\Client", "net40": "C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0", @@ -42,12 +44,13 @@ system_csharp_toolchain = rule( visibility = ["PUBLIC"], )""", attrs = { - "csc": attrs.string(default = "csc.exe", doc = "Executable name or a path to the C# compiler frequently referred to as csc.exe"), + "csc": attrs.option(attrs.string(), default = None, doc = "Executable name or a path to the C# compiler frequently referred to as csc.exe. If not provided, the csc compiler is inferred from the CSharpToolchainInfo provided in the _csharp_tools_info attribute."), "framework_dirs": attrs.dict( key = attrs.string(), value = attrs.one_of(attrs.source(), attrs.string()), - doc = "Dictionary of .NET framework assembly directories, where each key is a supported value in `framework_ver` and the value is a path to a directory containing .net assemblies such as System.dll matching the given framework version", + doc = "Dictionary of .NET framework assembly directories, where each key is a supported value in `framework_ver` and the value is a path to a directory containing .net assemblies such as System.dll matching the given framework version" ), + "_csharp_tools_info": attrs.exec_dep(providers = [CSharpToolchainInfo], default = "prelude//toolchains/msvc:roslyn_tools"), }, is_toolchain_rule = True, ) diff --git a/prelude/toolchains/msvc/BUCK b/prelude/toolchains/msvc/BUCK index 42d4db5370d7a..789f1237b6244 100644 --- a/prelude/toolchains/msvc/BUCK +++ b/prelude/toolchains/msvc/BUCK @@ -1,5 +1,5 @@ load("@prelude//utils:source_listing.bzl", "source_listing") -load(":tools.bzl", "find_msvc_tools") +load(":tools.bzl", "find_msvc_tools", "find_roslyn_tools") oncall("build_infra") @@ -22,3 +22,9 @@ find_msvc_tools( target_compatible_with = ["config//os:windows"], visibility = ["PUBLIC"], ) + +find_roslyn_tools( + name = "roslyn_tools", + target_compatible_with = ["config//os:windows"], + visibility = ["PUBLIC"], +) diff --git a/prelude/toolchains/msvc/tools.bzl b/prelude/toolchains/msvc/tools.bzl index e7903f2ffdece..1d8aa167cf366 100644 --- a/prelude/toolchains/msvc/tools.bzl +++ b/prelude/toolchains/msvc/tools.bzl @@ -6,6 +6,7 @@ # of this source tree. You may select, at your option, one of the # above-listed licenses. +load("@prelude//csharp:toolchain.bzl", "CSharpToolchainInfo") load("@prelude//cxx:cxx_toolchain_types.bzl", "LinkerType") load("@prelude//os_lookup:defs.bzl", "ScriptLanguage") load("@prelude//toolchains:cxx.bzl", "CxxToolsInfo") @@ -177,13 +178,64 @@ def _windows_linker_wrapper(ctx: AnalysisContext, linker: [cmd_args, str]) -> cm language = ScriptLanguage("bat"), ) +def _find_roslyn_tools_impl(ctx: AnalysisContext) -> list[Provider]: + csc_exe_json = ctx.actions.declare_output("csc.exe.json") + + cmd = [ + ctx.attrs.vswhere[RunInfo], + cmd_args(csc_exe_json.as_output(), format="--csc={}"), + ] + + ctx.actions.run( + cmd, + category = "vswhere", + local_only = True, + ) + + if ctx.attrs.use_path_compilers: + csc_exe_script = "csc.exe" + else: + run_msvc_tool = ctx.attrs.run_msvc_tool[RunInfo] + csc_exe_script = cmd_script( + actions = ctx.actions, + name = "csc", + cmd = cmd_args(run_msvc_tool, csc_exe_json), + language = ScriptLanguage("bat"), + ) + + return [ + DefaultInfo(sub_targets = { + "csc.exe": [ + RunInfo(args = [csc_exe_script]), + DefaultInfo(sub_targets = { + "json": [DefaultInfo(default_output = csc_exe_json)], + }), + ], + }), + CSharpToolchainInfo( + csc = csc_exe_script + ), + ] + +_MSVC_TOOL_ATTRIBUTES = { + "run_msvc_tool": attrs.default_only(attrs.dep(providers = [RunInfo], default = "prelude//toolchains/msvc:run_msvc_tool")), + "vswhere": attrs.default_only(attrs.dep(providers = [RunInfo], default = "prelude//toolchains/msvc:vswhere")), +} + find_msvc_tools = rule( impl = _find_msvc_tools_impl, attrs = { "linker_wrapper": attrs.default_only(attrs.exec_dep(providers = [RunInfo], default = "prelude//cxx/tools:linker_wrapper")), - "run_msvc_tool": attrs.default_only(attrs.dep(providers = [RunInfo], default = "prelude//toolchains/msvc:run_msvc_tool")), "use_path_compilers": attrs.bool(default = False), "use_path_linkers": attrs.bool(default = False), - "vswhere": attrs.default_only(attrs.dep(providers = [RunInfo], default = "prelude//toolchains/msvc:vswhere")), - }, + } + | _MSVC_TOOL_ATTRIBUTES, +) + +find_roslyn_tools = rule( + impl = _find_roslyn_tools_impl, + attrs = { + "use_path_compilers": attrs.bool(default = False), + } + | _MSVC_TOOL_ATTRIBUTES, ) diff --git a/prelude/toolchains/msvc/vswhere.py b/prelude/toolchains/msvc/vswhere.py index e5e5530551cba..95f9ba1f00573 100644 --- a/prelude/toolchains/msvc/vswhere.py +++ b/prelude/toolchains/msvc/vswhere.py @@ -10,6 +10,8 @@ # Translated from the Rust `cc` crate's windows_registry.rs. # https://github.com/rust-lang/cc-rs/blob/1.0.79/src/windows_registry.rs +from __future__ import annotations + import argparse import json import os @@ -19,7 +21,7 @@ import tempfile import winreg from pathlib import Path -from typing import IO, NamedTuple +from typing import IO, Any, NamedTuple VC_EXE_NAMES = ["cl.exe", "cvtres.exe", "lib.exe", "ml64.exe", "link.exe"] UCRT_EXE_NAMES = ["rc.exe"] @@ -33,6 +35,7 @@ class OutputJsonFiles(NamedTuple): ml64: IO[str] link: IO[str] rc: IO[str] + csc: IO[str] class Tool(NamedTuple): @@ -42,7 +45,7 @@ class Tool(NamedTuple): INCLUDE: list[Path] = [] -def find_in_path(executable, is_optional=False): +def find_in_path(executable: str, is_optional: bool = False) -> Tool | None: which = shutil.which(executable) if which is None: if is_optional: @@ -50,10 +53,10 @@ def find_in_path(executable, is_optional=False): else: print(f"{executable} not found in $PATH", file=sys.stderr) sys.exit(1) - return Tool(which) + return Tool(Path(which)) -def find_with_vswhere_exe(): +def run_vswhere(required_component: str) -> list[dict[str, Any]]: program_files = os.environ.get("ProgramFiles(x86)") if program_files is None: program_files = os.environ.get("ProgramFiles") @@ -73,7 +76,7 @@ def find_with_vswhere_exe(): "-products", "*", "-requires", - "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + required_component, "-format", "json", "-utf8", @@ -92,6 +95,12 @@ def find_with_vswhere_exe(): reverse=True, ) + return vswhere_json + + +def find_msvc_with_vswhere_exe() -> list[Tool | None]: + vswhere_json = run_vswhere("Microsoft.VisualStudio.Component.VC.Tools.x86.x64") + for vs_instance in list(vswhere_json): installation_path = Path(vs_instance["installationPath"]) @@ -126,8 +135,8 @@ def find_with_vswhere_exe(): LIB.append(ucrt / "lib" / ucrt_version / "ucrt" / "x64") INCLUDE.append(ucrt / "include" / ucrt_version / "ucrt") - ucrt_exe_paths = [ucrt_bin_path / exe for exe in UCRT_EXE_NAMES] - ucrt_exe_paths = [exe if exe.exists() else None for exe in ucrt_exe_paths] + ucrt_exe_paths_not_checked = [ucrt_bin_path / exe for exe in UCRT_EXE_NAMES] + ucrt_exe_paths = [exe if exe.exists() else None for exe in ucrt_exe_paths_not_checked] else: ucrt_exe_paths = [None for exe in UCRT_EXE_NAMES] @@ -141,7 +150,7 @@ def find_with_vswhere_exe(): INCLUDE.append(sdk / "include" / sdk_version / "shared") return [ - Tool(exe=exe, LIB=LIB, PATH=PATH, INCLUDE=INCLUDE) + None if exe == None else Tool(exe=exe, LIB=LIB, PATH=PATH, INCLUDE=INCLUDE) for exe in vc_exe_paths + ucrt_exe_paths ] @@ -153,13 +162,39 @@ def find_with_vswhere_exe(): sys.exit(1) +def find_roslyn_with_vswhere_exe() -> Tool: + vswhere_json = run_vswhere("Microsoft.VisualStudio.Component.Roslyn.Compiler") + + for vs_instance in list(vswhere_json): + installation_path = Path(vs_instance["installationPath"]) + + roslyn_path = ( + installation_path + / "MSBuild" + / "Current" + / "Bin" + / "Roslyn" + / "csc.exe" + ) + + if not roslyn_path.exists(): + continue + + return Tool(exe=roslyn_path, LIB=[], PATH=[], INCLUDE=[]) + + print( + "vswhere.exe did not find a suitable Roslyn toolchain containing csc.exe", + file=sys.stderr, + ) + sys.exit(1) + # To find the Universal CRT we look in a specific registry key for where all the # Universal CRTs are located and then sort them asciibetically to find the # newest version. While this sort of sorting isn't ideal, it is what vcvars does # so that's good enough for us. # # Returns a pair of (root, version) for the ucrt dir if found. -def get_ucrt_dir(): +def get_ucrt_dir() -> tuple[Path, str | None]: registry = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) key_name = "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots" registry_key = winreg.OpenKey(registry, key_name) @@ -187,7 +222,7 @@ def get_ucrt_dir(): # sdk version if one is already configured. # # Returns a pair of (root, version). -def get_sdk10_dir(): +def get_sdk10_dir() -> tuple[Path, str | None]: windows_sdk_dir = os.environ.get("WindowsSdkDir") windows_sdk_version = os.environ.get("WindowsSDKVersion") if windows_sdk_dir is not None and windows_sdk_version is not None: @@ -212,7 +247,7 @@ def get_sdk10_dir(): return installation_folder, max_version -def write_tool_json(out, tool): +def write_tool_json(out: IO[str], tool: Tool) -> None: j = json.dumps( tool._asdict(), indent=4, @@ -222,7 +257,7 @@ def write_tool_json(out, tool): # for use with the ewdk to grab the environment strings -def get_ewdk_env(ewdkdir: Path): +def get_ewdk_env(ewdkdir: Path) -> dict[str, str]: """ Inspiration taken from the following: http://pythonwise.blogspot.fr/2010/04/sourcing-shell-script.html (Miki Tebeka) @@ -238,7 +273,7 @@ def get_ewdk_env(ewdkdir: Path): print("set", file=tmp) env_script = ewdkdir / "BuildEnv" / "SetupBuildEnv.cmd" - cmd = [tmp.name, env_script, "amd64"] + cmd = [tmp.name, str(env_script), "amd64"] output = subprocess.check_output(cmd).decode("utf-8") env = {} @@ -250,7 +285,7 @@ def get_ewdk_env(ewdkdir: Path): return env -def find_with_ewdk(ewdkdir: Path): +def find_with_ewdk(ewdkdir: Path) -> list[Tool | None]: env = get_ewdk_env(ewdkdir) installation_path = Path(env["VSINSTALLDIR"]) @@ -275,8 +310,8 @@ def find_with_ewdk(ewdkdir: Path): LIB.append(ucrt / "lib" / ucrt_version / "ucrt" / "x64") INCLUDE.append(ucrt / "include" / ucrt_version / "ucrt") - ucrt_exe_paths = [ucrt_bin_path / exe for exe in UCRT_EXE_NAMES] - ucrt_exe_paths = [exe if exe.exists() else None for exe in ucrt_exe_paths] + ucrt_exe_paths_not_checked = [ucrt_bin_path / exe for exe in UCRT_EXE_NAMES] + ucrt_exe_paths = [exe if exe.exists() else None for exe in ucrt_exe_paths_not_checked] else: ucrt_exe_paths = [None for exe in UCRT_EXE_NAMES] @@ -291,43 +326,55 @@ def find_with_ewdk(ewdkdir: Path): INCLUDE.append(sdk / "include" / sdk_version / "shared") return [ - Tool(exe=bin_path / exe, LIB=LIB, PATH=PATH, INCLUDE=INCLUDE) + None if exe == None else Tool(exe=bin_path / exe, LIB=LIB, PATH=PATH, INCLUDE=INCLUDE) for exe in vc_exe_paths + ucrt_exe_paths ] -def main(): +def main() -> None: parser = argparse.ArgumentParser() - parser.add_argument("--cl", type=argparse.FileType("w"), required=True) - parser.add_argument("--cvtres", type=argparse.FileType("w"), required=True) - parser.add_argument("--lib", type=argparse.FileType("w"), required=True) - parser.add_argument("--ml64", type=argparse.FileType("w"), required=True) - parser.add_argument("--link", type=argparse.FileType("w"), required=True) - parser.add_argument("--rc", type=argparse.FileType("w"), required=True) + parser.add_argument("--cl", type=argparse.FileType("w")) + parser.add_argument("--cvtres", type=argparse.FileType("w")) + parser.add_argument("--lib", type=argparse.FileType("w")) + parser.add_argument("--ml64", type=argparse.FileType("w")) + parser.add_argument("--link", type=argparse.FileType("w")) + parser.add_argument("--rc", type=argparse.FileType("w")) + parser.add_argument("--csc", type=argparse.FileType("w")) output = OutputJsonFiles(**vars(parser.parse_args())) - # If vcvars has been run, it puts these tools onto $PATH. - if "VCINSTALLDIR" in os.environ: - cl_exe, cvtres_exe, lib_exe, ml64_exe, link_exe = ( - find_in_path(exe) for exe in VC_EXE_NAMES - ) - rc_exe = find_in_path("rc.exe", is_optional=True) - elif "EWDKDIR" in os.environ: - cl_exe, cvtres_exe, lib_exe, ml64_exe, link_exe, rc_exe = find_with_ewdk( - Path(os.environ["EWDKDIR"]) - ) - else: - cl_exe, cvtres_exe, lib_exe, ml64_exe, link_exe, rc_exe = ( - find_with_vswhere_exe() - ) - - write_tool_json(output.cl, cl_exe) - write_tool_json(output.cvtres, cvtres_exe) - write_tool_json(output.lib, lib_exe) - write_tool_json(output.ml64, ml64_exe) - write_tool_json(output.link, link_exe) - write_tool_json(output.rc, rc_exe) - + if output.cl or output.cvtres or output.lib or output.ml64 or output.link or output.rc: + # If vcvars has been run, it puts these tools onto $PATH. + if "VCINSTALLDIR" in os.environ: + cl_exe, cvtres_exe, lib_exe, ml64_exe, link_exe = ( + find_in_path(exe) for exe in VC_EXE_NAMES + ) + rc_exe = find_in_path("rc.exe", is_optional=True) + elif "EWDKDIR" in os.environ: + cl_exe, cvtres_exe, lib_exe, ml64_exe, link_exe, rc_exe = find_with_ewdk( + Path(os.environ["EWDKDIR"]) + ) + else: + cl_exe, cvtres_exe, lib_exe, ml64_exe, link_exe, rc_exe = ( + find_msvc_with_vswhere_exe() + ) + + if output.cl and cl_exe: + write_tool_json(output.cl, cl_exe) + if output.cvtres and cvtres_exe: + write_tool_json(output.cvtres, cvtres_exe) + if output.lib and lib_exe: + write_tool_json(output.lib, lib_exe) + if output.ml64 and ml64_exe: + write_tool_json(output.ml64, ml64_exe) + if output.link and link_exe: + write_tool_json(output.link, link_exe) + if output.rc and rc_exe: + write_tool_json(output.rc, rc_exe) + + if output.csc: + csc_exe = find_roslyn_with_vswhere_exe() + + write_tool_json(output.csc, csc_exe) if __name__ == "__main__": main()