74 Commits

Author SHA1 Message Date
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
Colin Woodbury
c2661279b7 1.5.0 2022-04-20 17:13:00 -07:00
Colin Woodbury
da899130ae docs(readme): explain new features 2022-04-20 17:12:09 -07:00
Colin Woodbury
8d0ba5be31 docs: update CHANGELOG 2022-04-20 17:04:30 -07:00
Colin Woodbury
227c3a7391 feat: use Display to pretty-print the arrays 2022-04-20 17:01:48 -07:00
Marcos Gutiérrez Alonso
0c485c43fc Added optional table 2022-04-20 16:32:05 -07:00
Marcos Gutiérrez Alonso
000b742a22 Added optional depends and optdepends 2022-04-20 16:32:05 -07:00
Colin Woodbury
861fa59aa3 fix: allow [[bin]] to be missing 2022-04-20 16:29:14 -07:00
Colin Woodbury
6a1269e4ae docs: update CHANGELOG 2022-04-20 16:19:40 -07:00
Colin Woodbury
7e353578c0 feat: alter which names appear where 2022-04-20 09:56:01 -07:00
Colin Woodbury
f837fd18a6 feat: account for custom binary names 2022-04-18 21:26:26 -07:00
Colin Woodbury
84f4a740b1 chore: bump dependencies 2022-04-18 20:11:03 -07:00
Colin Woodbury
d8105b04b3 Merge pull request #9 from fosskers/dependabot/github_actions/actions/checkout-3
Bump actions/checkout from 2.4.0 to 3
2022-03-04 12:57:53 -08:00
dependabot[bot]
c64d112a5d Bump actions/checkout from 2.4.0 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3.
- [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.4.0...v3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-02 14:02:38 +00:00
Colin Woodbury
428b273d91 Merge pull request #8 from fosskers/dependabot/github_actions/actions/checkout-2.4.0
Bump actions/checkout from 2.3.5 to 2.4.0
2021-11-06 18:51:23 +01:00
dependabot[bot]
a8c3736539 Bump actions/checkout from 2.3.5 to 2.4.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.5 to 2.4.0.
- [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.3.5...v2.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-03 14:01:38 +00:00
Colin Woodbury
5b413061b5 Merge pull request #7 from fosskers/dependabot/github_actions/actions/checkout-2.3.5
Bump actions/checkout from 2.3.4 to 2.3.5
2021-10-21 13:59:33 -07:00
dependabot[bot]
a32a16489f Bump actions/checkout from 2.3.4 to 2.3.5
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.4 to 2.3.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/v2.3.4...v2.3.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-18 14:01:21 +00:00
Colin Woodbury
d6f9faefdd 1.4.1 2021-09-06 18:17:45 -07:00
anteater
8dbdc1ab71 use CARGO_TARGET_DIR from the environment if set
in doing so, usr OsStr rather than string to support a wider variety of paths
2021-09-06 19:47:42 +00:00
Colin Woodbury
b7eb36b74d Minor doc tweaks 2021-06-08 08:35:34 -07:00
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
Colin Woodbury
8903c72da6 1.1.2 2020-08-11 14:24:24 -07:00
Colin Woodbury
109d9a66a7 Mention --musl in the README 2020-08-11 14:20:52 -07:00
Colin Woodbury
8e223c5a2d Warn if the MUSL target is missing when using --musl 2020-08-11 14:14:26 -07:00
Colin Woodbury
b1383ff6ff Strip the release binary before tarring it 2020-08-11 13:49:18 -07:00
8 changed files with 720 additions and 133 deletions

View File

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

View File

@@ -1,5 +1,108 @@
# `cargo-aur` Changelog
## 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
- Support for `[[bin]]` sections in `Cargo.toml`, allowing you to specify custom
binary names separate from the package name. [#13]
- Support for specifying PKGBUILD `depends` and `optdepends` via
`[package.metadata]`, as in:
```toml
[package.metadata]
depends = ["nachos", "pizza"]
optdepends = ["sushi", "ramen"]
```
[#13]: https://github.com/fosskers/cargo-aur/pull/13
## 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

View File

@@ -1,8 +1,8 @@
[package]
name = "cargo-aur"
version = "1.1.1"
version = "1.7.1"
authors = ["Colin Woodbury <colin@fosskers.ca>"]
edition = "2018"
edition = "2021"
description = "Prepare Rust projects to be released on the Arch Linux User Repository."
homepage = "https://github.com/fosskers/cargo-aur"
repository = "https://github.com/fosskers/cargo-aur"
@@ -12,13 +12,19 @@ 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"
serde = "1.0"
serde_derive = "1.0"
toml = "0.5"
hmac-sha256 = "1.1"
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"]]

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.

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
@@ -29,13 +29,16 @@ cargo install cargo-aur
## Usage
### Basics
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.
@@ -61,3 +64,60 @@ 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.
### Custom Binary Names
If you specify a `[[bin]]` section in your `Cargo.toml` and set the `name`
field, this will be used as the binary name to install within the PKGBUILD.
### `depends` and `optdepends`
If your package requires other Arch packages at runtime, you can specify these
within your `Cargo.toml` like this:
```toml
[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"
}
```
### Static Binaries
Run with `--musl` to produce a release binary that is statically linked via
[MUSL][2].
```
> cargo aur --musl
> cd target/x86_64-unknown-linux-musl/release/
> 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

52
src/error.rs Normal file
View File

@@ -0,0 +1,52 @@
//! Errors that can occur in this application.
use std::{fmt::Display, path::PathBuf};
pub(crate) enum Error {
IO(std::io::Error),
Toml(toml::de::Error),
Utf8(std::str::Utf8Error),
Utf8OsString,
MissingMuslTarget,
MissingLicense,
TargetNotAbsolute(PathBuf),
}
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::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())
}
}
}
}
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)
}
}

167
src/lib.rs Normal file
View File

@@ -0,0 +1,167 @@
//! 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)>,
}

View File

@@ -1,173 +1,351 @@
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::fs;
use std::process::{self, Command};
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, ExitCode};
/// 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] = &[
"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,
#[options(free)]
args: Vec<String>,
/// Display the current version of this software.
version: bool,
/// Set a custom output directory (default: target/).
output: Option<PathBuf>,
/// Use the MUSL build target to produce a static binary.
musl: 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
),
}
}
/// Don't actually build anything.
dryrun: bool,
/// Absorbs any extra junk arguments.
#[options(free)]
free: Vec<String>,
}
#[derive(Deserialize, Debug)]
struct Config {
package: Package,
#[serde(default)]
bin: Vec<Binary>,
}
impl Config {
/// The name of the compiled binary that should be copied to the tarball.
fn binary_name(&self) -> &str {
self.bin
.first()
.map(|bin| bin.name.as_str())
.unwrap_or(self.package.name.as_str())
}
}
#[derive(Deserialize, Debug)]
struct Package {
struct Binary {
name: String,
version: String,
authors: Vec<String>,
description: String,
homepage: String,
repository: String,
license: 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 let Err(e) = work(args) {
eprintln!("{}", e);
process::exit(1)
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);
ExitCode::FAILURE
} else {
println!("{} {}", "::".bold(), "Done.".bold().green());
ExitCode::SUCCESS
}
}
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()?
}
// 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()?;
release_build(args.musl)?;
tarball(args.musl, &config.package)?;
let sha256 = sha256sum(&config.package)?;
let pkgbuild = pkgbuild(&config.package, &sha256);
fs::write("PKGBUILD", pkgbuild)?;
// 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()?)
} else {
None
};
if args.dryrun.not() {
release_build(args.musl)?;
tarball(args.musl, &cargo_target, &output, license.as_ref(), &config)?;
let sha256: String = sha256sum(&config.package, &output)?;
// Write the PKGBUILD.
let path = output.join("PKGBUILD");
let file = BufWriter::new(File::create(path)?);
pkgbuild(file, &config, &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.
/// 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)
}
/// 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 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()
}
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>(
mut file: T,
config: &Config,
sha256: &str,
license: Option<&DirEntry>,
) -> 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()
.unwrap_or(GitHost::Github)
.source(&config.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.url())?;
writeln!(file, "license=(\"{}\")", package.license)?;
writeln!(file, "arch=(\"x86_64\")")?;
writeln!(file, "provides=(\"{}\")", package.name)?;
writeln!(file, "conflicts=(\"{}\")", package.name)?;
match package.metadata.as_ref() {
Some(metadata) if metadata.non_empty() => {
writeln!(file, "{}", metadata)?;
}
Some(_) | None => {}
}
writeln!(file, "source=(\"{}\")", source)?;
writeln!(file, "sha256sums=(\"{}\")", sha256)?;
writeln!(file)?;
writeln!(file, "package() {{")?;
writeln!(
file,
" install -Dm755 {} -t \"$pkgdir/usr/bin\"",
config.binary_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
)?;
}
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()
)?;
}
}
}
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)
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 {
format!("target/release/{}", package.name)
"release"
};
fs::copy(binary, &package.name)?;
Command::new("tar")
let binary_name = config.binary_name();
let binary = cargo_target.join(release_dir).join(binary_name);
strip(&binary)?;
std::fs::copy(binary, binary_name)?;
// Create the tarball.
p("Packing tarball...".bold());
let mut command = Command::new("tar");
command
.arg("czf")
.arg(package.tarball())
.arg(&package.name)
.status()?;
fs::remove_file(&package.name)?;
.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)?;
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, 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)
}
/// Does the user have the `x86_64-unknown-linux-musl` target installed?
fn musl_check() -> Result<(), Error> {
let args = ["target", "list", "--installed"];
let output = Command::new("rustup").args(args).output()?.stdout;
std::str::from_utf8(&output)?
.lines()
.any(|tc| tc == "x86_64-unknown-linux-musl")
.then_some(())
.ok_or(Error::MissingMuslTarget)
}
fn p(msg: ColoredString) {
println!("{} {}", "::".bold(), msg)
}