internal CSharpCompilerInputs(CSharpProjectFile projectFile)
                {
                    this.projectFile = projectFile;
                    var projectDirectory = Path.GetDirectoryName(projectFile.FilePath);
                    var outputDirectory = projectFile.GetTargetPath();
                    if (!string.IsNullOrEmpty(outputDirectory) && Path.IsPathRooted(outputDirectory))
                    {
                        outputDirectory = Path.GetDirectoryName(outputDirectory);
                    }
                    else
                    {
                        outputDirectory = projectDirectory;
                    }

                    this.ParseOptions = defaultParseOptions;
                    this.CompilationOptions = new CSharpCompilationOptions(
                        OutputKind.ConsoleApplication,
                        debugInformationKind: DebugInformationKind.None,
                        xmlReferenceResolver: new XmlFileResolver(projectDirectory),
                        sourceReferenceResolver: new SourceFileResolver(ImmutableArray<string>.Empty, projectDirectory),
                        metadataReferenceResolver: new MetadataFileReferenceResolver(ImmutableArray<string>.Empty, projectDirectory),
                        metadataReferenceProvider: MetadataFileReferenceProvider.Default,
                        strongNameProvider: new DesktopStrongNameProvider(ImmutableArray.Create(projectDirectory, outputDirectory)),
                        assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default);
                    this.Warnings = new Dictionary<string, ReportDiagnostic>();
                    this.Sources = SpecializedCollections.EmptyEnumerable<MSB.Framework.ITaskItem>();
                    this.References = SpecializedCollections.EmptyEnumerable<MSB.Framework.ITaskItem>();
                    this.AnalyzerReferences = SpecializedCollections.EmptyEnumerable<MSB.Framework.ITaskItem>();
                }
        //--- Methods ---
        public void Build(
            IFunction function,
            bool noCompile,
            bool noAssemblyValidation,
            string gitSha,
            string gitBranch,
            string buildConfiguration,
            bool forceBuild
            )
        {
            // collect sources with invoke methods
            var mappings = ExtractMappings(function);

            if (mappings == null)
            {
                // nothing to log since error was already logged
                return;
            }

            // check if a function package already exists
            if (!forceBuild)
            {
                var functionPackage = Provider.ExistingPackages.FirstOrDefault(p =>
                                                                               Path.GetFileName(p).StartsWith($"function_{Provider.ModuleFullName}_{function.LogicalId}_", StringComparison.Ordinal) &&
                                                                               p.EndsWith(".zip", StringComparison.Ordinal)
                                                                               );

                // to skip the build, we both need the function package and the function schema when mappings are present
                var schemaFile = Path.Combine(Provider.OutputDirectory, $"functionschema_{Provider.ModuleFullName}_{function.LogicalId}.json");
                if ((functionPackage != null) && (!mappings.Any() || File.Exists(schemaFile)))
                {
                    LogInfoVerbose($"=> Analyzing function {function.FullName} dependencies");

                    // find all files used to create the function package
                    var files = new HashSet <string>();
                    CSharpProjectFile.DiscoverDependencies(
                        files,
                        function.Project,
                        filePath => LogInfoVerbose($"... analyzing {filePath}"),
                        (message, exception) => LogError(message, exception)
                        );

                    // check if any of the files has been modified more recently than the function package
                    var functionPackageDate = File.GetLastWriteTime(functionPackage);
                    var file = files.FirstOrDefault(f => File.GetLastWriteTime(f) > functionPackageDate);
                    if (file == null)
                    {
                        var success = true;
                        if (mappings.Any())
                        {
                            // apply function schema to generate REST API and WebSocket models
                            try {
                                if (!ApplyInvocationSchemas(function, mappings, schemaFile, silent: true))
                                {
                                    success = false;

                                    // reset the mappings as the call to ApplyInvocationSchemas() may have modified them
                                    mappings = ExtractMappings(function);
                                    if (mappings == null)
                                    {
                                        // nothing to log since error was already logged
                                        return;
                                    }
                                }
                            } catch (Exception e) {
                                LogError("unable to read create-invoke-methods-schema output", e);
                                return;
                            }

                            // check if the mappings have changed by comparing the new data-structure to the one inside the zip file
                            if (success)
                            {
                                var newMappingsJson = JsonSerializer.Serialize(new ApiGatewayInvocationMappings {
                                    Mappings = mappings
                                }, _jsonOptions);
                                using (var zipArchive = ZipFile.Open(functionPackage, ZipArchiveMode.Read)) {
                                    var entry = zipArchive.Entries.FirstOrDefault(entry => entry.FullName == API_MAPPINGS);
                                    if (entry != null)
                                    {
                                        using (var stream = entry.Open())
                                            using (var reader = new StreamReader(stream)) {
                                                if (newMappingsJson != reader.ReadToEnd())
                                                {
                                                    // module mappings have change
                                                    success = false;
                                                    LogInfoVerbose($"... api mappings updated");
                                                }
                                            }
                                    }
                                    else
                                    {
                                        // we now have mappings and we didn't use to
                                        success = false;
                                        LogInfoVerbose($"... api mappings updated");
                                    }
                                }
                            }
                        }

                        // only skip compilation if we were able to apply the invocation schemas (or didn't have to)
                        if (success)
                        {
                            Provider.WriteLine($"=> Skipping function {Provider.InfoColor}{function.FullName}{Provider.ResetColor} (no changes found)");

                            // keep the existing package
                            Provider.ExistingPackages.Remove(functionPackage);

                            // set the module variable to the final package name
                            Provider.AddArtifact($"{function.FullName}::PackageName", functionPackage);
                            return;
                        }
                    }
                    else
                    {
                        LogInfoVerbose($"... change detected in {file}");
                    }
                }
            }
            else
            {
                LogInfoVerbose($"=> Analyzing function {function.FullName} dependencies");

                // find all files used to create the function package
                var files = new HashSet <string>();
                CSharpProjectFile.DiscoverDependencies(
                    files,
                    function.Project,
                    filePath => LogInfoVerbose($"... analyzing {filePath}"),
                    (message, exception) => LogError(message, exception)
                    );

                // loop over all project folders
                new CleanBuildFolders(BuildEventsConfig).Do(files);
            }

            // read settings from project file
            var projectFile = new CSharpProjectFile(function.Project);

            // compile function project
            var isReadyToRunSupported = VersionInfoCompatibility.IsReadyToRunSupported(projectFile.TargetFramework);
            var isAmazonLinux2        = Provider.IsAmazonLinux2();
            var isReadyToRun          = isReadyToRunSupported && isAmazonLinux2;
            var isSelfContained       = (projectFile.OutputType == "Exe") && (projectFile.AssemblyName == "bootstrap");
            var isTopLevelMain        = !isSelfContained && (projectFile.OutputType == "Exe");
            var readyToRunText        = isReadyToRun ? ", ReadyToRun" : "";
            var selfContained         = isSelfContained ? ", SelfContained" : "";

            Provider.WriteLine($"=> Building function {Provider.InfoColor}{function.FullName}{Provider.ResetColor} [{projectFile.TargetFramework}, {buildConfiguration}{readyToRunText}{selfContained}]");
            var projectDirectory = Path.Combine(Provider.WorkingDirectory, Path.GetFileNameWithoutExtension(function.Project));

            // check if the project contains an obsolete AWS Lambda Tools extension: <DotNetCliToolReference Include="Amazon.Lambda.Tools"/>
            if (projectFile.RemoveAmazonLambdaToolsReference())
            {
                LogWarn($"removing obsolete AWS Lambda Tools extension from {Path.GetRelativePath(Provider.WorkingDirectory, function.Project)}");
                projectFile.Save(function.Project);
            }

            // validate the project is using the most recent lambdasharp assembly references
            if (
                !noAssemblyValidation &&
                function.HasAssemblyValidation &&
                !projectFile.ValidateLambdaSharpPackageReferences(Provider.ToolVersion, LogWarn, LogError)
                )
            {
                return;
            }
            if (noCompile)
            {
                return;
            }

            // build project with AWS dotnet CLI lambda tool
            if (!DotNetPublish(projectFile.TargetFramework, buildConfiguration, projectDirectory, forceBuild, isReadyToRunSupported, isAmazonLinux2, isReadyToRun, isSelfContained, out var publishFolder))
            {
                // nothing to do; error was already reported
                return;
            }

            // building a function with top-level statements also creates an ELF file we don't need
            if (isTopLevelMain)
            {
                var elfBinary = Path.Combine(publishFolder, Path.GetFileNameWithoutExtension(function.Project));
                try {
                    File.Delete(elfBinary);
                } catch (Exception e) {
                    // no harm in leaving the file; report error as a warning
                    LogWarn($"Unable to delete unnecessary ELF binary at '{elfBinary}' (Error: {e})");
                }
            }

            // check if the assembly entry-point needs to be validated
            if (function.HasHandlerValidation)
            {
                if (isSelfContained || isTopLevelMain)
                {
                    // nothing to do
                }
                else
                {
                    // verify the function handler can be found in the compiled assembly
                    if (function.Handler != null)
                    {
                        if (!ValidateEntryPoint(
                                publishFolder,
                                function.Handler
                                ))
                        {
                            return;
                        }
                    }
                }
            }

            // add api mappings JSON file(s)
            if (mappings.Any())
            {
                // self-contained assemblies cannot be inspected
                if (isSelfContained)
                {
                    LogError("API Gateway mappings are not supported for self-contained Lambda functions");
                    return;
                }

                // create request/response schemas for invocation methods
                if (!LambdaSharpCreateInvocationSchemas(
                        function,
                        publishFolder,
                        projectFile.RootNamespace,
                        function.Handler,
                        mappings
                        ))
                {
                    LogError($"'{Provider.Lash} util create-invoke-methods-schema' command failed");
                    return;
                }

                // write api-mappings.json file to publish folder
                File.WriteAllText(Path.Combine(publishFolder, API_MAPPINGS), JsonSerializer.Serialize(new ApiGatewayInvocationMappings {
                    Mappings = mappings
                }, _jsonOptions));
            }

            // compute hash o publish folder
            string hash;

            using (var md5 = MD5.Create())
                using (var hashStream = new CryptoStream(Stream.Null, md5, CryptoStreamMode.Write)) {
                    foreach (var publishedFile in Directory.GetFiles(publishFolder, "*", SearchOption.AllDirectories).OrderBy(filePath => filePath))
                    {
                        // hash file path
                        var filePathBytes = Encoding.UTF8.GetBytes(Path.GetRelativePath(publishFolder, publishedFile).Replace('\\', '/'));
                        hashStream.Write(filePathBytes, 0, filePathBytes.Length);

                        // hash file contents
                        using (var stream = File.OpenRead(publishedFile)) {
                            stream.CopyTo(hashStream);
                        }
                    }
                    hashStream.FlushFinalBlock();
                    hash = md5.Hash !.ToHexString();
                }

            // genereate function package with hash
            var package = Path.Combine(Provider.OutputDirectory, $"function_{Provider.ModuleFullName}_{function.LogicalId}_{hash}.zip");

            if (Provider.ExistingPackages.Remove(package))
            {
                // remove old, existing package so we can create the new package in the same location (which also preserves the more recent build timestamp)
                File.Delete(package);
            }

            // write git-info.json file to publish folder
            File.WriteAllText(Path.Combine(publishFolder, GIT_INFO_FILE), JsonSerializer.Serialize(new ModuleManifestGitInfo {
                SHA    = gitSha,
                Branch = gitBranch
            }, _jsonOptions));

            // zip files in publishing folder
            new ZipTool(BuildEventsConfig).ZipFolderWithExecutable(package, publishFolder);

            // set the module variable to the final package name
            Provider.AddArtifact($"{function.FullName}::PackageName", package);
        }
 internal CSharpCompilerInputs(CSharpProjectFile projectFile)
 {
     _projectFile = projectFile;
     this.CommandLineArgs = new List<string>();
     this.Sources = SpecializedCollections.EmptyEnumerable<ITaskItem>();
     this.AdditionalSources = SpecializedCollections.EmptyEnumerable<ITaskItem>();
     this.ProjectDirectory = Path.GetDirectoryName(projectFile.FilePath);
     this.OutputDirectory = projectFile.GetOutputDirectory();
 }
                internal CSharpCompilerInputs(CSharpProjectFile projectFile)
                {
                    _projectFile = projectFile;
                    this.Options = new HostBuildOptions();
                    this.Sources = SpecializedCollections.EmptyEnumerable<MSB.Framework.ITaskItem>();
                    this.References = SpecializedCollections.EmptyEnumerable<MSB.Framework.ITaskItem>();
                    this.AnalyzerReferences = SpecializedCollections.EmptyEnumerable<MSB.Framework.ITaskItem>();
                    this.AdditionalFiles = SpecializedCollections.EmptyEnumerable<MSB.Framework.ITaskItem>();
                    this.LibPaths = SpecializedCollections.EmptyReadOnlyList<string>();

                    this.Options.ProjectDirectory = Path.GetDirectoryName(projectFile.FilePath);
                    this.Options.OutputDirectory = projectFile.GetOutputDirectory();
                }
        public AzureAttachModelCodeGenerator(Pipeline pipeline, ColumnInferenceResults columnInferenceResults, CodeGeneratorSettings options, string namespaceValue)
        {
            _pipeline = pipeline;
            _settings = options;
            _columnInferenceResult = columnInferenceResults;
            _nameSpaceValue        = namespaceValue;
            Name = $"{_settings.OutputName}.Model";

            ModelInputClass = new CSharpCodeFile()
            {
                File = new ModelInputClass()
                {
                    Namespace   = _nameSpaceValue,
                    ClassLabels = Utilities.Utils.GenerateClassLabels(_columnInferenceResult, _settings.OnnxInputMapping),
                    Target      = _settings.Target
                }.TransformText(),
                Name = "ModelInput.cs",
            };

            var  labelType       = _columnInferenceResult.TextLoaderOptions.Columns.Where(t => t.Name == _settings.LabelName).First().DataKind;
            Type labelTypeCsharp = Utils.GetCSharpType(labelType);

            AzureImageModelOutputClass = new CSharpCodeFile()
            {
                File = new AzureImageModelOutputClass()
                {
                    Namespace = _nameSpaceValue,
                    Target    = _settings.Target,
                    Labels    = _settings.ClassificationLabel,
                }.TransformText(),
                Name = "ModelOutput.cs",
            };

            AzureObjectDetectionModelOutputClass = new CSharpCodeFile()
            {
                File = new AzureObjectDetectionModelOutputClass()
                {
                    Namespace = _nameSpaceValue,
                    Target    = _settings.Target,
                    Labels    = _settings.ObjectLabel,
                }.TransformText(),
                Name = "ModelOutput.cs",
            };

            ModelProject = new CSharpProjectFile()
            {
                File = new ModelProject()
                {
                    IncludeFastTreePackage            = false,
                    IncludeImageClassificationPackage = false,
                    IncludeImageTransformerPackage    = _settings.IsImage,
                    IncludeLightGBMPackage            = false,
                    IncludeMklComponentsPackage       = false,
                    IncludeOnnxModel          = true,
                    IncludeOnnxRuntime        = _settings.IsObjectDetection,
                    IncludeRecommenderPackage = false,
                    StablePackageVersion      = _settings.StablePackageVersion,
                    UnstablePackageVersion    = _settings.UnstablePackageVersion,
                    OnnxRuntimePackageVersion = _settings.OnnxRuntimePacakgeVersion,
                    Target = _settings.Target,
                }.TransformText(),
                Name = $"{ _settings.OutputName }.Model.csproj",
            };

            ConsumeModel = new CSharpCodeFile()
            {
                File = new ConsumeModel()
                {
                    Namespace              = _nameSpaceValue,
                    Target                 = _settings.Target,
                    MLNetModelName         = _settings.ModelName,
                    OnnxModelName          = _settings.OnnxModelName,
                    IsAzureImage           = _settings.IsAzureAttach && _settings.IsImage,
                    IsAzureObjectDetection = _settings.IsObjectDetection && _settings.IsAzureAttach,
                }.TransformText(),
                Name = "ConsumeModel.cs",
            };
        }
