<commit_before>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
use std::cmp::Ordering;

use super::command::capture_command;
use crate::update_service::Platform;
use crate::util::errors::SetupError;
use lazy_static::lazy_static;
use regex::bytes::Regex as BinRegex;
use regex::Regex;
use tokio::fs;

use super::errors::AnyError;

lazy_static! {
	static ref LDCONFIG_STDC_RE: Regex = Regex::new(r"libstdc\+\+.* => (.+)").unwrap();
	static ref LDD_VERSION_RE: BinRegex = BinRegex::new(r"^ldd.*(.+)\.(.+)\s").unwrap();
	static ref LIBSTD_CXX_VERSION_RE: BinRegex =
		BinRegex::new(r"GLIBCXX_([0-9]+)\.([0-9]+)(?:\.([0-9]+))?").unwrap();
	static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 18);
	static ref MIN_LDD_VERSION: SimpleSemver = SimpleSemver::new(2, 17, 0);
}

pub struct PreReqChecker {}

impl Default for PreReqChecker {
	fn default() -> Self {
		Self::new()
	}
}

impl PreReqChecker {
	pub fn new() -> PreReqChecker {
		PreReqChecker {}
	}

	#[cfg(not(target_os = "linux"))]
	pub async fn verify(&self) -> Result<Platform, AnyError> {
		Platform::env_default().ok_or_else(|| {
			SetupError("VS Code is not supported on this platform".to_owned()).into()
		})
	}

	#[cfg(target_os = "linux")]
	pub async fn verify(&self) -> Result<Platform, AnyError> {
		let (gnu_a, gnu_b, or_musl) = tokio::join!(
			check_glibc_version(),
			check_glibcxx_version(),
			check_musl_interpreter()
		);

		if gnu_a.is_ok() && gnu_b.is_ok() {
			return Ok(if cfg!(target_arch = "x86_64") {
				Platform::LinuxX64
			} else if cfg!(target_arch = "armhf") {
				Platform::LinuxARM32
			} else {
				Platform::LinuxARM64
			});
		}

		if or_musl.is_ok() {
			return Ok(if cfg!(target_arch = "x86_64") {
				Platform::LinuxAlpineX64
			} else {
				Platform::LinuxAlpineARM64
			});
		}

		let mut errors: Vec<String> = vec![];
		if let Err(e) = gnu_a {
			errors.push(e);
		} else if let Err(e) = gnu_b {
			errors.push(e);
		}

		if let Err(e) = or_musl {
			errors.push(e);
		}

		let bullets = errors
			.iter()
			.map(|e| format!("  - {}", e))
			.collect::<Vec<String>>()
			.join("\n");

		Err(AnyError::from(SetupError(format!(
			"This machine not meet VS Code Server's prerequisites, expected either...\n{}",
			bullets,
		))))
	}
}

#[allow(dead_code)]
async fn check_musl_interpreter() -> Result<(), String> {
	const MUSL_PATH: &str = if cfg!(target_platform = "aarch64") {
		"/lib/ld-musl-aarch64.so.1"
	} else {
		"/lib/ld-musl-x86_64.so.1"
	};

	if fs::metadata(MUSL_PATH).await.is_err() {
		return Err(format!(
			"find {}, which is required to run the VS Code Server in musl environments",
			MUSL_PATH
		));
	}

	Ok(())
}

#[allow(dead_code)]
async fn check_glibc_version() -> Result<(), String> {
	let ldd_version = capture_command("ldd", ["--version"])
		.await
		.ok()
		.and_then(|o| extract_ldd_version(&o.stdout));

	if let Some(v) = ldd_version {
		return if v.gte(&MIN_LDD_VERSION) {
			Ok(())
		} else {
			Err(format!(
				"find GLIBC >= 2.17 (but found {} instead) for GNU environments",
				v
			))
		};
	}

	Ok(())
}

#[allow(dead_code)]
async fn check_glibcxx_version() -> Result<(), String> {
	let mut libstdc_path: Option<String> = None;

	const DEFAULT_LIB_PATH: &str = "/usr/lib64/libstdc++.so.6";
	const LDCONFIG_PATH: &str = "/sbin/ldconfig";

	if fs::metadata(DEFAULT_LIB_PATH).await.is_ok() {
		libstdc_path = Some(DEFAULT_LIB_PATH.to_owned());
	} else if fs::metadata(LDCONFIG_PATH).await.is_ok() {
		libstdc_path = capture_command(LDCONFIG_PATH, ["-p"])
			.await
			.ok()
			.and_then(|o| extract_libstd_from_ldconfig(&o.stdout));
	}

	match libstdc_path {
		Some(path) => match fs::read(&path).await {
			Ok(contents) => check_for_sufficient_glibcxx_versions(contents),
			Err(e) => Err(format!(
				"validate GLIBCXX version for GNU environments, but could not: {}",
				e
			)),
		},
		None => Err("find libstdc++.so or ldconfig for GNU environments".to_owned()),
	}
}

#[allow(dead_code)]
fn check_for_sufficient_glibcxx_versions(contents: Vec<u8>) -> Result<(), String> {
	let all_versions: Vec<SimpleSemver> = LIBSTD_CXX_VERSION_RE
		.captures_iter(&contents)
		.map(|m| SimpleSemver {
			major: m.get(1).map_or(0, |s| u32_from_bytes(s.as_bytes())),
			minor: m.get(2).map_or(0, |s| u32_from_bytes(s.as_bytes())),
			patch: m.get(3).map_or(0, |s| u32_from_bytes(s.as_bytes())),
		})
		.collect();

	if !all_versions.iter().any(|v| MIN_CXX_VERSION.gte(v)) {
		return Err(format!(
			"find GLIBCXX >= 3.4.18 (but found {} instead) for GNU environments",
			all_versions
				.iter()
				.map(String::from)
				.collect::<Vec<String>>()
				.join(", ")
		));
	}

	Ok(())
}

fn extract_ldd_version(output: &[u8]) -> Option<SimpleSemver> {
	LDD_VERSION_RE.captures(output).map(|m| SimpleSemver {
		major: m.get(1).map_or(0, |s| u32_from_bytes(s.as_bytes())),
		minor: m.get(2).map_or(0, |s| u32_from_bytes(s.as_bytes())),
		patch: 0,
	})
}

fn extract_libstd_from_ldconfig(output: &[u8]) -> Option<String> {
	String::from_utf8_lossy(output)
		.lines()
		.find_map(|l| LDCONFIG_STDC_RE.captures(l))
		.and_then(|cap| cap.get(1))
		.map(|cap| cap.as_str().to_owned())
}

fn u32_from_bytes(b: &[u8]) -> u32 {
	String::from_utf8_lossy(b).parse::<u32>().unwrap_or(0)
}

#[derive(Debug, PartialEq)]
struct SimpleSemver {
	major: u32,
	minor: u32,
	patch: u32,
}

impl From<&SimpleSemver> for String {
	fn from(s: &SimpleSemver) -> Self {
		format!("v{}.{}.{}", s.major, s.minor, s.patch)
	}
}

impl std::fmt::Display for SimpleSemver {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		write!(f, "{}", String::from(self))
	}
}

#[allow(dead_code)]
impl SimpleSemver {
	fn new(major: u32, minor: u32, patch: u32) -> SimpleSemver {
		SimpleSemver {
			major,
			minor,
			patch,
		}
	}

	fn gte(&self, other: &SimpleSemver) -> bool {
		match self.major.cmp(&other.major) {
			Ordering::Greater => true,
			Ordering::Less => false,
			Ordering::Equal => match self.minor.cmp(&other.minor) {
				Ordering::Greater => true,
				Ordering::Less => false,
				Ordering::Equal => self.patch >= other.patch,
			},
		}
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn test_extract_libstd_from_ldconfig() {
		let actual = "
            libstoken.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstoken.so.1
            libstemmer.so.0d (libc6,x86-64) => /lib/x86_64-linux-gnu/libstemmer.so.0d
            libstdc++.so.6 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstdc++.so.6
            libstartup-notification-1.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstartup-notification-1.so.0
            libssl3.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libssl3.so
        ".to_owned().into_bytes();

		assert_eq!(
			extract_libstd_from_ldconfig(&actual),
			Some("/lib/x86_64-linux-gnu/libstdc++.so.6".to_owned()),
		);

		assert_eq!(
			extract_libstd_from_ldconfig(&"nothing here!".to_owned().into_bytes()),
			None,
		);
	}

	#[test]
	fn test_gte() {
		assert!(SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 2, 3)));
		assert!(SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(0, 10, 10)));
		assert!(SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 1, 10)));

		assert!(!SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 2, 10)));
		assert!(!SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 3, 1)));
		assert!(!SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(2, 2, 1)));
	}

	#[test]
	fn check_for_sufficient_glibcxx_versions() {
		let actual = "ldd (Ubuntu GLIBC 2.31-0ubuntu9.7) 2.31
        Copyright (C) 2020 Free Software Foundation, Inc.
        This is free software; see the source for copying conditions.  There is NO
        warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
        Written by Roland McGrath and Ulrich Drepper."
			.to_owned()
			.into_bytes();

		assert_eq!(
			extract_ldd_version(&actual),
			Some(SimpleSemver::new(2, 31, 0)),
		);
	}
}
<commit_msg>cli: skip glibc check for nixos<commit_after>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
use std::cmp::Ordering;

use super::command::capture_command;
use crate::update_service::Platform;
use crate::util::errors::SetupError;
use lazy_static::lazy_static;
use regex::bytes::Regex as BinRegex;
use regex::Regex;
use tokio::fs;

use super::errors::AnyError;

lazy_static! {
	static ref LDCONFIG_STDC_RE: Regex = Regex::new(r"libstdc\+\+.* => (.+)").unwrap();
	static ref LDD_VERSION_RE: BinRegex = BinRegex::new(r"^ldd.*(.+)\.(.+)\s").unwrap();
	static ref LIBSTD_CXX_VERSION_RE: BinRegex =
		BinRegex::new(r"GLIBCXX_([0-9]+)\.([0-9]+)(?:\.([0-9]+))?").unwrap();
	static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 18);
	static ref MIN_LDD_VERSION: SimpleSemver = SimpleSemver::new(2, 17, 0);
}

const NIXOS_TEST_PATH: &str = "/etc/NIXOS";

pub struct PreReqChecker {}

impl Default for PreReqChecker {
	fn default() -> Self {
		Self::new()
	}
}

impl PreReqChecker {
	pub fn new() -> PreReqChecker {
		PreReqChecker {}
	}

	#[cfg(not(target_os = "linux"))]
	pub async fn verify(&self) -> Result<Platform, AnyError> {
		Platform::env_default().ok_or_else(|| {
			SetupError("VS Code is not supported on this platform".to_owned()).into()
		})
	}

	#[cfg(target_os = "linux")]
	pub async fn verify(&self) -> Result<Platform, AnyError> {
		let (is_nixos, gnu_a, gnu_b, or_musl) = tokio::join!(
			check_is_nixos(),
			check_glibc_version(),
			check_glibcxx_version(),
			check_musl_interpreter()
		);

		if (gnu_a.is_ok() && gnu_b.is_ok()) || is_nixos {
			return Ok(if cfg!(target_arch = "x86_64") {
				Platform::LinuxX64
			} else if cfg!(target_arch = "armhf") {
				Platform::LinuxARM32
			} else {
				Platform::LinuxARM64
			});
		}

		if or_musl.is_ok() {
			return Ok(if cfg!(target_arch = "x86_64") {
				Platform::LinuxAlpineX64
			} else {
				Platform::LinuxAlpineARM64
			});
		}

		let mut errors: Vec<String> = vec![];
		if let Err(e) = gnu_a {
			errors.push(e);
		} else if let Err(e) = gnu_b {
			errors.push(e);
		}

		if let Err(e) = or_musl {
			errors.push(e);
		}

		let bullets = errors
			.iter()
			.map(|e| format!("  - {}", e))
			.collect::<Vec<String>>()
			.join("\n");

		Err(AnyError::from(SetupError(format!(
			"This machine not meet VS Code Server's prerequisites, expected either...\n{}",
			bullets,
		))))
	}
}

