18 Commits

Author SHA1 Message Date
Colin Woodbury
5f1a44ab30 Merge branch 'master' of github.com:fosskers/cargo-aur 2021-06-07 10:55:09 -07:00
Colin Woodbury
b9e6f8ce6d 1.4.0 2021-06-07 10:51:49 -07:00
Colin Woodbury
deb0768f3b Mention that the PKGBUILD is auto-generated 2021-06-07 10:44:04 -07:00
Colin Woodbury
113a00c206 Install the LICENSE file if necessary 2021-06-07 10:39:48 -07:00
Colin Woodbury
a676669e63 If a LICENSE must be copied, ensure it's there 2021-06-07 10:02:53 -07:00
Colin Woodbury
0ea8be5a73 Output progress messages 2021-06-06 11:11:33 -07:00
Colin Woodbury
b8e33deed7 Only copy the LICENSE if we actually have to
Which is defined by whether or not the LICENSE is already present in
`/usr/share/licenses/common/`.
2021-06-06 10:51:45 -07:00
Colin Woodbury
ffed75dcb0 Copy LICENSE file to tarball if it's there 2021-06-06 10:33:12 -07:00
Colin Woodbury
6dd2e4c674 Check for missing LICENSE 2021-06-05 08:48:09 -07:00
Colin Woodbury
6c40291afe Output the conflicts field 2021-06-05 08:25:01 -07:00
Colin Woodbury
e1c9ff6fe1 Remove anyhow 2021-06-05 08:19:27 -07:00
Colin Woodbury
64acbe589a Add LICENSE 2021-06-05 08:09:32 -07:00
Colin Woodbury
ee5ee91cbc Merge pull request #4 from fosskers/dependabot/github_actions/actions/checkout-2.3.4
Bump actions/checkout from 2 to 2.3.4
2021-05-12 11:10:29 -07:00
dependabot[bot]
42f34e25dd Bump actions/checkout from 2 to 2.3.4
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 2.3.4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v2.3.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-12 05:03:28 +00:00
Colin Woodbury
32dc963fcd 1.3.0 2021-04-05 12:02:14 -07:00
Colin Woodbury
d47558481a Don't set strip explicitly, since it's already a default 2021-04-05 12:00:21 -07:00
Colin Woodbury
f7871899d9 1.2.0 2020-08-24 14:19:46 -07:00
Colin Woodbury
06c5ab9b69 Add --version flag 2020-08-24 14:17:55 -07:00
6 changed files with 249 additions and 75 deletions

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2.3.4
- name: Build
run: cargo build --verbose
- name: Run tests

View File

@@ -1,5 +1,29 @@
# `cargo-aur` Changelog
## 1.4.0 (2021-06-07)
#### Added
- The `conflicts` field is now added to the `PKGBUILD`.
- Progress messages in the terminal.
- `LICENSE` detection and installation. If your Rust crate has a license not
found in `/usr/share/licenses/common/` (like `MIT`), then `cargo aur` will
copy it into the source tarball and have the PKGBUILD install it. Naturally
this means you must actually have a `LICENSE` file in your project, or `cargo aur` will complain.
## 1.3.0 (2021-04-05)
#### Changed
- `cargo aur` no longer outputs `options=("strip")`, since this is set by
default in `/etc/makepkg.conf`.
## 1.2.0 (2020-08-24)
#### Added
- A `--version` flag to display the current version of `cargo-aur`.
## 1.1.2 (2020-08-11)
#### Added

View File

@@ -1,6 +1,6 @@
[package]
name = "cargo-aur"
version = "1.1.2"
version = "1.4.0"
authors = ["Colin Woodbury <colin@fosskers.ca>"]
edition = "2018"
description = "Prepare Rust projects to be released on the Arch Linux User Repository."
@@ -12,10 +12,10 @@ keywords = ["cargo", "subcommand", "archlinux", "aur"]
categories = ["command-line-utilities"]
[dependencies]
anyhow = "1.0"
colored = "2.0"
gumdrop = "0.8"
hmac-sha256 = "0.1"
itertools = "0.9"
itertools = "0.10"
serde = "1.0"
serde_derive = "1.0"
toml = "0.5"

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 - 2021 Colin Woodbury
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

48
src/error.rs Normal file
View File

@@ -0,0 +1,48 @@
//! Errors that can occur in this application.
use std::fmt::Display;
pub(crate) enum Error {
IO(std::io::Error),
Toml(toml::de::Error),
Utf8(std::str::Utf8Error),
Utf8OsString,
MissingTarget,
MissingLicense,
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::IO(e) => write!(f, "{}", e),
Error::Toml(e) => write!(f, "{}", e),
Error::Utf8(e) => write!(f, "{}", e),
Error::Utf8OsString => write!(f, "The `OsString` was not UTF-8!"),
Error::MissingTarget => write!(
f,
"Missing target! Try: rustup target add x86_64-unknown-linux-musl"
),
Error::MissingLicense => {
write!(f, "Missing LICENSE file. See https://choosealicense.com/")
}
}
}
}
impl From<std::str::Utf8Error> for Error {
fn from(v: std::str::Utf8Error) -> Self {
Self::Utf8(v)
}
}
impl From<toml::de::Error> for Error {
fn from(v: toml::de::Error) -> Self {
Self::Toml(v)
}
}
impl From<std::io::Error> for Error {
fn from(v: std::io::Error) -> Self {
Self::IO(v)
}
}

