#![allow(dead_code)]
pub mod analytics;
pub mod archive;
pub mod cache;
pub mod cmake;
pub mod cmake_templates;
pub mod docker;
pub mod elf;
pub mod incremental;
pub mod platforms;
pub mod toolchains;
pub mod verinfo;
use std::collections::HashSet;
use std::path::PathBuf;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use crate::commands::build::{BuildTarget, LinkType, WindowsToolchain};
use crate::config::CcgoConfig;
#[derive(Debug, Clone)]
pub struct BuildOptions {
pub target: BuildTarget,
pub architectures: Vec<String>,
pub link_type: LinkType,
pub use_docker: bool,
pub auto_docker: bool,
pub jobs: Option<usize>,
pub ide_project: bool,
pub release: bool,
pub native_only: bool,
pub toolchain: WindowsToolchain,
pub verbose: bool,
pub dev: bool,
pub features: Vec<String>,
pub use_default_features: bool,
pub all_features: bool,
pub cache: Option<String>,
pub analytics: bool,
}
impl Default for BuildOptions {
fn default() -> Self {
Self {
target: BuildTarget::Linux,
architectures: Vec::new(),
link_type: LinkType::Both,
use_docker: false,
auto_docker: false,
jobs: None,
ide_project: false,
release: true,
native_only: false,
toolchain: WindowsToolchain::Auto,
verbose: false,
dev: false,
features: Vec::new(),
use_default_features: true,
all_features: false,
cache: None,
analytics: false,
}
}
}
#[derive(Debug)]
pub struct BuildContext {
pub project_root: PathBuf,
pub config: CcgoConfig,
pub options: BuildOptions,
pub cmake_build_dir: PathBuf,
pub output_dir: PathBuf,
pub git_version: Option<crate::utils::git_version::GitVersion>,
}
impl BuildContext {
pub fn new(project_root: PathBuf, config: CcgoConfig, options: BuildOptions) -> Self {
let platform_name = options.target.to_string().to_lowercase();
let release_subdir = if options.release { "release" } else { "debug" };
let cmake_build_dir = project_root
.join("cmake_build")
.join(release_subdir)
.join(&platform_name);
let output_dir = project_root
.join("target")
.join(release_subdir)
.join(&platform_name);
let package = config.package.as_ref()
.expect("Build requires [package] section in CCGO.toml");
let git_version = crate::utils::git_version::GitVersion::from_project_root(
&project_root,
&package.version,
).ok();
if let Some(header_rel) = config
.build
.as_ref()
.and_then(|b| b.verinfo_path.as_deref())
{
if let Some(gv) = git_version.as_ref() {
let identity = gv.veridentity(&package.version);
let source_rel = config
.build
.as_ref()
.and_then(|b| b.verinfo_source_path.as_deref());
let _ = verinfo::generate(
&project_root,
header_rel,
source_rel,
&package.name,
&identity,
);
}
}
Self {
project_root,
config,
options,
cmake_build_dir,
output_dir,
git_version,
}
}
pub fn lib_name(&self) -> &str {
&self.config.package.as_ref()
.expect("Build requires [package] section")
.name
}
pub fn include_source_dir(&self) -> PathBuf {
if let Some(src) = self.config.include.as_ref().and_then(|c| c.src.as_deref()) {
self.project_root.join(src)
} else {
self.project_root.join("include")
}
}
pub fn version(&self) -> &str {
&self.config.package.as_ref()
.expect("Build requires [package] section")
.version
}
pub fn publish_suffix(&self) -> &str {
""
}
pub fn version_with_suffix(&self) -> &str {
self.version()
}
pub fn jobs(&self) -> usize {
self.options.jobs.unwrap_or_else(num_cpus)
}
pub fn deps_map(&self) -> Option<String> {
let submodule_deps = self.config.build.as_ref()
.map(|b| &b.submodule_deps)
.filter(|deps| !deps.is_empty())?;
let mut deps_list = Vec::new();
for (module, deps) in submodule_deps {
if !deps.is_empty() {
deps_list.push(module.clone());
deps_list.push(deps.join(","));
}
}
if deps_list.is_empty() {
None
} else {
Some(deps_list.join(";"))
}
}
pub fn symbol_visibility(&self) -> u8 {
if self.config.build.as_ref().map(|b| b.symbol_visibility).unwrap_or(false) {
1
} else {
0
}
}
pub fn resolved_features(&self) -> Result<HashSet<String>> {
let features_config = &self.config.features;
if self.options.all_features {
let mut all = HashSet::new();
for name in features_config.feature_names() {
features_config.resolve_feature(name, &mut all)?;
}
features_config.resolve_feature("default", &mut all)?;
Ok(all)
} else {
features_config.resolve_features(
&self.options.features,
self.options.use_default_features,
)
}
}
pub fn cmake_feature_defines(&self) -> Result<String> {
let resolved = self.resolved_features()?;
let defines: Vec<String> = resolved
.iter()
.filter(|f| !f.contains('/')) .map(|f| format!("CCGO_FEATURE_{}", f.to_uppercase().replace('-', "_")))
.collect();
Ok(defines.join(";"))
}
pub fn enabled_dependencies(&self) -> Result<Vec<&crate::config::DependencyConfig>> {
let resolved = self.resolved_features()?;
Ok(self.config.features.get_enabled_optional_deps(&resolved, &self.config.dependencies))
}
pub fn ccgo_cmake_dir(&self) -> Option<PathBuf> {
if let Ok(cmake_dir) = cmake_templates::get_cmake_dir() {
return Some(cmake_dir);
}
if let Ok(dir) = std::env::var("CCGO_CMAKE_DIR") {
let path = PathBuf::from(dir);
if path.exists() {
return Some(path);
}
}
if let Ok(exe) = std::env::current_exe() {
if let Some(ccgo_rs_root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
let cmake_dir = ccgo_rs_root
.parent()
.map(|p| p.join("ccgo/ccgo/build_scripts/cmake"));
if let Some(ref dir) = cmake_dir {
if dir.exists() {
return cmake_dir;
}
}
}
}
if let Ok(output) = std::process::Command::new("pip3")
.args(["show", "-f", "ccgo"])
.output()
{
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if let Some(location) = line.strip_prefix("Location: ") {
let cmake_dir = PathBuf::from(location.trim())
.join("ccgo/build_scripts/cmake");
if cmake_dir.exists() {
return Some(cmake_dir);
}
}
}
}
}
None
}
pub fn compiler_cache(&self) -> Option<cache::CacheConfig> {
let cache_option = self.options.cache.as_ref()?;
match cache_option.as_str() {
"none" | "disabled" | "off" => None,
"auto" => cache::CacheConfig::auto().ok(),
"ccache" => cache::CacheConfig::with_type(cache::CacheType::CCache).ok(),
"sccache" => cache::CacheConfig::with_type(cache::CacheType::SCache).ok(),
_ => {
eprintln!("⚠️ Unknown cache type '{}', using auto-detection", cache_option);
cache::CacheConfig::auto().ok()
}
}
}
pub fn config_hash(&self) -> Result<String> {
let config_path = self.project_root.join("CCGO.toml");
incremental::BuildState::hash_config(&config_path)
}
pub fn options_hash(&self) -> String {
let options_str = format!(
"target={:?},arch={:?},link={:?},release={},jobs={:?},features={:?},cache={:?}",
self.options.target,
self.options.architectures,
self.options.link_type,
self.options.release,
self.options.jobs,
self.options.features,
self.options.cache
);
incremental::BuildState::hash_options(&options_str)
}
pub fn create_incremental_analyzer(
&self,
link_type: &str,
) -> Result<incremental::IncrementalAnalyzer> {
let config_hash = self.config_hash()?;
let options_hash = self.options_hash();
incremental::IncrementalAnalyzer::new(
&self.cmake_build_dir,
self.lib_name().to_string(),
self.options.target.to_string(),
link_type.to_string(),
config_hash,
options_hash,
)
}
}
fn num_cpus() -> usize {
std::thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(4)
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BuildInfo {
pub project: String,
pub platform: String,
pub version: String,
pub link_type: String,
pub build_time: String,
pub build_host: String,
pub architectures: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub toolchain: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub git_commit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub git_branch: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BuildInfoFull {
pub build_metadata: BuildMetadata,
pub project: ProjectInfo,
pub git: GitInfo,
pub build: BuildDetails,
pub environment: EnvironmentInfo,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BuildMetadata {
pub version: String,
pub generated_at: String,
pub generator: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ProjectInfo {
pub name: String,
pub version: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GitInfo {
pub branch: String,
pub revision: String,
pub revision_full: String,
pub tag: String,
pub is_dirty: bool,
pub remote_url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BuildDetails {
pub time: String,
pub timestamp: i64,
pub platform: String,
#[serde(flatten)]
pub platform_info: Option<PlatformBuildInfo>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PlatformBuildInfo {
Ios { ios: IosBuildInfo },
Android { android: AndroidBuildInfo },
Macos { macos: MacosBuildInfo },
}
#[derive(Debug, Serialize, Deserialize)]
pub struct IosBuildInfo {
pub xcode_version: String,
pub xcode_build: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AndroidBuildInfo {
pub ndk_version: String,
pub stl: String,
pub min_sdk_version: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MacosBuildInfo {
pub xcode_version: String,
pub xcode_build: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct EnvironmentInfo {
pub os: String,
pub os_version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub python_version: Option<String>,
pub ccgo_version: String,
}
pub trait PlatformBuilder {
fn platform_name(&self) -> &str;
fn default_architectures(&self) -> Vec<String>;
fn validate_prerequisites(&self, ctx: &BuildContext) -> Result<()>;
fn build(&self, ctx: &BuildContext) -> Result<BuildResult>;
fn clean(&self, ctx: &BuildContext) -> Result<()>;
}
#[derive(Debug)]
pub struct BuildResult {
pub sdk_archive: PathBuf,
pub symbols_archive: Option<PathBuf>,
pub aar_archive: Option<PathBuf>,
pub duration_secs: f64,
pub architectures: Vec<String>,
}