/// <summary> /// Gets the absolute path to site packages directory. /// </summary> /// <param name="runtimeInfo">The runtime information.</param> /// <returns>Absolute path to site packages directory.</returns> /// <exception cref="DirectoryNotFoundException">site-packages not found for runtime '{runtimeInfo.Runtime}'</exception> /// <remarks> /// On non-Windows platforms, site-packages is sorted into python version directories. /// </remarks> public string GetSitePackagesDir(LambdaRuntimeInfo runtimeInfo) { if (this.platform.OSPlatform == OSPlatform.Windows) { return(Path.Combine(this.LibDir, "site-packages")); } // Linux/MacOs have a site packages for each python version var path = Path.Combine(this.LibDir, runtimeInfo.Runtime, "site-packages"); if (!Directory.Exists(path)) { throw new DirectoryNotFoundException($"site-packages not found for runtime '{runtimeInfo.Runtime}'"); } return(path); }
public PythonVirtualEnv Load(LambdaRuntimeInfo runtimeInfo) { var sitePackages = this.GetSitePackagesDir(runtimeInfo); foreach (var dist in Directory.EnumerateDirectories( sitePackages, "*.dist-info", SearchOption.TopDirectoryOnly)) { // RECORD file tells us where to find the code and is a 3 field header-less CSV var recordFile = Path.Combine(dist, "RECORD"); // Name comes from the dist-info directory name // Package names always have hyphens, not underscores var module = new PythonModule { Name = PackageNameRegex.Match(Path.GetFileName(dist)).Groups["packageName"].Value .Replace('_', '-') }; if (File.Exists(recordFile)) { var paths = new HashSet <string>(); // Pull all items from RECORD file into a set to get a unique list of what to include, ignoring dist-info files, Windows .pyd files and scripts placed in bin foreach (var path in File.ReadAllLines(recordFile) .Select(line => line.Split(',').First().Split('/').First()) .Where(path => !(path.EndsWith(".dist-info") || path.EndsWith(".pyd")) && path != ".." && path != "__pycache__")) { paths.Add(Path.Combine(sitePackages, path)); } // TODO: Warn if a binary is a Windows DLL. Not likely to be good in Lambda (linux) if (paths.Count == 0) { throw new PackagerException( $"Found no content for package {module.Name}"); } foreach (var p in paths) { module.Paths.Add(Path.Combine(sitePackages, p)); } } else { throw new FileNotFoundException( "Unable to determine package location - cannot find RECORD file", recordFile); } // METADATA file tells us any dependencies with Requires-Dist records. var metadataFile = Path.Combine(dist, "METADATA"); if (File.Exists(metadataFile)) { foreach (var dependency in File.ReadAllLines(metadataFile) .Where(l => l.StartsWith("Requires-Dist:"))) { var includeDependency = true; if (dependency.Contains(";")) { var expression = dependency.Split(';').Last(); var r = this.parser.Parse(expression); if (r.IsError) { var errorMessage = string.Join( Environment.NewLine, r.Errors.Select(e => e.ErrorMessage)); throw new PackagerException( $"Error parsing: {expression}\nIf this expression is valid, please raise an issue.\n{errorMessage}"); } // Set variables according to targeted Python version. this.pep508Variables["python_version"] = runtimeInfo.RuntimeVersion; this.pep508Variables["python_full_version"] = runtimeInfo.RuntimeVersion; var evaluation = r.Result.Evaluate(new ExpressionContext(this.pep508Variables)); includeDependency = (bool?)evaluation ?? throw new PackagerException( $"Error evaluating: {expression}\nIf this expression is valid, please raise an issue."); } if (includeDependency) { module.Dependencies.Add(RequirementsRegex.Match(dependency).Groups["dependency"].Value); } } } this.modules.Add(module); } return(this); }