1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
//! Various convenience functions for `built` at runtime. /// Parses version-strings with `semver::Version::parse()`. /// /// This function is only available if `built` was compiled with the /// `semver` feature. /// /// The function takes a reference to an array of names and version numbers as /// serialized by `built` and returns an iterator over the unchanged names /// and parsed version numbers. /// /// ``` /// pub mod build_info { /// pub const DEPENDENCIES: [(&'static str, &'static str); 1] = [("built", "0.1.0")]; /// } /// /// let deps = build_info::DEPENDENCIES; /// assert!(built::util::parse_versions(&deps) /// .any(|(name, ver)| name == "built" && /// ver >= semver::Version::parse("0.1.0").unwrap())); /// ``` /// /// # Panics /// If a version can't be parsed by `semver::Version::parse()`. This should never /// happen with version strings provided by Cargo and `built`. #[cfg(feature = "semver")] pub fn parse_versions<'a, T>( name_and_versions: T, ) -> impl Iterator<Item = (&'a str, semver::Version)> where T: IntoIterator<Item = &'a (&'a str, &'a str)>, { fn parse_version<'a>(t: &'a (&'a str, &'a str)) -> (&'a str, semver::Version) { (t.0, t.1.parse().unwrap()) } name_and_versions.into_iter().map(parse_version) } /// Parse a time-string as formatted by `built`. /// /// ``` /// use chrono::Datelike; /// /// pub mod build_info { /// pub const BUILT_TIME_UTC: &'static str = "Tue, 14 Feb 2017 05:21:41 GMT"; /// } /// /// assert_eq!(built::util::strptime(&build_info::BUILT_TIME_UTC).year(), 2017); /// ``` /// /// # Panics /// If the string can't be parsed. This should never happen with input provided /// by `built`. #[cfg(feature = "chrono")] pub fn strptime(s: &str) -> chrono::DateTime<chrono::offset::Utc> { chrono::DateTime::parse_from_rfc2822(s) .unwrap() .with_timezone(&chrono::offset::Utc) } /// Retrieves the git-tag or hash describing the exact version and a boolean /// that indicates if the repository currently has dirty/staged files. /// /// If a valid git-repo can't be discovered at or above the given path, /// `Ok(None)` is returned instead of an `Err`-value. /// /// # Errors /// Errors from `git2` are returned if the repository does exists at all. #[cfg(feature = "git2")] pub fn get_repo_description(root: &std::path::Path) -> Result<Option<(String, bool)>, git2::Error> { match git2::Repository::discover(root) { Ok(repo) => { let mut desc_opt = git2::DescribeOptions::new(); desc_opt.describe_tags().show_commit_oid_as_fallback(true); let tag = repo .describe(&desc_opt) .and_then(|desc| desc.format(None))?; let mut st_opt = git2::StatusOptions::new(); st_opt.include_ignored(false); st_opt.include_untracked(false); let dirty = repo .statuses(Some(&mut st_opt))? .iter() .any(|status| !matches!(status.status(), git2::Status::CURRENT)); Ok(Some((tag, dirty))) } Err(ref e) if e.class() == git2::ErrorClass::Repository && e.code() == git2::ErrorCode::NotFound => { Ok(None) } Err(e) => Err(e), } } /// Retrieves the branch name and hash of HEAD. /// /// The returned value is a tuple of head's reference name and long hash. The /// branch name will be `None` if the head is detached, or it's not valid UTF-8. /// /// If a valid git-repo can't be discovered at or above the given path, /// `Ok(None)` is returned instead of an `Err`-value. /// /// # Errors /// Errors from `git2` are returned if the repository does exists at all. #[cfg(feature = "git2")] pub fn get_repo_head( root: &std::path::Path, ) -> Result<Option<(Option<String>, String)>, git2::Error> { match git2::Repository::discover(root) { Ok(repo) => { // Supposed to be the reference pointed to by HEAD, but it's HEAD // itself, if detached let head_ref = repo.head()?; let branch = { // Check whether `head` is realy the pointed to reference and // not HEAD itself. if !repo.head_detached()? { head_ref.name() } else { None } }; let commit = head_ref.peel_to_commit()?.id(); Ok(Some(( branch.map(ToString::to_string), format!("{}", commit), ))) } Err(ref e) if e.class() == git2::ErrorClass::Repository && e.code() == git2::ErrorCode::NotFound => { Ok(None) } Err(e) => Err(e), } } /// Detect execution on various Continiuous Integration platforms. /// /// CI-platforms are detected by the presence of known environment variables. /// This allows to detect specific CI-platform (like `GitLab`); various /// generic environment variables are also checked, which may result in /// `CIPlatform::Generic`. /// /// Since some platforms have fairly generic environment variables to begin with /// (e.g. `TASK_ID`), this function may have false positives. #[must_use] pub fn detect_ci() -> Option<super::CIPlatform> { super::CIPlatform::detect_from_envmap(&super::get_environment()) }