Exemplo n.º 6
0
        public AzureAttachModelCodeGenerator(Pipeline pipeline, ColumnInferenceResults columnInferenceResults, CodeGeneratorSettings options, string namespaceValue)
        {
            _pipeline = pipeline;
            _settings = options;
            _columnInferenceResult = columnInferenceResults;
            _nameSpaceValue        = namespaceValue;
            Name = $"{_settings.OutputName}.Model";

            ModelInputClass = new CSharpCodeFile()
            {
                File = new ModelInputClass()
                {
                    Namespace   = _nameSpaceValue,
                    ClassLabels = Utilities.Utils.GenerateClassLabels(_columnInferenceResult, _settings.OnnxInputMapping),
                    Target      = _settings.Target
                }.TransformText(),
                Name = "ModelInput.cs",
            };

            var  labelType       = _columnInferenceResult.TextLoaderOptions.Columns.Where(t => t.Name == _settings.LabelName).First().DataKind;
            Type labelTypeCsharp = Utils.GetCSharpType(labelType);

            ModelOutputClass = new CSharpCodeFile()
            {
                File = new ModelOutputClass()
                {
                    Namespace           = _nameSpaceValue,
                    Target              = _settings.Target,
                    TaskType            = _settings.MlTask.ToString(),
                    PredictionLabelType = labelTypeCsharp.Name,
                }.TransformText(),
                Name = "ModelOutput.cs",
            };

            NormalizeMapping = new CSharpCodeFile()
            {
                File = new NormalizeMapping()
                {
                    Target    = _settings.Target,
                    Namespace = _nameSpaceValue,
                }.TransformText(),
                Name = "NormalizeMapping.cs",
            };

            ModelProject = new CSharpProjectFile()
            {
                File = new ModelProject()
                {
                    IncludeFastTreePackage            = false,
                    IncludeImageClassificationPackage = false,
                    IncludeImageTransformerPackage    = _settings.IsImage,
                    IncludeLightGBMPackage            = false,
                    IncludeMklComponentsPackage       = false,
                    IncludeOnnxModel          = true,
                    IncludeRecommenderPackage = false,
                    StablePackageVersion      = _settings.StablePackageVersion,
                    UnstablePackageVersion    = _settings.UnstablePackageVersion,
                }.TransformText(),
                Name = $"{ _settings.OutputName }.Model.csproj",
            };

            LabelMapping = new CSharpCodeFile()
            {
                File = new LabelMapping()
                {
                    Target    = _settings.Target,
                    Namespace = _nameSpaceValue,
                    LabelMappingInputLabelType = typeof(Int64).Name,
                    PredictionLabelType        = labelTypeCsharp.Name,
                    TaskType = _settings.MlTask.ToString(),
                }.TransformText(),
                Name = "LabelMapping.cs",
            };

            ImageLabelMapping = new CSharpCodeFile()
            {
                File = new ImageLabelMapping()
                {
                    Target    = _settings.Target,
                    Namespace = _nameSpaceValue,
                    Labels    = _settings.ClassificationLabel,
                }.TransformText(),
                Name = "LabelMapping.cs",
            };

            ConsumeModel = new CSharpCodeFile()
            {
                File = new ConsumeModel()
                {
                    Namespace           = _nameSpaceValue,
                    Target              = _settings.Target,
                    HasLabelMapping     = true,
                    HasNormalizeMapping = _settings.IsImage,
                    MLNetModelpath      = _settings.ModelPath,
                }.TransformText(),
                Name = "ConsumeModel.cs",
            };
        }
                internal CSharpCompilerInputs(CSharpProjectFile projectFile)
                {
                    _projectFile = projectFile;
                    var projectDirectory = Path.GetDirectoryName(projectFile.FilePath);
                    var outputDirectory = projectFile.GetOutputDirectory();

                    this.ParseOptions = s_defaultParseOptions;
                    this.CompilationOptions = new CSharpCompilationOptions(
                        OutputKind.ConsoleApplication,
                        xmlReferenceResolver: new XmlFileResolver(projectDirectory),
                        sourceReferenceResolver: new SourceFileResolver(ImmutableArray<string>.Empty, projectDirectory),
                        metadataReferenceResolver: new AssemblyReferenceResolver(
                            new MetadataFileReferenceResolver(ImmutableArray<string>.Empty, projectDirectory),
                            MetadataFileReferenceProvider.Default),
                        strongNameProvider: new DesktopStrongNameProvider(ImmutableArray.Create(projectDirectory, outputDirectory)),
                        assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default);
                    this.Warnings = new Dictionary<string, ReportDiagnostic>();
                    this.Sources = SpecializedCollections.EmptyEnumerable<MSB.Framework.ITaskItem>();
                    this.References = SpecializedCollections.EmptyEnumerable<MSB.Framework.ITaskItem>();
                    this.AnalyzerReferences = SpecializedCollections.EmptyEnumerable<MSB.Framework.ITaskItem>();
                    this.AdditionalFiles = SpecializedCollections.EmptyEnumerable<MSB.Framework.ITaskItem>();
                    this.LibPaths = SpecializedCollections.EmptyReadOnlyList<string>();
                }
