Skip to content
This repository has been archived by the owner on Jun 17, 2024. It is now read-only.

Commit

Permalink
feat: add generic project type
Browse files Browse the repository at this point in the history
  • Loading branch information
mistydemeo committed Nov 7, 2023
1 parent 1e160ee commit 8ef3615
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 7 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ required-features = ["cli"]
[features]
default = ["cli", "cargo-projects", "npm-projects"]
cli = ["axocli"]
cargo-projects = ["guppy", "semver"]
cargo-projects = ["guppy"]
npm-projects = ["oro-common", "oro-package-spec", "node-semver"]

[dependencies]
Expand All @@ -37,7 +37,7 @@ guppy = { version = "0.17.1", optional = true }
tracing = "0.1.37"
oro-common = { version = "0.3.14", optional = true }
serde = "1.0.159"
semver = { version = "1.0.17", optional = true }
semver = "1.0.17"
node-semver = { version = "2.1.0", optional = true }
oro-package-spec = { version = "0.3.14", optional = true }
thiserror = "1.0.40"
Expand Down
21 changes: 21 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,25 @@ pub enum AxoprojectError {
/// Version we were looking for
version: Version,
},

/// Indicates the package specification doesn't include the specified field.
/// Primarily occurs with generic packages, where this is manually specified
#[error("Manifest field {field} was not specified for this workspace!")]
ManifestFieldMissing {
/// Field name
field: String,
},

/// Incorrect type for a field when parsing dist.toml
#[error(
"Value in manifest field {field} has the wrong value; expected {expected}, got {actual}"
)]
ManifestWrongType {
/// Field name
field: String,
/// Expected type name
expected: String,
/// Actual type name
actual: String,
},
}
155 changes: 155 additions & 0 deletions src/generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//! Support for generic projects with cargo-dist build instructions

use axoasset::{toml_edit, SourceFile};
use camino::Utf8Path;

use crate::{PackageInfo, Result, Version, WorkspaceInfo, WorkspaceSearch};

/// Try to find a Cargo/Rust workspace at the given path
///
/// See [`crate::get_workspaces`][] for the semantics.
///
/// This relies on `cargo metadata` so will only work if you have `cargo` installed.
pub fn get_workspace(start_dir: &Utf8Path, clamp_to_dir: Option<&Utf8Path>) -> WorkspaceSearch {
let manifest_path = match crate::find_file("dist.toml", start_dir, clamp_to_dir) {
Ok(path) => path,
Err(e) => return WorkspaceSearch::Missing(e),
};

match workspace_from(&manifest_path) {
Ok(info) => WorkspaceSearch::Found(info),
Err(e) => WorkspaceSearch::Broken {
manifest_path,
cause: e,
},
}
}

fn workspace_from(manifest_path: &Utf8Path) -> Result<WorkspaceInfo> {
let workspace_dir = manifest_path.parent().unwrap().to_path_buf();
let root_auto_includes = crate::find_auto_includes(&workspace_dir)?;

let manifest = load_root_dist_toml(manifest_path)?;
let mut binaries = vec![];
let mut cstaticlibs = vec![];
let mut cdylibs = vec![];
let mut repository_url = None;
let mut name = String::new();
let mut version = None;
let mut build_command = None;

if let Some(package) = manifest.get("package").and_then(|t| t.as_table()) {
binaries = fetch_string_array(package, "binaries")?;
build_command = Some(fetch_string_array(package, "build-command")?);
let result = fetch_string_array(package, "cstaticlibs");
match result {
Ok(libs) => cstaticlibs = libs,
// If not specified, the default is fine
Err(crate::errors::AxoprojectError::ManifestFieldMissing { .. }) => {}
Err(err) => return Err(err),
}

let result = fetch_string_array(package, "cdylibs");
match result {
Ok(libs) => cdylibs = libs,
// If not specified, the default is fine
Err(crate::errors::AxoprojectError::ManifestFieldMissing { .. }) => {}
Err(err) => return Err(err),
}

if let Some(url) = package.get("repository") {
repository_url = url.as_str().map(|s| s.to_owned());
}
if let Some(n) = package.get("name") {
name = n.as_str().unwrap_or("").to_owned();
}
if let Some(v) = package.get("version") {
if let Some(s) = v.as_str() {
if let Ok(value) = semver::Version::parse(s) {
version = Some(Version::Generic(value))
}
}
}
};

let manifest_path = manifest_path.to_path_buf();

let package_info = PackageInfo {
manifest_path: manifest_path.clone(),
package_root: manifest_path.clone(),
name,
version,
// TODO
description: None,
// TODO
authors: vec![],
// TODO
license: None,
publish: true,
keywords: None,
repository_url: repository_url.clone(),
// TODO
homepage_url: None,
// TODO
documentation_url: None,
// TODO
readme_file: None,
// TODO
license_files: vec![],
// TODO
changelog_file: None,
binaries,
cstaticlibs,
cdylibs,
#[cfg(feature = "cargo-projects")]
cargo_metadata_table: None,
#[cfg(feature = "cargo-projects")]
cargo_package_id: None,
};

Ok(WorkspaceInfo {
kind: crate::WorkspaceKind::Generic,
target_dir: workspace_dir.clone(),
workspace_dir,
package_info: vec![package_info],
manifest_path,
repository_url,
root_auto_includes,
warnings: vec![],
build_command,
#[cfg(feature = "cargo-projects")]
cargo_metadata_table: None,
#[cfg(feature = "cargo-projects")]
cargo_profiles: crate::rust::CargoProfiles::new(),
})
}

