38 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
Colin Woodbury
c4aa908438 release: 1.6.0 2023-10-02 17:04:53 +09:00
Colin Woodbury
00c323a77d fix: check licenses via SPDX identifiers
Fixes #16
2023-10-02 16:49:25 +09:00
Colin Woodbury
9cca4c1737 docs: changelog 2023-10-02 16:28:38 +09:00
Colin Woodbury
506cbdaa7f fix: allow [package.metadata.aur] to be missing 2023-10-02 16:23:45 +09:00
Colin Woodbury
eb0e27f77a fix: ensure target/cargo-aur exists before writing to it 2023-10-02 16:14:24 +09:00
Colin Woodbury
1a0fbf55f9 fix: silently support the old metadata syntax
But warn the user if they're still using it. This is to avoid a breaking change.
2023-10-02 16:07:56 +09:00
Colin Woodbury
24eaa15769 Merge branch 'master' into BKSalman/master 2023-10-02 15:46:39 +09:00
Colin Woodbury
cee24138d1 docs: readme cleanup 2023-10-02 15:43:04 +09:00
Colin Woodbury
40f348f311 refactor: main yields ExitCode 2023-10-02 15:15:03 +09:00
Colin Woodbury
7f85ed3725 lint: drop itertools dependency 2023-10-02 15:08:04 +09:00
Colin Woodbury
698e14bb14 chore: bump dependencies 2023-10-02 15:01:42 +09:00
Colin Woodbury
10704fbc75 Merge pull request #18 from fosskers/dependabot/github_actions/actions/checkout-4
Bump actions/checkout from 3 to 4
2023-09-05 10:11:15 +09:00
dependabot[bot]
7e7f3d5217 Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 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/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 14:52:57 +00:00
BKSalman
ff1b93096c from 'target/aur' to 'target/cargo-aur' 2022-10-19 01:17:53 +03:00
BKSalman
1227ef8e36 made dependency a sub table of 'aur' to not conflict with other cargo subcommands, changed the directory the tar and PKGBUILD files are saved in to 'target/aur' 2022-10-19 01:10:39 +03:00
7 changed files with 426 additions and 155 deletions

View File

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

View File

@@ -1,5 +1,57 @@
# `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
- 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
cluttering the top-level of the repo.
- Reduced binary size.
#### Fixed
- LICENSE file checking is now done via SPDX identifiers.
## 1.5.0 (2022-04-20)
#### Added

View File

@@ -1,6 +1,6 @@
[package]
name = "cargo-aur"
version = "1.5.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."
@@ -15,11 +15,17 @@ categories = ["command-line-utilities"]
colored = "2.0"
gumdrop = "0.8"
hmac-sha256 = "1.1"
itertools = "0.10"
serde = "1.0"
serde_derive = "1.0"
toml = "0.5"
serde = { version = "1.0", features = ["derive"] }
toml = "0.8"
[profile.release]
lto = true
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

