in src/Amazon.Lambda.Tools/Commands/PublishLayerCommand.cs [161:303]
private async Task<CreateLayerZipFileResult> CreateRuntimePackageStoreLayerZipFile(string layerName, string s3Prefix)
{
var targetFramework = this.GetStringValueOrDefault(this.TargetFramework, CommonDefinedCommandOptions.ARGUMENT_FRAMEWORK, true);
var projectLocation = this.GetStringValueOrDefault(this.ProjectLocation, CommonDefinedCommandOptions.ARGUMENT_PROJECT_LOCATION, false);
var enableOptimization = this.GetBoolValueOrDefault(this.EnablePackageOptimization, LambdaDefinedCommandOptions.ARGUMENT_ENABLE_PACKAGE_OPTIMIZATION, false).GetValueOrDefault();
if(string.Equals(targetFramework, "netcoreapp3.1"))
{
var version = DotNetCLIWrapper.GetSdkVersion();
// .NET SDK 3.1 versions less then 3.1.400 have an issue throwing NullReferenceExceptions when pruning packages out with the manifest.
// https://github.com/dotnet/sdk/issues/10973
if (version < Version.Parse("3.1.400"))
{
var message = $"Publishing runtime package store layers targeting .NET Core 3.1 requires at least version 3.1.400 of the .NET SDK. Current version installed is {version}.";
throw new LambdaToolsException(message, LambdaToolsException.LambdaErrorCode.DisabledSupportForNET31Layers);
}
}
#if NETCOREAPP3_1_OR_GREATER
if (enableOptimization)
{
if(!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
throw new LambdaToolsException($"Package optimization is only possible on Amazon Linux. To use this feature execute the command in an Amazon Linux environment.", LambdaToolsException.LambdaErrorCode.UnsupportedOptimizationPlatform);
}
else
{
this.Logger.WriteLine("Warning: Package optimization has been enabled. Be sure to run this on an Amazon Linux environment or the optimization might not be compatbile with the Lambda runtime.");
}
}
#else
// This is the case the code is run in the AWS Toolkit for Visual Studio which will never run on Amazon Linux.
enableOptimization = false;
#endif
// This is the manifest that list the NuGet packages via <PackageReference> elements in the msbuild project file.
var packageManifest = this.GetStringValueOrDefault(this.PackageManifest, LambdaDefinedCommandOptions.ARGUMENT_PACKAGE_MANIFEST, false);
// If this is null attempt to use the current directory. This is likely if the intent is to make a
// layer from the current Lambda project.
if (string.IsNullOrEmpty(packageManifest))
{
packageManifest = Utilities.DetermineProjectLocation(this.WorkingDirectory, projectLocation);
}
// If this is a directory look to see if there is a single csproj of fsproj in the directory in use that.
// This is to make it easy to make a layer in the current directory of a Lambda function.
if (Directory.Exists(packageManifest))
{
var files = Directory.GetFiles(packageManifest, "*.csproj");
if(files.Length == 1)
{
packageManifest = Path.Combine(packageManifest, files[0]);
}
else if(files.Length == 0)
{
files = Directory.GetFiles(packageManifest, "*.fsproj");
if (files.Length == 1)
{
packageManifest = Path.Combine(packageManifest, files[0]);
}
}
}
if(!File.Exists(packageManifest))
{
throw new LambdaToolsException($"Can not find package manifest {packageManifest}. Make sure to point to a file not a directory.", LambdaToolsException.LambdaErrorCode.LayerPackageManifestNotFound);
}
// Create second subdirectory so that when the directory is zipped the sub directory is retained in the zip file.
// The sub directory will be created in the /opt directory in the Lambda environment.
var tempDirectoryName = $"{layerName}-{DateTime.UtcNow.Ticks}".ToLower();
var optDirectory = this.GetStringValueOrDefault(this.OptDirectory, LambdaDefinedCommandOptions.ARGUMENT_OPT_DIRECTORY, false);
if (string.IsNullOrEmpty(optDirectory))
{
optDirectory = LambdaConstants.DEFAULT_LAYER_OPT_DIRECTORY;
}
var tempRootPath = Path.Combine(Path.GetTempPath(), tempDirectoryName);
var storeOutputDirectory = Path.Combine(tempRootPath, optDirectory);
{
var convertResult = LambdaUtilities.ConvertManifestToSdkManifest(targetFramework, packageManifest);
if (convertResult.ShouldDelete)
{
this.Logger?.WriteLine("Converted ASP.NET Core project file to temporary package manifest file.");
}
var architecture = this.GetStringValueOrDefault(this.Architecture, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_ARCHITECTURE, false);
var cliWrapper = new LambdaDotNetCLIWrapper(this.Logger, this.WorkingDirectory);
if(cliWrapper.Store(defaults: this.DefaultConfig,
projectLocation: projectLocation,
outputLocation: storeOutputDirectory,
targetFramework: targetFramework,
packageManifest: convertResult.PackageManifest,
architecture: architecture,
enableOptimization: enableOptimization) != 0)
{
throw new LambdaToolsException($"Error executing the 'dotnet store' command", LambdaToolsException.LambdaErrorCode.StoreCommandError);
}
if (convertResult.ShouldDelete)
{
File.Delete(convertResult.PackageManifest);
}
}
// The artifact.xml file is generated by the "dotnet store" command that lists the packages that were added to the store.
// It is required during a "dotnet publish" so the NuGet packages in the store will be filtered out.
var artifactXmlPath = Path.Combine(storeOutputDirectory, "x64", targetFramework, "artifact.xml");
if(!File.Exists(artifactXmlPath))
{
throw new LambdaToolsException($"Failed to find artifact.xml file in created local store.", LambdaToolsException.LambdaErrorCode.FailedToFindArtifactZip);
}
this.Logger.WriteLine($"Uploading runtime package store manifest to S3");
var s3Key = await UploadFile(artifactXmlPath, $"{s3Prefix}artifact.xml");
this.Logger.WriteLine($"Create zip file of runtime package store directory");
var zipPath = Path.Combine(Path.GetTempPath(), $"{layerName}-{DateTime.UtcNow.Ticks}.zip");
if(File.Exists(zipPath))
{
File.Delete(zipPath);
}
LambdaPackager.BundleDirectory(zipPath, tempRootPath, false, this.Logger);
var result = new CreateLayerZipFileResult
{
ZipFile = zipPath,
LayerDirectory = optDirectory
};
var s3Bucket = this.GetStringValueOrDefault(this.S3Bucket, LambdaDefinedCommandOptions.ARGUMENT_S3_BUCKET, true);
// Set the description field to the our JSON layer manifest file so when the tooling is used
// to create a package of a Lambda function in the future the artifact.xml file can be used during "dotnet publish".
result.Description = GeneratorRuntimePackageManifestLayerDescription(optDirectory, s3Bucket, s3Key, enableOptimization);
var compatibleRuntime = LambdaUtilities.DetermineLambdaRuntimeFromTargetFramework(targetFramework);
if(!string.IsNullOrEmpty(compatibleRuntime))
{
result.CompatibleRuntimes = new List<string>() { compatibleRuntime };
}
return result;
}