mirror of
https://github.com/fosskers/cargo-aur.git
synced 2026-03-09 07:13:12 -05:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6f9faefdd | ||
|
|
8dbdc1ab71 | ||
|
|
b7eb36b74d | ||
|
|
5f1a44ab30 | ||
|
|
b9e6f8ce6d | ||
|
|
deb0768f3b | ||
|
|
113a00c206 | ||
|
|
a676669e63 | ||
|
|
0ea8be5a73 | ||
|
|
b8e33deed7 | ||
|
|
ffed75dcb0 | ||
|
|
6dd2e4c674 | ||
|
|
6c40291afe | ||
|
|
e1c9ff6fe1 | ||
|
|
64acbe589a | ||
|
|
ee5ee91cbc | ||
|
|
42f34e25dd | ||
|
|
32dc963fcd | ||
|
|
d47558481a | ||
|
|
f7871899d9 | ||
|
|
06c5ab9b69 | ||
|
|
8903c72da6 | ||
|
|
109d9a66a7 | ||
|
|
8e223c5a2d | ||
|
|
b1383ff6ff |
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@@ -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
|
||||
|
||||
43
CHANGELOG.md
43
CHANGELOG.md
@@ -1,5 +1,48 @@
|
||||
# `cargo-aur` Changelog
|
||||
|
||||
## 1.4.1 (2021-09-06)
|
||||
|
||||
#### Fixed
|
||||
|
||||
- `cargo aur` now respects `CARGO_TARGET_DIR`. [#6]
|
||||
|
||||
[#6]: https://github.com/fosskers/cargo-aur/pull/6
|
||||
|
||||
## 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
|
||||
|
||||
- When using `--musl`, the user is warned if they don't have the
|
||||
`x86_64-unknown-linux-musl` target installed.
|
||||
|
||||
#### Changed
|
||||
|
||||
- Run `strip` on the release binary before `tar`ring it.
|
||||
|
||||
## 1.1.1 (2020-08-11)
|
||||
|
||||
#### Fixed
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cargo-aur"
|
||||
version = "1.1.1"
|
||||
version = "1.4.1"
|
||||
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
21
LICENSE
Normal 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.
|
||||
12
README.md
12
README.md
@@ -61,3 +61,15 @@ At this point, it is up to you to:
|
||||
|
||||
Some of these steps may be automated in `cargo aur` at a later date if there is
|
||||
sufficient demand.
|
||||
|
||||
### Static Binaries
|
||||
|
||||
Run with `--musl` to produce a release binary that is statically linked via
|
||||
[MUSL](https://musl.libc.org/).
|
||||
|
||||
```
|
||||
> cargo aur --musl
|
||||
> cd target/x86_64-unknown-linux-musl/release/
|
||||
> ldd <your-binary>
|
||||
not a dynamic executable
|
||||
```
|
||||
|
||||
48
src/error.rs
Normal file
48
src/error.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
258
src/main.rs
258
src/main.rs
@@ -1,14 +1,35 @@
|
||||
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::ffi::OsString;
|
||||
use std::fs::{DirEntry, File};
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::ops::Not;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
/// Licenses avaiable from the Arch Linux `licenses` package.
|
||||
///
|
||||
/// 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>,
|
||||
|
||||
@@ -72,102 +93,205 @@ 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<()> {
|
||||
let config = cargo_config()?;
|
||||
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 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())?;
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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=("{}")
|
||||
/// 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()
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Write a legal PKGBUILD to some `Write` instance (a `File` in this case).
|
||||
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);
|
||||
|
||||
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<()> {
|
||||
let binary = if musl {
|
||||
format!("target/x86_64-unknown-linux-musl/release/{}", package.name)
|
||||
} else {
|
||||
format!("target/release/{}", package.name)
|
||||
fn tarball(musl: bool, license: Option<&DirEntry>, package: &Package) -> Result<(), Error> {
|
||||
let target_dir: OsString = match std::env::var_os("CARGO_TARGET_DIR") {
|
||||
Some(p) => p,
|
||||
None => "target".into(),
|
||||
};
|
||||
|
||||
fs::copy(binary, &package.name)?;
|
||||
Command::new("tar")
|
||||
.arg("czf")
|
||||
.arg(package.tarball())
|
||||
.arg(&package.name)
|
||||
.status()?;
|
||||
fs::remove_file(&package.name)?;
|
||||
let release_dir = if musl {
|
||||
"x86_64-unknown-linux-musl/release"
|
||||
} else {
|
||||
"release"
|
||||
};
|
||||
|
||||
let mut binary: PathBuf = target_dir.into();
|
||||
binary.push(release_dir);
|
||||
binary.push(&package.name);
|
||||
|
||||
strip(&binary)?;
|
||||
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(())
|
||||
}
|
||||
|
||||
fn sha256sum(package: &Package) -> anyhow::Result<String> {
|
||||
let bytes = fs::read(package.tarball())?;
|
||||
/// Strip the release binary, so that we aren't compressing more bytes than we
|
||||
/// need to.
|
||||
fn strip(path: &Path) -> Result<(), Error> {
|
||||
p("Stripping binary...".bold());
|
||||
Command::new("strip").arg(path).status()?;
|
||||
Ok(()) // FIXME Would love to use my `void` package here and elsewhere.
|
||||
}
|
||||
|
||||
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() -> Result<(), Error> {
|
||||
let args = vec!["target", "list", "--installed"];
|
||||
let output = Command::new("rustup").args(args).output()?.stdout;
|
||||
let installed = std::str::from_utf8(&output)?
|
||||
.lines()
|
||||
.any(|tc| tc == "x86_64-unknown-linux-musl");
|
||||
|
||||
if installed {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::MissingTarget)
|
||||
}
|
||||
}
|
||||
|
||||
fn p(msg: ColoredString) {
|
||||
println!("{} {}", "::".bold(), msg)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user