use color_eyre::{eyre::Context, Result};
use std::{fs::File, io::Read, path::Path};
use serde::Deserialize;
#[must_use]
pub fn is_solution_folder(id: &str) -> bool {
id == ID_SOLUTION_FOLDER
}
#[must_use]
pub fn is_web_site_project(id: &str) -> bool {
id == ID_WEB_SITE_PROJECT
}
#[must_use]
pub fn describe_project(id: &str) -> &str {
PROJECT_TYPES.get(id).unwrap_or(&id)
}
#[derive(Debug, Deserialize)]
pub struct Project {
#[serde(rename = "Sdk", default)]
pub sdk: Option<String>,
#[serde(rename = "ItemGroup", default)]
pub item_group: Option<Vec<ItemGroup>>,
#[serde(rename = "ImportGroup", default)]
pub import_group: Option<Vec<ImportGroup>>,
#[serde(rename = "Import")]
pub imports: Option<Vec<Import>>,
}
#[derive(Debug, Deserialize)]
pub struct ItemGroup {
#[serde(rename = "ProjectReference", default)]
pub project_reference: Option<Vec<ProjectReference>>,
#[serde(rename = "PackageReference", default)]
pub package_reference: Option<Vec<PackageReference>>,
#[serde(rename = "Condition", default)]
pub condition: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct ImportGroup {
#[serde(rename = "Import", default)]
pub imports: Option<Vec<Import>>,
}
#[derive(Debug, Deserialize)]
pub struct ProjectReference {
#[serde(rename = "Include", default)]
pub include: String,
}
#[derive(Debug, Deserialize)]
pub struct PackageReference {
#[serde(rename = "Include", default)]
pub name: String,
#[serde(rename = "Version", default)]
pub version: String,
}
#[derive(Debug, Deserialize)]
pub struct PackagesConfig {
#[serde(rename = "package", default)]
pub packages: Vec<Package>,
}
#[derive(Debug, Deserialize)]
pub struct Package {
#[serde(rename = "id", default)]
pub name: String,
pub version: String,
}
#[derive(Debug, Deserialize)]
pub struct Import {
#[serde(rename = "Project", default)]
pub project: String,
#[serde(rename = "Sdk", default)]
pub sdk: Option<String>,
#[serde(rename = "Condition", default)]
pub condition: Option<String>,
#[serde(rename = "Label", default)]
pub label: Option<String>,
}
const ID_SOLUTION_FOLDER: &str = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
const ID_WEB_SITE_PROJECT: &str = "{E24C65DC-7377-472B-9ABA-BC803B73C61A}";
static PROJECT_TYPES: phf::Map<&'static str, &'static str> = phf::phf_map! {
"{CC5FD16D-436D-48AD-A40C-5A424C6E3E79}" => "Azure Project",
"{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}" => "ASP.NET 5",
"{603C0E0B-DB56-11DC-BE95-000D561079B0}" => "ASP.NET MVC 1",
"{F85E285D-A4E0-4152-9332-AB1D724D3325}" => "ASP.NET MVC 2",
"{E53F8FEA-EAE0-44A6-8774-FFD645390401}" => "ASP.NET MVC 3",
"{E3E379DF-F4C6-4180-9B81-6769533ABE47}" => "ASP.NET MVC 4",
"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}" => "C#",
"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}" => "C#",
"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}" => "C++",
"{A9ACE9BB-CECE-4E62-9AA4-C7E7C5BD2124}" => "Database",
"{4F174C21-8C12-11D0-8340-0000F80270F8}" => "Database (other project types)",
"{3EA9E505-35AC-4774-B492-AD1749C4943A}" => "Deployment Cab",
"{06A35CCD-C46D-44D5-987B-CF40FF872267}" => "Deployment Merge Module",
"{978C614F-708E-4E1A-B201-565925725DBA}" => "Deployment Setup",
"{AB322303-2255-48EF-A496-5904EB18DA55}" => "Deployment Smart Device Cab",
"{F135691A-BF7E-435D-8960-F99683D2D49C}" => "Distributed System",
"{BF6F8E12-879D-49E7-ADF0-5503146B24B8}" => "Dynamics 2012 AX C# in AOT",
"{F2A71F9B-5D33-465A-A702-920D77279786}" => "F#",
"{E6FDF86B-F3D1-11D4-8576-0002A516ECE8}" => "J#",
"{20D4826A-C6FA-45DB-90F4-C717570B9F32}" => "Legacy (2003) Smart Device (C#)",
"{CB4CE8C6-1BDB-4DC7-A4D3-65A1999772F8}" => "Legacy (2003) Smart Device (VB.NET)",
"{b69e3092-b931-443c-abe7-7e7b65f2a37f}" => "Micro Framework",
"{EFBA0AD7-5A72-4C68-AF49-83D382785DCF}" => "Mono for Android or Xamarin.Android",
"{6BC8ED88-2882-458C-8E55-DFD12B67127B}" => "MonoTouch or Xamarin.iOS",
"{F5B4F3BC-B597-4E2B-B552-EF5D8A32436F}" => "MonoTouch Binding",
"{786C830F-07A1-408B-BD7F-6EE04809D6DB}" => "Portable Class Library",
"{66A26720-8FB5-11D2-AA7E-00C04F688DDE}" => "Project Folders",
"{593B0543-81F6-4436-BA1E-4747859CAAE2}" => "SharePoint (C#)",
"{EC05E597-79D4-47f3-ADA0-324C4F7C7484}" => "SharePoint (VB.NET)",
"{F8810EC1-6754-47FC-A15F-DFABD2E3FA90}" => "SharePoint Workflow",
"{A1591282-1198-4647-A2B1-27E5FF5F6F3B}" => "Silverlight",
"{4D628B5B-2FBC-4AA6-8C16-197242AEB884}" => "Smart Device (C#)",
"{68B1623D-7FB9-47D8-8664-7ECEA3297D4F}" => "Smart Device (VB.NET)",
"{2150E333-8FDC-42A3-9474-1A3956D46DE8}" => "Solution Folder",
"{3AC096D0-A1C2-E12C-1390-A8335801FDAB}" => "Test",
"{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A}" => "Universal Windows Class Library",
"{F184B08F-C81C-45F6-A57F-5ABD9991F28F}" => "VB.NET",
"{C252FEB5-A946-4202-B1D4-9916A0590387}" => "Visual Database Tools",
"{54435603-DBB4-11D2-8724-00A0C9A8B90C}" => "Visual Studio 2015 Installer Project Extension",
"{A860303F-1F3F-4691-B57E-529FC101A107}" => "Visual Studio Tools for Applications (VSTA)",
"{BAA0C2D2-18E2-41B9-852F-F413020CAA33}" => "Visual Studio Tools for Office (VSTO)",
"{349C5851-65DF-11DA-9384-00065B846F21}" => "Web Application",
"{E24C65DC-7377-472B-9ABA-BC803B73C61A}" => "Web Site",
"{3D9AD99F-2412-4246-B90B-4EAA41C64699}" => "Windows Communication Foundation (WCF)",
"{76F1466A-8B6D-4E39-A767-685A06062A39}" => "Windows Phone 8/8.1 Blank/Hub/Webview App",
"{C089C8C0-30E0-4E22-80C0-CE093F111A43}" => "Windows Phone 8/8.1 App (C#)",
"{DB03555F-0C8B-43BE-9FF9-57896B3C5E56}" => "Windows Phone 8/8.1 App (VB.NET)",
"{60DC8134-EBA5-43B8-BCC9-BB4BC16C2548}" => "Windows Presentation Foundation (WPF)",
"{BC8A1FFA-BEE3-4634-8014-F334798102B3}" => "Windows Store (Metro) Apps & Components",
"{14822709-B5A1-4724-98CA-57A101D1B079}" => "Workflow (C#)",
"{D59BE175-2ED0-4C54-BE3D-CDAA9F3214C8}" => "Workflow (VB.NET)",
"{32F31D43-81CC-4C15-9DE6-3FC5453562B6}" => "Workflow Foundation",
"{6D335F3A-9D43-41b4-9D22-F6F17C4BE596}" => "XNA (Windows)",
"{2DF5C3F4-5A5F-47a9-8E94-23B4456F55E2}" => "XNA (XBox)",
"{D399B71A-8929-442a-A9AC-8BEC78BB2433}" => "XNA (Zune)",
"{930C7802-8A8C-48F9-8165-68863BCCD9DD}" => "WiX (Windows Installer XML)",
"{778DAE3C-4631-46EA-AA77-85C1314464D9}" => "VB.NET",
"{D954291E-2A0B-460D-934E-DC6B0785DB48}" => "Windows Store App Universal",
"{EAF909A5-FA59-4C3D-9431-0FCC20D5BCF9}" => "Intel C++",
"{7CF6DF6D-3B04-46F8-A40B-537D21BCA0B4}" => "Sandcastle Documentation",
"{A33008B1-5DAC-44D5-9060-242E3B6E38F2}" => "#SharpDevelop",
"{CFEE4113-1246-4D54-95CB-156813CB8593}" => "WiX (Windows Installer XML)",
"{C1CDDADD-2546-481F-9697-4EA41081F2FC}" => "Office/SharePoint App",
"{581633EB-B896-402F-8E60-36F3DA191C85}" => "LightSwitch Project",
"{8BB0C5E8-0616-4F60-8E55-A43933E57E9C}" => "LightSwitch",
"{82B43B9B-A64C-4715-B499-D71E9CA2BD60}" => "Extensibility",
"{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}" => "Node.js",
"{E53339B2-1760-4266-BCC7-CA923CBCF16C}" => "Docker Application",
"{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}" => "SQL Server Database",
};
impl Project {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Project> {
let file = File::open(path).wrap_err_with(|| "Failed to read project file")?;
Project::from_reader(file)
}
pub fn from_reader<R: Read>(reader: R) -> Result<Project> {
let mut de =
serde_xml_rs::Deserializer::new_from_reader(reader).non_contiguous_seq_elements(true);
let project: Project =
Project::deserialize(&mut de).wrap_err_with(|| "Failed to deserialize project file")?;
Ok(project)
}
#[must_use]
pub fn is_sdk_project(&self) -> bool {
self.sdk.is_some()
|| self
.imports
.iter()
.any(|i| i.iter().any(|elt| elt.sdk.is_some()))
}
}
impl PackagesConfig {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<PackagesConfig> {
let file = File::open(path).wrap_err_with(|| "Failed to read packages.config")?;
PackagesConfig::from_reader(file)
}
pub fn from_reader<R: Read>(reader: R) -> Result<PackagesConfig> {
let mut de =
serde_xml_rs::Deserializer::new_from_reader(reader).non_contiguous_seq_elements(true);
let config: PackagesConfig = PackagesConfig::deserialize(&mut de)
.wrap_err_with(|| "Failed to deserialize packages.config")?;
Ok(config)
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
#[test]
fn read_packages_config_from_reader_test() {
let rdr = Cursor::new(PACKAGES_CONFIG);
let p = PackagesConfig::from_reader(rdr).unwrap();
assert_eq!(3, p.packages.len());
assert_eq!("YaccLexTools", p.packages[0].name);
assert_eq!("0.2.2", p.packages[0].version);
}
#[test]
fn read_project_from_reader_test() {
let rdr = Cursor::new(REAL_SDK_PROJECT);
let p = Project::from_reader(rdr).unwrap();
assert!(p.item_group.is_some());
assert_eq!(1, p.item_group.as_ref().unwrap().len());
assert_eq!(
13,
p.item_group.as_ref().unwrap()[0]
.package_reference
.as_ref()
.unwrap()
.len()
);
}
#[test]
fn read_project_with_nugets_and_project_refs_test() {
let rdr = Cursor::new(PROJECT_WITH_PKG_AND_REF);
let p = Project::from_reader(rdr).unwrap();
assert!(p.item_group.is_some());
assert_eq!(2, p.item_group.as_ref().unwrap().len());
assert_eq!(
6,
p.item_group.as_ref().unwrap()[0]
.package_reference
.as_ref()
.unwrap()
.len()
);
assert_eq!(
1,
p.item_group.as_ref().unwrap()[1]
.project_reference
.as_ref()
.unwrap()
.len()
);
}
#[test]
fn read_project_real_vcxproj_test() {
let rdr = Cursor::new(VCXPROJ);
let p = Project::from_reader(rdr).unwrap();
assert!(p.item_group.is_some());
assert!(p.imports.is_some());
assert_eq!(2, p.item_group.as_ref().unwrap().len());
assert_eq!(3, p.imports.as_ref().unwrap().len());
}
#[test]
fn sdk_project_default_project() {
let p = Project {
sdk: None,
item_group: None,
imports: None,
import_group: None,
};
let actual = p.is_sdk_project();
assert!(!actual);
}
#[test]
fn sdk_project_sdk_elt_set() {
let p = Project {
sdk: Some("1".to_owned()),
item_group: None,
imports: None,
import_group: None,
};
let actual = p.is_sdk_project();
assert!(actual);
}
#[test]
fn sdk_project_one_import_with_sdk_elt_set() {
let p = Project {
sdk: None,
item_group: None,
imports: Some(vec![Import {
project: "p1".to_owned(),
sdk: Some("1".to_owned()),
condition: None,
label: None,
}]),
import_group: None,
};
let actual = p.is_sdk_project();
assert!(actual);
}
#[test]
fn sdk_project_one_import_without_sdk_elt_set() {
let p = Project {
sdk: None,
item_group: None,
imports: Some(vec![Import {
project: "p1".to_owned(),
sdk: None,
condition: None,
label: None,
}]),
import_group: None,
};
let actual = p.is_sdk_project();
assert!(!actual);
}
const REAL_SDK_PROJECT: &str = r#"<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TestProjectType>UnitTest</TestProjectType>
<AssemblyTitle>_tst.net</AssemblyTitle>
<Company>Egoroff</Company>
<Product>_tst.net</Product>
<Copyright>Copyright © 2009-2022 Alexander Egorov</Copyright>
<OutputPath>bin\$(Configuration)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>portable</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.analyzers" Version="0.10.0" />
<PackageReference Include="xunit.assert" Version="2.4.1" />
<PackageReference Include="xunit.core" Version="2.4.1" />
<PackageReference Include="xunit.extensibility.core" Version="2.4.1" />
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>"#;
const PACKAGES_CONFIG: &str = r#"<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="YaccLexTools" version="0.2.2" targetFramework="net45" />
<package id="Enums.NET" version="4.0.0" targetFramework="net48" />
<package id="FluentValidation" version="9.5.2" targetFramework="net48" />
</packages>"#;
const PROJECT_WITH_PKG_AND_REF: &str = r#"<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="Moq" Version="4.15.2" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="1.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Hiring\Hiring.csproj" />
</ItemGroup>
</Project>
"#;
const VCXPROJ: &str = r#"<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug-static|Win32">
<Configuration>Debug-static</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\..\src\arena.c" />
<ClCompile Include="..\..\..\..\src\background_thread.c" />
<ClCompile Include="..\..\..\..\src\base.c" />
<ClCompile Include="..\..\..\..\src\bin.c" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{8D6BB292-9E1C-413D-9F98-4864BDC1514A}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>jemalloc</RootNamespace>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
<ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>"#;
}