Exemplo n.º 8
0
        //--- Methods ---
        public void Build(
            IApp app,
            bool noCompile,
            bool noAssemblyValidation,
            string?gitSha,
            string?gitBranch,
            string buildConfiguration,
            bool forceBuild,
            out string platform,
            out string framework,
            out string appVersionId
            )
        {
            // read settings from project file
            var projectFile     = new CSharpProjectFile(app.Project);
            var targetFramework = projectFile.TargetFramework;

            // set output parameters
            platform  = "Blazor WebAssembly";
            framework = targetFramework;

            // check if any app files are newer than the most recently built package; otherwise, skip build
            var appMetadataFilepath = Path.Combine(Provider.OutputDirectory, $"appmetadata_{Provider.ModuleFullName}_{app.LogicalId}.json");

            if (!forceBuild)
            {
                var appPackage = Provider.ExistingPackages.FirstOrDefault(p =>
                                                                          Path.GetFileName(p).StartsWith($"app_{Provider.ModuleFullName}_{app.LogicalId}_", StringComparison.Ordinal) &&
                                                                          p.EndsWith(".zip", StringComparison.Ordinal)
                                                                          );
                if ((appPackage != null) && File.Exists(appMetadataFilepath))
                {
                    LogInfoVerbose($"=> Analyzing app {app.FullName} dependencies");

                    // find all files used to create the app package
                    var files = new HashSet <string>();
                    CSharpProjectFile.DiscoverDependencies(
                        files,
                        app.Project,
                        filePath => LogInfoVerbose($"... analyzing {filePath}"),
                        (message, exception) => LogError(message, exception)
                        );

                    // check if any of the files has been modified more recently than the app package
                    var appPackageDate = File.GetLastWriteTime(appPackage);
                    var file           = files.FirstOrDefault(f => File.GetLastWriteTime(f) > appPackageDate);
                    if (file == null)
                    {
                        try {
                            // attempt to load extract assembly metadata
                            var appMetadata = JsonSerializer.Deserialize <LambdaSharpTool.AssemblyMetadata>(File.ReadAllText(appMetadataFilepath));
                            if (appMetadata.ModuleVersionId != null)
                            {
                                Provider.WriteLine($"=> Skipping app {Provider.InfoColor}{app.FullName}{Provider.ResetColor} (no changes found)");

                                // keep the existing files
                                Provider.ExistingPackages.Remove(appMetadataFilepath);
                                Provider.ExistingPackages.Remove(appPackage);

                                // set the module variable to the final package name
                                appVersionId = appMetadata.ModuleVersionId;
                                Provider.AddArtifact($"{app.FullName}::PackageName", appPackage);
                                return;
                            }
                        } catch {
                            // ignore exception and continue
                        }
                    }
                    else
                    {
                        LogInfoVerbose($"... found newer file: {file}");
                    }
                }
            }
            else
            {
                LogInfoVerbose($"=> Analyzing app {app.FullName} dependencies");

                // find all files used to create the app package
                var files = new HashSet <string>();
                CSharpProjectFile.DiscoverDependencies(
                    files,
                    app.Project,
                    filePath => LogInfoVerbose($"... analyzing {filePath}"),
                    (message, exception) => LogError(message, exception)
                    );

                // loop over all project folders
                new CleanBuildFolders(BuildEventsConfig).Do(files);
            }

            // validate the project is using the most recent lambdasharp assembly references
            if (
                !noAssemblyValidation &&
                app.HasAssemblyValidation &&
                !projectFile.ValidateLambdaSharpPackageReferences(Provider.ToolVersion, LogWarn, LogError)
                )
            {
                appVersionId = "<MISSING>";
                return;
            }
            if (noCompile)
            {
                appVersionId = "<MISSING>";
                return;
            }

            // compile app project
            Provider.WriteLine($"=> Building app {Provider.InfoColor}{app.FullName}{Provider.ResetColor} [{projectFile.TargetFramework}, {buildConfiguration}]");
            var projectDirectory = Path.Combine(Provider.WorkingDirectory, Path.GetFileNameWithoutExtension(app.Project));

            if (File.Exists(appMetadataFilepath))
            {
                File.Delete(appMetadataFilepath);
            }

            // build and publish Blazor app
            if (!DotNetBuildBlazor(projectFile.TargetFramework, buildConfiguration, projectDirectory))
            {
                // nothing to do; error was already reported
                appVersionId = "<MISSING>";
                return;
            }

            // extract version id from app
            var assemblyFilepath = (string.Compare(targetFramework, "netstandard2.1", StringComparison.Ordinal) == 0)
                ? Path.Combine(projectDirectory, "bin", buildConfiguration, targetFramework, "publish", "wwwroot", "_framework", "_bin", $"{Path.GetFileNameWithoutExtension(app.Project)}.dll")
                : Path.Combine(projectDirectory, "bin", buildConfiguration, targetFramework, "publish", "wwwroot", "_framework", $"{Path.GetFileNameWithoutExtension(app.Project)}.dll");
            var assemblyMetadata = LambdaSharpAppAssemblyInformation(assemblyFilepath, appMetadataFilepath);

            if (assemblyMetadata?.ModuleVersionId == null)
            {
                LogError($"unable to extract assembly metadata");
                appVersionId = "<MISSING>";
                return;
            }
            Provider.ExistingPackages.Remove(appMetadataFilepath);
            LogInfoVerbose($"... assembly version id: {assemblyMetadata.ModuleVersionId}");
            appVersionId = assemblyMetadata.ModuleVersionId;

            // update `blazor.boot.json` file by adding `appsettings.Production.json` to config list
            var wwwRootFolder = Path.Combine(projectDirectory, "bin", buildConfiguration, targetFramework, "publish", "wwwroot");

            if (File.Exists(Path.Combine(wwwRootFolder, AppSettingsProductionJsonFileName)))
            {
                LogError($"'{AppSettingsProductionJsonFileName}' is reserved for loading deployment generated configuration settings and cannot be used explicitly");
                return;
            }

            var blazorBootJsonFileName = Path.Combine(wwwRootFolder, "_framework", "blazor.boot.json");
            var blazorBootJson         = JsonToNativeConverter.ParseObject(File.ReadAllText(blazorBootJsonFileName));

            if (
                (blazorBootJson != null) &&
                (blazorBootJson.TryGetValue("config", out var blazorBootJsonConfig)) &&
                (blazorBootJsonConfig is List <object?> blazorBootJsonConfigList)
                )
            {
                var blazorBootJsonModified = false;
                if (!blazorBootJsonConfigList.Contains(AppSettingsJsonFileName) && ((gitSha != null) || (gitBranch != null)))
                {
                    // add instruction to load appsettings.json
                    blazorBootJsonConfigList.Add(AppSettingsJsonFileName);
                    blazorBootJsonModified = true;
                }
                if (!blazorBootJsonConfigList.Contains(AppSettingsProductionJsonFileName))
                {
                    // add instruction to load appsettings.Production.json
                    blazorBootJsonConfigList.Add(AppSettingsProductionJsonFileName);
                    blazorBootJsonModified = true;
                }
                if (blazorBootJsonModified)
                {
                    LogInfoVerbose("... updating 'blazor.boot.json' configuration file");
                    File.WriteAllText(blazorBootJsonFileName, JsonSerializer.Serialize(blazorBootJson));
                }
            }
            else
            {
                LogError($"unable to update {blazorBootJsonFileName}");
            }

            // zip output folder
            BuildPackage(app, gitSha, gitBranch, wwwRootFolder);
        }
