Skip to content

Commit

Permalink
Merge pull request #5 from WyvernIXTL/feature/cli
Browse files Browse the repository at this point in the history
Feature: API Change to Support Usage for Programs Outside of Build Scripts
  • Loading branch information
WyvernIXTL authored Jan 27, 2025
2 parents 7b8154c + dfd71d9 commit 2c46635
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 91 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "license-fetcher"
description = "Fetch licenses of dependencies at build time and embed them into your program."
authors = ["Adam McKellar <[email protected]>"]
version = "0.5.0"
version = "0.6.0"
edition = "2021"
readme = "README.md"
repository = "https://github.com/WyvernIXTL/license-fetcher"
Expand All @@ -14,7 +14,7 @@ keywords = ["license", "embed", "fetch", "about", "find"]
bincode = "=2.0.0-rc.3"
directories = {version = "5.0.1", optional = true}
log = { version = "0.4.22", optional = true }
miniz_oxide = { version = "0.8.0", optional = true }
miniz_oxide = { version = "0.8.0", optional = true, features = ["std"]}
once_cell = { version = "1.19.0", optional = true }
regex = { version = "1.10.6", optional = true }
serde = { version = "1.0.210", features = ["derive"], optional = true }
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Add following content to your `main.rs`:
use license_fetcher::get_package_list_macro;