#[allow(dead_code)]
async fn check_musl_interpreter() -> Result<(), String> {
	const MUSL_PATH: &str = if cfg!(target_platform = "aarch64") {
		"/lib/ld-musl-aarch64.so.1"
	} else {
		"/lib/ld-musl-x86_64.so.1"
	};

	if fs::metadata(MUSL_PATH).await.is_err() {
		return Err(format!(
			"find {}, which is required to run the VS Code Server in musl environments",
			MUSL_PATH
		));
	}

	Ok(())
}

#[allow(dead_code)]
async fn check_glibc_version() -> Result<(), String> {
	let ldd_version = capture_command("ldd", ["--version"])
		.await
		.ok()
		.and_then(|o| extract_ldd_version(&o.stdout));

	if let Some(v) = ldd_version {
		return if v.gte(&MIN_LDD_VERSION) {
			Ok(())
		} else {
			Err(format!(
				"find GLIBC >= 2.17 (but found {} instead) for GNU environments",
				v
			))
		};
	}

	Ok(())
}

/// Check for nixos to avoid mandating glibc versions. See:
/// https://github.com/microsoft/vscode-remote-release/issues/7129
#[allow(dead_code)]
async fn check_is_nixos() -> bool {
	fs::metadata(NIXOS_TEST_PATH).await.is_ok()
}

#[allow(dead_code)]
async fn check_glibcxx_version() -> Result<(), String> {
	let mut libstdc_path: Option<String> = None;

	const DEFAULT_LIB_PATH: &str = "/usr/lib64/libstdc++.so.6";
	const LDCONFIG_PATH: &str = "/sbin/ldconfig";

	if fs::metadata(DEFAULT_LIB_PATH).await.is_ok() {
		libstdc_path = Some(DEFAULT_LIB_PATH.to_owned());
	} else if fs::metadata(LDCONFIG_PATH).await.is_ok() {
		libstdc_path = capture_command(LDCONFIG_PATH, ["-p"])
			.await
			.ok()
			.and_then(|o| extract_libstd_from_ldconfig(&o.stdout));
	}

	match libstdc_path {
		Some(path) => match fs::read(&path).await {
			Ok(contents) => check_for_sufficient_glibcxx_versions(contents),
			Err(e) => Err(format!(
				"validate GLIBCXX version for GNU environments, but could not: {}",
				e
			)),
		},
		None => Err("find libstdc++.so or ldconfig for GNU environments".to_owned()),
	}
}

#[allow(dead_code)]
fn check_for_sufficient_glibcxx_versions(contents: Vec<u8>) -> Result<(), String> {
	let all_versions: Vec<SimpleSemver> = LIBSTD_CXX_VERSION_RE
		.captures_iter(&contents)
		.map(|m| SimpleSemver {
			major: m.get(1).map_or(0, |s| u32_from_bytes(s.as_bytes())),
			minor: m.get(2).map_or(0, |s| u32_from_bytes(s.as_bytes())),
			patch: m.get(3).map_or(0, |s| u32_from_bytes(s.as_bytes())),
		})
		.collect();

	if !all_versions.iter().any(|v| MIN_CXX_VERSION.gte(v)) {
		return Err(format!(
			"find GLIBCXX >= 3.4.18 (but found {} instead) for GNU environments",
			all_versions
				.iter()
				.map(String::from)
				.collect::<Vec<String>>()
				.join(", ")
		));
	}

	Ok(())
}

fn extract_ldd_version(output: &[u8]) -> Option<SimpleSemver> {
	LDD_VERSION_RE.captures(output).map(|m| SimpleSemver {
		major: m.get(1).map_or(0, |s| u32_from_bytes(s.as_bytes())),
		minor: m.get(2).map_or(0, |s| u32_from_bytes(s.as_bytes())),
		patch: 0,
	})
}

fn extract_libstd_from_ldconfig(output: &[u8]) -> Option<String> {
	String::from_utf8_lossy(output)
		.lines()
		.find_map(|l| LDCONFIG_STDC_RE.captures(l))
		.and_then(|cap| cap.get(1))
		.map(|cap| cap.as_str().to_owned())
}

fn u32_from_bytes(b: &[u8]) -> u32 {
	String::from_utf8_lossy(b).parse::<u32>().unwrap_or(0)
}

#[derive(Debug, PartialEq)]
struct SimpleSemver {
	major: u32,
	minor: u32,
	patch: u32,
}

impl From<&SimpleSemver> for String {
	fn from(s: &SimpleSemver) -> Self {
		format!("v{}.{}.{}", s.major, s.minor, s.patch)
	}
}

impl std::fmt::Display for SimpleSemver {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		write!(f, "{}", String::from(self))
	}
}

#[allow(dead_code)]
impl SimpleSemver {
	fn new(major: u32, minor: u32, patch: u32) -> SimpleSemver {
		SimpleSemver {
			major,
			minor,
			patch,
		}
	}

	fn gte(&self, other: &SimpleSemver) -> bool {
		match self.major.cmp(&other.major) {
			Ordering::Greater => true,
			Ordering::Less => false,
			Ordering::Equal => match self.minor.cmp(&other.minor) {
				Ordering::Greater => true,
				Ordering::Less => false,
				Ordering::Equal => self.patch >= other.patch,
			},
		}
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn test_extract_libstd_from_ldconfig() {
		let actual = "
            libstoken.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstoken.so.1
            libstemmer.so.0d (libc6,x86-64) => /lib/x86_64-linux-gnu/libstemmer.so.0d
            libstdc++.so.6 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstdc++.so.6
            libstartup-notification-1.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstartup-notification-1.so.0
            libssl3.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libssl3.so
        ".to_owned().into_bytes();

		assert_eq!(
			extract_libstd_from_ldconfig(&actual),
			Some("/lib/x86_64-linux-gnu/libstdc++.so.6".to_owned()),
		);

		assert_eq!(
			extract_libstd_from_ldconfig(&"nothing here!".to_owned().into_bytes()),
			None,
		);
	}

	#[test]
	fn test_gte() {
		assert!(SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 2, 3)));
		assert!(SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(0, 10, 10)));
		assert!(SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 1, 10)));

		assert!(!SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 2, 10)));
		assert!(!SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 3, 1)));
		assert!(!SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(2, 2, 1)));
	}

	#[test]
	fn check_for_sufficient_glibcxx_versions() {
		let actual = "ldd (Ubuntu GLIBC 2.31-0ubuntu9.7) 2.31
        Copyright (C) 2020 Free Software Foundation, Inc.
        This is free software; see the source for copying conditions.  There is NO
        warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
        Written by Roland McGrath and Ulrich Drepper."
			.to_owned()
			.into_bytes();

		assert_eq!(
			extract_ldd_version(&actual),
			Some(SimpleSemver::new(2, 31, 0)),
		);
	}
}
<|endoftext|>


<commit_before>use anyhow::*;
use diesel;
use diesel::prelude::*;
#[cfg(feature = "profile-index")]
use flame;
use log::{error, info};
use rayon::prelude::*;
use regex::Regex;
use std::fs;
use std::path::Path;
use std::time;
use std::sync::mpsc::*;

use crate::config::MiscSettings;
use crate::db::{directories, misc_settings, songs, DB};
use crate::index::metadata;
use crate::vfs::VFSSource;

const INDEX_BUILDING_INSERT_BUFFER_SIZE: usize = 1000; // Insertions in each transaction
const INDEX_BUILDING_CLEAN_BUFFER_SIZE: usize = 500; // Insertions in each transaction

pub fn update(db: &DB) -> Result<()> {
	let start = time::Instant::now();
	info!("Beginning library index update");
	clean(db)?;
	populate(db)?;
	info!(
		"Library index update took {} seconds",
		start.elapsed().as_millis() as f32 / 1000.0
	);
	#[cfg(feature = "profile-index")]
	flame::dump_html(&mut fs::File::create("index-flame-graph.html").unwrap()).unwrap();
	Ok(())
}

#[derive(Debug, Insertable)]
#[table_name = "songs"]
struct NewSong {
	path: String,
	parent: String,
	track_number: Option<i32>,
	disc_number: Option<i32>,
	title: Option<String>,
	artist: Option<String>,
	album_artist: Option<String>,
	year: Option<i32>,
	album: Option<String>,
	artwork: Option<String>,
	duration: Option<i32>,
}

#[derive(Debug, Insertable)]
#[table_name = "directories"]
struct NewDirectory {
	path: String,
	parent: Option<String>,
	artist: Option<String>,
	year: Option<i32>,
	album: Option<String>,
	artwork: Option<String>,
	date_added: i32,
}

struct IndexUpdater {
	directory_sender: Sender<NewDirectory>,
	song_sender: Sender<NewSong>,
	album_art_pattern: Regex,
}

impl IndexUpdater {
	#[cfg_attr(feature = "profile-index", flame)]
	fn new(album_art_pattern: Regex, directory_sender: Sender<NewDirectory>, song_sender: Sender<NewSong>) -> Result<IndexUpdater> {
		Ok(IndexUpdater {
			directory_sender,
			song_sender,
			album_art_pattern,
		})
	}

	#[cfg_attr(feature = "profile-index", flame)]
	fn push_song(&mut self, song: NewSong) -> Result<()> {
		self.song_sender.send(song).map_err(Error::new)
	}

	#[cfg_attr(feature = "profile-index", flame)]
	fn push_directory(&mut self, directory: NewDirectory) -> Result<()> {
		self.directory_sender.send(directory).map_err(Error::new)
	}

	fn get_artwork(&self, dir: &Path) -> Result<Option<String>> {
		for file in fs::read_dir(dir)? {
			let file = file?;
			if let Some(name_string) = file.file_name().to_str() {
				if self.album_art_pattern.is_match(name_string) {
					return Ok(file.path().to_str().map(|p| p.to_owned()));
				}
			}
		}
		Ok(None)
	}

