mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-10-15 00:12:29 +00:00
331 lines
10 KiB
Rust
331 lines
10 KiB
Rust
// Copyright 2023 Greptime Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use std::collections::BTreeSet;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
use std::{env, fs, io};
|
|
|
|
use cargo_manifest::Manifest;
|
|
use git2::{ErrorCode, Repository, RepositoryOpenFlags, StatusOptions};
|
|
|
|
const SHADOW_FILE_NAME: &str = "shadow.rs";
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Refresh VCS-derived build info only in release builds. In non-release builds (debug,
|
|
// bench, etc.), skip refreshing to preserve incremental compilation.
|
|
let profile = env::var("PROFILE").unwrap_or_default();
|
|
let refresh = profile == "release";
|
|
|
|
println!("cargo:rerun-if-env-changed=RUSTC");
|
|
println!("cargo:rerun-if-env-changed=GREPTIME_PRODUCT_NAME");
|
|
|
|
// The "CARGO_WORKSPACE_DIR" is set manually (not by Rust itself) in Cargo config file, to
|
|
// solve the problem where the "CARGO_MANIFEST_DIR" is not what we want when this repo is
|
|
// made as a submodule in another repo.
|
|
let workspace_dir =
|
|
env::var("CARGO_WORKSPACE_DIR").or_else(|_| env::var("CARGO_MANIFEST_DIR"))?;
|
|
let workspace_root = PathBuf::from(&workspace_dir);
|
|
println!(
|
|
"cargo:rerun-if-changed={}",
|
|
workspace_root.join("Cargo.toml").display()
|
|
);
|
|
|
|
let product_version = load_product_version(&workspace_root);
|
|
println!("cargo:rustc-env=GREPTIME_PRODUCT_VERSION={product_version}");
|
|
|
|
let product_name = load_product_name();
|
|
println!("cargo:rustc-env=GREPTIME_PRODUCT_NAME={product_name}");
|
|
|
|
let repository = open_repository(&workspace_root);
|
|
|
|
if refresh {
|
|
emit_workspace_watch_list(&workspace_root, repository.as_ref())?;
|
|
emit_git_watch_list(repository.as_ref());
|
|
}
|
|
|
|
let version_state = VersionState::collect(repository.as_ref());
|
|
|
|
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
|
|
let shadow_file = out_dir.join(SHADOW_FILE_NAME);
|
|
write_if_changed(&shadow_file, render_shadow_rs(&version_state))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct VersionState {
|
|
branch: String,
|
|
commit_hash: String,
|
|
short_commit: String,
|
|
git_clean: bool,
|
|
rust_version: String,
|
|
build_target: String,
|
|
}
|
|
|
|
impl VersionState {
|
|
fn collect(repository: Option<&Repository>) -> Self {
|
|
Self {
|
|
branch: git_branch(repository),
|
|
commit_hash: git_commit_hash(repository),
|
|
short_commit: git_short_commit(repository),
|
|
git_clean: git_clean(repository),
|
|
rust_version: rustc_version(),
|
|
build_target: env::var("TARGET").unwrap_or_default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn load_product_version(workspace_root: &Path) -> String {
|
|
let manifest =
|
|
Manifest::from_path(workspace_root.join("Cargo.toml")).expect("Failed to parse Cargo.toml");
|
|
|
|
manifest
|
|
.workspace
|
|
.as_ref()
|
|
.and_then(|w| {
|
|
w.metadata.as_ref().and_then(|m| {
|
|
m.get("greptime")
|
|
.and_then(|g| g.get("product_version").and_then(|v| v.as_str()))
|
|
})
|
|
})
|
|
.map(str::to_string)
|
|
.unwrap_or_else(|| env::var("CARGO_PKG_VERSION").unwrap())
|
|
}
|
|
|
|
fn load_product_name() -> String {
|
|
env::var("GREPTIME_PRODUCT_NAME").unwrap_or_else(|_| "GreptimeDB".to_string())
|
|
}
|
|
|
|
fn emit_workspace_watch_list(
|
|
workspace_root: &Path,
|
|
repository: Option<&Repository>,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
let mut watch_roots = BTreeSet::new();
|
|
|
|
if let Some(paths) = git_tracked_files(workspace_root, repository) {
|
|
watch_roots.extend(tracked_watch_roots(workspace_root, &paths));
|
|
}
|
|
|
|
if let Some(paths) = git_status_paths(workspace_root, repository) {
|
|
watch_roots.extend(tracked_watch_roots(workspace_root, &paths));
|
|
}
|
|
|
|
for path in watch_roots {
|
|
println!("cargo:rerun-if-changed={}", path.display());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn tracked_watch_roots(workspace_root: &Path, tracked_files: &[PathBuf]) -> BTreeSet<PathBuf> {
|
|
tracked_files
|
|
.iter()
|
|
.filter_map(|path| path.strip_prefix(workspace_root).ok())
|
|
.filter_map(|relative| {
|
|
relative
|
|
.components()
|
|
.next()
|
|
.map(|first| workspace_root.join(first))
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn git_tracked_files(
|
|
workspace_root: &Path,
|
|
repository: Option<&Repository>,
|
|
) -> Option<Vec<PathBuf>> {
|
|
let repository = repository?;
|
|
let index = match repository.index() {
|
|
Ok(index) => index,
|
|
Err(err) => {
|
|
cargo_warning(format!(
|
|
"Failed to read git index for build watch list: {err}. Git-derived build info may become stale."
|
|
));
|
|
return None;
|
|
}
|
|
};
|
|
|
|
Some(
|
|
index
|
|
.iter()
|
|
.filter_map(|entry| {
|
|
std::str::from_utf8(entry.path.as_ref())
|
|
.ok()
|
|
.map(|path| workspace_root.join(path))
|
|
})
|
|
.collect(),
|
|
)
|
|
}
|
|
|
|
fn git_status_paths(
|
|
workspace_root: &Path,
|
|
repository: Option<&Repository>,
|
|
) -> Option<Vec<PathBuf>> {
|
|
let repository = repository?;
|
|
let mut options = status_options();
|
|
|
|
match repository.statuses(Some(&mut options)) {
|
|
Ok(statuses) => Some(
|
|
statuses
|
|
.iter()
|
|
.filter_map(|entry| entry.path().map(|path| workspace_root.join(path)))
|
|
.collect(),
|
|
),
|
|
Err(err) => {
|
|
cargo_warning(format!(
|
|
"Failed to read git status for build watch list: {err}. Git-derived build info may become stale."
|
|
));
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
fn emit_git_watch_list(repository: Option<&Repository>) {
|
|
let Some(repository) = repository else {
|
|
return;
|
|
};
|
|
let git_dir = repository.path();
|
|
|
|
for path in [
|
|
git_dir.join("HEAD"),
|
|
git_dir.join("index"),
|
|
git_dir.join("packed-refs"),
|
|
] {
|
|
println!("cargo:rerun-if-changed={}", path.display());
|
|
}
|
|
|
|
if let Some(head_ref) = repository
|
|
.head()
|
|
.ok()
|
|
.and_then(|head| head.name().map(str::to_string))
|
|
.filter(|head_ref| head_ref.starts_with("refs/"))
|
|
{
|
|
println!(
|
|
"cargo:rerun-if-changed={}",
|
|
git_dir.join(head_ref).display()
|
|
);
|
|
}
|
|
}
|
|
|
|
fn render_shadow_rs(version_state: &VersionState) -> String {
|
|
format!(
|
|
"// Code automatically generated by src/common/version/build.rs, do not edit.\n\
|
|
pub const BRANCH: &str = {branch:?};\n\
|
|
pub const COMMIT_HASH: &str = {commit_hash:?};\n\
|
|
pub const SHORT_COMMIT: &str = {short_commit:?};\n\
|
|
pub const GIT_CLEAN: bool = {git_clean};\n\
|
|
pub const RUST_VERSION: &str = {rust_version:?};\n\
|
|
pub const BUILD_TARGET: &str = {build_target:?};\n",
|
|
branch = version_state.branch,
|
|
commit_hash = version_state.commit_hash,
|
|
short_commit = version_state.short_commit,
|
|
git_clean = version_state.git_clean,
|
|
rust_version = version_state.rust_version,
|
|
build_target = version_state.build_target,
|
|
)
|
|
}
|
|
|
|
fn write_if_changed(path: &Path, content: String) -> io::Result<()> {
|
|
if fs::read_to_string(path).ok().as_deref() == Some(content.as_str()) {
|
|
return Ok(());
|
|
}
|
|
|
|
fs::write(path, content)
|
|
}
|
|
|
|
fn open_repository(workspace_root: &Path) -> Option<Repository> {
|
|
match Repository::open_ext(
|
|
workspace_root,
|
|
RepositoryOpenFlags::NO_SEARCH,
|
|
std::iter::empty::<&Path>(),
|
|
) {
|
|
Ok(repository) => Some(repository),
|
|
Err(err) if err.code() == ErrorCode::NotFound => None,
|
|
Err(err) => {
|
|
cargo_warning(format!(
|
|
"Failed to open git repository at {}: {err}. Git-derived build info may be unavailable.",
|
|
workspace_root.display()
|
|
));
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
fn git_branch(repository: Option<&Repository>) -> String {
|
|
repository
|
|
.and_then(|repo| repo.head().ok())
|
|
.filter(|head| head.is_branch())
|
|
.and_then(|head| head.shorthand().map(str::to_string))
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn git_commit_hash(repository: Option<&Repository>) -> String {
|
|
repository
|
|
.and_then(|repo| head_target(Some(repo)))
|
|
.map(|oid| oid.to_string())
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn git_short_commit(repository: Option<&Repository>) -> String {
|
|
repository
|
|
.and_then(|repo| {
|
|
let object = repo.find_object(head_target(Some(repo))?, None).ok()?;
|
|
object.short_id().ok()?.as_str().map(str::to_string)
|
|
})
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn git_clean(repository: Option<&Repository>) -> bool {
|
|
let Some(repository) = repository else {
|
|
return false;
|
|
};
|
|
|
|
let mut options = status_options();
|
|
|
|
repository
|
|
.statuses(Some(&mut options))
|
|
.map(|statuses| statuses.is_empty())
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
fn head_target(repository: Option<&Repository>) -> Option<git2::Oid> {
|
|
repository?.head().ok()?.target()
|
|
}
|
|
|
|
fn rustc_version() -> String {
|
|
let rustc = env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
|
|
Command::new(rustc)
|
|
.arg("--version")
|
|
.output()
|
|
.ok()
|
|
.filter(|output| output.status.success())
|
|
.and_then(|output| String::from_utf8(output.stdout).ok())
|
|
.map(|stdout| stdout.trim().to_string())
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn status_options() -> StatusOptions {
|
|
let mut options = StatusOptions::new();
|
|
options
|
|
.include_untracked(true)
|
|
.recurse_untracked_dirs(true)
|
|
.renames_head_to_index(true);
|
|
options
|
|
}
|
|
|
|
fn cargo_warning(message: impl AsRef<str>) {
|
|
println!("cargo:warning={}", message.as_ref());
|
|
}
|