View File

@@ -1,16 +1,33 @@
use anyhow::anyhow;
pub(crate) mod error;
use crate::error::Error;
use colored::*;
use gumdrop::{Options, ParsingStyle};
use hmac_sha256::Hash;
use itertools::Itertools;
use serde_derive::Deserialize;
use std::fs;
use std::process::{self, Command};
use std::str;
use std::fs::{DirEntry, File};
use std::io::{BufWriter, Write};
use std::ops::Not;
use std::process::Command;
/// Licenses avaiable from the Arch Linux `licenses`.
///
/// That package contains other licenses, but I've excluded here those unlikely
/// to be used by Rust crates.
const LICENSES: &[&str] = &[
"AGPL3", "APACHE", "GPL2", "GPL3", "LGPL2.1", "LGPL3", "MPL", "MPL2",
];
#[derive(Options)]
struct Args {
/// Display this help message.
help: bool,
/// Display the current version of this software.
version: bool,
/// Unused.
#[options(free)]
args: Vec<String>,
@@ -74,88 +91,144 @@ impl Package {
fn main() {
let args = Args::parse_args_or_exit(ParsingStyle::AllOptions);
if let Err(e) = work(args) {
eprintln!("{}", e);
process::exit(1)
if args.version {
let version = env!("CARGO_PKG_VERSION");
println!("{}", version);
} else if let Err(e) = work(args) {
eprintln!("{} {}: {}", "::".bold(), "Error".bold().red(), e);
std::process::exit(1)
} else {
println!("{} {}", "::".bold(), "Done.".bold().green());
}
}
fn work(args: Args) -> anyhow::Result<()> {
fn work(args: Args) -> Result<(), Error> {
// We can't proceed if the user has specified `--musl` but doesn't have the
// target installed.
if args.musl {
p("Checking for musl toolchain...".bold());
musl_check()?
}
let config = cargo_config()?;
let package = cargo_config()?;
let license = if must_copy_license(&package.license) {
p("LICENSE file will be installed manually.".bold().yellow());
Some(license_file()?)
} else {
None
};
release_build(args.musl)?;
tarball(args.musl, &config.package)?;
let sha256 = sha256sum(&config.package)?;
let pkgbuild = pkgbuild(&config.package, &sha256);
fs::write("PKGBUILD", pkgbuild)?;
tarball(args.musl, license.as_ref(), &package)?;
let sha256: String = sha256sum(&package)?;
// Write the PKGBUILD.
let file = BufWriter::new(File::create("PKGBUILD")?);
pkgbuild(file, &package, &sha256, license.as_ref())?;
// fs::write("PKGBUILD", pkgbuild)?;
Ok(())
}
fn cargo_config() -> anyhow::Result<Config> {
let content = fs::read_to_string("Cargo.toml")?;
let proj = toml::from_str(&content)?;
Ok(proj) // TODO Would like to do this in one line with the above.
fn cargo_config() -> Result<Package, Error> {
let content = std::fs::read_to_string("Cargo.toml")?;
let proj: Config = toml::from_str(&content)?;
Ok(proj.package)
}
/// If a AUR package's license isn't included in `/usr/share/licenses/common/`,
/// then it must be installed manually by the PKGBUILD. MIT is such a missing
/// license, and since many Rust crates use MIT we must make this check.
fn must_copy_license(license: &str) -> bool {
LICENSES.contains(&license).not()
}
/// The path to the `LICENSE` file.
fn license_file() -> Result<DirEntry, Error> {
std::fs::read_dir(".")?
.filter_map(|entry| entry.ok())
.find(|entry| {
entry
.file_name()
.to_str()
.map(|s| s.starts_with("LICENSE"))
.unwrap_or(false)
})
.ok_or(Error::MissingLicense)
}
/// Produce a legal PKGBUILD.
fn pkgbuild(package: &Package, sha256: &str) -> String {
format!(
r#"{}
pkgname={}-bin
pkgver={}
pkgrel=1
pkgdesc="{}"
url="{}"
license=("{}")
arch=("x86_64")
provides=("{}")
options=("strip")
source=("{}")
sha256sums=("{}")
fn pkgbuild<T: Write>(
mut file: T,
package: &Package,
sha256: &str,
license: Option<&DirEntry>,
) -> Result<(), Error> {
let authors = package
.authors
.iter()
.map(|a| format!("# Maintainer: {}", a))
.join("\n");
let source = package
.git_host()
.unwrap_or(GitHost::Github)
.source(package);
package() {{
install -Dm755 {} -t "$pkgdir/usr/bin/"
}}
"#,
package
.authors
.iter()
.map(|a| format!("# Maintainer: {}", a))
.join("\n"),
package.name,
package.version,
package.description,
package.homepage,
package.license,
package.name,
package
.git_host()
.unwrap_or(GitHost::Github)
.source(package),
sha256,
package.name,
)
writeln!(file, "{}", authors)?;
writeln!(file, "#")?;
writeln!(
file,
"# This PKGBUILD was generated by `cargo aur`: https://crates.io/crates/cargo-aur"
)?;
writeln!(file)?;
writeln!(file, "pkgname={}-bin", package.name)?;
writeln!(file, "pkgver={}", package.version)?;
writeln!(file, "pkgrel=1")?;
writeln!(file, "pkgdesc=\"{}\"", package.description)?;
writeln!(file, "url=\"{}\"", package.homepage)?;
writeln!(file, "license=(\"{}\")", package.license)?;
writeln!(file, "arch=(\"x86_64\")")?;
writeln!(file, "provides=(\"{}\")", package.name)?;
writeln!(file, "conflicts=(\"{}\")", package.name)?;
writeln!(file, "source=(\"{}\")", source)?;
writeln!(file, "sha256sums=(\"{}\")", sha256)?;
writeln!(file)?;
writeln!(file, "package() {{")?;
writeln!(
file,
" install -Dm755 {} -t \"$pkgdir/usr/bin\"",
package.name
)?;
if let Some(lic) = license {
let file_name = lic
.file_name()
.into_string()
.map_err(|_| Error::Utf8OsString)?;
writeln!(
file,
" install -Dm644 {} \"$pkgdir/usr/share/licenses/$pkgname/{}\"",
file_name, file_name
)?;
}
writeln!(file, "}}")?;
Ok(())
}
/// Run `cargo build --release`, potentially building statically.
fn release_build(musl: bool) -> anyhow::Result<()> {
fn release_build(musl: bool) -> Result<(), Error> {
let mut args = vec!["build", "--release"];
if musl {
args.push("--target=x86_64-unknown-linux-musl");
}
p("Running release build...".bold());
Command::new("cargo").args(args).status()?;
Ok(())
}
fn tarball(musl: bool, package: &Package) -> anyhow::Result<()> {
fn tarball(musl: bool, license: Option<&DirEntry>, package: &Package) -> Result<(), Error> {
let binary = if musl {
format!("target/x86_64-unknown-linux-musl/release/{}", package.name)
} else {
@@ -163,44 +236,52 @@ fn tarball(musl: bool, package: &Package) -> anyhow::Result<()> {
};
strip(&binary)?;
fs::copy(binary, &package.name)?;
Command::new("tar")
.arg("czf")
.arg(package.tarball())
.arg(&package.name)
.status()?;
fs::remove_file(&package.name)?;
std::fs::copy(binary, &package.name)?;
// Create the tarball.
p("Packing tarball...".bold());
let mut command = Command::new("tar");
command.arg("czf").arg(package.tarball()).arg(&package.name);
if let Some(lic) = license {
command.arg(lic.path());
}
command.status()?;
std::fs::remove_file(&package.name)?;
Ok(())
}
/// Strip the release binary, so that we aren't compressing more bytes than we
/// need to.
fn strip(path: &str) -> anyhow::Result<()> {
fn strip(path: &str) -> Result<(), Error> {
p("Stripping binary...".bold());
Command::new("strip").arg(path).status()?;
Ok(())
Ok(()) // FIXME Would love to use my `void` package here and elsewhere.
}
fn sha256sum(package: &Package) -> anyhow::Result<String> {
let bytes = fs::read(package.tarball())?;
fn sha256sum(package: &Package) -> Result<String, Error> {
let bytes = std::fs::read(package.tarball())?;
let digest = Hash::hash(&bytes);
let hex = digest.iter().map(|u| format!("{:02x}", u)).collect();
Ok(hex)
}
/// Does the user have the `x86_64-unknown-linux-musl` target installed?
fn musl_check() -> anyhow::Result<()> {
fn musl_check() -> Result<(), Error> {
let args = vec!["target", "list", "--installed"];
let output = Command::new("rustup").args(args).output()?.stdout;
let installed = str::from_utf8(&output)?
let installed = std::str::from_utf8(&output)?
.lines()
.any(|tc| tc == "x86_64-unknown-linux-musl");
if installed {
Ok(())
} else {
Err(anyhow!(
"Missing target! Try: rustup target add x86_64-unknown-linux-musl"
))
Err(Error::MissingTarget)
}
}
fn p(msg: ColoredString) {
println!("{} {}", "::".bold(), msg)
}