Skip to content

Commit

Permalink
documenting and testing, still lots to do
Browse files Browse the repository at this point in the history
  • Loading branch information
AverseABFun committed Dec 21, 2024
1 parent 9c4fb96 commit 6b6b1f1
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 29 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "rust-pkg-gen"
version = "0.1.0"
edition = "2021"
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
repository = "https://github.com/AverseABFun/rust-pkg-gen"
homepage = "https://github.com/AverseABFun/rust-pkg-gen"
authors = ["Arthur Beck <[email protected]>"]
Expand Down
2 changes: 1 addition & 1 deletion rust-config.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[x64_package_linux_rust_pkg_gen]
toolchains = [
{ edition = "2021", channel = "nightly", profile = "complete", components = [
{ edition = "2021", channel = "nightly", components = [
"rustfmt",
"rustc",
"cargo",
Expand Down
48 changes: 41 additions & 7 deletions src/copied.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copied from crate rustup-mirror(https://github.com/jiegec/rustup-mirror)
// Some modifications made due to deprecated/changed APIs
// (and differing purposes/necessities)
//! Copied from crate [rustup-mirror](https://crates.io/crates/rustup-mirror/0.8.1)
//! Some modifications made due to deprecated/changed APIs
//! (and differing purposes/necessities).
//!
//! Note that I(the maintainer of rust-pkg-gen) wrote the doc comments, NOT
//! the maintainer of [rustup-mirror](https://crates.io/crates/rustup-mirror/0.8.1).
use anyhow::{anyhow, Error};
use filebuffer::FileBuffer;
Expand All @@ -12,8 +15,11 @@ use std::path::{Component, Path, PathBuf};
use toml::Value;
use url::Url;

/// The default upstream URL. Usually passed to [`download`] or [`download_all`]
/// when you don't have a custom upstream url to use.
pub const DEFAULT_UPSTREAM_URL: &str = "https://static.rust-lang.org/";

/// Produces the SHA256 hash of the provided file
fn file_sha256(file_path: &Path) -> Option<String> {
let file = Path::new(file_path);
if file.exists() {
Expand All @@ -24,6 +30,7 @@ fn file_sha256(file_path: &Path) -> Option<String> {
}
}

/// Download a path from the provided upstream URL.
fn download(upstream_url: &str, dir: &str, path: &str) -> Result<PathBuf, Error> {
println!("Downloading file {}...", path);
let manifest = format!("{}{}", upstream_url, path);
Expand All @@ -50,6 +57,7 @@ fn download(upstream_url: &str, dir: &str, path: &str) -> Result<PathBuf, Error>
Ok(mirror.join(path))
}

/// I'm honestly unsure what this one does. If you know, please submit an issue or PR!
pub fn normalize_path(path: &Path) -> PathBuf {
let mut components = path.components().peekable();
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
Expand Down Expand Up @@ -77,6 +85,13 @@ pub fn normalize_path(path: &Path) -> PathBuf {
ret
}

/// This is the beefy function. It takes an absurd number of arguments
/// and based on them downloads a certain subset of the rust components
/// that are relevant.
///
/// I changed this one from the original crate a *lot*. This is based
/// on part of the main function in the original crate with many more
/// validations and miscellaneous changes.
pub fn download_all(
channels: Vec<&str>,
upstream_url: &str,
Expand All @@ -87,22 +102,40 @@ pub fn download_all(
for_targets: Vec<&str>,
quiet: bool,
format_map: HashMap<&str, Vec<crate::Format>>,
) {
) -> Option<Error> {
for channel in channels.clone() {
if !crate::targets::RELEASE_CHANNELS.contains(&channel) {
return;
return Some(anyhow!("invalid channel"));
}
}
for target in targets.clone() {
if !crate::targets::TARGETS.contains(&target) {
return;
return Some(anyhow!("invalid rust target"));
}
}
for target in for_targets.clone() {
if !crate::targets::TARGETS.contains(&target) {
return;
return Some(anyhow!("invalid compilation target"));
}
}
for (target, formats) in format_map {
if !targets.contains(&target) {
return Some(anyhow!("target that is not being built for in target map"));
}
for format in formats {
if !vec![
String::from("msi"),
String::from("pkg"),
String::from("gz"),
String::from("xz"),
]
.contains(&format.format)
{
return Some(anyhow!("invalid format {}", format.format));
}
}
}

let mut all_targets = HashSet::new();

// All referenced files
Expand Down Expand Up @@ -336,4 +369,5 @@ pub fn download_all(
println!("Producing /{}", alt_sha256_new_file_name);
}
}
None
}
171 changes: 158 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,52 @@
#![warn(missing_docs)]

//! Main file for the crate. Contains most of the public API,
//! outside of mainly the code copied from [rustup-mirror](https://github.com/jiegec/rustup-mirror)
//! and the resources included in the output.
use anyhow::{anyhow, Error};
use serde::Deserialize;
use std::{collections::HashMap, fs, path::Path};

pub mod copied;
pub mod resources;
pub mod targets;
pub mod tests;
#[cfg(test)]
mod tests;

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
/// Contains all relevant information for a toolchain
///
/// `rust-config.toml` contains a list of these under `toolchains`
/// in each config.
pub struct Toolchain {
/// The edition of rust to use. Should be one of 2015, 2018, 2021, or 2024,
/// but isn't directly validated. Validation could be changed in the future.
pub edition: String,
/// The channel of rust to use. Should be in [`targets::RELEASE_CHANNELS`].
pub channel: String,
pub profile: String,
/// The components of rust to install.
pub components: Vec<String>,
/// The ID used to index into the [`Crates`] instance associated with the
/// rust config(technically [`RustConfigInner`], but whatever).
pub crate_id: String,
/// The list of targets to provide the rust components for.
pub platforms: Vec<String>,
/// The list of targets to allow the [`platforms`](Toolchain::platforms) to build for.
pub targets: Vec<String>,
/// A map of [`platforms`](Toolchain::platforms) to format IDs. Format IDs are used to
/// index into the [rust config's format list](RustConfigInner::formats).
pub format_map: HashMap<String, String>,
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
/// The suffix used for [Formats](Format).
pub enum Suffix {
/// Use the format if it's available,
/// but continue if it's not.
IfAvailable,
/// Use the provided format or stop building the package.
Only,
}

Expand All @@ -46,47 +71,153 @@ impl<'de> serde::de::Visitor<'de> for StringVisitor {
}
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
/// A format that includes one of the allowed [formats](FORMATS)
/// and a [`Suffix`].
pub struct Format {
/// The actual format. One of [`FORMATS`].
pub format: String,
/// The suffix. See [the type documentation](Suffix) for information about the valid values.
pub suffix: Suffix,
}

impl<'de> Deserialize<'de> for Format {
fn deserialize<D>(deserializer: D) -> Result<Format, D::Error>
where
D: serde::Deserializer<'de>,
{
let base_string = deserializer.deserialize_str(StringVisitor)?;
/// Slice of the formats that can be used in a [`Format`].
pub const FORMATS: [&str; 4] = ["msi", "pkg", "gz", "xz"];

impl Format {
/// this function is a basic wrapper around and thus
/// has the same semantic meaning as [`Format::from_string`]
pub fn from_str(base_string: &str) -> Result<Format, Error> {
Format::from_string(base_string.to_string())
}
/// this function is a basic wrapper around and thus
/// has the same semantic meaning as [`Format::from_string_no_err`]
pub fn from_str_no_err(base_string: &str) -> Format {
Format::from_string_no_err(base_string.to_string())
}
/// this function takes in a `String` and produces a [`Format`] if it's
/// valid. if not, it returns an [`anyhow::Error`].
///
/// any valid format has to match the regex:
///
/// `(?:(?:msi)|(?:pkg)|(?:gz)|(?:xz))(?:(?:-if-available)|(?:-only))?`
///
/// so `msi` or `gz-only` are valid but `dfsjj-only` isn't
///
/// (the mentioned regex is not internally used)
///
/// [`FORMATS`] is a slice of valid formats(msi, pkg, gz, and xz)
///
/// you can use [`Format::from_str`] if you want to convert a `&str`
/// to a Format, or [`Format::from_string_no_err`] if your application
/// requires that there not be an error case.
///
/// the reason you wouldn't just call [`Result::unwrap`] to remove the
/// error case is that `from_string_no_err` also allows cases that
/// this method would not. (for example, `from_string_no_err`
/// allows the aforementioned case of `dfsjj-only` or even
/// `dfsjj-ndfdsdf`)
pub fn from_string(base_string: String) -> Result<Format, Error> {
let split = base_string.split_once("-").unwrap_or((&base_string, ""));
let suffix = split.1.to_string();
let real_suffix = match suffix.as_str() {
"-only" => Suffix::Only,
_ => Suffix::IfAvailable,
"only" => Suffix::Only,
"" | "if-available" => Suffix::IfAvailable,
_ => return Err(anyhow!("invalid suffix {}", suffix)),
};
if !FORMATS.contains(&split.0) {
return Err(anyhow!("invalid format {}", split.0));
}
Ok(Format {
format: split.0.to_string(),
suffix: real_suffix,
})
}
/// see [`Format::from_string`] for the usage, this is virtually
/// identical however doesn't return a `Result`.
///
/// note that this method will report a value for any string(so anything
/// that matches `.*`) but will divide on the first occurance of `-`.
/// see the following code:
///
/// ```
/// # use rust_pkg_gen::{Format,Suffix};
/// let test_string = "test-ah-yes".to_string();
/// assert_eq!(Format::from_string_no_err(test_string),
/// Format {
/// format: "test".to_string(),
/// suffix: Suffix::IfAvailable
/// }
/// );
/// ```
///
/// keep this in mind when using this method. thus the regex `([\w&&[^-]]+)((?:-.*)?)`
/// matches the output where capture group one is the format and capture group two
/// is the suffix. however, if the suffix is not "only", then it will output
/// [`Suffix::IfAvailable`].
///
/// THIS REGEX MAY OR MAY NOT BE KEPT UP-TO-DATE AS IT IS NOT USED INTERNALLY
///
/// the reason that this method was implemented was to solve the problem
/// of constructing an error for Format::deserialize.
///
/// I don't like that we had to implement it, but it's kind of necessary.
/// If you have any ideas, please please please open an issue or PR!
pub fn from_string_no_err(base_string: String) -> Format {
let split = base_string.split_once("-").unwrap_or((&base_string, ""));
let suffix = split.1.to_string();
let real_suffix = match suffix.as_str() {
"only" => Suffix::Only,
_ => Suffix::IfAvailable,
};
Format {
format: split.0.to_string(),
suffix: real_suffix,
}
}
}

impl<'de> Deserialize<'de> for Format {
/// Deserialize this value from the given Serde deserializer.
fn deserialize<D>(deserializer: D) -> Result<Format, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(Format::from_string_no_err(
deserializer.deserialize_str(StringVisitor)?,
))
}
}

#[derive(Deserialize, Debug, Clone)]
#[serde(untagged)]
/// A crate(used in [a rust config's crates value](RustConfigInner::crates)).
///
/// Can be any valid value that can be put in a Cargo.toml's dependency section.
///
/// Doesn't include the crate's name, which is assumed to be placed elsewhere.
pub enum Crate {
/// A basic version. What is generally seen in most Cargo.toml's.
Version(String),
/// Detailed information about the dependency. Can include a version,
/// features, a path, and/or a git repository.
Detailed {
/// The version. Generally a semver.
version: Option<String>,
/// The required features.
features: Option<Vec<String>>,
/// The path to the crate.
path: Option<String>,
/// The git repository of the crate.
git: Option<String>,
},
}

impl Crate {
/// Serializes the [`Crate`] to the standard format used in a Cargo.toml.
pub fn serialize(self) -> String {
if let Crate::Version(str) = self {
return str;
return format!("\"{}\"", str);
} else {
let Crate::Detailed {
version,
Expand Down Expand Up @@ -119,17 +250,31 @@ impl Crate {
}
}

/// Many crates. The key for the outer HashMap is
/// a crate ID, and the key for the inner HashMap
/// is a crate.
pub type Crates = HashMap<String, HashMap<String, Crate>>;

#[derive(Deserialize, Debug, Clone)]
/// The actual Rust config. Referred to simply by "Rust config"
/// throughout this documentation. The entrypoint to deserializing
/// a `rust-config.toml` file's individual configs.
pub struct RustConfigInner {
/// A list of toolchains.
pub toolchains: Vec<Toolchain>,
/// A list of crates.
pub crates: Crates,
/// A list of formats.
pub formats: HashMap<String, Vec<Format>>,
}

/// A Rust config file. The entrypoint to deserializing a
/// `rust-config.toml` file.
pub type RustConfig = HashMap<String, RustConfigInner>;

/// Parse a `rust-config.toml` file. Simply reads a path and parses it as toml.
///
/// Currently panics upon an error; May change in the future.
pub fn parse_file(path: &Path) -> RustConfig {
toml::from_str(&fs::read_to_string(path).unwrap()).unwrap()
}
Loading

0 comments on commit 6b6b1f1

Please sign in to comment.