	#[cfg_attr(feature = "profile-index", flame)]
	fn populate_directory(&mut self, parent: Option<&Path>, path: &Path) -> Result<()> {
		// Find artwork
		let artwork = self.get_artwork(path).unwrap_or(None);

		// Extract path and parent path
		let parent_string = parent.and_then(|p| p.to_str()).map(|s| s.to_owned());
		let path_string = path.to_str().ok_or(anyhow!("Invalid directory path"))?;

		// Find date added
		let metadata = fs::metadata(path_string)?;
		let created = metadata
			.created()
			.or_else(|_| metadata.modified())?
			.duration_since(time::UNIX_EPOCH)?
			.as_secs() as i32;

		let mut directory_album = None;
		let mut directory_year = None;
		let mut directory_artist = None;
		let mut inconsistent_directory_album = false;
		let mut inconsistent_directory_year = false;
		let mut inconsistent_directory_artist = false;

		// Sub directories
		let mut sub_directories = Vec::new();

		// Insert content
		for file in fs::read_dir(path)? {
			#[cfg(feature = "profile-index")]
			let _guard = flame::start_guard("directory-entry");
			let file_path = match file {
				Ok(ref f) => f.path(),
				_ => {
					error!("File read error within {}", path_string);
					break;
				}
			};

			if file_path.is_dir() {
				sub_directories.push(file_path.to_path_buf());
				continue;
			}

			if let Some(file_path_string) = file_path.to_str() {
				if let Some(tags) = metadata::read(file_path.as_path()) {
					if tags.year.is_some() {
						inconsistent_directory_year |=
							directory_year.is_some() && directory_year != tags.year;
						directory_year = tags.year;
					}

					if tags.album.is_some() {
						inconsistent_directory_album |=
							directory_album.is_some() && directory_album != tags.album;
						directory_album = tags.album.as_ref().cloned();
					}

					if tags.album_artist.is_some() {
						inconsistent_directory_artist |=
							directory_artist.is_some() && directory_artist != tags.album_artist;
						directory_artist = tags.album_artist.as_ref().cloned();
					} else if tags.artist.is_some() {
						inconsistent_directory_artist |=
							directory_artist.is_some() && directory_artist != tags.artist;
						directory_artist = tags.artist.as_ref().cloned();
					}

					let song = NewSong {
						path: file_path_string.to_owned(),
						parent: path_string.to_owned(),
						disc_number: tags.disc_number.map(|n| n as i32),
						track_number: tags.track_number.map(|n| n as i32),
						title: tags.title,
						duration: tags.duration.map(|n| n as i32),
						artist: tags.artist,
						album_artist: tags.album_artist,
						album: tags.album,
						year: tags.year,
						artwork: artwork.as_ref().cloned(),
					};

					self.push_song(song)?;
				}
			}
		}

		// Insert directory
		if inconsistent_directory_year {
			directory_year = None;
		}
		if inconsistent_directory_album {
			directory_album = None;
		}
		if inconsistent_directory_artist {
			directory_artist = None;
		}

		let directory = NewDirectory {
			path: path_string.to_owned(),
			parent: parent_string,
			artwork,
			album: directory_album,
			artist: directory_artist,
			year: directory_year,
			date_added: created,
		};
		self.push_directory(directory)?;

		// Populate subdirectories
		for sub_directory in sub_directories {
			self.populate_directory(Some(path), &sub_directory)?;
		}

		Ok(())
	}
}

#[cfg_attr(feature = "profile-index", flame)]
pub fn clean(db: &DB) -> Result<()> {
	let vfs = db.get_vfs()?;

	{
		let all_songs: Vec<String>;
		{
			let connection = db.connect()?;
			all_songs = songs::table.select(songs::path).load(&connection)?;
		}

		let missing_songs = all_songs
			.par_iter()
			.filter(|ref song_path| {
				let path = Path::new(&song_path);
				!path.exists() || vfs.real_to_virtual(path).is_err()
			})
			.collect::<Vec<_>>();

		{
			let connection = db.connect()?;
			for chunk in missing_songs[..].chunks(INDEX_BUILDING_CLEAN_BUFFER_SIZE) {
				diesel::delete(songs::table.filter(songs::path.eq_any(chunk)))
					.execute(&connection)?;
			}
		}
	}

	{
		let all_directories: Vec<String>;
		{
			let connection = db.connect()?;
			all_directories = directories::table
				.select(directories::path)
				.load(&connection)?;
		}

		let missing_directories = all_directories
			.par_iter()
			.filter(|ref directory_path| {
				let path = Path::new(&directory_path);
				!path.exists() || vfs.real_to_virtual(path).is_err()
			})
			.collect::<Vec<_>>();

		{
			let connection = db.connect()?;
			for chunk in missing_directories[..].chunks(INDEX_BUILDING_CLEAN_BUFFER_SIZE) {
				diesel::delete(directories::table.filter(directories::path.eq_any(chunk)))
					.execute(&connection)?;
			}
		}
	}

	Ok(())
}

#[cfg_attr(feature = "profile-index", flame)]
pub fn populate(db: &DB) -> Result<()> {
	let vfs = db.get_vfs()?;
	let mount_points = vfs.get_mount_points();

	let album_art_pattern;
	{
		let connection = db.connect()?;
		let settings: MiscSettings = misc_settings::table.get_result(&connection)?;
		album_art_pattern = Regex::new(&settings.index_album_art_pattern)?;
	}

	let (directory_sender, directory_receiver) = channel();
	let directories_db = db.clone();
	std::thread::spawn(move || {
		insert_directories(directory_receiver, directories_db);
	});

	let (song_sender, song_receiver) = channel();
	let songs_db = db.clone();
	std::thread::spawn(move || {
		insert_songs(song_receiver, songs_db);
	});

	let mut updater = IndexUpdater::new(album_art_pattern, directory_sender, song_sender)?;
	for target in mount_points.values() {
		updater.populate_directory(None, target.as_path())?;
	}

	Ok(())
}

fn insert_songs(receiver: Receiver<NewSong>, db: DB) {
	let mut new_entries = Vec::new();
	new_entries.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);

	loop {
		match receiver.recv() {
			Ok(s) => {
				new_entries.push(s);
				if new_entries.len() >= INDEX_BUILDING_INSERT_BUFFER_SIZE {
					if db.connect()
					.and_then(|connection|{
						diesel::insert_into(songs::table)
							.values(&new_entries)
							.execute(&*connection) // TODO https://github.com/diesel-rs/diesel/issues/1822
							.map_err(Error::new)
					})
					.is_err() {
						error!("Could not insert new songs in database");
					}
					new_entries.clear();
				}
			},
			Err(_) => break,
		}
	}

	if new_entries.len() > 0 {
		if db.connect()
		.and_then(|connection|{
			diesel::insert_into(songs::table)
				.values(&new_entries)
				.execute(&*connection) // TODO https://github.com/diesel-rs/diesel/issues/1822
				.map_err(Error::new)
		})
		.is_err() {
			error!("Could not insert new songs in database");
		}
	}
}

fn insert_directories(receiver: Receiver<NewDirectory>, db: DB) {
	let mut new_entries = Vec::new();
	new_entries.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);

	loop {
		match receiver.recv() {
			Ok(s) => {
				new_entries.push(s);
				if new_entries.len() >= INDEX_BUILDING_INSERT_BUFFER_SIZE {
					if db.connect()
					.and_then(|connection|{
						diesel::insert_into(directories::table)
							.values(&new_entries)
							.execute(&*connection) // TODO https://github.com/diesel-rs/diesel/issues/1822
							.map_err(Error::new)
					})
					.is_err() {
						error!("Could not insert new directories in database");
					}
					new_entries.clear();
				}
			},
			Err(_) => break,
		}
	}

	if new_entries.len() > 0 {
		if db.connect()
		.and_then(|connection|{
			diesel::insert_into(directories::table)
				.values(&new_entries)
				.execute(&*connection) // TODO https://github.com/diesel-rs/diesel/issues/1822
				.map_err(Error::new)
		})
		.is_err() {
			error!("Could not insert new directories in database");
		}
	}
}
<commit_msg>Don't return from populate() while still writing to database<commit_after>use anyhow::*;
use diesel;
use diesel::prelude::*;
#[cfg(feature = "profile-index")]
use flame;
use log::{error, info};
use rayon::prelude::*;
use regex::Regex;
use std::fs;
use std::path::Path;
use std::time;
use std::sync::mpsc::*;

use crate::config::MiscSettings;
use crate::db::{directories, misc_settings, songs, DB};
use crate::index::metadata;
use crate::vfs::VFSSource;

const INDEX_BUILDING_INSERT_BUFFER_SIZE: usize = 1000; // Insertions in each transaction
const INDEX_BUILDING_CLEAN_BUFFER_SIZE: usize = 500; // Insertions in each transaction

pub fn update(db: &DB) -> Result<()> {
	let start = time::Instant::now();
	info!("Beginning library index update");
	clean(db)?;
	populate(db)?;
	info!(
		"Library index update took {} seconds",
		start.elapsed().as_millis() as f32 / 1000.0
	);
	#[cfg(feature = "profile-index")]
	flame::dump_html(&mut fs::File::create("index-flame-graph.html").unwrap()).unwrap();
	Ok(())
}

#[derive(Debug, Insertable)]
#[table_name = "songs"]
struct NewSong {
	path: String,
	parent: String,
	track_number: Option<i32>,
	disc_number: Option<i32>,
	title: Option<String>,
	artist: Option<String>,
	album_artist: Option<String>,
	year: Option<i32>,
	album: Option<String>,
	artwork: Option<String>,
	duration: Option<i32>,
}

#[derive(Debug, Insertable)]
#[table_name = "directories"]
struct NewDirectory {
	path: String,
	parent: Option<String>,
	artist: Option<String>,
	year: Option<i32>,
	album: Option<String>,
	artwork: Option<String>,
	date_added: i32,
}

struct IndexUpdater {
	directory_sender: Sender<NewDirectory>,
	song_sender: Sender<NewSong>,
	album_art_pattern: Regex,
}

impl IndexUpdater {
	#[cfg_attr(feature = "profile-index", flame)]
	fn new(album_art_pattern: Regex, directory_sender: Sender<NewDirectory>, song_sender: Sender<NewSong>) -> Result<IndexUpdater> {
		Ok(IndexUpdater {
			directory_sender,
			song_sender,
			album_art_pattern,
		})
	}

	#[cfg_attr(feature = "profile-index", flame)]
	fn push_song(&mut self, song: NewSong) -> Result<()> {
		self.song_sender.send(song).map_err(Error::new)
	}

	#[cfg_attr(feature = "profile-index", flame)]
	fn push_directory(&mut self, directory: NewDirectory) -> Result<()> {
		self.directory_sender.send(directory).map_err(Error::new)
	}

	fn get_artwork(&self, dir: &Path) -> Result<Option<String>> {
		for file in fs::read_dir(dir)? {
			let file = file?;
			if let Some(name_string) = file.file_name().to_str() {
				if self.album_art_pattern.is_match(name_string) {
					return Ok(file.path().to_str().map(|p| p.to_owned()));
				}
			}
		}
		Ok(None)
	}

	#[cfg_attr(feature = "profile-index", flame)]
	fn populate_directory(&mut self, parent: Option<&Path>, path: &Path) -> Result<()> {
		// Find artwork
		let artwork = self.get_artwork(path).unwrap_or(None);

		// Extract path and parent path
		let parent_string = parent.and_then(|p| p.to_str()).map(|s| s.to_owned());
		let path_string = path.to_str().ok_or(anyhow!("Invalid directory path"))?;

		// Find date added
		let metadata = fs::metadata(path_string)?;
		let created = metadata
			.created()
			.or_else(|_| metadata.modified())?
			.duration_since(time::UNIX_EPOCH)?
			.as_secs() as i32;

		let mut directory_album = None;
		let mut directory_year = None;
		let mut directory_artist = None;
		let mut inconsistent_directory_album = false;
		let mut inconsistent_directory_year = false;
		let mut inconsistent_directory_artist = false;

		// Sub directories
		let mut sub_directories = Vec::new();

		// Insert content
		for file in fs::read_dir(path)? {
			#[cfg(feature = "profile-index")]
			let _guard = flame::start_guard("directory-entry");
			let file_path = match file {
				Ok(ref f) => f.path(),
				_ => {
					error!("File read error within {}", path_string);
					break;
				}
			};

			if file_path.is_dir() {
				sub_directories.push(file_path.to_path_buf());
				continue;
			}

			if let Some(file_path_string) = file_path.to_str() {
				if let Some(tags) = metadata::read(file_path.as_path()) {
					if tags.year.is_some() {
						inconsistent_directory_year |=
							directory_year.is_some() && directory_year != tags.year;
						directory_year = tags.year;
					}

					if tags.album.is_some() {
						inconsistent_directory_album |=
							directory_album.is_some() && directory_album != tags.album;
						directory_album = tags.album.as_ref().cloned();
					}

					if tags.album_artist.is_some() {
						inconsistent_directory_artist |=
							directory_artist.is_some() && directory_artist != tags.album_artist;
						directory_artist = tags.album_artist.as_ref().cloned();
					} else if tags.artist.is_some() {
						inconsistent_directory_artist |=
							directory_artist.is_some() && directory_artist != tags.artist;
						directory_artist = tags.artist.as_ref().cloned();
					}

					let song = NewSong {
						path: file_path_string.to_owned(),
						parent: path_string.to_owned(),
						disc_number: tags.disc_number.map(|n| n as i32),
						track_number: tags.track_number.map(|n| n as i32),
						title: tags.title,
						duration: tags.duration.map(|n| n as i32),
						artist: tags.artist,
						album_artist: tags.album_artist,
						album: tags.album,
						year: tags.year,
						artwork: artwork.as_ref().cloned(),
					};

					self.push_song(song)?;
				}
			}
		}

		// Insert directory
		if inconsistent_directory_year {
			directory_year = None;
		}
		if inconsistent_directory_album {
			directory_album = None;
		}
		if inconsistent_directory_artist {
			directory_artist = None;
		}

		let directory = NewDirectory {
			path: path_string.to_owned(),
			parent: parent_string,
			artwork,
			album: directory_album,
			artist: directory_artist,
			year: directory_year,
			date_added: created,
		};
		self.push_directory(directory)?;

		// Populate subdirectories
		for sub_directory in sub_directories {
			self.populate_directory(Some(path), &sub_directory)?;
		}

		Ok(())
	}
}

