public static void ProcessScala(
            FunctionItem function,
            bool skipCompile,
            bool noAssemblyValidation,
            string gitSha,
            string gitBranch,
            string buildConfiguration,
            string outputDirectory,
            HashSet <string> existingPackages,
            string gitInfoFilename,
            ModuleBuilder builder
            )
        {
            var showOutput     = Settings.VerboseLevel >= VerboseLevel.Detailed;
            var scalaOutputJar = ScalaPackager.Process(function, skipCompile, noAssemblyValidation, gitSha, gitBranch, buildConfiguration, showOutput);

            // compute hash for zip contents
            string hash;

            using (var zipArchive = ZipFile.OpenRead(scalaOutputJar)) {
                using (var md5 = MD5.Create())
                    using (var hashStream = new CryptoStream(Stream.Null, md5, CryptoStreamMode.Write)) {
                        foreach (var entry in zipArchive.Entries.OrderBy(e => e.FullName))
                        {
                            // hash file path
                            var filePathBytes = Encoding.UTF8.GetBytes(entry.FullName.Replace('\\', '/'));
                            hashStream.Write(filePathBytes, 0, filePathBytes.Length);

                            // hash file contents
                            using (var stream = entry.Open()) {
                                stream.CopyTo(hashStream);
                            }
                        }
                        hashStream.FlushFinalBlock();
                        hash = md5.Hash.ToHexString();
                    }
            }

            // rename function package with hash
            var package = Path.Combine(outputDirectory, $"function_{function.Name}_{hash}.jar");

            if (!existingPackages.Remove(package))
            {
                File.Move(scalaOutputJar, package);

                // add git-info.json file
                using (var zipArchive = ZipFile.Open(package, ZipArchiveMode.Update)) {
                    var entry = zipArchive.CreateEntry(gitInfoFilename);

                    // Set RW-R--R-- permissions attributes on non-Windows operating system
                    if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                    {
                        entry.ExternalAttributes = 0b1_000_000_110_100_100 << 16;
                    }
                    using (var stream = entry.Open()) {
                        stream.Write(Encoding.UTF8.GetBytes(JObject.FromObject(new ModuleManifestGitInfo {
                            SHA    = gitSha,
                            Branch = gitBranch
                        }).ToString(Formatting.None)));
                    }
                }
            }
            else
            {
                File.Delete(scalaOutputJar);
            }

            // decompress project zip into temporary folder so we can add the 'GITSHAFILE' files
            builder.AddAsset($"{function.FullName}::PackageName", package);
        }
        private void ProcessDotNet(
            FunctionItem function,
            bool noCompile,
            bool noAssemblyValidation,
            string gitSha,
            string gitBranch,
            string buildConfiguration
            )
        {
            function.Language = "csharp";

            // check if AWS Lambda Tools extension is installed
            if (!CheckDotNetLambdaToolIsInstalled())
            {
                return;
            }

            // read settings from project file
            var csproj            = XDocument.Load(function.Project, LoadOptions.PreserveWhitespace);
            var mainPropertyGroup = csproj.Element("Project")?.Element("PropertyGroup");
            var targetFramework   = mainPropertyGroup?.Element("TargetFramework").Value;
            var rootNamespace     = mainPropertyGroup?.Element("RootNamespace")?.Value;
            var projectName       = mainPropertyGroup?.Element("AssemblyName")?.Value ?? Path.GetFileNameWithoutExtension(function.Project);

            // compile function project
            Console.WriteLine($"=> Building function {function.Name} [{targetFramework}, {buildConfiguration}]");
            var projectDirectory = Path.Combine(Settings.WorkingDirectory, Path.GetFileNameWithoutExtension(function.Project));
            var temporaryPackage = Path.Combine(Settings.OutputDirectory, $"function_{function.Name}_temporary.zip");

            // check if the project contains an obsolete AWS Lambda Tools extension: <DotNetCliToolReference Include="Amazon.Lambda.Tools"/>
            var obsoleteNodes = csproj.DescendantNodes()
                                .Where(node =>
                                       (node is XElement element) &&
                                       (element.Name == "DotNetCliToolReference") &&
                                       ((string)element.Attribute("Include") == "Amazon.Lambda.Tools")
                                       )
                                .ToList();

            if (obsoleteNodes.Any())
            {
                LogWarn($"removing obsolete AWS Lambda Tools extension from {Path.GetRelativePath(Settings.WorkingDirectory, function.Project)}");
                foreach (var obsoleteNode in obsoleteNodes)
                {
                    var parent = obsoleteNode.Parent;

                    // remove obsolete node
                    obsoleteNode.Remove();

                    // remove parent if no children are left
                    if (!parent.Elements().Any())
                    {
                        parent.Remove();
                    }
                }
                csproj.Save(function.Project);
            }

            // validate the project is using the most recent lambdasharp assembly references
            if (!noAssemblyValidation && function.HasAssemblyValidation)
            {
                var includes = csproj.Element("Project")
                               ?.Elements("ItemGroup")
                               .Elements("PackageReference")
                               .Where(elem => elem.Attribute("Include")?.Value.StartsWith("LambdaSharp", StringComparison.Ordinal) ?? false);
                if (includes != null)
                {
                    foreach (var include in includes)
                    {
                        var expectedVersion    = VersionInfo.Parse($"{Settings.ToolVersion.Major}.{Settings.ToolVersion.Minor}{Settings.ToolVersion.Suffix}");
                        var library            = include.Attribute("Include").Value;
                        var libraryVersionText = include.Attribute("Version")?.Value;
                        if (libraryVersionText == null)
                        {
                            LogError($"csproj file is missing a version attribute in its assembly reference for {library} (expected version: '{expectedVersion}')");
                        }
                        else if (libraryVersionText.EndsWith(".*", StringComparison.Ordinal))
                        {
                            if (!VersionInfo.TryParse(libraryVersionText.Substring(0, libraryVersionText.Length - 2), out var libraryVersion))
                            {
                                LogError($"csproj file contains an invalid wildcard version in its assembly reference for {library} (expected version: '{expectedVersion}', found: '{libraryVersionText}')");
                            }
                            else if (!libraryVersion.IsCompatibleWith(expectedVersion))
                            {
                                LogError($"csproj file contains a mismatched assembly reference for {library} (expected version: '{expectedVersion}', found: '{libraryVersionText}')");
                            }
                        }
                        else if (!VersionInfo.TryParse(libraryVersionText, out var libraryVersion))
                        {
                            LogError($"csproj file contains an invalid version in its assembly reference for {library} (expected version: '{expectedVersion}', found: '{libraryVersionText}')");
                        }
                        else if (!libraryVersion.IsCompatibleWith(expectedVersion))
                        {
                            LogError($"csproj file contains a mismatched assembly reference for {library} (expected version: '{expectedVersion}', found: '{libraryVersionText}')");
                        }
                    }
                    if (Settings.HasErrors)
                    {
                        return;
                    }
                }
            }
            if (noCompile)
            {
                return;
            }

            // build project with AWS dotnet CLI lambda tool
            if (!DotNetLambdaPackage(targetFramework, buildConfiguration, temporaryPackage, projectDirectory))
            {
                LogError("'dotnet lambda package' command failed");
                return;
            }

            // collect sources with invoke methods
            var mappings = Enumerable.Empty <ApiGatewayInvocationMapping>()
                           .Union(function.Sources
                                  .OfType <RestApiSource>()
                                  .Where(source => source.Invoke != null)
                                  .Select(source => new ApiGatewayInvocationMapping {
                RestApi       = $"{source.HttpMethod}:/{string.Join("/", source.Path)}",
                Method        = source.Invoke,
                RestApiSource = source
            })
                                  )
                           .Union(function.Sources
                                  .OfType <WebSocketSource>()
                                  .Where(source => source.Invoke != null)
                                  .Select(source => new ApiGatewayInvocationMapping {
                WebSocket       = source.RouteKey,
                Method          = source.Invoke,
                WebSocketSource = source
            })
                                  )
                           .ToList();

            // verify the function handler can be found in the compiled assembly
            var buildFolder = Path.Combine(projectDirectory, "bin", buildConfiguration, targetFramework, "publish");

            if (function.HasHandlerValidation)
            {
                if (function.Function.Handler is string handler)
                {
                    ValidateEntryPoint(
                        buildFolder,
                        handler
                        );
                }
            }

            // create request/response schemas for invocation methods
            if (!LambdaSharpCreateInvocationSchemas(
                    buildFolder,
                    rootNamespace,
                    function.Function.Handler as string,
                    mappings
                    ))
            {
                LogError("'lash util create-invoke-methods-schema' command failed");
                return;
            }

            // add api mappings JSON file(s)
            if (mappings.Any())
            {
                using (var zipArchive = ZipFile.Open(temporaryPackage, ZipArchiveMode.Update)) {
                    var entry = zipArchive.CreateEntry(API_MAPPINGS);

                    // Set RW-R--R-- permissions attributes on non-Windows operating system
                    if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                    {
                        entry.ExternalAttributes = 0b1_000_000_110_100_100 << 16;
                    }
                    using (var stream = entry.Open()) {
                        stream.Write(Encoding.UTF8.GetBytes(JObject.FromObject(new ApiGatewayInvocationMappings {
                            Mappings = mappings
                        }).ToString(Formatting.None)));
                    }
                }
            }

            // compute hash for zip contents
            string hash;

            using (var zipArchive = ZipFile.OpenRead(temporaryPackage)) {
                using (var md5 = MD5.Create())
                    using (var hashStream = new CryptoStream(Stream.Null, md5, CryptoStreamMode.Write)) {
                        foreach (var entry in zipArchive.Entries.OrderBy(e => e.FullName))
                        {
                            // hash file path
                            var filePathBytes = Encoding.UTF8.GetBytes(entry.FullName.Replace('\\', '/'));
                            hashStream.Write(filePathBytes, 0, filePathBytes.Length);

                            // hash file contents
                            using (var stream = entry.Open()) {
                                stream.CopyTo(hashStream);
                            }
                        }
                        hashStream.FlushFinalBlock();
                        hash = md5.Hash.ToHexString();
                    }
            }

            // rename function package with hash
            var package = Path.Combine(Settings.OutputDirectory, $"function_{function.Name}_{hash}.zip");

            if (!_existingPackages.Remove(package))
            {
                File.Move(temporaryPackage, package);

                // add git-info.json file
                using (var zipArchive = ZipFile.Open(package, ZipArchiveMode.Update)) {
                    var entry = zipArchive.CreateEntry(GIT_INFO_FILE);

                    // Set RW-R--R-- permissions attributes on non-Windows operating system
                    if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                    {
                        entry.ExternalAttributes = 0b1_000_000_110_100_100 << 16;
                    }
                    using (var stream = entry.Open()) {
                        stream.Write(Encoding.UTF8.GetBytes(JObject.FromObject(new ModuleManifestGitInfo {
                            SHA    = gitSha,
                            Branch = gitBranch
                        }).ToString(Formatting.None)));
                    }
                }
            }
            else
            {
                File.Delete(temporaryPackage);
            }

            // set the module variable to the final package name
            _builder.AddAsset($"{function.FullName}::PackageName", package);
        }