File cargo-c-0.10.15.obscpio of Package cargo-c
07070100000000000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000001800000000cargo-c-0.10.15/.github07070100000001000081A40000000000000000000000016898C2E900000053000000000000000000000000000000000000002400000000cargo-c-0.10.15/.github/FUNDING.yml# These are supported funding model platforms
liberapay: lu_zero
github: lu-zero
07070100000002000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002300000000cargo-c-0.10.15/.github/actions-rs07070100000003000081A40000000000000000000000016898C2E900000036000000000000000000000000000000000000002D00000000cargo-c-0.10.15/.github/actions-rs/grcov.ymlignore-not-existing: true
ignore:
- "/*"
- "../*"
07070100000004000081A40000000000000000000000016898C2E9000001B8000000000000000000000000000000000000002700000000cargo-c-0.10.15/.github/dependabot.ymlversion: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
- package-ecosystem: cargo
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
ignore:
- dependency-name: cbindgen
versions:
- 0.17.0
- 0.18.0
- 0.19.0
- dependency-name: cargo
versions:
- 0.50.0
- 0.51.0
- 0.52.0
- dependency-name: serde
versions:
- 1.0.123
07070100000005000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002200000000cargo-c-0.10.15/.github/workflows07070100000006000081A40000000000000000000000016898C2E900000E7F000000000000000000000000000000000000002D00000000cargo-c-0.10.15/.github/workflows/deploy.ymlname: deploy
on:
push:
tags:
- 'v*.*.*'
- 'pre-*.*.*'
jobs:
windows-binaries:
strategy:
matrix:
conf:
- msvc
- gnu
include:
- conf: msvc
toolchain: stable
- conf: gnu
toolchain: stable-x86_64-pc-windows-gnu
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.toolchain }}
- name: Build cargo-c
run: |
cargo build --profile release-strip
- name: Create zip
run: |
cd target/release-strip
7z a ../../cargo-c-windows-${{ matrix.conf }}.zip `
"cargo-capi.exe" `
"cargo-cbuild.exe" `
"cargo-cinstall.exe" `
"cargo-ctest.exe"
- name: Upload binaries
uses: actions/upload-artifact@v4
with:
name: cargo-c-windows-${{ matrix.conf }}-binaries
path: cargo-c-windows-${{ matrix.conf }}.zip
linux-binaries:
strategy:
fail-fast: false
matrix:
target:
- i686-unknown-linux-musl
- x86_64-unknown-linux-musl
- powerpc64le-unknown-linux-gnu
- aarch64-unknown-linux-musl
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Install cross
run: |
cargo install cross --git https://github.com/cross-rs/cross
- name: Build cargo-c
run: |
cross build --target ${{ matrix.target }} \
--features=vendored-openssl \
--profile release-strip
- name: Create tar
run: |
cd target/${{ matrix.target }}/release-strip
tar -czvf $GITHUB_WORKSPACE/cargo-c-${{ matrix.target }}.tar.gz \
cargo-capi \
cargo-cbuild \
cargo-cinstall \
cargo-ctest
- name: Upload binaries
uses: actions/upload-artifact@v4
with:
name: cargo-c-linux-binaries-${{ matrix.target }}
path: cargo-c-${{ matrix.target }}.tar.gz
macos-binaries:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Install stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Build cargo-c
run: |
cargo build --features=vendored-openssl --profile release-strip
- name: Create zip
run: |
cd target/release-strip
zip $GITHUB_WORKSPACE/cargo-c-macos.zip \
cargo-capi \
cargo-cbuild \
cargo-cinstall \
cargo-ctest
- name: Upload binaries
uses: actions/upload-artifact@v4
with:
name: cargo-c-macos-binaries
path: cargo-c-macos.zip
deploy:
needs: [windows-binaries, linux-binaries, macos-binaries]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Download zip files
uses: actions/download-artifact@v5
- name: Create Cargo.lock
run: |
cargo update
- name: Create a release
uses: softprops/action-gh-release@v2
with:
files: |
Cargo.lock
cargo-c-linux-binaries*/*.tar.gz
cargo-c-macos-binaries/cargo-c-macos.zip
cargo-c-windows-msvc-binaries/cargo-c-windows-msvc.zip
cargo-c-windows-gnu-binaries/cargo-c-windows-gnu.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
07070100000007000081A40000000000000000000000016898C2E9000010EB000000000000000000000000000000000000003600000000cargo-c-0.10.15/.github/workflows/example-project.ymlname: Build example project
on: [push, pull_request]
env:
CARGO_TERM_COLOR: always
jobs:
example-project:
strategy:
matrix:
include:
- os: ubuntu-latest
# x86_64:
- os: macos-13
# arm64:
- os: macos-latest
- os: windows-latest
toolchain-suffix: -gnu
- os: windows-latest
toolchain-suffix: -msvc
runs-on: ${{ matrix.os }}
steps:
- name: Clone Git repository
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable${{ matrix.toolchain-suffix }}
- name: Install pkgconf
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
id: msys2
with:
msystem: ucrt64
install: mingw-w64-ucrt-x86_64-pkgconf
- name: Put pkgconf on PATH
if: runner.os == 'Windows'
run: Add-Content $env:GITHUB_PATH "${{ steps.msys2.outputs.msys2-location }}\ucrt64\bin"
- name: Install cargo-c applet
run: cargo install --path .
- name: Test example project
working-directory: example-project
run: cargo test --verbose
- name: Build C API for example project
working-directory: example-project
run: cargo cbuild --verbose --release
- name: Run C API tests for example project
working-directory: example-project
run: cargo ctest --verbose --release
- name: Install into /usr/local
if: runner.os != 'Windows'
working-directory: example-project
run: sudo -E env PATH=$PATH cargo cinstall --verbose --release --prefix=/usr/local
- name: Install into MSYS2 root
if: runner.os == 'Windows'
working-directory: example-project
run: cargo cinstall --verbose --release --prefix="${{ steps.msys2.outputs.msys2-location }}\ucrt64"
- name: Test pkgconf
if: runner.os == 'macOS'
run: |
set -x
pkgconf --version
test "$(pkgconf --cflags example_project)" = "-I/usr/local/include/example-project-0.1"
test "$(pkgconf --libs example_project)" = "-L/usr/local/lib -lexample-project"
- name: Test pkgconf
if: runner.os == 'Linux'
run: |
set -x
pkgconf --version
ARCHDIR=`dpkg-architecture -qDEB_HOST_MULTIARCH`
# ubuntu seems to add trailing spaces for no specific reasons.
CFLAGS=$(pkgconf --cflags example_project)
LIBS=$(pkgconf --libs example_project)
test "${CFLAGS%% }" = "-I/usr/local/include/example-project-0.1"
test "${LIBS%% }" = "-L/usr/local/lib/${ARCHDIR} -lexample-project"
- name: Test pkgconf
if: runner.os == 'Windows'
shell: bash
run: |
set -x
pkgconf --version
# use --define-variable=prefix=C:/foo to test relative libdir/includedir generation
# https://github.com/lu-zero/cargo-c/commit/76a66cd72eb4271501557eebea7060821e63b702
test "$(pkgconf --define-variable=prefix=C:/foo --cflags example_project)" = "-IC:/foo/include/example-project-0.1"
test "$(pkgconf --define-variable=prefix=C:/foo --libs example_project)" = "-LC:/foo/lib -lexample-project"
- name: Update dynamic linker cache
if: runner.os == 'Linux'
run: sudo ldconfig
- name: Test usage from C (using Makefile)
if: runner.os != 'Windows'
working-directory: example-project/usage-from-c
run: make
- name: Setup Meson + Ninja
if: runner.os == 'Windows' && matrix.toolchain-suffix == '-msvc'
run: |
python3 -m pip install --upgrade pip setuptools wheel
python3 -m pip install meson ninja
- name: Setup MSVC for test
if: runner.os == 'Windows' && matrix.toolchain-suffix == '-msvc'
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x86_64
- name: Test usage from C (Meson)
if: runner.os == 'Windows' && matrix.toolchain-suffix == '-msvc'
working-directory: example-project/usage-from-c
env:
PKG_CONFIG: pkgconf
run: |
meson setup build
meson compile -C build
meson test -C build
07070100000008000081A40000000000000000000000016898C2E90000082F000000000000000000000000000000000000002B00000000cargo-c-0.10.15/.github/workflows/rust.ymlname: Rust
on: [push, pull_request]
jobs:
rustfmt-clippy:
name: Format and Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: clippy, rustfmt
- name: Run rustfmt
run: |
cargo fmt --all -- --check
- name: Run clippy
uses: actions-rs-plus/clippy-check@v2.3.0
with:
args: --all -- -D warnings
coverage:
needs: arch-test
name: Code coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Install grcov
env:
LINK: https://github.com/mozilla/grcov/releases/download
GRCOV_VERSION: 0.8.7
run: |
curl -L "$LINK/v$GRCOV_VERSION/grcov-x86_64-unknown-linux-gnu.tar.bz2" |
tar xj -C $HOME/.cargo/bin
- name: Set up MinGW
uses: egor-tensin/setup-mingw@v2
with:
platform: x64
cc: false
- name: Run grcov
id: coverage
run: bash coverage.sh
- name: Codecov upload
uses: codecov/codecov-action@v5
with:
files: coverage.lcov
arch-test:
needs: rustfmt-clippy
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-13, macos-latest]
toolchain: [nightly, stable]
include:
- toolchain: nightly-gnu
os: windows-latest
- toolchain: stable-gnu
os: windows-latest
name: ${{matrix.os}}-${{matrix.toolchain}}
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4
- name: Install ${{matrix.toolchain}}
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{matrix.toolchain}}
- name: Build
run: |
cargo build --verbose
- name: Run tests
run: |
cargo test --verbose
07070100000009000081A40000000000000000000000016898C2E90000005F000000000000000000000000000000000000001B00000000cargo-c-0.10.15/.gitignore/target
**/*.rs.bk
Cargo.lock
/example-project/target/
/example-project/usage-from-c/run_tests
0707010000000A000081A40000000000000000000000016898C2E9000005ED000000000000000000000000000000000000001B00000000cargo-c-0.10.15/Cargo.toml[package]
name = "cargo-c"
version = "0.10.15+cargo-0.90.0"
authors = ["Luca Barbato <lu_zero@gentoo.org>"]
description = "Helper program to build and install c-like libraries"
license = "MIT"
edition = "2021"
readme = "README.md"
repository = "https://github.com/lu-zero/cargo-c"
categories = ["command-line-utilities", "development-tools::cargo-plugins"]
keywords = ["cargo", "cdylib"]
rust-version = "1.87"
[[bin]]
name = "cargo-capi"
path = "src/bin/capi.rs"
[[bin]]
name = "cargo-cinstall"
path = "src/bin/cinstall.rs"
[[bin]]
name = "cargo-cbuild"
path = "src/bin/cbuild.rs"
[[bin]]
name = "cargo-ctest"
path = "src/bin/ctest.rs"
[dependencies]
cargo = "0.90.0"
cargo-util = "0.2"
semver = "1.0.3"
log = "0.4"
clap = { version = "4.5.18", features = ["color", "derive", "cargo", "string", "wrap_help"] }
regex = "1.5.6"
cbindgen = { version="0.29.0", default-features=false }
toml = "0.8"
serde = "1.0.123"
serde_derive = "1.0"
serde_json = "1.0.62"
anyhow = "1.0"
cc = "1.0"
glob = "0.3"
itertools = "0.14"
implib = "0.4.0"
object = { version = "0.37.1", default-features = false, features = ["std", "read_core", "pe"] }
[features]
default = []
vendored-openssl = ["cargo/vendored-openssl"]
[profile.release-strip-nolto]
inherits = "release"
strip = "symbols"
opt-level = "s"
codegen-units = 1
[profile.release-strip-lto]
inherits = "release-strip-nolto"
lto = true
[profile.release-strip]
inherits = "release-strip-lto"
# To avoid #414
[workspace]
exclude = ["example-project", "example-workspace"]
0707010000000B000081A40000000000000000000000016898C2E90000042D000000000000000000000000000000000000001800000000cargo-c-0.10.15/LICENSEMIT License
Copyright (c) 2019 Luca Barbato
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.
0707010000000C000081A40000000000000000000000016898C2E90000312F000000000000000000000000000000000000001A00000000cargo-c-0.10.15/README.md# Cargo C-ABI helpers
[](LICENSE)
[](https://crates.io/crates/cargo-c)
[](https://github.com/lu-zero/cargo-c/actions?query=workflow:Rust)
[](https://rust-av.zulipchat.com/#narrow/stream/254255-cargo-c)
[](https://deps.rs/repo/github/lu-zero/cargo-c)
[cargo](https://doc.rust-lang.org/cargo) applet to build and install C-ABI compatible dynamic and static libraries.
It produces and installs a correct [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) file, a static library and a dynamic library, and a C header to be used by any C (and C-compatible) software.
## Installation
**cargo-c** may be installed from [crates.io](https://crates.io/crates/cargo-c).
``` sh
cargo install cargo-c
```
The `rustc` version supported is the same as the one supported by the `cargo` version embedded in the package version, or as set in the
[rust-version](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) field.
You must have the **cargo** build [requirements](https://github.com/rust-lang/cargo#compiling-from-source) satisfied in order to build **cargo-c**:
* `git`
* `pkg-config` (on Unix, used to figure out the host-provided headers/libraries)
* `curl` (on Unix)
* OpenSSL headers (only for Unix, this is the `libssl-dev` package on deb-based distributions)
You may pass `--features=vendored-openssl` if you have problems building openssl-sys using the host-provided OpenSSL.
``` sh
cargo install cargo-c --features=vendored-openssl
```
## Usage
``` sh
# build the library, create the .h header, create the .pc file
$ cargo cbuild --destdir=${D} --prefix=/usr --libdir=/usr/lib64
```
``` sh
# build the library, create the .h header, create the .pc file, build and run the tests
$ cargo ctest
```
``` sh
# build the library, create the .h header, create the .pc file and install all of it
$ cargo cinstall --destdir=${D} --prefix=/usr --libdir=/usr/lib64
```
For a more in-depth explanation of how `cargo-c` works and how to use it for
your crates, read [Building Crates so they Look Like C ABI Libraries][dev.to].
### The TL;DR:
This is the ideal setup for a project that wants to keep their C-API within the main crate:
- [Create][diff-1] a `capi.rs` with the C-API you want to expose and use
~~`#[cfg(cargo_c)]`~~`#[cfg(feature="capi")]` to hide it when you build a normal rust library.
- [Make sure][diff-2] you have a lib target and if you are using a workspace
the first member is the crate you want to export, that means that you might
have [to add a "." member at the start of the list][diff-3].
- ~~Since Rust 1.38, also add "staticlib" to the "lib" `crate-type`.~~ Do not specify the `crate-type`, cargo-c will add the correct library target by itself.
- You may use the feature `capi` to add C-API-specific optional dependencies.
> **NOTE**: It must be always present in `Cargo.toml`
- Remember to [add][diff-4] a [`cbindgen.toml`][cbindgen-toml] and fill it with
at least the include guard and probably you want to set the language to C (it
defaults to C++)
- Once you are happy with the result update your documentation to tell the user
to install `cargo-c` and do `cargo cinstall --prefix=/usr
--destdir=/tmp/some-place` or something along those lines.
If you plan to keep the bindings as a separate crate and do not need to autogenerate the headers you may just [populate Cargo.toml][diff-5]:
- Add a `capi` feature, since it is used by cargo-c to identify packages that has to be built as C-libraries within a workspace.
- Set the entry in `package.metadata.capi.header.generate` to `false`.
- Optionally override the path to the header to a custom one instead of the default one.
[diff-1]: https://github.com/RustAudio/lewton/pull/50/commits/557cb4ce35beedf6d6bfaa481f29936094a71669
[diff-2]: https://github.com/RustAudio/lewton/pull/50/commits/e7ea8fff6423213d1892e86d51c0c499d8904dc1
[diff-3]: https://github.com/xiph/rav1e/pull/1381/commits/7d558125f42f4b503bcdcda5a82765da76a227e0#diff-80398c5faae3c069e4e6aa2ed11b28c0R94
[diff-4]: https://github.com/RustAudio/lewton/pull/51/files
[diff-5]: https://github.com/linebender/resvg/commit/c0777c7ce26bf40efed7ba38d0a70e5af83feb78
[cbindgen-toml]: https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml
## Advanced
You may override various aspects of `cargo-c` via settings in `Cargo.toml` under the `package.metadata.capi` key
```toml
[package.metadata.capi]
# Configures the minimum required cargo-c version. Trying to run with an
# older version causes an error.
min_version = "0.6.10"
```
### Header Generation
```toml
[package.metadata.capi.header]
# Used as header file name. By default this is equal to the crate name.
# The name can be with or without the header filename extension `.h`
name = "new_name"
# Install the header into a subdirectory with the name of the crate. This
# is enabled by default, pass `false` or "" to disable it.
subdirectory = "libfoo-2.0/foo"
# Generate the header file with `cbindgen`, or copy a pre-generated header
# from the `assets` subdirectory. By default a header is generated.
generation = true
# Can be use to disable header generation completely.
# This can be used when generating dynamic modules instead of an actual library.
enabled = true
# Whether to emit library version constants (e.g. `FOO_MAJOR`, `FOO_MINOR` and
# `FOO_PATCH`) at the top of the header file. Enabled by default.
emit_version_constants = true
```
### `pkg-config` File Generation
```toml
[package.metadata.capi.pkg_config]
# Used as the package name in the pkg-config file and defaults to the crate name.
name = "libfoo"
# Used as the pkg-config file name and defaults to the crate name.
filename = "libfoo-2.0"
# Used as the package description in the pkg-config file and defaults to the crate description.
description = "some description"
# Used as the package version in the pkg-config file and defaults to the crate version.
version = "1.2.3"
# Used as the Requires field in the pkg-config file, if defined
requires = "gstreamer-1.0, gstreamer-base-1.0"
# Used as the Requires.private field in the pkg-config file, if defined
requires_private = "gobject-2.0, glib-2.0 >= 2.56.0, gmodule-2.0"
# Strip the include search path from the last n components, useful to support installing in a
# subdirectory but then include with the path. By default it is 0.
strip_include_path_components = 1
```
### Library Generation
```toml
[package.metadata.capi.library]
# Used as the library name and defaults to the crate name. This might get
# prefixed with `lib` depending on the target platform.
name = "new_name"
# Used as library version and defaults to the crate version. How this is used
# depends on the target platform.
version = "1.2.3"
# Used to install the library to a subdirectory of `libdir`.
install_subdir = "gstreamer-1.0"
# Used to disable versioning links when installing the dynamic library
versioning = false
# Instead of using semver, select a fixed number of version components for your SONAME version suffix:
# Setting this to 1 with a version of 0.0.0 allows a suffix of `.so.0`
# Setting this to 3 always includes the full version in the SONAME (indicate any update is ABI breaking)
#version_suffix_components = 2
# Add `-Cpanic=abort` to the RUSTFLAGS automatically, it may be useful in case
# something might panic in the crates used by the library.
rustflags = "-Cpanic=abort"
# Used to disable the generation of additional import library file in platforms
# that have the concept such as Windows
import_library = false
```
### Custom data install
```toml
[package.metadata.capi.install.data]
# Used to install the data to a subdirectory of `datadir`. By default it is the same as `name`
subdirectory = "foodata"
# Copy the pre-generated data files found in {root_dir}/{from} to {datadir}/{to}/{matched subdirs}
# If {from} is a single path instead of a glob, the destination is {datapath}/{to}.
# datapath is {datadir}/{subdirectory}
asset = [{from="pattern/with/or/without/**/*", to="destination"}]
# Copy the pre-generated data files found in {OUT_DIR}/{from} to {includedir}/{to}/{matched subdirs}
# If {from} is a single path instead of a glob, the destination is {datapath}/{to}.
# datapath is {datadir}/{subdirectory}
generated = [{from="pattern/with/or/without/**/*", to="destination"}]
[package.metadata.capi.install.include]
# Copy the pre-generated includes found in {root_dir}/{from} to {includedir}/{to}/{matched subdirs}
# If {from} is a single path instead of a glob, the destination is {includepath}/{to}.
# includepath is {includedir}/{header.subdirectory}
asset = [{from="pattern/with/or/without/**/*", to="destination"}]
# Copy the pre-generated includes found in {OUT_DIR}/{from} to {includedir}/{to}/{matched subdirs}
# If {from} is a single path instead of a glob, the destination is {includedpath}/{to}.
# includepath is {includedir}/{header.subdirectory}
generated = [{from="pattern/with/or/without/**/*", to="destination"}]
```
### Notes
Do **not** pass `RUSTFLAGS` that are managed by cargo through other means, (e.g. the flags driven by `[profiles]` or the flags driven by `[target.<>]`), cargo-c effectively builds as if the *target* is always explicitly passed.
## Users
- [ebur128](https://github.com/sdroege/ebur128#c-api)
- [gcode-rs](https://github.com/Michael-F-Bryan/gcode-rs)
- [gst-plugins-rs](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs)
- [lewton](https://github.com/RustAudio/lewton)
- [libdovi](https://github.com/quietvoid/dovi_tool/tree/main/dolby_vision#libdovi-c-api)
- [libimagequant](https://github.com/ImageOptim/libimagequant#building-with-cargo-c)
- [librsvg](https://github.com/GNOME/librsvg/blob/main/rsvg/meson.build)
- [rav1e](https://github.com/xiph/rav1e)
- [rustls-ffi](https://github.com/rustls/rustls-ffi)
- [sled](https://github.com/spacejam/sled/tree/master/bindings/sled-native)
- [pathfinder](https://github.com/servo/pathfinder#c)
- [udbserver](https://github.com/bet4it/udbserver)
## Status
- [x] cli
- [x] build command
- [x] install command
- [x] test command
- [x] cargo applet support
- [x] build targets
- [x] pkg-config generation
- [x] header generation (cbindgen integration)
- [x] `staticlib` support
- [x] `cdylib` support
- [x] Generate version information in the header
- [ ] Make it tunable
- [x] Extra Cargo.toml keys
- [x] Better status reporting
[dev.to]: https://dev.to/luzero/building-crates-so-they-look-like-c-abi-libraries-1ibn
[using]: https://dev.to/luzero/building-crates-so-they-look-like-c-abi-libraries-1ibn#using-cargoc
## Availability
[](https://repology.org/project/cargo-c/versions)
## Troubleshooting
### Shared libraries are not built on musl systems
When running on a musl-based system (e.g. Alpine Linux), it could be that using the `cdylib` library type results in the following error (as reported [here](https://github.com/lu-zero/cargo-c/issues/180)):
> Error: CliError { error: Some(cannot produce cdylib for <package> as the target x86_64-unknown-linux-musl does not support these crate types), exit_code: 101 }
This suggests that Rust was not built with `crt-static=false` and it typically happens if Rust has been installed through rustup.
Shared libraries can be enabled manually in this case, by editing the file `.cargo/config` like so:
```toml
# .cargo/config
[target.x86_64-unknown-linux-musl]
rustflags = [
"-C", "target-feature=-crt-static",
]
```
However, it is preferred to install Rust through the system package manager instead of rustup (e.g. with `apk add rust`), because the provided package should already handle this (see e.g. [here](https://git.alpinelinux.org/aports/tree/main/rust/APKBUILD?h=3.19-stable#n232)).
### On Debian-like system the libdir includes the host triplet by default
In order to accomodate Debian's [multiarch](https://wiki.debian.org/Multiarch/Implementation) approach the `cargo-c` default for the `libdir` is `lib/<triplet>` on such system.
Either pass an explicit `--libdir` or pass `--target` to return to the common `libdir=lib` default.
## Acknowledgements
This software has been partially developed in the scope of the H2020 project SIFIS-Home with GA n. 952652.
0707010000000D000081A40000000000000000000000016898C2E9000000D9000000000000000000000000000000000000001C00000000cargo-c-0.10.15/codecov.ymlcoverage:
status:
project:
default:
target: 80% # the required coverage value
threshold: 10% # the leniency in hitting the target
patch:
default:
informational: true
0707010000000E000081A40000000000000000000000016898C2E900000675000000000000000000000000000000000000001C00000000cargo-c-0.10.15/coverage.shexport LLVM_PROFILE_FILE="cargo-c-%p-%m.profraw"
export RUSTFLAGS=-Cinstrument-coverage
export CARGO_INCREMENTAL=0
rustup default stable
cargo build
cargo test
rustup target add x86_64-pc-windows-gnu
unset RUSTFLAGS
function run() {
echo "$*"
$*
}
for project in example-project example-workspace; do
run target/debug/cargo-capi capi --help
run target/debug/cargo-capi capi test --manifest-path=${project}/Cargo.toml
run target/debug/cargo-capi capi clean --manifest-path=${project}/Cargo.toml
run target/debug/cargo-capi capi build --manifest-path=${project}/Cargo.toml
run target/debug/cargo-capi capi install --manifest-path=${project}/Cargo.toml --destdir=/tmp/staging
run target/debug/cargo-capi clean --manifest-path=${project}/Cargo.toml
run target/debug/cargo-cbuild --help
run target/debug/cargo-cbuild clean --manifest-path=${project}/Cargo.toml
run target/debug/cargo-cbuild cbuild --manifest-path=${project}/Cargo.toml
run target/debug/cargo-ctest metadata --help
run target/debug/cargo-ctest ctest --manifest-path=${project}/Cargo.toml
run target/debug/cargo-cinstall --help
run target/debug/cargo-cinstall cinstall --manifest-path=${project}/Cargo.toml --destdir=/tmp/staging
run target/debug/cargo-cinstall clean --manifest-path=${project}/Cargo.toml
run target/debug/cargo-cinstall cinstall --manifest-path=${project}/Cargo.toml --destdir=/tmp/staging-win --target=x86_64-pc-windows-gnu --dlltool=x86_64-w64-mingw32-dlltool
done
grcov . --binary-path target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --ignore '../**' --ignore '/*' -o coverage.lcov
0707010000000F000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002000000000cargo-c-0.10.15/example-project07070100000010000081A40000000000000000000000016898C2E900000292000000000000000000000000000000000000002B00000000cargo-c-0.10.15/example-project/Cargo.toml[package]
name = "example-project"
version = "0.1.0"
edition = "2021"
[features]
default = []
capi = ["libc"]
[dependencies]
libc = { version = "0.2", optional = true }
[dev-dependencies]
inline-c = "0.1"
[build-dependencies]
cargo_metadata = "0.14"
[package.metadata.capi.header]
subdirectory = "example-project-0.1/example_project"
[package.metadata.capi.pkg_config]
strip_include_path_components = 1
[package.metadata.capi.library]
rustflags = "-Cpanic=abort"
name = "example-project"
[package.metadata.capi.install.include]
asset = [{from = "include/file.h", to = "otherplace" }]
generated = [{from = "include/other_file.h", to = "otherplace" }]
07070100000011000081A40000000000000000000000016898C2E9000002C6000000000000000000000000000000000000002A00000000cargo-c-0.10.15/example-project/README.mdExample project using `cargo-c`
===============================
For detailed usage instructions, have a look at the
[Github workflow configuration](../.github/workflows/example-project.yml).
Note that `cargo install --path .` is used to install `cargo-c`
from the locally cloned Git repository.
If you want to install the latest release from
[crates.io](https://crates.io/crates/cargo-c),
you should use this instead:
cargo install cargo-c
Running `cargo cbuild --release` will create the C header file
`example_project.h` in the `target/release` directory.
This file will contain the comments from the file [`capi.rs`](src/capi.rs).
Run `cargo doc --open` to view the documentation of the Rust code.
07070100000012000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002700000000cargo-c-0.10.15/example-project/assets07070100000013000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002C00000000cargo-c-0.10.15/example-project/assets/capi07070100000014000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000003400000000cargo-c-0.10.15/example-project/assets/capi/include07070100000015000081A40000000000000000000000016898C2E900000011000000000000000000000000000000000000003B00000000cargo-c-0.10.15/example-project/assets/capi/include/file.h// Pre-generated
07070100000016000081A40000000000000000000000016898C2E900000350000000000000000000000000000000000000002900000000cargo-c-0.10.15/example-project/build.rsuse cargo_metadata::*;
use std::path::*;
fn main() {
let path = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let meta = MetadataCommand::new()
.manifest_path("./Cargo.toml")
.current_dir(&path)
.exec()
.unwrap();
println!("{:?}", meta);
let out = std::env::var("OUT_DIR").unwrap();
let out = Path::new(&out);
let path = out.join("capi/include/");
let subdir = path.join("subdir");
let include = out.join("include");
std::fs::create_dir_all(&path).unwrap();
std::fs::create_dir_all(&subdir).unwrap();
std::fs::create_dir_all(&include).unwrap();
std::fs::write(path.join("generated.h"), "// Generated").unwrap();
std::fs::write(subdir.join("in_subdir.h"), "// Generated").unwrap();
std::fs::write(include.join("other_file.h"), "// Generated").unwrap();
}
07070100000017000081A40000000000000000000000016898C2E900000097000000000000000000000000000000000000002E00000000cargo-c-0.10.15/example-project/cbindgen.tomlinclude_guard = "EXAMPLE_PROJECT_H"
include_version = true
language = "C"
cpp_compat = true
[export.rename]
"OddCounter" = "ExampleProjectOddCounter"
07070100000018000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002400000000cargo-c-0.10.15/example-project/src07070100000019000081A40000000000000000000000016898C2E9000003EC000000000000000000000000000000000000002C00000000cargo-c-0.10.15/example-project/src/capi.rsuse crate::OddCounter;
// NB: The documentation comments from this file will be available
// in the auto-generated header file example_project.h
/// Create new counter object given a start value.
///
/// On error (if an even start value is used), NULL is returned.
/// The returned object must be eventually discarded with example_project_oddcounter_free().
#[no_mangle]
pub extern "C" fn example_project_oddcounter_new(start: u32) -> Option<Box<OddCounter>> {
OddCounter::new(start).ok().map(Box::new)
}
/// Discard a counter object.
///
/// Passing NULL is allowed.
#[no_mangle]
pub extern "C" fn example_project_oddcounter_free(_: Option<Box<OddCounter>>) {}
/// Increment a counter object.
#[no_mangle]
pub extern "C" fn example_project_oddcounter_increment(counter: &mut OddCounter) {
counter.increment()
}
/// Obtain the current value of a counter object.
#[no_mangle]
pub extern "C" fn example_project_oddcounter_get_current(counter: &OddCounter) -> u32 {
counter.current()
}
0707010000001A000081A40000000000000000000000016898C2E9000005AB000000000000000000000000000000000000002B00000000cargo-c-0.10.15/example-project/src/lib.rs/*!
Example library for [cargo-c].
[cargo-c]: https://crates.io/crates/cargo-c
*/
#![warn(rust_2018_idioms)]
#![deny(missing_docs)]
#[cfg(feature = "capi")]
mod capi;
/// A counter for odd numbers.
///
/// Note that this `struct` does *not* use `#[repr(C)]`.
/// It can therefore contain arbitrary Rust types.
/// In the C API, it will be available as an *opaque pointer*.
#[derive(Debug)]
pub struct OddCounter {
number: u32,
}
impl OddCounter {
/// Create a new counter, given an odd number to start.
pub fn new(start: u32) -> Result<OddCounter, OddCounterError> {
if start % 2 == 0 {
Err(OddCounterError::Even)
} else {
Ok(OddCounter { number: start })
}
}
/// Increment by 2.
pub fn increment(&mut self) {
self.number += 2;
}
/// Obtain the current (odd) number.
pub fn current(&self) -> u32 {
self.number
}
}
/// Error type for [OddCounter::new].
///
/// In a "real" library, there would probably be more error variants.
#[derive(Debug)]
pub enum OddCounterError {
/// An even number was specified as `start` value.
Even,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create42() {
assert!(OddCounter::new(42).is_err());
}
#[test]
fn increment43() {
let mut counter = OddCounter::new(43).unwrap();
counter.increment();
assert_eq!(counter.current(), 45);
}
}
0707010000001B000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002600000000cargo-c-0.10.15/example-project/tests0707010000001C000081A40000000000000000000000016898C2E900000403000000000000000000000000000000000000002E00000000cargo-c-0.10.15/example-project/tests/capi.rs#[cfg(feature = "capi")]
mod capi {
use inline_c::assert_c;
#[test]
fn test_capi() {
(assert_c! {
#include <example_project.h>
#include <stdio.h>
int main() {
ExampleProjectOddCounter *counter = example_project_oddcounter_new(4);
if (counter) {
printf("Unexpected success\n");
return 1;
}
counter = example_project_oddcounter_new(5);
if (!counter) {
printf("Error creating ExampleProjectOddCounter\n");
return 1;
}
example_project_oddcounter_increment(counter);
uint32_t result = example_project_oddcounter_get_current(counter);
example_project_oddcounter_free(counter);
if (result == 7) {
return 0;
} else {
printf("Error: unexpected result: %d\n", result);
return 1;
}
}
})
.success();
}
}
0707010000001D000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002D00000000cargo-c-0.10.15/example-project/usage-from-c0707010000001E000081A40000000000000000000000016898C2E900000115000000000000000000000000000000000000003600000000cargo-c-0.10.15/example-project/usage-from-c/Makefile# cargo-c saves all the information in the .pc file
# Do not try to pass simply -lname since it will not work for static linking
LDLIBS = `pkg-config --libs example_project`
CFLAGS = `pkg-config --cflags example_project`
test: run_tests
./run_tests
clean:
$(RM) run_tests
0707010000001F000081A40000000000000000000000016898C2E9000000C2000000000000000000000000000000000000003900000000cargo-c-0.10.15/example-project/usage-from-c/meson.buildproject('example', 'c')
dep = dependency('example_project', static: true, required: true)
exe = executable('run_tests', files('run_tests.c'), dependencies: dep)
test('run_tests', exe)
07070100000020000081A40000000000000000000000016898C2E9000002B8000000000000000000000000000000000000003900000000cargo-c-0.10.15/example-project/usage-from-c/run_tests.c#include "example_project/example_project.h"
#include <stdio.h>
int main() {
ExampleProjectOddCounter *counter = example_project_oddcounter_new(4);
if (counter) {
printf("Unexpected success\n");
return 1;
}
counter = example_project_oddcounter_new(5);
if (!counter) {
printf("Error creating ExampleProjectOddCounter\n");
return 1;
}
example_project_oddcounter_increment(counter);
uint32_t result = example_project_oddcounter_get_current(counter);
example_project_oddcounter_free(counter);
if (result == 7) {
return 0;
} else {
printf("Error: unexpected result: %d\n", result);
return 1;
}
}
07070100000021000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002200000000cargo-c-0.10.15/example-workspace07070100000022000081A40000000000000000000000016898C2E900000034000000000000000000000000000000000000002D00000000cargo-c-0.10.15/example-workspace/Cargo.toml[workspace]
members = [
"api-a",
"api-b",
]
07070100000023000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002800000000cargo-c-0.10.15/example-workspace/api-a07070100000024000081A40000000000000000000000016898C2E900000126000000000000000000000000000000000000003300000000cargo-c-0.10.15/example-workspace/api-a/Cargo.toml[package]
name = "api-a"
version = "0.1.0"
authors = ["Luca Barbato <lu_zero@gentoo.org>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
capi = ["libc"]
[dependencies]
libc = { version = "0.2", optional = true }
07070100000025000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002C00000000cargo-c-0.10.15/example-workspace/api-a/src07070100000026000081A40000000000000000000000016898C2E9000000D4000000000000000000000000000000000000003300000000cargo-c-0.10.15/example-workspace/api-a/src/lib.rs#[cfg(feature = "capi")]
mod capi {
#[no_mangle]
extern "C" fn info() {
eprintln!("C-API");
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
07070100000027000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002800000000cargo-c-0.10.15/example-workspace/api-b07070100000028000081A40000000000000000000000016898C2E900000125000000000000000000000000000000000000003300000000cargo-c-0.10.15/example-workspace/api-b/Cargo.toml[package]
name = "api-b"
version = "0.1.0"
authors = ["Luca Barbato <lu_zero@gentoo.org>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
capi = ["libc"]
[dependencies]
libc = { version = "0.2", optional = true }
07070100000029000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000002C00000000cargo-c-0.10.15/example-workspace/api-b/src0707010000002A000081A40000000000000000000000016898C2E9000000D6000000000000000000000000000000000000003300000000cargo-c-0.10.15/example-workspace/api-b/src/lib.rs#[cfg(feature = "capi")]
mod capi {
#[no_mangle]
extern "C" fn info() {
eprintln!("C-API B");
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
0707010000002B000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000001400000000cargo-c-0.10.15/src0707010000002C000041ED0000000000000000000000026898C2E900000000000000000000000000000000000000000000001800000000cargo-c-0.10.15/src/bin0707010000002D000081A40000000000000000000000016898C2E900000915000000000000000000000000000000000000002000000000cargo-c-0.10.15/src/bin/capi.rsuse cargo_c::build::{cbuild, ctest};
use cargo_c::cli::*;
use cargo_c::config::*;
use cargo_c::install::cinstall;
use cargo::util::command_prelude::flag;
use cargo::util::command_prelude::ArgMatchesExt;
use cargo::{CliResult, GlobalContext};
use clap::*;
fn main() -> CliResult {
let mut config = GlobalContext::default()?;
let cli_build = subcommand_build("build", "Build the crate C-API");
let cli_install = subcommand_install("install", "Install the crate C-API");
let cli_test = subcommand_test("test");
let mut app = main_cli().subcommand(
Command::new("capi")
.allow_external_subcommands(true)
.about("Build or install the crate C-API")
.arg(flag("version", "Print version info and exit").short('V'))
.subcommand(cli_build)
.subcommand(cli_install)
.subcommand(cli_test),
);
let args = app.clone().get_matches();
let (cmd, subcommand_args, default_profile, build_tests) = match args.subcommand() {
Some(("capi", args)) => match args.subcommand() {
Some(("build", args)) => ("build", args, "dev", false),
Some(("test", args)) => ("test", args, "dev", true),
Some(("install", args)) => ("install", args, "release", false),
Some((cmd, args)) => {
return run_cargo_fallback(cmd, args);
}
_ => {
// No subcommand provided.
app.print_help()?;
return Ok(());
}
},
Some((cmd, args)) => {
return run_cargo_fallback(cmd, args);
}
_ => {
app.print_help()?;
return Ok(());
}
};
if subcommand_args.flag("version") {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
return Ok(());
}
global_context_configure(&mut config, subcommand_args)?;
let mut ws = subcommand_args.workspace(&config)?;
let (packages, compile_opts) = cbuild(
&mut ws,
&config,
subcommand_args,
default_profile,
build_tests,
)?;
if cmd == "install" {
cinstall(&ws, &packages)?;
} else if cmd == "test" {
ctest(&ws, subcommand_args, &packages, compile_opts)?;
}
Ok(())
}
0707010000002E000081A40000000000000000000000016898C2E900000462000000000000000000000000000000000000002200000000cargo-c-0.10.15/src/bin/cbuild.rsuse cargo::util::command_prelude::ArgMatchesExt;
use cargo::CliResult;
use cargo::GlobalContext;
use cargo_c::build::*;
use cargo_c::cli::{main_cli, run_cargo_fallback, subcommand_build};
use cargo_c::config::*;
fn main() -> CliResult {
let mut config = GlobalContext::default()?;
let subcommand = subcommand_build("cbuild", "Build the crate C-API");
let mut app = main_cli().subcommand(subcommand);
let args = app.clone().get_matches();
let subcommand_args = match args.subcommand() {
Some(("cbuild", args)) => args,
Some((cmd, args)) => {
return run_cargo_fallback(cmd, args);
}
_ => {
// No subcommand provided.
app.print_help()?;
return Ok(());
}
};
if subcommand_args.flag("version") {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
return Ok(());
}
global_context_configure(&mut config, subcommand_args)?;
let mut ws = subcommand_args.workspace(&config)?;
let _ = cbuild(&mut ws, &config, subcommand_args, "dev", false)?;
Ok(())
}
0707010000002F000081A40000000000000000000000016898C2E9000004D8000000000000000000000000000000000000002400000000cargo-c-0.10.15/src/bin/cinstall.rsuse cargo::util::command_prelude::ArgMatchesExt;
use cargo::CliResult;
use cargo::GlobalContext;
use cargo_c::build::cbuild;
use cargo_c::cli::{main_cli, run_cargo_fallback, subcommand_install};
use cargo_c::config::global_context_configure;
use cargo_c::install::cinstall;
fn main() -> CliResult {
let mut config = GlobalContext::default()?;
let subcommand = subcommand_install("cinstall", "Install the crate C-API");
let mut app = main_cli().subcommand(subcommand);
let args = app.clone().get_matches();
let subcommand_args = match args.subcommand() {
Some(("cinstall", args)) => args,
Some((cmd, args)) => {
return run_cargo_fallback(cmd, args);
}
_ => {
// No subcommand provided.
app.print_help()?;
return Ok(());
}
};
if subcommand_args.flag("version") {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
return Ok(());
}
global_context_configure(&mut config, subcommand_args)?;
let mut ws = subcommand_args.workspace(&config)?;
let (packages, _) = cbuild(&mut ws, &config, subcommand_args, "release", false)?;
cinstall(&ws, &packages)?;
Ok(())
}
07070100000030000081A40000000000000000000000016898C2E90000044E000000000000000000000000000000000000002100000000cargo-c-0.10.15/src/bin/ctest.rsuse cargo::util::command_prelude::*;
use cargo_c::build::*;
use cargo_c::cli::{main_cli, run_cargo_fallback, subcommand_test};
use cargo_c::config::*;
fn main() -> CliResult {
let mut config = GlobalContext::default()?;
let subcommand = subcommand_test("ctest");
let mut app = main_cli().subcommand(subcommand);
let args = app.clone().get_matches();
let subcommand_args = match args.subcommand() {
Some(("ctest", args)) => args,
Some((cmd, args)) => {
return run_cargo_fallback(cmd, args);
}
_ => {
// No subcommand provided.
app.print_help()?;
return Ok(());
}
};
if subcommand_args.flag("version") {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
return Ok(());
}
global_context_configure(&mut config, subcommand_args)?;
let mut ws = subcommand_args.workspace(&config)?;
let (packages, compile_opts) = cbuild(&mut ws, &config, subcommand_args, "dev", true)?;
ctest(&ws, subcommand_args, &packages, compile_opts)
}
07070100000031000081A40000000000000000000000016898C2E90000B402000000000000000000000000000000000000001D00000000cargo-c-0.10.15/src/build.rsuse std::collections::HashMap;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::io::Read;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use cargo::core::compiler::{unit_graph::UnitDep, unit_graph::UnitGraph, Executor, Unit};
use cargo::core::profiles::Profiles;
use cargo::core::{FeatureValue, Package, PackageId, Target, TargetKind, Workspace};
use cargo::ops::{self, CompileFilter, CompileOptions, FilterRule, LibRule};
use cargo::util::command_prelude::{ArgMatches, ArgMatchesExt, ProfileChecking, UserIntent};
use cargo::util::interning::InternedString;
use cargo::{CliResult, GlobalContext};
use anyhow::Context as _;
use cargo_util::paths::{copy, create_dir_all, open, read, read_bytes, write};
use implib::def::ModuleDef;
use implib::{Flavor, ImportLibrary, MachineType};
use semver::Version;
use crate::build_targets::BuildTargets;
use crate::install::InstallPaths;
use crate::pkg_config_gen::PkgConfig;
use crate::target;
/// Build the C header
fn build_include_file(
ws: &Workspace,
name: &str,
version: Option<&Version>,
root_output: &Path,
root_path: &Path,
) -> anyhow::Result<()> {
ws.gctx()
.shell()
.status("Building", "header file using cbindgen")?;
let mut header_name = PathBuf::from(name);
header_name.set_extension("h");
let include_path = root_output.join(header_name);
let crate_path = root_path;
// TODO: map the errors
let mut config = cbindgen::Config::from_root_or_default(crate_path);
if let Some(version) = version {
let warning = config.autogen_warning.unwrap_or_default();
let version_info = format!(
"\n#define {0}_MAJOR {1}\n#define {0}_MINOR {2}\n#define {0}_PATCH {3}\n",
name.to_uppercase().replace('-', "_"),
version.major,
version.minor,
version.patch
);
config.autogen_warning = Some(warning + &version_info);
}
cbindgen::Builder::new()
.with_crate(crate_path)
.with_config(config)
.generate()
.unwrap()
.write_to_file(include_path);
Ok(())
}
/// Copy the pre-built C header from the asset directory to the root_dir
fn copy_prebuilt_include_file(
ws: &Workspace,
build_targets: &BuildTargets,
root_output: &Path,
) -> anyhow::Result<()> {
let mut shell = ws.gctx().shell();
shell.status("Populating", "uninstalled header directory")?;
for (from, to) in build_targets.extra.include.iter() {
let to = root_output.join("include").join(to);
create_dir_all(to.parent().unwrap())?;
copy(from, to)?;
}
Ok(())
}
fn build_pc_file(name: &str, root_output: &Path, pc: &PkgConfig) -> anyhow::Result<()> {
let pc_path = root_output.join(format!("{name}.pc"));
let buf = pc.render();
write(pc_path, buf)
}
fn build_pc_files(
ws: &Workspace,
filename: &str,
root_output: &Path,
pc: &PkgConfig,
) -> anyhow::Result<()> {
ws.gctx().shell().status("Building", "pkg-config files")?;
build_pc_file(filename, root_output, pc)?;
let pc_uninstalled = pc.uninstalled(root_output);
build_pc_file(
&format!("{filename}-uninstalled"),
root_output,
&pc_uninstalled,
)
}
fn patch_target(
pkg: &mut Package,
library_types: LibraryTypes,
capi_config: &CApiConfig,
) -> anyhow::Result<()> {
use cargo::core::compiler::CrateType;
let manifest = pkg.manifest_mut();
let targets = manifest.targets_mut();
let mut kinds = Vec::with_capacity(3);
if library_types.rlib {
kinds.push(CrateType::Rlib);
}
if library_types.staticlib {
kinds.push(CrateType::Staticlib);
}
if library_types.cdylib {
kinds.push(CrateType::Cdylib);
}
for target in targets.iter_mut().filter(|t| t.is_lib()) {
target.set_kind(TargetKind::Lib(kinds.to_vec()));
target.set_name(&capi_config.library.name);
}
Ok(())
}
/// Build def file for windows-msvc
fn build_def_file(
ws: &Workspace,
name: &str,
target: &target::Target,
targetdir: &Path,
) -> anyhow::Result<()> {
if target.os == "windows" && target.env == "msvc" {
ws.gctx().shell().status("Building", ".def file")?;
// Parse the .dll as an object file
let dll_path = targetdir.join(format!("{}.dll", name.replace('-', "_")));
let dll_content = std::fs::read(&dll_path)?;
let dll_file = object::File::parse(&*dll_content)?;
// Create the .def output file
let def_file = cargo_util::paths::create(targetdir.join(format!("{name}.def")))?;
write_def_file(dll_file, def_file)?;
}
Ok(())
}
fn write_def_file<W: std::io::Write>(dll_file: object::File, mut def_file: W) -> anyhow::Result<W> {
use object::read::Object;
writeln!(def_file, "EXPORTS")?;
for export in dll_file.exports()? {
def_file.write_all(export.name())?;
def_file.write_all(b"\n")?;
}
Ok(def_file)
}
/// Build import library for windows
fn build_implib_file(
ws: &Workspace,
build_targets: &BuildTargets,
name: &str,
target: &target::Target,
targetdir: &Path,
) -> anyhow::Result<()> {
if target.os == "windows" {
ws.gctx().shell().status("Building", "implib")?;
let def_path = targetdir.join(format!("{name}.def"));
let def_contents = cargo_util::paths::read(&def_path)?;
let flavor = match target.env.as_str() {
"msvc" => Flavor::Msvc,
_ => Flavor::Gnu,
};
let machine_type = match target.arch.as_str() {
"x86_64" => MachineType::AMD64,
"x86" => MachineType::I386,
"aarch64" => MachineType::ARM64,
_ => {
return Err(anyhow::anyhow!(
"Windows support for {} is not implemented yet.",
target.arch
))
}
};
let lib_name = build_targets
.shared_output_file_name()
.unwrap()
.into_string()
.unwrap();
let implib_path = build_targets.impl_lib.as_ref().unwrap();
let implib_file = cargo_util::paths::create(implib_path)?;
write_implib(implib_file, lib_name, machine_type, flavor, &def_contents)?;
}
Ok(())
}
fn write_implib<W: std::io::Write + std::io::Seek>(
mut w: W,
lib_name: String,
machine_type: MachineType,
flavor: Flavor,
def_contents: &str,
) -> anyhow::Result<W> {
let mut module_def = ModuleDef::parse(def_contents, machine_type)?;
module_def.import_name = lib_name;
let import_library = ImportLibrary::from_def(module_def, machine_type, flavor);
import_library.write_to(&mut w)?;
Ok(w)
}
#[derive(Debug)]
struct FingerPrint {
id: PackageId,
root_output: PathBuf,
build_targets: BuildTargets,
install_paths: InstallPaths,
static_libs: String,
hasher: DefaultHasher,
}
#[derive(serde::Serialize, serde::Deserialize)]
struct Cache {
hash: String,
static_libs: String,
}
impl FingerPrint {
fn new(
id: &PackageId,
root_output: &Path,
build_targets: &BuildTargets,
install_paths: &InstallPaths,
capi_config: &CApiConfig,
) -> Self {
let mut hasher = DefaultHasher::new();
capi_config.hash(&mut hasher);
Self {
id: id.to_owned(),
root_output: root_output.to_owned(),
build_targets: build_targets.clone(),
install_paths: install_paths.clone(),
static_libs: String::new(),
hasher,
}
}
fn hash(&self) -> anyhow::Result<Option<String>> {
let mut hasher = self.hasher.clone();
self.install_paths.hash(&mut hasher);
let mut paths: Vec<&PathBuf> = Vec::new();
if let Some(include) = &self.build_targets.include {
paths.push(include);
}
paths.extend(&self.build_targets.static_lib);
paths.extend(&self.build_targets.shared_lib);
for path in paths.iter() {
if let Ok(buf) = read_bytes(path) {
hasher.write(&buf);
} else {
return Ok(None);
};
}
let hash = hasher.finish();
// the hash is stored in a toml file which does not support u64 so store
// it as a string to prevent overflows.
Ok(Some(hash.to_string()))
}
fn path(&self) -> PathBuf {
// Use the crate name in the cache file as the same target dir
// may be used to build various libs
self.root_output
.join(format!("cargo-c-{}.cache", self.id.name()))
}
fn load_previous(&self) -> anyhow::Result<Cache> {
let mut f = open(self.path())?;
let mut cache_str = String::new();
f.read_to_string(&mut cache_str)?;
let cache = toml::de::from_str(&cache_str)?;
Ok(cache)
}
fn is_valid(&self) -> bool {
match (self.load_previous(), self.hash()) {
(Ok(prev), Ok(Some(current))) => prev.hash == current,
_ => false,
}
}
fn store(&self) -> anyhow::Result<()> {
if let Some(hash) = self.hash()? {
let cache = Cache {
hash,
static_libs: self.static_libs.to_owned(),
};
let buf = toml::ser::to_string(&cache)?;
write(self.path(), buf)?;
}
Ok(())
}
}
#[derive(Debug, Hash)]
pub struct CApiConfig {
pub header: HeaderCApiConfig,
pub pkg_config: PkgConfigCApiConfig,
pub library: LibraryCApiConfig,
pub install: InstallCApiConfig,
}
#[derive(Debug, Hash)]
pub struct HeaderCApiConfig {
pub name: String,
pub subdirectory: String,
pub generation: bool,
pub enabled: bool,
pub emit_version_constants: bool,
}
#[derive(Debug, Hash)]
pub struct PkgConfigCApiConfig {
pub name: String,
pub filename: String,
pub description: String,
pub version: String,
pub requires: Option<String>,
pub requires_private: Option<String>,
pub strip_include_path_components: usize,
}
#[derive(Debug, Hash)]
pub enum VersionSuffix {
Major,
MajorMinor,
MajorMinorPatch,
}
#[derive(Debug, Hash)]
pub struct LibraryCApiConfig {
pub name: String,
pub version: Version,
pub install_subdir: Option<String>,
pub versioning: bool,
pub version_suffix_components: Option<VersionSuffix>,
pub import_library: bool,
pub rustflags: Vec<String>,
}
impl LibraryCApiConfig {
pub fn sover(&self) -> String {
let major = self.version.major;
let minor = self.version.minor;
let patch = self.version.patch;
match self.version_suffix_components {
None => match (major, minor, patch) {
(0, 0, patch) => format!("0.0.{patch}"),
(0, minor, _) => format!("0.{minor}"),
(major, _, _) => format!("{major}"),
},
Some(VersionSuffix::Major) => format!("{major}"),
Some(VersionSuffix::MajorMinor) => format!("{major}.{minor}"),
Some(VersionSuffix::MajorMinorPatch) => format!("{major}.{minor}.{patch}"),
}
}
}
#[derive(Debug, Default, Hash)]
pub struct InstallCApiConfig {
pub include: Vec<InstallTarget>,
pub data: Vec<InstallTarget>,
}
#[derive(Debug, Hash)]
pub enum InstallTarget {
Asset(InstallTargetPaths),
Generated(InstallTargetPaths),
}
#[derive(Clone, Debug, Hash)]
pub struct InstallTargetPaths {
/// pattern to feed to glob::glob()
///
/// if the InstallTarget is Asset its root is the the root_path
/// if the InstallTarget is Generated its root is the root_output
pub from: String,
/// The path to be joined to the canonical directory to install the files discovered by the
/// glob, e.g. `{includedir}/{to}` for includes.
pub to: String,
}
impl InstallTargetPaths {
pub fn from_value(value: &toml::value::Value, default_to: &str) -> anyhow::Result<Self> {
let from = value
.get("from")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("a from field is required"))?;
let to = value
.get("to")
.and_then(|v| v.as_str())
.unwrap_or(default_to);
Ok(InstallTargetPaths {
from: from.to_string(),
to: to.to_string(),
})
}
pub fn install_paths(
&self,
root: &Path,
) -> anyhow::Result<impl Iterator<Item = (PathBuf, PathBuf)>> {
let pattern = root.join(&self.from);
let base_pattern = if self.from.contains("/**") {
pattern
.iter()
.take_while(|&c| c != std::ffi::OsStr::new("**"))
.collect()
} else {
pattern.parent().unwrap().to_path_buf()
};
let pattern = pattern.to_str().unwrap();
let to = PathBuf::from(&self.to);
let g = glob::glob(pattern)?.filter_map(move |p| {
if let Ok(p) = p {
if p.is_file() {
let from = p;
let to = to.join(from.strip_prefix(&base_pattern).unwrap());
Some((from, to))
} else {
None
}
} else {
None
}
});
Ok(g)
}
}
fn load_manifest_capi_config(
pkg: &Package,
rustc_target: &target::Target,
) -> anyhow::Result<CApiConfig> {
let name = &pkg
.manifest()
.targets()
.iter()
.find(|t| t.is_lib())
.unwrap()
.crate_name();
let root_path = pkg.root().to_path_buf();
let manifest_str = read(&root_path.join("Cargo.toml"))?;
let toml = manifest_str.parse::<toml::Value>()?;
let capi = toml
.get("package")
.and_then(|v| v.get("metadata"))
.and_then(|v| v.get("capi"));
if let Some(min_version) = capi
.as_ref()
.and_then(|capi| capi.get("min_version"))
.and_then(|v| v.as_str())
{
let min_version = Version::parse(min_version)?;
let version = Version::parse(env!("CARGO_PKG_VERSION"))?;
if min_version > version {
anyhow::bail!(
"Minimum required cargo-c version is {} but using cargo-c version {}",
min_version,
version
);
}
}
let header = capi.and_then(|v| v.get("header"));
let subdirectory = header
.as_ref()
.and_then(|h| h.get("subdirectory"))
.map(|v| {
if let Ok(b) = v.clone().try_into::<bool>() {
Ok(if b {
String::from(name)
} else {
String::from("")
})
} else {
v.clone().try_into::<String>()
}
})
.unwrap_or_else(|| Ok(String::from(name)))?;
let header = if let Some(capi) = capi {
HeaderCApiConfig {
name: header
.as_ref()
.and_then(|h| h.get("name"))
.or_else(|| capi.get("header_name"))
.map(|v| v.clone().try_into())
.unwrap_or_else(|| Ok(String::from(name)))?,
subdirectory,
generation: header
.as_ref()
.and_then(|h| h.get("generation"))
.map(|v| v.clone().try_into())
.unwrap_or(Ok(true))?,
enabled: header
.as_ref()
.and_then(|h| h.get("enabled"))
.map(|v| v.clone().try_into())
.unwrap_or(Ok(true))?,
emit_version_constants: header
.as_ref()
.and_then(|h| h.get("emit_version_constants"))
.map(|v| v.clone().try_into())
.unwrap_or(Ok(true))?,
}
} else {
HeaderCApiConfig {
name: String::from(name),
subdirectory: String::from(name),
generation: true,
enabled: true,
emit_version_constants: true,
}
};
let pc = capi.and_then(|v| v.get("pkg_config"));
let mut pc_name = String::from(name);
let mut pc_filename = String::from(name);
let mut description = String::from(
pkg.manifest()
.metadata()
.description
.as_deref()
.unwrap_or(""),
);
let mut version = pkg.version().to_string();
let mut requires = None;
let mut requires_private = None;
let mut strip_include_path_components = 0;
if let Some(pc) = pc {
if let Some(override_name) = pc.get("name").and_then(|v| v.as_str()) {
pc_name = String::from(override_name);
}
if let Some(override_filename) = pc.get("filename").and_then(|v| v.as_str()) {
pc_filename = String::from(override_filename);
}
if let Some(override_description) = pc.get("description").and_then(|v| v.as_str()) {
description = String::from(override_description);
}
if let Some(override_version) = pc.get("version").and_then(|v| v.as_str()) {
version = String::from(override_version);
}
if let Some(req) = pc.get("requires").and_then(|v| v.as_str()) {
requires = Some(String::from(req));
}
if let Some(req) = pc.get("requires_private").and_then(|v| v.as_str()) {
requires_private = Some(String::from(req));
}
strip_include_path_components = pc
.get("strip_include_path_components")
.map(|v| v.clone().try_into())
.unwrap_or_else(|| Ok(0))?
}
let pkg_config = PkgConfigCApiConfig {
name: pc_name,
filename: pc_filename,
description,
version,
requires,
requires_private,
strip_include_path_components,
};
let library = capi.and_then(|v| v.get("library"));
let mut lib_name = String::from(name);
let mut version = pkg.version().clone();
let mut install_subdir = None;
let mut versioning = true;
let mut version_suffix_components = None;
let mut import_library = true;
let mut rustflags = Vec::new();
if let Some(library) = library {
if let Some(override_name) = library.get("name").and_then(|v| v.as_str()) {
lib_name = String::from(override_name);
}
if let Some(override_version) = library.get("version").and_then(|v| v.as_str()) {
version = Version::parse(override_version)?;
}
if let Some(subdir) = library.get("install_subdir").and_then(|v| v.as_str()) {
install_subdir = Some(String::from(subdir));
}
versioning = library
.get("versioning")
.and_then(|v| v.as_bool())
.unwrap_or(true);
if let Some(value) = library.get("version_suffix_components") {
let value = value.as_integer().with_context(|| {
format!("Value for `version_suffix_components` is not an integer: {value:?}")
})?;
version_suffix_components = Some(match value {
1 => VersionSuffix::Major,
2 => VersionSuffix::MajorMinor,
3 => VersionSuffix::MajorMinorPatch,
_ => anyhow::bail!("Out of range value for version suffix components: {value}"),
});
}
import_library = library
.get("import_library")
.and_then(|v| v.as_bool())
.unwrap_or(true);
if let Some(args) = library.get("rustflags").and_then(|v| v.as_str()) {
let args = args
.split(' ')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string);
rustflags.extend(args);
}
}
if rustc_target.os == "android" {
versioning = false;
}
let library = LibraryCApiConfig {
name: lib_name,
version,
install_subdir,
versioning,
version_suffix_components,
import_library,
rustflags,
};
let default_assets_include = InstallTargetPaths {
from: "assets/capi/include/**/*".to_string(),
to: header.subdirectory.clone(),
};
let header_name = if header.name.ends_with(".h") {
format!("assets/{}", header.name)
} else {
format!("assets/{}.h", header.name)
};
let default_legacy_asset_include = InstallTargetPaths {
from: header_name,
to: header.subdirectory.clone(),
};
let default_generated_include = InstallTargetPaths {
from: "capi/include/**/*".to_string(),
to: header.subdirectory.clone(),
};
let mut include_targets = vec![
InstallTarget::Asset(default_assets_include),
InstallTarget::Asset(default_legacy_asset_include),
InstallTarget::Generated(default_generated_include),
];
let mut data_targets = Vec::new();
let mut data_subdirectory = name.clone();
fn custom_install_target_paths(
root: &toml::Value,
subdirectory: &str,
targets: &mut Vec<InstallTarget>,
) -> anyhow::Result<()> {
if let Some(assets) = root.get("asset").and_then(|v| v.as_array()) {
for asset in assets {
let target_paths = InstallTargetPaths::from_value(asset, subdirectory)?;
targets.push(InstallTarget::Asset(target_paths));
}
}
if let Some(generated) = root.get("generated").and_then(|v| v.as_array()) {
for gen in generated {
let target_paths = InstallTargetPaths::from_value(gen, subdirectory)?;
targets.push(InstallTarget::Generated(target_paths));
}
}
Ok(())
}
let install = capi.and_then(|v| v.get("install"));
if let Some(install) = install {
if let Some(includes) = install.get("include") {
custom_install_target_paths(includes, &header.subdirectory, &mut include_targets)?;
}
if let Some(data) = install.get("data") {
if let Some(subdir) = data.get("subdirectory").and_then(|v| v.as_str()) {
data_subdirectory = String::from(subdir);
}
custom_install_target_paths(data, &data_subdirectory, &mut data_targets)?;
}
}
let default_assets_data = InstallTargetPaths {
from: "assets/capi/share/**/*".to_string(),
to: data_subdirectory.clone(),
};
let default_generated_data = InstallTargetPaths {
from: "capi/share/**/*".to_string(),
to: data_subdirectory,
};
data_targets.extend([
InstallTarget::Asset(default_assets_data),
InstallTarget::Generated(default_generated_data),
]);
let install = InstallCApiConfig {
include: include_targets,
data: data_targets,
};
Ok(CApiConfig {
header,
pkg_config,
library,
install,
})
}
fn compile_options(
ws: &Workspace,
gctx: &GlobalContext,
args: &ArgMatches,
profile: InternedString,
compile_intent: UserIntent,
) -> anyhow::Result<CompileOptions> {
use cargo::core::compiler::CompileKind;
let mut compile_opts =
args.compile_options(gctx, compile_intent, Some(ws), ProfileChecking::Custom)?;
compile_opts.build_config.requested_profile = profile;
std::rc::Rc::get_mut(&mut compile_opts.cli_features.features)
.unwrap()
.insert(FeatureValue::new("capi".into()));
compile_opts.filter = CompileFilter::new(
LibRule::True,
FilterRule::none(),
FilterRule::none(),
FilterRule::none(),
FilterRule::none(),
);
compile_opts.build_config.unit_graph = false;
let rustc = gctx.load_global_rustc(Some(ws))?;
// Always set the target, requested_kinds is a vec of a single element.
if compile_opts.build_config.requested_kinds[0].is_host() {
compile_opts.build_config.requested_kinds =
CompileKind::from_requested_targets(gctx, &[rustc.host.to_string()])?
}
Ok(compile_opts)
}
#[derive(Default)]
struct Exec {
ran: AtomicBool,
link_line: Mutex<HashMap<PackageId, String>>,
}
use cargo::CargoResult;
use cargo_util::ProcessBuilder;
impl Executor for Exec {
fn exec(
&self,
cmd: &ProcessBuilder,
id: PackageId,
_target: &Target,
_mode: CompileMode,
on_stdout_line: &mut dyn FnMut(&str) -> CargoResult<()>,
on_stderr_line: &mut dyn FnMut(&str) -> CargoResult<()>,
) -> CargoResult<()> {
self.ran.store(true, Ordering::Relaxed);
cmd.exec_with_streaming(
on_stdout_line,
&mut |s| {
#[derive(serde::Deserialize, Debug)]
struct Message {
message: String,
level: String,
}
if let Ok(msg) = serde_json::from_str::<Message>(s) {
// suppress the native-static-libs messages
if msg.level == "note" {
if msg.message.starts_with("Link against the following native artifacts when linking against this static library") {
Ok(())
} else if let Some(link_line) = msg.message.strip_prefix("native-static-libs:") {
self.link_line.lock().unwrap().insert(id, link_line.to_string());
Ok(())
} else {
on_stderr_line(s)
}
} else {
on_stderr_line(s)
}
} else {
on_stderr_line(s)
}
},
false,
)
.map(drop)
}
}
use cargo::core::compiler::{unit_graph, CompileMode, UnitInterner};
use cargo::ops::create_bcx;
fn set_deps_args(
dep: &UnitDep,
graph: &UnitGraph,
extra_compiler_args: &mut HashMap<Unit, Vec<String>>,
global_args: &[String],
) {
if !dep.unit_for.is_for_host() {
for dep in graph[&dep.unit].iter() {
set_deps_args(dep, graph, extra_compiler_args, global_args);
}
extra_compiler_args
.entry(dep.unit.clone())
.or_insert_with(|| global_args.to_owned());
}
}
fn compile_with_exec(
ws: &Workspace<'_>,
options: &CompileOptions,
exec: &Arc<dyn Executor>,
rustc_target: &target::Target,
root_output: &Path,
args: &ArgMatches,
) -> CargoResult<HashMap<PackageId, PathBuf>> {
ws.emit_warnings()?;
let interner = UnitInterner::new();
let mut bcx = create_bcx(ws, options, &interner)?;
let unit_graph = &bcx.unit_graph;
let extra_compiler_args = &mut bcx.extra_compiler_args;
for unit in bcx.roots.iter() {
let pkg = &unit.pkg;
let capi_config = load_manifest_capi_config(pkg, rustc_target)?;
let name = &capi_config.library.name;
let install_paths = InstallPaths::new(name, rustc_target, args, &capi_config);
let pkg_rustflags = &capi_config.library.rustflags;
let mut leaf_args: Vec<String> = rustc_target
.shared_object_link_args(&capi_config, &install_paths.libdir, root_output)
.into_iter()
.flat_map(|l| ["-C".to_string(), format!("link-arg={l}")])
.collect();
leaf_args.extend(pkg_rustflags.clone());
leaf_args.push("--cfg".into());
leaf_args.push("cargo_c".into());
leaf_args.push("--print".into());
leaf_args.push("native-static-libs".into());
if args.flag("crt-static") {
leaf_args.push("-C".into());
leaf_args.push("target-feature=+crt-static".into());
}
extra_compiler_args.insert(unit.clone(), leaf_args.to_owned());
for dep in unit_graph[unit].iter() {
set_deps_args(dep, unit_graph, extra_compiler_args, pkg_rustflags);
}
}
if options.build_config.unit_graph {
unit_graph::emit_serialized_unit_graph(&bcx.roots, &bcx.unit_graph, ws.gctx())?;
return Ok(HashMap::new());
}
let cx = cargo::core::compiler::BuildRunner::new(&bcx)?;
let r = cx.compile(exec)?;
let out_dirs = r
.cdylibs
.iter()
.filter_map(|l| {
let id = l.unit.pkg.package_id();
if let Some(ref m) = l.script_meta {
if let Some(env) = r.extra_env.get(m) {
env.iter().find_map(|e| {
if e.0 == "OUT_DIR" {
Some((id, PathBuf::from(&e.1)))
} else {
None
}
})
} else {
None
}
} else {
None
}
})
.collect();
Ok(out_dirs)
}
#[derive(Debug)]
pub struct CPackage {
pub version: Version,
pub root_path: PathBuf,
pub capi_config: CApiConfig,
pub build_targets: BuildTargets,
pub install_paths: InstallPaths,
finger_print: FingerPrint,
}
impl CPackage {
fn from_package(
pkg: &mut Package,
args: &ArgMatches,
library_types: LibraryTypes,
rustc_target: &target::Target,
root_output: &Path,
) -> anyhow::Result<CPackage> {
let id = pkg.package_id();
let version = pkg.version().clone();
let root_path = pkg.root().to_path_buf();
let capi_config = load_manifest_capi_config(pkg, rustc_target)?;
patch_target(pkg, library_types, &capi_config)?;
let name = &capi_config.library.name;
let install_paths = InstallPaths::new(name, rustc_target, args, &capi_config);
let build_targets = BuildTargets::new(
name,
rustc_target,
root_output,
library_types,
&capi_config,
args.get_flag("meson"),
)?;
let finger_print = FingerPrint::new(
&id,
root_output,
&build_targets,
&install_paths,
&capi_config,
);
Ok(CPackage {
version,
root_path,
capi_config,
build_targets,
install_paths,
finger_print,
})
}
}
fn deprecation_warnings(ws: &Workspace, args: &ArgMatches) -> anyhow::Result<()> {
if args.contains_id("dlltool") {
ws.gctx()
.shell()
.warn("The `dlltool` support is now builtin. The cli option is deprecated and will be removed in the future")?;
}
Ok(())
}
/// What library types to build
#[derive(Debug, Clone, Copy)]
pub struct LibraryTypes {
pub staticlib: bool,
pub cdylib: bool,
pub rlib: bool,
}
impl LibraryTypes {
fn from_target(target: &target::Target) -> Self {
// for os == "none", cdylib does not make sense. By default cdylib is also not built on
// musl, but that can be overriden by the user. That is useful when musl is being used as
// main libc, e.g. in Alpine, Gentoo and OpenWRT
//
// See also
//
// - https://github.com/lu-zero/cargo-c?tab=readme-ov-file#shared-libraries-are-not-built-on-musl-systems
// - https://github.com/lu-zero/cargo-c/issues/180
Self {
staticlib: true,
cdylib: target.os != "none",
rlib: false,
}
}
fn from_args(target: &target::Target, args: &ArgMatches, want_rlib: bool) -> Self {
Self {
rlib: want_rlib,
..match args.get_many::<String>("library-type") {
Some(library_types) => Self::from_library_types(target, library_types),
None => Self::from_target(target),
}
}
}
fn from_library_types<S: AsRef<str>>(
target: &target::Target,
library_types: impl Iterator<Item = S>,
) -> Self {
let (mut staticlib, mut cdylib) = (false, false);
for library_type in library_types {
staticlib |= library_type.as_ref() == "staticlib";
cdylib |= library_type.as_ref() == "cdylib";
}
// when os is none, a cdylib cannot be produced
// forcing a cdylib for musl is allowed here (see [`LibraryTypes::from_target`])
cdylib &= target.os != "none";
Self {
staticlib,
cdylib,
rlib: false,
}
}
const fn only_staticlib(self) -> bool {
self.staticlib && !self.cdylib
}
const fn only_cdylib(self) -> bool {
self.cdylib && !self.staticlib
}
}
fn static_libraries(link_line: &str, rustc_target: &target::Target) -> String {
link_line
.trim()
.split(' ')
.filter(|s| {
if rustc_target.env == "msvc" && s.starts_with("/defaultlib") {
return false;
}
!s.is_empty()
})
.map(|lib| {
if rustc_target.env == "msvc" && lib.ends_with(".lib") {
return format!("-l{}", lib.trim_end_matches(".lib"));
}
lib.trim().to_string()
})
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
}
pub fn cbuild(
ws: &mut Workspace,
config: &GlobalContext,
args: &ArgMatches,
default_profile: &str,
tests: bool,
) -> anyhow::Result<(Vec<CPackage>, CompileOptions)> {
deprecation_warnings(ws, args)?;
let (target, is_target_overridden) = match args.targets()?.as_slice() {
[] => (config.load_global_rustc(Some(ws))?.host.to_string(), false),
[target] => (target.to_string(), true),
[..] => anyhow::bail!("Multiple targets not supported yet"),
};
let rustc_target = target::Target::new(Some(&target), is_target_overridden)?;
let library_types = LibraryTypes::from_args(&rustc_target, args, tests);
let profile = args.get_profile_name(default_profile, ProfileChecking::Custom)?;
let profiles = Profiles::new(ws, profile)?;
let mut compile_opts = compile_options(ws, config, args, profile, UserIntent::Build)?;
// TODO: there must be a simpler way to get the right path.
let root_output = ws
.target_dir()
.as_path_unlocked()
.to_path_buf()
.join(PathBuf::from(target))
.join(profiles.get_dir_name());
let mut members = Vec::new();
let mut pristine = false;
let requested: Vec<_> = compile_opts
.spec
.get_packages(ws)?
.iter()
.map(|p| p.package_id())
.collect();
let capi_feature = InternedString::new("capi");
let is_relevant_package = |package: &Package| {
package.library().is_some()
&& package.summary().features().contains_key(&capi_feature)
&& requested.contains(&package.package_id())
};
for m in ws.members_mut().filter(|p| is_relevant_package(p)) {
let cpkg = CPackage::from_package(m, args, library_types, &rustc_target, &root_output)?;
pristine |= cpkg.finger_print.load_previous().is_err() || !cpkg.finger_print.is_valid();
members.push(cpkg);
}
// If the cache is somehow missing force a full rebuild;
compile_opts.build_config.force_rebuild |= pristine;
let exec = Arc::new(Exec::default());
let out_dirs = compile_with_exec(
ws,
&compile_opts,
&(exec.clone() as Arc<dyn Executor>),
&rustc_target,
&root_output,
args,
)?;
for cpkg in members.iter_mut() {
let out_dir = out_dirs.get(&cpkg.finger_print.id).map(|p| p.as_path());
cpkg.build_targets
.extra
.setup(&cpkg.capi_config, &cpkg.root_path, out_dir)?;
if cpkg.capi_config.header.generation {
let mut header_name = PathBuf::from(&cpkg.capi_config.header.name);
header_name.set_extension("h");
let from = root_output.join(&header_name);
let to = Path::new(&cpkg.capi_config.header.subdirectory).join(&header_name);
cpkg.build_targets.extra.include.push((from, to));
}
}
if pristine {
// restore the default to make sure the tests do not trigger a second rebuild.
compile_opts.build_config.force_rebuild = false;
}
let new_build = exec.ran.load(Ordering::Relaxed);
for cpkg in members.iter_mut() {
// it is a new build, build the additional files and update update the cache
if new_build {
let name = &cpkg.capi_config.library.name;
let (pkg_config_static_libs, static_libs) = if library_types.only_cdylib() {
(String::new(), String::new())
} else if let Some(libs) = exec.link_line.lock().unwrap().get(&cpkg.finger_print.id) {
(static_libraries(libs, &rustc_target), libs.to_string())
} else {
(String::new(), String::new())
};
let capi_config = &cpkg.capi_config;
let build_targets = &cpkg.build_targets;
let mut pc = PkgConfig::from_workspace(name, &cpkg.install_paths, args, capi_config);
if library_types.only_staticlib() {
pc.add_lib(&pkg_config_static_libs);
}
pc.add_lib_private(&pkg_config_static_libs);
build_pc_files(ws, &capi_config.pkg_config.filename, &root_output, &pc)?;
if !library_types.only_staticlib() && capi_config.library.import_library {
let lib_name = name;
build_def_file(ws, lib_name, &rustc_target, &root_output)?;
build_implib_file(ws, build_targets, lib_name, &rustc_target, &root_output)?;
}
if capi_config.header.enabled {
let header_name = &capi_config.header.name;
let emit_version_constants = capi_config.header.emit_version_constants;
if capi_config.header.generation {
build_include_file(
ws,
header_name,
emit_version_constants.then_some(&cpkg.version),
&root_output,
&cpkg.root_path,
)?;
}
copy_prebuilt_include_file(ws, build_targets, &root_output)?;
}
if name.contains('-') {
let from_build_targets = BuildTargets::new(
&name.replace('-', "_"),
&rustc_target,
&root_output,
library_types,
capi_config,
args.get_flag("meson"),
)?;
if let (Some(from_static_lib), Some(to_static_lib)) = (
from_build_targets.static_lib.as_ref(),
build_targets.static_lib.as_ref(),
) {
copy(from_static_lib, to_static_lib)?;
}
if let (Some(from_shared_lib), Some(to_shared_lib)) = (
from_build_targets.shared_lib.as_ref(),
build_targets.shared_lib.as_ref(),
) {
copy(from_shared_lib, to_shared_lib)?;
}
if let (Some(from_debug_info), Some(to_debug_info)) = (
from_build_targets.debug_info.as_ref(),
build_targets.debug_info.as_ref(),
) {
copy(from_debug_info, to_debug_info)?;
}
}
// This can be supplied to Rust, so it must be in
// linker-native syntax
cpkg.finger_print.static_libs = static_libs;
cpkg.finger_print.store()?;
} else {
// It is not a new build, recover the static_libs value from the cache
cpkg.finger_print.static_libs = cpkg.finger_print.load_previous()?.static_libs;
}
ws.gctx().shell().verbose(|s| {
let path = &format!("PKG_CONFIG_PATH=\"{}\"", root_output.display());
s.note(path)
})?;
}
Ok((members, compile_opts))
}
pub fn ctest(
ws: &Workspace,
args: &ArgMatches,
packages: &[CPackage],
mut compile_opts: CompileOptions,
) -> CliResult {
compile_opts.build_config.requested_profile =
args.get_profile_name("test", ProfileChecking::Custom)?;
compile_opts.build_config.intent = UserIntent::Test;
compile_opts.filter = ops::CompileFilter::new(
LibRule::Default, // compile the library, so the unit tests can be run filtered
FilterRule::none(), // we do not have binaries
FilterRule::All, // compile the tests, so the integration tests can be run filtered
FilterRule::none(), // specify --examples to unit test binaries filtered
FilterRule::none(), // specify --benches to unit test benchmarks filtered
);
compile_opts.target_rustc_args = None;
let ops = ops::TestOptions {
no_run: args.flag("no-run"),
no_fail_fast: args.flag("no-fail-fast"),
compile_opts,
};
let test_args = args.get_one::<String>("TESTNAME").into_iter();
let test_args = test_args.chain(args.get_many::<String>("args").unwrap_or_default());
let test_args = test_args.map(String::as_str).collect::<Vec<_>>();
use std::ffi::OsString;
let mut cflags = OsString::new();
for pkg in packages {
let static_lib_path = pkg.build_targets.static_lib.as_ref().unwrap();
let builddir = static_lib_path.parent().unwrap();
cflags.push("-I");
cflags.push(builddir);
cflags.push(" ");
// We push the full path here to work around macos ld not supporting the -l:{filename} syntax
cflags.push(static_lib_path);
// We push the static_libs as CFLAGS as well to avoid mangling the options on msvc
cflags.push(" ");
cflags.push(&pkg.finger_print.static_libs);
}
std::env::set_var("INLINE_C_RS_CFLAGS", cflags);
ops::run_tests(ws, &ops, &test_args)
}
#[cfg(test)]
mod tests {
use super::*;
use semver::Version;
fn make_test_library_config(version: &str) -> LibraryCApiConfig {
LibraryCApiConfig {
name: "example".to_string(),
version: Version::parse(version).unwrap(),
install_subdir: None,
versioning: true,
version_suffix_components: None,
import_library: true,
rustflags: vec![],
}
}
#[test]
pub fn test_semver_zero_zero_zero() {
let library = make_test_library_config("0.0.0");
let sover = library.sover();
assert_eq!(sover, "0.0.0");
}
#[test]
pub fn test_semver_zero_one_zero() {
let library = make_test_library_config("0.1.0");
let sover = library.sover();
assert_eq!(sover, "0.1");
}
#[test]
pub fn test_semver_one_zero_zero() {
let library = make_test_library_config("1.0.0");
let sover = library.sover();
assert_eq!(sover, "1");
}
#[test]
pub fn text_one_fixed_zero_zero_zero() {
let mut library = make_test_library_config("0.0.0");
library.version_suffix_components = Some(VersionSuffix::Major);
let sover = library.sover();
assert_eq!(sover, "0");
}
#[test]
pub fn text_two_fixed_one_zero_zero() {
let mut library = make_test_library_config("1.0.0");
library.version_suffix_components = Some(VersionSuffix::MajorMinor);
let sover = library.sover();
assert_eq!(sover, "1.0");
}
#[test]
pub fn text_three_fixed_one_zero_zero() {
let mut library = make_test_library_config("1.0.0");
library.version_suffix_components = Some(VersionSuffix::MajorMinorPatch);
let sover = library.sover();
assert_eq!(sover, "1.0.0");
}
#[test]
pub fn test_lib_listing() {
let libs_osx = "-lSystem -lc -lm";
let libs_linux = "-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc";
let libs_hurd = "-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc";
let libs_msvc = "kernel32.lib advapi32.lib kernel32.lib ntdll.lib userenv.lib ws2_32.lib kernel32.lib ws2_32.lib kernel32.lib msvcrt.lib /defaultlib:msvcrt";
let libs_mingw = "-lkernel32 -ladvapi32 -lkernel32 -lntdll -luserenv -lws2_32 -lkernel32 -lws2_32 -lkernel32";
let target_osx = target::Target::new(Some("x86_64-apple-darwin"), false).unwrap();
let target_linux = target::Target::new(Some("x86_64-unknown-linux-gnu"), false).unwrap();
let target_hurd = target::Target::new(Some("x86_64-unknown-hurd-gnu"), false).unwrap();
let target_msvc = target::Target::new(Some("x86_64-pc-windows-msvc"), false).unwrap();
let target_mingw = target::Target::new(Some("x86_64-pc-windows-gnu"), false).unwrap();
assert_eq!(static_libraries(libs_osx, &target_osx), "-lSystem -lc -lm");
assert_eq!(
static_libraries(libs_linux, &target_linux),
"-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc"
);
assert_eq!(
static_libraries(libs_hurd, &target_hurd),
"-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc"
);
assert_eq!(
static_libraries(libs_msvc, &target_msvc),
"-lkernel32 -ladvapi32 -lkernel32 -lntdll -luserenv -lws2_32 -lkernel32 -lws2_32 -lkernel32 -lmsvcrt"
);
assert_eq!(
static_libraries(libs_mingw, &target_mingw),
"-lkernel32 -ladvapi32 -lkernel32 -lntdll -luserenv -lws2_32 -lkernel32 -lws2_32 -lkernel32"
);
}
}
07070100000032000081A40000000000000000000000016898C2E900002942000000000000000000000000000000000000002500000000cargo-c-0.10.15/src/build_targets.rsuse std::ffi::OsString;
use std::path::{Path, PathBuf};
use crate::build::{CApiConfig, InstallTarget, LibraryTypes};
use crate::install::LibType;
use crate::target::Target;
#[derive(Debug, Default, Clone)]
pub struct ExtraTargets {
pub include: Vec<(PathBuf, PathBuf)>,
pub data: Vec<(PathBuf, PathBuf)>,
}
impl ExtraTargets {
pub fn setup(
&mut self,
capi_config: &CApiConfig,
root_dir: &Path,
out_dir: Option<&Path>,
) -> anyhow::Result<()> {
self.include = extra_targets(&capi_config.install.include, root_dir, out_dir)?;
self.data = extra_targets(&capi_config.install.data, root_dir, out_dir)?;
Ok(())
}
}
fn extra_targets(
targets: &[InstallTarget],
root_path: &Path,
root_output: Option<&Path>,
) -> anyhow::Result<Vec<(PathBuf, PathBuf)>> {
use itertools::*;
targets
.iter()
.filter_map(|t| match t {
InstallTarget::Asset(paths) => Some(paths.install_paths(root_path)),
InstallTarget::Generated(paths) => {
root_output.map(|root_output| paths.install_paths(root_output))
}
})
.flatten_ok()
.collect()
}
#[derive(Debug, Clone)]
pub struct BuildTargets {
pub name: String,
pub include: Option<PathBuf>,
pub static_lib: Option<PathBuf>,
pub shared_lib: Option<PathBuf>,
pub impl_lib: Option<PathBuf>,
pub debug_info: Option<PathBuf>,
pub def: Option<PathBuf>,
pub pc: PathBuf,
pub target: Target,
pub extra: ExtraTargets,
pub use_meson_naming_convention: bool,
}
impl BuildTargets {
pub fn new(
name: &str,
target: &Target,
targetdir: &Path,
library_types: LibraryTypes,
capi_config: &CApiConfig,
use_meson_naming_convention: bool,
) -> anyhow::Result<BuildTargets> {
let pc = targetdir.join(format!("{}.pc", &capi_config.pkg_config.filename));
let include = if capi_config.header.enabled && capi_config.header.generation {
Some(targetdir.join(&capi_config.header.name).with_extension("h"))
} else {
None
};
let Some(file_names) =
FileNames::from_target(target, name, targetdir, use_meson_naming_convention)
else {
return Err(anyhow::anyhow!(
"The target {}-{} is not supported yet",
target.os,
target.env
));
};
Ok(BuildTargets {
pc,
include,
static_lib: library_types.staticlib.then_some(file_names.static_lib),
shared_lib: library_types.cdylib.then_some(file_names.shared_lib),
impl_lib: file_names.impl_lib,
debug_info: file_names.debug_info,
def: file_names.def,
use_meson_naming_convention,
name: name.into(),
target: target.clone(),
extra: Default::default(),
})
}
fn lib_type(&self) -> LibType {
LibType::from_build_targets(self)
}
pub fn debug_info_file_name(&self, bindir: &Path, libdir: &Path) -> Option<PathBuf> {
match self.lib_type() {
// FIXME: Requires setting split-debuginfo to packed and
// specifying the corresponding file name convention
// in BuildTargets::new.
LibType::So | LibType::Dylib => {
Some(libdir.join(self.debug_info.as_ref()?.file_name()?))
}
LibType::Windows => Some(bindir.join(self.debug_info.as_ref()?.file_name()?)),
}
}
pub fn static_output_file_name(&self) -> Option<OsString> {
match self.lib_type() {
LibType::Windows => {
if self.static_lib.is_some() && self.use_meson_naming_convention {
Some(format!("lib{}.a", self.name).into())
} else {
Some(self.static_lib.as_ref()?.file_name()?.to_owned())
}
}
_ => Some(self.static_lib.as_ref()?.file_name()?.to_owned()),
}
}
pub fn shared_output_file_name(&self) -> Option<OsString> {
match self.lib_type() {
LibType::Windows => {
if self.shared_lib.is_some()
&& self.use_meson_naming_convention
&& self.target.env == "gnu"
{
Some(format!("lib{}.dll", self.name).into())
} else {
Some(self.shared_lib.as_ref()?.file_name()?.to_owned())
}
}
_ => Some(self.shared_lib.as_ref()?.file_name()?.to_owned()),
}
}
}
#[derive(Debug, PartialEq, Eq)]
struct FileNames {
static_lib: PathBuf,
shared_lib: PathBuf,
impl_lib: Option<PathBuf>,
debug_info: Option<PathBuf>,
def: Option<PathBuf>,
}
impl FileNames {
fn from_target(
target: &Target,
lib_name: &str,
targetdir: &Path,
use_meson_naming_convention: bool,
) -> Option<Self> {
let (shared_lib, static_lib, impl_lib, debug_info, def) = match target.os.as_str() {
"none" | "linux" | "freebsd" | "dragonfly" | "netbsd" | "android" | "haiku"
| "illumos" | "openbsd" | "emscripten" | "hurd" => {
let static_lib = targetdir.join(format!("lib{lib_name}.a"));
let shared_lib = targetdir.join(format!("lib{lib_name}.so"));
(shared_lib, static_lib, None, None, None)
}
"macos" | "ios" | "tvos" | "visionos" => {
let static_lib = targetdir.join(format!("lib{lib_name}.a"));
let shared_lib = targetdir.join(format!("lib{lib_name}.dylib"));
(shared_lib, static_lib, None, None, None)
}
"windows" => {
let shared_lib = targetdir.join(format!("{lib_name}.dll"));
let def = targetdir.join(format!("{lib_name}.def"));
if target.env == "msvc" {
let static_lib = targetdir.join(format!("{lib_name}.lib"));
let impl_lib = if use_meson_naming_convention {
targetdir.join(format!("{lib_name}.lib"))
} else {
targetdir.join(format!("{lib_name}.dll.lib"))
};
let pdb = Some(targetdir.join(format!("{lib_name}.pdb")));
(shared_lib, static_lib, Some(impl_lib), pdb, Some(def))
} else {
let static_lib = targetdir.join(format!("lib{lib_name}.a"));
let impl_lib = if use_meson_naming_convention {
targetdir.join(format!("lib{lib_name}.dll.a"))
} else {
targetdir.join(format!("{lib_name}.dll.a"))
};
let pdb = None;
(shared_lib, static_lib, Some(impl_lib), pdb, Some(def))
}
}
_ => return None,
};
Some(Self {
static_lib,
shared_lib,
impl_lib,
debug_info,
def,
})
}
}
#[cfg(test)]
mod test {
use std::path::{Path, PathBuf};
use super::{FileNames, Target};
#[test]
fn unix() {
for os in [
"none",
"linux",
"freebsd",
"dragonfly",
"netbsd",
"android",
"haiku",
"illumos",
"emscripten",
"hurd",
] {
let target = Target {
is_target_overridden: false,
arch: String::from(""),
os: os.to_string(),
env: String::from(""),
};
let file_names =
FileNames::from_target(&target, "ferris", Path::new("/foo/bar"), false);
let expected = FileNames {
static_lib: PathBuf::from("/foo/bar/libferris.a"),
shared_lib: PathBuf::from("/foo/bar/libferris.so"),
impl_lib: None,
debug_info: None,
def: None,
};
assert_eq!(file_names.unwrap(), expected);
}
}
#[test]
fn apple() {
for os in ["macos", "ios", "tvos", "visionos"] {
let target = Target {
is_target_overridden: false,
arch: String::from(""),
os: os.to_string(),
env: String::from(""),
};
let file_names =
FileNames::from_target(&target, "ferris", Path::new("/foo/bar"), false);
let expected = FileNames {
static_lib: PathBuf::from("/foo/bar/libferris.a"),
shared_lib: PathBuf::from("/foo/bar/libferris.dylib"),
impl_lib: None,
debug_info: None,
def: None,
};
assert_eq!(file_names.unwrap(), expected);
}
}
#[test]
fn windows_msvc() {
let target = Target {
is_target_overridden: false,
arch: String::from(""),
os: String::from("windows"),
env: String::from("msvc"),
};
let file_names = FileNames::from_target(&target, "ferris", Path::new("/foo/bar"), false);
let expected = FileNames {
static_lib: PathBuf::from("/foo/bar/ferris.lib"),
shared_lib: PathBuf::from("/foo/bar/ferris.dll"),
impl_lib: Some(PathBuf::from("/foo/bar/ferris.dll.lib")),
debug_info: Some(PathBuf::from("/foo/bar/ferris.pdb")),
def: Some(PathBuf::from("/foo/bar/ferris.def")),
};
assert_eq!(file_names.unwrap(), expected);
}
#[test]
fn windows_gnu() {
let target = Target {
is_target_overridden: false,
arch: String::from(""),
os: String::from("windows"),
env: String::from("gnu"),
};
let file_names = FileNames::from_target(&target, "ferris", Path::new("/foo/bar"), false);
let expected = FileNames {
static_lib: PathBuf::from("/foo/bar/libferris.a"),
shared_lib: PathBuf::from("/foo/bar/ferris.dll"),
impl_lib: Some(PathBuf::from("/foo/bar/ferris.dll.a")),
debug_info: None,
def: Some(PathBuf::from("/foo/bar/ferris.def")),
};
assert_eq!(file_names.unwrap(), expected);
}
}
07070100000033000081A40000000000000000000000016898C2E900002247000000000000000000000000000000000000001B00000000cargo-c-0.10.15/src/cli.rsuse std::ffi::{OsStr, OsString};
use std::path::PathBuf;
use cargo::util::command_prelude::CommandExt;
use cargo::util::command_prelude::{flag, multi_opt, opt};
use cargo::util::{style, CliError, CliResult};
use cargo_util::{ProcessBuilder, ProcessError};
use clap::{Arg, ArgAction, ArgMatches, Command, CommandFactory, Parser};
use crate::target::Target;
// TODO: convert to a function using cargo opt()
#[allow(dead_code)]
#[derive(Clone, Debug, Parser)]
struct Common {
/// Path to directory where target should be copied to
#[clap(long = "destdir")]
destdir: Option<PathBuf>,
/// Directory path used to construct the values of
/// `bindir`, `datarootdir`, `includedir`, `libdir`
///
/// If they are absolute the prefix is ignored.
#[clap(long = "prefix", default_value = "/usr/local")]
prefix: PathBuf,
/// Path to directory for installing generated library files
#[clap(long = "libdir", default_value = "lib")]
libdir: PathBuf,
/// Path to directory for installing generated headers files
#[clap(long = "includedir", default_value = "include")]
includedir: PathBuf,
/// Path to directory for installing generated executable files
#[clap(long = "bindir", default_value = "bin")]
bindir: Option<PathBuf>,
/// Path to directory for installing generated pkg-config .pc files
///
/// [default: {libdir}/pkgconfig]
#[clap(long = "pkgconfigdir")]
pkgconfigdir: Option<PathBuf>,
/// Path to directory for installing read-only data
#[clap(long = "datarootdir", default_value = "share")]
datarootdir: PathBuf,
/// Path to directory for installing read-only application-specific data
///
/// [default: {datarootdir}]
#[clap(long = "datadir")]
datadir: Option<PathBuf>,
#[clap(long = "dlltool")]
/// Use the provided dlltool when building for the windows-gnu targets. (deprecated and no-op)
dlltool: Option<PathBuf>,
#[clap(long = "crt-static")]
/// Build the library embedding the C runtime
crt_static: bool,
/// Use the Linux/Meson library naming convention on Windows
#[clap(long = "meson-paths", default_value = "false")]
meson: bool,
}
pub fn main_cli() -> Command {
let styles = {
clap::builder::styling::Styles::styled()
.header(style::HEADER)
.usage(style::USAGE)
.literal(style::LITERAL)
.placeholder(style::PLACEHOLDER)
.error(style::ERROR)
.valid(style::VALID)
.invalid(style::INVALID)
};
clap::command!()
.dont_collapse_args_in_usage(true)
.allow_external_subcommands(true)
.styles(styles)
}
fn base_cli() -> Command {
let default_target = Target::new::<&str>(None, false);
let app = Common::command()
.allow_external_subcommands(true)
.arg(flag("version", "Print version info and exit").short('V'))
.arg(flag("list", "List installed commands"))
.arg(opt("explain", "Run `rustc --explain CODE`").value_name("CODE"))
.arg(
opt(
"verbose",
"Use verbose output (-vv very verbose/build.rs output)",
)
.short('v')
.action(ArgAction::Count)
.global(true),
)
.arg_silent_suggestion()
.arg(
opt("color", "Coloring: auto, always, never")
.value_name("WHEN")
.global(true),
)
.arg(flag("frozen", "Require Cargo.lock and cache are up to date").global(true))
.arg(flag("locked", "Require Cargo.lock is up to date").global(true))
.arg(flag("offline", "Run without accessing the network").global(true))
.arg(multi_opt("config", "KEY=VALUE", "Override a configuration value").global(true))
.arg(
Arg::new("unstable-features")
.help("Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details")
.short('Z')
.value_name("FLAG")
.action(ArgAction::Append)
.global(true),
)
.arg_parallel()
.arg_targets_all(
"Build only this package's library",
"Build only the specified binary",
"Build all binaries",
"Build only the specified example",
"Build all examples",
"Build only the specified test target",
"Build all tests",
"Build only the specified bench target",
"Build all benches",
"Build all targets",
)
.arg(
multi_opt(
"library-type",
"LIBRARY-TYPE",
"Build only a type of library",
)
.global(true)
.ignore_case(true)
.value_parser(["cdylib", "staticlib"]),
)
.arg_profile("Build artifacts with the specified profile")
.arg_features()
.arg_target_triple("Build for the target triple")
.arg_target_dir()
.arg_manifest_path()
.arg_message_format()
.arg_build_plan();
if let Ok(t) = default_target {
app.mut_arg("prefix", |a| {
a.default_value(t.default_prefix().as_os_str().to_os_string())
})
.mut_arg("libdir", |a| {
a.default_value(t.default_libdir().as_os_str().to_os_string())
})
.mut_arg("datadir", |a| {
a.default_value(t.default_datadir().as_os_str().to_os_string())
})
.mut_arg("includedir", |a| {
a.default_value(t.default_includedir().as_os_str().to_os_string())
})
} else {
app
}
}
pub fn subcommand_build(name: &'static str, about: &'static str) -> Command {
base_cli()
.name(name)
.about(about)
.arg_release("Build artifacts in release mode, with optimizations")
.arg_package_spec_no_all(
"Package to build (see `cargo help pkgid`)",
"Build all packages in the workspace",
"Exclude packages from the build",
)
.after_help(
"
Compilation can be configured via the use of profiles which are configured in
the manifest. The default profile for this command is `dev`, but passing
the --release flag will use the `release` profile instead.
",
)
}
pub fn subcommand_install(name: &'static str, about: &'static str) -> Command {
base_cli()
.name(name)
.about(about)
.arg(flag("debug", "Build in debug mode instead of release mode"))
.arg_release(
"Build artifacts in release mode, with optimizations. This is the default behavior.",
)
.arg_package_spec_no_all(
"Package to install (see `cargo help pkgid`)",
"Install all packages in the workspace",
"Exclude packages from being installed",
)
.after_help(
"
Compilation can be configured via the use of profiles which are configured in
the manifest. The default profile for this command is `release`, but passing
the --debug flag will use the `dev` profile instead.
",
)
}
pub fn subcommand_test(name: &'static str) -> Command {
base_cli()
.name(name)
.about("Test the crate C-API")
.arg(
Arg::new("TESTNAME")
.action(ArgAction::Set)
.help("If specified, only run tests containing this string in their names"),
)
.arg(
Arg::new("args")
.help("Arguments for the test binary")
.num_args(0..)
.last(true),
)
.arg_release("Build artifacts in release mode, with optimizations")
.arg_package_spec_no_all(
"Package to run tests for",
"Test all packages in the workspace",
"Exclude packages from the test",
)
.arg(flag("no-run", "Compile, but don't run tests"))
.arg(flag("no-fail-fast", "Run all tests regardless of failure"))
}
pub fn run_cargo_fallback(subcommand: &str, subcommand_args: &ArgMatches) -> CliResult {
let cargo = std::env::var("CARGO_C_CARGO").unwrap_or_else(|_| "cargo".to_owned());
let mut args = vec![OsStr::new(subcommand)];
args.extend(
subcommand_args
.get_many::<OsString>("")
.unwrap_or_default()
.map(OsStr::new),
);
let err = match ProcessBuilder::new(cargo).args(&args).exec_replace() {
Ok(()) => return Ok(()),
Err(e) => e,
};
if let Some(perr) = err.downcast_ref::<ProcessError>() {
if let Some(code) = perr.code {
return Err(CliError::code(code));
}
}
Err(CliError::new(err, 101))
}
07070100000034000081A40000000000000000000000016898C2E9000004EE000000000000000000000000000000000000001E00000000cargo-c-0.10.15/src/config.rsuse std::env;
use cargo::util::command_prelude::{ArgMatches, ArgMatchesExt};
use cargo::{CliResult, GlobalContext};
// Take the original cargo instance and save it as a separate env var if not already set.
fn setup_env() {
if env::var("CARGO_C_CARGO").is_err() {
let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned());
env::set_var("CARGO_C_CARGO", cargo);
}
}
pub fn global_context_configure(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
let arg_target_dir = &args.value_of_path("target-dir", gctx);
let config_args: Vec<_> = args
.get_many::<String>("config")
.unwrap_or_default()
.map(|s| s.to_owned())
.collect();
gctx.configure(
args.verbose(),
args.flag("quiet"),
args.get_one::<String>("color").map(String::as_str),
args.flag("frozen"),
args.flag("locked"),
args.flag("offline"),
arg_target_dir,
&args
.get_many::<String>("unstable-features")
.unwrap_or_default()
.map(|s| s.to_owned())
.collect::<Vec<String>>(),
&config_args,
)?;
// Make sure that the env-vars are correctly set at this point.
setup_env();
Ok(())
}
07070100000035000081A40000000000000000000000016898C2E9000030E4000000000000000000000000000000000000001F00000000cargo-c-0.10.15/src/install.rsuse clap::ArgMatches;
use std::path::{Component, Path, PathBuf};
use cargo::core::Workspace;
use cargo_util::paths::{self, create_dir_all};
use crate::build::*;
use crate::build_targets::BuildTargets;
use crate::target::Target;
pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(ws: &Workspace, from: P, to: Q) -> anyhow::Result<u64> {
ws.gctx().shell().verbose(|shell| {
shell.status(
"Copying",
format!("{} to {}", from.as_ref().display(), to.as_ref().display()),
)
})?;
paths::copy(from, to)
}
fn append_to_destdir(destdir: Option<&Path>, path: &Path) -> PathBuf {
if let Some(destdir) = destdir {
let mut joined = destdir.to_path_buf();
for component in path.components() {
match component {
Component::Prefix(_) | Component::RootDir => {}
_ => joined.push(component),
};
}
joined
} else {
path.to_path_buf()
}
}
#[cfg(test)]
mod tests {
use std::path::{Path, PathBuf};
#[test]
fn append_to_destdir() {
assert_eq!(
super::append_to_destdir(Some(Path::new(r"/foo")), &PathBuf::from(r"/bar/./..")),
PathBuf::from(r"/foo/bar/./..")
);
assert_eq!(
super::append_to_destdir(Some(Path::new(r"foo")), &PathBuf::from(r"bar")),
PathBuf::from(r"foo/bar")
);
assert_eq!(
super::append_to_destdir(Some(Path::new(r"")), &PathBuf::from(r"")),
PathBuf::from(r"")
);
if cfg!(windows) {
assert_eq!(
super::append_to_destdir(Some(Path::new(r"X:\foo")), &PathBuf::from(r"Y:\bar")),
PathBuf::from(r"X:\foo\bar")
);
assert_eq!(
super::append_to_destdir(Some(Path::new(r"A:\foo")), &PathBuf::from(r"B:bar")),
PathBuf::from(r"A:\foo\bar")
);
assert_eq!(
super::append_to_destdir(Some(Path::new(r"\foo")), &PathBuf::from(r"\bar")),
PathBuf::from(r"\foo\bar")
);
assert_eq!(
super::append_to_destdir(
Some(Path::new(r"C:\dest")),
Path::new(r"\\server\share\foo\bar")
),
PathBuf::from(r"C:\\dest\\foo\\bar")
);
}
}
}
pub(crate) enum LibType {
So,
Dylib,
Windows,
}
impl LibType {
pub(crate) fn from_build_targets(build_targets: &BuildTargets) -> Self {
let target = &build_targets.target;
let os = &target.os;
let env = &target.env;
match (os.as_str(), env.as_str()) {
("linux", _)
| ("freebsd", _)
| ("dragonfly", _)
| ("netbsd", _)
| ("android", _)
| ("haiku", _)
| ("illumos", _)
| ("openbsd", _)
| ("emscripten", _)
| ("hurd", _) => LibType::So,
("macos", _) | ("ios", _) | ("tvos", _) | ("visionos", _) => LibType::Dylib,
("windows", _) => LibType::Windows,
_ => unimplemented!("The target {}-{} is not supported yet", os, env),
}
}
}
pub(crate) struct UnixLibNames {
canonical: String,
with_main_ver: String,
with_full_ver: String,
}
impl UnixLibNames {
pub(crate) fn new(lib_type: LibType, library: &LibraryCApiConfig) -> Option<Self> {
let lib_name = &library.name;
let lib_version = &library.version;
let main_version = library.sover();
match lib_type {
LibType::So => {
let lib = format!("lib{lib_name}.so");
let lib_with_full_ver = format!(
"{}.{}.{}.{}",
lib, lib_version.major, lib_version.minor, lib_version.patch
);
let lib_with_main_ver = format!("{lib}.{main_version}");
Some(Self {
canonical: lib,
with_main_ver: lib_with_main_ver,
with_full_ver: lib_with_full_ver,
})
}
LibType::Dylib => {
let lib = format!("lib{lib_name}.dylib");
let lib_with_main_ver = format!("lib{lib_name}.{main_version}.dylib");
let lib_with_full_ver = format!(
"lib{}.{}.{}.{}.dylib",
lib_name, lib_version.major, lib_version.minor, lib_version.patch
);
Some(Self {
canonical: lib,
with_main_ver: lib_with_main_ver,
with_full_ver: lib_with_full_ver,
})
}
LibType::Windows => None,
}
}
fn links(&self, install_path_lib: &Path) {
if self.with_main_ver != self.with_full_ver {
let mut ln_sf = std::process::Command::new("ln");
ln_sf.arg("-sf");
ln_sf
.arg(&self.with_full_ver)
.arg(install_path_lib.join(&self.with_main_ver));
let _ = ln_sf.status().unwrap();
}
let mut ln_sf = std::process::Command::new("ln");
ln_sf.arg("-sf");
ln_sf
.arg(&self.with_full_ver)
.arg(install_path_lib.join(&self.canonical));
let _ = ln_sf.status().unwrap();
}
pub(crate) fn install(
&self,
ws: &Workspace,
capi_config: &CApiConfig,
shared_lib: &Path,
install_path_lib: &Path,
) -> anyhow::Result<()> {
if capi_config.library.versioning {
copy(ws, shared_lib, install_path_lib.join(&self.with_full_ver))?;
self.links(install_path_lib);
} else {
copy(ws, shared_lib, install_path_lib.join(&self.canonical))?;
}
Ok(())
}
}
pub fn cinstall(ws: &Workspace, packages: &[CPackage]) -> anyhow::Result<()> {
for pkg in packages {
let paths = &pkg.install_paths;
let capi_config = &pkg.capi_config;
let build_targets = &pkg.build_targets;
let destdir = &paths.destdir;
let mut install_path_lib = paths.libdir.clone();
if let Some(subdir) = &capi_config.library.install_subdir {
install_path_lib.push(subdir);
}
let install_path_bin = append_to_destdir(destdir.as_deref(), &paths.bindir);
let install_path_lib = append_to_destdir(destdir.as_deref(), &install_path_lib);
let install_path_pc = append_to_destdir(destdir.as_deref(), &paths.pkgconfigdir);
let install_path_include = append_to_destdir(destdir.as_deref(), &paths.includedir);
let install_path_data = append_to_destdir(destdir.as_deref(), &paths.datadir);
create_dir_all(&install_path_lib)?;
create_dir_all(&install_path_pc)?;
ws.gctx().shell().status("Installing", "pkg-config file")?;
copy(
ws,
&build_targets.pc,
install_path_pc.join(build_targets.pc.file_name().unwrap()),
)?;
if capi_config.header.enabled {
ws.gctx().shell().status("Installing", "header file")?;
for (from, to) in build_targets.extra.include.iter() {
let to = install_path_include.join(to);
create_dir_all(to.parent().unwrap())?;
copy(ws, from, to)?;
}
}
if !build_targets.extra.data.is_empty() {
ws.gctx().shell().status("Installing", "data file")?;
for (from, to) in build_targets.extra.data.iter() {
let to = install_path_data.join(to);
create_dir_all(to.parent().unwrap())?;
copy(ws, from, to)?;
}
}
if let Some(ref static_lib) = build_targets.static_lib {
ws.gctx().shell().status("Installing", "static library")?;
let file_name = build_targets.static_output_file_name().unwrap();
copy(ws, static_lib, install_path_lib.join(file_name))?;
}
if let Some(ref shared_lib) = build_targets.shared_lib {
ws.gctx().shell().status("Installing", "shared library")?;
let lib_type = LibType::from_build_targets(build_targets);
match lib_type {
LibType::So | LibType::Dylib => {
let lib = UnixLibNames::new(lib_type, &capi_config.library).unwrap();
lib.install(ws, capi_config, shared_lib, &install_path_lib)?;
}
LibType::Windows => {
let lib_name = build_targets.shared_output_file_name().unwrap();
if capi_config.library.install_subdir.is_none() {
let install_path_bin = append_to_destdir(destdir.as_deref(), &paths.bindir);
create_dir_all(&install_path_bin)?;
copy(ws, shared_lib, install_path_bin.join(lib_name))?;
} else {
// We assume they are plugins, install them in the custom libdir path
copy(ws, shared_lib, install_path_lib.join(lib_name))?;
}
if capi_config.library.import_library {
let impl_lib = build_targets.impl_lib.as_ref().unwrap();
let impl_lib_name = impl_lib.file_name().unwrap();
copy(ws, impl_lib, install_path_lib.join(impl_lib_name))?;
let def = build_targets.def.as_ref().unwrap();
let def_name = def.file_name().unwrap();
copy(ws, def, install_path_lib.join(def_name))?;
}
}
}
}
if let Some(ref debug_info) = build_targets.debug_info {
if debug_info.exists() {
ws.gctx()
.shell()
.status("Installing", "debugging information")?;
let destination_path = build_targets
.debug_info_file_name(&install_path_bin, &install_path_lib)
.unwrap();
create_dir_all(destination_path.parent().unwrap())?;
copy(ws, debug_info, destination_path)?;
} else {
ws.gctx()
.shell()
.verbose(|shell| shell.status("Absent", "debugging information"))?;
}
}
}
Ok(())
}
#[derive(Debug, Hash, Clone)]
pub struct InstallPaths {
pub subdir_name: PathBuf,
pub destdir: Option<PathBuf>,
pub prefix: PathBuf,
pub libdir: PathBuf,
pub includedir: PathBuf,
pub datadir: PathBuf,
pub bindir: PathBuf,
pub pkgconfigdir: PathBuf,
}
fn get_path_or(args: &ArgMatches, id: &str, f: impl FnOnce() -> PathBuf) -> PathBuf {
if matches!(
args.value_source(id),
Some(clap::parser::ValueSource::DefaultValue)
) {
f()
} else {
args.get_one::<PathBuf>(id).unwrap().to_owned()
}
}
impl InstallPaths {
pub fn new(
_name: &str,
rustc_target: &Target,
args: &ArgMatches,
capi_config: &CApiConfig,
) -> Self {
let destdir = args.get_one::<PathBuf>("destdir").map(PathBuf::from);
let prefix = get_path_or(args, "prefix", || rustc_target.default_prefix());
let libdir = prefix.join(get_path_or(args, "libdir", || {
rustc_target.default_libdir()
}));
let includedir = prefix.join(get_path_or(args, "includedir", || {
rustc_target.default_includedir()
}));
let datarootdir = prefix.join(get_path_or(args, "datarootdir", || {
rustc_target.default_datadir()
}));
let datadir = args
.get_one::<PathBuf>("datadir")
.map(|d| prefix.join(d))
.unwrap_or_else(|| datarootdir.clone());
let subdir_name = PathBuf::from(&capi_config.header.subdirectory);
let bindir = prefix.join(args.get_one::<PathBuf>("bindir").unwrap());
let pkgconfigdir = args
.get_one::<PathBuf>("pkgconfigdir")
.map(|d| prefix.join(d))
.unwrap_or_else(|| libdir.join("pkgconfig"));
InstallPaths {
subdir_name,
destdir,
prefix,
libdir,
includedir,
datadir,
bindir,
pkgconfigdir,
}
}
}
07070100000036000081A40000000000000000000000016898C2E90000007C000000000000000000000000000000000000001B00000000cargo-c-0.10.15/src/lib.rspub mod build;
pub mod build_targets;
pub mod cli;
pub mod config;
pub mod install;
pub mod pkg_config_gen;
pub mod target;
07070100000037000081A40000000000000000000000016898C2E90000371D000000000000000000000000000000000000002600000000cargo-c-0.10.15/src/pkg_config_gen.rs#![allow(dead_code)]
use crate::build::CApiConfig;
use crate::install::InstallPaths;
use std::path::{Component, Path, PathBuf};
fn canonicalize<P: AsRef<Path>>(path: P) -> String {
let mut stack = Vec::with_capacity(16);
struct Item<'a> {
separator: bool,
component: Component<'a>,
}
let mut separator = false;
for component in path.as_ref().components() {
match component {
Component::RootDir => {
separator = true;
}
Component::Prefix(_) => stack.push(Item {
separator: false,
component,
}),
Component::ParentDir => {
let _ = stack.pop();
}
Component::CurDir => stack.push(Item {
separator: false,
component,
}),
Component::Normal(_) => {
stack.push(Item {
separator,
component,
});
separator = true;
}
}
}
if stack.is_empty() {
String::from("/")
} else {
let mut buf = String::with_capacity(64);
for item in stack {
if item.separator {
buf.push('/');
}
buf.push_str(&item.component.as_os_str().to_string_lossy());
}
buf
}
}
#[derive(Debug, Clone)]
pub struct PkgConfig {
prefix: PathBuf,
exec_prefix: PathBuf,
includedir: PathBuf,
libdir: PathBuf,
name: String,
description: String,
version: String,
requires: Vec<String>,
requires_private: Vec<String>,
libs: Vec<String>,
libs_private: Vec<String>,
cflags: Vec<String>,
conflicts: Vec<String>,
}
impl PkgConfig {
///
/// Build a pkgconfig structure with the following defaults:
///
/// prefix=/usr/local
/// exec_prefix=${prefix}
/// includedir=${prefix}/include
/// libdir=${exec_prefix}/lib
///
/// Name: $name
/// Description: $description
/// Version: $version
/// Cflags: -I${includedir}/$name
/// Libs: -L${libdir} -l$name
///
pub fn new(_name: &str, capi_config: &CApiConfig) -> Self {
let requires = match &capi_config.pkg_config.requires {
Some(reqs) => reqs.split(',').map(|s| s.trim().to_string()).collect(),
_ => Vec::new(),
};
let requires_private = match &capi_config.pkg_config.requires_private {
Some(reqs) => reqs.split(',').map(|s| s.trim().to_string()).collect(),
_ => Vec::new(),
};
let mut libdir = PathBuf::new();
libdir.push("${libdir}");
if let Some(subdir) = &capi_config.library.install_subdir {
libdir.push(subdir);
}
let libs = vec![
format!("-L{}", canonicalize(libdir.display().to_string())),
format!("-l{}", capi_config.library.name),
];
let cflags = if capi_config.header.enabled {
let includedir = Path::new("${includedir}").join(&capi_config.header.subdirectory);
let includedir = includedir
.ancestors()
.nth(capi_config.pkg_config.strip_include_path_components)
.unwrap_or_else(|| Path::new(""));
format!("-I{}", canonicalize(includedir))
} else {
String::from("")
};
PkgConfig {
name: capi_config.pkg_config.name.clone(),
description: capi_config.pkg_config.description.clone(),
version: capi_config.pkg_config.version.clone(),
prefix: "/usr/local".into(),
exec_prefix: "${prefix}".into(),
includedir: "${prefix}/include".into(),
libdir: "${exec_prefix}/lib".into(),
libs,
libs_private: Vec::new(),
requires,
requires_private,
cflags: vec![cflags],
conflicts: Vec::new(),
}
}
pub(crate) fn from_workspace(
name: &str,
install_paths: &InstallPaths,
args: &clap::ArgMatches,
capi_config: &CApiConfig,
) -> Self {
let mut pc = PkgConfig::new(name, capi_config);
pc.prefix.clone_from(&install_paths.prefix);
// TODO: support exec_prefix
if args.contains_id("includedir") {
if let Ok(suffix) = install_paths.includedir.strip_prefix(&pc.prefix) {
pc.includedir = PathBuf::from("${prefix}").join(suffix);
} else {
pc.includedir.clone_from(&install_paths.includedir);
}
}
if args.contains_id("libdir") {
if let Ok(suffix) = install_paths.libdir.strip_prefix(&pc.prefix) {
pc.libdir = PathBuf::from("${prefix}").join(suffix);
} else {
pc.libdir.clone_from(&install_paths.libdir);
}
}
pc
}
pub(crate) fn uninstalled(&self, output: &Path) -> Self {
let mut uninstalled = self.clone();
uninstalled.prefix = output.to_path_buf();
uninstalled.includedir = "${prefix}/include".into();
uninstalled.libdir = "${prefix}".into();
// First libs item is the search path
uninstalled.libs[0] = "-L${prefix}".into();
uninstalled
}
pub fn set_description<S: AsRef<str>>(&mut self, descr: S) -> &mut Self {
descr.as_ref().clone_into(&mut self.description);
self
}
pub fn set_libs<S: AsRef<str>>(&mut self, lib: S) -> &mut Self {
let lib = lib.as_ref().to_owned();
self.libs.clear();
self.libs.push(lib);
self
}
pub fn add_lib<S: AsRef<str>>(&mut self, lib: S) -> &mut Self {
let lib = lib.as_ref().to_owned();
self.libs.push(lib);
self
}
pub fn set_libs_private<S: AsRef<str>>(&mut self, lib: S) -> &mut Self {
let lib = lib.as_ref().to_owned();
self.libs_private.clear();
self.libs_private.push(lib);
self
}
pub fn add_lib_private<S: AsRef<str>>(&mut self, lib: S) -> &mut Self {
let lib = lib.as_ref().to_owned();
self.libs_private.push(lib);
self
}
pub fn set_cflags<S: AsRef<str>>(&mut self, flag: S) -> &mut Self {
let flag = flag.as_ref().to_owned();
self.cflags.clear();
self.cflags.push(flag);
self
}
pub fn add_cflag<S: AsRef<str>>(&mut self, flag: S) -> &mut Self {
let flag = flag.as_ref();
self.cflags.push(flag.to_owned());
self
}
pub fn render(&self) -> String {
// writing to a String only fails on OOM, which we disregard
self.render_help(String::with_capacity(1024)).unwrap()
}
fn render_help<W: core::fmt::Write>(&self, mut w: W) -> Result<W, core::fmt::Error> {
writeln!(w, "prefix={}", canonicalize(&self.prefix))?;
writeln!(w, "exec_prefix={}", canonicalize(&self.exec_prefix))?;
writeln!(w, "libdir={}", canonicalize(&self.libdir))?;
writeln!(w, "includedir={}", canonicalize(&self.includedir))?;
writeln!(w)?;
writeln!(w, "Name: {}", self.name)?;
writeln!(w, "Description: {}", self.description.replace('\n', " "))?; // avoid endlines
writeln!(w, "Version: {}", self.version)?;
writeln!(w, "Libs: {}", self.libs.join(" "))?;
writeln!(w, "Cflags: {}", self.cflags.join(" "))?;
if !self.libs_private.is_empty() {
writeln!(w, "Libs.private: {}", self.libs_private.join(" "))?;
}
if !self.requires.is_empty() {
writeln!(w, "Requires: {}", self.requires.join(", "))?;
}
if !self.requires_private.is_empty() {
let joined = self.requires_private.join(", ");
writeln!(w, "Requires.private: {joined}")?;
}
Ok(w)
}
}
#[cfg(test)]
mod test {
use super::*;
use semver::Version;
#[test]
fn simple() {
let mut pkg = PkgConfig::new(
"foo",
&CApiConfig {
header: crate::build::HeaderCApiConfig {
name: "foo".into(),
subdirectory: "".into(),
generation: true,
enabled: true,
emit_version_constants: true,
},
pkg_config: crate::build::PkgConfigCApiConfig {
name: "foo".into(),
filename: "foo".into(),
description: "".into(),
version: "0.1".into(),
requires: Some("somelib, someotherlib".into()),
requires_private: Some("someprivatelib >= 1.0".into()),
strip_include_path_components: 0,
},
library: crate::build::LibraryCApiConfig {
name: "foo".into(),
version: Version::parse("0.1.0").unwrap(),
install_subdir: None,
versioning: true,
version_suffix_components: None,
import_library: true,
rustflags: Vec::default(),
},
install: Default::default(),
},
);
pkg.add_lib("-lbar").add_cflag("-DFOO");
let expected = concat!(
"prefix=/usr/local\n",
"exec_prefix=${prefix}\n",
"libdir=${exec_prefix}/lib\n",
"includedir=${prefix}/include\n",
"\n",
"Name: foo\n",
"Description: \n",
"Version: 0.1\n",
"Libs: -L${libdir} -lfoo -lbar\n",
"Cflags: -I${includedir} -DFOO\n",
"Requires: somelib, someotherlib\n",
"Requires.private: someprivatelib >= 1.0\n",
);
assert_eq!(expected, pkg.render());
}
mod test_canonicalize {
use super::canonicalize;
#[test]
fn test_absolute_path() {
let path = "/home/user/docs";
let result = canonicalize(path);
assert_eq!(result, "/home/user/docs");
}
#[test]
fn test_relative_path() {
let path = "home/user/docs";
let result = canonicalize(path);
assert_eq!(result, "home/user/docs");
}
#[test]
fn test_current_directory() {
let path = "/home/user/./docs";
let result = canonicalize(path);
assert_eq!(result, "/home/user/docs");
}
#[test]
fn test_parent_directory() {
let path = "/home/user/../docs";
let result = canonicalize(path);
assert_eq!(result, "/home/docs");
}
#[test]
fn test_mixed_dots_and_parent_dirs() {
let path = "/home/./user/../docs/./files";
let result = canonicalize(path);
assert_eq!(result, "/home/docs/files");
}
#[test]
fn test_multiple_consecutive_slashes() {
let path = "/home//user///docs";
let result = canonicalize(path);
assert_eq!(result, "/home/user/docs");
}
#[test]
fn test_empty_path() {
let path = "";
let result = canonicalize(path);
assert_eq!(result, "/");
}
#[test]
fn test_single_dot() {
let path = ".";
let result = canonicalize(path);
assert_eq!(result, ".");
}
#[test]
fn test_single_dot_in_absolute_path() {
let path = "/.";
let result = canonicalize(path);
assert_eq!(result, "/");
}
#[test]
fn test_trailing_slash() {
let path = "/home/user/docs/";
let result = canonicalize(path);
assert_eq!(result, "/home/user/docs");
}
#[test]
fn test_dots_complex_case() {
let path = "/a/b/./c/../d//e/./../f";
let result = canonicalize(path);
assert_eq!(result, "/a/b/d/f");
}
#[cfg(windows)]
mod windows {
use std::path::Path;
use super::*;
#[test]
fn test_canonicalize_basic_windows_path() {
let input = Path::new(r"C:\Users\test\..\Documents");
let expected = r"C:/Users/Documents";
let result = canonicalize(input);
assert_eq!(result, expected);
}
#[test]
fn test_canonicalize_with_current_dir() {
let input = Path::new(r"C:\Users\.\Documents");
let expected = r"C:/Users/Documents";
let result = canonicalize(input);
assert_eq!(result, expected);
}
#[test]
fn test_canonicalize_with_double_parent_dir() {
let input = Path::new(r"C:\Users\test\..\..\Documents");
let expected = r"C:/Documents";
let result = canonicalize(input);
assert_eq!(result, expected);
}
#[test]
fn test_canonicalize_with_trailing_slash() {
let input = Path::new(r"C:\Users\test\..\Documents\");
let expected = r"C:/Users/Documents";
let result = canonicalize(input);
assert_eq!(result, expected);
}
#[test]
fn test_canonicalize_relative_path() {
let input = Path::new(r"Users\test\..\Documents");
let expected = r"Users/Documents";
let result = canonicalize(input);
assert_eq!(result, expected);
}
#[test]
fn test_canonicalize_current_dir_only() {
let input = Path::new(r".\");
let expected = r".";
let result = canonicalize(input);
assert_eq!(result, expected);
}
}
}
}
07070100000038000081A40000000000000000000000016898C2E900001787000000000000000000000000000000000000001E00000000cargo-c-0.10.15/src/target.rsuse std::env::consts;
use std::path::{Path, PathBuf};
use anyhow::*;
use crate::build::CApiConfig;
/// Split a target string to its components
///
/// Because of https://github.com/rust-lang/rust/issues/61558
/// It uses internally `rustc` to validate the string.
#[derive(Clone, Debug)]
pub struct Target {
pub is_target_overridden: bool,
pub arch: String,
// pub vendor: String,
pub os: String,
pub env: String,
}
impl Target {
pub fn new<T: AsRef<std::ffi::OsStr>>(
target: Option<T>,
is_target_overridden: bool,
) -> Result<Self, anyhow::Error> {
let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".into());
let mut cmd = std::process::Command::new(rustc);
cmd.arg("--print").arg("cfg");
if let Some(target) = target {
cmd.arg("--target").arg(target);
}
let out = cmd.output()?;
if out.status.success() {
fn match_re(re: regex::Regex, s: &str) -> String {
re.captures(s)
.map_or("", |cap| cap.get(1).unwrap().as_str())
.to_owned()
}
let arch_re = regex::Regex::new(r#"target_arch="(.+)""#).unwrap();
// let vendor_re = regex::Regex::new(r#"target_vendor="(.+)""#).unwrap();
let os_re = regex::Regex::new(r#"target_os="(.+)""#).unwrap();
let env_re = regex::Regex::new(r#"target_env="(.+)""#).unwrap();
let s = std::str::from_utf8(&out.stdout).unwrap();
Ok(Target {
arch: match_re(arch_re, s),
// vendor: match_re(vendor_re, s),
os: match_re(os_re, s),
env: match_re(env_re, s),
is_target_overridden,
})
} else {
Err(anyhow!("Cannot run {:?}", cmd))
}
}
/// Build a list of linker arguments
pub fn shared_object_link_args(
&self,
capi_config: &CApiConfig,
libdir: &Path,
target_dir: &Path,
) -> Vec<String> {
let mut lines = Vec::new();
let lib_name = &capi_config.library.name;
let version = &capi_config.library.version;
let major = version.major;
let minor = version.minor;
let patch = version.patch;
let os = &self.os;
let env = &self.env;
let sover = capi_config.library.sover();
if os == "android" {
lines.push(format!("-Wl,-soname,lib{lib_name}.so"));
} else if os == "linux"
|| os == "freebsd"
|| os == "dragonfly"
|| os == "netbsd"
|| os == "haiku"
|| os == "illumos"
|| os == "openbsd"
|| os == "hurd"
{
lines.push(if capi_config.library.versioning {
format!("-Wl,-soname,lib{lib_name}.so.{sover}")
} else {
format!("-Wl,-soname,lib{lib_name}.so")
});
} else if os == "macos" || os == "ios" || os == "tvos" || os == "visionos" {
let line = if capi_config.library.versioning {
format!("-Wl,-install_name,{1}/lib{0}.{5}.dylib,-current_version,{2}.{3}.{4},-compatibility_version,{5}",
lib_name, libdir.display(), major, minor, patch, sover)
} else {
format!(
"-Wl,-install_name,{1}/lib{0}.dylib",
lib_name,
libdir.display()
)
};
lines.push(line);
// Enable larger LC_RPATH and install_name entries
lines.push("-Wl,-headerpad_max_install_names".to_string());
} else if os == "windows" && env == "gnu" {
// This is only set up to work on GNU toolchain versions of Rust
lines.push(format!(
"-Wl,--output-def,{}",
target_dir.join(format!("{lib_name}.def")).display()
));
}
// Emscripten doesn't support soname or other dynamic linking flags (yet).
// See: https://github.com/emscripten-core/emscripten/blob/3.1.39/emcc.py#L92-L94
// else if os == "emscripten"
lines
}
fn is_freebsd(&self) -> bool {
self.os.eq_ignore_ascii_case("freebsd")
}
fn is_haiku(&self) -> bool {
self.os.eq_ignore_ascii_case("haiku")
}
fn is_windows(&self) -> bool {
self.os.eq_ignore_ascii_case("windows")
}
pub fn default_libdir(&self) -> PathBuf {
if self.is_target_overridden || self.is_freebsd() {
return "lib".into();
}
if PathBuf::from("/etc/debian_version").exists() {
let pc = std::process::Command::new("dpkg-architecture")
.arg("-qDEB_HOST_MULTIARCH")
.output();
if let std::result::Result::Ok(v) = pc {
if v.status.success() {
let archpath = String::from_utf8_lossy(&v.stdout);
return format!("lib/{}", archpath.trim()).into();
}
}
}
if consts::ARCH.eq_ignore_ascii_case(&self.arch)
&& consts::OS.eq_ignore_ascii_case(&self.os)
{
let usr_lib64 = PathBuf::from("/usr/lib64");
if usr_lib64.exists() && !usr_lib64.is_symlink() {
return "lib64".into();
}
}
"lib".into()
}
pub fn default_prefix(&self) -> PathBuf {
if self.is_windows() {
"c:/".into()
} else if self.is_haiku() {
"/boot/system/non-packaged".into()
} else {
"/usr/local".into()
}
}
pub fn default_datadir(&self) -> PathBuf {
if self.is_haiku() {
return "data".into();
}
"share".into()
}
pub fn default_includedir(&self) -> PathBuf {
if self.is_haiku() {
return "develop/headers".into();
}
"include".into()
}
}
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!293 blocks