2020-07-07 22:45:32 +00:00
|
|
|
use quick_xml::events::Event;
|
|
|
|
use quick_xml::Reader;
|
2019-10-02 06:56:49 +00:00
|
|
|
use std::ffi::OsStr;
|
|
|
|
use std::iter::Iterator;
|
2020-08-07 19:13:12 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
2019-10-02 06:56:49 +00:00
|
|
|
use std::str;
|
|
|
|
|
2022-03-26 09:42:19 +00:00
|
|
|
use super::{Context, Module, ModuleConfig};
|
2019-10-02 06:56:49 +00:00
|
|
|
use crate::configs::dotnet::DotnetConfig;
|
2020-07-07 22:45:32 +00:00
|
|
|
use crate::formatter::StringFormatter;
|
2020-01-15 18:52:38 +00:00
|
|
|
use crate::utils;
|
2019-10-02 06:56:49 +00:00
|
|
|
|
|
|
|
type JValue = serde_json::Value;
|
2021-04-29 21:22:20 +00:00
|
|
|
use crate::formatter::VersionFormatter;
|
2019-10-02 06:56:49 +00:00
|
|
|
|
|
|
|
const GLOBAL_JSON_FILE: &str = "global.json";
|
|
|
|
const PROJECT_JSON_FILE: &str = "project.json";
|
|
|
|
|
|
|
|
/// A module which shows the latest (or pinned) version of the dotnet SDK
|
2021-02-20 14:38:03 +00:00
|
|
|
|
2019-10-02 06:56:49 +00:00
|
|
|
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
2021-02-20 14:38:03 +00:00
|
|
|
let mut module = context.new_module("dotnet");
|
|
|
|
let config = DotnetConfig::try_load(module.config);
|
|
|
|
|
2020-04-03 16:34:35 +00:00
|
|
|
// First check if this is a DotNet Project before doing the O(n)
|
|
|
|
// check for the version using the JSON files
|
|
|
|
let is_dotnet_project = context
|
|
|
|
.try_begin_scan()?
|
2021-02-20 14:38:03 +00:00
|
|
|
.set_files(&config.detect_files)
|
|
|
|
.set_extensions(&config.detect_extensions)
|
|
|
|
.set_folders(&config.detect_folders)
|
2020-04-03 16:34:35 +00:00
|
|
|
.is_match();
|
|
|
|
|
|
|
|
if !is_dotnet_project {
|
2019-10-02 06:56:49 +00:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2020-04-03 16:34:35 +00:00
|
|
|
let dotnet_files = get_local_dotnet_files(context).ok()?;
|
|
|
|
|
2019-10-02 06:56:49 +00:00
|
|
|
// Internally, this module uses its own mechanism for version detection.
|
|
|
|
// Typically it is twice as fast as running `dotnet --version`.
|
|
|
|
let enable_heuristic = config.heuristic;
|
|
|
|
|
2020-07-07 22:45:32 +00:00
|
|
|
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
|
|
|
|
formatter
|
|
|
|
.map_style(|variable| match variable {
|
|
|
|
"style" => Some(Ok(config.style)),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.map(|variable| match variable {
|
|
|
|
"symbol" => Some(Ok(config.symbol)),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.map(|variable| match variable {
|
|
|
|
"version" => {
|
|
|
|
let version = if enable_heuristic {
|
2021-12-03 20:15:31 +00:00
|
|
|
let repo_root = context.get_repo().ok().and_then(|r| r.workdir.as_deref());
|
2021-02-11 20:34:47 +00:00
|
|
|
estimate_dotnet_version(
|
|
|
|
context,
|
|
|
|
&dotnet_files,
|
|
|
|
&context.current_dir,
|
|
|
|
repo_root,
|
|
|
|
)
|
2020-07-07 22:45:32 +00:00
|
|
|
} else {
|
2021-02-11 20:34:47 +00:00
|
|
|
get_version_from_cli(context)
|
2020-07-07 22:45:32 +00:00
|
|
|
};
|
2021-04-29 21:22:20 +00:00
|
|
|
VersionFormatter::format_module_version(
|
|
|
|
module.get_name(),
|
|
|
|
&version?,
|
|
|
|
config.version_format,
|
|
|
|
)
|
|
|
|
.map(Ok)
|
2020-07-07 22:45:32 +00:00
|
|
|
}
|
|
|
|
"tfm" => find_current_tfm(&dotnet_files).map(Ok),
|
|
|
|
_ => None,
|
|
|
|
})
|
2021-11-01 21:18:45 +00:00
|
|
|
.parse(None, Some(context))
|
2020-07-07 22:45:32 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
module.set_segments(match parsed {
|
|
|
|
Ok(segments) => segments,
|
|
|
|
Err(error) => {
|
|
|
|
log::warn!("Error in module `dotnet`:\n{}", error);
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
});
|
2019-10-02 06:56:49 +00:00
|
|
|
|
|
|
|
Some(module)
|
|
|
|
}
|
|
|
|
|
2020-08-07 19:13:12 +00:00
|
|
|
fn find_current_tfm(files: &[DotNetFile]) -> Option<String> {
|
2020-07-07 22:45:32 +00:00
|
|
|
let get_file_of_type = |t: FileType| files.iter().find(|f| f.file_type == t);
|
|
|
|
|
|
|
|
let relevant_file = get_file_of_type(FileType::ProjectFile)?;
|
|
|
|
|
2020-08-07 19:13:12 +00:00
|
|
|
get_tfm_from_project_file(relevant_file.path.as_path())
|
2020-07-07 22:45:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_tfm_from_project_file(path: &Path) -> Option<String> {
|
|
|
|
let project_file = utils::read_file(path).ok()?;
|
|
|
|
let mut reader = Reader::from_str(&project_file);
|
|
|
|
reader.trim_text(true);
|
|
|
|
|
|
|
|
let mut in_tfm = false;
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
|
|
|
|
loop {
|
|
|
|
match reader.read_event(&mut buf) {
|
|
|
|
// for triggering namespaced events, use this instead:
|
|
|
|
// match reader.read_namespaced_event(&mut buf) {
|
|
|
|
Ok(Event::Start(ref e)) => {
|
|
|
|
// for namespaced:
|
|
|
|
// Ok((ref namespace_value, Event::Start(ref e)))
|
|
|
|
match e.name() {
|
|
|
|
b"TargetFrameworks" => in_tfm = true,
|
|
|
|
b"TargetFramework" => in_tfm = true,
|
|
|
|
_ => in_tfm = false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// unescape and decode the text event using the reader encoding
|
|
|
|
Ok(Event::Text(e)) => {
|
|
|
|
if in_tfm {
|
|
|
|
return e.unescape_and_decode(&reader).ok();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(Event::Eof) => break, // exits the loop when reaching end of file
|
|
|
|
Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
|
|
|
|
_ => (), // There are several other `Event`s we do not consider here
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we don't keep a borrow elsewhere, we can clear the buffer to keep memory usage low
|
|
|
|
buf.clear();
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2020-08-07 19:13:12 +00:00
|
|
|
fn estimate_dotnet_version(
|
2021-02-11 20:34:47 +00:00
|
|
|
context: &Context,
|
2020-08-07 19:13:12 +00:00
|
|
|
files: &[DotNetFile],
|
2019-10-02 06:56:49 +00:00
|
|
|
current_dir: &Path,
|
|
|
|
repo_root: Option<&Path>,
|
2021-04-29 21:22:20 +00:00
|
|
|
) -> Option<String> {
|
2019-10-02 06:56:49 +00:00
|
|
|
let get_file_of_type = |t: FileType| files.iter().find(|f| f.file_type == t);
|
|
|
|
|
|
|
|
// It's important to check for a global.json or a solution file first,
|
|
|
|
// but otherwise we can take any relevant file. We'll take whichever is first.
|
|
|
|
let relevant_file = get_file_of_type(FileType::GlobalJson)
|
|
|
|
.or_else(|| get_file_of_type(FileType::SolutionFile))
|
|
|
|
.or_else(|| files.iter().next())?;
|
|
|
|
|
|
|
|
match relevant_file.file_type {
|
2020-08-07 19:13:12 +00:00
|
|
|
FileType::GlobalJson => get_pinned_sdk_version_from_file(relevant_file.path.as_path())
|
2021-02-11 20:34:47 +00:00
|
|
|
.or_else(|| get_latest_sdk_from_cli(context)),
|
2019-10-02 06:56:49 +00:00
|
|
|
FileType::SolutionFile => {
|
|
|
|
// With this heuristic, we'll assume that a "global.json" won't
|
|
|
|
// be found in any directory above the solution file.
|
2021-02-11 20:34:47 +00:00
|
|
|
get_latest_sdk_from_cli(context)
|
2019-10-02 06:56:49 +00:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
// If we see a dotnet project, we'll check a small number of neighboring
|
|
|
|
// directories to see if we can find a global.json. Otherwise, assume the
|
|
|
|
// latest SDK is in use.
|
2021-02-11 20:34:47 +00:00
|
|
|
try_find_nearby_global_json(current_dir, repo_root)
|
|
|
|
.or_else(|| get_latest_sdk_from_cli(context))
|
2019-10-02 06:56:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Looks for a `global.json` which may exist in one of the parent directories of the current path.
|
|
|
|
/// If there is one present, and it contains valid version pinning information, then return that version.
|
|
|
|
///
|
|
|
|
/// The following places are scanned:
|
|
|
|
/// - The parent of the current directory
|
|
|
|
/// (Unless there is a git repository, and the parent is above the root of that repository)
|
|
|
|
/// - The root of the git repository
|
|
|
|
/// (If there is one)
|
2021-04-29 21:22:20 +00:00
|
|
|
fn try_find_nearby_global_json(current_dir: &Path, repo_root: Option<&Path>) -> Option<String> {
|
2021-07-29 18:27:46 +00:00
|
|
|
let current_dir_is_repo_root = repo_root.map_or(false, |r| r == current_dir);
|
2019-10-02 06:56:49 +00:00
|
|
|
let parent_dir = if current_dir_is_repo_root {
|
|
|
|
// Don't scan the parent directory if it's above the root of a git repository
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
current_dir.parent()
|
|
|
|
};
|
|
|
|
|
|
|
|
// Check the parent directory, or otherwise the repository root, for a global.json
|
|
|
|
let mut check_dirs = parent_dir
|
|
|
|
.iter()
|
2021-09-02 15:48:29 +00:00
|
|
|
.chain(&repo_root)
|
2019-10-02 06:56:49 +00:00
|
|
|
.copied() // Copies the reference, not the Path itself
|
|
|
|
.collect::<Vec<&Path>>();
|
|
|
|
|
|
|
|
// The parent directory and repository root may be the same directory,
|
|
|
|
// so avoid checking it twice.
|
|
|
|
check_dirs.dedup();
|
|
|
|
|
|
|
|
check_dirs
|
|
|
|
.iter()
|
|
|
|
// repo_root may be the same as the current directory. We don't need to scan it again.
|
|
|
|
.filter(|&&d| d != current_dir)
|
2021-02-20 14:38:03 +00:00
|
|
|
.find_map(|d| check_directory_for_global_json(d))
|
2019-10-02 06:56:49 +00:00
|
|
|
}
|
|
|
|
|
2021-04-29 21:22:20 +00:00
|
|
|
fn check_directory_for_global_json(path: &Path) -> Option<String> {
|
2019-10-02 06:56:49 +00:00
|
|
|
let global_json_path = path.join(GLOBAL_JSON_FILE);
|
|
|
|
log::debug!(
|
|
|
|
"Checking if global.json exists at: {}",
|
|
|
|
&global_json_path.display()
|
|
|
|
);
|
|
|
|
if global_json_path.exists() {
|
|
|
|
get_pinned_sdk_version_from_file(&global_json_path)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-29 21:22:20 +00:00
|
|
|
fn get_pinned_sdk_version_from_file(path: &Path) -> Option<String> {
|
2019-10-02 06:56:49 +00:00
|
|
|
let json_text = crate::utils::read_file(path).ok()?;
|
|
|
|
log::debug!(
|
|
|
|
"Checking if .NET SDK version is pinned in: {}",
|
|
|
|
path.display()
|
|
|
|
);
|
|
|
|
get_pinned_sdk_version(&json_text)
|
|
|
|
}
|
|
|
|
|
2021-04-29 21:22:20 +00:00
|
|
|
fn get_pinned_sdk_version(json: &str) -> Option<String> {
|
2019-10-02 06:56:49 +00:00
|
|
|
let parsed_json: JValue = serde_json::from_str(json).ok()?;
|
|
|
|
|
|
|
|
match parsed_json {
|
|
|
|
JValue::Object(root) => {
|
|
|
|
let sdk = root.get("sdk")?;
|
|
|
|
match sdk {
|
|
|
|
JValue::Object(sdk) => {
|
|
|
|
let version = sdk.get("version")?;
|
|
|
|
match version {
|
|
|
|
JValue::String(version_string) => {
|
|
|
|
let mut buffer = String::with_capacity(version_string.len() + 1);
|
|
|
|
buffer.push_str(version_string);
|
2021-04-29 21:22:20 +00:00
|
|
|
Some(buffer)
|
2019-10-02 06:56:49 +00:00
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 19:38:11 +00:00
|
|
|
fn get_local_dotnet_files(context: &Context) -> Result<Vec<DotNetFile>, std::io::Error> {
|
2019-10-02 06:56:49 +00:00
|
|
|
Ok(context
|
2020-02-03 21:57:48 +00:00
|
|
|
.dir_contents()?
|
|
|
|
.files()
|
2019-10-02 06:56:49 +00:00
|
|
|
.filter_map(|p| {
|
|
|
|
get_dotnet_file_type(p).map(|t| DotNetFile {
|
2020-08-07 19:13:12 +00:00
|
|
|
path: context.current_dir.join(p),
|
2019-10-02 06:56:49 +00:00
|
|
|
file_type: t,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_dotnet_file_type(path: &Path) -> Option<FileType> {
|
|
|
|
let file_name_lower = map_str_to_lower(path.file_name());
|
|
|
|
|
|
|
|
match file_name_lower.as_ref().map(|f| f.as_ref()) {
|
|
|
|
Some(GLOBAL_JSON_FILE) => return Some(FileType::GlobalJson),
|
|
|
|
Some(PROJECT_JSON_FILE) => return Some(FileType::ProjectJson),
|
|
|
|
_ => (),
|
|
|
|
};
|
|
|
|
|
|
|
|
let extension_lower = map_str_to_lower(path.extension());
|
|
|
|
|
|
|
|
match extension_lower.as_ref().map(|f| f.as_ref()) {
|
|
|
|
Some("sln") => return Some(FileType::SolutionFile),
|
2021-07-29 18:27:46 +00:00
|
|
|
Some("csproj" | "fsproj" | "xproj") => return Some(FileType::ProjectFile),
|
|
|
|
Some("props" | "targets") => return Some(FileType::MsBuildFile),
|
2019-10-02 06:56:49 +00:00
|
|
|
_ => (),
|
|
|
|
};
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn map_str_to_lower(value: Option<&OsStr>) -> Option<String> {
|
|
|
|
Some(value?.to_str()?.to_ascii_lowercase())
|
|
|
|
}
|
|
|
|
|
2021-04-29 21:22:20 +00:00
|
|
|
fn get_version_from_cli(context: &Context) -> Option<String> {
|
2021-02-11 20:34:47 +00:00
|
|
|
let version_output = context.exec_cmd("dotnet", &["--version"])?;
|
2021-04-29 21:22:20 +00:00
|
|
|
Some(format!("v{}", version_output.stdout.trim()))
|
2019-10-02 06:56:49 +00:00
|
|
|
}
|
|
|
|
|
2021-04-29 21:22:20 +00:00
|
|
|
fn get_latest_sdk_from_cli(context: &Context) -> Option<String> {
|
2021-02-11 20:34:47 +00:00
|
|
|
match context.exec_cmd("dotnet", &["--list-sdks"]) {
|
2020-01-15 18:52:38 +00:00
|
|
|
Some(sdks_output) => {
|
|
|
|
fn parse_failed<T>() -> Option<T> {
|
|
|
|
log::warn!("Unable to parse the output from `dotnet --list-sdks`.");
|
|
|
|
None
|
2021-01-16 12:26:52 +00:00
|
|
|
}
|
2020-01-15 18:52:38 +00:00
|
|
|
let latest_sdk = sdks_output
|
|
|
|
.stdout
|
|
|
|
.lines()
|
|
|
|
.map(str::trim)
|
|
|
|
.filter(|l| !l.is_empty())
|
|
|
|
.last()
|
|
|
|
.or_else(parse_failed)?;
|
|
|
|
let take_until = latest_sdk.find('[').or_else(parse_failed)? - 1;
|
|
|
|
if take_until > 1 {
|
|
|
|
let version = &latest_sdk[..take_until];
|
|
|
|
let mut buffer = String::with_capacity(version.len() + 1);
|
|
|
|
buffer.push_str(version);
|
2021-04-29 21:22:20 +00:00
|
|
|
Some(buffer)
|
2020-01-15 18:52:38 +00:00
|
|
|
} else {
|
|
|
|
parse_failed()
|
|
|
|
}
|
2019-10-02 06:56:49 +00:00
|
|
|
}
|
2020-01-15 18:52:38 +00:00
|
|
|
None => {
|
|
|
|
// Older versions of the dotnet cli do not support the --list-sdks command
|
|
|
|
// So, if the status code indicates failure, fall back to `dotnet --version`
|
2020-09-28 20:38:50 +00:00
|
|
|
log::debug!(
|
2020-01-15 18:52:38 +00:00
|
|
|
"Received a non-success exit code from `dotnet --list-sdks`. \
|
|
|
|
Falling back to `dotnet --version`.",
|
|
|
|
);
|
2021-02-11 20:34:47 +00:00
|
|
|
get_version_from_cli(context)
|
2019-10-02 06:56:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-07 19:13:12 +00:00
|
|
|
struct DotNetFile {
|
|
|
|
path: PathBuf,
|
2019-10-02 06:56:49 +00:00
|
|
|
file_type: FileType,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(PartialEq)]
|
|
|
|
enum FileType {
|
|
|
|
ProjectJson,
|
|
|
|
ProjectFile,
|
|
|
|
GlobalJson,
|
|
|
|
SolutionFile,
|
2020-06-21 08:22:20 +00:00
|
|
|
MsBuildFile,
|
2019-10-02 06:56:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-07 19:13:12 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use crate::test::ModuleRenderer;
|
2021-07-16 19:20:59 +00:00
|
|
|
use crate::utils::create_command;
|
2020-08-07 19:13:12 +00:00
|
|
|
use ansi_term::Color;
|
|
|
|
use std::fs::{self, OpenOptions};
|
|
|
|
use std::io::{self, Write};
|
|
|
|
use tempfile::{self, TempDir};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_nothing_in_directory_with_zero_relevant_files() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
2021-07-29 18:27:46 +00:00
|
|
|
expect_output(workspace.path(), None);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_latest_in_directory_with_directory_build_props_file() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
|
|
|
touch_path(&workspace, "Directory.Build.props", None)?;
|
|
|
|
expect_output(
|
2021-07-29 18:27:46 +00:00
|
|
|
workspace.path(),
|
2021-07-04 14:32:47 +00:00
|
|
|
Some(format!(
|
|
|
|
"via {}",
|
|
|
|
Color::Blue.bold().paint(".NET v3.1.103 ")
|
|
|
|
)),
|
2021-02-11 20:08:17 +00:00
|
|
|
);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_latest_in_directory_with_directory_build_targets_file() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
|
|
|
touch_path(&workspace, "Directory.Build.targets", None)?;
|
|
|
|
expect_output(
|
2021-07-29 18:27:46 +00:00
|
|
|
workspace.path(),
|
2021-07-04 14:32:47 +00:00
|
|
|
Some(format!(
|
|
|
|
"via {}",
|
|
|
|
Color::Blue.bold().paint(".NET v3.1.103 ")
|
|
|
|
)),
|
2021-02-11 20:08:17 +00:00
|
|
|
);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_latest_in_directory_with_packages_props_file() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
|
|
|
touch_path(&workspace, "Packages.props", None)?;
|
|
|
|
expect_output(
|
2021-07-29 18:27:46 +00:00
|
|
|
workspace.path(),
|
2021-07-04 14:32:47 +00:00
|
|
|
Some(format!(
|
|
|
|
"via {}",
|
|
|
|
Color::Blue.bold().paint(".NET v3.1.103 ")
|
|
|
|
)),
|
2021-02-11 20:08:17 +00:00
|
|
|
);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_latest_in_directory_with_solution() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
|
|
|
touch_path(&workspace, "solution.sln", None)?;
|
2021-07-29 18:27:46 +00:00
|
|
|
expect_output(workspace.path(), None);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_latest_in_directory_with_csproj() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
|
|
|
let csproj = make_csproj_with_tfm("TargetFramework", "netstandard2.0");
|
|
|
|
touch_path(&workspace, "project.csproj", Some(&csproj))?;
|
|
|
|
expect_output(
|
2021-07-29 18:27:46 +00:00
|
|
|
workspace.path(),
|
2020-08-07 19:13:12 +00:00
|
|
|
Some(format!(
|
2021-07-04 14:32:47 +00:00
|
|
|
"via {}",
|
2021-03-18 17:46:54 +00:00
|
|
|
Color::Blue.bold().paint(".NET v3.1.103 🎯 netstandard2.0 ")
|
2020-08-07 19:13:12 +00:00
|
|
|
)),
|
2021-02-11 20:08:17 +00:00
|
|
|
);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_latest_in_directory_with_fsproj() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
|
|
|
touch_path(&workspace, "project.fsproj", None)?;
|
|
|
|
expect_output(
|
2021-07-29 18:27:46 +00:00
|
|
|
workspace.path(),
|
2021-07-04 14:32:47 +00:00
|
|
|
Some(format!(
|
|
|
|
"via {}",
|
|
|
|
Color::Blue.bold().paint(".NET v3.1.103 ")
|
|
|
|
)),
|
2021-02-11 20:08:17 +00:00
|
|
|
);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_latest_in_directory_with_xproj() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
|
|
|
touch_path(&workspace, "project.xproj", None)?;
|
|
|
|
expect_output(
|
2021-07-29 18:27:46 +00:00
|
|
|
workspace.path(),
|
2021-07-04 14:32:47 +00:00
|
|
|
Some(format!(
|
|
|
|
"via {}",
|
|
|
|
Color::Blue.bold().paint(".NET v3.1.103 ")
|
|
|
|
)),
|
2021-02-11 20:08:17 +00:00
|
|
|
);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_latest_in_directory_with_project_json() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
|
|
|
touch_path(&workspace, "project.json", None)?;
|
|
|
|
expect_output(
|
2021-07-29 18:27:46 +00:00
|
|
|
workspace.path(),
|
2021-07-04 14:32:47 +00:00
|
|
|
Some(format!(
|
|
|
|
"via {}",
|
|
|
|
Color::Blue.bold().paint(".NET v3.1.103 ")
|
|
|
|
)),
|
2021-02-11 20:08:17 +00:00
|
|
|
);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_pinned_in_directory_with_global_json() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
|
|
|
let global_json = make_pinned_sdk_json("1.2.3");
|
|
|
|
touch_path(&workspace, "global.json", Some(&global_json))?;
|
|
|
|
expect_output(
|
2021-07-29 18:27:46 +00:00
|
|
|
workspace.path(),
|
2021-07-04 14:32:47 +00:00
|
|
|
Some(format!("via {}", Color::Blue.bold().paint(".NET v1.2.3 "))),
|
2021-02-11 20:08:17 +00:00
|
|
|
);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_pinned_in_project_below_root_with_global_json() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
|
|
|
let global_json = make_pinned_sdk_json("1.2.3");
|
|
|
|
let csproj = make_csproj_with_tfm("TargetFramework", "netstandard2.0");
|
|
|
|
touch_path(&workspace, "global.json", Some(&global_json))?;
|
|
|
|
touch_path(&workspace, "project/project.csproj", Some(&csproj))?;
|
|
|
|
expect_output(
|
|
|
|
&workspace.path().join("project"),
|
|
|
|
Some(format!(
|
2021-07-04 14:32:47 +00:00
|
|
|
"via {}",
|
2021-03-18 17:46:54 +00:00
|
|
|
Color::Blue.bold().paint(".NET v1.2.3 🎯 netstandard2.0 ")
|
2020-08-07 19:13:12 +00:00
|
|
|
)),
|
2021-02-11 20:08:17 +00:00
|
|
|
);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_pinned_in_deeply_nested_project_within_repository() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(true)?;
|
|
|
|
let global_json = make_pinned_sdk_json("1.2.3");
|
|
|
|
let csproj = make_csproj_with_tfm("TargetFramework", "netstandard2.0");
|
|
|
|
touch_path(&workspace, "global.json", Some(&global_json))?;
|
|
|
|
touch_path(
|
|
|
|
&workspace,
|
|
|
|
"deep/path/to/project/project.csproj",
|
|
|
|
Some(&csproj),
|
|
|
|
)?;
|
|
|
|
expect_output(
|
|
|
|
&workspace.path().join("deep/path/to/project"),
|
|
|
|
Some(format!(
|
2021-07-04 14:32:47 +00:00
|
|
|
"via {}",
|
2021-03-18 17:46:54 +00:00
|
|
|
Color::Blue.bold().paint(".NET v1.2.3 🎯 netstandard2.0 ")
|
2020-08-07 19:13:12 +00:00
|
|
|
)),
|
2021-02-11 20:08:17 +00:00
|
|
|
);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_single_tfm() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
|
|
|
let csproj = make_csproj_with_tfm("TargetFramework", "netstandard2.0");
|
|
|
|
touch_path(&workspace, "project.csproj", Some(&csproj))?;
|
|
|
|
expect_output(
|
|
|
|
workspace.path(),
|
|
|
|
Some(format!(
|
2021-07-04 14:32:47 +00:00
|
|
|
"via {}",
|
2021-03-18 17:46:54 +00:00
|
|
|
Color::Blue.bold().paint(".NET v3.1.103 🎯 netstandard2.0 ")
|
2020-08-07 19:13:12 +00:00
|
|
|
)),
|
2021-02-11 20:08:17 +00:00
|
|
|
);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn shows_multiple_tfms() -> io::Result<()> {
|
|
|
|
let workspace = create_workspace(false)?;
|
|
|
|
let csproj = make_csproj_with_tfm("TargetFrameworks", "netstandard2.0;net461");
|
|
|
|
touch_path(&workspace, "project.csproj", Some(&csproj))?;
|
|
|
|
expect_output(
|
|
|
|
workspace.path(),
|
|
|
|
Some(format!(
|
2021-07-04 14:32:47 +00:00
|
|
|
"via {}",
|
2020-08-07 19:13:12 +00:00
|
|
|
Color::Blue
|
|
|
|
.bold()
|
2021-03-18 17:46:54 +00:00
|
|
|
.paint(".NET v3.1.103 🎯 netstandard2.0;net461 ")
|
2020-08-07 19:13:12 +00:00
|
|
|
)),
|
2021-02-11 20:08:17 +00:00
|
|
|
);
|
2020-08-07 19:13:12 +00:00
|
|
|
workspace.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_workspace(is_repo: bool) -> io::Result<TempDir> {
|
|
|
|
let repo_dir = tempfile::tempdir()?;
|
|
|
|
|
|
|
|
if is_repo {
|
2021-07-16 19:20:59 +00:00
|
|
|
create_command("git")?
|
2020-08-07 19:13:12 +00:00
|
|
|
.args(&["init", "--quiet"])
|
|
|
|
.current_dir(repo_dir.path())
|
|
|
|
.output()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(repo_dir)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn touch_path(
|
|
|
|
workspace: &TempDir,
|
|
|
|
relative_path: &str,
|
|
|
|
contents: Option<&str>,
|
|
|
|
) -> io::Result<()> {
|
|
|
|
let path = workspace.path().join(relative_path);
|
|
|
|
|
|
|
|
fs::create_dir_all(
|
|
|
|
path.parent()
|
|
|
|
.expect("Expected relative_path to be a file in a directory"),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let mut file = OpenOptions::new()
|
|
|
|
.write(true)
|
|
|
|
.create(true)
|
|
|
|
.truncate(true)
|
|
|
|
.open(&path)?;
|
|
|
|
write!(file, "{}", contents.unwrap_or(""))?;
|
|
|
|
file.sync_data()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn make_pinned_sdk_json(version: &str) -> String {
|
|
|
|
let json_text = r#"
|
|
|
|
{
|
|
|
|
"sdk": {
|
|
|
|
"version": "INSERT_VERSION"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#;
|
|
|
|
json_text.replace("INSERT_VERSION", version)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn make_csproj_with_tfm(tfm_element: &str, tfm: &str) -> String {
|
|
|
|
let json_text = r#"
|
|
|
|
<Project>
|
|
|
|
<PropertyGroup>
|
|
|
|
<TFM_ELEMENT>TFM_VALUE</TFM_ELEMENT>
|
|
|
|
</PropertyGroup>
|
|
|
|
</Project>
|
|
|
|
"#;
|
|
|
|
json_text
|
|
|
|
.replace("TFM_ELEMENT", tfm_element)
|
|
|
|
.replace("TFM_VALUE", tfm)
|
|
|
|
}
|
|
|
|
|
2021-02-11 20:08:17 +00:00
|
|
|
fn expect_output(dir: &Path, expected: Option<String>) {
|
2020-08-07 19:13:12 +00:00
|
|
|
let actual = ModuleRenderer::new("dotnet").path(dir).collect();
|
|
|
|
|
|
|
|
assert_eq!(actual, expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_parse_version_from_global_json() {
|
|
|
|
let json_text = r#"
|
2019-10-02 06:56:49 +00:00
|
|
|
{
|
|
|
|
"sdk": {
|
|
|
|
"version": "1.2.3"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#;
|
|
|
|
|
2020-08-07 19:13:12 +00:00
|
|
|
let version = get_pinned_sdk_version(json_text).unwrap();
|
2021-04-29 21:22:20 +00:00
|
|
|
assert_eq!("1.2.3", version);
|
2020-08-07 19:13:12 +00:00
|
|
|
}
|
2019-10-02 06:56:49 +00:00
|
|
|
|
2020-08-07 19:13:12 +00:00
|
|
|
#[test]
|
|
|
|
fn should_ignore_empty_global_json() {
|
|
|
|
let json_text = "{}";
|
2019-10-02 06:56:49 +00:00
|
|
|
|
2020-08-07 19:13:12 +00:00
|
|
|
let version = get_pinned_sdk_version(json_text);
|
|
|
|
assert!(version.is_none());
|
|
|
|
}
|
2019-10-02 06:56:49 +00:00
|
|
|
}
|