2 Commits

Author SHA1 Message Date
Sylvestre Ledru 8cff2df69a release: version 0.5.0 2024-04-04 00:17:25 +02:00
Sylvestre Ledru d35249ffab chore: wow shiny new cargo-dist CI 2024-04-04 00:08:20 +02:00
71 changed files with 2555 additions and 10761 deletions
-15
View File
@@ -1,15 +0,0 @@
name: Security audit
on:
schedule:
- cron: "0 0 * * *"
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
+76 -46
View File
@@ -2,9 +2,6 @@ on: [push, pull_request]
name: Basic CI
env:
CARGO_INCREMENTAL: "0"
jobs:
check:
name: cargo check
@@ -13,7 +10,13 @@ jobs:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install `rust` toolchain
run: |
## Install `rust` toolchain
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
rustup default stable
# For bindgen: https://github.com/rust-lang/rust-bindgen/issues/1797
- uses: KyleMayes/install-llvm-action@v2
if: matrix.os == 'windows-latest'
@@ -34,7 +37,13 @@ jobs:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install `rust` toolchain
run: |
## Install `rust` toolchain
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
rustup default stable
# For bindgen: https://github.com/rust-lang/rust-bindgen/issues/1797
- uses: KyleMayes/install-llvm-action@v2
if: matrix.os == 'windows-latest'
@@ -44,15 +53,22 @@ jobs:
- run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
if: matrix.os == 'windows-latest'
- name: Test
- name: Check
run: |
cargo test
cargo check
fmt:
name: cargo fmt --all -- --check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install `rust` toolchain
run: |
## Install `rust` toolchain
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
rustup default stable
- run: rustup component add rustfmt
- name: cargo fmt
run: |
cargo fmt --all -- --check
@@ -61,32 +77,16 @@ jobs:
name: cargo clippy -- -D warnings
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: |
cargo clippy --all-targets -- -D warnings
wasm:
name: cargo build (wasm32-wasip1)
runs-on: ubuntu-latest
env:
WASI_SDK_VERSION: "33"
WASI_SDK_PATH: ${{ github.workspace }}/wasi-sdk
# onig_sys compiles bundled C, so it needs a C compiler with a WASI
# sysroot to cross-compile to wasm.
CC_wasm32_wasip1: ${{ github.workspace }}/wasi-sdk/bin/clang
AR_wasm32_wasip1: ${{ github.workspace }}/wasi-sdk/bin/llvm-ar
steps:
- uses: actions/checkout@v6
- name: Install wasm target
run: rustup target add wasm32-wasip1
- name: Install WASI SDK
- uses: actions/checkout@v4
- name: Install `rust` toolchain
run: |
curl -sSfL "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/wasi-sdk-${WASI_SDK_VERSION}.0-x86_64-linux.tar.gz" | tar xz
mv "wasi-sdk-${WASI_SDK_VERSION}.0-x86_64-linux" "${WASI_SDK_PATH}"
- name: Build
run: cargo build --target wasm32-wasip1
- name: Clippy
run: cargo clippy --target wasm32-wasip1 -- -D warnings
## Install `rust` toolchain
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
rustup default stable
- run: rustup component add clippy
- name: cargo clippy
run: |
cargo clippy -- -D warnings
grcov:
name: Code coverage
@@ -95,29 +95,59 @@ jobs:
matrix:
os:
- ubuntu-latest
toolchain:
- nightly
cargo_flags:
- "--all-features"
steps:
- name: Checkout source code
uses: actions/checkout@v6
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate code coverage
env:
RUSTC_BOOTSTRAP: 1
run: |
cargo llvm-cov --all-features --lcov --branch --output-path lcov.info
uses: actions/checkout@v4
- name: Install `rust` toolchain
run: |
## Install `rust` toolchain
rustup toolchain install nightly --no-self-update -c rustfmt --profile minimal
rustup default nightly
- name: "`grcov` ~ install"
run: cargo install grcov
- name: cargo test
run: |
cargo test --all --no-fail-fast ${{ matrix.cargo_flags }}
env:
CARGO_INCREMENTAL: "0"
RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off'
RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off'
- name: Generate coverage data
id: grcov
run: |
grcov target/debug/ \
--branch \
--llvm \
--source-dir . \
--output-path lcov.info \
--ignore-not-existing \
--excl-line "#\\[derive\\(" \
--excl-br-line "#\\[derive\\(" \
--excl-start "#\\[cfg\\(test\\)\\]" \
--excl-br-start "#\\[cfg\\(test\\)\\]" \
--commit-sha ${{ github.sha }} \
--service-job-id ${{ github.job }} \
--service-name "GitHub Actions" \
--service-number ${{ github.run_id }}
- name: Upload coverage as artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: lcov.info
# path: ${{ steps.grcov.outputs.report }}
path: lcov.info
- name: Upload coverage to codecov.io
uses: codecov/codecov-action@v7
uses: codecov/codecov-action@v4
with:
files: lcov.info
token: ${{ secrets.CODECOV_TOKEN }}
# file: ${{ steps.grcov.outputs.report }}
file: lcov.info
fail_ci_if_error: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
-37
View File
@@ -1,37 +0,0 @@
name: CodSpeed
on:
push:
branches:
- "main"
pull_request:
# `workflow_dispatch` allows CodSpeed to trigger backtest
# performance analysis in order to generate initial data.
workflow_dispatch:
permissions:
contents: read
id-token: write
jobs:
codspeed:
name: Run benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup Rust toolchain, cache and cargo-codspeed binary
uses: moonrepo/setup-rust@v1
with:
channel: stable
cache-target: release
bins: cargo-codspeed
- name: Build the benchmark target(s)
run: cargo codspeed build
- name: Run the benchmarks
uses: CodSpeedHQ/action@v4
with:
mode: simulation
run: cargo codspeed run
-76
View File
@@ -1,76 +0,0 @@
on:
workflow_run:
workflows: [External-testsuites]
types: [completed]
name: Comment Test results on the PR
permissions: {}
jobs:
upload-pr-comment:
if: ${{ github.event.workflow_run.event == 'pull_request' }}
name: Upload PR comment
runs-on: ubuntu-latest
permissions:
actions: read
pull-requests: write
steps:
- name: List Annotations
uses: actions/github-script@v9
with:
script: |
let artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }},
});
// List all artifacts
let matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "comment"
})[0];
// Download the artifact to github.workspace
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync('${{ github.workspace }}/comment.zip', Buffer.from(download.data));
- run: unzip comment.zip
- name: Comment on PR
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
let fs = require('fs');
let annotations = JSON.parse(fs.readFileSync('./annotations.json', 'utf8'));
let annotationContent = annotations
.data
.map(annotation => `${annotation.run}: ${annotation.annotation.message}`)
.join('\n');
// check if no changes
let gnuTestReport = annotationContent.includes('Run GNU findutils tests: Gnu tests No changes');
let bfsTestReport = annotationContent.includes('Run BFS tests: BFS tests No changes');
if (gnuTestReport && bfsTestReport) {
console.log('No changes');
return;
}
// Comment on the PR
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: annotations.pull_request_number,
body: 'Commit ${{ github.event.workflow_run.head_sha }} has GNU testsuite comparison:\n```\n' + annotationContent + '\n```\n'
});
+137 -249
View File
@@ -1,261 +1,149 @@
on: [push, pull_request]
name: External-testsuites
env:
CARGO_INCREMENTAL: "0"
name: External testsuites
jobs:
gnu-tests:
permissions:
actions: read
contents: write # Publish binaries instead of discarding
name: Run GNU findutils tests
runs-on: ubuntu-latest
steps:
- name: Checkout findutils
uses: actions/checkout@v6
with:
path: findutils
- name: Checkout GNU findutils
uses: actions/checkout@v6
with:
repository: gnu-mirror-unofficial/findutils
path: findutils.gnu
ref: 5768a03ddfb5e18b1682e339d6cdd24ff721c510
submodules: false
- name: Override submodule URL and initialize submodules
# Use github instead of upstream git server
run: |
git submodule sync --recursive
git config submodule.gnulib.url https://github.com/coreutils/gnulib.git
git submodule update --init --recursive --depth 1
working-directory: findutils.gnu
- name: Install dependencies
shell: bash
run: |
# Enable sources & install dependencies
sudo sed -i 's/^Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update
sudo apt-get build-dep findutils
- name: Run GNU tests
shell: bash
run: |
cd findutils
bash util/build-gnu.sh ||:
install --strip -Dm755 target/release/{find,xargs} -t findutils-x86_64-unknown-linux-gnu
ZSTD_CLEVEL=19 tar --zstd -caf ../findutils-x86_64-unknown-linux-gnu.tar.zst findutils-x86_64-unknown-linux-gnu
- name: Publish latest commit
uses: softprops/action-gh-release@v3
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
tag_name: latest-commit
body: |
commit: ${{ github.sha }}
draft: false
prerelease: true
files: |
findutils-x86_64-unknown-linux-gnu.tar.zst
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Extract testing info
shell: bash
run: |
- name: Upload gnu-test-report
uses: actions/upload-artifact@v7
with:
name: gnu-test-report
path: |
findutils.gnu/find/testsuite/*.log
findutils.gnu/xargs/testsuite/*.log
findutils.gnu/tests/**/*.log
- name: Upload gnu-result
uses: actions/upload-artifact@v7
with:
name: gnu-result
path: gnu-result.json
- name: Download artifacts (gnu-result and gnu-test-report)
uses: actions/github-script@v9
with:
script: |
let fs = require('fs');
fs.mkdirSync('${{ github.workspace }}/dl', { recursive: true });
async function downloadArtifact(artifactName) {
// List all artifacts from the workflow run
let artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.run_id }},
});
// Find the specified artifact
let matchArtifact = artifacts.data.artifacts.find((artifact) => artifact.name === artifactName);
if (!matchArtifact) {
throw new Error(`Artifact "${artifactName}" not found.`);
}
// Download the artifact
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
// Save the artifact to a file
fs.writeFileSync(`${{ github.workspace }}/dl/${artifactName}.zip`, Buffer.from(download.data));
}
// Download the required artifacts
await downloadArtifact("gnu-result");
await downloadArtifact("gnu-test-report");
- name: Compare failing tests against master
shell: bash
run: |
./findutils/util/diff-gnu.sh ./dl ./findutils.gnu
- name: Compare against main results
shell: bash
run: |
unzip dl/gnu-result.zip -d dl/
unzip dl/gnu-test-report.zip -d dl/
mv dl/gnu-result.json latest-gnu-result.json
python findutils/util/compare_gnu_result.py
- name: Checkout findutils
uses: actions/checkout@v4
with:
path: findutils
- name: Checkout GNU findutils
uses: actions/checkout@v4
with:
repository: gnu-mirror-unofficial/findutils
path: findutils.gnu
ref: 5768a03ddfb5e18b1682e339d6cdd24ff721c510
submodules: true
- name: Install `rust` toolchain
run: |
## Install `rust` toolchain
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
rustup default stable
- name: Install dependencies
shell: bash
run: |
# Enable sources & install dependencies
sudo find /etc/apt/sources.list* -type f -exec sed -i 'p; s/^deb /deb-src /' '{}' +
sudo apt-get update
sudo apt-get build-dep findutils
- name: Run GNU tests
shell: bash
run: |
cd findutils
bash util/build-gnu.sh ||:
- name: Extract testing info
shell: bash
run: |
- name: Upload gnu-test-report
uses: actions/upload-artifact@v4
with:
name: gnu-test-report
path: |
findutils.gnu/find/testsuite/*.log
findutils.gnu/xargs/testsuite/*.log
findutils.gnu/tests/**/*.log
- name: Upload gnu-result
uses: actions/upload-artifact@v4
with:
name: gnu-result
path: gnu-result.json
- name: Download the result
uses: dawidd6/action-download-artifact@v3
with:
workflow: compat.yml
workflow_conclusion: completed
name: gnu-result
repo: uutils/findutils
branch: main
path: dl
- name: Download the log
uses: dawidd6/action-download-artifact@v3
with:
workflow: compat.yml
workflow_conclusion: completed
name: gnu-test-report
repo: uutils/findutils
branch: main
path: dl
- name: Compare failing tests against master
shell: bash
run: |
./findutils/util/diff-gnu.sh ./dl ./findutils.gnu
- name: Compare against main results
shell: bash
run: |
mv dl/gnu-result.json latest-gnu-result.json
python findutils/util/compare_gnu_result.py
bfs-tests:
name: Run BFS tests
runs-on: ubuntu-latest
steps:
- name: Checkout findutils
uses: actions/checkout@v6
with:
path: findutils
- name: Checkout BFS
uses: actions/checkout@v6
with:
repository: tavianator/bfs
path: bfs
ref: "4.0"
- name: Install dependencies
shell: bash
run: |
# Enable sources & install dependencies
sudo sed -i 's/^Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update
sudo apt-get build-dep bfs
- name: Run BFS tests
shell: bash
run: |
cd findutils
export CARGO_INCREMENTAL=0
bash util/build-bfs.sh ||:
- name: Upload bfs-test-report
uses: actions/upload-artifact@v7
with:
name: bfs-test-report
path: bfs/tests.log
- name: Upload bfs-result
uses: actions/upload-artifact@v7
with:
name: bfs-result
path: bfs-result.json
- name: Download artifacts (gnu-result and bfs-test-report)
uses: actions/github-script@v9
with:
script: |
let fs = require('fs');
fs.mkdirSync('${{ github.workspace }}/dl', { recursive: true });
async function downloadArtifact(artifactName) {
// List all artifacts from the workflow run
let artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.run_id }},
});
// Find the specified artifact
let matchArtifact = artifacts.data.artifacts.find((artifact) => artifact.name === artifactName);
if (!matchArtifact) {
throw new Error(`Artifact "${artifactName}" not found.`);
}
// Download the artifact
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
// Save the artifact to a file
fs.writeFileSync(`${{ github.workspace }}/dl/${artifactName}.zip`, Buffer.from(download.data));
}
// Download the required artifacts
await downloadArtifact("bfs-result");
await downloadArtifact("bfs-test-report");
- name: Compare failing tests against main
shell: bash
run: |
./findutils/util/diff-bfs.sh dl/tests.log bfs/tests.log
- name: Compare against main results
shell: bash
run: |
unzip dl/bfs-result.zip -d dl/
unzip dl/bfs-test-report.zip -d dl/
mv dl/bfs-result.json latest-bfs-result.json
python findutils/util/compare_bfs_result.py
upload-annotations:
name: Upload annotations
runs-on: ubuntu-latest
needs: [gnu-tests, bfs-tests]
if: ${{ github.event_name == 'pull_request' }}
steps:
- name: List Annotations
uses: actions/github-script@v9
with:
script: |
let runs = await github.rest.checks.listForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: '${{ github.event.pull_request.head.sha }}'
});
let names = ['Run GNU findutils tests', 'Run BFS tests'];
let results = [];
runs.data.check_runs.filter(check => names.includes(check.name)).forEach(run => results.push(run));
let annotations = { data: [], pull_request_number: '${{ github.event.number }}' };
for (let result of results) {
let run = await github.rest.checks.listAnnotations({
owner: context.repo.owner,
repo: context.repo.repo,
check_run_id: result.id
});
run.data.forEach(data => {
annotations.data.push({
run: result.name,
annotation: data
});
});
}
// Remove duplicate items.
annotations.data = annotations.data.filter((value, index, self) =>
self.findIndex(v => v.annotation.message === value.annotation.message) === index);
let fs = require('fs');
fs.writeFileSync('${{ github.workspace }}/annotations.json', JSON.stringify(annotations));
- name: Upload annotations
uses: actions/upload-artifact@v7
with:
name: comment
path: annotations.json
- name: Checkout findutils
uses: actions/checkout@v4
with:
path: findutils
- name: Checkout BFS
uses: actions/checkout@v4
with:
repository: tavianator/bfs
path: bfs
ref: '3.1.3'
- name: Install `rust` toolchain
run: |
## Install `rust` toolchain
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
rustup default stable
- name: Install dependencies
shell: bash
run: |
# Enable sources & install dependencies
sudo find /etc/apt/sources.list* -type f -exec sed -i 'p; s/^deb /deb-src /' '{}' +
sudo apt-get update
sudo apt-get build-dep bfs
- name: Run BFS tests
shell: bash
run: |
cd findutils
bash util/build-bfs.sh ||:
- name: Upload bfs-test-report
uses: actions/upload-artifact@v4
with:
name: bfs-test-report
path: bfs/tests.log
- name: Upload bfs-result
uses: actions/upload-artifact@v4
with:
name: bfs-result
path: bfs-result.json
- name: Download the result
uses: dawidd6/action-download-artifact@v3
with:
workflow: compat.yml
workflow_conclusion: completed
name: bfs-result
repo: uutils/findutils
branch: main
path: dl
- name: Download the log
uses: dawidd6/action-download-artifact@v3
with:
workflow: compat.yml
workflow_conclusion: completed
name: bfs-test-report
repo: uutils/findutils
branch: main
path: dl
- name: Compare failing tests against main
shell: bash
run: |
./findutils/util/diff-bfs.sh dl/tests.log bfs/tests.log
- name: Compare against main results
shell: bash
run: |
mv dl/bfs-result.json latest-bfs-result.json
python findutils/util/compare_bfs_result.py
+69 -94
View File
@@ -1,21 +1,20 @@
# This file was autogenerated by dist: https://opensource.axo.dev/cargo-dist/
#
# Copyright 2022-2024, axodotdev
# Copyright 2022-2023, axodotdev
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
#
# * checks for a Git Tag that looks like a release
# * builds artifacts with dist (archives, installers, hashes)
# * builds artifacts with cargo-dist (archives, installers, hashes)
# * uploads those artifacts to temporary workflow zip
# * on success, uploads the artifacts to a GitHub Release
# * on success, uploads the artifacts to a Github Release
#
# Note that the GitHub Release will be created with a generated
# Note that the Github Release will be created with a generated
# title/body based on your changelogs.
name: Release
permissions:
"contents": "write"
contents: write
# This task will run whenever you push a git tag that looks like a version
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
@@ -24,30 +23,30 @@ permissions:
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
#
# If PACKAGE_NAME is specified, then the announcement will be for that
# package (erroring out if it doesn't have the given version or isn't dist-able).
# package (erroring out if it doesn't have the given version or isn't cargo-dist-able).
#
# If PACKAGE_NAME isn't specified, then the announcement will be for all
# (dist-able) packages in the workspace with that version (this mode is
# (cargo-dist-able) packages in the workspace with that version (this mode is
# intended for workspaces with only one dist-able package, or with all dist-able
# packages versioned/released in lockstep).
#
# If you push multiple tags at once, separate instances of this workflow will
# spin up, creating an independent announcement for each one. However, GitHub
# spin up, creating an independent announcement for each one. However Github
# will hard limit this to 3 tags per commit, as it will assume more tags is a
# mistake.
#
# If there's a prerelease-style suffix to the version, then the release(s)
# will be marked as a prerelease.
on:
pull_request:
push:
tags:
- '**[0-9]+.[0-9]+.[0-9]+*'
pull_request:
jobs:
# Run 'dist plan' (or host) to determine what tasks we need to do
# Run 'cargo dist plan' (or host) to determine what tasks we need to do
plan:
runs-on: "ubuntu-latest"
runs-on: ubuntu-latest
outputs:
val: ${{ steps.plan.outputs.manifest }}
tag: ${{ !github.event.pull_request && github.ref_name || '' }}
@@ -56,19 +55,14 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dist
- name: Install cargo-dist
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.28.0/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@v7
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.12.0/cargo-dist-installer.sh | sh"
# sure would be cool if github gave us proper conditionals...
# so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
# functionality based on whether this is a pull_request, and whether it's from a fork.
@@ -76,12 +70,12 @@ jobs:
# but also really annoying to build CI around when it needs secrets to work right.)
- id: plan
run: |
dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json
echo "dist ran successfully"
cargo dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json
echo "cargo dist ran successfully"
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
@@ -95,41 +89,31 @@ jobs:
if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }}
strategy:
fail-fast: false
# Target platforms/runners are computed by dist in create-release.
# Target platforms/runners are computed by cargo-dist in create-release.
# Each member of the matrix has the following arguments:
#
# - runner: the github runner
# - dist-args: cli flags to pass to dist
# - install-dist: expression to run to install dist on the runner
# - dist-args: cli flags to pass to cargo dist
# - install-dist: expression to run to install cargo-dist on the runner
#
# Typically there will be:
# - 1 "global" task that builds universal installers
# - N "local" tasks that build each platform's binaries and platform-specific installers
matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
runs-on: ${{ matrix.runner }}
container: ${{ matrix.container && matrix.container.image || null }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
steps:
- name: enable windows longpaths
run: |
git config --global core.longpaths true
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Rust non-interactively if not already installed
if: ${{ matrix.container }}
run: |
if ! command -v cargo > /dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
fi
- name: Install dist
run: ${{ matrix.install_dist.run }}
- uses: swatinem/rust-cache@v2
- name: Install cargo-dist
run: ${{ matrix.install_dist }}
# Get the dist-manifest
- name: Fetch local artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
@@ -140,8 +124,8 @@ jobs:
- name: Build artifacts
run: |
# Actually do builds and make zips and whatnot
dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
echo "dist ran successfully"
cargo dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
echo "cargo dist ran successfully"
- id: cargo-dist
name: Post-build
# We force bash here just because github makes it really hard to get values up
@@ -151,12 +135,12 @@ jobs:
run: |
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT"
jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
path: |
@@ -168,23 +152,20 @@ jobs:
needs:
- plan
- build-local-artifacts
runs-on: "ubuntu-latest"
runs-on: "ubuntu-20.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v8
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
- name: Install cargo-dist
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.12.0/cargo-dist-installer.sh | sh"
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
@@ -192,17 +173,17 @@ jobs:
- id: cargo-dist
shell: bash
run: |
dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
echo "dist ran successfully"
cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
echo "cargo dist ran successfully"
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: artifacts-build-global
path: |
@@ -218,62 +199,38 @@ jobs:
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "ubuntu-latest"
runs-on: "ubuntu-20.04"
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v8
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
- name: Install cargo-dist
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.12.0/cargo-dist-installer.sh | sh"
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
# This is a harmless no-op for Github Releases, hosting for that happens in "announce"
- id: host
shell: bash
run: |
dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
cargo dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
echo "artifacts uploaded and released successfully"
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
path: dist-manifest.json
# Create a GitHub Release while uploading all files to it
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@v8
with:
pattern: artifacts-*
path: artifacts
merge-multiple: true
- name: Cleanup
run: |
# Remove the granular manifests
rm -f artifacts/*-dist-manifest.json
- name: Create GitHub Release
env:
PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}"
ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}"
ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}"
RELEASE_COMMIT: "${{ github.sha }}"
run: |
# Write and read notes from a file to avoid quoting breaking things
echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt
gh release create "${{ needs.plan.outputs.tag }}" --draft --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*
# Create a Github Release while uploading all files to it
announce:
needs:
- plan
@@ -282,10 +239,28 @@ jobs:
# still allowing individual publish jobs to skip themselves (for prereleases).
# "host" however must run to completion, no skipping allowed!
if: ${{ always() && needs.host.result == 'success' }}
runs-on: "ubuntu-latest"
runs-on: "ubuntu-20.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
submodules: recursive
- name: "Download Github Artifacts"
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: artifacts
merge-multiple: true
- name: Cleanup
run: |
# Remove the granular manifests
rm -f artifacts/*-dist-manifest.json
- name: Create Github Release
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.plan.outputs.tag }}
name: ${{ fromJson(needs.host.outputs.val).announcement_title }}
body: ${{ fromJson(needs.host.outputs.val).announcement_github_body }}
prerelease: ${{ fromJson(needs.host.outputs.val).announcement_is_prerelease }}
artifacts: "artifacts/*"
-2
View File
@@ -15,5 +15,3 @@ repos:
pass_filenames: false
types: [file, rust]
language: system
ci:
skip: [rust-linting, rust-clippy]
+5 -3
View File
@@ -11,8 +11,6 @@ check out these documents:
- Our community's [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
- [DEVELOPMENT.md](./DEVELOPMENT.md) for setting up your development
environment.
- Our [Review Guidelines](https://uutils.github.io/reviews/) for what we expect
from a pull request and how reviews are done.
Now follows a very important warning:
@@ -182,7 +180,7 @@ To ensure easy collaboration, we have guidelines for using Git and GitHub.
### Commit messages
You can read this section in the Git book to learn how to write good commit
messages: https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project.
messages: https://git-scm.com/book/ch5-2.html.
In addition, here are a few examples for a summary line when committing to
uutils:
@@ -233,6 +231,10 @@ you contribute must at least compile without warnings for all platforms in the
CI. However, you can use `#[cfg(...)]` attributes to create platform dependent
features.
**Tip:** For Windows, Microsoft provides some images (VMWare, Hyper-V,
VirtualBox and Parallels) for development:
<https://developer.microsoft.com/windows/downloads/virtual-machines/>
## Licensing
uutils is distributed under the terms of the MIT License; see the `LICENSE` file
Generated
+396 -1494
View File
File diff suppressed because it is too large Load Diff
+26 -86
View File
@@ -1,6 +1,6 @@
[package]
name = "findutils"
version = "0.9.0"
version = "0.5.0"
homepage = "https://github.com/uutils/findutils"
repository = "https://github.com/uutils/findutils"
edition = "2021"
@@ -10,56 +10,28 @@ description = "Rust implementation of GNU findutils"
authors = ["uutils developers"]
[dependencies]
argmax = "0.4.0"
chrono = "0.4.45"
clap = { version = "4.6", features = ["env"] }
chrono = "0.4.37"
clap = "4.5"
faccess = "0.2.4"
nix = { version = "0.31", features = ["fs", "user"] }
onig = { version = "6.5", default-features = false }
regex = "1.12"
uucore = { version = "0.9.0", features = ["entries", "fs", "fsext", "mode"] }
walkdir = "2.5"
itertools = "0.14.0"
thiserror = "2.0.12"
regex = "1.7"
once_cell = "1.19"
onig = { version = "6.4", default-features = false }
uucore = { version = "0.0.25", features = ["entries", "fs", "fsext", "mode"] }
[dev-dependencies]
criterion = { version = "4.7.0", package = "codspeed-criterion-compat" }
assert_cmd = "2"
ctor = "1.0"
filetime = "0.2"
nix = { version = "0.31", features = ["fs"] }
rstest = "0.26.1"
nix = { version = "0.28", features = ["fs"] }
predicates = "3"
serial_test = "3.0"
tempfile = "3"
uutests = "0.9.0"
[[bench]]
name = "find_bench"
harness = false
[[bench]]
name = "xargs_bench"
harness = false
[[bench]]
name = "updatedb_bench"
harness = false
[[bench]]
name = "locate_bench"
harness = false
pretty_assertions = "1.4.0"
[[bin]]
name = "find"
path = "src/find/main.rs"
[[bin]]
name = "locate"
path = "src/locate/main.rs"
[[bin]]
name = "updatedb"
path = "src/updatedb/main.rs"
[[bin]]
name = "xargs"
path = "src/xargs/main.rs"
@@ -68,52 +40,20 @@ path = "src/xargs/main.rs"
name = "testing-commandline"
path = "src/testing/commandline/main.rs"
[profile.release]
lto = "thin"
codegen-units = 1
[profile.release-fast]
# The profile that 'cargo dist' will build with
[profile.dist]
inherits = "release"
panic = "abort"
lto = "thin"
[lints.clippy]
all = { level = "warn", priority = -1 }
cargo = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
use_self = "warn" # nursery lint
# Group-level allows for lints that are too noisy or not worth enforcing for now.
multiple_crate_versions = "allow"
cargo_common_metadata = "allow"
uninlined_format_args = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
must_use_candidate = "allow"
match_same_arms = "allow"
cast_possible_truncation = "allow"
too_many_lines = "allow"
cast_possible_wrap = "allow"
cast_sign_loss = "allow"
struct_excessive_bools = "allow"
cast_precision_loss = "allow"
cast_lossless = "allow"
ignored_unit_patterns = "allow"
similar_names = "allow"
float_cmp = "allow"
return_self_not_must_use = "allow"
inline_always = "allow"
fn_params_excessive_bools = "allow"
used_underscore_items = "allow"
should_panic_without_expect = "allow"
doc_markdown = "allow"
unused_self = "allow"
enum_glob_use = "allow"
unnested_or_patterns = "allow"
implicit_hasher = "allow"
doc_link_with_quotes = "allow"
format_push_string = "allow"
flat_map_option = "allow"
from_iter_instead_of_collect = "allow"
large_types_passed_by_value = "allow"
# Disable for now, we have a few occurrences
# panic = "warn"
# Config for 'cargo dist'
[workspace.metadata.dist]
# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.12.0"
# CI backends to support
ci = ["github"]
# The installers to generate for each app
installers = []
# Target platforms to build apps for (Rust target-triple syntax)
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
# Publish jobs to run in CI
pr-run-mode = "plan"
+2 -2
View File
@@ -20,8 +20,8 @@ bash util/build-gnu.sh tests/misc/help-version.sh
## Comparing with GNU
![Evolution over time - GNU testsuite](https://github.com/uutils/findutils-tracking/blob/main/gnu-results.svg?raw=true)
![Evolution over time - BFS testsuite](https://github.com/uutils/findutils-tracking/blob/main/bfs-results.svg?raw=true)
![Evolution over time - GNU testsuite](https://github.com/uutils/findutils-tracking/blob/main/gnu-results.png?raw=true)
![Evolution over time - BFS testsuite](https://github.com/uutils/findutils-tracking/blob/main/bfs-results.png?raw=true)
## Build/run with BFS
-44
View File
@@ -1,44 +0,0 @@
# Security Policy
## Supported Versions
We provide security updates only for the latest released version of `uutils/findutils`.
Older versions may not receive patches.
If you are using a version packaged by your Linux distribution, please check with your distribution maintainers for their update policy.
---
## Reporting a Vulnerability
**Do not open public GitHub issues for security vulnerabilities.**
This prevents accidental disclosure before a fix is available.
Instead, please use the following method:
- **Email:** [sylvestre@debian.org](mailto:Sylvestre@debian.org)
- **Encryption (optional):** You may encrypt your report using our PGP key:
Fingerprint: B60D B599 4D39 BEC4 D1A9 5CCF 7E65 28DA 752F 1BE1
---
### What to Include in Your Report
To help us investigate and resolve the issue quickly, please include as much detail as possible:
- **Type of issue:** e.g. privilege escalation, information disclosure.
- **Location in the source:** file path, commit hash, branch, or tag.
- **Steps to reproduce:** exact commands, test cases, or scripts.
- **Special configuration:** any flags, environment variables, or system setup required.
- **Affected systems:** OS/distribution and version(s) where the issue occurs.
- **Impact:** your assessment of the potential severity (DoS, RCE, data leak, etc.).
---
## Disclosure Policy
We follow a **Coordinated Vulnerability Disclosure (CVD)** process:
1. We will acknowledge receipt of your report within **10 days**.
2. We will investigate, reproduce, and assess the issue.
3. We will provide a timeline for developing and releasing a fix.
4. Once a fix is available, we will publish a GitHub Security Advisory.
5. You will be credited in the advisory unless you request anonymity.
-166
View File
@@ -1,166 +0,0 @@
// Copyright 2024 the uutils developers
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
//! End-to-end benchmarks for `find`, run through the real `find_main` entry
//! point so they exercise argument parsing, the matcher tree and the directory
//! walk together. Output is sent to a sink so the benchmarks measure the work
//! `find` does rather than terminal I/O.
use std::cell::RefCell;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use findutils::find::{find_main, Dependencies};
/// `Dependencies` implementation that throws output away. A fixed `now` keeps
/// time-based matchers (`-newer`, `-mtime`, …) deterministic across runs.
struct SinkDependencies {
output: RefCell<io::Sink>,
now: SystemTime,
}
impl SinkDependencies {
fn new() -> Self {
Self {
output: RefCell::new(io::sink()),
now: SystemTime::now(),
}
}
}
impl Dependencies for SinkDependencies {
fn get_output(&self) -> &RefCell<dyn Write> {
&self.output
}
fn now(&self) -> SystemTime {
self.now
}
fn confirm(&self, _prompt: &str) -> bool {
false
}
}
/// Run `find` end-to-end. `args` are the arguments after the program name
/// (flags, paths, expression). The exit status is ignored — we only care about
/// the work performed.
fn run(args: &[&str]) {
let mut argv: Vec<&str> = Vec::with_capacity(args.len() + 1);
argv.push("find");
argv.extend_from_slice(args);
let deps = SinkDependencies::new();
let _ = find_main(&argv, &deps);
}
/// Build a moderately deep directory tree to walk. `depth` levels, each holding
/// `dirs` sub-directories and `files` files. File names vary so name/regex
/// matchers have something to discriminate on, and sizes vary so `-size` does
/// real work. Returns the root directory.
fn build_tree(depth: u32, dirs: u32, files: u32) -> PathBuf {
let root = std::env::temp_dir().join(format!("uu_find_bench_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).unwrap();
populate(&root, depth, dirs, files, 0);
root
}
fn populate(dir: &Path, depth: u32, dirs: u32, files: u32, seed: u32) {
for f in 0..files {
let n = seed.wrapping_add(f);
// A mix of extensions and a sprinkling of a rare marker name.
let name = match n % 5 {
0 => format!("module_{n}.rs"),
1 => format!("data_{n}.txt"),
2 => format!("image_{n}.png"),
3 => format!("README_{n}.md"),
_ if n % 500 == 0 => format!("RAREHIT_{n}.log"),
_ => format!("file_{n}.log"),
};
// Sizes from a few bytes up to ~8 KiB so -size buckets differ.
let size = (n as usize % 8192) + 1;
let path = dir.join(name);
std::fs::write(&path, vec![b'x'; size]).unwrap();
}
if depth == 0 {
return;
}
for d in 0..dirs {
let sub = dir.join(format!("dir_{d}"));
std::fs::create_dir_all(&sub).unwrap();
populate(
&sub,
depth - 1,
dirs,
files,
seed.wrapping_add((d + 1) * 31),
);
}
}
fn bench_e2e(c: &mut Criterion) {
// depth 4, 4 dirs/level, 25 files/dir → a few thousand entries.
let root = build_tree(4, 4, 25);
let dir = root.to_str().unwrap();
let mut group = c.benchmark_group("find");
// Plain full walk with the implicit -print.
group.bench_function("walk_all", |b| {
b.iter(|| run(black_box(&[dir])));
});
// Filter by file type only.
group.bench_function("type_f", |b| {
b.iter(|| run(black_box(&[dir, "-type", "f"])));
});
// Glob name match — a common invocation.
group.bench_function("name_glob", |b| {
b.iter(|| run(black_box(&[dir, "-name", "*.rs"])));
});
// Case-insensitive name match.
group.bench_function("iname_glob", |b| {
b.iter(|| run(black_box(&[dir, "-iname", "*.RS"])));
});
// Regex over the whole path.
group.bench_function("regex_path", |b| {
b.iter(|| run(black_box(&[dir, "-regex", r".*/module_[0-9]+\.rs"])));
});
// Size predicate forces a stat per entry.
group.bench_function("size_gt", |b| {
b.iter(|| run(black_box(&[dir, "-type", "f", "-size", "+4k"])));
});
// Combined predicate with AND/OR and grouping.
group.bench_function("combined_expr", |b| {
b.iter(|| {
run(black_box(&[
dir, "-type", "f", "(", "-name", "*.rs", "-o", "-name", "*.md", ")",
]));
});
});
// Prune whole subtrees, then print the rest.
group.bench_function("prune", |b| {
b.iter(|| {
run(black_box(&[
dir, "-name", "dir_0", "-prune", "-o", "-type", "f", "-print",
]));
});
});
// -printf with several directives exercises the formatter.
group.bench_function("printf", |b| {
b.iter(|| run(black_box(&[dir, "-type", "f", "-printf", "%p %s %y\\n"])));
});
group.finish();
let _ = std::fs::remove_dir_all(&root);
}
criterion_group!(benches, bench_e2e);
criterion_main!(benches);
-127
View File
@@ -1,127 +0,0 @@
// Copyright 2024 the uutils developers
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
//! End-to-end benchmarks for `locate`, run through the real `locate_main` entry
//! point. A LOCATE02 database is built once (via `updatedb`) from a generated
//! tree, then each case scans it in `-c`/count mode so the database decoding
//! and pattern matching dominate rather than terminal output.
//!
//! `locate` is Unix-only (it relies on `OsStr`/`CStr` byte APIs), so the
//! benchmark body is compiled only there.
#[cfg(unix)]
mod bench {
use std::path::{Path, PathBuf};
use criterion::{black_box, criterion_group, Criterion};
use findutils::locate::locate_main;
use findutils::updatedb::updatedb_main;
/// Run `locate` end-to-end. `args` are the arguments after the program name.
fn run(args: &[&str]) {
let mut argv: Vec<&str> = Vec::with_capacity(args.len() + 1);
argv.push("locate");
argv.extend_from_slice(args);
let _ = locate_main(&argv);
}
fn build_tree(depth: u32, dirs: u32, files: u32) -> PathBuf {
let root = std::env::temp_dir().join(format!("uu_locate_bench_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).unwrap();
populate(&root, depth, dirs, files, 0);
root
}
fn populate(dir: &Path, depth: u32, dirs: u32, files: u32, seed: u32) {
for f in 0..files {
let n = seed.wrapping_add(f);
let name = match n % 4 {
0 => format!("module_{n}.rs"),
1 => format!("data_{n}.txt"),
2 => format!("image_{n}.png"),
_ => format!("file_{n}.log"),
};
std::fs::write(dir.join(name), b"x").unwrap();
}
if depth == 0 {
return;
}
for d in 0..dirs {
let sub = dir.join(format!("dir_{d}"));
std::fs::create_dir_all(&sub).unwrap();
populate(
&sub,
depth - 1,
dirs,
files,
seed.wrapping_add((d + 1) * 31),
);
}
}
pub fn bench_e2e(c: &mut Criterion) {
// ~8.5k paths, then front-coded into a LOCATE02 database once.
let root = build_tree(4, 4, 25);
let dir = root.to_str().unwrap();
let db = std::env::temp_dir().join(format!("uu_locate_bench_{}.db", std::process::id()));
let db_path = db.to_str().unwrap();
let _ = updatedb_main(&[
"updatedb",
&format!("--localpaths={dir}"),
&format!("--output={db_path}"),
"--prunepaths=",
"--prunefs=",
]);
let mut group = c.benchmark_group("locate");
// Pattern matching nothing: forces a full decode + match of every entry.
group.bench_function("count_no_match", |b| {
b.iter(|| run(black_box(&["-d", db_path, "-c", "NONEXISTENT_TOKEN_XYZ"])));
});
// Substring matching many entries (count mode → no per-match output).
group.bench_function("count_many", |b| {
b.iter(|| run(black_box(&["-d", db_path, "-c", ".log"])));
});
// Basename matching (-b) restricts the comparison to the file name.
group.bench_function("basename", |b| {
b.iter(|| run(black_box(&["-d", db_path, "-c", "-b", "module"])));
});
// Case-insensitive matching (-i).
group.bench_function("ignore_case", |b| {
b.iter(|| run(black_box(&["-d", db_path, "-c", "-i", "MODULE"])));
});
// Regex matching (-r) over the whole path.
group.bench_function("regex", |b| {
b.iter(|| {
run(black_box(&[
"-d",
db_path,
"-c",
"-r",
r"module_[0-9]+\.rs",
]));
});
});
group.finish();
let _ = std::fs::remove_dir_all(&root);
let _ = std::fs::remove_file(&db);
}
criterion_group!(benches, bench_e2e);
}
#[cfg(unix)]
criterion::criterion_main!(bench::benches);
#[cfg(not(unix))]
fn main() {}
-110
View File
@@ -1,110 +0,0 @@
// Copyright 2024 the uutils developers
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
//! End-to-end benchmarks for `updatedb`, run through the real `updatedb_main`
//! entry point. Each case walks a generated directory tree (via the internal
//! `find`), encodes it into the LOCATE02 database format and writes the result
//! to a temp file, so the walk + front-coding + write dominate the measurement.
use std::path::{Path, PathBuf};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use findutils::updatedb::updatedb_main;
/// Run `updatedb` end-to-end. `args` are the arguments after the program name.
fn run(args: &[&str]) {
let mut argv: Vec<&str> = Vec::with_capacity(args.len() + 1);
argv.push("updatedb");
argv.extend_from_slice(args);
let _ = updatedb_main(&argv);
}
/// Build a directory tree to index. `depth` levels, each holding `dirs`
/// sub-directories and `files` files with varied names. Returns the root.
fn build_tree(depth: u32, dirs: u32, files: u32) -> PathBuf {
let root = std::env::temp_dir().join(format!("uu_updatedb_bench_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).unwrap();
populate(&root, depth, dirs, files, 0);
root
}
fn populate(dir: &Path, depth: u32, dirs: u32, files: u32, seed: u32) {
for f in 0..files {
let n = seed.wrapping_add(f);
let name = match n % 4 {
0 => format!("module_{n}.rs"),
1 => format!("data_{n}.txt"),
2 => format!("image_{n}.png"),
_ => format!("file_{n}.log"),
};
// Tiny files: updatedb only records names, so content size is irrelevant.
std::fs::write(dir.join(name), b"x").unwrap();
}
if depth == 0 {
return;
}
for d in 0..dirs {
let sub = dir.join(format!("dir_{d}"));
std::fs::create_dir_all(&sub).unwrap();
populate(
&sub,
depth - 1,
dirs,
files,
seed.wrapping_add((d + 1) * 31),
);
}
}
fn bench_e2e(c: &mut Criterion) {
// depth 4, 4 dirs/level, 25 files/dir → ~8.5k indexed paths.
let root = build_tree(4, 4, 25);
let dir = root.to_str().unwrap();
let db = std::env::temp_dir().join(format!("uu_updatedb_bench_{}.db", std::process::id()));
let db_path = db.to_str().unwrap();
let localpaths = format!("--localpaths={dir}");
let output = format!("--output={db_path}");
let mut group = c.benchmark_group("updatedb");
// Full build: walk, front-code and write the whole tree. Empty prune
// options keep the run deterministic regardless of where temp_dir lives.
group.bench_function("build_full", |b| {
b.iter(|| {
run(black_box(&[
&localpaths,
&output,
"--prunepaths=",
"--prunefs=",
]));
});
});
// Build with a prune clause that drops every `dir_0` subtree, exercising the
// -regex/-prune path updatedb hands to find.
group.bench_function("build_pruned", |b| {
b.iter(|| {
run(black_box(&[
&localpaths,
&output,
"--prunepaths=.*/dir_0",
"--prunefs=",
]));
});
});
group.finish();
let _ = std::fs::remove_dir_all(&root);
let _ = std::fs::remove_file(&db);
}
criterion_group!(benches, bench_e2e);
criterion_main!(benches);
-77
View File
@@ -1,77 +0,0 @@
// Copyright 2024 the uutils developers
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
//! End-to-end benchmarks for `xargs`, run through the real `xargs_main` entry
//! point. Input is supplied via `-a <file>` (rather than stdin) so the harness
//! can feed a fixed corpus without touching the process's stdin, and the
//! command is `true` so the measurement is dominated by `xargs`'s own work
//! (reading, splitting and batching arguments) rather than the child.
use std::path::PathBuf;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use findutils::xargs::xargs_main;
/// Run `xargs` end-to-end. `args` are the arguments after the program name.
/// The exit status is ignored — we only care about the work performed.
fn run(args: &[&str]) {
let mut argv: Vec<&str> = Vec::with_capacity(args.len() + 1);
argv.push("xargs");
argv.extend_from_slice(args);
let _ = xargs_main(&argv);
}
/// Write `count` short tokens to a temp file, joined by `sep`. Returns the path.
fn build_input(count: u32, sep: &str, tag: &str) -> PathBuf {
let mut content = String::new();
for i in 0..count {
if i > 0 {
content.push_str(sep);
}
content.push_str(&format!("item_{i}"));
}
let path = std::env::temp_dir().join(format!("uu_xargs_bench_{}_{tag}", std::process::id()));
std::fs::write(&path, content).unwrap();
path
}
fn bench_e2e(c: &mut Criterion) {
// Whitespace- and newline-separated corpora, plus a NUL-separated one for
// the `-0` path. A few thousand short tokens keeps parsing dominant while
// `true` batches stay to a handful of (untraced) spawns.
let ws = build_input(4000, " ", "ws");
let nul = build_input(4000, "\0", "nul");
let ws_path = ws.to_str().unwrap();
let nul_path = nul.to_str().unwrap();
let mut group = c.benchmark_group("xargs");
// Default whitespace splitting, single batch (fits one command line).
group.bench_function("split_whitespace", |b| {
b.iter(|| run(black_box(&["-a", ws_path, "true"])));
});
// NUL-delimited input (`find -print0 | xargs -0` shape).
group.bench_function("split_null", |b| {
b.iter(|| run(black_box(&["-0", "-a", nul_path, "true"])));
});
// Cap arguments per command with -n, exercising the batching path across
// several (untraced) invocations.
group.bench_function("batched_n", |b| {
b.iter(|| run(black_box(&["-a", ws_path, "-n", "1000", "true"])));
});
// Bound each command line by size with -s, another batching trigger.
group.bench_function("batched_size", |b| {
b.iter(|| run(black_box(&["-a", ws_path, "-s", "4096", "true"])));
});
group.finish();
let _ = std::fs::remove_file(&ws);
let _ = std::fs::remove_file(&nul);
}
criterion_group!(benches, bench_e2e);
criterion_main!(benches);
-24
View File
@@ -1,24 +0,0 @@
[workspace]
members = ["cargo:."]
# Config for 'dist'
[dist]
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.28.0"
# CI backends to support
ci = "github"
# The installers to generate for each app
installers = ["shell"]
# Target platforms to build apps for (Rust target-triple syntax)
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
# Which actions to run on pull requests
pr-run-mode = "plan"
# Path that installers should place binaries in
install-path = "CARGO_HOME"
# Whether to install an updater program
install-updater = false
# Ignore out-of-date contents
allow-dirty = ["ci"]
[dist.github-custom-runners]
x86_64-unknown-linux-gnu = "ubuntu-latest"
-5
View File
@@ -5,11 +5,6 @@
// https://opensource.org/licenses/MIT.
fn main() {
// Ignores the SIGPIPE signal.
// This is to solve the problem that when find is used with a pipe character,
// the downstream software of the standard output stream closes the pipe and triggers a panic.
uucore::panic::mute_sigpipe_panic();
let args = std::env::args().collect::<Vec<String>>();
let strs: Vec<&str> = args.iter().map(std::convert::AsRef::as_ref).collect();
let deps = findutils::find::StandardDependencies::new();
+3 -2
View File
@@ -5,8 +5,9 @@
// https://opensource.org/licenses/MIT.
use faccess::PathExt;
use walkdir::DirEntry;
use super::{Matcher, MatcherIO, WalkEntry};
use super::{Matcher, MatcherIO};
/// Matcher for -{read,writ,execut}able.
pub enum AccessMatcher {
@@ -16,7 +17,7 @@ pub enum AccessMatcher {
}
impl Matcher for AccessMatcher {
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
let path = file_info.path();
match self {
+12 -10
View File
@@ -7,29 +7,32 @@
* file that was distributed with this source code.
*/
use std::fs;
use std::fs::{self, FileType};
use std::io::{self, stderr, Write};
use std::path::Path;
use super::{Matcher, MatcherIO, WalkEntry};
use walkdir::DirEntry;
use super::{Matcher, MatcherIO};
pub struct DeleteMatcher;
impl DeleteMatcher {
pub fn new() -> Self {
Self
DeleteMatcher
}
fn delete(&self, entry: &WalkEntry) -> io::Result<()> {
if entry.file_type().is_dir() && !entry.path_is_symlink() {
fs::remove_dir(entry.path())
fn delete(&self, file_path: &Path, file_type: FileType) -> io::Result<()> {
if file_type.is_dir() {
fs::remove_dir(file_path)
} else {
fs::remove_file(entry.path())
fs::remove_file(file_path)
}
}
}
impl Matcher for DeleteMatcher {
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
let path = file_info.path();
let path_str = path.to_string_lossy();
@@ -40,10 +43,9 @@ impl Matcher for DeleteMatcher {
return true;
}
match self.delete(file_info) {
match self.delete(path, file_info.file_type()) {
Ok(()) => true,
Err(e) => {
matcher_io.set_exit_code(1);
writeln!(&mut stderr(), "Failed to delete {path_str}: {e}").unwrap();
false
}

Some files were not shown because too many files have changed in this diff Show More