// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r // SPDX-License-Identifier: Apache-2.0 using System; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Xml; using AWS.Deploy.Common.IO; namespace AWS.Deploy.Common { public interface IProjectDefinitionParser { /// /// Scans for a valid project file and reads it to /// fully populate a /// /// /// Thrown if no project can be found at /// Task Parse(string projectPath); } public class ProjectDefinitionParser : IProjectDefinitionParser { private readonly IFileManager _fileManager; private readonly IDirectoryManager _directoryManager; public ProjectDefinitionParser(IFileManager fileManager, IDirectoryManager directoryManager) { _fileManager = fileManager; _directoryManager = directoryManager; } /// /// This method parses the target application project and sets the /// appropriate metadata as part of the /// /// The project path can be an absolute or a relative path to the /// target application project directory or the application project file. /// public async Task Parse(string projectPath) { if (_directoryManager.Exists(projectPath)) { projectPath = _directoryManager.GetDirectoryInfo(projectPath).FullName; var files = _directoryManager.GetFiles(projectPath, "*.csproj"); if (files.Length == 1) { projectPath = Path.Combine(projectPath, files[0]); } else if (files.Length == 0) { files = _directoryManager.GetFiles(projectPath, "*.fsproj"); if (files.Length == 1) { projectPath = Path.Combine(projectPath, files[0]); } } } if (!_fileManager.Exists(projectPath)) { throw new ProjectFileNotFoundException(DeployToolErrorCode.ProjectPathNotFound, $"Failed to find a valid .csproj or .fsproj file at path {projectPath}"); } var extension = Path.GetExtension(projectPath); if (!string.Equals(extension, ".csproj") && !string.Equals(extension, ".fsproj")) { var errorMeesage = $"Invalid project path {projectPath}. The project path must point to a .csproj or .fsproj file"; throw new ProjectFileNotFoundException(DeployToolErrorCode.ProjectPathNotFound, errorMeesage); } var xmlProjectFile = new XmlDocument(); xmlProjectFile.LoadXml(await _fileManager.ReadAllTextAsync(projectPath)); var projectDefinition = new ProjectDefinition( xmlProjectFile, projectPath, await GetProjectSolutionFile(projectPath), xmlProjectFile.DocumentElement?.Attributes["Sdk"]?.Value ?? throw new InvalidProjectDefinitionException(DeployToolErrorCode.ProjectParserNoSdkAttribute, "The project file that is being referenced does not contain and 'Sdk' attribute.") ); var targetFramework = xmlProjectFile.GetElementsByTagName("TargetFramework"); if (targetFramework.Count > 0) { projectDefinition.TargetFramework = targetFramework[0]?.InnerText; } var assemblyName = xmlProjectFile.GetElementsByTagName("AssemblyName"); if (assemblyName.Count > 0) { projectDefinition.AssemblyName = (string.IsNullOrWhiteSpace(assemblyName[0]?.InnerText) ? Path.GetFileNameWithoutExtension(projectPath) : assemblyName[0]?.InnerText); } else { projectDefinition.AssemblyName = Path.GetFileNameWithoutExtension(projectPath); } return projectDefinition; } private async Task GetProjectSolutionFile(string projectPath) { var projectDirectory = Directory.GetParent(projectPath); while (projectDirectory != null) { var files = _directoryManager.GetFiles(projectDirectory.FullName, "*.sln"); foreach (var solutionFile in files) { if (await ValidateProjectInSolution(projectPath, solutionFile)) { return solutionFile; } } projectDirectory = projectDirectory.Parent; } return string.Empty; } private async Task ValidateProjectInSolution(string projectPath, string solutionFile) { var projectFileName = Path.GetFileName(projectPath); if (string.IsNullOrWhiteSpace(solutionFile) || string.IsNullOrWhiteSpace(projectFileName)) { return false; } var lines = await _fileManager.ReadAllLinesAsync(solutionFile); var projectLines = lines.Where(x => x.StartsWith("Project")); var projectPaths = projectLines .Select(x => x.Split(',')) .Where(x => x.Length > 1) .Select(x => x[1] .Replace('\"', ' ') .Trim()) .Select(x => x.Replace('\\', Path.DirectorySeparatorChar)) .ToList(); //Validate project exists in solution return projectPaths.Select(x => Path.GetFileName(x)).Any(x => x.Equals(projectFileName)); } } }