[ENG-134] OSS Licenses and Projects (#575)

* add `deps-generator` crate

* move `clap` types to separate file

* idiomatic filtering

* experimentally update ci

* add *really* basic dependency page

* remove `license_id` from `License`

* add bare minimum JSON files

* Revert "experimentally update ci"

This reverts commit c04897d902.

* re-insert comments

* Using reqwest blocking feature to avoid asyncness

---------

Co-authored-by: Ericson Soares <ericson.ds999@gmail.com>
This commit is contained in:
jake 2023-02-20 05:58:55 +00:00 committed by GitHub
parent e9e8d2286c
commit 4cc582e08c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 286 additions and 4 deletions

40
Cargo.lock generated
View file

@ -451,6 +451,12 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
[[package]]
name = "base64"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "base64ct"
version = "1.5.2"
@ -732,6 +738,20 @@ dependencies = [
"serde_json",
]
[[package]]
name = "cargo_metadata"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07"
dependencies = [
"camino",
"cargo-platform",
"semver 1.0.14",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "cargo_toml"
version = "0.11.8"
@ -1453,6 +1473,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "deps-generator"
version = "0.0.0"
dependencies = [
"anyhow",
"cargo_metadata 0.15.3",
"clap",
"reqwest",
"serde",
"serde_json",
]
[[package]]
name = "derivative"
version = "2.2.0"
@ -5219,11 +5251,11 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.11.12"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc"
checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
dependencies = [
"base64 0.13.1",
"base64 0.21.0",
"bytes",
"encoding_rs",
"futures-core",
@ -6104,7 +6136,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8"
dependencies = [
"bytecount",
"cargo_metadata",
"cargo_metadata 0.14.2",
"error-chain",
"glob",
"pulldown-cmark",

View file

@ -0,0 +1,14 @@
[package]
name = "deps-generator"
version = "0.0.0"
edition = "2021"
authors = ["Jake Robinson <jake@spacedrive.com>"]
description = "A tool to compile all Spacedrive dependencies and their respective licenses"
[dependencies]
reqwest = { version = "0.11.14", features = ["blocking"] }
clap = { version = "4.0.32", features = ["derive"] }
anyhow = "1.0.68"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
cargo_metadata = "0.15.3"

View file

@ -0,0 +1,75 @@
use anyhow::Result;
use cargo_metadata::CargoOpt;
use clap::Parser;
use std::{fs::File, path::PathBuf};
use types::{
backend::BackendDependency,
cli::{Action, Arguments},
frontend::FrontendDependency,
};
pub mod types;
const FOSSA_BASE_URL: &str =
"https://app.fossa.com/api/revisions/git%2Bgithub.com%2Fspacedriveapp%2Fspacedrive%24";
fn main() -> Result<()> {
let args = Arguments::parse();
match args.action {
Action::Frontend(sub_args) => write_frontend_deps(sub_args.revision, sub_args.path),
Action::Backend(sub_args) => {
write_backend_deps(sub_args.manifest_path, sub_args.output_path)
}
}
}
fn write_backend_deps(manifest_path: PathBuf, output_path: PathBuf) -> Result<()> {
let cmd = cargo_metadata::MetadataCommand::new()
.manifest_path(manifest_path)
.features(CargoOpt::AllFeatures)
.exec()?;
let deps: Vec<BackendDependency> = cmd
.packages
.into_iter()
.filter_map(|p| {
(!cmd.workspace_members.iter().any(|t| &p.id == t)).then_some(BackendDependency {
title: p.name,
description: p.description,
url: p.repository,
version: p.version.to_string(),
authors: p.authors,
license: p.license,
})
})
.collect();
let mut file = File::create(output_path)?;
serde_json::to_writer(&mut file, &deps)?;
Ok(())
}
fn write_frontend_deps(rev: String, path: PathBuf) -> Result<()> {
let url = format!("{FOSSA_BASE_URL}{rev}/dependencies");
let response = reqwest::blocking::get(url)?.text()?;
let json: Vec<types::frontend::Dependency> = serde_json::from_str(&response)?;
let deps: Vec<_> = json
.into_iter()
.map(|dep| FrontendDependency {
title: dep.project.title,
authors: dep.project.authors,
description: dep.project.description,
url: dep.project.url,
license: dep.licenses,
})
.collect();
let mut file = File::create(path)?;
serde_json::to_writer(&mut file, &deps)?;
Ok(())
}

View file

@ -0,0 +1,12 @@
use serde::Serialize;
#[allow(clippy::module_name_repetitions)]
#[derive(Serialize)]
pub struct BackendDependency {
pub title: String,
pub description: Option<String>,
pub url: Option<String>,
pub version: String,
pub authors: Vec<String>,
pub license: Option<String>,
}

View file

@ -0,0 +1,33 @@
use std::path::PathBuf;
use clap::{Args, Parser, Subcommand};
#[derive(Parser)]
pub struct Arguments {
#[command(subcommand)]
pub action: Action,
}
#[derive(Subcommand)]
pub enum Action {
Frontend(FrontendArgs),
Backend(BackendArgs),
}
#[derive(Args)]
pub struct FrontendArgs {
// could source this from `$GITHUB_SHA` for CI, if not set
#[arg(help = "the git revision")]
pub revision: String,
#[arg(help = "the output path")]
pub path: PathBuf,
}
#[derive(Args)]
pub struct BackendArgs {
// could use `Cargo.toml` as the default from current dir (if not set)
#[arg(help = "path to the cargo manifest")]
pub manifest_path: PathBuf,
#[arg(help = "the output path")]
pub output_path: PathBuf,
}

View file

@ -0,0 +1,36 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct Dependency {
pub project: Project,
pub licenses: Vec<License>,
}
#[derive(Serialize, Deserialize)]
pub struct Project {
// pub locator: Option<String>,
pub title: String,
pub description: Option<String>,
pub url: Option<String>,
pub authors: Vec<Option<String>>,
}
#[derive(Serialize, Deserialize)]
pub struct License {
pub text: Option<String>,
// pub license_id: Option<String>, // always null AFAIK
pub copyright: Option<String>,
// pub license_group_id: i64,
// pub ignored: bool, // always false from my testing
// pub revision_id: Option<String>,
}
#[allow(clippy::module_name_repetitions)]
#[derive(Serialize)]
pub struct FrontendDependency {
pub title: String,
pub description: Option<String>,
pub url: Option<String>,
pub authors: Vec<Option<String>>,
pub license: Vec<License>,
}

View file

@ -0,0 +1,3 @@
pub mod backend;
pub mod cli;
pub mod frontend;

View file

@ -0,0 +1,10 @@
[
{
"title": "Placeholder",
"description": "",
"url": "https://spacedrive.com",
"version": "0.0.0",
"authors": [""],
"license": ""
}
]

View file

@ -0,0 +1,14 @@
[
{
"title": "Placeholder",
"description": "",
"url": "https://spacedrive.com",
"authors": [""],
"license": [
{
"text": "",
"copyright": ""
}
]
}
]

View file

@ -2,6 +2,7 @@ import {
Books,
FlyingSaucer,
GearSix,
Graph,
HardDrive,
Heart,
Key,
@ -83,6 +84,10 @@ export const SettingsSidebar = () => {
<SettingsIcon component={Receipt} />
Changelog
</SidebarLink>
<SidebarLink to="/settings/dependencies">
<SettingsIcon component={Graph} />
Dependencies
</SidebarLink>
<SidebarLink to="/settings/support">
<SettingsIcon component={Heart} />
Support

View file

@ -26,6 +26,7 @@ const routes: RouteProps[] = [
{ path: 'privacy', element: lazyEl(() => import('./client/PrivacySettings')) },
{ path: 'about', element: lazyEl(() => import('./info/AboutSpacedrive')) },
{ path: 'changelog', element: lazyEl(() => import('./info/Changelog')) },
{ path: 'dependencies', element: lazyEl(() => import('./info/Dependencies')) },
{ path: 'support', element: lazyEl(() => import('./info/Support')) }
];

View file

@ -0,0 +1,47 @@
import { useQuery } from '@tanstack/react-query';
import { ScreenHeading } from '@sd/ui';
import { usePlatform } from '~/util/Platform';
export default function DependenciesScreen() {
const frontEnd = useQuery(['frontend-deps'], () => import('@sd/assets/deps/frontend-deps.json'));
const backEnd = useQuery(['backend-deps'], () => import('@sd/assets/deps/backend-deps.json'));
const platform = usePlatform();
return (
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll app-background">
<ScreenHeading>Dependencies</ScreenHeading>
{/* item has a LOT more data that we can display, i just went with the basics */}
<ScreenHeading className="mb-2">Frontend Dependencies</ScreenHeading>
<div className="grid gap-6 space-x-1 xl:grid-cols-4 2xl:grid-cols-6">
{frontEnd.data &&
frontEnd.data?.default.map((item) => {
return (
<a key={item.title} onClick={() => platform.openLink(item.url ?? '')}>
<div className="px-4 py-4 text-gray-300 border-2 border-gray-500 rounded">
<h4 className="text-center">
{item.title.trimEnd().substring(0, 24) + (item.title.length > 24 ? '...' : '')}
</h4>
</div>
</a>
);
})}
</div>
<ScreenHeading className="mb-2">Backend Dependencies</ScreenHeading>
<div className="grid gap-6 space-x-1 lg:grid-cols-7">
{backEnd.data &&
backEnd.data?.default.map((item) => {
return (
<a key={item.title} onClick={() => platform.openLink(item.url ?? '')}>
<div className="px-4 py-4 text-gray-300 border-2 border-gray-500 rounded">
<h4 className="text-center">{item.title.trimEnd()}</h4>
</div>
</a>
);
})}
</div>
</div>
);
}