#[cfg_attr(feature = "profile-index", flame)]
pub fn clean(db: &DB) -> Result<()> {
	let vfs = db.get_vfs()?;

	{
		let all_songs: Vec<String>;
		{
			let connection = db.connect()?;
			all_songs = songs::table.select(songs::path).load(&connection)?;
		}

		let missing_songs = all_songs
			.par_iter()
			.filter(|ref song_path| {
				let path = Path::new(&song_path);
				!path.exists() || vfs.real_to_virtual(path).is_err()
			})
			.collect::<Vec<_>>();

		{
			let connection = db.connect()?;
			for chunk in missing_songs[..].chunks(INDEX_BUILDING_CLEAN_BUFFER_SIZE) {
				diesel::delete(songs::table.filter(songs::path.eq_any(chunk)))
					.execute(&connection)?;
			}
		}
	}

	{
		let all_directories: Vec<String>;
		{
			let connection = db.connect()?;
			all_directories = directories::table
				.select(directories::path)
				.load(&connection)?;
		}

		let missing_directories = all_directories
			.par_iter()
			.filter(|ref directory_path| {
				let path = Path::new(&directory_path);
				!path.exists() || vfs.real_to_virtual(path).is_err()
			})
			.collect::<Vec<_>>();

		{
			let connection = db.connect()?;
			for chunk in missing_directories[..].chunks(INDEX_BUILDING_CLEAN_BUFFER_SIZE) {
				diesel::delete(directories::table.filter(directories::path.eq_any(chunk)))
					.execute(&connection)?;
			}
		}
	}

	Ok(())
}

#[cfg_attr(feature = "profile-index", flame)]
pub fn populate(db: &DB) -> Result<()> {
	let vfs = db.get_vfs()?;
	let mount_points = vfs.get_mount_points();

	let album_art_pattern  = {
		let connection = db.connect()?;
		let settings: MiscSettings = misc_settings::table.get_result(&connection)?;
		Regex::new(&settings.index_album_art_pattern)?
	};

	let (directory_sender, directory_receiver) = channel();
	let (song_sender, song_receiver) = channel();

	let songs_db = db.clone();
	let directories_db = db.clone();

	let directories_thread = std::thread::spawn(move || {
		insert_directories(directory_receiver, directories_db);
	});

	let songs_thread = std::thread::spawn(move || {
		insert_songs(song_receiver, songs_db);
	});

	{
		let mut updater = IndexUpdater::new(album_art_pattern, directory_sender, song_sender)?;
		for target in mount_points.values() {
			updater.populate_directory(None, target.as_path())?;
		}
	}

	match directories_thread.join() {
		Err(e) => error!("Error while waiting for directory insertions to complete: {:?}", e),
		_ => (),
	}

	match songs_thread.join() {
		Err(e) => error!("Error while waiting for song insertions to complete: {:?}", e),
		_ => (),
	}

	Ok(())
}

fn insert_songs(receiver: Receiver<NewSong>, db: DB) {
	let mut new_entries = Vec::new();
	new_entries.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);

	loop {
		match receiver.recv() {
			Ok(s) => {
				new_entries.push(s);
				if new_entries.len() >= INDEX_BUILDING_INSERT_BUFFER_SIZE {
					if db.connect()
					.and_then(|connection|{
						diesel::insert_into(songs::table)
							.values(&new_entries)
							.execute(&*connection) // TODO https://github.com/diesel-rs/diesel/issues/1822
							.map_err(Error::new)
					})
					.is_err() {
						error!("Could not insert new songs in database");
					}
					new_entries.clear();
				}
			},
			Err(_) => break,
		}
	}

	if new_entries.len() > 0 {
		if db.connect()
		.and_then(|connection|{
			diesel::insert_into(songs::table)
				.values(&new_entries)
				.execute(&*connection) // TODO https://github.com/diesel-rs/diesel/issues/1822
				.map_err(Error::new)
		})
		.is_err() {
			error!("Could not insert new songs in database");
		}
	}
}

fn insert_directories(receiver: Receiver<NewDirectory>, db: DB) {
	let mut new_entries = Vec::new();
	new_entries.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);

	loop {
		match receiver.recv() {
			Ok(s) => {
				new_entries.push(s);
				if new_entries.len() >= INDEX_BUILDING_INSERT_BUFFER_SIZE {
					if db.connect()
					.and_then(|connection|{
						diesel::insert_into(directories::table)
							.values(&new_entries)
							.execute(&*connection) // TODO https://github.com/diesel-rs/diesel/issues/1822
							.map_err(Error::new)
					})
					.is_err() {
						error!("Could not insert new directories in database");
					}
					new_entries.clear();
				}
			},
			Err(_) => break,
		}
	}

	if new_entries.len() > 0 {
		if db.connect()
		.and_then(|connection|{
			diesel::insert_into(directories::table)
				.values(&new_entries)
				.execute(&*connection) // TODO https://github.com/diesel-rs/diesel/issues/1822
				.map_err(Error::new)
		})
		.is_err() {
			error!("Could not insert new directories in database");
		}
	}
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! This module contains traits in script used generically in the rest of Servo.
//! The traits are here instead of in script so that these modules won't have
//! to depend on script.

#![deny(unsafe_code)]
#![feature(associated_type_defaults)]

extern crate app_units;
extern crate atomic_refcell;
extern crate canvas_traits;
extern crate cssparser;
extern crate euclid;
extern crate gfx_traits;
#[macro_use]
extern crate html5ever;
extern crate ipc_channel;
extern crate libc;
#[macro_use]
extern crate log;
extern crate malloc_size_of;
#[macro_use]
extern crate malloc_size_of_derive;
extern crate metrics;
extern crate msg;
extern crate net_traits;
extern crate profile_traits;
extern crate range;
extern crate script_traits;
extern crate selectors;
extern crate servo_arc;
extern crate servo_atoms;
extern crate servo_channel;
extern crate servo_url;
extern crate style;
extern crate webrender_api;

pub mod message;
pub mod reporter;
pub mod rpc;
pub mod wrapper_traits;

use atomic_refcell::AtomicRefCell;
use canvas_traits::canvas::{CanvasMsg, CanvasId};
use ipc_channel::ipc::IpcSender;
use libc::c_void;
use net_traits::image_cache::PendingImageId;
use script_traits::UntrustedNodeAddress;
use servo_url::ServoUrl;
use std::ptr::NonNull;
use std::sync::atomic::AtomicIsize;
use style::data::ElementData;

#[repr(C)]
pub struct StyleData {
    /// Data that the style system associates with a node. When the
    /// style system is being used standalone, this is all that hangs
    /// off the node. This must be first to permit the various
    /// transmutations between ElementData and PersistentLayoutData.
    pub element_data: AtomicRefCell<ElementData>,

    /// Information needed during parallel traversals.
    pub parallel: DomParallelInfo,
}

impl StyleData {
    pub fn new() -> Self {
        Self {
            element_data: AtomicRefCell::new(ElementData::default()),
            parallel: DomParallelInfo::new(),
        }
    }
}

#[derive(Clone, Copy, MallocSizeOf)]
pub struct OpaqueStyleAndLayoutData {
    // NB: We really store a `StyleAndLayoutData` here, so be careful!
    #[ignore_malloc_size_of = "TODO(#6910) Box value that should be counted but \
                               the type lives in layout"]
    pub ptr: NonNull<StyleData>,
}

#[allow(unsafe_code)]
unsafe impl Send for OpaqueStyleAndLayoutData {}

/// Information that we need stored in each DOM node.
#[derive(MallocSizeOf)]
pub struct DomParallelInfo {
    /// The number of children remaining to process during bottom-up traversal.
    pub children_to_process: AtomicIsize,
}

