use crate::args::Args;
use crate::artifact::{Artifact, ArtifactType};
use crate::error::{Error, Result};
use crate::manifest::Manifest;
use crate::profile::Profile;
use crate::{utils, CrateType, LocalizedConfig};
use std::collections::HashMap;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct Subcommand {
args: Args,
package: String,
workspace_manifest: Option<PathBuf>,
manifest: PathBuf,
target_dir: PathBuf,
host_triple: String,
profile: Profile,
lib_artifact: Option<Artifact>,
bin_artifacts: Vec<Artifact>,
example_artifacts: Vec<Artifact>,
config: Option<LocalizedConfig>,
}
impl Subcommand {
pub fn new(args: Args) -> Result<Self> {
assert!(
args.package.len() < 2,
"Multiple packages are not supported yet by `cargo-subcommand`"
);
let package = args.package.get(0).map(|s| s.as_str());
assert!(
!args.workspace,
"`--workspace` is not supported yet by `cargo-subcommand`"
);
assert!(
args.exclude.is_empty(),
"`--exclude` is not supported yet by `cargo-subcommand`"
);
let manifest_path = args
.manifest_path
.clone()
.map(|path| {
if path.file_name() != Some(OsStr::new("Cargo.toml")) || !path.is_file() {
Err(Error::ManifestPathNotFound)
} else {
Ok(path)
}
})
.transpose()?;
let search_path = manifest_path.map_or_else(
|| std::env::current_dir().map_err(|e| Error::Io(PathBuf::new(), e)),
|manifest_path| utils::canonicalize(manifest_path.parent().unwrap()),
)?;
let potential_manifest = utils::find_manifest(&search_path)?;
let workspace_manifest = utils::find_workspace(&search_path)?;
let (manifest_path, manifest) = {
if let Some(workspace_manifest) = &workspace_manifest {
utils::find_package_manifest_in_workspace(
workspace_manifest,
potential_manifest,
package,
)?
} else {
let (manifest_path, manifest) = potential_manifest;
manifest.map_nonvirtual_package(manifest_path, package)?
}
};
let package = &manifest.package.as_ref().unwrap().name;
let root_dir = manifest_path.parent().unwrap();
let config = LocalizedConfig::find_cargo_config_for_workspace(root_dir)?;
if let Some(config) = &config {
config.set_env_vars().unwrap();
}
let parsed_manifest = Manifest::parse_from_toml(&manifest_path)?;
let target_dir = args
.target_dir
.clone()
.or_else(|| {
std::env::var_os("CARGO_BUILD_TARGET_DIR")
.or_else(|| std::env::var_os("CARGO_TARGET_DIR"))
.map(|os_str| os_str.into())
})
.map(|target_dir| {
if target_dir.is_relative() {
std::env::current_dir().unwrap().join(target_dir)
} else {
target_dir
}
});
let target_dir = target_dir.unwrap_or_else(|| {
workspace_manifest
.as_ref()
.map(|(path, _)| path)
.unwrap_or_else(|| &manifest_path)
.parent()
.unwrap()
.join(utils::get_target_dir_name(config.as_deref()).unwrap())
});
let main_bin_path = Path::new("src/main.rs");
let main_lib_path = Path::new("src/lib.rs");
let mut bin_artifacts = HashMap::new();
let mut example_artifacts = HashMap::new();
fn find_main_file(dir: &Path, name: &str) -> Option<PathBuf> {
let alt_path = dir.join(format!("{}.rs", name));
alt_path.is_file().then_some(alt_path).or_else(|| {
let alt_path = dir.join(name).join("main.rs");
alt_path.is_file().then_some(alt_path)
})
}
for bin in &parsed_manifest.bins {
let path = bin
.path
.clone()
.or_else(|| find_main_file(&root_dir.join("src/bin"), &bin.name))
.ok_or_else(|| Error::BinNotFound(bin.name.clone()))?;
let prev = bin_artifacts.insert(
bin.name.clone(),
Artifact {
name: bin.name.clone(),
path,
r#type: ArtifactType::Bin,
},
);
if prev.is_some() {
return Err(Error::DuplicateBin(bin.name.clone()));
}
}
for example in &parsed_manifest.examples {
let path = example
.path
.clone()
.or_else(|| find_main_file(&root_dir.join("examples"), &example.name))
.ok_or_else(|| Error::ExampleNotFound(example.name.clone()))?;
let prev = example_artifacts.insert(
example.name.clone(),
Artifact {
name: example.name.clone(),
path,
r#type: ArtifactType::Example,
},
);
if prev.is_some() {
return Err(Error::DuplicateExample(example.name.clone()));
}
}
fn insert_if_unconfigured(
name: Option<String>,
path: &Path,
r#type: ArtifactType,
artifacts: &mut HashMap<String, Artifact>,
) {
if artifacts.values().any(|bin| bin.path == path) {
println!("Already configuring {path:?}");
return;
}
let name =
name.unwrap_or_else(|| path.file_stem().unwrap().to_str().unwrap().to_owned());
artifacts.entry(name.clone()).or_insert(Artifact {
name,
path: path.to_owned(),
r#type,
});
}
if parsed_manifest
.package
.as_ref()
.map_or(true, |p| p.autobins)
{
if root_dir.join(main_bin_path).is_file() {
insert_if_unconfigured(
Some(package.clone()),
main_bin_path,
ArtifactType::Bin,
&mut bin_artifacts,
);
}
for file in utils::list_rust_files(&root_dir.join("src").join("bin"))? {
let file = file.strip_prefix(root_dir).unwrap();
insert_if_unconfigured(None, file, ArtifactType::Bin, &mut bin_artifacts);
}
}
if parsed_manifest
.package
.as_ref()
.map_or(true, |p| p.autoexamples)
{
for file in utils::list_rust_files(&root_dir.join("examples"))? {
let file = file.strip_prefix(root_dir).unwrap();
insert_if_unconfigured(None, file, ArtifactType::Example, &mut example_artifacts);
}
}
let mut lib_artifact = parsed_manifest
.lib
.as_ref()
.map(|lib| Artifact {
name: lib.name.as_ref().unwrap_or(package).clone(),
path: lib.path.as_deref().unwrap_or(main_lib_path).to_owned(),
r#type: ArtifactType::Lib,
})
.or_else(|| {
root_dir.join(main_lib_path).is_file().then(|| Artifact {
name: package.clone(),
path: main_lib_path.to_owned(),
r#type: ArtifactType::Lib,
})
});
let specific_target_selected = args.specific_target_selected();
if specific_target_selected {
if !args.lib {
lib_artifact = None;
}
if !args.bins {
bin_artifacts.retain(|a, _| args.bin.contains(a));
}
if !args.examples {
example_artifacts.retain(|a, _| args.example.contains(a));
}
}
let host_triple = current_platform::CURRENT_PLATFORM.to_owned();
let profile = args.profile();
Ok(Self {
args,
package: package.clone(),
workspace_manifest: workspace_manifest.map(|(path, _)| path),
manifest: manifest_path,
target_dir,
host_triple,
profile,
lib_artifact,
bin_artifacts: bin_artifacts.into_values().collect(),
example_artifacts: example_artifacts.into_values().collect(),
config,
})
}
pub fn args(&self) -> &Args {
&self.args
}
pub fn package(&self) -> &str {
&self.package
}
pub fn workspace_manifest(&self) -> Option<&Path> {
self.workspace_manifest.as_deref()
}
pub fn manifest(&self) -> &Path {
&self.manifest
}
pub fn target(&self) -> Option<&str> {
self.args.target.as_deref()
}
pub fn profile(&self) -> &Profile {
&self.profile
}
pub fn artifacts(&self) -> impl Iterator<Item = &Artifact> {
self.lib_artifact
.iter()
.chain(&self.bin_artifacts)
.chain(&self.example_artifacts)
}
pub fn target_dir(&self) -> &Path {
&self.target_dir
}
pub fn host_triple(&self) -> &str {
&self.host_triple
}
pub fn quiet(&self) -> bool {
self.args.quiet
}
pub fn config(&self) -> Option<&LocalizedConfig> {
self.config.as_ref()
}
pub fn build_dir(&self, target: Option<&str>) -> PathBuf {
let target_dir = dunce::simplified(self.target_dir());
let arch_dir = if let Some(target) = target {
target_dir.join(target)
} else {
target_dir.to_path_buf()
};
arch_dir.join(self.profile())
}
pub fn artifact(
&self,
artifact: &Artifact,
target: Option<&str>,
crate_type: CrateType,
) -> PathBuf {
let triple = target.unwrap_or_else(|| self.host_triple());
let file_name = artifact.file_name(crate_type, triple);
self.build_dir(target)
.join(artifact.build_dir())
.join(file_name)
}
}