23 Commits

Author SHA1 Message Date
dependabot[bot]
6a047906db chore(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [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/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-22 07:51:52 +09:00
Colin Woodbury
da9934bf00 Merge pull request #36 from fosskers/dependabot/github_actions/actions/checkout-5
chore(deps): bump actions/checkout from 4 to 5
2025-08-12 09:51:01 +09:00
dependabot[bot]
ccd4e9cdb9 chore(deps): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [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/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-11 20:07:17 +00:00
Colin Woodbury
fdc2a8ee9c Merge pull request #35 from dcampbell24/add-namcap
Add mention of namcap.
2025-05-11 08:59:00 +09:00
David Campbell
244d4f2980 Add mention of namcap. 2025-05-10 14:33:30 -04:00
Colin Woodbury
6ea9720f90 feat: inject custom commands into package() 2024-03-22 08:19:40 +09:00
Colin Woodbury
99f36f8f47 release: 1.7.1 2024-03-18 09:41:21 +09:00
Colin Woodbury
94bd36af28 fix: absorb any extra free arguments
Perhaps new versions of cargo are passing the name of the cargo
subtool (`aur` in this case) to the tool itself? Either way, `gumdrop`
suddenly started barfing on this.
2024-03-18 09:39:36 +09:00
Colin Woodbury
7e75c4bf57 release: 1.7.0 2024-03-07 20:43:06 +09:00
Colin Woodbury
4bebc62331 feat: prepend $pkgdir to avoid user hassle 2024-03-07 20:40:25 +09:00
Colin Woodbury
5df440310e docs: show examples of files 2024-03-05 21:57:39 +09:00
Colin Woodbury
d08e994b52 feat: actually copy the designated files into the tarball 2024-03-05 21:57:27 +09:00
Colin Woodbury
fc23ad83fe feat: support files field in package metadata 2024-03-05 14:34:28 +09:00
Colin Woodbury
9707e33204 docs: update CHANGELOG 2024-03-05 10:33:48 +09:00
Colin Woodbury
4080395c00 refactor: a few minor cleanups 2024-03-05 10:32:22 +09:00
bunburya
4d99751ee6 detect url from homepage, documentation or repository fields 2024-03-04 22:18:30 +00:00
Colin Woodbury
4da661c605 docs: add a few docstrings 2023-11-27 15:39:41 +09:00
Colin Woodbury
c36fdec13a fix: default output should respect CARGO_TARGET_DIR 2023-11-27 15:35:21 +09:00
Colin Woodbury
37e93df347 lint: remove unused "free" args field 2023-11-27 15:04:25 +09:00
Colin Woodbury
d08192a721 refactor: minor alterations 2023-11-27 15:01:48 +09:00
Colin Woodbury
2bc4b8c7b5 refactor: move types to lib.rs
This is so that they can be more easily tested in a REPL.
2023-11-27 14:54:04 +09:00
Sergio Ribera
55fc12153b fix: replace string path by pathbuf 2023-11-21 17:07:23 -04:00
Sergio Ribera
0484b382eb feat: add custom output as argument 2023-11-05 23:22:50 -04:00
7 changed files with 339 additions and 151 deletions

View File

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

View File

@@ -1,5 +1,42 @@
# `cargo-aur` Changelog
## Unreleased
#### Added
- A new `custom` field in `[package.metadata.aur]` which accepts a list of
strings that will be added as-is to the `package()` function of the PKGBUILD.
This allows the user to add specific extra commands to their build process.
See the README for more details.
## 1.7.1 (2024-03-18)
#### Fixed
- The crypt startup error `unexpected free argument aur`.
## 1.7.0 (2024-03-07)
#### Added
- The `--output` flag for customizing the location of the output produced by
`cargo aur`. If unused, the default remains `target/cargo-aur/`.
- A new `files` field in `[package.metadata.aur]`, which accepts a list-of-pairs
of additional files you want copied to the user's filesystem upon package
installation. Output looks like:
```
package() {
install -Dm755 cargo-aur -t "$pkgdir/usr/bin"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
install -Dm644 "/path/to/original/foo.txt" "$pkgdir/path/to/target/foo.txt"
}
```
#### Fixed
- Supply the `url` from backup sources if `homepage` is not specified in the `Cargo.toml`.
## 1.6.0 (2023-10-02)
#### Changed
@@ -7,7 +44,7 @@
- The `[package.metadata]` section for adding extra dependency information
should now be named `[package.metadata.aur]`. The old syntax will still work,
but you will be warned. This fixes a conflict with other `cargo` subcommands.
- The PKGBUILD and tarball are now output to `target/cargo-aur` to avoid
- The PKGBUILD and tarball are now output to `target/cargo-aur/` to avoid
cluttering the top-level of the repo.
- Reduced binary size.

View File

@@ -1,6 +1,6 @@
[package]
name = "cargo-aur"
version = "1.6.0"
version = "1.7.1"
authors = ["Colin Woodbury <colin@fosskers.ca>"]
edition = "2021"
description = "Prepare Rust projects to be released on the Arch Linux User Repository."
@@ -24,3 +24,8 @@ strip = true
opt-level = "z"
codegen-units = 1
panic = "abort"
[package.metadata.aur]
# depends = ["blah"]
# files = [[".github/dependabot.yml", "/usr/local/share/cargo-aur/dependabot.yml"]]
custom = ["echo hi"]

View File

@@ -51,6 +51,12 @@ If you wish, you can now run `makepkg` to ensure that your package actually buil
==> Finished making: cargo-aur-bin 1.0.0-1 (Wed 10 Jun 2020 08:23:47 PM PDT)
```
You can also run `namcap` to verify your package doesn't have errors.
```sh
> namcap *.zst
```
Notice that the built package itself is postfixed with `-bin`, which follows the
AUR standard.
@@ -83,6 +89,48 @@ optdepends = ["sushi", "ramen"]
And these settings will be copied to your PKGBUILD.
### Including Additional Files
The `files` list can be used to designated initial files to be copied the user's
filesystem. So this:
```toml
[package.metadata.aur]
files = [["path/to/local/foo.txt", "/usr/local/share/your-app/foo.txt"]]
```
will result in this:
```toml
package() {
install -Dm755 your-app -t "$pkgdir/usr/bin"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
install -Dm644 "path/to/local/foo.txt" "$pkgdir/usr/local/share/your-app/foo.txt"
}
```
### Custom commands within `package()`
The `custom` list can be used to add specific commands to the `package()`
function. This config:
```toml
[package.metadata.aur]
custom = ["echo hi"]
```
yields:
```
package() {
install -Dm755 cargo-aur -t "$pkgdir/usr/bin"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
echo hi
}
```
**Note:** Caveat emptor. No attempt is made to verify the injected commands.
### Static Binaries
Run with `--musl` to produce a release binary that is statically linked via

View File

@@ -1,6 +1,6 @@
//! Errors that can occur in this application.
use std::fmt::Display;
use std::{fmt::Display, path::PathBuf};
pub(crate) enum Error {
IO(std::io::Error),
@@ -9,6 +9,7 @@ pub(crate) enum Error {
Utf8OsString,
MissingMuslTarget,
MissingLicense,
TargetNotAbsolute(PathBuf),
}
impl Display for Error {
@@ -25,6 +26,9 @@ impl Display for Error {
Error::MissingLicense => {
write!(f, "Missing LICENSE file. See https://choosealicense.com/")
}
Error::TargetNotAbsolute(p) => {
write!(f, "Target filepath is not absolute: {}", p.display())
}
}
}
}

169
src/lib.rs Normal file
View File

@@ -0,0 +1,169 @@
//! Independently testable types and functions.
use serde::Deserialize;
use std::ops::Not;
use std::path::{Path, PathBuf};
/// The git forge in which a project's source code is stored.
pub enum GitHost {
Github,
Gitlab,
}
impl GitHost {
pub fn source(&self, package: &Package) -> String {
match self {
GitHost::Github => format!(
"{}/releases/download/v$pkgver/{}-$pkgver-x86_64.tar.gz",
package.repository, package.name
),
GitHost::Gitlab => format!(
"{}/-/archive/v$pkgver/{}-$pkgver-x86_64.tar.gz",
package.repository, package.name
),
}
}
}
/// The critical fields read from a `Cargo.toml` and rewritten into a PKGBUILD.
#[derive(Deserialize, Debug)]
pub struct Package {
pub name: String,
pub version: String,
pub authors: Vec<String>,
pub description: String,
pub repository: String,
pub license: String,
pub metadata: Option<Metadata>,
pub homepage: Option<String>,
pub documentation: Option<String>,
}
impl Package {
/// The name of the tarball that should be produced from this `Package`.
pub fn tarball(&self, output: &Path) -> PathBuf {
output.join(format!("{}-{}-x86_64.tar.gz", self.name, self.version))
}
pub fn git_host(&self) -> Option<GitHost> {
if self.repository.starts_with("https://github") {
Some(GitHost::Github)
} else if self.repository.starts_with("https://gitlab") {
Some(GitHost::Gitlab)
} else {
None
}
}
/// Fetch the package URL from its `homepage`, `documentation` or
/// `repository` field.
pub fn url(&self) -> &str {
self.homepage
.as_deref()
.or(self.documentation.as_deref())
.unwrap_or(&self.repository)
}
}
// {
// Package {
// name: "aura".to_string(),
// version: "1.2.3".to_string(),
// authors: vec![],
// description: "".to_string(),
// homepage: "".to_string(),
// repository: "".to_string(),
// license: "".to_string(),
// metadata: None,
// }.tarball(Path::new("foobar"))
// }
/// The `[package.metadata]` TOML block.
#[derive(Deserialize, Debug)]
pub struct Metadata {
/// Deprecated.
#[serde(default)]
pub depends: Vec<String>,
/// Deprecated.
#[serde(default)]
pub optdepends: Vec<String>,
/// > [package.metadata.aur]
pub aur: Option<AUR>,
}
impl Metadata {
/// The metadata block actually has some contents.
pub fn non_empty(&self) -> bool {
self.depends.is_empty().not()
|| self.optdepends.is_empty().not()
|| self
.aur
.as_ref()
.is_some_and(|aur| aur.depends.is_empty().not() || aur.optdepends.is_empty().not())
}
}
impl std::fmt::Display for Metadata {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Reconcile which section to read extra dependency information from.
// The format we hope the user is using is:
//
// > [package.metadata.aur]
//
// But version 1.5 originally supported:
//
// > [package.metadata]
//
// To avoid a sudden breakage for users, we support both definition
// locations but favour the newer one.
//
// We print a warning to the user elsewhere if they're still using the
// old way.
let (deps, opts) = if let Some(aur) = self.aur.as_ref() {
(aur.depends.as_slice(), aur.optdepends.as_slice())
} else {
(self.depends.as_slice(), self.optdepends.as_slice())
};
match deps {
[middle @ .., last] => {
write!(f, "depends=(")?;
for item in middle {
write!(f, "\"{}\" ", item)?;
}
if opts.is_empty().not() {
writeln!(f, "\"{}\")", last)?;
} else {
write!(f, "\"{}\")", last)?;
}
}
[] => {}
}
match opts {
[middle @ .., last] => {
write!(f, "optdepends=(")?;
for item in middle {
write!(f, "\"{}\" ", item)?;
}
write!(f, "\"{}\")", last)?;
}
[] => {}
}
Ok(())
}
}
/// The inner values of a `[package.metadata.aur]` TOML block.
#[derive(Deserialize, Debug)]
pub struct AUR {
#[serde(default)]
depends: Vec<String>,
#[serde(default)]
optdepends: Vec<String>,
#[serde(default)]
pub files: Vec<(PathBuf, PathBuf)>,
#[serde(default)]
pub custom: Vec<String>,
}

View File

@@ -1,11 +1,11 @@
mod error;
use crate::error::Error;
use cargo_aur::{GitHost, Package};
use colored::*;
use gumdrop::{Options, ParsingStyle};
use hmac_sha256::Hash;
use serde::Deserialize;
use std::ffi::OsString;
use std::fs::{DirEntry, File};
use std::io::{BufWriter, Write};
use std::ops::Not;
@@ -39,33 +39,15 @@ struct Args {
help: bool,
/// Display the current version of this software.
version: bool,
/// Unused.
#[options(free)]
args: Vec<String>,
/// Set a custom output directory (default: target/).
output: Option<PathBuf>,
/// Use the MUSL build target to produce a static binary.
musl: bool,
/// Don't actually build anything.
dryrun: bool,
}
enum GitHost {
Github,
Gitlab,
}
impl GitHost {
fn source(&self, package: &Package) -> String {
match self {
GitHost::Github => format!(
"{}/releases/download/v$pkgver/{}-$pkgver-x86_64.tar.gz",
package.repository, package.name
),
GitHost::Gitlab => format!(
"{}/-/archive/v$pkgver/{}-$pkgver-x86_64.tar.gz",
package.repository, package.name
),
}
}
/// Absorbs any extra junk arguments.
#[options(free)]
free: Vec<String>,
}
#[derive(Deserialize, Debug)]
@@ -85,115 +67,11 @@ impl Config {
}
}
#[derive(Deserialize, Debug)]
struct Package {
name: String,
version: String,
authors: Vec<String>,
description: String,
homepage: String,
repository: String,
license: String,
metadata: Option<Metadata>,
}
#[derive(Deserialize, Debug)]
struct Metadata {
/// Deprecated.
#[serde(default)]
depends: Vec<String>,
/// Deprecated.
#[serde(default)]
optdepends: Vec<String>,
/// > [package.metadata.aur]
aur: Option<AUR>,
}
#[derive(Deserialize, Debug)]
struct AUR {
#[serde(default)]
depends: Vec<String>,
#[serde(default)]
optdepends: Vec<String>,
}
impl std::fmt::Display for Metadata {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Reconcile which section to read extra dependency information from.
// The format we hope the user is using is:
//
// > [package.metadata.aur]
//
// But version 1.5 originally supported:
//
// > [package.metadata]
//
// To avoid a sudden breakage for users, we support both definition
// locations but favour the newer one.
//
// We print a warning to the user elsewhere if they're still using the
// old way.
let (deps, opts) = if let Some(aur) = self.aur.as_ref() {
(aur.depends.as_slice(), aur.optdepends.as_slice())
} else {
(self.depends.as_slice(), self.optdepends.as_slice())
};
match deps {
[middle @ .., last] => {
write!(f, "depends=(")?;
for item in middle {
write!(f, "\"{}\" ", item)?;
}
if opts.is_empty().not() {
writeln!(f, "\"{}\")", last)?;
} else {
write!(f, "\"{}\")", last)?;
}
}
[] => {}
}
match opts {
[middle @ .., last] => {
write!(f, "optdepends=(")?;
for item in middle {
write!(f, "\"{}\" ", item)?;
}
write!(f, "\"{}\")", last)?;
}
[] => {}
}
Ok(())
}
}
#[derive(Deserialize, Debug)]
struct Binary {
name: String,
}
impl Package {
/// The name of the tarball that should be produced from this `Package`.
fn tarball(&self) -> String {
format!(
"target/cargo-aur/{}-{}-x86_64.tar.gz",
self.name, self.version
)
}
fn git_host(&self) -> Option<GitHost> {
if self.repository.starts_with("https://github") {
Some(GitHost::Github)
} else if self.repository.starts_with("https://gitlab") {
Some(GitHost::Gitlab)
} else {
None
}
}
}
fn main() -> ExitCode {
let args = Args::parse_args_or_exit(ParsingStyle::AllOptions);
@@ -218,9 +96,19 @@ fn work(args: Args) -> Result<(), Error> {
musl_check()?
}
// Where cargo expects to read and write to. By default we want to read the
// built binary from `target/release` and we want to write our results to
// `target/cargo-aur`, but these are configurable by the user.
let cargo_target: PathBuf = match std::env::var_os("CARGO_TARGET_DIR") {
Some(p) => PathBuf::from(p),
None => PathBuf::from("target"),
};
let output = args.output.unwrap_or(cargo_target.join("cargo-aur"));
// Ensure the target can actually be written to. Otherwise the `tar`
// operation later on will fail.
std::fs::create_dir_all("target/cargo-aur")?;
std::fs::create_dir_all(&output)?;
let config = cargo_config()?;
@@ -240,18 +128,23 @@ fn work(args: Args) -> Result<(), Error> {
if args.dryrun.not() {
release_build(args.musl)?;
tarball(args.musl, license.as_ref(), &config)?;
let sha256: String = sha256sum(&config.package)?;
tarball(args.musl, &cargo_target, &output, license.as_ref(), &config)?;
let sha256: String = sha256sum(&config.package, &output)?;
// Write the PKGBUILD.
let file = BufWriter::new(File::create("target/cargo-aur/PKGBUILD")?);
let path = output.join("PKGBUILD");
let file = BufWriter::new(File::create(path)?);
pkgbuild(file, &config, &sha256, license.as_ref())?;
}
Ok(())
}
/// Read the `Cargo.toml` for all the fields of concern to this tool.
fn cargo_config() -> Result<Config, Error> {
// NOTE 2023-11-27 Yes it looks silly to be reading the whole thing into a
// string here, but the `toml` library doesn't allow deserialization from
// anything else but a string.
let content = std::fs::read_to_string("Cargo.toml")?;
let proj: Config = toml::from_str(&content)?;
Ok(proj)
@@ -312,14 +205,17 @@ where
writeln!(file, "pkgver={}", package.version)?;
writeln!(file, "pkgrel=1")?;
writeln!(file, "pkgdesc=\"{}\"", package.description)?;
writeln!(file, "url=\"{}\"", package.homepage)?;
writeln!(file, "url=\"{}\"", package.url())?;
writeln!(file, "license=(\"{}\")", package.license)?;
writeln!(file, "arch=(\"x86_64\")")?;
writeln!(file, "provides=(\"{}\")", package.name)?;
writeln!(file, "conflicts=(\"{}\")", package.name)?;
if let Some(metadata) = package.metadata.as_ref() {
writeln!(file, "{}", metadata)?;
match package.metadata.as_ref() {
Some(metadata) if metadata.non_empty() => {
writeln!(file, "{}", metadata)?;
}
Some(_) | None => {}
}
writeln!(file, "source=(\"{}\")", source)?;
@@ -344,6 +240,25 @@ where
)?;
}
if let Some(aur) = package.metadata.as_ref().and_then(|m| m.aur.as_ref()) {
for (source, target) in aur.files.iter() {
if target.has_root().not() {
return Err(Error::TargetNotAbsolute(target.to_path_buf()));
} else {
writeln!(
file,
" install -Dm644 \"{}\" \"$pkgdir{}\"",
source.display(),
target.display()
)?;
}
}
for custom in aur.custom.iter() {
writeln!(file, " {}", custom)?;
}
}
writeln!(file, "}}")?;
Ok(())
}
@@ -361,12 +276,13 @@ fn release_build(musl: bool) -> Result<(), Error> {
Ok(())
}
fn tarball(musl: bool, license: Option<&DirEntry>, config: &Config) -> Result<(), Error> {
let target_dir: OsString = match std::env::var_os("CARGO_TARGET_DIR") {
Some(p) => p,
None => "target".into(),
};
fn tarball(
musl: bool,
cargo_target: &Path,
output: &Path,
license: Option<&DirEntry>,
config: &Config,
) -> Result<(), Error> {
let release_dir = if musl {
"x86_64-unknown-linux-musl/release"
} else {
@@ -374,9 +290,7 @@ fn tarball(musl: bool, license: Option<&DirEntry>, config: &Config) -> Result<()
};
let binary_name = config.binary_name();
let mut binary: PathBuf = target_dir.into();
binary.push(release_dir);
binary.push(binary_name);
let binary = cargo_target.join(release_dir).join(binary_name);
strip(&binary)?;
std::fs::copy(binary, binary_name)?;
@@ -386,11 +300,22 @@ fn tarball(musl: bool, license: Option<&DirEntry>, config: &Config) -> Result<()
let mut command = Command::new("tar");
command
.arg("czf")
.arg(config.package.tarball())
.arg(config.package.tarball(output))
.arg(binary_name);
if let Some(lic) = license {
command.arg(lic.path());
}
if let Some(files) = config
.package
.metadata
.as_ref()
.and_then(|m| m.aur.as_ref())
.map(|a| a.files.as_slice())
{
for (file, _) in files {
command.arg(file);
}
}
command.status()?;
std::fs::remove_file(binary_name)?;
@@ -406,8 +331,8 @@ fn strip(path: &Path) -> Result<(), Error> {
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())?;
fn sha256sum(package: &Package, output: &Path) -> Result<String, Error> {
let bytes = std::fs::read(package.tarball(output))?;
let digest = Hash::hash(&bytes);
let hex = digest.iter().map(|u| format!("{:02x}", u)).collect();
Ok(hex)