Exemplo n.º 9
0
        //--- Methods ---
        public void Build(
            IFunction function,
            bool noCompile,
            bool noAssemblyValidation,
            string gitSha,
            string gitBranch,
            string buildConfiguration,
            bool forceBuild
            )
        {
            // check if AWS Lambda Tools extension is installed
            if (!new AmazonLambdaTool(BuildEventsConfig).CheckIsInstalled())
            {
                return;
            }

            // collect sources with invoke methods
            var mappings = ExtractMappings(function);

            if (mappings == null)
            {
                // nothing to log since error was already logged
                return;
            }

            // check if a function package already exists
            if (!forceBuild)
            {
                var functionPackage = Provider.ExistingPackages.FirstOrDefault(p =>
                                                                               Path.GetFileName(p).StartsWith($"function_{Provider.ModuleFullName}_{function.LogicalId}_", StringComparison.Ordinal) &&
                                                                               p.EndsWith(".zip", StringComparison.Ordinal)
                                                                               );

                // to skip the build, we both need the function package and the function schema when mappings are present
                var schemaFile = Path.Combine(Provider.OutputDirectory, $"functionschema_{Provider.ModuleFullName}_{function.LogicalId}.json");
                if ((functionPackage != null) && (!mappings.Any() || File.Exists(schemaFile)))
                {
                    LogInfoVerbose($"=> Analyzing function {function.FullName} dependencies");

                    // find all files used to create the function package
                    var files = new HashSet <string>();
                    CSharpProjectFile.DiscoverDependencies(
                        files,
                        function.Project,
                        filePath => LogInfoVerbose($"... analyzing {filePath}"),
                        (message, exception) => LogError(message, exception)
                        );

                    // check if any of the files has been modified more recently than the function package
                    var functionPackageDate = File.GetLastWriteTime(functionPackage);
                    var file = files.FirstOrDefault(f => File.GetLastWriteTime(f) > functionPackageDate);
                    if (file == null)
                    {
                        var success = true;
                        if (mappings.Any())
                        {
                            // apply function schema to generate REST API and WebSocket models
                            try {
                                if (!ApplyInvocationSchemas(function, mappings, schemaFile, silent: true))
                                {
                                    success = false;

                                    // reset the mappings as the call to ApplyInvocationSchemas() may have modified them
                                    mappings = ExtractMappings(function);
                                    if (mappings == null)
                                    {
                                        // nothing to log since error was already logged
                                        return;
                                    }
                                }
                            } catch (Exception e) {
                                LogError("unable to read create-invoke-methods-schema output", e);
                                return;
                            }

                            // check if the mappings have changed by comparing the new data-structure to the one inside the zip file
                            if (success)
                            {
                                var newMappingsJson = JsonSerializer.Serialize(new ApiGatewayInvocationMappings {
                                    Mappings = mappings
                                }, _jsonOptions);
                                using (var zipArchive = ZipFile.Open(functionPackage, ZipArchiveMode.Read)) {
                                    var entry = zipArchive.Entries.FirstOrDefault(entry => entry.FullName == API_MAPPINGS);
                                    if (entry != null)
                                    {
                                        using (var stream = entry.Open())
                                            using (var reader = new StreamReader(stream)) {
                                                if (newMappingsJson != reader.ReadToEnd())
                                                {
                                                    // module mappings have change
                                                    success = false;
                                                    LogInfoVerbose($"... api mappings updated");
                                                }
                                            }
                                    }
                                    else
                                    {
                                        // we now have mappings and we didn't use to
                                        success = false;
                                        LogInfoVerbose($"... api mappings updated");
                                    }
                                }
                            }
                        }

                        // only skip compilation if we were able to apply the invocation schemas (or didn't have to)
                        if (success)
                        {
                            Provider.WriteLine($"=> Skipping function {Provider.InfoColor}{function.FullName}{Provider.ResetColor} (no changes found)");

                            // keep the existing package
                            Provider.ExistingPackages.Remove(functionPackage);

                            // set the module variable to the final package name
                            Provider.AddArtifact($"{function.FullName}::PackageName", functionPackage);
                            return;
                        }
                    }
                    else
                    {
                        LogInfoVerbose($"... change detected in {file}");
                    }
                }
            }
            else
            {
                LogInfoVerbose($"=> Analyzing function {function.FullName} dependencies");

                // find all files used to create the function package
                var files = new HashSet <string>();
                CSharpProjectFile.DiscoverDependencies(
                    files,
                    function.Project,
                    filePath => LogInfoVerbose($"... analyzing {filePath}"),
                    (message, exception) => LogError(message, exception)
                    );

                // loop over all project folders
                new CleanBuildFolders(BuildEventsConfig).Do(files);
            }

            // read settings from project file
            var projectFile = new CSharpProjectFile(function.Project);

            // compile function project
            var isNetCore31OrLater = projectFile.TargetFramework.CompareTo("netcoreapp3.") >= 0;
            var isAmazonLinux2     = Provider.IsAmazonLinux2();
            var isReadyToRun       = isNetCore31OrLater && isAmazonLinux2;
            var readyToRunText     = isReadyToRun ? ", ReadyToRun" : "";

            Provider.WriteLine($"=> Building function {Provider.InfoColor}{function.FullName}{Provider.ResetColor} [{projectFile.TargetFramework}, {buildConfiguration}{readyToRunText}]");
            var projectDirectory = Path.Combine(Provider.WorkingDirectory, Path.GetFileNameWithoutExtension(function.Project));
            var temporaryPackage = Path.Combine(Provider.OutputDirectory, $"function_{Provider.ModuleFullName}_{function.LogicalId}_temporary.zip");

            // check if the project contains an obsolete AWS Lambda Tools extension: <DotNetCliToolReference Include="Amazon.Lambda.Tools"/>
            if (projectFile.RemoveAmazonLambdaToolsReference())
            {
                LogWarn($"removing obsolete AWS Lambda Tools extension from {Path.GetRelativePath(Provider.WorkingDirectory, function.Project)}");
                projectFile.Save(function.Project);
            }

            // validate the project is using the most recent lambdasharp assembly references
            if (
                !noAssemblyValidation &&
                function.HasAssemblyValidation &&
                !projectFile.ValidateLambdaSharpPackageReferences(Provider.ToolVersion, LogWarn, LogError)
                )
            {
                return;
            }
            if (noCompile)
            {
                return;
            }

            // build project with AWS dotnet CLI lambda tool
            if (!DotNetLambdaPackage(projectFile.TargetFramework, buildConfiguration, temporaryPackage, projectDirectory, forceBuild, isNetCore31OrLater, isAmazonLinux2, isReadyToRun))
            {
                // nothing to do; error was already reported
                return;
            }

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

            if (function.HasHandlerValidation)
            {
                if (function.Handler != null)
                {
                    if (!ValidateEntryPoint(
                            buildFolder,
                            function.Handler
                            ))
                    {
                        return;
                    }
                }
            }

            // create request/response schemas for invocation methods
            if (!LambdaSharpCreateInvocationSchemas(
                    function,
                    buildFolder,
                    projectFile.RootNamespace,
                    function.Handler,
                    mappings
                    ))
            {
                LogError($"'{Provider.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(JsonSerializer.Serialize(new ApiGatewayInvocationMappings {
                            Mappings = mappings
                        }, _jsonOptions)));
                    }
                }
            }

            // 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(Provider.OutputDirectory, $"function_{Provider.ModuleFullName}_{function.LogicalId}_{hash}.zip");

            if (Provider.ExistingPackages.Remove(package))
            {
                // remove old, existing package so we can move the new package into location (which also preserves the more recent build timestamp)
                File.Delete(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(JsonSerializer.Serialize(new ModuleManifestGitInfo {
                        SHA    = gitSha,
                        Branch = gitBranch
                    }, _jsonOptions)));
                }
            }

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