Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
devel:languages:rust
cargo-c
cargo-c-0.10.3~git0.ee7d7ef.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File cargo-c-0.10.3~git0.ee7d7ef.obscpio of Package cargo-c
07070100000000000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000002400000000cargo-c-0.10.3~git0.ee7d7ef/.github07070100000001000081A400000000000000000000000166A873C100000053000000000000000000000000000000000000003000000000cargo-c-0.10.3~git0.ee7d7ef/.github/FUNDING.yml# These are supported funding model platforms liberapay: lu_zero github: lu-zero 07070100000002000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000002F00000000cargo-c-0.10.3~git0.ee7d7ef/.github/actions-rs07070100000003000081A400000000000000000000000166A873C100000036000000000000000000000000000000000000003900000000cargo-c-0.10.3~git0.ee7d7ef/.github/actions-rs/grcov.ymlignore-not-existing: true ignore: - "/*" - "../*" 07070100000004000081A400000000000000000000000166A873C100000163000000000000000000000000000000000000003300000000cargo-c-0.10.3~git0.ee7d7ef/.github/dependabot.ymlversion: 2 updates: - 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 07070100000005000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000002E00000000cargo-c-0.10.3~git0.ee7d7ef/.github/workflows07070100000006000081A400000000000000000000000166A873C100000E7F000000000000000000000000000000000000003900000000cargo-c-0.10.3~git0.ee7d7ef/.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@v4 - 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 }} 07070100000007000081A400000000000000000000000166A873C100000B41000000000000000000000000000000000000004200000000cargo-c-0.10.3~git0.ee7d7ef/.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 - 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 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 temporary location working-directory: example-project run: | cargo cinstall --verbose --release --destdir=temp - name: Copy installed files to /usr/local if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') working-directory: example-project run: | sudo cp -r temp/usr/local/* /usr/local/ - name: Test pkg-config if: startsWith(matrix.os, 'macos') run: | set -x test "$(pkg-config --cflags example_project)" = "-I/usr/local/include/example-project-0.1" test "$(pkg-config --libs example_project)" = "-L/usr/local/lib -lexample-project" - name: Install pkgconf if: startsWith(matrix.os, 'ubuntu') uses: awalsh128/cache-apt-pkgs-action@latest with: packages: pkgconf - name: Test pkgconf if: startsWith(matrix.os, 'ubuntu') run: | set -x pkgconf --version pkg-config --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: Update dynamic linker cache if: startsWith(matrix.os, 'ubuntu') run: | sudo ldconfig - name: Test usage from C (using Makefile) if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') working-directory: example-project/usage-from-c run: | make 07070100000008000081A400000000000000000000000166A873C100000821000000000000000000000000000000000000003700000000cargo-c-0.10.3~git0.ee7d7ef/.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.2.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@v4 with: files: coverage.lcov arch-test: needs: rustfmt-clippy strategy: matrix: os: [ubuntu-latest, windows-latest, macos-12] 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 07070100000009000081A400000000000000000000000166A873C10000005F000000000000000000000000000000000000002700000000cargo-c-0.10.3~git0.ee7d7ef/.gitignore/target **/*.rs.bk Cargo.lock /example-project/target/ /example-project/usage-from-c/run_tests 0707010000000A000081A400000000000000000000000166A873C1000005A9000000000000000000000000000000000000002700000000cargo-c-0.10.3~git0.ee7d7ef/Cargo.toml[package] name = "cargo-c" version = "0.10.3+cargo-0.81.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.78" [[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.81.0" cargo-util = "0.2" semver = "1.0.3" log = "0.4" clap = { version = "4.0.29", features = ["color", "derive", "cargo", "string"] } regex = "1.5.6" cbindgen = { version="0.26.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.13" # workaround cargo [target.'cfg(windows)'.dependencies.windows-sys] version = "0.52" features = [ "Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Console", "Win32_System_Threading", "Win32_System_JobObjects", "Win32_Security", "Win32_System_SystemServices" ] [features] default = [] vendored-openssl = ["cargo/vendored-openssl"] [profile.release-strip] inherits = "release" strip = "symbols" 0707010000000B000081A400000000000000000000000166A873C10000042D000000000000000000000000000000000000002400000000cargo-c-0.10.3~git0.ee7d7ef/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. 0707010000000C000081A400000000000000000000000166A873C100002DCF000000000000000000000000000000000000002600000000cargo-c-0.10.3~git0.ee7d7ef/README.md# Cargo C-ABI helpers [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Crates.io](https://img.shields.io/crates/v/cargo-c.svg)](https://crates.io/crates/cargo-c) [![Build Status](https://github.com/lu-zero/cargo-c/workflows/Rust/badge.svg)](https://github.com/lu-zero/cargo-c/actions?query=workflow:Rust) [![cargo-c chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://rust-av.zulipchat.com/#narrow/stream/254255-cargo-c) [![dependency status](https://deps.rs/repo/github/lu-zero/cargo-c/status.svg)](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: - [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. [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 [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 ``` ### `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) - [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 [![Packaging status](https://repology.org/badge/vertical-allrepos/cargo-c.svg)](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. 0707010000000D000081A400000000000000000000000166A873C1000000D9000000000000000000000000000000000000002800000000cargo-c-0.10.3~git0.ee7d7ef/codecov.ymlcoverage: status: project: default: target: 80% # the required coverage value threshold: 10% # the leniency in hitting the target patch: default: informational: true 0707010000000E000081A400000000000000000000000166A873C100000675000000000000000000000000000000000000002800000000cargo-c-0.10.3~git0.ee7d7ef/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 0707010000000F000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000002C00000000cargo-c-0.10.3~git0.ee7d7ef/example-project07070100000010000081A400000000000000000000000166A873C100000292000000000000000000000000000000000000003700000000cargo-c-0.10.3~git0.ee7d7ef/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" }] 07070100000011000081A400000000000000000000000166A873C1000002C6000000000000000000000000000000000000003600000000cargo-c-0.10.3~git0.ee7d7ef/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. 07070100000012000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000003300000000cargo-c-0.10.3~git0.ee7d7ef/example-project/assets07070100000013000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000003800000000cargo-c-0.10.3~git0.ee7d7ef/example-project/assets/capi07070100000014000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000004000000000cargo-c-0.10.3~git0.ee7d7ef/example-project/assets/capi/include07070100000015000081A400000000000000000000000166A873C100000011000000000000000000000000000000000000004700000000cargo-c-0.10.3~git0.ee7d7ef/example-project/assets/capi/include/file.h// Pre-generated 07070100000016000081A400000000000000000000000166A873C100000350000000000000000000000000000000000000003500000000cargo-c-0.10.3~git0.ee7d7ef/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(); } 07070100000017000081A400000000000000000000000166A873C100000097000000000000000000000000000000000000003A00000000cargo-c-0.10.3~git0.ee7d7ef/example-project/cbindgen.tomlinclude_guard = "EXAMPLE_PROJECT_H" include_version = true language = "C" cpp_compat = true [export.rename] "OddCounter" = "ExampleProjectOddCounter" 07070100000018000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000003000000000cargo-c-0.10.3~git0.ee7d7ef/example-project/src07070100000019000081A400000000000000000000000166A873C1000003EC000000000000000000000000000000000000003800000000cargo-c-0.10.3~git0.ee7d7ef/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() } 0707010000001A000081A400000000000000000000000166A873C1000005AB000000000000000000000000000000000000003700000000cargo-c-0.10.3~git0.ee7d7ef/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); } } 0707010000001B000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000003200000000cargo-c-0.10.3~git0.ee7d7ef/example-project/tests0707010000001C000081A400000000000000000000000166A873C100000403000000000000000000000000000000000000003A00000000cargo-c-0.10.3~git0.ee7d7ef/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(); } } 0707010000001D000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000003900000000cargo-c-0.10.3~git0.ee7d7ef/example-project/usage-from-c0707010000001E000081A400000000000000000000000166A873C100000115000000000000000000000000000000000000004200000000cargo-c-0.10.3~git0.ee7d7ef/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 0707010000001F000081A400000000000000000000000166A873C1000002B8000000000000000000000000000000000000004500000000cargo-c-0.10.3~git0.ee7d7ef/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; } } 07070100000020000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000002E00000000cargo-c-0.10.3~git0.ee7d7ef/example-workspace07070100000021000081A400000000000000000000000166A873C100000034000000000000000000000000000000000000003900000000cargo-c-0.10.3~git0.ee7d7ef/example-workspace/Cargo.toml[workspace] members = [ "api-a", "api-b", ] 07070100000022000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000003400000000cargo-c-0.10.3~git0.ee7d7ef/example-workspace/api-a07070100000023000081A400000000000000000000000166A873C100000126000000000000000000000000000000000000003F00000000cargo-c-0.10.3~git0.ee7d7ef/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 } 07070100000024000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000003800000000cargo-c-0.10.3~git0.ee7d7ef/example-workspace/api-a/src07070100000025000081A400000000000000000000000166A873C1000000D4000000000000000000000000000000000000003F00000000cargo-c-0.10.3~git0.ee7d7ef/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); } } 07070100000026000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000003400000000cargo-c-0.10.3~git0.ee7d7ef/example-workspace/api-b07070100000027000081A400000000000000000000000166A873C100000125000000000000000000000000000000000000003F00000000cargo-c-0.10.3~git0.ee7d7ef/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 } 07070100000028000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000003800000000cargo-c-0.10.3~git0.ee7d7ef/example-workspace/api-b/src07070100000029000081A400000000000000000000000166A873C1000000D6000000000000000000000000000000000000003F00000000cargo-c-0.10.3~git0.ee7d7ef/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); } } 0707010000002A000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000002000000000cargo-c-0.10.3~git0.ee7d7ef/src0707010000002B000041ED00000000000000000000000266A873C100000000000000000000000000000000000000000000002400000000cargo-c-0.10.3~git0.ee7d7ef/src/bin0707010000002C000081A400000000000000000000000166A873C100000945000000000000000000000000000000000000002C00000000cargo-c-0.10.3~git0.ee7d7ef/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 = clap::command!() .dont_collapse_args_in_usage(true) .allow_external_subcommands(true) .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) = match args.subcommand() { Some(("capi", args)) => match args.subcommand() { Some(("build", args)) => ("build", args, "dev"), Some(("test", args)) => ("test", args, "dev"), Some(("install", args)) => ("install", args, "release"), 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)?; if cmd == "install" { cinstall(&ws, &packages)?; } else if cmd == "test" { ctest(&ws, &config, subcommand_args, &packages, compile_opts)?; } Ok(()) } 0707010000002D000081A400000000000000000000000166A873C1000004C5000000000000000000000000000000000000002E00000000cargo-c-0.10.3~git0.ee7d7ef/src/bin/cbuild.rsuse cargo::util::command_prelude::ArgMatchesExt; use cargo::CliResult; use cargo::GlobalContext; use cargo_c::build::*; use cargo_c::cli::run_cargo_fallback; use cargo_c::cli::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 = clap::command!() .dont_collapse_args_in_usage(true) .allow_external_subcommands(true) .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")?; Ok(()) } 0707010000002E000081A400000000000000000000000166A873C10000053B000000000000000000000000000000000000003000000000cargo-c-0.10.3~git0.ee7d7ef/src/bin/cinstall.rsuse cargo::util::command_prelude::ArgMatchesExt; use cargo::CliResult; use cargo::GlobalContext; use cargo_c::build::cbuild; use cargo_c::cli::run_cargo_fallback; use cargo_c::cli::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 = clap::command!() .dont_collapse_args_in_usage(true) .allow_external_subcommands(true) .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")?; cinstall(&ws, &packages)?; Ok(()) } 0707010000002F000081A400000000000000000000000166A873C1000004BB000000000000000000000000000000000000002D00000000cargo-c-0.10.3~git0.ee7d7ef/src/bin/ctest.rsuse cargo::util::command_prelude::*; use cargo_c::build::*; use cargo_c::cli::run_cargo_fallback; use cargo_c::cli::subcommand_test; use cargo_c::config::*; fn main() -> CliResult { let mut config = GlobalContext::default()?; let subcommand = subcommand_test("ctest"); let mut app = clap::command!() .dont_collapse_args_in_usage(true) .allow_external_subcommands(true) .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")?; ctest(&ws, &config, subcommand_args, &packages, compile_opts) } 07070100000030000081A400000000000000000000000166A873C10000A9BB000000000000000000000000000000000000002900000000cargo-c-0.10.3~git0.ee7d7ef/src/build.rsuse std::collections::HashMap; use std::io::{BufRead, BufReader, Read, Write}; 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, CompileMode, ProfileChecking}; use cargo::util::interning::InternedString; use cargo::{CliResult, GlobalContext}; use anyhow::Context as _; use cargo_util::paths::{copy, create, create_dir_all, open, read, read_bytes, write}; 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: &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); 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")?; let path = &format!("PKG_CONFIG_PATH=\"{}\"", root_output.display()); shell.verbose(move |s| s.note(path))?; 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, libkinds: &[&str], capi_config: &CApiConfig, ) -> anyhow::Result<()> { use cargo::core::compiler::CrateType; let manifest = pkg.manifest_mut(); let targets = manifest.targets_mut(); let kinds: Vec<_> = libkinds .iter() .map(|&kind| match kind { "staticlib" => CrateType::Staticlib, "cdylib" => CrateType::Cdylib, _ => unreachable!(), }) .collect(); for target in targets.iter_mut() { if target.is_lib() { target.set_kind(TargetKind::Lib(kinds.clone())); 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<()> { let os = &target.os; let env = &target.env; if os == "windows" && env == "msvc" { ws.gctx() .shell() .status("Building", ".def file using dumpbin")?; let txt_path = targetdir.join(format!("{name}.txt")); let target_str = format!("{}-pc-windows-msvc", &target.arch); let mut dumpbin = match cc::windows_registry::find(&target_str, "dumpbin.exe") { Some(command) => command, None => std::process::Command::new("dumpbin"), }; dumpbin .arg("/EXPORTS") .arg(targetdir.join(format!("{}.dll", name.replace('-', "_")))); dumpbin.arg(format!("/OUT:{}", txt_path.to_str().unwrap())); let out = dumpbin.output()?; if out.status.success() { let txt_file = open(txt_path)?; let buf_reader = BufReader::new(txt_file); let mut def_file = create(targetdir.join(format!("{name}.def")))?; writeln!(def_file, "EXPORTS")?; // The Rust loop below is analogue to the following loop. // for /f "skip=19 tokens=4" %A in (file.txt) do echo %A > file.def // The most recent versions of dumpbin adds three lines of copyright // information before the relevant content. // If the "/OUT:file.txt" dumpbin's option is used, the three // copyright lines are added to the shell, so the txt file // contains three lines less. // The Rust loop first skips 16 lines and then, for each line, // deletes all the characters up to the fourth space included // (skip=16 tokens=4) for line in buf_reader .lines() .skip(16) .take_while(|l| !l.as_ref().unwrap().is_empty()) .map(|l| { l.unwrap() .as_str() .split_whitespace() .nth(3) .unwrap() .to_string() }) { writeln!(def_file, "\t{line}")?; } Ok(()) } else { Err(anyhow::anyhow!("Command failed {:?}", dumpbin)) } } else { Ok(()) } } /// Build import library for windows-gnu fn build_implib_file( ws: &Workspace, name: &str, target: &target::Target, targetdir: &Path, dlltool: &Path, ) -> anyhow::Result<()> { let os = &target.os; let env = &target.env; if os == "windows" { let arch = &target.arch; if env == "gnu" { ws.gctx() .shell() .status("Building", "implib using dlltool")?; let binutils_arch = match arch.as_str() { "x86_64" => "i386:x86-64", "x86" => "i386", "aarch64" => "arm64", _ => unimplemented!("Windows support for {} is not implemented yet.", arch), }; let mut dlltool_command = std::process::Command::new(dlltool.to_str().unwrap_or("dlltool")); dlltool_command.arg("-m").arg(binutils_arch); dlltool_command.arg("-D").arg(format!("{name}.dll")); dlltool_command .arg("-l") .arg(targetdir.join(format!("{name}.dll.a"))); dlltool_command .arg("-d") .arg(targetdir.join(format!("{name}.def"))); let out = dlltool_command.output()?; if out.status.success() { Ok(()) } else { Err(anyhow::anyhow!("Command failed {:?}", dlltool_command)) } } else { ws.gctx().shell().status("Building", "implib using lib")?; let target_str = format!("{}-pc-windows-msvc", &target.arch); let mut lib = match cc::windows_registry::find(&target_str, "lib.exe") { Some(command) => command, None => std::process::Command::new("lib"), }; let lib_arch = match arch.as_str() { "x86_64" => "X64", "x86" => "IX86", _ => unimplemented!("Windows support for {} is not implemented yet.", arch), }; lib.arg(format!( "/DEF:{}", targetdir.join(format!("{name}.def")).display() )); lib.arg(format!("/MACHINE:{lib_arch}")); lib.arg(format!("/NAME:{name}.dll")); lib.arg(format!( "/OUT:{}", targetdir.join(format!("{name}.dll.lib")).display() )); let out = lib.output()?; if out.status.success() { Ok(()) } else { Err(anyhow::anyhow!("Command failed {:?}", lib)) } } } else { Ok(()) } } #[derive(Debug)] struct FingerPrint { id: PackageId, root_output: PathBuf, build_targets: BuildTargets, install_paths: InstallPaths, static_libs: String, } #[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, ) -> Self { 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(), } } fn hash(&self) -> anyhow::Result<Option<String>> { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; let mut hasher = DefaultHasher::new(); 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)] pub struct CApiConfig { pub header: HeaderCApiConfig, pub pkg_config: PkgConfigCApiConfig, pub library: LibraryCApiConfig, pub install: InstallCApiConfig, } #[derive(Debug)] pub struct HeaderCApiConfig { pub name: String, pub subdirectory: String, pub generation: bool, pub enabled: bool, } #[derive(Debug)] 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)] pub enum VersionSuffix { Major, MajorMinor, MajorMinorPatch, } #[derive(Debug)] 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)] pub struct InstallCApiConfig { pub include: Vec<InstallTarget>, pub data: Vec<InstallTarget>, } #[derive(Debug)] pub enum InstallTarget { Asset(InstallTargetPaths), Generated(InstallTargetPaths), } #[derive(Clone, Debug)] 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))?, } } else { HeaderCApiConfig { name: String::from(name), subdirectory: String::from(name), generation: true, enabled: 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_mode: CompileMode, ) -> anyhow::Result<CompileOptions> { use cargo::core::compiler::CompileKind; let mut compile_opts = args.compile_options(gctx, compile_mode, 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, 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| vec!["-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, libkinds: &[&str], 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, libkinds, &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, libkinds, &capi_config, args.get_flag("meson"), )?; let finger_print = FingerPrint::new(&id, root_output, &build_targets, &install_paths); Ok(CPackage { version, root_path, capi_config, build_targets, install_paths, finger_print, }) } } pub fn cbuild( ws: &mut Workspace, config: &GlobalContext, args: &ArgMatches, default_profile: &str, ) -> anyhow::Result<(Vec<CPackage>, CompileOptions)> { let rustc = config.load_global_rustc(Some(ws))?; let targets = args.targets()?; let (target, is_target_overridden) = match targets.len() { 0 => (rustc.host.to_string(), false), 1 => (targets[0].to_string(), true), _ => { anyhow::bail!("Multiple targets not supported yet"); } }; let rustc_target = target::Target::new(Some(&target), is_target_overridden)?; let default_kind = || match (rustc_target.os.as_str(), rustc_target.env.as_str()) { ("none", _) | (_, "musl") => vec!["staticlib"], _ => vec!["staticlib", "cdylib"], }; let libkinds = args .get_many::<String>("library-type") .map_or_else(default_kind, |v| v.map(String::as_str).collect::<Vec<_>>()); let only_staticlib = !libkinds.contains(&"cdylib"); let only_cdylib = !libkinds.contains(&"staticlib"); let profile = args.get_profile_name(config, default_profile, ProfileChecking::Custom)?; let profiles = Profiles::new(ws, profile)?; let mut compile_opts = compile_options(ws, config, args, profile, CompileMode::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 capi_feature = InternedString::new("capi"); 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(); for m in ws.members_mut().filter(|m| { m.library().is_some() && m.summary().features().contains_key(&capi_feature) && requested.contains(&m.package_id()) }) { let cpkg = CPackage::from_package(m, args, &libkinds, &rustc_target, &root_output)?; pristine = pristine || cpkg.finger_print.load_previous().is_err(); members.push(cpkg); } if pristine { // If the cache is somehow missing force a full rebuild; compile_opts.build_config.force_rebuild = true; } 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 the hash value does not match. if new_build && !cpkg.finger_print.is_valid() { let name = &cpkg.capi_config.library.name; let static_libs = if only_cdylib { "".to_string() } else { exec.link_line .lock() .unwrap() .values() .next() .unwrap() .to_string() }; 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 only_staticlib { pc.add_lib(&static_libs); } pc.add_lib_private(&static_libs); build_pc_files(ws, &capi_config.pkg_config.filename, &root_output, &pc)?; if !only_staticlib && capi_config.library.import_library { let lib_name = name; build_def_file(ws, lib_name, &rustc_target, &root_output)?; let mut dlltool = std::env::var_os("DLLTOOL") .map(PathBuf::from) .unwrap_or_else(|| PathBuf::from("dlltool")); // dlltool argument overwrites environment var if args.contains_id("dlltool") { dlltool = args .get_one::<PathBuf>("dlltool") .map(PathBuf::from) .unwrap(); } build_implib_file(ws, lib_name, &rustc_target, &root_output, &dlltool)?; } if capi_config.header.enabled { let header_name = &capi_config.header.name; if capi_config.header.generation { build_include_file( ws, header_name, &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, &libkinds, 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)?; } } 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; } } Ok((members, compile_opts)) } pub fn ctest( ws: &Workspace, config: &GlobalContext, args: &ArgMatches, packages: &[CPackage], mut compile_opts: CompileOptions, ) -> CliResult { compile_opts.build_config.requested_profile = args.get_profile_name(config, "test", ProfileChecking::Custom)?; compile_opts.build_config.mode = CompileMode::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"); } } 07070100000031000081A400000000000000000000000166A873C1000018B7000000000000000000000000000000000000003100000000cargo-c-0.10.3~git0.ee7d7ef/src/build_targets.rsuse std::ffi::OsString; use std::path::{Path, PathBuf}; use crate::build::{CApiConfig, InstallTarget}; 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, libkinds: &[&str], 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 { let mut header_name = PathBuf::from(&capi_config.header.name); header_name.set_extension("h"); Some(targetdir.join(&header_name)) } else { None }; let lib_name = name; let os = &target.os; let env = &target.env; let (shared_lib, static_lib, impl_lib, debug_info, def) = match (os.as_str(), env.as_str()) { ("none", _) | ("linux", _) | ("freebsd", _) | ("dragonfly", _) | ("netbsd", _) | ("android", _) | ("haiku", _) | ("illumos", _) | ("emscripten", _) => { 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", env) => { let static_lib = if env == "msvc" { targetdir.join(format!("{lib_name}.lib")) } else { targetdir.join(format!("lib{lib_name}.a")) }; let shared_lib = targetdir.join(format!("{lib_name}.dll")); let impl_lib = if env == "msvc" { targetdir.join(format!("{lib_name}.dll.lib")) } else { targetdir.join(format!("{lib_name}.dll.a")) }; let def = targetdir.join(format!("{lib_name}.def")); let pdb = if env == "msvc" { Some(targetdir.join(format!("{lib_name}.pdb"))) } else { None }; (shared_lib, static_lib, Some(impl_lib), pdb, Some(def)) } _ => unimplemented!("The target {}-{} is not supported yet", os, env), }; let static_lib = if libkinds.contains(&"staticlib") { Some(static_lib) } else { None }; // Bare metal does not support shared objects let shared_lib = if libkinds.contains(&"cdylib") && os.as_str() != "none" { Some(shared_lib) } else { None }; Ok(BuildTargets { pc, include, static_lib, shared_lib, impl_lib, debug_info, 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> { if self.shared_lib.is_some() && self.use_meson_naming_convention { Some(format!("lib{}.dll", self.name).into()) } else { Some(self.shared_lib.as_ref()?.file_name().unwrap().to_owned()) } } } 07070100000032000081A400000000000000000000000166A873C1000020B9000000000000000000000000000000000000002700000000cargo-c-0.10.3~git0.ee7d7ef/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::{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. 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, } 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_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( multi_opt( "library-type", "LIBRARY-TYPE", "Build only a type of library", ) .global(true) .ignore_case(true) .value_parser(["cdylib", "staticlib"]), ) .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( multi_opt( "library-type", "LIBRARY-TYPE", "Build only a type of library", ) .global(true) .ignore_case(true) .value_parser(["cdylib", "staticlib"]), ) .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() .trailing_var_arg(true) .name(name) .about("Test the crate C-API") .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)) } 07070100000033000081A400000000000000000000000166A873C1000004EE000000000000000000000000000000000000002A00000000cargo-c-0.10.3~git0.ee7d7ef/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(()) } 07070100000034000081A400000000000000000000000166A873C100002FFF000000000000000000000000000000000000002B00000000cargo-c-0.10.3~git0.ee7d7ef/src/install.rsuse clap::ArgMatches; use std::path::{Component, Path, PathBuf}; use cargo::core::Workspace; use cargo_util::paths::{copy, create_dir_all}; use crate::build::*; use crate::build_targets::BuildTargets; use crate::target::Target; 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", _) | ("emscripten", _) => 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{}.{}.dylib", lib_name, main_version); 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, capi_config: &CApiConfig, shared_lib: &Path, install_path_lib: &Path, ) -> anyhow::Result<()> { if capi_config.library.versioning { copy(shared_lib, install_path_lib.join(&self.with_full_ver))?; self.links(install_path_lib); } else { copy(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( &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(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(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(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(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(shared_lib, install_path_bin.join(lib_name))?; } else { // We assume they are plugins, install them in the custom libdir path copy(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 = if build_targets.use_meson_naming_convention { format!("{}.lib", build_targets.name).into() } else { impl_lib.file_name().unwrap().to_owned() }; copy(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(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(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, } } } 07070100000035000081A400000000000000000000000166A873C10000007C000000000000000000000000000000000000002700000000cargo-c-0.10.3~git0.ee7d7ef/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; 07070100000036000081A400000000000000000000000166A873C100002295000000000000000000000000000000000000003200000000cargo-c-0.10.3~git0.ee7d7ef/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 separator = ""; let out = path .as_ref() .components() .map(|p| match p { Component::RootDir => { separator = "/"; String::new() } Component::Prefix(_) => p.as_os_str().to_string_lossy().to_string(), _ => { let c = format!("{}{}", separator, p.as_os_str().to_string_lossy()); separator = "/"; c } }) .collect::<String>(); if out.is_empty() { "/".to_string() } else { out } } #[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{}", libdir.display()), 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) { let mut includedir = PathBuf::from("${prefix}"); includedir.push(suffix); pc.includedir = includedir; } else { pc.includedir.clone_from(&install_paths.includedir); } } if args.contains_id("libdir") { if let Ok(suffix) = install_paths.libdir.strip_prefix(&pc.prefix) { let mut libdir = PathBuf::from("${prefix}"); libdir.push(suffix); pc.libdir = libdir; } 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 { let mut base = format!( "prefix={} exec_prefix={} libdir={} includedir={} Name: {} Description: {} Version: {} Libs: {} Cflags: {}", canonicalize(&self.prefix), canonicalize(&self.exec_prefix), canonicalize(&self.libdir), canonicalize(&self.includedir), self.name, // avoid endlines self.description.replace('\n', " "), self.version, self.libs.join(" "), self.cflags.join(" "), ); if !self.libs_private.is_empty() { base.push_str( " Libs.private: ", ); base.push_str(&self.libs_private.join(" ")); } if !self.requires.is_empty() { base.push_str( " Requires: ", ); base.push_str(&self.requires.join(", ")); } if !self.requires_private.is_empty() { base.push_str( " Requires.private: ", ); base.push_str(&self.requires_private.join(", ")); } /* Conflicts: Libs.private: ).to_owned() */ base.push('\n'); base } } #[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, }, 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"); println!("{:?}\n{}", pkg, pkg.render()); } } 07070100000037000081A400000000000000000000000166A873C10000174C000000000000000000000000000000000000002A00000000cargo-c-0.10.3~git0.ee7d7ef/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" { 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!!!265 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor