load("@rules_dotnet//dotnet/private:common.bzl", "is_debug") load("@rules_dotnet//dotnet/private:providers.bzl", "DotnetAssemblyRuntimeInfo") load(":dotnet_utils.bzl", "dotnet_preamble") def _guess_dotnet_version(label, assembly_info): if len(assembly_info.libs) == 0: fail("Cannot guess .Net version without an output dll: ", assembly_info.name) # We're going to rely on the structure of the output names for now # rather than scanning through the dependencies. If this works, # life will be good. # The dirname will be something like `bazel-out/darwin_arm64-fastbuild-ST-5c013bc87029/bin/dotnet/src/webdriver/bazelout/net5.0` # Note that the last segment of the path is the framework we're # targeting. Happy days! return assembly_info.libs[0].dirname.split("/")[::-1][0] def nuget_pack_impl(ctx): nuspec = ctx.actions.declare_file("%s-generated.nuspec" % ctx.label.name) ctx.actions.expand_template( template = ctx.file.nuspec_template, output = nuspec, substitutions = { "$packageid$": ctx.attr.id, "$version$": ctx.attr.version, }, ) build_flavor = "Debug" if is_debug(ctx) else "Release" # A mapping of files to the paths in which we expect to find them in the package paths = {} for (lib, name) in ctx.attr.libs.items(): assembly_info = lib[DotnetAssemblyRuntimeInfo] for dll in assembly_info.libs: paths[dll] = "lib/%s/%s.dll" % (_guess_dotnet_version(lib.label, assembly_info), name) for pdb in assembly_info.pdbs: paths[pdb] = "lib/%s/%s.pdb" % (_guess_dotnet_version(lib.label, assembly_info), name) for doc in assembly_info.xml_docs: paths[doc] = "lib/%s/%s.xml" % (_guess_dotnet_version(lib.label, assembly_info), name) csproj_template = """<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <AssemblyName>%s</AssemblyName> <RootNamespace>OpenQA.Selenium</RootNamespace> </PropertyGroup> </Project> """ % ctx.attr.id csproj_file = ctx.actions.declare_file("%s-generated.csproj" % ctx.label.name) ctx.actions.write(csproj_file, csproj_template) paths[csproj_file] = "project.csproj" for (file, name) in ctx.attr.files.items(): paths[file.files.to_list()[0]] = name # Zip everything up so we have the right file structure zip_file = ctx.actions.declare_file("%s-intermediate.zip" % ctx.label.name) args = ctx.actions.args() args.add_all(["Cc", zip_file]) for (file, path) in paths.items(): args.add("%s=%s" % (path, file.path)) args.add("project.nuspec=%s" % (nuspec.path)) ctx.actions.run( executable = ctx.executable._zip, arguments = [args], inputs = paths.keys() + [nuspec], outputs = [zip_file], ) # Now lay everything out on disk and execute the dotnet pack rule # Now we have everything, let's build our package toolchain = ctx.toolchains["@rules_dotnet//dotnet:toolchain_type"] nupkg_name_stem = "%s.%s" % (ctx.attr.id, ctx.attr.version) dotnet = toolchain.runtime.files_to_run.executable pkg = ctx.actions.declare_file("%s.nupkg" % nupkg_name_stem) symbols_pkg = ctx.actions.declare_file("%s.snupkg" % nupkg_name_stem) # Prepare our cache of nupkg files packages = ctx.actions.declare_directory("%s-nuget-packages" % ctx.label.name) packages_cmd = "mkdir -p %s " % packages.path transitive_libs = depset(transitive = [l[DotnetAssemblyRuntimeInfo].deps for l in ctx.attr.libs]).to_list() package_files = depset([lib.nuget_info.nupkg for lib in transitive_libs if lib.nuget_info]).to_list() if len(package_files): packages_cmd += "&& cp " + " ".join([f.path for f in package_files]) + " " + packages.path ctx.actions.run_shell( outputs = [packages], inputs = package_files, command = packages_cmd, mnemonic = "LayoutNugetPackages", ) cmd = dotnet_preamble(toolchain) + \ "mkdir %s-working-dir && " % ctx.label.name + \ "echo $(pwd) && " + \ "$(location @bazel_tools//tools/zip:zipper) x %s -d %s-working-dir && " % (zip_file.path, ctx.label.name) + \ "cd %s-working-dir && " % ctx.label.name + \ "echo '<configuration><packageSources><clear /><add key=\"local\" value=\"%%CWD%%/%s\" /></packageSources></configuration>' >nuget.config && " % packages.path + \ "$DOTNET restore --no-dependencies && " + \ "$DOTNET pack --no-build --include-symbols -p:NuspecFile=project.nuspec --include-symbols -p:SymbolPackageFormat=snupkg -p:Configuration=%s -p:PackageId=%s -p:Version=%s -p:PackageVersion=%s -p:NuspecProperties=\"version=%s\" && " % (build_flavor, ctx.attr.id, ctx.attr.version, ctx.attr.version, ctx.attr.version) + \ "cp bin/%s/%s.%s.nupkg ../%s && " % (build_flavor, ctx.attr.id, ctx.attr.version, pkg.path) + \ "cp bin/%s/%s.%s.snupkg ../%s" % (build_flavor, ctx.attr.id, ctx.attr.version, symbols_pkg.path) cmd = ctx.expand_location( cmd, targets = [ ctx.attr._zip, ], ) ctx.actions.run_shell( outputs = [pkg, symbols_pkg], inputs = [ zip_file, dotnet, packages, ], tools = [ ctx.executable._zip, dotnet, ] + toolchain.default.files.to_list() + toolchain.runtime.default_runfiles.files.to_list() + toolchain.runtime.data_runfiles.files.to_list(), command = cmd, mnemonic = "CreateNupkg", ) return [ DefaultInfo( files = depset([pkg, symbols_pkg]), runfiles = ctx.runfiles(files = [pkg, symbols_pkg]), ), ] nuget_pack = rule( nuget_pack_impl, attrs = { "id": attr.string( doc = "Nuget ID of the package", mandatory = True, ), "version": attr.string( mandatory = True, ), "libs": attr.label_keyed_string_dict( doc = "The .Net libraries that are being published", providers = [DotnetAssemblyRuntimeInfo], ), "files": attr.label_keyed_string_dict( doc = "Mapping of files to paths within the nuget package", allow_empty = True, allow_files = True, ), "property_group_vars": attr.string_dict( doc = "Keys and values for variables declared in `PropertyGroup`s in the `csproj_file`", allow_empty = True, ), "nuspec_template": attr.label( mandatory = True, allow_single_file = True, ), "_zip": attr.label( default = "@bazel_tools//tools/zip:zipper", executable = True, cfg = "exec", ), }, toolchains = ["@rules_dotnet//dotnet:toolchain_type"], )