fn main() {
let packages = get_package_list_macro!();
let packages = get_package_list_macro!().unwrap();
println!("{}", packages);
}
```
Expand All @@ -72,7 +72,7 @@ fn main() {
+ Also retrieves licenses in the build step and loads them into the program.

#### Cons
- Does not fetch licenses from loacal source files.
- Does not fetch licenses from local source files.
- Very slow.
- Does not compress licenses.

Expand All @@ -84,7 +84,7 @@ fn main() {

#### Cons
- Is not a library to access said data but rather a command line tool.
- Does not fetch licenses from loacal source files.
- Does not fetch licenses from local source files.


## Screenshots
Expand Down
149 changes: 100 additions & 49 deletions src/build_script/mod.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
// Copyright Adam McKellar 2024
// Copyright Adam McKellar 2024, 2025
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at
// https://www.boost.org/LICENSE_1_0.txt)

use std::env::{var_os, var};
use std::collections::BTreeSet;
use std::env::{var, var_os};
use std::ffi::OsString;
use std::fs::write;
use std::path::PathBuf;
use std::process::Command;
use std::collections::BTreeSet;
use std::time::Instant;
use std::path::PathBuf;

#[cfg(feature = "compress")]
use miniz_oxide::deflate::compress_to_vec;

use serde_json::from_slice;
use log::info;
use simplelog::{TermLogger, LevelFilter, Config, TerminalMode, ColorChoice};
use serde_json::from_slice;
use simplelog::{ColorChoice, Config, LevelFilter, TermLogger, TerminalMode};

mod metadata;
mod cargo_source;
mod metadata;

use crate::*;
use build_script::metadata::*;
use cargo_source::{licenses_text_from_cargo_src_folder, license_text_from_folder};
use cargo_source::{license_text_from_folder, licenses_text_from_cargo_src_folder};


fn walk_dependencies<'a>(used_dependencies: &mut BTreeSet<&'a String>, dependencies: &'a Vec<MetadataResolveNode>, root: &String) {
fn walk_dependencies<'a>(
used_dependencies: &mut BTreeSet<&'a String>,
dependencies: &'a Vec<MetadataResolveNode>,
root: &String,
) {
let package = match dependencies.iter().find(|&dep| dep.id == *root) {
Some(pack) => pack,
None => return,
Expand All @@ -38,27 +42,36 @@ fn walk_dependencies<'a>(used_dependencies: &mut BTreeSet<&'a String>, dependenc
}
}

fn generate_package_list() -> PackageList {
let cargo_path = var_os("CARGO").unwrap();
let manifest_path = var_os("CARGO_MANIFEST_DIR").unwrap();
fn generate_package_list(cargo_path: Option<OsString>, manifest_dir_path: OsString) -> PackageList {
let cargo_path = cargo_path.unwrap_or_else(|| OsString::from("cargo"));

let mut metadata_output = Command::new(&cargo_path)
.current_dir(&manifest_path)
.args(["metadata", "--format-version", "1", "--frozen", "--color", "never"])
.output()
.unwrap();
.current_dir(&manifest_dir_path)
.args([
"metadata",
"--format-version",
"1",
"--frozen",
"--color",
"never",
])
.output()
.unwrap();

#[cfg(not(feature = "frozen"))]
if !metadata_output.status.success() {
metadata_output = Command::new(&cargo_path)
.current_dir(&manifest_path)
.args(["metadata", "--format-version", "1", "--color", "never"])
.output()
.unwrap();
.current_dir(&manifest_dir_path)
.args(["metadata", "--format-version", "1", "--color", "never"])
.output()
.unwrap();
}

if !metadata_output.status.success() {
panic!("Failed executing cargo metadata with:\n{}", String::from_utf8_lossy(&metadata_output.stderr));
panic!(
"Failed executing cargo metadata with:\n{}",
String::from_utf8_lossy(&metadata_output.stderr)
);
}

let metadata_parsed: Metadata = from_slice(&metadata_output.stdout).unwrap();
Expand All @@ -71,7 +84,6 @@ fn generate_package_list() -> PackageList {

walk_dependencies(&mut used_packages, &dependencies, &package_id);


// Add dependencies:

let mut package_list = vec![];
Expand All @@ -89,24 +101,56 @@ fn generate_package_list() -> PackageList {
repository: package.repository,
});
}

}

PackageList(package_list)
}

/// Generates a package list with package name, authors and license text. Uses supplied parameters for cargo path and manifest path.
///
/// Thist function is not as usefull as [generate_package_list_with_licenses()] for build scripts.
/// [generate_package_list_with_licenses()] fetches `cargo_path` and `manifest_dir_path` automatically.
/// This function does not.
/// The main use is for other rust programs to fetch the metadata outside of a build script.
///
/// ### Arguments
///
/// * **cargo_path - Absolute path to cargo executable. If omited tries to fetch the path from `PATH`.
/// * **manifest_dir_path** - Relative or absolut path to manifest dir.
/// * **this_package_name** - Name of the package. `cargo metadata` does not disclode the name, but it is needed for parsing the used licenses.
pub fn generate_package_list_with_licenses_without_env_calls(
cargo_path: Option<OsString>,
manifest_dir_path: OsString,
this_package_name: String,
) -> PackageList {
let mut package_list = generate_package_list(cargo_path, manifest_dir_path.clone());

licenses_text_from_cargo_src_folder(&mut package_list);

info!("Fetching license for: {}", &this_package_name);
let this_package_index = package_list
.iter()
.enumerate()
.filter(|(_, p)| p.name == this_package_name)
.map(|(i, _)| i)
.next()
.unwrap();
package_list[this_package_index].license_text =
license_text_from_folder(&PathBuf::from(manifest_dir_path));
package_list.swap(this_package_index, 0);

/// Generates a package list with package name, authors and license text.
///
package_list
}

/// Generates a package list with package name, authors and license text. Uses env variables supplied by cargo during build.
///
/// This function:
/// 1. Calls `cargo tree -e normal --frozen`. *(After error tries again online if not `frozen` feature is set.)*
/// 2. Calls `cargo metadata --frozen`. *(After error tries again online if not `frozen` feature is set.)*
/// 3. Takes the packages gotten from `cargo tree` with the metadata of `cargo metadata`.
/// 4. Fetches the licenses from github with the `repository` link if it includes `github` in name.
/// 5. Serializes, copmresses and writes said package list to `OUT_DIR/LICENSE-3RD-PARTY.bincode` file.
///
///
/// Needs the feature `build` and is only meant to be used in build scripts.
///
///
/// # Example
/// In `build.rs`:
/// ```no_run
Expand All @@ -120,43 +164,50 @@ fn generate_package_list() -> PackageList {
/// }
/// ```
pub fn generate_package_list_with_licenses() -> PackageList {
TermLogger::init(LevelFilter::Trace, Config::default(), TerminalMode::Stderr, ColorChoice::Auto).unwrap();

let mut package_list = generate_package_list();

licenses_text_from_cargo_src_folder(&mut package_list);
TermLogger::init(
LevelFilter::Trace,
Config::default(),
TerminalMode::Stderr,
ColorChoice::Auto,
)
.unwrap();

let cargo_path = var_os("CARGO").unwrap();
let manifest_dir_path = var_os("CARGO_MANIFEST_DIR").unwrap();
let this_package_name = var("CARGO_PKG_NAME").unwrap();
info!("Fetching license for: {}", &this_package_name);
let this_package_path = var("CARGO_MANIFEST_DIR").unwrap();
let this_package_index = package_list.iter().enumerate().filter(|(_, p)| p.name == this_package_name).map(|(i, _)| i).next().unwrap();
package_list[this_package_index].license_text = license_text_from_folder(&PathBuf::from(this_package_path));
package_list.swap(this_package_index, 0);

package_list
generate_package_list_with_licenses_without_env_calls(
Some(cargo_path),
manifest_dir_path,
this_package_name,
)
}

impl PackageList {
/// Writes the [PackageList] to the file and folder where they can be embedded into the program at compile time.
///
///
/// Copmresses and writes the PackageList into the `OUT_DIR` with file name `LICENSE-3RD-PARTY.bincode`.
pub fn write(self) {
let mut path = var_os("OUT_DIR").unwrap();
path.push("/LICENSE-3RD-PARTY.bincode");

let data = bincode::encode_to_vec(self, config::standard()).unwrap();

info!("License data size: {} Bytes", data.len());
let instant_before_compression = Instant::now();

#[cfg(feature = "compress")]
let compressed_data = compress_to_vec(&data, 10);

#[cfg(not(feature = "compress"))]
let compressed_data = data;

info!("Compressed data size: {} Bytes in {}ms", compressed_data.len(), instant_before_compression.elapsed().as_millis());


info!(
"Compressed data size: {} Bytes in {}ms",
compressed_data.len(),
instant_before_compression.elapsed().as_millis()
);

info!("Writing to file: {:?}", &path);
write(path, compressed_data).unwrap();
}
Expand Down
48 changes: 48 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright Adam McKellar 2025
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at
// https://www.boost.org/LICENSE_1_0.txt)

use std::error::Error;
use std::fmt;

/// Error union representing errors that might occur during unpacking of license data.
#[derive(Debug)]
pub enum UnpackError {
#[cfg(feature = "compress")]
DecompressError(miniz_oxide::inflate::DecompressError),
DecodeError(bincode::error::DecodeError),
}

#[cfg(feature = "compress")]
impl From<miniz_oxide::inflate::DecompressError> for UnpackError {
fn from(value: miniz_oxide::inflate::DecompressError) -> Self {
Self::DecompressError(value)
}
}

impl From<bincode::error::DecodeError> for UnpackError {
fn from(value: bincode::error::DecodeError) -> Self {
Self::DecodeError(value)
}
}

impl fmt::Display for UnpackError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[cfg(feature = "compress")]
Self::DecompressError(e) => writeln!(f, "{}", e),
Self::DecodeError(e) => writeln!(f, "{}", e),
}
}
}

impl Error for UnpackError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(match self {
#[cfg(feature = "compress")]
Self::DecompressError(e) => e,
Self::DecodeError(e) => e,
})
}
}
Loading

0 comments on commit 2c46635

Please sign in to comment.