fn fetch_string_array(table: &toml_edit::Table, field: &str) -> Result<Vec<String>> {
if let Some(array) = table.get(field) {
if !array.is_array() {
Err(crate::errors::AxoprojectError::ManifestWrongType {
field: field.to_owned(),
expected: "array".to_owned(),
actual: array.type_name().to_owned(),
})
} else {
Ok(array
.as_array()
.unwrap()
.into_iter()
.map(|b| b.as_str().unwrap_or("").to_owned())
.collect())
}
} else {
Err(crate::errors::AxoprojectError::ManifestFieldMissing {
field: field.to_owned(),
})
}
}

/// Load the root workspace toml into toml-edit form
pub fn load_root_dist_toml(manifest_path: &Utf8Path) -> Result<toml_edit::Document> {
let manifest_src = SourceFile::load_local(manifest_path)?;
let manifest = manifest_src.deserialize_toml_edit()?;
Ok(manifest)
}
1 change: 1 addition & 0 deletions src/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ fn read_workspace(manifest_path: &Utf8Path) -> Result<WorkspaceInfo> {
repository_url,
root_auto_includes,
warnings: vec![],
build_command: None,
#[cfg(feature = "cargo-projects")]
cargo_metadata_table: None,
#[cfg(feature = "cargo-projects")]
Expand Down
27 changes: 22 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub use guppy::PackageId;

pub mod changelog;
pub mod errors;
pub mod generic;
#[cfg(feature = "npm-projects")]
pub mod javascript;
pub mod platforms;
Expand All @@ -34,6 +35,8 @@ use crate::repo::GithubRepoInput;

/// Information about various kinds of workspaces
pub struct Workspaces {
/// Info about the generic workspace
pub generic: WorkspaceSearch,
/// Info about the cargo/rust workspace
#[cfg(feature = "cargo-projects")]
pub rust: WorkspaceSearch,
Expand All @@ -51,6 +54,8 @@ impl Workspaces {
let mut max_depth = 0;
let mut projects = vec![];

projects.push(self.generic);

// FIXME: should we provide feedback/logging here?
#[cfg(feature = "cargo-projects")]
projects.push(self.rust);
Expand Down Expand Up @@ -96,6 +101,8 @@ pub enum WorkspaceSearch {
/// Kind of workspace
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum WorkspaceKind {
/// generic cargo-dist compatible workspace
Generic,
/// cargo/rust workspace
#[cfg(feature = "cargo-projects")]
Rust,
Expand Down Expand Up @@ -137,6 +144,8 @@ pub struct WorkspaceInfo {
pub root_auto_includes: AutoIncludes,
/// Non-fatal issues that were encountered and should probably be reported
pub warnings: Vec<AxoprojectError>,
/// Build command to run for this workspace; not required for cargo
pub build_command: Option<Vec<String>>,
/// Raw cargo `[workspace.metadata]` table
#[cfg(feature = "cargo-projects")]
pub cargo_metadata_table: Option<serde_json::Value>,
Expand Down Expand Up @@ -312,6 +321,8 @@ pub struct PackageIdx(pub usize);
/// A Version abstracted over project type
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Version {
/// generic version (assumed to be semver)
Generic(semver::Version),
/// cargo version
#[cfg(feature = "cargo-projects")]
Cargo(semver::Version),
Expand All @@ -323,6 +334,7 @@ pub enum Version {
impl Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Version::Generic(v) => v.fmt(f),
#[cfg(feature = "cargo-projects")]
Version::Cargo(v) => v.fmt(f),
#[cfg(feature = "npm-projects")]
Expand All @@ -335,11 +347,11 @@ impl Version {
/// Assume it's a cargo Version
#[cfg(feature = "cargo-projects")]
pub fn cargo(&self) -> &semver::Version {
#[allow(irrefutable_let_patterns)]
if let Version::Cargo(v) = self {
v
} else {
panic!("Version wasn't in the cargo format")
#[allow(unreachable_patterns)]
match self {
// For now treat generic versions as cargo versions
Version::Cargo(v) | Version::Generic(v) => v,
_ => panic!("Version wasn't in the cargo format"),
}
}

Expand All @@ -357,6 +369,7 @@ impl Version {
/// Returns whether the version is stable (no pre/build component)
pub fn is_stable(&self) -> bool {
match self {
Version::Generic(v) => v.pre.is_empty() && v.build.is_empty(),
#[cfg(feature = "cargo-projects")]
Version::Cargo(v) => v.pre.is_empty() && v.build.is_empty(),
#[cfg(feature = "npm-projects")]
Expand All @@ -367,6 +380,9 @@ impl Version {
/// Gets a copy of the version with only the stable parts (pre/build components stripped)
pub fn stable_part(&self) -> Self {
match self {
Version::Generic(v) => {
Version::Generic(semver::Version::new(v.major, v.minor, v.patch))
}
#[cfg(feature = "cargo-projects")]
Version::Cargo(v) => Version::Cargo(semver::Version::new(v.major, v.minor, v.patch)),
#[cfg(feature = "npm-projects")]
Expand Down Expand Up @@ -411,6 +427,7 @@ pub struct AutoIncludes {
/// the top level [`WorkspaceKind`][].
pub fn get_workspaces(start_dir: &Utf8Path, clamp_to_dir: Option<&Utf8Path>) -> Workspaces {
Workspaces {
generic: generic::get_workspace(start_dir, clamp_to_dir),
#[cfg(feature = "cargo-projects")]
rust: rust::get_workspace(start_dir, clamp_to_dir),
#[cfg(feature = "npm-projects")]
Expand Down
7 changes: 7 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ fn real_main(app: &axocli::CliApp<Cli>) -> Result<(), Report> {
}

fn print_searches(workspaces: Workspaces) {
print_search(workspaces.generic, "generic");
#[cfg(feature = "cargo-projects")]
print_search(workspaces.rust, "rust");
#[cfg(feature = "npm-projects")]
Expand Down Expand Up @@ -158,6 +159,7 @@ fn print_workspace(project: &WorkspaceInfo) {
fn print_searches_json(root: Option<&Utf8Path>, workspaces: Workspaces) {
let output = JsonOutput {
root: root.map(|p| p.to_owned()),
generic: JsonWorkspaceSearch::from_real(root, workspaces.generic),
#[cfg(feature = "cargo-projects")]
rust: JsonWorkspaceSearch::from_real(root, workspaces.rust),
#[cfg(feature = "npm-projects")]
Expand All @@ -170,6 +172,7 @@ fn print_searches_json(root: Option<&Utf8Path>, workspaces: Workspaces) {
#[derive(Serialize, Deserialize)]
struct JsonOutput {
root: Option<Utf8PathBuf>,
generic: JsonWorkspaceSearch,
#[cfg(feature = "cargo-projects")]
rust: JsonWorkspaceSearch,
#[cfg(feature = "npm-projects")]
Expand Down Expand Up @@ -357,6 +360,9 @@ impl JsonRelPath {
/// Kind of workspace
#[derive(Debug, Serialize, Deserialize)]
pub enum JsonWorkspaceKind {
/// generic workspace
#[serde(rename = "generic")]
Generic,
/// cargo/rust workspace
#[cfg(feature = "cargo-projects")]
#[serde(rename = "rust")]
Expand All @@ -370,6 +376,7 @@ pub enum JsonWorkspaceKind {
impl JsonWorkspaceKind {
fn from_real(real: WorkspaceKind) -> Self {
match real {
WorkspaceKind::Generic => Self::Generic,
#[cfg(feature = "cargo-projects")]
WorkspaceKind::Rust => Self::Rust,
#[cfg(feature = "npm-projects")]
Expand Down
1 change: 1 addition & 0 deletions src/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ fn workspace_info(pkg_graph: &PackageGraph) -> Result<WorkspaceInfo> {
cargo_profiles,

warnings,
build_command: None,
})
}

Expand Down
16 changes: 16 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,3 +559,19 @@ fn test_changelog_errors() {
Err(AxoprojectError::ParseChangelog(..))
));
}

#[test]
fn test_generic_c() {
let project = crate::get_workspaces("tests/projects/generic-c/".into(), None)
.best()
.unwrap();
assert_eq!(project.kind, WorkspaceKind::Generic);
assert_eq!(project.package_info.len(), 1);

let package = &project.package_info[0];
assert_eq!(package.name, "testprog");
assert_eq!(package.binaries.len(), 1);

let binary = &package.binaries[0];
assert_eq!(binary, "main");
}
13 changes: 13 additions & 0 deletions tests/projects/generic-c/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CC := gcc
RM := rm
EXEEXT :=

all: main$(EXEEXT)

main$(EXEEXT):
$(CC) main.c -o main$(EXEEXT)

clean:
$(RM) -f main$(EXEEXT)

.PHONY: all clean
Loading

0 comments on commit 8ef3615

Please sign in to comment.