impl DomParallelInfo {
    pub fn new() -> DomParallelInfo {
        DomParallelInfo {
            children_to_process: AtomicIsize::new(0),
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LayoutNodeType {
    Element(LayoutElementType),
    Text,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LayoutElementType {
    Element,
    HTMLBRElement,
    HTMLCanvasElement,
    HTMLIFrameElement,
    HTMLImageElement,
    HTMLInputElement,
    HTMLObjectElement,
    HTMLParagraphElement,
    HTMLTableCellElement,
    HTMLTableColElement,
    HTMLTableElement,
    HTMLTableRowElement,
    HTMLTableSectionElement,
    HTMLTextAreaElement,
    SVGSVGElement,
}

pub enum HTMLCanvasDataSource {
    WebGL(webrender_api::ImageKey),
    Image(Option<IpcSender<CanvasMsg>>),
}

pub struct HTMLCanvasData {
    pub source: HTMLCanvasDataSource,
    pub width: u32,
    pub height: u32,
    pub canvas_id: CanvasId,
}

pub struct SVGSVGData {
    pub width: u32,
    pub height: u32,
}

/// The address of a node known to be valid. These are sent from script to layout.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TrustedNodeAddress(pub *const c_void);

#[allow(unsafe_code)]
unsafe impl Send for TrustedNodeAddress {}

pub fn is_image_data(uri: &str) -> bool {
    static TYPES: &'static [&'static str] =
        &["data:image/png", "data:image/gif", "data:image/jpeg"];
    TYPES.iter().any(|&type_| uri.starts_with(type_))
}

/// Whether the pending image needs to be fetched or is waiting on an existing fetch.
pub enum PendingImageState {
    Unrequested(ServoUrl),
    PendingResponse,
}

/// The data associated with an image that is not yet present in the image cache.
/// Used by the script thread to hold on to DOM elements that need to be repainted
/// when an image fetch is complete.
pub struct PendingImage {
    pub state: PendingImageState,
    pub node: UntrustedNodeAddress,
    pub id: PendingImageId,
}
<commit_msg>layout: add HTMLMediaFrameSource trait and HTMLMediaData struct<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! This module contains traits in script used generically in the rest of Servo.
//! The traits are here instead of in script so that these modules won't have
//! to depend on script.

#![deny(unsafe_code)]
#![feature(associated_type_defaults)]

extern crate app_units;
extern crate atomic_refcell;
extern crate canvas_traits;
extern crate cssparser;
extern crate euclid;
extern crate gfx_traits;
#[macro_use]
extern crate html5ever;
extern crate ipc_channel;
extern crate libc;
#[macro_use]
extern crate log;
extern crate malloc_size_of;
#[macro_use]
extern crate malloc_size_of_derive;
extern crate metrics;
extern crate msg;
extern crate net_traits;
extern crate profile_traits;
extern crate range;
extern crate script_traits;
extern crate selectors;
extern crate servo_arc;
extern crate servo_atoms;
extern crate servo_channel;
extern crate servo_url;
extern crate style;
extern crate webrender_api;

pub mod message;
pub mod reporter;
pub mod rpc;
pub mod wrapper_traits;

use atomic_refcell::AtomicRefCell;
use canvas_traits::canvas::{CanvasMsg, CanvasId};
use ipc_channel::ipc::IpcSender;
use libc::c_void;
use net_traits::image_cache::PendingImageId;
use script_traits::UntrustedNodeAddress;
use servo_url::ServoUrl;
use std::ptr::NonNull;
use std::sync::atomic::AtomicIsize;
use style::data::ElementData;

#[repr(C)]
pub struct StyleData {
    /// Data that the style system associates with a node. When the
    /// style system is being used standalone, this is all that hangs
    /// off the node. This must be first to permit the various
    /// transmutations between ElementData and PersistentLayoutData.
    pub element_data: AtomicRefCell<ElementData>,

    /// Information needed during parallel traversals.
    pub parallel: DomParallelInfo,
}

impl StyleData {
    pub fn new() -> Self {
        Self {
            element_data: AtomicRefCell::new(ElementData::default()),
            parallel: DomParallelInfo::new(),
        }
    }
}

#[derive(Clone, Copy, MallocSizeOf)]
pub struct OpaqueStyleAndLayoutData {
    // NB: We really store a `StyleAndLayoutData` here, so be careful!
    #[ignore_malloc_size_of = "TODO(#6910) Box value that should be counted but \
                               the type lives in layout"]
    pub ptr: NonNull<StyleData>,
}

#[allow(unsafe_code)]
unsafe impl Send for OpaqueStyleAndLayoutData {}

/// Information that we need stored in each DOM node.
#[derive(MallocSizeOf)]
pub struct DomParallelInfo {
    /// The number of children remaining to process during bottom-up traversal.
    pub children_to_process: AtomicIsize,
}

impl DomParallelInfo {
    pub fn new() -> DomParallelInfo {
        DomParallelInfo {
            children_to_process: AtomicIsize::new(0),
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LayoutNodeType {
    Element(LayoutElementType),
    Text,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LayoutElementType {
    Element,
    HTMLBRElement,
    HTMLCanvasElement,
    HTMLIFrameElement,
    HTMLImageElement,
    HTMLInputElement,
    HTMLMediaElement,
    HTMLObjectElement,
    HTMLParagraphElement,
    HTMLTableCellElement,
    HTMLTableColElement,
    HTMLTableElement,
    HTMLTableRowElement,
    HTMLTableSectionElement,
    HTMLTextAreaElement,
    SVGSVGElement,
}

pub enum HTMLCanvasDataSource {
    WebGL(webrender_api::ImageKey),
    Image(Option<IpcSender<CanvasMsg>>),
}

pub struct HTMLCanvasData {
    pub source: HTMLCanvasDataSource,
    pub width: u32,
    pub height: u32,
    pub canvas_id: CanvasId,
}

pub struct SVGSVGData {
    pub width: u32,
    pub height: u32,
}

/// The address of a node known to be valid. These are sent from script to layout.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TrustedNodeAddress(pub *const c_void);

#[allow(unsafe_code)]
unsafe impl Send for TrustedNodeAddress {}

pub fn is_image_data(uri: &str) -> bool {
    static TYPES: &'static [&'static str] =
        &["data:image/png", "data:image/gif", "data:image/jpeg"];
    TYPES.iter().any(|&type_| uri.starts_with(type_))
}

/// Whether the pending image needs to be fetched or is waiting on an existing fetch.
pub enum PendingImageState {
    Unrequested(ServoUrl),
    PendingResponse,
}

/// The data associated with an image that is not yet present in the image cache.
/// Used by the script thread to hold on to DOM elements that need to be repainted
/// when an image fetch is complete.
pub struct PendingImage {
    pub state: PendingImageState,
    pub node: UntrustedNodeAddress,
    pub id: PendingImageId,
}

/// FIXME(victor): probably this doesn't belong here
pub trait HTMLMediaFrameSource: Send + Sync + 'static {
    fn get_current_frame(&self) -> Option<(webrender_api::ImageKey, i32, i32)>;
    fn clone_boxed(&self) -> Box<HTMLMediaFrameSource>;
}

pub struct HTMLMediaData {
    pub frame_source: Box<HTMLMediaFrameSource>,
}
<|endoftext|>


<commit_before>extern crate yoga_wrapper;
extern crate rustbox;
extern crate yoga_rustbox;
extern crate yoga;

use yoga::{Backend, Builds, Renders, Renderable};

use std::error::Error;
use std::default::Default;

use rustbox::RustBox;
use rustbox::Key;

fn main() {
    let rustbox = match RustBox::init(Default::default()) {
        Result::Ok(v) => v,
        Result::Err(e) => panic!("{}", e),
    };

    let builder = yoga_rustbox::Builder::new();

    let mut text = builder.text("Yo!");
    text.set_height(3.0);
    text.set_align_self(yoga_wrapper::Align::Center);
    text.set_flex_grow(1.0);

    let mut image = builder.view();
    image.set_width(8.0);
    image.set_margin(yoga_wrapper::Edge::End, 2.0);

    let mut root = builder.view();
    root.set_width(50.0);
    root.set_height(12.0);
    root.set_flex_direction(yoga_wrapper::FlexDirection::Row);
    root.set_padding(yoga_wrapper::Edge::All, 2.0);

    root.insert_child(&image, 0);
    root.insert_child(&text, 1);

    root.calculate_layout();

    yoga_rustbox::Backend::new(&rustbox).render(&root);

    loop {
        match rustbox.poll_event(false) {
            Ok(rustbox::Event::KeyEvent(key)) => {
                match key {
                    Key::Char('q') => {
                        break;
                    }
                    _ => {}
                }
            }
            Err(e) => panic!("{}", e.description()),
            _ => {}
        }
    }
}
<commit_msg>feat(example): use background color in c example<commit_after>extern crate yoga_wrapper;
extern crate rustbox;
extern crate yoga_rustbox;
extern crate yoga;

use yoga::{Backend, Builds, Renders, Renderable};

use std::error::Error;
use std::default::Default;

use rustbox::RustBox;
use rustbox::Key;

fn main() {
    let rustbox = match RustBox::init(Default::default()) {
        Result::Ok(v) => v,
        Result::Err(e) => panic!("{}", e),
    };

    let builder = yoga_rustbox::Builder::new();

    let mut text = builder.text("Yo!");
    text.set_height(3.0);
    text.set_align_self(yoga_wrapper::Align::Center);
    text.set_flex_grow(1.0);
    text.set_background_color(Some(yoga::style::BackgroundColor::Color(rustbox::Color::Cyan)));

    let mut image = builder.view();
    image.set_width(8.0);
    image.set_margin(yoga_wrapper::Edge::End, 2.0);
    image.set_background_color(Some(yoga::style::BackgroundColor::Color(rustbox::Color::Cyan)));

    let mut root = builder.view();
    root.set_width(50.0);
    root.set_height(12.0);
    root.set_flex_direction(yoga_wrapper::FlexDirection::Row);
    root.set_padding(yoga_wrapper::Edge::All, 2.0);
    root.set_background_color(Some(yoga::style::BackgroundColor::Color(rustbox::Color::White)));

    root.insert_child(&image, 0);
    root.insert_child(&text, 1);

    root.calculate_layout();

    yoga_rustbox::Backend::new(&rustbox).render(&root);

    loop {
        match rustbox.poll_event(false) {
            Ok(rustbox::Event::KeyEvent(key)) => {
                match key {
                    Key::Char('q') => {
                        break;
                    }
                    _ => {}
                }
            }
            Err(e) => panic!("{}", e.description()),
            _ => {}
        }
    }
}
<|endoftext|>


<commit_before>//#include <stdio.h>
//#define GLFW_INCLUDE_ES3
//#include <GLFW/glfw3.h>
//#include "nanovg.h"
//#define NANOVG_GLES3_IMPLEMENTATION
//#include "nanovg_gl.h"
//#include "nanovg_gl_utils.h"
//#include "demo.h"
//#include "perf.h"

#![feature(globs)]
#![feature(macro_rules)]
//#![macro_escape]

extern crate native;
extern crate libc;
extern crate glfw;
extern crate gl;
extern crate nanovg;

use glfw::Context;
//use gl::*;
use gl::types::*;
use std::cell::Cell;

use nanovg::NVGcolor;
use nanovg::NVGcontext;

macro_rules! verify(
    ($e: expr) => (
        {
            $e;
            assert_eq!(gl::GetError(), 0);
        }
    )
)

mod perf;

#[start]
fn start(argc: int, argv: *const *const u8) -> int {
    native::start(argc, argv, main)
}

fn error_callback(_: glfw::Error, description: String, error_count: &Cell<uint>) {
    println!("GLFW error {}: {}", error_count.get(), description);
    error_count.set(error_count.get() + 1);
}

fn init_gl() {
    verify!(gl::FrontFace(gl::CCW));
    verify!(gl::Enable(gl::DEPTH_TEST));
    verify!(gl::Enable(gl::SCISSOR_TEST));
    verify!(gl::DepthFunc(gl::LEQUAL));
    verify!(gl::FrontFace(gl::CCW));
    verify!(gl::Enable(gl::CULL_FACE));
    verify!(gl::CullFace(gl::BACK));
}

static blowup: bool = false;
static screenshot: bool = false;
static premult: bool = false;

fn main()
{
//  struct DemoData data;

    let glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();

	//glfw.set_error_callback(glfw::FAIL_ON_ERRORS);
	glfw.set_error_callback(Some(
	    glfw::Callback {
	        f: error_callback,
	        data: Cell::new(0),
	    }
	));

	//#ifndef _WIN32 // don't require this on win32, and works with more cards
	//	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	//	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
	//	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	//	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	//#endif
	//	glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1);

	glfw.window_hint(glfw::ContextVersion(3, 2));
 	glfw.window_hint(glfw::OpenglForwardCompat(true));
 	glfw.window_hint(glfw::OpenglProfile(glfw::OpenGlCoreProfile));
 	glfw.window_hint(glfw::OpenglDebugContext(true));

    let (window, events) = glfw.create_window(300, 300, "NanoVG GL3 exmaple", glfw::Windowed)
        .expect("Failed to create GLFW window.");

	// window.set_key_callback(key);
    window.set_key_polling(true);

    window.make_current();

    // use glfw to load GL function pointers
    verify!(gl::load_with(|name| glfw.get_proc_address(name)));
    init_gl();

    let vg: *mut nanovg::NVGcontext;
    unsafe {
     	vg = nanovg::nvgCreateGL3(nanovg::NVG_ANTIALIAS | nanovg::NVG_STENCIL_STROKES);
     	assert!(!vg.is_null());
    }
    println!("created NVGcontext: {}", vg);

	//if (vg == NULL) {
	//  printf("Could not init nanovg.\n");
	//  return -1;
	//}

//  if (loadDemoData(vg, &data) == -1)
//    return -1;

	glfw.set_swap_interval(0);

	glfw.set_time(0.0);
	let mut prevt = glfw.get_time();

	let mut fps = perf::PerfGraph::init(perf::FPS, "Frame Time");

	println!("starting event loop");

    while !window.should_close()
    {
    	let mut t: f64 = glfw.get_time();
    	let mut dt: f64 = t - prevt;
    	prevt = t;
    	fps.update(dt);

        let (mx, my) = window.get_cursor_pos(); // (f64,f64)
        let (winWidth, winHeight) = window.get_size();  // (i32,i32)
        let (fbWidth, fbHeight) = window.get_framebuffer_size();
        // Calculate pixel ration for hi-dpi devices.
        let pxRatio = fbWidth as f64 / winWidth as f64;

        // Update and render
        verify!(gl::Viewport(0, 0, fbWidth, fbHeight));
        if premult {
        	verify!(gl::ClearColor(0.0, 0.0, 0.0, 0.0));
        } else {
        	verify!(gl::ClearColor(0.3, 0.3, 0.32, 1.0));
        }
        verify!(gl::Clear(gl::COLOR_BUFFER_BIT|gl::DEPTH_BUFFER_BIT|gl::STENCIL_BUFFER_BIT));

        verify!(gl::Enable(gl::BLEND));
        verify!(gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA));
        verify!(gl::Enable(gl::CULL_FACE));
        verify!(gl::Disable(gl::DEPTH_TEST));

        //unsafe { nanovg::nvgBeginFrame(vg, winWidth, winHeight, pxRatio as f32); }

        //renderDemo(vg, mx,my, winWidth,winHeight, t, blowup, &data);
        //fps.render(vg, 5.0, 5.0);

        //unsafe { nanovg::nvgEndFrame(vg); }

        gl::Enable(gl::DEPTH_TEST);

        //if screenshot {
        //  screenshot = false;
        //  saveScreenShot(fbWidth, fbHeight, premult, "dump.png");
        //}

    	window.swap_buffers();

        glfw.poll_events();
        for (_, event) in glfw::flush_messages(&events) {
            handle_window_event(&window, event);
        }
    }

//  freeDemoData(vg, &data);
//
//  nvgDeleteGLES3(vg);
//
//  glfwTerminate();
//  return 0;
}

fn handle_window_event(window: &glfw::Window, event: glfw::WindowEvent) {
    match event {
        glfw::KeyEvent(glfw::KeyEscape, _, glfw::Press, _) => {
            window.set_should_close(true)
        }
        _ => {}
    }
}
//static void key(GLFWwindow* window, int key, int scancode, int action, int mods)
//{
//  NVG_NOTUSED(scancode);
//  NVG_NOTUSED(mods);
//  if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
//    glfwSetWindowShouldClose(window, GL_TRUE);
//  if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
//    blowup = !blowup;
//  if (key == GLFW_KEY_S && action == GLFW_PRESS)
//    screenshot = 1;
//  if (key == GLFW_KEY_P && action == GLFW_PRESS)
//    premult = !premult;
//}
<commit_msg>nvg context created (nvgCreateGL3)<commit_after>//#include <stdio.h>
//#define GLFW_INCLUDE_ES3
//#include <GLFW/glfw3.h>
//#include "nanovg.h"
//#define NANOVG_GLES3_IMPLEMENTATION
//#include "nanovg_gl.h"
//#include "nanovg_gl_utils.h"
//#include "demo.h"
//#include "perf.h"

#![feature(globs)]
#![feature(macro_rules)]
//#![macro_escape]

extern crate native;
extern crate libc;
extern crate glfw;
extern crate gl;
extern crate nanovg;

use glfw::Context;
//use gl::*;
use gl::types::*;
use std::cell::Cell;

use nanovg::NVGcolor;
use nanovg::NVGcontext;

macro_rules! verify(
    ($e: expr) => (
        {
            $e;
            assert_eq!(gl::GetError(), 0);
        }
    )
)

mod perf;

#[start]
fn start(argc: int, argv: *const *const u8) -> int {
    native::start(argc, argv, main)
}

fn error_callback(_: glfw::Error, description: String, error_count: &Cell<uint>) {
    println!("GLFW error {}: {}", error_count.get(), description);
    error_count.set(error_count.get() + 1);
}

fn init_gl() {
    verify!(gl::FrontFace(gl::CCW));
    verify!(gl::Enable(gl::DEPTH_TEST));
    verify!(gl::Enable(gl::SCISSOR_TEST));
    verify!(gl::DepthFunc(gl::LEQUAL));
    verify!(gl::FrontFace(gl::CCW));
    verify!(gl::Enable(gl::CULL_FACE));
    verify!(gl::CullFace(gl::BACK));
}

static blowup: bool = false;
static screenshot: bool = false;
static premult: bool = false;

fn main()
{
//  struct DemoData data;

    let glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();

	//glfw.set_error_callback(glfw::FAIL_ON_ERRORS);
	glfw.set_error_callback(Some(
	    glfw::Callback {
	        f: error_callback,
	        data: Cell::new(0),
	    }
	));

	//#ifndef _WIN32 // don't require this on win32, and works with more cards
	//	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	//	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
	//	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	//	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	//#endif
	//	glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1);

	glfw.window_hint(glfw::ContextVersion(3, 2));
 	glfw.window_hint(glfw::OpenglForwardCompat(true));
 	glfw.window_hint(glfw::OpenglProfile(glfw::OpenGlCoreProfile));
 	glfw.window_hint(glfw::OpenglDebugContext(true));

    let (window, events) = glfw.create_window(300, 300, "NanoVG GL3 exmaple", glfw::Windowed)
        .expect("Failed to create GLFW window.");

	// window.set_key_callback(key);
    window.set_key_polling(true);

    window.make_current();

    // use glfw to load GL function pointers
    verify!(gl::load_with(|name| glfw.get_proc_address(name)));
    init_gl();

    let vg: *mut nanovg::NVGcontext;
    unsafe {
     	vg = nanovg::nvgCreateGL3(nanovg::NVG_ANTIALIAS | nanovg::NVG_STENCIL_STROKES);
     	assert!(!vg.is_null());
    }
    println!("created NVGcontext: {}", vg);

	//if (vg == NULL) {
	//  printf("Could not init nanovg.\n");
	//  return -1;
	//}

//  if (loadDemoData(vg, &data) == -1)
//    return -1;

	glfw.set_swap_interval(0);

	glfw.set_time(0.0);
	let mut prevt = glfw.get_time();

	let mut fps = perf::PerfGraph::init(perf::FPS, "Frame Time");

    while !window.should_close()
    {
    	let mut t: f64 = glfw.get_time();
    	let mut dt: f64 = t - prevt;
    	prevt = t;
    	fps.update(dt);

        let (mx, my) = window.get_cursor_pos(); // (f64,f64)
        let (winWidth, winHeight) = window.get_size();  // (i32,i32)
        let (fbWidth, fbHeight) = window.get_framebuffer_size();
        // Calculate pixel ration for hi-dpi devices.
        let pxRatio = fbWidth as f64 / winWidth as f64;

        // Update and render
        verify!(gl::Viewport(0, 0, fbWidth, fbHeight));
        if premult {
        	verify!(gl::ClearColor(0.0, 0.0, 0.0, 0.0));
        } else {
        	verify!(gl::ClearColor(0.3, 0.3, 0.32, 1.0));
        }
        verify!(gl::Clear(gl::COLOR_BUFFER_BIT|gl::DEPTH_BUFFER_BIT|gl::STENCIL_BUFFER_BIT));

        verify!(gl::Enable(gl::BLEND));
        verify!(gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA));
        verify!(gl::Enable(gl::CULL_FACE));
        verify!(gl::Disable(gl::DEPTH_TEST));

        unsafe { nanovg::nvgBeginFrame(vg, winWidth, winHeight, pxRatio as f32); }

        //renderDemo(vg, mx,my, winWidth,winHeight, t, blowup, &data);
        fps.render(vg, 5.0, 5.0);

        unsafe { nanovg::nvgEndFrame(vg); }

        gl::Enable(gl::DEPTH_TEST);

        //if screenshot {
        //  screenshot = false;
        //  saveScreenShot(fbWidth, fbHeight, premult, "dump.png");
        //}

    	window.swap_buffers();

        glfw.poll_events();
        for (_, event) in glfw::flush_messages(&events) {
            handle_window_event(&window, event);
        }
    }

//  freeDemoData(vg, &data);
//
//  nvgDeleteGLES3(vg);
//
//  glfwTerminate();
//  return 0;
}

fn handle_window_event(window: &glfw::Window, event: glfw::WindowEvent) {
    match event {
        glfw::KeyEvent(glfw::KeyEscape, _, glfw::Press, _) => {
            window.set_should_close(true)
        }
        _ => {}
    }
}
//static void key(GLFWwindow* window, int key, int scancode, int action, int mods)
//{
//  NVG_NOTUSED(scancode);
//  NVG_NOTUSED(mods);
//  if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
//    glfwSetWindowShouldClose(window, GL_TRUE);
//  if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
//    blowup = !blowup;
//  if (key == GLFW_KEY_S && action == GLFW_PRESS)
//    screenshot = 1;
//  if (key == GLFW_KEY_P && action == GLFW_PRESS)
//    premult = !premult;
//}
<|endoftext|>


<commit_before>///! Gathering dependencies is the largest part of annotating.
use cargo_metadata::{Metadata as CargoMetadata, Node, NodeDep, Package, PackageId};
use serde::{Deserialize, Serialize};

use crate::utils::sanitize_module_name;
use crate::utils::starlark::{Select, SelectList};

/// A representation of a crate dependency
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct Dependency {
    /// The PackageId of the target
    pub package_id: PackageId,

    /// The library target name of the dependency.
    pub target_name: String,

    /// The alias for the dependency from the perspective of the current package
    pub alias: Option<String>,
}

/// A collection of [Dependency]s sorted by dependency kind.
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct DependencySet {
    pub normal_deps: SelectList<Dependency>,
    pub normal_dev_deps: SelectList<Dependency>,
    pub proc_macro_deps: SelectList<Dependency>,
    pub proc_macro_dev_deps: SelectList<Dependency>,
    pub build_deps: SelectList<Dependency>,
    pub build_proc_macro_deps: SelectList<Dependency>,
}

impl DependencySet {
    /// Collect all dependencies for a given node in the resolve graph.
    pub fn new_for_node(node: &Node, metadata: &CargoMetadata) -> Self {
        let (normal_dev_deps, normal_deps) = {
            let (dev, normal) = node
                .deps
                .iter()
                // Do not track workspace members as dependencies. Users are expected to maintain those connections
                .filter(|dep| !is_workspace_member(dep, metadata))
                .filter(|dep| is_lib_package(&metadata[&dep.pkg]))
                .filter(|dep| is_normal_dependency(dep) || is_dev_dependency(dep))
                .partition(|dep| is_dev_dependency(dep));

            (
                collect_deps_selectable(dev, metadata),
                collect_deps_selectable(normal, metadata),
            )
        };

        let (proc_macro_dev_deps, proc_macro_deps) = {
            let (dev, normal) = node
                .deps
                .iter()
                // Do not track workspace members as dependencies. Users are expected to maintain those connections
                .filter(|dep| !is_workspace_member(dep, metadata))
                .filter(|dep| is_proc_macro_package(&metadata[&dep.pkg]))
                .filter(|dep| !is_build_dependency(dep))
                .partition(|dep| is_dev_dependency(dep));

            (
                collect_deps_selectable(dev, metadata),
                collect_deps_selectable(normal, metadata),
            )
        };

        let (build_proc_macro_deps, mut build_deps) = {
            let (proc_macro, normal) = node
                .deps
                .iter()
                // Do not track workspace members as dependencies. Users are expected to maintain those connections
                .filter(|dep| !is_workspace_member(dep, metadata))
                .filter(|dep| is_build_dependency(dep))
                .filter(|dep| !is_dev_dependency(dep))
                .partition(|dep| is_proc_macro_package(&metadata[&dep.pkg]));

            (
                collect_deps_selectable(proc_macro, metadata),
                collect_deps_selectable(normal, metadata),
            )
        };

        // `*-sys` packages follow slightly different rules than other dependencies. These
        // packages seem to provide some environment variables required to build the top level
        // package and are expected to be avialable to other build scripts. If a target depends
        // on a `*-sys` crate for itself, so would it's build script. Hopefully this is correct.
        // https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
        // https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages
        let sys_name = format!("{}-sys", &metadata[&node.id].name);
        normal_deps.configurations().into_iter().for_each(|config| {
            normal_deps
                .get_iter(config)
                // Iterating over known key should be safe
                .unwrap()
                // Add any normal dependency to build dependencies that are associated `*-sys` crates
                .for_each(|dep| {
                    let dep_pkg_name = &metadata[&dep.package_id].name;
                    if *dep_pkg_name == sys_name {
                        build_deps.insert(dep.clone(), config.cloned())
                    }
                });
        });

        Self {
            normal_deps,
            normal_dev_deps,
            proc_macro_deps,
            proc_macro_dev_deps,
            build_deps,
            build_proc_macro_deps,
        }
    }
}

fn collect_deps_selectable(
    deps: Vec<&NodeDep>,
    metadata: &cargo_metadata::Metadata,
) -> SelectList<Dependency> {
    let mut selectable = SelectList::default();

    for dep in deps.into_iter() {
        let dep_pkg = &metadata[&dep.pkg];
        let target_name = get_library_target_name(dep_pkg, &dep.name);
        let alias = get_target_alias(&dep.name, dep_pkg);

        for kind_info in &dep.dep_kinds {
            selectable.insert(
                Dependency {
                    package_id: dep.pkg.clone(),
                    target_name: target_name.clone(),
                    alias: alias.clone(),
                },
                kind_info
                    .target
                    .as_ref()
                    .map(|platform| platform.to_string()),
            );
        }
    }

    selectable
}

fn is_lib_package(package: &Package) -> bool {
    package.targets.iter().any(|target| {
        target
            .crate_types
            .iter()
            .any(|t| ["lib", "rlib"].contains(&t.as_str()))
    })
}

fn is_proc_macro_package(package: &Package) -> bool {
    package
        .targets
        .iter()
        .any(|target| target.crate_types.iter().any(|t| t == "proc-macro"))
}

fn is_dev_dependency(node_dep: &NodeDep) -> bool {
    let is_normal_dep = is_normal_dependency(node_dep);
    let is_dev_dep = node_dep
        .dep_kinds
        .iter()
        .any(|k| matches!(k.kind, cargo_metadata::DependencyKind::Development));

    // In the event that a dependency is listed as both a dev and normal dependency,
    // it's only considered a dev dependency if it's __not__ a normal dependency.
    !is_normal_dep && is_dev_dep
}

fn is_build_dependency(node_dep: &NodeDep) -> bool {
    node_dep
        .dep_kinds
        .iter()
        .any(|k| matches!(k.kind, cargo_metadata::DependencyKind::Build))
}

fn is_normal_dependency(node_dep: &NodeDep) -> bool {
    node_dep
        .dep_kinds
        .iter()
        .any(|k| matches!(k.kind, cargo_metadata::DependencyKind::Normal))
}

fn is_workspace_member(node_dep: &NodeDep, metadata: &CargoMetadata) -> bool {
    metadata
        .workspace_members
        .iter()
        .any(|id| id == &node_dep.pkg)
}

fn get_library_target_name(package: &Package, potential_name: &str) -> String {
    // If the potential name is not an alias in a dependent's package, a target's name
    // should match which means we already know what the target library name is.
    if package.targets.iter().any(|t| t.name == potential_name) {
        return potential_name.to_string();
    }

    // Locate any library type targets
    let lib_targets: Vec<&cargo_metadata::Target> = package
        .targets
        .iter()
        .filter(|t| t.kind.iter().any(|k| k == "lib" || k == "proc-macro"))
        .collect();

    // Only one target should be found
    assert_eq!(lib_targets.len(), 1);

    let target = lib_targets.into_iter().last().unwrap();
    target.name.clone()
}

/// The resolve graph (resolve.nodes[#].deps[#].name) of Cargo metadata uses module names
/// for targets where packages (packages[#].targets[#].name) uses crate names. In order to
/// determine whether or not a dependency is aliased, we compare it with all available targets
/// on it's package. Note that target names are not guaranteed to be module names where Node
/// dependnecies are, so we need to do a conversion to check for this
fn get_target_alias(target_name: &str, package: &Package) -> Option<String> {
    match package
        .targets
        .iter()
        .all(|t| sanitize_module_name(&t.name) != target_name)
    {
        true => Some(target_name.to_string()),
        false => None,
    }
}

#[cfg(test)]
mod test {
    use std::collections::BTreeSet;

    use super::*;

    use crate::test::*;

    /// Locate the [cargo_metadata::Node] for the crate matching the given name
    fn find_metadata_node<'a>(
        name: &str,
        metadata: &'a cargo_metadata::Metadata,
    ) -> &'a cargo_metadata::Node {
        metadata
            .resolve
            .as_ref()
            .unwrap()
            .nodes
            .iter()
            .find(|node| {
                let pkg = &metadata[&node.id];
                pkg.name == name
            })
            .unwrap()
    }

    #[test]
    fn sys_dependencies() {
        let metadata = metadata::build_scripts();

        let openssl_node = find_metadata_node("openssl", &metadata);

        let dependencies = DependencySet::new_for_node(openssl_node, &metadata);

        let sys_crate = dependencies
            .normal_deps
            .get_iter(None)
            .unwrap()
            .find(|dep| {
                let pkg = &metadata[&dep.package_id];
                pkg.name == "openssl-sys"
            });

        // sys crates like `openssl-sys` should always be dependencies of any
        // crate which matches it's name minus the `-sys` suffix
        assert!(sys_crate.is_some());
    }

    #[test]
    fn tracked_aliases() {
        let metadata = metadata::alias();

        let aliases_node = find_metadata_node("aliases", &metadata);
        let dependencies = DependencySet::new_for_node(aliases_node, &metadata);

        let aliases: Vec<&Dependency> = dependencies
            .normal_deps
            .get_iter(None)
            .unwrap()
            .filter(|dep| dep.alias.is_some())
            .collect();

        assert_eq!(aliases.len(), 2);

        let expected: BTreeSet<String> = aliases
            .into_iter()
            .map(|dep| dep.alias.as_ref().unwrap().clone())
            .collect();

        assert_eq!(
            expected,
            BTreeSet::from(["pinned_log".to_owned(), "pinned_names".to_owned()])
        );
    }

    #[test]
    fn matched_rlib() {
        let metadata = metadata::crate_types();

        let node = find_metadata_node("crate-types", &metadata);
        let dependencies = DependencySet::new_for_node(node, &metadata);

        let rlib_deps: Vec<&Dependency> = dependencies
            .normal_deps
            .get_iter(None)
            .unwrap()
            .filter(|dep| {
                let pkg = &metadata[&dep.package_id];
                pkg.targets
                    .iter()
                    .any(|t| t.crate_types.contains(&"rlib".to_owned()))
            })
            .collect();

        // Currently the only expected __explicitly__ "rlib" target in this metadata is `sysinfo`.
        assert_eq!(rlib_deps.len(), 1);

        let sysinfo_dep = rlib_deps.iter().last().unwrap();
        assert_eq!(sysinfo_dep.target_name, "sysinfo");
    }

    #[test]
    fn multiple_dep_kinds() {
        let metadata = metadata::multi_cfg_dep();

        let node = find_metadata_node("cpufeatures", &metadata);
        let dependencies = DependencySet::new_for_node(node, &metadata);

        let libc_cfgs: Vec<Option<String>> = dependencies
            .normal_deps
            .configurations()
            .into_iter()
            .flat_map(|conf| {
                dependencies
                    .normal_deps
                    .get_iter(conf)
                    .expect("Iterating over known keys should never panic")
                    .filter(|dep| dep.target_name == "libc")
                    .map(move |_| conf.cloned())
            })
            .collect();

        assert_eq!(libc_cfgs.len(), 2);

        let cfg_strs: BTreeSet<String> = libc_cfgs.into_iter().flatten().collect();
        assert_eq!(
            cfg_strs,
            BTreeSet::from([
                "aarch64-apple-darwin".to_owned(),
                "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))".to_owned(),
            ])
        );
    }
}
<commit_msg>Fixed crate_universe not finding target library names for "rlib"s (#1429)<commit_after>///! Gathering dependencies is the largest part of annotating.
use anyhow::{bail, Result};
use cargo_metadata::{Metadata as CargoMetadata, Node, NodeDep, Package, PackageId};
use serde::{Deserialize, Serialize};

use crate::utils::sanitize_module_name;
use crate::utils::starlark::{Select, SelectList};

/// A representation of a crate dependency
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct Dependency {
    /// The PackageId of the target
    pub package_id: PackageId,

    /// The library target name of the dependency.
    pub target_name: String,

    /// The alias for the dependency from the perspective of the current package
    pub alias: Option<String>,
}

/// A collection of [Dependency]s sorted by dependency kind.
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct DependencySet {
    pub normal_deps: SelectList<Dependency>,
    pub normal_dev_deps: SelectList<Dependency>,
    pub proc_macro_deps: SelectList<Dependency>,
    pub proc_macro_dev_deps: SelectList<Dependency>,
    pub build_deps: SelectList<Dependency>,
    pub build_proc_macro_deps: SelectList<Dependency>,
}

impl DependencySet {
    /// Collect all dependencies for a given node in the resolve graph.
    pub fn new_for_node(node: &Node, metadata: &CargoMetadata) -> Self {
        let (normal_dev_deps, normal_deps) = {
            let (dev, normal) = node
                .deps
                .iter()
                // Do not track workspace members as dependencies. Users are expected to maintain those connections
                .filter(|dep| !is_workspace_member(dep, metadata))
                .filter(|dep| is_lib_package(&metadata[&dep.pkg]))
                .filter(|dep| is_normal_dependency(dep) || is_dev_dependency(dep))
                .partition(|dep| is_dev_dependency(dep));

            (
                collect_deps_selectable(dev, metadata),
                collect_deps_selectable(normal, metadata),
            )
        };

        let (proc_macro_dev_deps, proc_macro_deps) = {
            let (dev, normal) = node
                .deps
                .iter()
                // Do not track workspace members as dependencies. Users are expected to maintain those connections
                .filter(|dep| !is_workspace_member(dep, metadata))
                .filter(|dep| is_proc_macro_package(&metadata[&dep.pkg]))
                .filter(|dep| !is_build_dependency(dep))
                .partition(|dep| is_dev_dependency(dep));

            (
                collect_deps_selectable(dev, metadata),
                collect_deps_selectable(normal, metadata),
            )
        };

        let (build_proc_macro_deps, mut build_deps) = {
            let (proc_macro, normal) = node
                .deps
                .iter()
                // Do not track workspace members as dependencies. Users are expected to maintain those connections
                .filter(|dep| !is_workspace_member(dep, metadata))
                .filter(|dep| is_build_dependency(dep))
                .filter(|dep| !is_dev_dependency(dep))
                .partition(|dep| is_proc_macro_package(&metadata[&dep.pkg]));

            (
                collect_deps_selectable(proc_macro, metadata),
                collect_deps_selectable(normal, metadata),
            )
        };

        // `*-sys` packages follow slightly different rules than other dependencies. These
        // packages seem to provide some environment variables required to build the top level
        // package and are expected to be avialable to other build scripts. If a target depends
        // on a `*-sys` crate for itself, so would it's build script. Hopefully this is correct.
        // https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
        // https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages
        let sys_name = format!("{}-sys", &metadata[&node.id].name);
        normal_deps.configurations().into_iter().for_each(|config| {
            normal_deps
                .get_iter(config)
                // Iterating over known key should be safe
                .unwrap()
                // Add any normal dependency to build dependencies that are associated `*-sys` crates
                .for_each(|dep| {
                    let dep_pkg_name = &metadata[&dep.package_id].name;
                    if *dep_pkg_name == sys_name {
                        build_deps.insert(dep.clone(), config.cloned())
                    }
                });
        });

        Self {
            normal_deps,
            normal_dev_deps,
            proc_macro_deps,
            proc_macro_dev_deps,
            build_deps,
            build_proc_macro_deps,
        }
    }
}

fn collect_deps_selectable(
    deps: Vec<&NodeDep>,
    metadata: &cargo_metadata::Metadata,
) -> SelectList<Dependency> {
    let mut selectable = SelectList::default();

    for dep in deps.into_iter() {
        let dep_pkg = &metadata[&dep.pkg];
        let target_name = get_library_target_name(dep_pkg, &dep.name)
            .expect("Nodes Dependencies are expected to exclusively be library-like targets");
        let alias = get_target_alias(&dep.name, dep_pkg);

        for kind_info in &dep.dep_kinds {
            selectable.insert(
                Dependency {
                    package_id: dep.pkg.clone(),
                    target_name: target_name.clone(),
                    alias: alias.clone(),
                },
                kind_info
                    .target
                    .as_ref()
                    .map(|platform| platform.to_string()),
            );
        }
    }

    selectable
}

fn is_lib_package(package: &Package) -> bool {
    package.targets.iter().any(|target| {
        target
            .crate_types
            .iter()
            .any(|t| ["lib", "rlib"].contains(&t.as_str()))
    })
}

fn is_proc_macro_package(package: &Package) -> bool {
    package
        .targets
        .iter()
        .any(|target| target.crate_types.iter().any(|t| t == "proc-macro"))
}

fn is_dev_dependency(node_dep: &NodeDep) -> bool {
    let is_normal_dep = is_normal_dependency(node_dep);
    let is_dev_dep = node_dep
        .dep_kinds
        .iter()
        .any(|k| matches!(k.kind, cargo_metadata::DependencyKind::Development));

    // In the event that a dependency is listed as both a dev and normal dependency,
    // it's only considered a dev dependency if it's __not__ a normal dependency.
    !is_normal_dep && is_dev_dep
}

fn is_build_dependency(node_dep: &NodeDep) -> bool {
    node_dep
        .dep_kinds
        .iter()
        .any(|k| matches!(k.kind, cargo_metadata::DependencyKind::Build))
}

fn is_normal_dependency(node_dep: &NodeDep) -> bool {
    node_dep
        .dep_kinds
        .iter()
        .any(|k| matches!(k.kind, cargo_metadata::DependencyKind::Normal))
}

fn is_workspace_member(node_dep: &NodeDep, metadata: &CargoMetadata) -> bool {
    metadata
        .workspace_members
        .iter()
        .any(|id| id == &node_dep.pkg)
}

fn get_library_target_name(package: &Package, potential_name: &str) -> Result<String> {
    // If the potential name is not an alias in a dependent's package, a target's name
    // should match which means we already know what the target library name is.
    if package.targets.iter().any(|t| t.name == potential_name) {
        return Ok(potential_name.to_string());
    }

    // Locate any library type targets
    let lib_targets: Vec<&cargo_metadata::Target> = package
        .targets
        .iter()
        .filter(|t| {
            t.kind
                .iter()
                .any(|k| k == "lib" || k == "rlib" || k == "proc-macro")
        })
        .collect();

    // Only one target should be found
    if lib_targets.len() != 1 {
        bail!(
            "Unexpected number of 'library-like' targets found for {}: {:?}",
            package.name,
            package.targets
        )
    }

    let target = lib_targets.into_iter().last().unwrap();
    Ok(target.name.clone())
}

/// The resolve graph (resolve.nodes[#].deps[#].name) of Cargo metadata uses module names
/// for targets where packages (packages[#].targets[#].name) uses crate names. In order to
/// determine whether or not a dependency is aliased, we compare it with all available targets
/// on it's package. Note that target names are not guaranteed to be module names where Node
/// dependnecies are, so we need to do a conversion to check for this
fn get_target_alias(target_name: &str, package: &Package) -> Option<String> {
    match package
        .targets
        .iter()
        .all(|t| sanitize_module_name(&t.name) != target_name)
    {
        true => Some(target_name.to_string()),
        false => None,
    }
}

#[cfg(test)]
mod test {
    use std::collections::BTreeSet;

    use super::*;

    use crate::test::*;

    #[test]
    fn get_expected_lib_target_name() {
        let mut package = mock_cargo_metadata_package();
        package
            .targets
            .extend(vec![serde_json::from_value(serde_json::json!({
                "name": "potential",
                "kind": ["lib"],
                "crate_types": [],
                "required_features": [],
                "src_path": "/tmp/mock.rs",
                "edition": "2021",
                "doctest": false,
                "test": false,
                "doc": false,
            }))
            .unwrap()]);

        assert_eq!(
            get_library_target_name(&package, "potential").unwrap(),
            "potential"
        );
    }

    #[test]
    fn get_lib_target_name() {
        let mut package = mock_cargo_metadata_package();
        package
            .targets
            .extend(vec![serde_json::from_value(serde_json::json!({
                "name": "lib_target",
                "kind": ["lib"],
                "crate_types": [],
                "required_features": [],
                "src_path": "/tmp/mock.rs",
                "edition": "2021",
                "doctest": false,
                "test": false,
                "doc": false,
            }))
            .unwrap()]);

        assert_eq!(
            get_library_target_name(&package, "mock-pkg").unwrap(),
            "lib_target"
        );
    }

    #[test]
    fn get_rlib_target_name() {
        let mut package = mock_cargo_metadata_package();
        package
            .targets
            .extend(vec![serde_json::from_value(serde_json::json!({
                "name": "rlib_target",
                "kind": ["rlib"],
                "crate_types": [],
                "required_features": [],
                "src_path": "/tmp/mock.rs",
                "edition": "2021",
                "doctest": false,
                "test": false,
                "doc": false,
            }))
            .unwrap()]);

        assert_eq!(
            get_library_target_name(&package, "mock-pkg").unwrap(),
            "rlib_target"
        );
    }

    #[test]
    fn get_proc_macro_target_name() {
        let mut package = mock_cargo_metadata_package();
        package
            .targets
            .extend(vec![serde_json::from_value(serde_json::json!({
                "name": "proc_macro_target",
                "kind": ["proc-macro"],
                "crate_types": [],
                "required_features": [],
                "src_path": "/tmp/mock.rs",
                "edition": "2021",
                "doctest": false,
                "test": false,
                "doc": false,
            }))
            .unwrap()]);

        assert_eq!(
            get_library_target_name(&package, "mock-pkg").unwrap(),
            "proc_macro_target"
        );
    }

    #[test]
    fn get_bin_target_name() {
        let mut package = mock_cargo_metadata_package();
        package
            .targets
            .extend(vec![serde_json::from_value(serde_json::json!({
                "name": "bin_target",
                "kind": ["bin"],
                "crate_types": [],
                "required_features": [],
                "src_path": "/tmp/mock.rs",
                "edition": "2021",
                "doctest": false,
                "test": false,
                "doc": false,
            }))
            .unwrap()]);

        // It's an error for no library target to be found.
        assert!(get_library_target_name(&package, "mock-pkg").is_err());
    }

    /// Locate the [cargo_metadata::Node] for the crate matching the given name
    fn find_metadata_node<'a>(
        name: &str,
        metadata: &'a cargo_metadata::Metadata,
    ) -> &'a cargo_metadata::Node {
        metadata
            .resolve
            .as_ref()
            .unwrap()
            .nodes
            .iter()
            .find(|node| {
                let pkg = &metadata[&node.id];
                pkg.name == name
            })
            .unwrap()
    }