@@ -1,27 +1,27 @@
# cargo-aur
[![Build](https://github.com/fosskers/cargo-aur/workflows/Build/badge.svg)](https://github.com/fosskers/cargo-aur/actions)
[![](https://img.shields.io/crates/v/cargo-aur.svg)](https://crates.io/crates/cargo-aur)
![AUR version](https://img.shields.io/aur/version/cargo-aur-bin)
[![Build](https://github.com/fosskers/cargo-aur/workflows/Build/badge.svg)][3]
[![](https://img.shields.io/crates/v/cargo-aur.svg)][4]
![AUR version][5]
`cargo-aur` is a new subcommand for `cargo` that produces a release tarball and
PKGBUILD file for a Rust project, so that it can be released on the Arch Linux
User Repository (AUR).
No extra configuration is necessary. As long as your `Cargo.toml` has [the usual
fields](https://rust-lang.github.io/api-guidelines/documentation.html#c-metadata),
a PKGBUILD will be generated with all the necessary sections filled out.
fields][0], a PKGBUILD will be generated with all the necessary sections filled
out.
## Installation
Guess what? `cargo-aur` itself is on the AUR! Install it with an AUR-compatible
package manager like [`aura`](https://github.com/fosskers/aura):
package manager like [`aura`][1]:
```
sudo aura -A cargo-aur-bin
```
... or via `cargo`:
...or via `cargo`:
```
cargo install cargo-aur
@@ -37,7 +37,8 @@ Navigate to a Rust project, and run:
cargo aur
```
This will produce a `foobar-1.2.3-x86_64.tar.gz` tarball and a PKGBUILD.
This will produce a `foobar-1.2.3-x86_64.tar.gz` tarball and a PKGBUILD within
`target/cargo-aur`.
If you wish, you can now run `makepkg` to ensure that your package actually builds.
@@ -50,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.
@@ -75,17 +82,59 @@ If your package requires other Arch packages at runtime, you can specify these
within your `Cargo.toml` like this:
```toml
[package.metadata]
[package.metadata.aur]
depends = ["nachos", "pizza"]
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
[MUSL](https://musl.libc.org/).
[MUSL][2].
```
> cargo aur --musl
@@ -93,3 +142,10 @@ Run with `--musl` to produce a release binary that is statically linked via
> ldd <your-binary>
not a dynamic executable
```
[0]: https://rust-lang.github.io/api-guidelines/documentation.html#c-metadata
[1]: https://github.com/fosskers/aura
[2]: https://musl.libc.org/
[3]: https://github.com/fosskers/cargo-aur/actions
[4]: https://crates.io/crates/cargo-aur
[5]: https://img.shields.io/aur/version/cargo-aur-bin

View File

@@ -1,14 +1,15 @@
//! 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),
Toml(toml::de::Error),
Utf8(std::str::Utf8Error),
Utf8OsString,
MissingTarget,
MissingMuslTarget,
MissingLicense,
TargetNotAbsolute(PathBuf),
}
impl Display for Error {
@@ -18,13 +19,16 @@ impl Display for Error {
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!(
Error::MissingMuslTarget => write!(
f,
"Missing target! Try: rustup target add x86_64-unknown-linux-musl"
),
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,63 +1,53 @@
pub(crate) mod error;
mod error;
use crate::error::Error;
use cargo_aur::{GitHost, Package};
use colored::*;
use gumdrop::{Options, ParsingStyle};
use hmac_sha256::Hash;
use itertools::Itertools;
use serde_derive::Deserialize;
use std::ffi::OsString;
use serde::Deserialize;
use std::fs::{DirEntry, File};
use std::io::{BufWriter, Write};
use std::ops::Not;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::process::{Command, ExitCode};
/// Licenses avaiable from the Arch Linux `licenses` package.
/// Licenses available 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",
"AGPL-3.0-only",
"AGPL-3.0-or-later",
"Apache-2.0",
"BSL-1.0", // Boost Software License.
"GPL-2.0-only",
"GPL-2.0-or-later",
"GPL-3.0-only",
"GPL-3.0-or-later",
"LGPL-2.0-only",
"LGPL-2.0-or-later",
"LGPL-3.0-only",
"LGPL-3.0-or-later",
"MPL-2.0", // Mozilla Public License.
"Unlicense", // Not to be confused with "Unlicensed".
];
#[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>,
/// 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)]
@@ -77,91 +67,24 @@ 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 {
#[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 {
match self.depends.as_slice() {
[middle @ .., last] => {
write!(f, "depends=(")?;
for item in middle {
write!(f, "\"{}\" ", item)?;
}
if self.optdepends.is_empty().not() {
writeln!(f, "\"{}\")", last)?;
} else {
write!(f, "\"{}\")", last)?;
}
}
[] => {}
}
match self.optdepends.as_slice() {
[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!("{}-{}-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() {
fn main() -> ExitCode {
let args = Args::parse_args_or_exit(ParsingStyle::AllOptions);
if args.version {
let version = env!("CARGO_PKG_VERSION");
println!("{}", version);
ExitCode::SUCCESS
} else if let Err(e) = work(args) {
eprintln!("{} {}: {}", "::".bold(), "Error".bold().red(), e);
std::process::exit(1)
ExitCode::FAILURE
} else {
println!("{} {}", "::".bold(), "Done.".bold().green());
ExitCode::SUCCESS
}
}
@@ -173,7 +96,29 @@ 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(&output)?;
let config = cargo_config()?;
// Warn if the user if still using the old metadata definition style.
if let Some(metadata) = config.package.metadata.as_ref() {
if metadata.depends.is_empty().not() || metadata.optdepends.is_empty().not() {
p("Use of [package.metadata] is deprecated. Please specify extra dependencies under [package.metadata.aur].".bold().yellow());
}
}
let license = if must_copy_license(&config.package.license) {
p("LICENSE file will be installed manually.".bold().yellow());
Some(license_file()?)
@@ -183,26 +128,32 @@ 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("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)
}
/// 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.
/// then it must be installed manually by the PKGBUILD. MIT and BSD3 are such
/// missing licenses, and since many Rust crates use them we must make this
/// check.
fn must_copy_license(license: &str) -> bool {
LICENSES.contains(&license).not()
}
@@ -222,17 +173,21 @@ fn license_file() -> Result<DirEntry, Error> {
}
/// Write a legal PKGBUILD to some `Write` instance (a `File` in this case).
fn pkgbuild<T: Write>(
fn pkgbuild<T>(
mut file: T,
config: &Config,
sha256: &str,
license: Option<&DirEntry>,
) -> Result<(), Error> {
) -> Result<(), Error>
where
T: Write,
{
let package = &config.package;
let authors = package
.authors
.iter()
.map(|a| format!("# Maintainer: {}", a))
.collect::<Vec<_>>()
.join("\n");
let source = package
.git_host()
@@ -250,14 +205,17 @@ fn pkgbuild<T: Write>(
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)?;
@@ -282,6 +240,25 @@ fn pkgbuild<T: Write>(
)?;
}
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(())
}
@@ -299,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 {
@@ -312,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)?;
@@ -324,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)?;
@@ -344,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)
@@ -353,17 +340,14 @@ fn sha256sum(package: &Package) -> Result<String, Error> {
/// Does the user have the `x86_64-unknown-linux-musl` target installed?
fn musl_check() -> Result<(), Error> {
let args = vec!["target", "list", "--installed"];
let args = ["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)
}
std::str::from_utf8(&output)?
.lines()
.any(|tc| tc == "x86_64-unknown-linux-musl")
.then_some(())
.ok_or(Error::MissingMuslTarget)
}
fn p(msg: ColoredString) {