    #[test]
    fn sys_dependencies() {
        let metadata = metadata::build_scripts();

        let openssl_node = find_metadata_node("openssl", &metadata);

        let dependencies = DependencySet::new_for_node(openssl_node, &metadata);

        let sys_crate = dependencies
            .normal_deps
            .get_iter(None)
            .unwrap()
            .find(|dep| {
                let pkg = &metadata[&dep.package_id];
                pkg.name == "openssl-sys"
            });

        // sys crates like `openssl-sys` should always be dependencies of any
        // crate which matches it's name minus the `-sys` suffix
        assert!(sys_crate.is_some());
    }

    #[test]
    fn tracked_aliases() {
        let metadata = metadata::alias();

        let aliases_node = find_metadata_node("aliases", &metadata);
        let dependencies = DependencySet::new_for_node(aliases_node, &metadata);

        let aliases: Vec<&Dependency> = dependencies
            .normal_deps
            .get_iter(None)
            .unwrap()
            .filter(|dep| dep.alias.is_some())
            .collect();

        assert_eq!(aliases.len(), 2);

        let expected: BTreeSet<String> = aliases
            .into_iter()
            .map(|dep| dep.alias.as_ref().unwrap().clone())
            .collect();

        assert_eq!(
            expected,
            BTreeSet::from(["pinned_log".to_owned(), "pinned_names".to_owned()])
        );
    }

    #[test]
    fn matched_rlib() {
        let metadata = metadata::crate_types();

        let node = find_metadata_node("crate-types", &metadata);
        let dependencies = DependencySet::new_for_node(node, &metadata);

        let rlib_deps: Vec<&Dependency> = dependencies
            .normal_deps
            .get_iter(None)
            .unwrap()
            .filter(|dep| {
                let pkg = &metadata[&dep.package_id];
                pkg.targets
                    .iter()
                    .any(|t| t.crate_types.contains(&"rlib".to_owned()))
            })
            .collect();

        // Currently the only expected __explicitly__ "rlib" target in this metadata is `sysinfo`.
        assert_eq!(rlib_deps.len(), 1);

        let sysinfo_dep = rlib_deps.iter().last().unwrap();
        assert_eq!(sysinfo_dep.target_name, "sysinfo");
    }

    #[test]
    fn multiple_dep_kinds() {
        let metadata = metadata::multi_cfg_dep();

        let node = find_metadata_node("cpufeatures", &metadata);
        let dependencies = DependencySet::new_for_node(node, &metadata);

        let libc_cfgs: Vec<Option<String>> = dependencies
            .normal_deps
            .configurations()
            .into_iter()
            .flat_map(|conf| {
                dependencies
                    .normal_deps
                    .get_iter(conf)
                    .expect("Iterating over known keys should never panic")
                    .filter(|dep| dep.target_name == "libc")
                    .map(move |_| conf.cloned())
            })
            .collect();

        assert_eq!(libc_cfgs.len(), 2);

        let cfg_strs: BTreeSet<String> = libc_cfgs.into_iter().flatten().collect();
        assert_eq!(
            cfg_strs,
            BTreeSet::from([
                "aarch64-apple-darwin".to_owned(),
                "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))".to_owned(),
            ])
        );
    }
}
<|endoftext|>